kaspad/domain/migrate.go
Michael Sutton 1c9bb54cc2
Crucial fix for the UTXO difference mechanism (#2114)
* Illustrate the bug through prints

* Change consensus API to a single ResolveVirtual call

* nil changeset is not expected when err=nil

* Fixes a deep bug in the resolve virtual process

* Be more defensive at resolving virtual when adding a block

* When finally resolved, set virtual parents properly

* Return nil changeset when nothing happened

* Make sure the block at the split point is reversed to new chain as well

* bump to version 0.12.4

* Avoid locking consensus twice in the common case of adding block with updateVirtual=true

* check err

* Parents must be picked first before set as virtual parents

* Keep the flag for tracking virtual state, since tip sorting perf is high with many tips

* Improve and clarify resolve virtual tests

* Addressing minor review comments

* Fixed a bug in the reported virtual changeset, and modified the test to verify it

* Addressing review comments
2022-07-15 12:35:20 +03:00

233 lines
5.4 KiB
Go

package domain
import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"math"
)
func (d *domain) migrate() error {
log.Infof("Starting migration")
pruningPoint, err := d.Consensus().PruningPoint()
if err != nil {
return err
}
log.Infof("Current pruning point: %s", pruningPoint)
if d.consensusConfig.Params.GenesisHash.Equal(pruningPoint) {
err = d.initStagingConsensus(d.consensusConfig)
if err != nil {
return err
}
} else {
err = d.InitStagingConsensusWithoutGenesis()
if err != nil {
return err
}
err = syncConsensuses(d.Consensus(), d.StagingConsensus())
if err != nil {
return err
}
}
err = d.CommitStagingConsensus()
if err != nil {
return err
}
log.Info("Done migrating")
return nil
}
func syncConsensuses(syncer, syncee externalapi.Consensus) error {
pruningPointProof, err := syncer.BuildPruningPointProof()
if err != nil {
return err
}
err = syncee.ApplyPruningPointProof(pruningPointProof)
if err != nil {
return err
}
pruningPointHeaders, err := syncer.PruningPointHeaders()
if err != nil {
return err
}
err = syncee.ImportPruningPoints(pruningPointHeaders)
if err != nil {
return err
}
pruningPointAndItsAnticone, err := syncer.PruningPointAndItsAnticone()
if err != nil {
return err
}
for _, blockHash := range pruningPointAndItsAnticone {
block, err := syncer.GetBlock(blockHash)
if err != nil {
return err
}
blockDAAWindowHashes, err := syncer.BlockDAAWindowHashes(blockHash)
if err != nil {
return err
}
ghostdagDataBlockHashes, err := syncer.TrustedBlockAssociatedGHOSTDAGDataBlockHashes(blockHash)
if err != nil {
return err
}
blockWithTrustedData := &externalapi.BlockWithTrustedData{
Block: block,
DAAWindow: make([]*externalapi.TrustedDataDataDAAHeader, 0, len(blockDAAWindowHashes)),
GHOSTDAGData: make([]*externalapi.BlockGHOSTDAGDataHashPair, 0, len(ghostdagDataBlockHashes)),
}
for i, daaBlockHash := range blockDAAWindowHashes {
trustedDataDataDAAHeader, err := syncer.TrustedDataDataDAAHeader(blockHash, daaBlockHash, uint64(i))
if err != nil {
return err
}
blockWithTrustedData.DAAWindow = append(blockWithTrustedData.DAAWindow, trustedDataDataDAAHeader)
}
for _, ghostdagDataBlockHash := range ghostdagDataBlockHashes {
data, err := syncer.TrustedGHOSTDAGData(ghostdagDataBlockHash)
if err != nil {
return err
}
blockWithTrustedData.GHOSTDAGData = append(blockWithTrustedData.GHOSTDAGData, &externalapi.BlockGHOSTDAGDataHashPair{
Hash: ghostdagDataBlockHash,
GHOSTDAGData: data,
})
}
err = syncee.ValidateAndInsertBlockWithTrustedData(blockWithTrustedData, false)
if err != nil {
return err
}
}
syncerVirtualSelectedParent, err := syncer.GetVirtualSelectedParent()
if err != nil {
return err
}
pruningPoint, err := syncer.PruningPoint()
if err != nil {
return err
}
missingBlocks, _, err := syncer.GetHashesBetween(pruningPoint, syncerVirtualSelectedParent, math.MaxUint64)
if err != nil {
return err
}
syncerTips, err := syncer.Tips()
if err != nil {
return err
}
for _, tip := range syncerTips {
if tip.Equal(syncerVirtualSelectedParent) {
continue
}
anticone, err := syncer.GetAnticone(syncerVirtualSelectedParent, tip, 0)
if err != nil {
return err
}
missingBlocks = append(missingBlocks, anticone...)
}
percents := 0
for i, blocksHash := range missingBlocks {
blockInfo, err := syncee.GetBlockInfo(blocksHash)
if err != nil {
return err
}
if blockInfo.Exists {
continue
}
block, err := syncer.GetBlock(blocksHash)
if err != nil {
return err
}
err = syncee.ValidateAndInsertBlock(block, false)
if err != nil {
return err
}
newPercents := 100 * i / len(missingBlocks)
if newPercents > percents {
percents = newPercents
log.Infof("Processed %d%% of the blocks", 100*i/len(missingBlocks))
}
}
var fromOutpoint *externalapi.DomainOutpoint
const step = 100_000
for {
outpointAndUTXOEntryPairs, err := syncer.GetPruningPointUTXOs(pruningPoint, fromOutpoint, step)
if err != nil {
return err
}
fromOutpoint = outpointAndUTXOEntryPairs[len(outpointAndUTXOEntryPairs)-1].Outpoint
err = syncee.AppendImportedPruningPointUTXOs(outpointAndUTXOEntryPairs)
if err != nil {
return err
}
if len(outpointAndUTXOEntryPairs) < step {
break
}
}
// Check that ValidateAndInsertImportedPruningPoint works given the right arguments.
err = syncee.ValidateAndInsertImportedPruningPoint(pruningPoint)
if err != nil {
return err
}
emptyCoinbase := &externalapi.DomainCoinbaseData{
ScriptPublicKey: &externalapi.ScriptPublicKey{
Script: nil,
Version: 0,
},
}
// Check that we can build a block just after importing the pruning point.
_, err = syncee.BuildBlock(emptyCoinbase, nil)
if err != nil {
return err
}
estimatedVirtualDAAScoreTarget, err := syncer.GetVirtualDAAScore()
if err != nil {
return err
}
err = syncer.ResolveVirtual(func(virtualDAAScoreStart uint64, virtualDAAScore uint64) {
if estimatedVirtualDAAScoreTarget-virtualDAAScoreStart <= 0 {
percents = 100
} else {
percents = int(float64(virtualDAAScore-virtualDAAScoreStart) / float64(estimatedVirtualDAAScoreTarget-virtualDAAScoreStart) * 100)
}
log.Infof("Resolving virtual. Estimated progress: %d%%", percents)
})
if err != nil {
return err
}
log.Infof("Resolved virtual")
return nil
}