diff --git a/app/appmessage/rpc_estimate_network_hashes_per_second.go b/app/appmessage/rpc_estimate_network_hashes_per_second.go index 48fe6c46e..d298c91bc 100644 --- a/app/appmessage/rpc_estimate_network_hashes_per_second.go +++ b/app/appmessage/rpc_estimate_network_hashes_per_second.go @@ -4,6 +4,7 @@ package appmessage // its respective RPC message type EstimateNetworkHashesPerSecondRequestMessage struct { baseMessage + StartHash string WindowSize uint32 } @@ -13,8 +14,9 @@ func (msg *EstimateNetworkHashesPerSecondRequestMessage) Command() MessageComman } // NewEstimateNetworkHashesPerSecondRequestMessage returns a instance of the message -func NewEstimateNetworkHashesPerSecondRequestMessage(windowSize uint32) *EstimateNetworkHashesPerSecondRequestMessage { +func NewEstimateNetworkHashesPerSecondRequestMessage(startHash string, windowSize uint32) *EstimateNetworkHashesPerSecondRequestMessage { return &EstimateNetworkHashesPerSecondRequestMessage{ + StartHash: startHash, WindowSize: windowSize, } } diff --git a/app/protocol/flows/testing/handle_relay_invs_test.go b/app/protocol/flows/testing/handle_relay_invs_test.go index cf753ab36..230638a01 100644 --- a/app/protocol/flows/testing/handle_relay_invs_test.go +++ b/app/protocol/flows/testing/handle_relay_invs_test.go @@ -130,7 +130,7 @@ type fakeRelayInvsContext struct { rwLock sync.RWMutex } -func (f *fakeRelayInvsContext) EstimateNetworkHashesPerSecond(windowSize int) (uint64, error) { +func (f *fakeRelayInvsContext) EstimateNetworkHashesPerSecond(startHash *externalapi.DomainHash, windowSize int) (uint64, error) { panic(errors.Errorf("called unimplemented function from test '%s'", f.testName)) } diff --git a/app/rpc/rpchandlers/estimate_network_hashes_per_second.go b/app/rpc/rpchandlers/estimate_network_hashes_per_second.go index 41e359a5f..505c62b2a 100644 --- a/app/rpc/rpchandlers/estimate_network_hashes_per_second.go +++ b/app/rpc/rpchandlers/estimate_network_hashes_per_second.go @@ -3,19 +3,35 @@ package rpchandlers import ( "github.com/kaspanet/kaspad/app/appmessage" "github.com/kaspanet/kaspad/app/rpc/rpccontext" + "github.com/kaspanet/kaspad/domain/consensus/model" + "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" "github.com/kaspanet/kaspad/infrastructure/network/netadapter/router" ) // HandleEstimateNetworkHashesPerSecond handles the respectively named RPC command -func HandleEstimateNetworkHashesPerSecond(context *rpccontext.Context, _ *router.Router, request appmessage.Message) (appmessage.Message, error) { +func HandleEstimateNetworkHashesPerSecond( + context *rpccontext.Context, _ *router.Router, request appmessage.Message) (appmessage.Message, error) { + estimateNetworkHashesPerSecondRequest := request.(*appmessage.EstimateNetworkHashesPerSecondRequestMessage) windowSize := int(estimateNetworkHashesPerSecondRequest.WindowSize) - networkHashesPerSecond, err := context.Domain.Consensus().EstimateNetworkHashesPerSecond(windowSize) + startHash := model.VirtualBlockHash + if estimateNetworkHashesPerSecondRequest.StartHash != "" { + var err error + startHash, err = externalapi.NewDomainHashFromString(estimateNetworkHashesPerSecondRequest.StartHash) + if err != nil { + response := &appmessage.EstimateNetworkHashesPerSecondResponseMessage{} + response.Error = appmessage.RPCErrorf("StartHash '%s' is not a valid block hash", + estimateNetworkHashesPerSecondRequest.StartHash) + return response, nil + } + } + + networkHashesPerSecond, err := context.Domain.Consensus().EstimateNetworkHashesPerSecond(startHash, windowSize) if err != nil { response := &appmessage.EstimateNetworkHashesPerSecondResponseMessage{} response.Error = appmessage.RPCErrorf("could not resolve network hashes per "+ - "second for window size %d: %s", windowSize, err) + "second for startHash %s and window size %d: %s", startHash, windowSize, err) return response, nil } diff --git a/cmd/kaspactl/command_parser.go b/cmd/kaspactl/command_parser.go index ee74bfc6b..6c174d9df 100644 --- a/cmd/kaspactl/command_parser.go +++ b/cmd/kaspactl/command_parser.go @@ -48,6 +48,10 @@ func setField(commandValue reflect.Value, parameterValue reflect.Value, paramete } func stringToValue(parameterDesc *parameterDescription, valueStr string) (reflect.Value, error) { + if valueStr == "-" { + return reflect.Zero(parameterDesc.typeof), nil + } + var value interface{} var err error switch parameterDesc.typeof.Kind() { diff --git a/cmd/kaspactl/config.go b/cmd/kaspactl/config.go index 0b240529a..7053d4573 100644 --- a/cmd/kaspactl/config.go +++ b/cmd/kaspactl/config.go @@ -27,7 +27,8 @@ func parseConfig() (*configFlags, error) { } parser := flags.NewParser(cfg, flags.HelpFlag) parser.Usage = "kaspactl [OPTIONS] [COMMAND] [COMMAND PARAMETERS].\n\nCommand can be supplied only if --json is not used." + - "\n\nUse `kaspactl --list-commands` to get a list of all commands and their parameters" + "\n\nUse `kaspactl --list-commands` to get a list of all commands and their parameters." + + "\nFor optional parameters- use '-' without quotes to not pass the parameter.\n" remainingArgs, err := parser.Parse() if err != nil { return nil, err diff --git a/cmd/kaspawallet/daemon/server/log.go b/cmd/kaspawallet/daemon/server/log.go index b58080a29..82a77a34a 100644 --- a/cmd/kaspawallet/daemon/server/log.go +++ b/cmd/kaspawallet/daemon/server/log.go @@ -1,6 +1,12 @@ package server import ( + "fmt" + "os" + "path/filepath" + + "github.com/kaspanet/kaspad/util" + "github.com/kaspanet/kaspad/infrastructure/logger" "github.com/kaspanet/kaspad/util/panics" ) @@ -9,4 +15,33 @@ var ( backendLog = logger.NewBackend() log = backendLog.Logger("KSWD") spawn = panics.GoroutineWrapperFunc(log) + + defaultAppDir = util.AppDir("kaspawallet", false) + defaultLogFile = filepath.Join(defaultAppDir, "daemon.log") + defaultErrLogFile = filepath.Join(defaultAppDir, "daemon_err.log") ) + +func initLog(logFile, errLogFile string) { + log.SetLevel(logger.LevelDebug) + err := backendLog.AddLogFile(logFile, logger.LevelTrace) + if err != nil { + fmt.Fprintf(os.Stderr, "Error adding log file %s as log rotator for level %s: %s", logFile, logger.LevelTrace, err) + os.Exit(1) + } + err = backendLog.AddLogFile(errLogFile, logger.LevelWarn) + if err != nil { + fmt.Fprintf(os.Stderr, "Error adding log file %s as log rotator for level %s: %s", errLogFile, logger.LevelWarn, err) + os.Exit(1) + } + err = backendLog.AddLogWriter(os.Stdout, logger.LevelInfo) + if err != nil { + fmt.Fprintf(os.Stderr, "Error adding stdout to the loggerfor level %s: %s", logger.LevelWarn, err) + os.Exit(1) + } + err = backendLog.Run() + if err != nil { + fmt.Fprintf(os.Stderr, "Error starting the logger: %s ", err) + os.Exit(1) + } + +} diff --git a/cmd/kaspawallet/daemon/server/server.go b/cmd/kaspawallet/daemon/server/server.go index bf5090161..c2cf8cbe2 100644 --- a/cmd/kaspawallet/daemon/server/server.go +++ b/cmd/kaspawallet/daemon/server/server.go @@ -2,6 +2,11 @@ package server import ( "fmt" + "net" + "os" + "sync" + "time" + "github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb" "github.com/kaspanet/kaspad/cmd/kaspawallet/keys" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" @@ -10,10 +15,6 @@ import ( "github.com/kaspanet/kaspad/infrastructure/os/signal" "github.com/kaspanet/kaspad/util/panics" "github.com/pkg/errors" - "net" - "os" - "sync" - "time" "google.golang.org/grpc" ) @@ -33,6 +34,8 @@ type server struct { // Start starts the kaspawalletd server func Start(params *dagconfig.Params, listen, rpcServer string, keysFilePath string) error { + initLog(defaultLogFile, defaultErrLogFile) + defer panics.HandlePanic(log, "MAIN", nil) interrupt := signal.InterruptListener() @@ -40,6 +43,7 @@ func Start(params *dagconfig.Params, listen, rpcServer string, keysFilePath stri if err != nil { return (errors.Wrapf(err, "Error listening to tcp at %s", listen)) } + log.Infof("Listening on %s", listen) rpcClient, err := connectToRPC(params, rpcServer) if err != nil { diff --git a/cmd/kaspawallet/libkaspawallet/bip39.go b/cmd/kaspawallet/libkaspawallet/bip39.go index a64667fa8..53acda51b 100644 --- a/cmd/kaspawallet/libkaspawallet/bip39.go +++ b/cmd/kaspawallet/libkaspawallet/bip39.go @@ -2,6 +2,7 @@ package libkaspawallet import ( "fmt" + "github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet/bip32" "github.com/kaspanet/kaspad/domain/dagconfig" "github.com/pkg/errors" @@ -15,23 +16,23 @@ func CreateMnemonic() (string, error) { return bip39.NewMnemonic(entropy) } +// Purpose and CoinType constants +const ( + SingleSignerPurpose = 44 + // Note: this is not entirely compatible to BIP 45 since + // BIP 45 doesn't have a coin type in its derivation path. + MultiSigPurpose = 45 + // TODO: Register the coin type in https://github.com/satoshilabs/slips/blob/master/slip-0044.md + CoinType = 111111 +) + func defaultPath(isMultisig bool) string { - const ( - singleSignerPurpose = 44 - - // Note: this is not entirely compatible to BIP 45 since - // BIP 45 doesn't have a coin type in its derivation path. - multiSigPurpose = 45 - ) - - purpose := singleSignerPurpose + purpose := SingleSignerPurpose if isMultisig { - purpose = multiSigPurpose + purpose = MultiSigPurpose } - // TODO: Register the coin type in https://github.com/satoshilabs/slips/blob/master/slip-0044.md - const coinType = 111111 - return fmt.Sprintf("m/%d'/%d'/0'", coinType, purpose) + return fmt.Sprintf("m/%d'/%d'/0'", purpose, CoinType) } // MasterPublicKeyFromMnemonic returns the master public key with the correct derivation for the given mnemonic. diff --git a/cmd/kaspawallet/libkaspawallet/keypair.go b/cmd/kaspawallet/libkaspawallet/keypair.go index bf4b69f51..41c99de36 100644 --- a/cmd/kaspawallet/libkaspawallet/keypair.go +++ b/cmd/kaspawallet/libkaspawallet/keypair.go @@ -1,14 +1,15 @@ package libkaspawallet import ( + "math" + "sort" + "strings" + "github.com/kaspanet/go-secp256k1" "github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet/bip32" "github.com/kaspanet/kaspad/domain/dagconfig" "github.com/kaspanet/kaspad/util" "github.com/pkg/errors" - "math" - "sort" - "strings" ) // CreateKeyPair generates a private-public key pair diff --git a/cmd/kaspawallet/utils/readline.go b/cmd/kaspawallet/utils/readline.go index 90342edce..7b83cb0b8 100644 --- a/cmd/kaspawallet/utils/readline.go +++ b/cmd/kaspawallet/utils/readline.go @@ -3,13 +3,15 @@ package utils import ( "bufio" "strings" + + "github.com/pkg/errors" ) // ReadLine reads one line from the given reader with trimmed white space. func ReadLine(reader *bufio.Reader) (string, error) { line, err := reader.ReadBytes('\n') if err != nil { - return "", err + return "", errors.WithStack(err) } return strings.TrimSpace(string(line)), nil diff --git a/domain/consensus/consensus.go b/domain/consensus/consensus.go index fa0d7c1b7..425a9b808 100644 --- a/domain/consensus/consensus.go +++ b/domain/consensus/consensus.go @@ -536,9 +536,9 @@ func (s *consensus) Anticone(blockHash *externalapi.DomainHash) ([]*externalapi. return s.dagTraversalManager.Anticone(stagingArea, blockHash) } -func (s *consensus) EstimateNetworkHashesPerSecond(windowSize int) (uint64, error) { +func (s *consensus) EstimateNetworkHashesPerSecond(startHash *externalapi.DomainHash, windowSize int) (uint64, error) { s.lock.Lock() defer s.lock.Unlock() - return s.difficultyManager.EstimateNetworkHashesPerSecond(windowSize) + return s.difficultyManager.EstimateNetworkHashesPerSecond(startHash, windowSize) } diff --git a/domain/consensus/model/externalapi/consensus.go b/domain/consensus/model/externalapi/consensus.go index 64075cf39..c565a0f90 100644 --- a/domain/consensus/model/externalapi/consensus.go +++ b/domain/consensus/model/externalapi/consensus.go @@ -33,5 +33,5 @@ type Consensus interface { IsInSelectedParentChainOf(blockHashA *DomainHash, blockHashB *DomainHash) (bool, error) GetHeadersSelectedTip() (*DomainHash, error) Anticone(blockHash *DomainHash) ([]*DomainHash, error) - EstimateNetworkHashesPerSecond(windowSize int) (uint64, error) + EstimateNetworkHashesPerSecond(startHash *DomainHash, windowSize int) (uint64, error) } diff --git a/domain/consensus/model/ghostdag.go b/domain/consensus/model/ghostdag.go index 5b77cd83d..4ebe4372e 100644 --- a/domain/consensus/model/ghostdag.go +++ b/domain/consensus/model/ghostdag.go @@ -67,13 +67,3 @@ func (bgd *BlockGHOSTDAGData) MergeSetReds() []*externalapi.DomainHash { func (bgd *BlockGHOSTDAGData) BluesAnticoneSizes() map[externalapi.DomainHash]KType { return bgd.bluesAnticoneSizes } - -// MergeSet returns the whole MergeSet of the block (equivalent to MergeSetBlues+MergeSetReds) -func (bgd *BlockGHOSTDAGData) MergeSet() []*externalapi.DomainHash { - mergeSet := make([]*externalapi.DomainHash, len(bgd.mergeSetBlues)+len(bgd.mergeSetReds)) - copy(mergeSet, bgd.mergeSetBlues) - if len(bgd.mergeSetReds) > 0 { - copy(mergeSet[len(bgd.mergeSetBlues):], bgd.mergeSetReds) - } - return mergeSet -} diff --git a/domain/consensus/model/interface_processes_difficultymanager.go b/domain/consensus/model/interface_processes_difficultymanager.go index 999e74ec0..704b0b929 100644 --- a/domain/consensus/model/interface_processes_difficultymanager.go +++ b/domain/consensus/model/interface_processes_difficultymanager.go @@ -9,5 +9,5 @@ import ( type DifficultyManager interface { StageDAADataAndReturnRequiredDifficulty(stagingArea *StagingArea, blockHash *externalapi.DomainHash) (uint32, error) RequiredDifficulty(stagingArea *StagingArea, blockHash *externalapi.DomainHash) (uint32, error) - EstimateNetworkHashesPerSecond(windowSize int) (uint64, error) + EstimateNetworkHashesPerSecond(startHash *externalapi.DomainHash, windowSize int) (uint64, error) } diff --git a/domain/consensus/model/interface_processes_ghostdagmanager.go b/domain/consensus/model/interface_processes_ghostdagmanager.go index be831c488..95025696a 100644 --- a/domain/consensus/model/interface_processes_ghostdagmanager.go +++ b/domain/consensus/model/interface_processes_ghostdagmanager.go @@ -8,4 +8,5 @@ type GHOSTDAGManager interface { ChooseSelectedParent(stagingArea *StagingArea, blockHashes ...*externalapi.DomainHash) (*externalapi.DomainHash, error) Less(blockHashA *externalapi.DomainHash, ghostdagDataA *BlockGHOSTDAGData, blockHashB *externalapi.DomainHash, ghostdagDataB *BlockGHOSTDAGData) bool + GetSortedMergeSet(stagingArea *StagingArea, current *externalapi.DomainHash) ([]*externalapi.DomainHash, error) } diff --git a/domain/consensus/processes/blockvalidator/block_header_in_context.go b/domain/consensus/processes/blockvalidator/block_header_in_context.go index 71fe0be8b..957295c2c 100644 --- a/domain/consensus/processes/blockvalidator/block_header_in_context.go +++ b/domain/consensus/processes/blockvalidator/block_header_in_context.go @@ -147,7 +147,7 @@ func (v *blockValidator) checkMergeSizeLimit(stagingArea *model.StagingArea, has return err } - mergeSetSize := len(ghostdagData.MergeSet()) + mergeSetSize := len(ghostdagData.MergeSetBlues()) + len(ghostdagData.MergeSetReds()) if uint64(mergeSetSize) > v.mergeSetSizeLimit { return errors.Wrapf(ruleerrors.ErrViolatingMergeLimit, diff --git a/domain/consensus/processes/blockvalidator/pruning_violation_proof_of_work_and_difficulty_test.go b/domain/consensus/processes/blockvalidator/pruning_violation_proof_of_work_and_difficulty_test.go index 73b595774..972c8259a 100644 --- a/domain/consensus/processes/blockvalidator/pruning_violation_proof_of_work_and_difficulty_test.go +++ b/domain/consensus/processes/blockvalidator/pruning_violation_proof_of_work_and_difficulty_test.go @@ -313,6 +313,6 @@ func (dm *mocDifficultyManager) StageDAADataAndReturnRequiredDifficulty(stagingA return dm.testDifficulty, nil } -func (dm *mocDifficultyManager) EstimateNetworkHashesPerSecond(windowSize int) (uint64, error) { +func (dm *mocDifficultyManager) EstimateNetworkHashesPerSecond(startHash *externalapi.DomainHash, windowSize int) (uint64, error) { return 0, nil } diff --git a/domain/consensus/processes/coinbasemanager/coinbasemanager.go b/domain/consensus/processes/coinbasemanager/coinbasemanager.go index a19e739ec..61230429d 100644 --- a/domain/consensus/processes/coinbasemanager/coinbasemanager.go +++ b/domain/consensus/processes/coinbasemanager/coinbasemanager.go @@ -39,8 +39,9 @@ func (c *coinbaseManager) ExpectedCoinbaseTransaction(stagingArea *model.Staging } txOuts := make([]*externalapi.DomainTransactionOutput, 0, len(ghostdagData.MergeSetBlues())) - for i, blue := range ghostdagData.MergeSetBlues() { - txOut, hasReward, err := c.coinbaseOutputForBlueBlock(stagingArea, blue, acceptanceData[i], daaAddedBlocksSet) + acceptanceDataMap := acceptanceDataFromArrayToMap(acceptanceData) + for _, blue := range ghostdagData.MergeSetBlues() { + txOut, hasReward, err := c.coinbaseOutputForBlueBlock(stagingArea, blue, acceptanceDataMap[*blue], daaAddedBlocksSet) if err != nil { return nil, err } @@ -120,10 +121,10 @@ func (c *coinbaseManager) coinbaseOutputForRewardFromRedBlocks(stagingArea *mode ghostdagData *model.BlockGHOSTDAGData, acceptanceData externalapi.AcceptanceData, daaAddedBlocksSet hashset.HashSet, coinbaseData *externalapi.DomainCoinbaseData) (*externalapi.DomainTransactionOutput, bool, error) { + acceptanceDataMap := acceptanceDataFromArrayToMap(acceptanceData) totalReward := uint64(0) - mergeSetBluesCount := len(ghostdagData.MergeSetBlues()) - for i, red := range ghostdagData.MergeSetReds() { - reward, err := c.calcMergedBlockReward(stagingArea, red, acceptanceData[mergeSetBluesCount+i], daaAddedBlocksSet) + for _, red := range ghostdagData.MergeSetReds() { + reward, err := c.calcMergedBlockReward(stagingArea, red, acceptanceDataMap[*red], daaAddedBlocksSet) if err != nil { return nil, false, err } @@ -141,6 +142,14 @@ func (c *coinbaseManager) coinbaseOutputForRewardFromRedBlocks(stagingArea *mode }, true, nil } +func acceptanceDataFromArrayToMap(acceptanceData externalapi.AcceptanceData) map[externalapi.DomainHash]*externalapi.BlockAcceptanceData { + acceptanceDataMap := make(map[externalapi.DomainHash]*externalapi.BlockAcceptanceData, len(acceptanceData)) + for _, blockAcceptanceData := range acceptanceData { + acceptanceDataMap[*blockAcceptanceData.BlockHash] = blockAcceptanceData + } + return acceptanceDataMap +} + // calcBlockSubsidy returns the subsidy amount a block at the provided blue score // should have. This is mainly used for determining how much the coinbase for // newly generated blocks awards as well as validating the coinbase for blocks diff --git a/domain/consensus/processes/consensusstatemanager/add_block_to_virtual.go b/domain/consensus/processes/consensusstatemanager/add_block_to_virtual.go index 629a4bd4b..e4ea5ccbb 100644 --- a/domain/consensus/processes/consensusstatemanager/add_block_to_virtual.go +++ b/domain/consensus/processes/consensusstatemanager/add_block_to_virtual.go @@ -129,7 +129,8 @@ func (csm *consensusStateManager) calculateNewTips( if err != nil { return nil, err } - log.Debugf("The current tips are: %s", currentTips) + log.Debugf("The number of tips is: %d", len(currentTips)) + log.Tracef("The current tips are: %s", currentTips) newTipParents, err := csm.dagTopologyManager.Parents(stagingArea, newTipHash) if err != nil { @@ -151,7 +152,8 @@ func (csm *consensusStateManager) calculateNewTips( newTips = append(newTips, currentTip) } } - log.Debugf("The calculated new tips are: %s", newTips) + log.Debugf("The new number of tips is: %d", len(newTips)) + log.Tracef("The new tips are: %s", newTips) return newTips, nil } diff --git a/domain/consensus/processes/consensusstatemanager/calculate_past_utxo.go b/domain/consensus/processes/consensusstatemanager/calculate_past_utxo.go index d1cb3bb49..e67ed39d0 100644 --- a/domain/consensus/processes/consensusstatemanager/calculate_past_utxo.go +++ b/domain/consensus/processes/consensusstatemanager/calculate_past_utxo.go @@ -61,8 +61,7 @@ func (csm *consensusStateManager) calculatePastUTXOAndAcceptanceDataWithSelected } log.Debugf("Applying blue blocks to the selected parent past UTXO of block %s", blockHash) - acceptanceData, utxoDiff, err := csm.applyMergeSetBlocks( - stagingArea, blockHash, selectedParentPastUTXO, blockGHOSTDAGData, daaScore) + acceptanceData, utxoDiff, err := csm.applyMergeSetBlocks(stagingArea, blockHash, selectedParentPastUTXO, daaScore) if err != nil { return nil, nil, nil, err } @@ -136,13 +135,16 @@ func (csm *consensusStateManager) restorePastUTXO( } func (csm *consensusStateManager) applyMergeSetBlocks(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash, - selectedParentPastUTXODiff externalapi.UTXODiff, ghostdagData *model.BlockGHOSTDAGData, daaScore uint64) ( + selectedParentPastUTXODiff externalapi.UTXODiff, daaScore uint64) ( externalapi.AcceptanceData, externalapi.MutableUTXODiff, error) { log.Debugf("applyMergeSetBlocks start for block %s", blockHash) defer log.Debugf("applyMergeSetBlocks end for block %s", blockHash) - mergeSetHashes := ghostdagData.MergeSet() + mergeSetHashes, err := csm.ghostdagManager.GetSortedMergeSet(stagingArea, blockHash) + if err != nil { + return nil, nil, err + } log.Debugf("Merge set for block %s is %v", blockHash, mergeSetHashes) mergeSetBlocks, err := csm.blockStore.Blocks(csm.databaseContext, stagingArea, mergeSetHashes) if err != nil { @@ -266,8 +268,7 @@ func (csm *consensusStateManager) maybeAcceptTransaction(stagingArea *model.Stag return true, accumulatedMassAfter, nil } -func (csm *consensusStateManager) checkTransactionMass( - transaction *externalapi.DomainTransaction, accumulatedMassBefore uint64) ( +func (csm *consensusStateManager) checkTransactionMass(transaction *externalapi.DomainTransaction, accumulatedMassBefore uint64) ( isAccepted bool, accumulatedMassAfter uint64) { transactionID := consensushashing.TransactionID(transaction) diff --git a/domain/consensus/processes/consensusstatemanager/pick_virtual_parents.go b/domain/consensus/processes/consensusstatemanager/pick_virtual_parents.go index c49ddfd83..40be9ed06 100644 --- a/domain/consensus/processes/consensusstatemanager/pick_virtual_parents.go +++ b/domain/consensus/processes/consensusstatemanager/pick_virtual_parents.go @@ -2,6 +2,7 @@ package consensusstatemanager import ( "github.com/kaspanet/kaspad/infrastructure/logger" + "github.com/kaspanet/kaspad/util/math" "github.com/pkg/errors" "github.com/kaspanet/kaspad/domain/consensus/model" @@ -34,7 +35,16 @@ func (csm *consensusStateManager) pickVirtualParents(stagingArea *model.StagingA } log.Debugf("The selected parent of the virtual is: %s", virtualSelectedParent) - candidates := candidatesHeap.ToSlice() + // Limit to maxBlockParents*3 candidates, that way we don't go over thousands of tips when the network isn't healthy. + // There's no specific reason for a factor of 3, and its not a consensus rule, just an estimation saying we probably + // don't want to consider and calculate 3 times the amount of candidates for the set of parents. + maxCandidates := int(csm.maxBlockParents) * 3 + candidateAllocationSize := math.MinInt(maxCandidates, candidatesHeap.Len()) + candidates := make([]*externalapi.DomainHash, 0, candidateAllocationSize) + for len(candidates) < maxCandidates && candidatesHeap.Len() > 0 { + candidates = append(candidates, candidatesHeap.Pop()) + } + // prioritize half the blocks with highest blueWork and half with lowest, so the network will merge splits faster. if len(candidates) >= int(csm.maxBlockParents) { // We already have the selectedParent, so we're left with csm.maxBlockParents-1. @@ -45,12 +55,6 @@ func (csm *consensusStateManager) pickVirtualParents(stagingArea *model.StagingA end-- } } - // Limit to maxBlockParents*3 candidates, that way we don't go over thousands of tips when the network isn't healthy. - // There's no specific reason for a factor of 3, and its not a consensus rule, just an estimation saying we probably - // don't want to consider and calculate 3 times the amount of candidates for the set of parents. - if len(candidates) > int(csm.maxBlockParents)*3 { - candidates = candidates[:int(csm.maxBlockParents)*3] - } selectedVirtualParents := []*externalapi.DomainHash{virtualSelectedParent} mergeSetSize := uint64(1) // starts counting from 1 because selectedParent is already in the mergeSet diff --git a/domain/consensus/processes/consensusstatemanager/resolve_block_status_test.go b/domain/consensus/processes/consensusstatemanager/resolve_block_status_test.go index 97d7ab8b9..280b25e9a 100644 --- a/domain/consensus/processes/consensusstatemanager/resolve_block_status_test.go +++ b/domain/consensus/processes/consensusstatemanager/resolve_block_status_test.go @@ -2,11 +2,12 @@ package consensusstatemanager_test import ( "errors" + "github.com/kaspanet/kaspad/domain/consensus/utils/constants" + "github.com/kaspanet/kaspad/domain/consensus/utils/subnetworks" + "github.com/kaspanet/kaspad/domain/consensus/utils/utxo" "testing" "github.com/kaspanet/kaspad/domain/consensus/model" - "github.com/kaspanet/kaspad/domain/consensus/utils/constants" - "github.com/kaspanet/kaspad/domain/consensus/utils/subnetworks" "github.com/kaspanet/kaspad/domain/consensus/ruleerrors" @@ -157,14 +158,15 @@ func TestDoubleSpends(t *testing.T) { }) } -// TestTransactionAcceptance checks that blue blocks transactions are favoured above -// red blocks transactions, and that the block reward is paid only for blue blocks. +// TestTransactionAcceptance checks that block transactions are accepted correctly when the merge set is sorted topologically. +// DAG diagram: +// genesis <- blockA <- blockB <- blockC <- ..(chain of k-blocks).. lastBlockInChain <- blockD <- blockE <- blockF +// ^ ^ | +// | redBlock <------------------------ blueChildOfRedBlock <-------------------- func TestTransactionAcceptance(t *testing.T) { testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) { stagingArea := model.NewStagingArea() - consensusConfig.BlockCoinbaseMaturity = 0 - factory := consensus.NewFactory() testConsensus, teardown, err := factory.NewTestConsensus(consensusConfig, "TestTransactionAcceptance") if err != nil { @@ -172,221 +174,199 @@ func TestTransactionAcceptance(t *testing.T) { } defer teardown(false) - fundingBlock1Hash, _, err := testConsensus.AddBlock([]*externalapi.DomainHash{consensusConfig.GenesisHash}, nil, nil) + blockHashA, _, err := testConsensus.AddBlock([]*externalapi.DomainHash{consensusConfig.GenesisHash}, nil, nil) if err != nil { - t.Fatalf("Error creating fundingBlock1: %+v", err) + t.Fatalf("Error creating blockA: %+v", err) } - - fundingBlock2Hash, _, err := testConsensus.AddBlock([]*externalapi.DomainHash{fundingBlock1Hash}, nil, nil) + blockHashB, _, err := testConsensus.AddBlock([]*externalapi.DomainHash{blockHashA}, nil, nil) if err != nil { - t.Fatalf("Error creating fundingBlock2: %+v", err) + t.Fatalf("Error creating blockB: %+v", err) } - - // Generate fundingBlock3 to pay for fundingBlock2 - fundingBlock3Hash, _, err := testConsensus.AddBlock([]*externalapi.DomainHash{fundingBlock2Hash}, nil, nil) + blockHashC, _, err := testConsensus.AddBlock([]*externalapi.DomainHash{blockHashB}, nil, nil) if err != nil { - t.Fatalf("Error creating fundingBlock3: %+v", err) + t.Fatalf("Error creating blockC: %+v", err) } - - // Add a chain of K blocks above fundingBlock3 so we'll + // Add a chain of K blocks above blockC so we'll // be able to mine a red block on top of it. - tipHash := fundingBlock3Hash + chainTipHash := blockHashC for i := model.KType(0); i < consensusConfig.K; i++ { var err error - tipHash, _, err = testConsensus.AddBlock([]*externalapi.DomainHash{tipHash}, nil, nil) + chainTipHash, _, err = testConsensus.AddBlock([]*externalapi.DomainHash{chainTipHash}, nil, nil) if err != nil { - t.Fatalf("Error creating fundingBlock1: %+v", err) + t.Fatalf("Error creating a block: %+v", err) } } - - fundingBlock2, err := testConsensus.GetBlock(fundingBlock2Hash) + lastBlockInChain := chainTipHash + blockC, err := testConsensus.GetBlock(blockHashC) if err != nil { - t.Fatalf("Error getting fundingBlock: %+v", err) + t.Fatalf("Error getting blockC: %+v", err) } - - fundingTransaction1 := fundingBlock2.Transactions[transactionhelper.CoinbaseTransactionIndex] - - fundingBlock3, err := testConsensus.GetBlock(fundingBlock3Hash) + fees := uint64(1) + transactionFromBlockC := blockC.Transactions[transactionhelper.CoinbaseTransactionIndex] + // transactionFromRedBlock is spending TransactionFromBlockC. + transactionFromRedBlock, err := testutils.CreateTransaction(transactionFromBlockC, fees) if err != nil { - t.Fatalf("Error getting fundingBlock: %+v", err) + t.Fatalf("Error creating a transactionFromRedBlock: %+v", err) } - - fundingTransaction2 := fundingBlock3.Transactions[transactionhelper.CoinbaseTransactionIndex] - - spendingTransaction1, err := testutils.CreateTransaction(fundingTransaction1, 1) + transactionFromRedBlockInput0UTXOEntry, err := testConsensus.ConsensusStateStore(). + UTXOByOutpoint(testConsensus.DatabaseContext(), stagingArea, &transactionFromRedBlock.Inputs[0].PreviousOutpoint) if err != nil { - t.Fatalf("Error creating spendingTransaction1: %+v", err) + t.Fatalf("Error getting UTXOEntry for transactionFromRedBlockInput: %s", err) } - spendingTransaction1UTXOEntry, err := testConsensus.ConsensusStateStore(). - UTXOByOutpoint(testConsensus.DatabaseContext(), stagingArea, &spendingTransaction1.Inputs[0].PreviousOutpoint) - if err != nil { - t.Fatalf("Error getting UTXOEntry for spendingTransaction1: %s", err) - } - - spendingTransaction2, err := testutils.CreateTransaction(fundingTransaction2, 1) - if err != nil { - t.Fatalf("Error creating spendingTransaction1: %+v", err) - } - spendingTransaction2UTXOEntry, err := testConsensus.ConsensusStateStore(). - UTXOByOutpoint(testConsensus.DatabaseContext(), stagingArea, &spendingTransaction2.Inputs[0].PreviousOutpoint) - if err != nil { - t.Fatalf("Error getting UTXOEntry for spendingTransaction2: %s", err) - } - - redHash, _, err := testConsensus.AddBlock([]*externalapi.DomainHash{fundingBlock3Hash}, nil, - []*externalapi.DomainTransaction{spendingTransaction1, spendingTransaction2}) + redHash, _, err := testConsensus.AddBlock([]*externalapi.DomainHash{blockHashC}, nil, + []*externalapi.DomainTransaction{transactionFromRedBlock}) if err != nil { t.Fatalf("Error creating redBlock: %+v", err) } - blueScriptPublicKey := &externalapi.ScriptPublicKey{Script: []byte{1}, Version: 0} - blueHash, _, err := testConsensus.AddBlock([]*externalapi.DomainHash{tipHash}, &externalapi.DomainCoinbaseData{ - ScriptPublicKey: blueScriptPublicKey, - ExtraData: nil, - }, - []*externalapi.DomainTransaction{spendingTransaction1}) + transactionFromBlueChildOfRedBlock, err := testutils.CreateTransaction(transactionFromRedBlock, fees) if err != nil { - t.Fatalf("Error creating blue: %+v", err) + t.Fatalf("Error creating transactionFromBlueChildOfRedBlock: %+v", err) } - - // Mining two blocks so tipHash will definitely be the selected tip. - tipHash, _, err = testConsensus.AddBlock([]*externalapi.DomainHash{tipHash}, nil, nil) + transactionFromBlueChildOfRedBlockInput0UTXOEntry, err := testConsensus.ConsensusStateStore(). + UTXOByOutpoint(testConsensus.DatabaseContext(), stagingArea, &transactionFromBlueChildOfRedBlock.Inputs[0].PreviousOutpoint) if err != nil { - t.Fatalf("Error creating tip: %+v", err) + t.Fatalf("Error getting UTXOEntry for transactionFromBlueChildOfRedBlockInput: %s", err) } - - finalTipSelectedParentScriptPublicKey := &externalapi.ScriptPublicKey{Script: []byte{3}, Version: 0} - finalTipSelectedParentHash, _, err := testConsensus.AddBlock([]*externalapi.DomainHash{tipHash}, + blueChildOfRedBlockScriptPublicKey := &externalapi.ScriptPublicKey{Script: []byte{3}, Version: 0} + // The blueChildOfRedBlock contains a transaction that spent an output from the red block. + hashBlueChildOfRedBlock, _, err := testConsensus.AddBlock([]*externalapi.DomainHash{lastBlockInChain, redHash}, &externalapi.DomainCoinbaseData{ - ScriptPublicKey: finalTipSelectedParentScriptPublicKey, + ScriptPublicKey: blueChildOfRedBlockScriptPublicKey, + ExtraData: nil, + }, []*externalapi.DomainTransaction{transactionFromBlueChildOfRedBlock}) + if err != nil { + t.Fatalf("Error creating blueChildOfRedBlock: %+v", err) + } + + // K blocks minded between blockC and blockD. + blockHashD, _, err := testConsensus.AddBlock([]*externalapi.DomainHash{lastBlockInChain}, nil, nil) + if err != nil { + t.Fatalf("Error creating blockD : %+v", err) + } + blockEScriptPublicKey := &externalapi.ScriptPublicKey{Script: []byte{4}, Version: 0} + blockHashE, _, err := testConsensus.AddBlock([]*externalapi.DomainHash{blockHashD}, + &externalapi.DomainCoinbaseData{ + ScriptPublicKey: blockEScriptPublicKey, ExtraData: nil, }, nil) if err != nil { - t.Fatalf("Error creating tip: %+v", err) + t.Fatalf("Error creating blockE: %+v", err) } - - finalTipScriptPublicKey := &externalapi.ScriptPublicKey{Script: []byte{4}, Version: 0} - finalTipHash, _, err := testConsensus.AddBlock([]*externalapi.DomainHash{finalTipSelectedParentHash, redHash, blueHash}, + blockFScriptPublicKey := &externalapi.ScriptPublicKey{Script: []byte{5}, Version: 0} + blockHashF, _, err := testConsensus.AddBlock([]*externalapi.DomainHash{blockHashE, hashBlueChildOfRedBlock}, &externalapi.DomainCoinbaseData{ - ScriptPublicKey: finalTipScriptPublicKey, + ScriptPublicKey: blockFScriptPublicKey, ExtraData: nil, - }, - nil) + }, nil) if err != nil { - t.Fatalf("Error creating finalTip: %+v", err) + t.Fatalf("Error creating blockF: %+v", err) } - acceptanceData, err := testConsensus.AcceptanceDataStore().Get(testConsensus.DatabaseContext(), stagingArea, finalTipHash) + acceptanceData, err := testConsensus.AcceptanceDataStore().Get(testConsensus.DatabaseContext(), stagingArea, blockHashF) if err != nil { t.Fatalf("Error getting acceptance data: %+v", err) } - - finalTipSelectedParent, err := testConsensus.GetBlock(finalTipSelectedParentHash) + blueChildOfRedBlock, err := testConsensus.GetBlock(hashBlueChildOfRedBlock) if err != nil { - t.Fatalf("Error getting finalTipSelectedParent: %+v", err) + t.Fatalf("Error getting blueChildOfRedBlock: %+v", err) } - - blue, err := testConsensus.GetBlock(blueHash) + blockE, err := testConsensus.GetBlock(blockHashE) if err != nil { - t.Fatalf("Error getting blue: %+v", err) + t.Fatalf("Error getting blockE: %+v", err) } - - red, err := testConsensus.GetBlock(redHash) + redBlock, err := testConsensus.GetBlock(redHash) if err != nil { - t.Fatalf("Error getting red: %+v", err) + t.Fatalf("Error getting redBlock: %+v", err) } - - // We expect spendingTransaction1 to be accepted by the blue block and not by the red one, because - // blue blocks in the merge set should always be ordered before red blocks in the merge set. - // We also expect spendingTransaction2 to be accepted by the red because nothing conflicts it. + blockF, err := testConsensus.GetBlock(blockHashF) + if err != nil { + t.Fatalf("Error getting blockF: %+v", err) + } + updatedDAAScoreVirtualBlock := 25 + //We expect the second transaction in the "blue block" (blueChildOfRedBlock) to be accepted because the merge set is ordered topologically + //and the red block is ordered topologically before the "blue block" so the input is known in the UTXOSet. expectedAcceptanceData := externalapi.AcceptanceData{ { - BlockHash: finalTipSelectedParentHash, + BlockHash: blockHashE, TransactionAcceptanceData: []*externalapi.TransactionAcceptanceData{ { - Transaction: finalTipSelectedParent.Transactions[0], + Transaction: blockE.Transactions[0], Fee: 0, IsAccepted: true, TransactionInputUTXOEntries: []externalapi.UTXOEntry{}, }, }, }, - { - BlockHash: blueHash, - TransactionAcceptanceData: []*externalapi.TransactionAcceptanceData{ - { - Transaction: blue.Transactions[0], - Fee: 0, - IsAccepted: false, - TransactionInputUTXOEntries: []externalapi.UTXOEntry{}, - }, - { - Transaction: spendingTransaction1, - Fee: 1, - IsAccepted: true, - TransactionInputUTXOEntries: []externalapi.UTXOEntry{spendingTransaction1UTXOEntry}, - }, - }, - }, { BlockHash: redHash, TransactionAcceptanceData: []*externalapi.TransactionAcceptanceData{ - { - Transaction: red.Transactions[0], + { //Coinbase transaction outputs are added to the UTXO-set only if they are in the selected parent chain, + // and this block isn't. + Transaction: redBlock.Transactions[0], Fee: 0, IsAccepted: false, TransactionInputUTXOEntries: []externalapi.UTXOEntry{}, }, { - Transaction: spendingTransaction1, - Fee: 0, - IsAccepted: false, - TransactionInputUTXOEntries: []externalapi.UTXOEntry{}, - }, - { - Transaction: spendingTransaction2, - Fee: 1, + Transaction: redBlock.Transactions[1], + Fee: fees, IsAccepted: true, - TransactionInputUTXOEntries: []externalapi.UTXOEntry{spendingTransaction2UTXOEntry}, + TransactionInputUTXOEntries: []externalapi.UTXOEntry{transactionFromRedBlockInput0UTXOEntry}, + }, + }, + }, + { + BlockHash: hashBlueChildOfRedBlock, + TransactionAcceptanceData: []*externalapi.TransactionAcceptanceData{ + { //Coinbase transaction outputs are added to the UTXO-set only if they are in the selected parent chain, + // and this block isn't. + Transaction: blueChildOfRedBlock.Transactions[0], + Fee: 0, + IsAccepted: false, + TransactionInputUTXOEntries: []externalapi.UTXOEntry{}, + }, + { // The DAAScore was calculated by the virtual block pov. The DAAScore has changed since more blocks were added to the DAG. + // So we will change the DAAScore in the UTXOEntryInput to the updated virtual DAAScore. + Transaction: blueChildOfRedBlock.Transactions[1], + Fee: fees, + IsAccepted: true, + TransactionInputUTXOEntries: []externalapi.UTXOEntry{ + utxo.NewUTXOEntry(transactionFromBlueChildOfRedBlockInput0UTXOEntry.Amount(), + transactionFromBlueChildOfRedBlockInput0UTXOEntry.ScriptPublicKey(), + transactionFromBlueChildOfRedBlockInput0UTXOEntry.IsCoinbase(), uint64(updatedDAAScoreVirtualBlock))}, }, }, }, } - if !acceptanceData.Equal(expectedAcceptanceData) { t.Fatalf("The acceptance data is not the expected acceptance data") } - - finalTip, err := testConsensus.GetBlock(finalTipHash) - if err != nil { - t.Fatalf("Error getting finalTip: %+v", err) - } - - // We expect the coinbase transaction to pay reward for the selected parent, the - // blue block, and bestow the red block reward to the merging block. + // We expect the coinbase transaction to pay reward for the selected parent(block E), the + // blueChildOfRedBlock, and bestow the red block reward to the merging block. expectedCoinbase := &externalapi.DomainTransaction{ Version: constants.MaxTransactionVersion, Inputs: nil, Outputs: []*externalapi.DomainTransactionOutput{ { Value: 50 * constants.SompiPerKaspa, - ScriptPublicKey: finalTipSelectedParentScriptPublicKey, + ScriptPublicKey: blockEScriptPublicKey, }, { - Value: 50*constants.SompiPerKaspa + 1, // testutils.CreateTransaction pays a fee of 1 sompi - ScriptPublicKey: blueScriptPublicKey, + Value: 50*constants.SompiPerKaspa + fees, // testutils.CreateTransaction pays fees + ScriptPublicKey: blueChildOfRedBlockScriptPublicKey, }, { - Value: 50*constants.SompiPerKaspa + 1, - ScriptPublicKey: finalTipScriptPublicKey, + Value: 50*constants.SompiPerKaspa + fees, // testutils.CreateTransaction pays fees + ScriptPublicKey: blockFScriptPublicKey, }, }, LockTime: 0, SubnetworkID: subnetworks.SubnetworkIDCoinbase, Gas: 0, - Payload: finalTip.Transactions[0].Payload, + Payload: blockF.Transactions[0].Payload, } - if !finalTip.Transactions[transactionhelper.CoinbaseTransactionIndex].Equal(expectedCoinbase) { + if !blockF.Transactions[transactionhelper.CoinbaseTransactionIndex].Equal(expectedCoinbase) { t.Fatalf("Unexpected coinbase transaction") } }) diff --git a/domain/consensus/processes/difficultymanager/difficultymanager.go b/domain/consensus/processes/difficultymanager/difficultymanager.go index 793368ccc..7dcf6ff36 100644 --- a/domain/consensus/processes/difficultymanager/difficultymanager.go +++ b/domain/consensus/processes/difficultymanager/difficultymanager.go @@ -167,9 +167,13 @@ func (dm *difficultyManager) calculateDaaScoreAndAddedBlocks(stagingArea *model. if err != nil { return 0, nil, err } + mergeSetLength := len(ghostdagData.MergeSetBlues()) + len(ghostdagData.MergeSetReds()) + mergeSet := make(map[externalapi.DomainHash]struct{}, mergeSetLength) + for _, hash := range ghostdagData.MergeSetBlues() { + mergeSet[*hash] = struct{}{} + } - mergeSet := make(map[externalapi.DomainHash]struct{}, len(ghostdagData.MergeSet())) - for _, hash := range ghostdagData.MergeSet() { + for _, hash := range ghostdagData.MergeSetReds() { mergeSet[*hash] = struct{}{} } diff --git a/domain/consensus/processes/difficultymanager/hashrate.go b/domain/consensus/processes/difficultymanager/hashrate.go index 1bd04e10e..b217b8c0f 100644 --- a/domain/consensus/processes/difficultymanager/hashrate.go +++ b/domain/consensus/processes/difficultymanager/hashrate.go @@ -1,26 +1,31 @@ package difficultymanager import ( + "math/big" + "github.com/kaspanet/kaspad/domain/consensus/model" + "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" "github.com/kaspanet/kaspad/infrastructure/logger" "github.com/pkg/errors" - "math/big" ) -func (dm *difficultyManager) EstimateNetworkHashesPerSecond(windowSize int) (uint64, error) { +func (dm *difficultyManager) EstimateNetworkHashesPerSecond(startHash *externalapi.DomainHash, windowSize int) (uint64, error) { onEnd := logger.LogAndMeasureExecutionTime(log, "EstimateNetworkHashesPerSecond") defer onEnd() stagingArea := model.NewStagingArea() - return dm.estimateNetworkHashesPerSecond(stagingArea, windowSize) + return dm.estimateNetworkHashesPerSecond(stagingArea, startHash, windowSize) } -func (dm *difficultyManager) estimateNetworkHashesPerSecond(stagingArea *model.StagingArea, windowSize int) (uint64, error) { - if windowSize < 2 { - return 0, errors.Errorf("windowSize must be equal to or greater than 2") +func (dm *difficultyManager) estimateNetworkHashesPerSecond(stagingArea *model.StagingArea, + startHash *externalapi.DomainHash, windowSize int) (uint64, error) { + + const minWindowSize = 1000 + if windowSize < minWindowSize { + return 0, errors.Errorf("windowSize must be equal to or greater than %d", minWindowSize) } - blockWindow, windowHashes, err := dm.blockWindow(stagingArea, model.VirtualBlockHash, windowSize) + blockWindow, windowHashes, err := dm.blockWindow(stagingArea, startHash, windowSize) if err != nil { return 0, err } @@ -57,8 +62,13 @@ func (dm *difficultyManager) estimateNetworkHashesPerSecond(stagingArea *model.S } } + windowsDiff := (maxWindowTimestamp - minWindowTimestamp) / 1000 // Divided by 1000 to convert milliseconds to seconds + if windowsDiff == 0 { + return 0, nil + } + nominator := new(big.Int).Sub(maxWindowBlueWork, minWindowBlueWork) - denominator := big.NewInt((maxWindowTimestamp - minWindowTimestamp) / 1000) // Divided by 1000 to convert milliseconds to seconds + denominator := big.NewInt(windowsDiff) networkHashesPerSecondBigInt := new(big.Int).Div(nominator, denominator) return networkHashesPerSecondBigInt.Uint64(), nil } diff --git a/domain/consensus/processes/ghostdag2/ghostdagimpl.go b/domain/consensus/processes/ghostdag2/ghostdagimpl.go index c48276e04..22003878a 100644 --- a/domain/consensus/processes/ghostdag2/ghostdagimpl.go +++ b/domain/consensus/processes/ghostdag2/ghostdagimpl.go @@ -409,3 +409,7 @@ func (gh *ghostdagHelper) ChooseSelectedParent(stagingArea *model.StagingArea, b func (gh *ghostdagHelper) Less(blockHashA *externalapi.DomainHash, ghostdagDataA *model.BlockGHOSTDAGData, blockHashB *externalapi.DomainHash, ghostdagDataB *model.BlockGHOSTDAGData) bool { panic("implement me") } + +func (gh *ghostdagHelper) GetSortedMergeSet(*model.StagingArea, *externalapi.DomainHash) ([]*externalapi.DomainHash, error) { + panic("implement me") +} diff --git a/domain/consensus/processes/ghostdagmanager/mergeset.go b/domain/consensus/processes/ghostdagmanager/mergeset.go index 8b2fd49c1..f6851947f 100644 --- a/domain/consensus/processes/ghostdagmanager/mergeset.go +++ b/domain/consensus/processes/ghostdagmanager/mergeset.go @@ -82,3 +82,47 @@ func (gm *ghostdagManager) sortMergeSet(stagingArea *model.StagingArea, mergeSet }) return err } + +// GetSortedMergeSet return the merge set sorted in a toplogical order. +func (gm *ghostdagManager) GetSortedMergeSet(stagingArea *model.StagingArea, + current *externalapi.DomainHash) ([]*externalapi.DomainHash, error) { + + currentGhostdagData, err := gm.ghostdagDataStore.Get(gm.databaseContext, stagingArea, current) + if err != nil { + return nil, err + } + + blueMergeSet := currentGhostdagData.MergeSetBlues() + redMergeSet := currentGhostdagData.MergeSetReds() + sortedMergeSet := make([]*externalapi.DomainHash, 0, len(blueMergeSet)+len(redMergeSet)) + // If the current block is the genesis block: + if len(blueMergeSet) == 0 { + return sortedMergeSet, nil + } + selectedParent, blueMergeSet := blueMergeSet[0], blueMergeSet[1:] + sortedMergeSet = append(sortedMergeSet, selectedParent) + i, j := 0, 0 + for i < len(blueMergeSet) && j < len(redMergeSet) { + currentBlue := blueMergeSet[i] + currentBlueGhostdagData, err := gm.ghostdagDataStore.Get(gm.databaseContext, stagingArea, currentBlue) + if err != nil { + return nil, err + } + currentRed := redMergeSet[j] + currentRedGhostdagData, err := gm.ghostdagDataStore.Get(gm.databaseContext, stagingArea, currentRed) + if err != nil { + return nil, err + } + if gm.Less(currentBlue, currentBlueGhostdagData, currentRed, currentRedGhostdagData) { + sortedMergeSet = append(sortedMergeSet, currentBlue) + i++ + } else { + sortedMergeSet = append(sortedMergeSet, currentRed) + j++ + } + } + sortedMergeSet = append(sortedMergeSet, blueMergeSet[i:]...) + sortedMergeSet = append(sortedMergeSet, redMergeSet[j:]...) + + return sortedMergeSet, nil +} diff --git a/domain/consensus/processes/syncmanager/antipast.go b/domain/consensus/processes/syncmanager/antipast.go index 6909118f6..46b270cfa 100644 --- a/domain/consensus/processes/syncmanager/antipast.go +++ b/domain/consensus/processes/syncmanager/antipast.go @@ -71,7 +71,7 @@ func (sm *syncManager) antiPastHashesBetween(stagingArea *model.StagingArea, low // Since the rest of the merge set is in the anticone of selectedParent, it's position in the list does not // matter, even though it's blue score is the highest, we can arbitrarily decide it comes first. // Therefore we first append the selectedParent, then the rest of blocks in ghostdag order. - sortedMergeSet, err := sm.getSortedMergeSet(stagingArea, current) + sortedMergeSet, err := sm.ghostdagManager.GetSortedMergeSet(stagingArea, current) if err != nil { return nil, nil, err } @@ -97,49 +97,9 @@ func (sm *syncManager) antiPastHashesBetween(stagingArea *model.StagingArea, low return blockHashes, highHash, nil } -func (sm *syncManager) getSortedMergeSet(stagingArea *model.StagingArea, current *externalapi.DomainHash) ( - []*externalapi.DomainHash, error) { - - currentGhostdagData, err := sm.ghostdagDataStore.Get(sm.databaseContext, stagingArea, current) - if err != nil { - return nil, err - } - - blueMergeSet := currentGhostdagData.MergeSetBlues() - redMergeSet := currentGhostdagData.MergeSetReds() - sortedMergeSet := make([]*externalapi.DomainHash, 0, len(blueMergeSet)+len(redMergeSet)) - selectedParent, blueMergeSet := blueMergeSet[0], blueMergeSet[1:] - sortedMergeSet = append(sortedMergeSet, selectedParent) - i, j := 0, 0 - for i < len(blueMergeSet) && j < len(redMergeSet) { - currentBlue := blueMergeSet[i] - currentBlueGhostdagData, err := sm.ghostdagDataStore.Get(sm.databaseContext, stagingArea, currentBlue) - if err != nil { - return nil, err - } - currentRed := redMergeSet[j] - currentRedGhostdagData, err := sm.ghostdagDataStore.Get(sm.databaseContext, stagingArea, currentRed) - if err != nil { - return nil, err - } - if sm.ghostdagManager.Less(currentBlue, currentBlueGhostdagData, currentRed, currentRedGhostdagData) { - sortedMergeSet = append(sortedMergeSet, currentBlue) - i++ - } else { - sortedMergeSet = append(sortedMergeSet, currentRed) - j++ - } - } - sortedMergeSet = append(sortedMergeSet, blueMergeSet[i:]...) - sortedMergeSet = append(sortedMergeSet, redMergeSet[j:]...) - - return sortedMergeSet, nil -} - -func (sm *syncManager) findHighHashAccordingToMaxBlueScoreDifference(stagingArea *model.StagingArea, - lowHash *externalapi.DomainHash, highHash *externalapi.DomainHash, maxBlueScoreDifference uint64, - highBlockGHOSTDAGData *model.BlockGHOSTDAGData, lowBlockGHOSTDAGData *model.BlockGHOSTDAGData) ( - *externalapi.DomainHash, error) { +func (sm *syncManager) findHighHashAccordingToMaxBlueScoreDifference(stagingArea *model.StagingArea, lowHash *externalapi.DomainHash, + highHash *externalapi.DomainHash, maxBlueScoreDifference uint64, highBlockGHOSTDAGData *model.BlockGHOSTDAGData, + lowBlockGHOSTDAGData *model.BlockGHOSTDAGData) (*externalapi.DomainHash, error) { if highBlockGHOSTDAGData.BlueScore()-lowBlockGHOSTDAGData.BlueScore() <= maxBlueScoreDifference { return highHash, nil diff --git a/infrastructure/network/netadapter/server/grpcserver/protowire/rpc.pb.go b/infrastructure/network/netadapter/server/grpcserver/protowire/rpc.pb.go index c3f210765..742929ad2 100644 --- a/infrastructure/network/netadapter/server/grpcserver/protowire/rpc.pb.go +++ b/infrastructure/network/netadapter/server/grpcserver/protowire/rpc.pb.go @@ -17,11 +17,12 @@ package protowire import ( + reflect "reflect" + sync "sync" + proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" - reflect "reflect" - sync "sync" ) const ( @@ -5008,6 +5009,7 @@ type EstimateNetworkHashesPerSecondRequestMessage struct { unknownFields protoimpl.UnknownFields WindowSize uint32 `protobuf:"varint,1,opt,name=windowSize,proto3" json:"windowSize,omitempty"` + StartHash string `protobuf:"bytes,2,opt,name=blockHash,proto3" json:"blockHash,omitempty"` } func (x *EstimateNetworkHashesPerSecondRequestMessage) Reset() { @@ -5049,6 +5051,13 @@ func (x *EstimateNetworkHashesPerSecondRequestMessage) GetWindowSize() uint32 { return 0 } +func (x *EstimateNetworkHashesPerSecondRequestMessage) GetBlockHash() string { + if x != nil { + return x.StartHash + } + return "" +} + type EstimateNetworkHashesPerSecondResponseMessage struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -5727,25 +5736,26 @@ var file_rpc_proto_rawDesc = []byte{ 0x01, 0x28, 0x04, 0x52, 0x0b, 0x6d, 0x65, 0x6d, 0x70, 0x6f, 0x6f, 0x6c, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0xe8, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x52, 0x50, 0x43, - 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x4e, 0x0a, 0x2c, + 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x6c, 0x0a, 0x2c, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x50, 0x65, 0x72, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x53, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, - 0x52, 0x0a, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x93, 0x01, 0x0a, - 0x2d, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, - 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x50, 0x65, 0x72, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x36, - 0x0a, 0x16, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x50, - 0x65, 0x72, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x16, + 0x52, 0x0a, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1c, 0x0a, 0x09, + 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x22, 0x93, 0x01, 0x0a, 0x2d, 0x45, + 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x48, 0x61, + 0x73, 0x68, 0x65, 0x73, 0x50, 0x65, 0x72, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x36, 0x0a, 0x16, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x50, 0x65, 0x72, - 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, - 0xe8, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, - 0x72, 0x65, 0x2e, 0x52, 0x50, 0x43, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, - 0x6f, 0x72, 0x42, 0x26, 0x5a, 0x24, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x6e, 0x65, 0x74, 0x2f, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x64, - 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x33, + 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x16, 0x6e, 0x65, + 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x50, 0x65, 0x72, 0x53, 0x65, + 0x63, 0x6f, 0x6e, 0x64, 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0xe8, 0x07, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, + 0x2e, 0x52, 0x50, 0x43, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, + 0x42, 0x26, 0x5a, 0x24, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6b, + 0x61, 0x73, 0x70, 0x61, 0x6e, 0x65, 0x74, 0x2f, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x64, 0x2f, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x77, 0x69, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/infrastructure/network/netadapter/server/grpcserver/protowire/rpc.proto b/infrastructure/network/netadapter/server/grpcserver/protowire/rpc.proto index dc0efd00c..d38682449 100644 --- a/infrastructure/network/netadapter/server/grpcserver/protowire/rpc.proto +++ b/infrastructure/network/netadapter/server/grpcserver/protowire/rpc.proto @@ -592,6 +592,7 @@ message GetInfoResponseMessage{ message EstimateNetworkHashesPerSecondRequestMessage{ uint32 windowSize = 1; + string blockHash = 2; } message EstimateNetworkHashesPerSecondResponseMessage{ diff --git a/infrastructure/network/netadapter/server/grpcserver/protowire/rpc_estimate_network_hashes_per_second.go b/infrastructure/network/netadapter/server/grpcserver/protowire/rpc_estimate_network_hashes_per_second.go index 8b3a22b46..cee088402 100644 --- a/infrastructure/network/netadapter/server/grpcserver/protowire/rpc_estimate_network_hashes_per_second.go +++ b/infrastructure/network/netadapter/server/grpcserver/protowire/rpc_estimate_network_hashes_per_second.go @@ -15,6 +15,7 @@ func (x *KaspadMessage_EstimateNetworkHashesPerSecondRequest) toAppMessage() (ap func (x *KaspadMessage_EstimateNetworkHashesPerSecondRequest) fromAppMessage(message *appmessage.EstimateNetworkHashesPerSecondRequestMessage) error { x.EstimateNetworkHashesPerSecondRequest = &EstimateNetworkHashesPerSecondRequestMessage{ WindowSize: message.WindowSize, + StartHash: message.StartHash, } return nil } @@ -25,6 +26,7 @@ func (x *EstimateNetworkHashesPerSecondRequestMessage) toAppMessage() (appmessag } return &appmessage.EstimateNetworkHashesPerSecondRequestMessage{ WindowSize: x.WindowSize, + StartHash: x.StartHash, }, nil } diff --git a/infrastructure/network/rpcclient/rpc_estimate_network_hashes_per_second.go b/infrastructure/network/rpcclient/rpc_estimate_network_hashes_per_second.go index 1776aba31..39c00f3f5 100644 --- a/infrastructure/network/rpcclient/rpc_estimate_network_hashes_per_second.go +++ b/infrastructure/network/rpcclient/rpc_estimate_network_hashes_per_second.go @@ -3,8 +3,8 @@ package rpcclient import "github.com/kaspanet/kaspad/app/appmessage" // EstimateNetworkHashesPerSecond sends an RPC request respective to the function's name and returns the RPC server's response -func (c *RPCClient) EstimateNetworkHashesPerSecond(windowSize uint32) (*appmessage.EstimateNetworkHashesPerSecondResponseMessage, error) { - err := c.rpcRouter.outgoingRoute().Enqueue(appmessage.NewEstimateNetworkHashesPerSecondRequestMessage(windowSize)) +func (c *RPCClient) EstimateNetworkHashesPerSecond(startHash string, windowSize uint32) (*appmessage.EstimateNetworkHashesPerSecondResponseMessage, error) { + err := c.rpcRouter.outgoingRoute().Enqueue(appmessage.NewEstimateNetworkHashesPerSecondRequestMessage(startHash, windowSize)) if err != nil { return nil, err } diff --git a/stability-tests/run/run-fast.sh b/stability-tests/run/run-fast.sh index 93c442958..d50cb4c2f 100755 --- a/stability-tests/run/run-fast.sh +++ b/stability-tests/run/run-fast.sh @@ -38,6 +38,10 @@ echo "Running reorg" cd "${PROJECT_ROOT}/reorg/run" && ./run.sh || failedTests+=("reorg") echo "Done running reorg" +echo "Running many-tips" +cd "${PROJECT_ROOT}/many-tips/run" && ./run.sh || failedTests+=("many-tips") +echo "Done running many-tips" + echo "Running netsync - fast" cd "${PROJECT_ROOT}/netsync/run" && ./run-fast.sh || failedTests+=("netsync") echo "Done running netsync - fast" diff --git a/stability-tests/run/run-slow.sh b/stability-tests/run/run-slow.sh index 509404eb5..94d4cc19d 100755 --- a/stability-tests/run/run-slow.sh +++ b/stability-tests/run/run-slow.sh @@ -34,6 +34,10 @@ echo "Running orphans" cd "${PROJECT_ROOT}/orphans/run" && ./run.sh || failedTests+=("orphans") echo "Done running orphans" +echo "Running many-tips" +cd "${PROJECT_ROOT}/many-tips/run" && ./run.sh || failedTests+=("many-tips") +echo "Done running many-tips" + echo "Running reorg" cd "${PROJECT_ROOT}/reorg/run" && ./run-full-finality-window-reorg.sh || failedTests+=("reorg") echo "Done running reorg"