Merge branch 'dev' into fix-missing-mempool-utxoentries

This commit is contained in:
Ori Newman 2022-07-26 21:42:09 +03:00 committed by GitHub
commit b6427da480
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 558 additions and 194 deletions

View File

@ -686,37 +686,28 @@ func (flow *handleIBDFlow) banIfBlockIsHeaderOnly(block *externalapi.DomainBlock
} }
func (flow *handleIBDFlow) resolveVirtual(estimatedVirtualDAAScoreTarget uint64) error { func (flow *handleIBDFlow) resolveVirtual(estimatedVirtualDAAScoreTarget uint64) error {
virtualDAAScoreStart, err := flow.Domain().Consensus().GetVirtualDAAScore() err := flow.Domain().Consensus().ResolveVirtual(func(virtualDAAScoreStart uint64, virtualDAAScore uint64) {
var percents int
if estimatedVirtualDAAScoreTarget-virtualDAAScoreStart <= 0 {
percents = 100
} else {
percents = int(float64(virtualDAAScore-virtualDAAScoreStart) / float64(estimatedVirtualDAAScoreTarget-virtualDAAScoreStart) * 100)
}
if percents < 0 {
percents = 0
} else if percents > 100 {
percents = 100
}
log.Infof("Resolving virtual. Estimated progress: %d%%", percents)
})
if err != nil { if err != nil {
return err return err
} }
for i := 0; ; i++ { log.Infof("Resolved virtual")
if i%10 == 0 { err = flow.OnNewBlockTemplate()
virtualDAAScore, err := flow.Domain().Consensus().GetVirtualDAAScore() if err != nil {
if err != nil { return err
return err
}
var percents int
if estimatedVirtualDAAScoreTarget-virtualDAAScoreStart <= 0 {
percents = 100
} else {
percents = int(float64(virtualDAAScore-virtualDAAScoreStart) / float64(estimatedVirtualDAAScoreTarget-virtualDAAScoreStart) * 100)
}
log.Infof("Resolving virtual. Estimated progress: %d%%", percents)
}
isCompletelyResolved, err := flow.Domain().Consensus().ResolveVirtual()
if err != nil {
return err
}
if isCompletelyResolved {
log.Infof("Resolved virtual")
err = flow.OnNewBlockTemplate()
if err != nil {
return err
}
return nil
}
} }
return nil
} }

View File

@ -1,3 +1,9 @@
Kaspad v0.12.4 - 2022-07-17
===========================
* Crucial fix for the UTXO difference mechanism (#2114)
* Implement multi-layer auto-compound (#2115)
Kaspad v0.12.3 - 2022-06-29 Kaspad v0.12.3 - 2022-06-29
=========================== ===========================

View File

@ -111,7 +111,8 @@ type startDaemonConfig struct {
KeysFile string `long:"keys-file" short:"f" description:"Keys file location (default: ~/.kaspawallet/keys.json (*nix), %USERPROFILE%\\AppData\\Local\\Kaspawallet\\key.json (Windows))"` KeysFile string `long:"keys-file" short:"f" description:"Keys file location (default: ~/.kaspawallet/keys.json (*nix), %USERPROFILE%\\AppData\\Local\\Kaspawallet\\key.json (Windows))"`
Password string `long:"password" short:"p" description:"Wallet password"` Password string `long:"password" short:"p" description:"Wallet password"`
RPCServer string `long:"rpcserver" short:"s" description:"RPC server to connect to"` RPCServer string `long:"rpcserver" short:"s" description:"RPC server to connect to"`
Listen string `short:"l" long:"listen" description:"Address to listen on (default: 0.0.0.0:8082)"` Listen string `long:"listen" short:"l" description:"Address to listen on (default: 0.0.0.0:8082)"`
Timeout uint32 `long:"wait-timeout" short:"w" description:"Waiting timeout for RPC calls, seconds (default: 30 s)"`
Profile string `long:"profile" description:"Enable HTTP profiling on given port -- NOTE port must be between 1024 and 65536"` Profile string `long:"profile" description:"Enable HTTP profiling on given port -- NOTE port must be between 1024 and 65536"`
config.NetworkFlags config.NetworkFlags
} }
@ -181,7 +182,6 @@ func parseCommandLine() (subCommand string, config interface{}) {
parser.AddCommand(startDaemonSubCmd, "Start the wallet daemon", "Start the wallet daemon", startDaemonConf) parser.AddCommand(startDaemonSubCmd, "Start the wallet daemon", "Start the wallet daemon", startDaemonConf)
_, err := parser.Parse() _, err := parser.Parse()
if err != nil { if err != nil {
var flagsErr *flags.Error var flagsErr *flags.Error
if ok := errors.As(err, &flagsErr); ok && flagsErr.Type == flags.ErrHelp { if ok := errors.As(err, &flagsErr); ok && flagsErr.Type == flags.ErrHelp {

View File

@ -3,20 +3,22 @@ package server
import ( import (
"context" "context"
"fmt" "fmt"
"time"
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb" "github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet" "github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
"github.com/kaspanet/kaspad/domain/consensus/utils/constants" "github.com/kaspanet/kaspad/domain/consensus/utils/constants"
"github.com/kaspanet/kaspad/util" "github.com/kaspanet/kaspad/util"
"github.com/pkg/errors" "github.com/pkg/errors"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
"time"
) )
// TODO: Implement a better fee estimation mechanism // TODO: Implement a better fee estimation mechanism
const feePerInput = 10000 const feePerInput = 10000
func (s *server) CreateUnsignedTransactions(_ context.Context, request *pb.CreateUnsignedTransactionsRequest) ( func (s *server) CreateUnsignedTransactions(_ context.Context, request *pb.CreateUnsignedTransactionsRequest) (
*pb.CreateUnsignedTransactionsResponse, error) { *pb.CreateUnsignedTransactionsResponse, error,
) {
s.lock.Lock() s.lock.Lock()
defer s.lock.Unlock() defer s.lock.Unlock()
@ -33,12 +35,14 @@ func (s *server) createUnsignedTransactions(address string, amount uint64, fromA
return nil, errors.Errorf("wallet daemon is not synced yet, %s", s.formatSyncStateReport()) return nil, errors.Errorf("wallet daemon is not synced yet, %s", s.formatSyncStateReport())
} }
err := s.refreshUTXOs() // make sure address string is correct before proceeding to a
// potentially long UTXO refreshment operation
toAddress, err := util.DecodeAddress(address, s.params.Prefix)
if err != nil { if err != nil {
return nil, err return nil, err
} }
toAddress, err := util.DecodeAddress(address, s.params.Prefix) err = s.refreshUTXOs()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -87,8 +91,8 @@ func (s *server) createUnsignedTransactions(address string, amount uint64, fromA
} }
func (s *server) selectUTXOs(spendAmount uint64, feePerInput uint64, fromAddresses []*walletAddress) ( func (s *server) selectUTXOs(spendAmount uint64, feePerInput uint64, fromAddresses []*walletAddress) (
selectedUTXOs []*libkaspawallet.UTXO, changeSompi uint64, err error) { selectedUTXOs []*libkaspawallet.UTXO, changeSompi uint64, err error,
) {
selectedUTXOs = []*libkaspawallet.UTXO{} selectedUTXOs = []*libkaspawallet.UTXO{}
totalValue := uint64(0) totalValue := uint64(0)

View File

@ -1,15 +1,26 @@
package server package server
import ( import (
"time"
"github.com/kaspanet/kaspad/domain/dagconfig" "github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/infrastructure/network/rpcclient" "github.com/kaspanet/kaspad/infrastructure/network/rpcclient"
) )
func connectToRPC(params *dagconfig.Params, rpcServer string) (*rpcclient.RPCClient, error) { func connectToRPC(params *dagconfig.Params, rpcServer string, timeout uint32) (*rpcclient.RPCClient, error) {
rpcAddress, err := params.NormalizeRPCServerAddress(rpcServer) rpcAddress, err := params.NormalizeRPCServerAddress(rpcServer)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return rpcclient.NewRPCClient(rpcAddress) rpcClient, err := rpcclient.NewRPCClient(rpcAddress)
if err != nil {
return nil, err
}
if timeout != 0 {
rpcClient.SetTimeout(time.Duration(timeout) * time.Second)
}
return rpcClient, err
} }

View File

@ -45,7 +45,7 @@ type server struct {
} }
// Start starts the kaspawalletd server // Start starts the kaspawalletd server
func Start(params *dagconfig.Params, listen, rpcServer string, keysFilePath string, profile string) error { func Start(params *dagconfig.Params, listen, rpcServer string, keysFilePath string, profile string, timeout uint32) error {
initLog(defaultLogFile, defaultErrLogFile) initLog(defaultLogFile, defaultErrLogFile)
defer panics.HandlePanic(log, "MAIN", nil) defer panics.HandlePanic(log, "MAIN", nil)
@ -62,7 +62,7 @@ func Start(params *dagconfig.Params, listen, rpcServer string, keysFilePath stri
log.Infof("Listening to TCP on %s", listen) log.Infof("Listening to TCP on %s", listen)
log.Infof("Connecting to a node at %s...", rpcServer) log.Infof("Connecting to a node at %s...", rpcServer)
rpcClient, err := connectToRPC(params, rpcServer) rpcClient, err := connectToRPC(params, rpcServer, timeout)
if err != nil { if err != nil {
return (errors.Wrapf(err, "Error connecting to RPC server %s", rpcServer)) return (errors.Wrapf(err, "Error connecting to RPC server %s", rpcServer))
} }

View File

@ -27,18 +27,10 @@ func (s *server) maybeAutoCompoundTransaction(transactionBytes []byte, toAddress
return nil, err return nil, err
} }
splitTransactions, err := s.maybeSplitTransaction(transaction, changeAddress) splitTransactions, err := s.maybeSplitAndMergeTransaction(transaction, toAddress, changeAddress, changeWalletAddress)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(splitTransactions) > 1 {
mergeTransaction, err := s.mergeTransaction(splitTransactions, transaction, toAddress, changeAddress, changeWalletAddress)
if err != nil {
return nil, err
}
splitTransactions = append(splitTransactions, mergeTransaction)
}
splitTransactionsBytes := make([][]byte, len(splitTransactions)) splitTransactionsBytes := make([][]byte, len(splitTransactions))
for i, splitTransaction := range splitTransactions { for i, splitTransaction := range splitTransactions {
splitTransactionsBytes[i], err = serialization.SerializePartiallySignedTransaction(splitTransaction) splitTransactionsBytes[i], err = serialization.SerializePartiallySignedTransaction(splitTransaction)
@ -113,8 +105,8 @@ func (s *server) mergeTransaction(
return serialization.DeserializePartiallySignedTransaction(mergeTransactionBytes) return serialization.DeserializePartiallySignedTransaction(mergeTransactionBytes)
} }
func (s *server) maybeSplitTransaction(transaction *serialization.PartiallySignedTransaction, func (s *server) maybeSplitAndMergeTransaction(transaction *serialization.PartiallySignedTransaction, toAddress util.Address,
changeAddress util.Address) ([]*serialization.PartiallySignedTransaction, error) { changeAddress util.Address, changeWalletAddress *walletAddress) ([]*serialization.PartiallySignedTransaction, error) {
transactionMass, err := s.estimateMassAfterSignatures(transaction) transactionMass, err := s.estimateMassAfterSignatures(transaction)
if err != nil { if err != nil {
@ -141,6 +133,20 @@ func (s *server) maybeSplitTransaction(transaction *serialization.PartiallySigne
} }
} }
if len(splitTransactions) > 1 {
mergeTransaction, err := s.mergeTransaction(splitTransactions, transaction, toAddress, changeAddress, changeWalletAddress)
if err != nil {
return nil, err
}
// Recursion will be 2-3 iterations deep even in the rarest` cases, so considered safe..
splitMergeTransaction, err := s.maybeSplitAndMergeTransaction(mergeTransaction, toAddress, changeAddress, changeWalletAddress)
if err != nil {
return nil, err
}
splitTransactions = append(splitTransactions, splitMergeTransaction...)
}
return splitTransactions, nil return splitTransactions, nil
} }

View File

@ -3,5 +3,5 @@ package main
import "github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/server" import "github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/server"
func startDaemon(conf *startDaemonConfig) error { func startDaemon(conf *startDaemonConfig) error {
return server.Start(conf.NetParams(), conf.Listen, conf.RPCServer, conf.KeysFile, conf.Profile) return server.Start(conf.NetParams(), conf.Listen, conf.RPCServer, conf.KeysFile, conf.Profile, conf.Timeout)
} }

View File

@ -194,11 +194,33 @@ func (s *consensus) BuildBlockTemplate(coinbaseData *externalapi.DomainCoinbaseD
// ValidateAndInsertBlock validates the given block and, if valid, applies it // ValidateAndInsertBlock validates the given block and, if valid, applies it
// to the current state // to the current state
func (s *consensus) ValidateAndInsertBlock(block *externalapi.DomainBlock, shouldValidateAgainstUTXO bool) error { func (s *consensus) ValidateAndInsertBlock(block *externalapi.DomainBlock, updateVirtual bool) error {
if updateVirtual {
s.lock.Lock()
if s.virtualNotUpdated {
s.lock.Unlock()
err := s.ResolveVirtual(nil)
if err != nil {
return err
}
return s.validateAndInsertBlockWithLock(block, updateVirtual)
}
defer s.lock.Unlock()
_, err := s.validateAndInsertBlockNoLock(block, updateVirtual)
if err != nil {
return err
}
return nil
}
return s.validateAndInsertBlockWithLock(block, updateVirtual)
}
func (s *consensus) validateAndInsertBlockWithLock(block *externalapi.DomainBlock, updateVirtual bool) error {
s.lock.Lock() s.lock.Lock()
defer s.lock.Unlock() defer s.lock.Unlock()
_, err := s.validateAndInsertBlockNoLock(block, shouldValidateAgainstUTXO) _, err := s.validateAndInsertBlockNoLock(block, updateVirtual)
if err != nil { if err != nil {
return err return err
} }
@ -206,19 +228,6 @@ func (s *consensus) ValidateAndInsertBlock(block *externalapi.DomainBlock, shoul
} }
func (s *consensus) validateAndInsertBlockNoLock(block *externalapi.DomainBlock, updateVirtual bool) (*externalapi.VirtualChangeSet, error) { func (s *consensus) validateAndInsertBlockNoLock(block *externalapi.DomainBlock, updateVirtual bool) (*externalapi.VirtualChangeSet, error) {
// If virtual is in non-updated state, and the caller requests updating virtual -- then we must first
// resolve virtual so that the new block can be fully processed properly
if updateVirtual && s.virtualNotUpdated {
for s.virtualNotUpdated {
// We use 10000 << finality interval. See comment in `ResolveVirtual`.
// We give up responsiveness of consensus in this rare case.
_, err := s.resolveVirtualNoLock(10000) // Note `s.virtualNotUpdated` is updated within the call
if err != nil {
return nil, err
}
}
}
virtualChangeSet, blockStatus, err := s.blockProcessor.ValidateAndInsertBlock(block, updateVirtual) virtualChangeSet, blockStatus, err := s.blockProcessor.ValidateAndInsertBlock(block, updateVirtual)
if err != nil { if err != nil {
return nil, err return nil, err
@ -257,7 +266,7 @@ func (s *consensus) sendBlockAddedEvent(block *externalapi.DomainBlock, blockSta
} }
func (s *consensus) sendVirtualChangedEvent(virtualChangeSet *externalapi.VirtualChangeSet, wasVirtualUpdated bool) error { func (s *consensus) sendVirtualChangedEvent(virtualChangeSet *externalapi.VirtualChangeSet, wasVirtualUpdated bool) error {
if !wasVirtualUpdated || s.consensusEventsChan == nil { if !wasVirtualUpdated || s.consensusEventsChan == nil || virtualChangeSet == nil {
return nil return nil
} }
@ -888,41 +897,68 @@ func (s *consensus) PopulateMass(transaction *externalapi.DomainTransaction) {
s.transactionValidator.PopulateMass(transaction) s.transactionValidator.PopulateMass(transaction)
} }
func (s *consensus) ResolveVirtual() (bool, error) { func (s *consensus) ResolveVirtual(progressReportCallback func(uint64, uint64)) error {
virtualDAAScoreStart, err := s.GetVirtualDAAScore()
if err != nil {
return err
}
for i := 0; ; i++ {
if i%10 == 0 && progressReportCallback != nil {
virtualDAAScore, err := s.GetVirtualDAAScore()
if err != nil {
return err
}
progressReportCallback(virtualDAAScoreStart, virtualDAAScore)
}
// In order to prevent a situation that the consensus lock is held for too much time, we
// release the lock each time we resolve 100 blocks.
// Note: maxBlocksToResolve should be smaller than `params.FinalityDuration` in order to avoid a situation
// where UpdatePruningPointByVirtual skips a pruning point.
_, isCompletelyResolved, err := s.resolveVirtualChunkWithLock(100)
if err != nil {
return err
}
if isCompletelyResolved {
break
}
}
return nil
}
func (s *consensus) resolveVirtualChunkWithLock(maxBlocksToResolve uint64) (*externalapi.VirtualChangeSet, bool, error) {
s.lock.Lock() s.lock.Lock()
defer s.lock.Unlock() defer s.lock.Unlock()
// In order to prevent a situation that the consensus lock is held for too much time, we return s.resolveVirtualChunkNoLock(maxBlocksToResolve)
// release the lock each time resolve 100 blocks.
// Note: maxBlocksToResolve should be smaller than finality interval in order to avoid a situation
// where UpdatePruningPointByVirtual skips a pruning point.
return s.resolveVirtualNoLock(100)
} }
func (s *consensus) resolveVirtualNoLock(maxBlocksToResolve uint64) (bool, error) { func (s *consensus) resolveVirtualChunkNoLock(maxBlocksToResolve uint64) (*externalapi.VirtualChangeSet, bool, error) {
virtualChangeSet, isCompletelyResolved, err := s.consensusStateManager.ResolveVirtual(maxBlocksToResolve) virtualChangeSet, isCompletelyResolved, err := s.consensusStateManager.ResolveVirtual(maxBlocksToResolve)
if err != nil { if err != nil {
return false, err return nil, false, err
} }
s.virtualNotUpdated = !isCompletelyResolved s.virtualNotUpdated = !isCompletelyResolved
stagingArea := model.NewStagingArea() stagingArea := model.NewStagingArea()
err = s.pruningManager.UpdatePruningPointByVirtual(stagingArea) err = s.pruningManager.UpdatePruningPointByVirtual(stagingArea)
if err != nil { if err != nil {
return false, err return nil, false, err
} }
err = staging.CommitAllChanges(s.databaseContext, stagingArea) err = staging.CommitAllChanges(s.databaseContext, stagingArea)
if err != nil { if err != nil {
return false, err return nil, false, err
} }
err = s.sendVirtualChangedEvent(virtualChangeSet, true) err = s.sendVirtualChangedEvent(virtualChangeSet, true)
if err != nil { if err != nil {
return false, err return nil, false, err
} }
return isCompletelyResolved, nil return virtualChangeSet, isCompletelyResolved, nil
} }
func (s *consensus) BuildPruningPointProof() (*externalapi.PruningPointProof, error) { func (s *consensus) BuildPruningPointProof() (*externalapi.PruningPointProof, error) {

View File

@ -515,6 +515,7 @@ func (f *factory) NewConsensus(config *Config, db infrastructuredatabase.Databas
blocksWithTrustedDataDAAWindowStore: daaWindowStore, blocksWithTrustedDataDAAWindowStore: daaWindowStore,
consensusEventsChan: consensusEventsChan, consensusEventsChan: consensusEventsChan,
virtualNotUpdated: true,
} }
if isOldReachabilityInitialized { if isOldReachabilityInitialized {

View File

@ -589,18 +589,13 @@ func TestFinalityResolveVirtual(t *testing.T) {
} }
} }
for i := 0; ; i++ { err = tc.ResolveVirtual(nil)
isCompletelyResolved, err := tc.ResolveVirtual() if err != nil {
if err != nil { panic(err)
panic(err)
}
if isCompletelyResolved {
t.Log("Resolved virtual")
break
}
} }
t.Log("Resolved virtual")
sideChainTipGHOSTDAGData, err = tc.GHOSTDAGDataStore().Get(tc.DatabaseContext(), stagingArea, sideChainTipHash, false) sideChainTipGHOSTDAGData, err = tc.GHOSTDAGDataStore().Get(tc.DatabaseContext(), stagingArea, sideChainTipHash, false)
if err != nil { if err != nil {
panic(err) panic(err)

View File

@ -5,7 +5,7 @@ type Consensus interface {
Init(skipAddingGenesis bool) error Init(skipAddingGenesis bool) error
BuildBlock(coinbaseData *DomainCoinbaseData, transactions []*DomainTransaction) (*DomainBlock, error) BuildBlock(coinbaseData *DomainCoinbaseData, transactions []*DomainTransaction) (*DomainBlock, error)
BuildBlockTemplate(coinbaseData *DomainCoinbaseData, transactions []*DomainTransaction) (*DomainBlockTemplate, error) BuildBlockTemplate(coinbaseData *DomainCoinbaseData, transactions []*DomainTransaction) (*DomainBlockTemplate, error)
ValidateAndInsertBlock(block *DomainBlock, shouldValidateAgainstUTXO bool) error ValidateAndInsertBlock(block *DomainBlock, updateVirtual bool) error
ValidateAndInsertBlockWithTrustedData(block *BlockWithTrustedData, validateUTXO bool) error ValidateAndInsertBlockWithTrustedData(block *BlockWithTrustedData, validateUTXO bool) error
ValidateTransactionAndPopulateWithConsensusData(transaction *DomainTransaction) error ValidateTransactionAndPopulateWithConsensusData(transaction *DomainTransaction) error
ImportPruningPoints(pruningPoints []BlockHeader) error ImportPruningPoints(pruningPoints []BlockHeader) error
@ -48,7 +48,7 @@ type Consensus interface {
Anticone(blockHash *DomainHash) ([]*DomainHash, error) Anticone(blockHash *DomainHash) ([]*DomainHash, error)
EstimateNetworkHashesPerSecond(startHash *DomainHash, windowSize int) (uint64, error) EstimateNetworkHashesPerSecond(startHash *DomainHash, windowSize int) (uint64, error)
PopulateMass(transaction *DomainTransaction) PopulateMass(transaction *DomainTransaction)
ResolveVirtual() (bool, error) ResolveVirtual(progressReportCallback func(uint64, uint64)) error
BlockDAAWindowHashes(blockHash *DomainHash) ([]*DomainHash, error) BlockDAAWindowHashes(blockHash *DomainHash) ([]*DomainHash, error)
TrustedDataDataDAAHeader(trustedBlockHash, daaBlockHash *DomainHash, daaBlockWindowIndex uint64) (*TrustedDataDataDAAHeader, error) TrustedDataDataDAAHeader(trustedBlockHash, daaBlockHash *DomainHash, daaBlockWindowIndex uint64) (*TrustedDataDataDAAHeader, error)
TrustedBlockAssociatedGHOSTDAGDataBlockHashes(blockHash *DomainHash) ([]*DomainHash, error) TrustedBlockAssociatedGHOSTDAGDataBlockHashes(blockHash *DomainHash) ([]*DomainHash, error)

View File

@ -49,7 +49,7 @@ type TestConsensus interface {
*externalapi.VirtualChangeSet, error) *externalapi.VirtualChangeSet, error)
UpdatePruningPointByVirtual() error UpdatePruningPointByVirtual() error
ResolveVirtualWithMaxParam(maxBlocksToResolve uint64) (bool, error) ResolveVirtualWithMaxParam(maxBlocksToResolve uint64) (*externalapi.VirtualChangeSet, bool, error)
MineJSON(r io.Reader, blockType MineJSONBlockType) (tips []*externalapi.DomainHash, err error) MineJSON(r io.Reader, blockType MineJSONBlockType) (tips []*externalapi.DomainHash, err error)
ToJSON(w io.Writer) error ToJSON(w io.Writer) error

View File

@ -5,22 +5,22 @@ import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/infrastructure/logger" "github.com/kaspanet/kaspad/infrastructure/logger"
"github.com/kaspanet/kaspad/util/staging" "github.com/kaspanet/kaspad/util/staging"
"github.com/pkg/errors"
"sort" "sort"
) )
func (csm *consensusStateManager) ResolveVirtual(maxBlocksToResolve uint64) (*externalapi.VirtualChangeSet, bool, error) { // tipsInDecreasingGHOSTDAGParentSelectionOrder returns the current DAG tips in decreasing parent selection order.
onEnd := logger.LogAndMeasureExecutionTime(log, "csm.ResolveVirtual") // This means that the first tip in the resulting list would be the GHOSTDAG selected parent, and if removed from the list,
defer onEnd() // the second tip would be the selected parent, and so on.
func (csm *consensusStateManager) tipsInDecreasingGHOSTDAGParentSelectionOrder(stagingArea *model.StagingArea) ([]*externalapi.DomainHash, error) {
readStagingArea := model.NewStagingArea() tips, err := csm.consensusStateStore.Tips(stagingArea, csm.databaseContext)
tips, err := csm.consensusStateStore.Tips(readStagingArea, csm.databaseContext)
if err != nil { if err != nil {
return nil, false, err return nil, err
} }
var sortErr error var sortErr error
sort.Slice(tips, func(i, j int) bool { sort.Slice(tips, func(i, j int) bool {
selectedParent, err := csm.ghostdagManager.ChooseSelectedParent(readStagingArea, tips[i], tips[j]) selectedParent, err := csm.ghostdagManager.ChooseSelectedParent(stagingArea, tips[i], tips[j])
if err != nil { if err != nil {
sortErr = err sortErr = err
return false return false
@ -29,16 +29,22 @@ func (csm *consensusStateManager) ResolveVirtual(maxBlocksToResolve uint64) (*ex
return selectedParent.Equal(tips[i]) return selectedParent.Equal(tips[i])
}) })
if sortErr != nil { if sortErr != nil {
return nil, false, sortErr return nil, sortErr
}
return tips, nil
}
func (csm *consensusStateManager) findNextPendingTip(stagingArea *model.StagingArea) (*externalapi.DomainHash, externalapi.BlockStatus, error) {
orderedTips, err := csm.tipsInDecreasingGHOSTDAGParentSelectionOrder(stagingArea)
if err != nil {
return nil, externalapi.StatusInvalid, err
} }
var selectedTip *externalapi.DomainHash for _, tip := range orderedTips {
isCompletelyResolved := true
for _, tip := range tips {
log.Debugf("Resolving tip %s", tip) log.Debugf("Resolving tip %s", tip)
isViolatingFinality, shouldNotify, err := csm.isViolatingFinality(readStagingArea, tip) isViolatingFinality, shouldNotify, err := csm.isViolatingFinality(stagingArea, tip)
if err != nil { if err != nil {
return nil, false, err return nil, externalapi.StatusInvalid, err
} }
if isViolatingFinality { if isViolatingFinality {
@ -49,55 +55,147 @@ func (csm *consensusStateManager) ResolveVirtual(maxBlocksToResolve uint64) (*ex
continue continue
} }
resolveStagingArea := model.NewStagingArea() status, err := csm.blockStatusStore.Get(csm.databaseContext, stagingArea, tip)
unverifiedBlocks, err := csm.getUnverifiedChainBlocks(resolveStagingArea, tip)
if err != nil { if err != nil {
return nil, false, err return nil, externalapi.StatusInvalid, err
} }
if status == externalapi.StatusUTXOValid || status == externalapi.StatusUTXOPendingVerification {
resolveTip := tip return tip, status, nil
hasMoreUnverifiedThanMax := maxBlocksToResolve != 0 && uint64(len(unverifiedBlocks)) > maxBlocksToResolve
if hasMoreUnverifiedThanMax {
resolveTip = unverifiedBlocks[uint64(len(unverifiedBlocks))-maxBlocksToResolve]
log.Debugf("Has more than %d blocks to resolve. Changing the resolve tip to %s", maxBlocksToResolve, resolveTip)
}
blockStatus, reversalData, err := csm.resolveBlockStatus(resolveStagingArea, resolveTip, true)
if err != nil {
return nil, false, err
}
if blockStatus == externalapi.StatusUTXOValid {
selectedTip = resolveTip
isCompletelyResolved = !hasMoreUnverifiedThanMax
err = staging.CommitAllChanges(csm.databaseContext, resolveStagingArea)
if err != nil {
return nil, false, err
}
if reversalData != nil {
err = csm.ReverseUTXODiffs(resolveTip, reversalData)
if err != nil {
return nil, false, err
}
}
break
} }
} }
if selectedTip == nil { return nil, externalapi.StatusInvalid, nil
log.Warnf("Non of the DAG tips are valid") }
return nil, true, nil
// getGHOSTDAGLowerTips returns the set of tips which are lower in GHOSTDAG parent selection order than `pendingTip`. i.e.,
// they can be added to virtual parents but `pendingTip` will remain the virtual selected parent
func (csm *consensusStateManager) getGHOSTDAGLowerTips(stagingArea *model.StagingArea, pendingTip *externalapi.DomainHash) ([]*externalapi.DomainHash, error) {
tips, err := csm.consensusStateStore.Tips(stagingArea, csm.databaseContext)
if err != nil {
return nil, err
} }
oldVirtualGHOSTDAGData, err := csm.ghostdagDataStore.Get(csm.databaseContext, readStagingArea, model.VirtualBlockHash, false) lowerTips := []*externalapi.DomainHash{pendingTip}
for _, tip := range tips {
if tip.Equal(pendingTip) {
continue
}
selectedParent, err := csm.ghostdagManager.ChooseSelectedParent(stagingArea, tip, pendingTip)
if err != nil {
return nil, err
}
if selectedParent.Equal(pendingTip) {
lowerTips = append(lowerTips, tip)
}
}
return lowerTips, nil
}
func (csm *consensusStateManager) ResolveVirtual(maxBlocksToResolve uint64) (*externalapi.VirtualChangeSet, bool, error) {
onEnd := logger.LogAndMeasureExecutionTime(log, "csm.ResolveVirtual")
defer onEnd()
// We use a read-only staging area for some read-only actions, to avoid
// confusion with the resolve/updateVirtual staging areas below
readStagingArea := model.NewStagingArea()
pendingTip, pendingTipStatus, err := csm.findNextPendingTip(readStagingArea)
if err != nil { if err != nil {
return nil, false, err return nil, false, err
} }
if pendingTip == nil {
log.Warnf("None of the DAG tips are valid")
return nil, true, nil
}
previousVirtualSelectedParent, err := csm.virtualSelectedParent(readStagingArea)
if err != nil {
return nil, false, err
}
if pendingTipStatus == externalapi.StatusUTXOValid && previousVirtualSelectedParent.Equal(pendingTip) {
return nil, true, nil
}
// Resolve a chunk from the pending chain
resolveStagingArea := model.NewStagingArea()
unverifiedBlocks, err := csm.getUnverifiedChainBlocks(resolveStagingArea, pendingTip)
if err != nil {
return nil, false, err
}
// Initially set the resolve processing point to the pending tip
processingPoint := pendingTip
// Too many blocks to verify, so we only process a chunk and return
if maxBlocksToResolve != 0 && uint64(len(unverifiedBlocks)) > maxBlocksToResolve {
processingPointIndex := uint64(len(unverifiedBlocks)) - maxBlocksToResolve
processingPoint = unverifiedBlocks[processingPointIndex]
isNewVirtualSelectedParent, err := csm.isNewSelectedTip(readStagingArea, processingPoint, previousVirtualSelectedParent)
if err != nil {
return nil, false, err
}
// We must find a processing point which wins previous virtual selected parent
// even if we process more than `maxBlocksToResolve` for that.
// Otherwise, internal UTXO diff logic gets all messed up
for !isNewVirtualSelectedParent {
if processingPointIndex == 0 {
return nil, false, errors.Errorf(
"Expecting the pending tip %s to overcome the previous selected parent %s", pendingTip, previousVirtualSelectedParent)
}
processingPointIndex--
processingPoint = unverifiedBlocks[processingPointIndex]
isNewVirtualSelectedParent, err = csm.isNewSelectedTip(readStagingArea, processingPoint, previousVirtualSelectedParent)
if err != nil {
return nil, false, err
}
}
log.Debugf("Has more than %d blocks to resolve. Setting the resolve processing point to %s", maxBlocksToResolve, processingPoint)
}
processingPointStatus, reversalData, err := csm.resolveBlockStatus(
resolveStagingArea, processingPoint, true)
if err != nil {
return nil, false, err
}
if processingPointStatus == externalapi.StatusUTXOValid {
err = staging.CommitAllChanges(csm.databaseContext, resolveStagingArea)
if err != nil {
return nil, false, err
}
if reversalData != nil {
err = csm.ReverseUTXODiffs(processingPoint, reversalData)
if err != nil {
return nil, false, err
}
}
}
isActualTip := processingPoint.Equal(pendingTip)
isCompletelyResolved := isActualTip && processingPointStatus == externalapi.StatusUTXOValid
updateVirtualStagingArea := model.NewStagingArea() updateVirtualStagingArea := model.NewStagingArea()
virtualUTXODiff, err := csm.updateVirtualWithParents(updateVirtualStagingArea, []*externalapi.DomainHash{selectedTip})
virtualParents := []*externalapi.DomainHash{processingPoint}
// If `isCompletelyResolved`, set virtual correctly with all tips which have less blue work than pending
if isCompletelyResolved {
lowerTips, err := csm.getGHOSTDAGLowerTips(readStagingArea, pendingTip)
if err != nil {
return nil, false, err
}
log.Debugf("Picking virtual parents from relevant tips len: %d", len(lowerTips))
virtualParents, err = csm.pickVirtualParents(readStagingArea, lowerTips)
if err != nil {
return nil, false, err
}
log.Debugf("Picked virtual parents: %s", virtualParents)
}
virtualUTXODiff, err := csm.updateVirtualWithParents(updateVirtualStagingArea, virtualParents)
if err != nil { if err != nil {
return nil, false, err return nil, false, err
} }
@ -108,12 +206,12 @@ func (csm *consensusStateManager) ResolveVirtual(maxBlocksToResolve uint64) (*ex
} }
selectedParentChainChanges, err := csm.dagTraversalManager. selectedParentChainChanges, err := csm.dagTraversalManager.
CalculateChainPath(readStagingArea, oldVirtualGHOSTDAGData.SelectedParent(), selectedTip) CalculateChainPath(updateVirtualStagingArea, previousVirtualSelectedParent, processingPoint)
if err != nil { if err != nil {
return nil, false, err return nil, false, err
} }
virtualParents, err := csm.dagTopologyManager.Parents(readStagingArea, model.VirtualBlockHash) virtualParentsOutcome, err := csm.dagTopologyManager.Parents(updateVirtualStagingArea, model.VirtualBlockHash)
if err != nil { if err != nil {
return nil, false, err return nil, false, err
} }
@ -121,6 +219,6 @@ func (csm *consensusStateManager) ResolveVirtual(maxBlocksToResolve uint64) (*ex
return &externalapi.VirtualChangeSet{ return &externalapi.VirtualChangeSet{
VirtualSelectedParentChainChanges: selectedParentChainChanges, VirtualSelectedParentChainChanges: selectedParentChainChanges,
VirtualUTXODiff: virtualUTXODiff, VirtualUTXODiff: virtualUTXODiff,
VirtualParents: virtualParents, VirtualParents: virtualParentsOutcome,
}, isCompletelyResolved, nil }, isCompletelyResolved, nil
} }

View File

@ -233,7 +233,7 @@ func (csm *consensusStateManager) resolveSingleBlockStatus(stagingArea *model.St
return externalapi.StatusUTXOValid, nil, nil return externalapi.StatusUTXOValid, nil, nil
} }
oldSelectedTip, err := csm.selectedTip(stagingArea) oldSelectedTip, err := csm.virtualSelectedParent(stagingArea)
if err != nil { if err != nil {
return 0, nil, err return 0, nil, err
} }
@ -298,7 +298,7 @@ func (csm *consensusStateManager) isNewSelectedTip(stagingArea *model.StagingAre
return blockHash.Equal(newSelectedTip), nil return blockHash.Equal(newSelectedTip), nil
} }
func (csm *consensusStateManager) selectedTip(stagingArea *model.StagingArea) (*externalapi.DomainHash, error) { func (csm *consensusStateManager) virtualSelectedParent(stagingArea *model.StagingArea) (*externalapi.DomainHash, error) {
virtualGHOSTDAGData, err := csm.ghostdagDataStore.Get(csm.databaseContext, stagingArea, model.VirtualBlockHash, false) virtualGHOSTDAGData, err := csm.ghostdagDataStore.Get(csm.databaseContext, stagingArea, model.VirtualBlockHash, false)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -1,6 +1,9 @@
package consensusstatemanager_test package consensusstatemanager_test
import ( import (
"fmt"
"github.com/kaspanet/kaspad/domain/consensus/model"
"github.com/kaspanet/kaspad/domain/consensus/model/testapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing" "github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
"testing" "testing"
@ -21,11 +24,14 @@ func TestAddBlockBetweenResolveVirtualCalls(t *testing.T) {
} }
defer teardown(false) defer teardown(false)
hashes := []*externalapi.DomainHash{consensusConfig.GenesisHash}
// Create a chain of blocks // Create a chain of blocks
const initialChainLength = 10 const initialChainLength = 10
previousBlockHash := consensusConfig.GenesisHash previousBlockHash := consensusConfig.GenesisHash
for i := 0; i < initialChainLength; i++ { for i := 0; i < initialChainLength; i++ {
previousBlockHash, _, err = tc.AddBlock([]*externalapi.DomainHash{previousBlockHash}, nil, nil) previousBlockHash, _, err = tc.AddBlock([]*externalapi.DomainHash{previousBlockHash}, nil, nil)
hashes = append(hashes, previousBlockHash)
if err != nil { if err != nil {
t.Fatalf("Error mining block no. %d in initial chain: %+v", i, err) t.Fatalf("Error mining block no. %d in initial chain: %+v", i, err)
} }
@ -40,6 +46,7 @@ func TestAddBlockBetweenResolveVirtualCalls(t *testing.T) {
t.Fatalf("Error mining block no. %d in re-org chain: %+v", i, err) t.Fatalf("Error mining block no. %d in re-org chain: %+v", i, err)
} }
previousBlockHash = consensushashing.BlockHash(previousBlock) previousBlockHash = consensushashing.BlockHash(previousBlock)
hashes = append(hashes, previousBlockHash)
// Do not UTXO validate in order to resolve virtual later // Do not UTXO validate in order to resolve virtual later
err = tc.ValidateAndInsertBlock(previousBlock, false) err = tc.ValidateAndInsertBlock(previousBlock, false)
@ -49,7 +56,7 @@ func TestAddBlockBetweenResolveVirtualCalls(t *testing.T) {
} }
// Resolve one step // Resolve one step
_, err = tc.ResolveVirtualWithMaxParam(2) _, _, err = tc.ResolveVirtualWithMaxParam(2)
if err != nil { if err != nil {
t.Fatalf("Error resolving virtual in re-org chain: %+v", err) t.Fatalf("Error resolving virtual in re-org chain: %+v", err)
} }
@ -68,7 +75,7 @@ func TestAddBlockBetweenResolveVirtualCalls(t *testing.T) {
} }
// Resolve one more step // Resolve one more step
isCompletelyResolved, err := tc.ResolveVirtualWithMaxParam(2) _, isCompletelyResolved, err := tc.ResolveVirtualWithMaxParam(2)
if err != nil { if err != nil {
t.Fatalf("Error resolving virtual in re-org chain: %+v", err) t.Fatalf("Error resolving virtual in re-org chain: %+v", err)
} }
@ -78,14 +85,17 @@ func TestAddBlockBetweenResolveVirtualCalls(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Error mining block during virtual resolution of reorg: %+v", err) t.Fatalf("Error mining block during virtual resolution of reorg: %+v", err)
} }
hashes = append(hashes, consensushashing.BlockHash(blockTemplate.Block))
// Complete resolving virtual // Complete resolving virtual
for !isCompletelyResolved { for !isCompletelyResolved {
isCompletelyResolved, err = tc.ResolveVirtualWithMaxParam(2) _, isCompletelyResolved, err = tc.ResolveVirtualWithMaxParam(2)
if err != nil { if err != nil {
t.Fatalf("Error resolving virtual in re-org chain: %+v", err) t.Fatalf("Error resolving virtual in re-org chain: %+v", err)
} }
} }
verifyUtxoDiffPaths(t, tc, hashes)
}) })
} }
@ -100,11 +110,14 @@ func TestAddGenesisChildAfterOneResolveVirtualCall(t *testing.T) {
} }
defer teardown(false) defer teardown(false)
hashes := []*externalapi.DomainHash{consensusConfig.GenesisHash}
// Create a chain of blocks // Create a chain of blocks
const initialChainLength = 6 const initialChainLength = 6
previousBlockHash := consensusConfig.GenesisHash previousBlockHash := consensusConfig.GenesisHash
for i := 0; i < initialChainLength; i++ { for i := 0; i < initialChainLength; i++ {
previousBlockHash, _, err = tc.AddBlock([]*externalapi.DomainHash{previousBlockHash}, nil, nil) previousBlockHash, _, err = tc.AddBlock([]*externalapi.DomainHash{previousBlockHash}, nil, nil)
hashes = append(hashes, previousBlockHash)
if err != nil { if err != nil {
t.Fatalf("Error mining block no. %d in initial chain: %+v", i, err) t.Fatalf("Error mining block no. %d in initial chain: %+v", i, err)
} }
@ -119,6 +132,7 @@ func TestAddGenesisChildAfterOneResolveVirtualCall(t *testing.T) {
t.Fatalf("Error mining block no. %d in re-org chain: %+v", i, err) t.Fatalf("Error mining block no. %d in re-org chain: %+v", i, err)
} }
previousBlockHash = consensushashing.BlockHash(previousBlock) previousBlockHash = consensushashing.BlockHash(previousBlock)
hashes = append(hashes, previousBlockHash)
// Do not UTXO validate in order to resolve virtual later // Do not UTXO validate in order to resolve virtual later
err = tc.ValidateAndInsertBlock(previousBlock, false) err = tc.ValidateAndInsertBlock(previousBlock, false)
@ -128,7 +142,7 @@ func TestAddGenesisChildAfterOneResolveVirtualCall(t *testing.T) {
} }
// Resolve one step // Resolve one step
isCompletelyResolved, err := tc.ResolveVirtualWithMaxParam(2) _, isCompletelyResolved, err := tc.ResolveVirtualWithMaxParam(2)
if err != nil { if err != nil {
t.Fatalf("Error resolving virtual in re-org chain: %+v", err) t.Fatalf("Error resolving virtual in re-org chain: %+v", err)
} }
@ -140,11 +154,13 @@ func TestAddGenesisChildAfterOneResolveVirtualCall(t *testing.T) {
// Complete resolving virtual // Complete resolving virtual
for !isCompletelyResolved { for !isCompletelyResolved {
isCompletelyResolved, err = tc.ResolveVirtualWithMaxParam(2) _, isCompletelyResolved, err = tc.ResolveVirtualWithMaxParam(2)
if err != nil { if err != nil {
t.Fatalf("Error resolving virtual in re-org chain: %+v", err) t.Fatalf("Error resolving virtual in re-org chain: %+v", err)
} }
} }
verifyUtxoDiffPaths(t, tc, hashes)
}) })
} }
@ -159,11 +175,14 @@ func TestAddGenesisChildAfterTwoResolveVirtualCalls(t *testing.T) {
} }
defer teardown(false) defer teardown(false)
hashes := []*externalapi.DomainHash{consensusConfig.GenesisHash}
// Create a chain of blocks // Create a chain of blocks
const initialChainLength = 6 const initialChainLength = 6
previousBlockHash := consensusConfig.GenesisHash previousBlockHash := consensusConfig.GenesisHash
for i := 0; i < initialChainLength; i++ { for i := 0; i < initialChainLength; i++ {
previousBlockHash, _, err = tc.AddBlock([]*externalapi.DomainHash{previousBlockHash}, nil, nil) previousBlockHash, _, err = tc.AddBlock([]*externalapi.DomainHash{previousBlockHash}, nil, nil)
hashes = append(hashes, previousBlockHash)
if err != nil { if err != nil {
t.Fatalf("Error mining block no. %d in initial chain: %+v", i, err) t.Fatalf("Error mining block no. %d in initial chain: %+v", i, err)
} }
@ -178,6 +197,7 @@ func TestAddGenesisChildAfterTwoResolveVirtualCalls(t *testing.T) {
t.Fatalf("Error mining block no. %d in re-org chain: %+v", i, err) t.Fatalf("Error mining block no. %d in re-org chain: %+v", i, err)
} }
previousBlockHash = consensushashing.BlockHash(previousBlock) previousBlockHash = consensushashing.BlockHash(previousBlock)
hashes = append(hashes, previousBlockHash)
// Do not UTXO validate in order to resolve virtual later // Do not UTXO validate in order to resolve virtual later
err = tc.ValidateAndInsertBlock(previousBlock, false) err = tc.ValidateAndInsertBlock(previousBlock, false)
@ -187,13 +207,13 @@ func TestAddGenesisChildAfterTwoResolveVirtualCalls(t *testing.T) {
} }
// Resolve one step // Resolve one step
_, err = tc.ResolveVirtualWithMaxParam(2) _, _, err = tc.ResolveVirtualWithMaxParam(2)
if err != nil { if err != nil {
t.Fatalf("Error resolving virtual in re-org chain: %+v", err) t.Fatalf("Error resolving virtual in re-org chain: %+v", err)
} }
// Resolve one more step // Resolve one more step
isCompletelyResolved, err := tc.ResolveVirtualWithMaxParam(2) _, isCompletelyResolved, err := tc.ResolveVirtualWithMaxParam(2)
if err != nil { if err != nil {
t.Fatalf("Error resolving virtual in re-org chain: %+v", err) t.Fatalf("Error resolving virtual in re-org chain: %+v", err)
} }
@ -205,10 +225,220 @@ func TestAddGenesisChildAfterTwoResolveVirtualCalls(t *testing.T) {
// Complete resolving virtual // Complete resolving virtual
for !isCompletelyResolved { for !isCompletelyResolved {
isCompletelyResolved, err = tc.ResolveVirtualWithMaxParam(2) _, isCompletelyResolved, err = tc.ResolveVirtualWithMaxParam(2)
if err != nil { if err != nil {
t.Fatalf("Error resolving virtual in re-org chain: %+v", err) t.Fatalf("Error resolving virtual in re-org chain: %+v", err)
} }
} }
verifyUtxoDiffPaths(t, tc, hashes)
}) })
} }
func TestResolveVirtualBackAndForthReorgs(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
factory := consensus.NewFactory()
tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestAddGenesisChildAfterTwoResolveVirtualCalls")
if err != nil {
t.Fatalf("Error setting up consensus: %+v", err)
}
defer teardown(false)
hashes := []*externalapi.DomainHash{consensusConfig.GenesisHash}
blocks := make(map[externalapi.DomainHash]string)
blocks[*consensusConfig.GenesisHash] = "g"
blocks[*model.VirtualBlockHash] = "v"
printfDebug("%s\n\n", consensusConfig.GenesisHash)
// Create a chain of blocks
const initialChainLength = 6
previousBlockHash := consensusConfig.GenesisHash
for i := 0; i < initialChainLength; i++ {
previousBlockHash, _, err = tc.AddBlock([]*externalapi.DomainHash{previousBlockHash}, nil, nil)
blocks[*previousBlockHash] = fmt.Sprintf("A_%d", i)
hashes = append(hashes, previousBlockHash)
printfDebug("A_%d: %s\n", i, previousBlockHash)
if err != nil {
t.Fatalf("Error mining block no. %d in initial chain: %+v", i, err)
}
}
printfDebug("\n")
verifyUtxoDiffPaths(t, tc, hashes)
firstChainTip := previousBlockHash
// Mine a chain with more blocks, to re-organize the DAG
const reorgChainLength = 12 // initialChainLength + 1
previousBlockHash = consensusConfig.GenesisHash
for i := 0; i < reorgChainLength; i++ {
previousBlock, _, err := tc.BuildBlockWithParents([]*externalapi.DomainHash{previousBlockHash}, nil, nil)
if err != nil {
t.Fatalf("Error mining block no. %d in re-org chain: %+v", i, err)
}
previousBlockHash = consensushashing.BlockHash(previousBlock)
blocks[*previousBlockHash] = fmt.Sprintf("B_%d", i)
hashes = append(hashes, previousBlockHash)
printfDebug("B_%d: %s\n", i, previousBlockHash)
// Do not UTXO validate in order to resolve virtual later
err = tc.ValidateAndInsertBlock(previousBlock, false)
if err != nil {
t.Fatalf("Error mining block no. %d in re-org chain: %+v", i, err)
}
}
printfDebug("\n")
printUtxoDiffChildren(t, tc, hashes, blocks)
verifyUtxoDiffPaths(t, tc, hashes)
previousVirtualSelectedParent, err := tc.GetVirtualSelectedParent()
if err != nil {
t.Fatal(err)
}
// Resolve one step
virtualChangeSet, _, err := tc.ResolveVirtualWithMaxParam(3)
if err != nil {
printUtxoDiffChildren(t, tc, hashes, blocks)
t.Fatalf("Error resolving virtual in re-org chain: %+v", err)
}
newVirtualSelectedParent, err := tc.GetVirtualSelectedParent()
if err != nil {
t.Fatal(err)
}
// Make sure the reported change-set is compatible with actual changes.
// Checking this for one call should suffice to avoid possible bugs.
reportedPreviousVirtualSelectedParent := virtualChangeSet.VirtualSelectedParentChainChanges.Removed[0]
reportedNewVirtualSelectedParent := virtualChangeSet.VirtualSelectedParentChainChanges.
Added[len(virtualChangeSet.VirtualSelectedParentChainChanges.Added)-1]
if !previousVirtualSelectedParent.Equal(reportedPreviousVirtualSelectedParent) {
t.Fatalf("The reported changeset is incompatible with actual changes")
}
if !newVirtualSelectedParent.Equal(reportedNewVirtualSelectedParent) {
t.Fatalf("The reported changeset is incompatible with actual changes")
}
// Resolve one more step
_, isCompletelyResolved, err := tc.ResolveVirtualWithMaxParam(3)
if err != nil {
t.Fatalf("Error resolving virtual in re-org chain: %+v", err)
}
// Complete resolving virtual
for !isCompletelyResolved {
_, isCompletelyResolved, err = tc.ResolveVirtualWithMaxParam(3)
if err != nil {
t.Fatalf("Error resolving virtual in re-org chain: %+v", err)
}
}
printUtxoDiffChildren(t, tc, hashes, blocks)
verifyUtxoDiffPaths(t, tc, hashes)
// Now get the first chain back to the wining position
previousBlockHash = firstChainTip
for i := 0; i < reorgChainLength; i++ {
previousBlockHash, _, err = tc.AddBlock([]*externalapi.DomainHash{previousBlockHash}, nil, nil)
blocks[*previousBlockHash] = fmt.Sprintf("A_%d", initialChainLength+i)
hashes = append(hashes, previousBlockHash)
printfDebug("A_%d: %s\n", initialChainLength+i, previousBlockHash)
if err != nil {
t.Fatalf("Error mining block no. %d in initial chain: %+v", initialChainLength+i, err)
}
}
printfDebug("\n")
printUtxoDiffChildren(t, tc, hashes, blocks)
verifyUtxoDiffPaths(t, tc, hashes)
})
}
func verifyUtxoDiffPathToRoot(t *testing.T, tc testapi.TestConsensus, stagingArea *model.StagingArea, block, utxoDiffRoot *externalapi.DomainHash) {
current := block
for !current.Equal(utxoDiffRoot) {
hasUTXODiffChild, err := tc.UTXODiffStore().HasUTXODiffChild(tc.DatabaseContext(), stagingArea, current)
if err != nil {
t.Fatalf("Error while reading utxo diff store: %+v", err)
}
if !hasUTXODiffChild {
t.Fatalf("%s is expected to have a UTXO diff child", current)
}
current, err = tc.UTXODiffStore().UTXODiffChild(tc.DatabaseContext(), stagingArea, current)
if err != nil {
t.Fatalf("Error while reading utxo diff store: %+v", err)
}
}
}
func verifyUtxoDiffPaths(t *testing.T, tc testapi.TestConsensus, hashes []*externalapi.DomainHash) {
stagingArea := model.NewStagingArea()
virtualGHOSTDAGData, err := tc.GHOSTDAGDataStore().Get(tc.DatabaseContext(), stagingArea, model.VirtualBlockHash, false)
if err != nil {
t.Fatal(err)
}
utxoDiffRoot := virtualGHOSTDAGData.SelectedParent()
hasUTXODiffChild, err := tc.UTXODiffStore().HasUTXODiffChild(tc.DatabaseContext(), stagingArea, utxoDiffRoot)
if err != nil {
t.Fatalf("Error while reading utxo diff store: %+v", err)
}
if hasUTXODiffChild {
t.Fatalf("Virtual selected parent is not expected to have an explicit diff child")
}
_, err = tc.UTXODiffStore().UTXODiff(tc.DatabaseContext(), stagingArea, utxoDiffRoot)
if err != nil {
t.Fatalf("Virtual selected parent is expected to have a utxo diff: %+v", err)
}
for _, block := range hashes {
hasUTXODiffChild, err = tc.UTXODiffStore().HasUTXODiffChild(tc.DatabaseContext(), stagingArea, block)
if err != nil {
t.Fatalf("Error while reading utxo diff store: %+v", err)
}
isOnVirtualSelectedChain, err := tc.DAGTopologyManager().IsInSelectedParentChainOf(stagingArea, block, utxoDiffRoot)
if err != nil {
t.Fatal(err)
}
// We expect a valid path to root in both cases: (i) block has a diff child, (ii) block is on the virtual selected chain
if hasUTXODiffChild || isOnVirtualSelectedChain {
verifyUtxoDiffPathToRoot(t, tc, stagingArea, block, utxoDiffRoot)
}
}
}
func printfDebug(format string, a ...any) {
// Uncomment below when debugging the test
//fmt.Printf(format, a...)
}
func printUtxoDiffChildren(t *testing.T, tc testapi.TestConsensus, hashes []*externalapi.DomainHash, blocks map[externalapi.DomainHash]string) {
printfDebug("\n===============================\nBlock\t\tDiff child\n")
stagingArea := model.NewStagingArea()
for _, block := range hashes {
hasUTXODiffChild, err := tc.UTXODiffStore().HasUTXODiffChild(tc.DatabaseContext(), stagingArea, block)
if err != nil {
t.Fatalf("Error while reading utxo diff store: %+v", err)
}
if hasUTXODiffChild {
utxoDiffChild, err := tc.UTXODiffStore().UTXODiffChild(tc.DatabaseContext(), stagingArea, block)
if err != nil {
t.Fatalf("Error while reading utxo diff store: %+v", err)
}
printfDebug("%s\t\t\t%s\n", blocks[*block], blocks[*utxoDiffChild])
} else {
printfDebug("%s\n", blocks[*block])
}
}
printfDebug("\n===============================\n")
}

View File

@ -56,12 +56,6 @@ func (csm *consensusStateManager) ReverseUTXODiffs(tipHash *externalapi.DomainHa
return err return err
} }
// We stop reversing when current's UTXODiffChild is not current's SelectedParent
if !currentBlockGHOSTDAGData.SelectedParent().Equal(currentBlockUTXODiffChild) {
log.Debugf("Block %s's UTXODiffChild is not it's selected parent - finish reversing", currentBlock)
break
}
currentUTXODiff := previousUTXODiff.Reversed() currentUTXODiff := previousUTXODiff.Reversed()
// retrieve current utxoDiff for Bi, to be used by next block // retrieve current utxoDiff for Bi, to be used by next block
@ -75,6 +69,12 @@ func (csm *consensusStateManager) ReverseUTXODiffs(tipHash *externalapi.DomainHa
return err return err
} }
// We stop reversing when current's UTXODiffChild is not current's SelectedParent
if !currentBlockGHOSTDAGData.SelectedParent().Equal(currentBlockUTXODiffChild) {
log.Debugf("Block %s's UTXODiffChild is not it's selected parent - finish reversing", currentBlock)
break
}
previousBlock = currentBlock previousBlock = currentBlock
previousBlockGHOSTDAGData = currentBlockGHOSTDAGData previousBlockGHOSTDAGData = currentBlockGHOSTDAGData

View File

@ -110,7 +110,7 @@ func (csm *consensusStateManager) updateSelectedTipUTXODiff(
onEnd := logger.LogAndMeasureExecutionTime(log, "updateSelectedTipUTXODiff") onEnd := logger.LogAndMeasureExecutionTime(log, "updateSelectedTipUTXODiff")
defer onEnd() defer onEnd()
selectedTip, err := csm.selectedTip(stagingArea) selectedTip, err := csm.virtualSelectedParent(stagingArea)
if err != nil { if err != nil {
return err return err
} }

View File

@ -112,11 +112,11 @@ func (tc *testConsensus) AddUTXOInvalidBlock(parentHashes []*externalapi.DomainH
return consensushashing.BlockHash(block), virtualChangeSet, nil return consensushashing.BlockHash(block), virtualChangeSet, nil
} }
func (tc *testConsensus) ResolveVirtualWithMaxParam(maxBlocksToResolve uint64) (bool, error) { func (tc *testConsensus) ResolveVirtualWithMaxParam(maxBlocksToResolve uint64) (*externalapi.VirtualChangeSet, bool, error) {
tc.lock.Lock() tc.lock.Lock()
defer tc.lock.Unlock() defer tc.lock.Unlock()
return tc.resolveVirtualNoLock(maxBlocksToResolve) return tc.resolveVirtualChunkNoLock(maxBlocksToResolve)
} }
// jsonBlock is a json representation of a block in mine format // jsonBlock is a json representation of a block in mine format

View File

@ -214,34 +214,19 @@ func syncConsensuses(syncer, syncee externalapi.Consensus) error {
return err return err
} }
virtualDAAScoreStart, err := syncee.GetVirtualDAAScore() 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 { if err != nil {
return err return err
} }
percents = 0 log.Infof("Resolved virtual")
for i := 0; ; i++ {
if i%10 == 0 {
virtualDAAScore, err := syncee.GetVirtualDAAScore()
if err != nil {
return err
}
newPercents := int(float64(virtualDAAScore-virtualDAAScoreStart) / float64(estimatedVirtualDAAScoreTarget-virtualDAAScoreStart) * 100)
if newPercents > percents {
percents = newPercents
log.Infof("Resolving virtual. Estimated progress: %d%%", percents)
}
}
isCompletelyResolved, err := syncee.ResolveVirtual()
if err != nil {
return err
}
if isCompletelyResolved {
log.Infof("Resolved virtual")
break
}
}
return nil return nil
} }

View File

@ -1,6 +1,9 @@
package rpcclient package rpcclient
import ( import (
"sync/atomic"
"time"
"github.com/kaspanet/kaspad/app/appmessage" "github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/infrastructure/logger" "github.com/kaspanet/kaspad/infrastructure/logger"
routerpkg "github.com/kaspanet/kaspad/infrastructure/network/netadapter/router" routerpkg "github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
@ -8,8 +11,6 @@ import (
"github.com/kaspanet/kaspad/util/panics" "github.com/kaspanet/kaspad/util/panics"
"github.com/kaspanet/kaspad/version" "github.com/kaspanet/kaspad/version"
"github.com/pkg/errors" "github.com/pkg/errors"
"sync/atomic"
"time"
) )
const defaultTimeout = 30 * time.Second const defaultTimeout = 30 * time.Second
@ -28,7 +29,7 @@ type RPCClient struct {
timeout time.Duration timeout time.Duration
} }
// NewRPCClient creates a new RPC client // NewRPCClient сreates a new RPC client with a default call timeout value
func NewRPCClient(rpcAddress string) (*RPCClient, error) { func NewRPCClient(rpcAddress string) (*RPCClient, error) {
rpcClient := &RPCClient{ rpcClient := &RPCClient{
rpcAddress: rpcAddress, rpcAddress: rpcAddress,

View File

@ -11,7 +11,7 @@ const validCharacters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrs
const ( const (
appMajor uint = 0 appMajor uint = 0
appMinor uint = 12 appMinor uint = 12
appPatch uint = 3 appPatch uint = 4
) )
// appBuild is defined as a variable so it can be overridden during the build // appBuild is defined as a variable so it can be overridden during the build