From 7e9b5b90106e962d5063cd004f4fe80464b0d639 Mon Sep 17 00:00:00 2001 From: Ori Newman Date: Wed, 21 Sep 2022 18:58:32 +0300 Subject: [PATCH] Security Patch + HF (#2142) * HF * Fix lint --- app/appmessage/p2p_msgtx.go | 2 +- app/appmessage/p2p_msgtx_test.go | 2 +- app/rpc/rpccontext/verbosedata.go | 13 +++- .../daemon/server/split_transaction.go | 6 +- .../daemon/server/split_transaction_test.go | 2 +- cmd/kaspawallet/sweep.go | 2 +- domain/consensus/consensus.go | 3 +- domain/consensus/factory.go | 10 +++ .../interface_processes_coinbasemanager.go | 2 +- ...nterface_processes_transactionvalidator.go | 2 +- .../processes/blockbuilder/block_builder.go | 9 ++- .../blockbuilder/test_block_builder.go | 4 +- .../validate_and_insert_block_test.go | 2 +- .../blockvalidator/block_body_in_context.go | 2 +- .../blockvalidator/block_body_in_isolation.go | 17 ++++- .../block_body_in_isolation_test.go | 3 +- .../blockvalidator/blockvalidator.go | 3 + ...ation_proof_of_work_and_difficulty_test.go | 2 +- .../coinbasemanager/coinbasemanager.go | 8 ++- .../coinbasemanager_external_test.go | 69 +++++++++++++++++++ .../coinbasemanager/coinbasemanager_test.go | 2 + .../processes/coinbasemanager/payload.go | 10 ++- .../consensus_state_manager.go | 6 +- .../verify_and_build_utxo.go | 10 +-- .../processes/transactionvalidator/mass.go | 4 +- .../transaction_in_context.go | 27 ++++++-- .../transaction_in_isolation.go | 22 +++++- .../transaction_in_isolation_test.go | 11 ++- .../transactionvalidator.go | 50 ++++++++------ .../transactionvalidator_test.go | 26 ------- domain/consensus/ruleerrors/rule_error.go | 4 ++ .../utils/consensushashing/transaction.go | 28 +++++--- domain/consensus/utils/constants/constants.go | 3 + domain/consensus/utils/merkle/merkle.go | 4 +- domain/dagconfig/params.go | 5 ++ .../blocktemplatebuilder.go | 6 +- .../blocktemplatebuilder/txselection.go | 16 +++++ domain/miningmanager/factory.go | 2 +- .../server/grpcserver/connection_loops.go | 21 ++++++ .../protowire/rpc_get_connected_peer_info.go | 2 +- util/txmass/calculator.go | 13 ++-- version/version.go | 2 +- 42 files changed, 325 insertions(+), 112 deletions(-) create mode 100644 domain/consensus/processes/coinbasemanager/coinbasemanager_external_test.go diff --git a/app/appmessage/p2p_msgtx.go b/app/appmessage/p2p_msgtx.go index 2062f03c7..f369a7aea 100644 --- a/app/appmessage/p2p_msgtx.go +++ b/app/appmessage/p2p_msgtx.go @@ -159,7 +159,7 @@ func (msg *MsgTx) IsCoinBase() bool { // TxHash generates the Hash for the transaction. func (msg *MsgTx) TxHash() *externalapi.DomainHash { - return consensushashing.TransactionHash(MsgTxToDomainTransaction(msg)) + return consensushashing.TransactionHash(MsgTxToDomainTransaction(msg), false) } // TxID generates the Hash for the transaction without the signature script, gas and payload fields. diff --git a/app/appmessage/p2p_msgtx_test.go b/app/appmessage/p2p_msgtx_test.go index a75d6228a..57f8e7346 100644 --- a/app/appmessage/p2p_msgtx_test.go +++ b/app/appmessage/p2p_msgtx_test.go @@ -134,7 +134,7 @@ func TestTx(t *testing.T) { // TestTxHash tests the ability to generate the hash of a transaction accurately. func TestTxHashAndID(t *testing.T) { txHash1Str := "93663e597f6c968d32d229002f76408edf30d6a0151ff679fc729812d8cb2acc" - txID1Str := "24079c6d2bdf602fc389cc307349054937744a9c8dc0f07c023e6af0e949a4e7" + txID1Str := "e20225c3d065ee41743607ee627db44d01ef396dc9779b05b2caf55bac50e12d" wantTxID1, err := transactionid.FromString(txID1Str) if err != nil { t.Fatalf("NewTxIDFromStr: %v", err) diff --git a/app/rpc/rpccontext/verbosedata.go b/app/rpc/rpccontext/verbosedata.go index 9bfa5c81e..4de2669cf 100644 --- a/app/rpc/rpccontext/verbosedata.go +++ b/app/rpc/rpccontext/verbosedata.go @@ -122,9 +122,20 @@ func (ctx *Context) PopulateTransactionWithVerboseData( } ctx.Domain.Consensus().PopulateMass(domainTransaction) + + var daaScore uint64 + if domainBlockHeader != nil { + daaScore = domainBlockHeader.DAAScore() + } else { + daaScore, err = ctx.Domain.Consensus().GetVirtualDAAScore() + if err != nil { + return err + } + } + transaction.VerboseData = &appmessage.RPCTransactionVerboseData{ TransactionID: consensushashing.TransactionID(domainTransaction).String(), - Hash: consensushashing.TransactionHash(domainTransaction).String(), + Hash: consensushashing.TransactionHash(domainTransaction, daaScore >= ctx.Config.NetParams().HFDAAScore).String(), Mass: domainTransaction.Mass, } if domainBlockHeader != nil { diff --git a/cmd/kaspawallet/daemon/server/split_transaction.go b/cmd/kaspawallet/daemon/server/split_transaction.go index b2154c900..9e15f6890 100644 --- a/cmd/kaspawallet/daemon/server/split_transaction.go +++ b/cmd/kaspawallet/daemon/server/split_transaction.go @@ -158,7 +158,7 @@ func (s *server) splitAndInputPerSplitCounts(transaction *serialization.Partiall // to calculate how much mass do all the inputs have transactionWithoutInputs := transaction.Tx.Clone() transactionWithoutInputs.Inputs = []*externalapi.DomainTransactionInput{} - massWithoutInputs := s.txMassCalculator.CalculateTransactionMass(transactionWithoutInputs) + massWithoutInputs := s.txMassCalculator.CalculateTransactionMass(transactionWithoutInputs, true) massOfAllInputs := transactionMass - massWithoutInputs @@ -177,7 +177,7 @@ func (s *server) splitAndInputPerSplitCounts(transaction *serialization.Partiall return 0, 0, err } massForEverythingExceptInputsInSplitTransaction := - s.txMassCalculator.CalculateTransactionMass(splitTransactionWithoutInputs.Tx) + s.txMassCalculator.CalculateTransactionMass(splitTransactionWithoutInputs.Tx, true) massForInputsInSplitTransaction := mempool.MaximumStandardTransactionMass - massForEverythingExceptInputsInSplitTransaction inputsPerSplitCount = int(massForInputsInSplitTransaction / massPerInput) @@ -245,7 +245,7 @@ func (s *server) estimateMassAfterSignatures(transaction *serialization.Partiall return 0, err } - return s.txMassCalculator.CalculateTransactionMass(transactionWithSignatures), nil + return s.txMassCalculator.CalculateTransactionMass(transactionWithSignatures, true), nil } func (s *server) moreUTXOsForMergeTransaction(alreadySelectedUTXOs []*libkaspawallet.UTXO, requiredAmount uint64) ( diff --git a/cmd/kaspawallet/daemon/server/split_transaction_test.go b/cmd/kaspawallet/daemon/server/split_transaction_test.go index 0f521bb62..3f2ebb335 100644 --- a/cmd/kaspawallet/daemon/server/split_transaction_test.go +++ b/cmd/kaspawallet/daemon/server/split_transaction_test.go @@ -58,7 +58,7 @@ func TestEstimateMassAfterSignatures(t *testing.T) { t.Fatalf("ExtractTransaction: %+v", err) } - actualMassAfterSignatures := serverInstance.txMassCalculator.CalculateTransactionMass(extractedSignedTx) + actualMassAfterSignatures := serverInstance.txMassCalculator.CalculateTransactionMass(extractedSignedTx, true) if estimatedMassAfterSignatures != actualMassAfterSignatures { t.Errorf("Estimated mass after signatures: %d but actually got %d", diff --git a/cmd/kaspawallet/sweep.go b/cmd/kaspawallet/sweep.go index d36065d05..90da0341f 100644 --- a/cmd/kaspawallet/sweep.go +++ b/cmd/kaspawallet/sweep.go @@ -184,7 +184,7 @@ func createSplitTransactionsWithSchnorrPrivteKey( ScriptPublicKey: scriptPublicKey, } - if massCalculater.CalculateTransactionMass(currentTx)+extraMass >= mempool.MaximumStandardTransactionMass { + if massCalculater.CalculateTransactionMass(currentTx, true)+extraMass >= mempool.MaximumStandardTransactionMass { //in this loop we assume a transaction with one input and one output cannot violate max transaction mass, hence a sanity check. if len(currentTx.Inputs) == 1 { diff --git a/domain/consensus/consensus.go b/domain/consensus/consensus.go index a615869f6..a2a3a724e 100644 --- a/domain/consensus/consensus.go +++ b/domain/consensus/consensus.go @@ -21,6 +21,7 @@ type consensus struct { genesisBlock *externalapi.DomainBlock genesisHash *externalapi.DomainHash + hfDAAScore uint64 expectedDAAWindowDurationInMilliseconds int64 @@ -917,7 +918,7 @@ func (s *consensus) EstimateNetworkHashesPerSecond(startHash *externalapi.Domain } func (s *consensus) PopulateMass(transaction *externalapi.DomainTransaction) { - s.transactionValidator.PopulateMass(transaction) + s.transactionValidator.PopulateMass(transaction, s.hfDAAScore) } func (s *consensus) ResolveVirtual(progressReportCallback func(uint64, uint64)) error { diff --git a/domain/consensus/factory.go b/domain/consensus/factory.go index d53e49bb4..91e5067fd 100644 --- a/domain/consensus/factory.go +++ b/domain/consensus/factory.go @@ -216,6 +216,9 @@ func (f *factory) NewConsensus(config *Config, db infrastructuredatabase.Databas transactionValidator := transactionvalidator.New(config.BlockCoinbaseMaturity, config.EnableNonNativeSubnetworks, config.MaxCoinbasePayloadLength, + config.HFDAAScore, + config.K, + config.CoinbasePayloadScriptPublicKeyMaxLength, dbManager, pastMedianTimeManager, ghostdagDataStore, @@ -237,12 +240,15 @@ func (f *factory) NewConsensus(config *Config, db infrastructuredatabase.Databas config.GenesisBlock.Header.Bits()) coinbaseManager := coinbasemanager.New( dbManager, + config.SubsidyGenesisReward, config.PreDeflationaryPhaseBaseSubsidy, config.CoinbasePayloadScriptPublicKeyMaxLength, config.GenesisHash, config.DeflationaryPhaseDaaScore, config.DeflationaryPhaseBaseSubsidy, + config.HFDAAScore, + dagTraversalManager, ghostdagDataStore, acceptanceDataStore, @@ -278,6 +284,7 @@ func (f *factory) NewConsensus(config *Config, db infrastructuredatabase.Databas config.MaxBlockParents, config.MergeSetSizeLimit, genesisHash, + config.HFDAAScore, ghostdagManager, dagTopologyManager, @@ -346,6 +353,7 @@ func (f *factory) NewConsensus(config *Config, db infrastructuredatabase.Databas config.TimestampDeviationTolerance, config.TargetTimePerBlock, config.MaxBlockLevel, + config.HFDAAScore, dbManager, difficultyManager, @@ -393,6 +401,7 @@ func (f *factory) NewConsensus(config *Config, db infrastructuredatabase.Databas blockBuilder := blockbuilder.New( dbManager, genesisHash, + config.HFDAAScore, difficultyManager, pastMedianTimeManager, @@ -474,6 +483,7 @@ func (f *factory) NewConsensus(config *Config, db infrastructuredatabase.Databas genesisBlock: config.GenesisBlock, genesisHash: config.GenesisHash, + hfDAAScore: config.HFDAAScore, expectedDAAWindowDurationInMilliseconds: config.TargetTimePerBlock.Milliseconds() * int64(config.DifficultyAdjustmentWindowSize), diff --git a/domain/consensus/model/interface_processes_coinbasemanager.go b/domain/consensus/model/interface_processes_coinbasemanager.go index 878c02caf..33bf5ace1 100644 --- a/domain/consensus/model/interface_processes_coinbasemanager.go +++ b/domain/consensus/model/interface_processes_coinbasemanager.go @@ -8,5 +8,5 @@ type CoinbaseManager interface { ExpectedCoinbaseTransaction(stagingArea *StagingArea, blockHash *externalapi.DomainHash, coinbaseData *externalapi.DomainCoinbaseData) (expectedTransaction *externalapi.DomainTransaction, hasRedReward bool, err error) CalcBlockSubsidy(stagingArea *StagingArea, blockHash *externalapi.DomainHash) (uint64, error) - ExtractCoinbaseDataBlueScoreAndSubsidy(coinbaseTx *externalapi.DomainTransaction) (blueScore uint64, coinbaseData *externalapi.DomainCoinbaseData, subsidy uint64, err error) + ExtractCoinbaseDataBlueScoreAndSubsidy(coinbaseTx *externalapi.DomainTransaction, postHF bool) (blueScore uint64, coinbaseData *externalapi.DomainCoinbaseData, subsidy uint64, err error) } diff --git a/domain/consensus/model/interface_processes_transactionvalidator.go b/domain/consensus/model/interface_processes_transactionvalidator.go index a43d0adc9..f95f8b48c 100644 --- a/domain/consensus/model/interface_processes_transactionvalidator.go +++ b/domain/consensus/model/interface_processes_transactionvalidator.go @@ -12,5 +12,5 @@ type TransactionValidator interface { povBlockHash *externalapi.DomainHash, povBlockPastMedianTime int64) error ValidateTransactionInContextAndPopulateFee(stagingArea *StagingArea, tx *externalapi.DomainTransaction, povBlockHash *externalapi.DomainHash) error - PopulateMass(transaction *externalapi.DomainTransaction) + PopulateMass(transaction *externalapi.DomainTransaction, daaScore uint64) } diff --git a/domain/consensus/processes/blockbuilder/block_builder.go b/domain/consensus/processes/blockbuilder/block_builder.go index bef78afd8..2678fc779 100644 --- a/domain/consensus/processes/blockbuilder/block_builder.go +++ b/domain/consensus/processes/blockbuilder/block_builder.go @@ -20,6 +20,7 @@ import ( type blockBuilder struct { databaseContext model.DBManager genesisHash *externalapi.DomainHash + hfDAAScore uint64 difficultyManager model.DifficultyManager pastMedianTimeManager model.PastMedianTimeManager @@ -42,6 +43,7 @@ type blockBuilder struct { func New( databaseContext model.DBManager, genesisHash *externalapi.DomainHash, + hfDAAScore uint64, difficultyManager model.DifficultyManager, pastMedianTimeManager model.PastMedianTimeManager, @@ -63,6 +65,7 @@ func New( return &blockBuilder{ databaseContext: databaseContext, genesisHash: genesisHash, + hfDAAScore: hfDAAScore, difficultyManager: difficultyManager, pastMedianTimeManager: pastMedianTimeManager, @@ -206,7 +209,7 @@ func (bb *blockBuilder) buildHeader(stagingArea *model.StagingArea, transactions if err != nil { return nil, err } - hashMerkleRoot := bb.newBlockHashMerkleRoot(transactions) + hashMerkleRoot := bb.newBlockHashMerkleRoot(transactions, daaScore >= bb.hfDAAScore) acceptedIDMerkleRoot, err := bb.newBlockAcceptedIDMerkleRoot(stagingArea) if err != nil { return nil, err @@ -279,8 +282,8 @@ func (bb *blockBuilder) newBlockDifficulty(stagingArea *model.StagingArea) (uint return bb.difficultyManager.RequiredDifficulty(stagingArea, model.VirtualBlockHash) } -func (bb *blockBuilder) newBlockHashMerkleRoot(transactions []*externalapi.DomainTransaction) *externalapi.DomainHash { - return merkle.CalculateHashMerkleRoot(transactions) +func (bb *blockBuilder) newBlockHashMerkleRoot(transactions []*externalapi.DomainTransaction, postHF bool) *externalapi.DomainHash { + return merkle.CalculateHashMerkleRoot(transactions, postHF) } func (bb *blockBuilder) newBlockAcceptedIDMerkleRoot(stagingArea *model.StagingArea) (*externalapi.DomainHash, error) { diff --git a/domain/consensus/processes/blockbuilder/test_block_builder.go b/domain/consensus/processes/blockbuilder/test_block_builder.go index d7089cc8d..9a623dd52 100644 --- a/domain/consensus/processes/blockbuilder/test_block_builder.go +++ b/domain/consensus/processes/blockbuilder/test_block_builder.go @@ -76,7 +76,7 @@ func (bb *testBlockBuilder) buildUTXOInvalidHeader(stagingArea *model.StagingAre return nil, err } - hashMerkleRoot := bb.newBlockHashMerkleRoot(transactions) + hashMerkleRoot := bb.newBlockHashMerkleRoot(transactions, daaScore >= bb.hfDAAScore) pruningPoint, err := bb.newBlockPruningPoint(stagingArea, tempBlockHash) if err != nil { @@ -120,7 +120,7 @@ func (bb *testBlockBuilder) buildHeaderWithParents(stagingArea *model.StagingAre return nil, err } - hashMerkleRoot := bb.newBlockHashMerkleRoot(transactions) + hashMerkleRoot := bb.newBlockHashMerkleRoot(transactions, daaScore >= bb.hfDAAScore) acceptedIDMerkleRoot, err := bb.calculateAcceptedIDMerkleRoot(acceptanceData) if err != nil { return nil, err diff --git a/domain/consensus/processes/blockprocessor/validate_and_insert_block_test.go b/domain/consensus/processes/blockprocessor/validate_and_insert_block_test.go index 3f4000a6d..bad3af72d 100644 --- a/domain/consensus/processes/blockprocessor/validate_and_insert_block_test.go +++ b/domain/consensus/processes/blockprocessor/validate_and_insert_block_test.go @@ -94,7 +94,7 @@ func TestBlockStatus(t *testing.T) { invalidBlock.Header = blockheader.NewImmutableBlockHeader( disqualifiedBlock.Header.Version(), disqualifiedBlock.Header.Parents(), - merkle.CalculateHashMerkleRoot(invalidBlock.Transactions), + merkle.CalculateHashMerkleRoot(invalidBlock.Transactions, invalidBlock.Header.DAAScore() >= consensusConfig.HFDAAScore), disqualifiedBlock.Header.AcceptedIDMerkleRoot(), disqualifiedBlock.Header.UTXOCommitment(), disqualifiedBlock.Header.TimeInMilliseconds(), diff --git a/domain/consensus/processes/blockvalidator/block_body_in_context.go b/domain/consensus/processes/blockvalidator/block_body_in_context.go index f032fd96f..3467a8906 100644 --- a/domain/consensus/processes/blockvalidator/block_body_in_context.go +++ b/domain/consensus/processes/blockvalidator/block_body_in_context.go @@ -187,7 +187,7 @@ func (v *blockValidator) checkCoinbaseSubsidy( return err } - _, _, subsidy, err := v.coinbaseManager.ExtractCoinbaseDataBlueScoreAndSubsidy(block.Transactions[transactionhelper.CoinbaseTransactionIndex]) + _, _, subsidy, err := v.coinbaseManager.ExtractCoinbaseDataBlueScoreAndSubsidy(block.Transactions[transactionhelper.CoinbaseTransactionIndex], block.Header.DAAScore() >= v.hfDAAScore) if err != nil { return err } diff --git a/domain/consensus/processes/blockvalidator/block_body_in_isolation.go b/domain/consensus/processes/blockvalidator/block_body_in_isolation.go index ea2999895..bcaff2813 100644 --- a/domain/consensus/processes/blockvalidator/block_body_in_isolation.go +++ b/domain/consensus/processes/blockvalidator/block_body_in_isolation.go @@ -5,6 +5,7 @@ import ( "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" "github.com/kaspanet/kaspad/domain/consensus/ruleerrors" "github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing" + "github.com/kaspanet/kaspad/domain/consensus/utils/constants" "github.com/kaspanet/kaspad/domain/consensus/utils/merkle" "github.com/kaspanet/kaspad/domain/consensus/utils/subnetworks" "github.com/kaspanet/kaspad/domain/consensus/utils/transactionhelper" @@ -88,11 +89,21 @@ func (v *blockValidator) ValidateBodyInIsolation(stagingArea *model.StagingArea, return err } + if block.Header.DAAScore() < v.hfDAAScore { + totalInputs := 0 + for _, tx := range block.Transactions { + totalInputs += len(tx.Inputs) + if totalInputs > constants.MaxBlockInputsPreHF { + return errors.Wrapf(ruleerrors.ErrOverMaxBlockInputsPreHF, "block has more than %d inputs", constants.MaxBlockInputsPreHF) + } + } + } + return nil } func (v *blockValidator) checkCoinbaseBlueScore(block *externalapi.DomainBlock) error { - coinbaseBlueScore, _, _, err := v.coinbaseManager.ExtractCoinbaseDataBlueScoreAndSubsidy(block.Transactions[transactionhelper.CoinbaseTransactionIndex]) + coinbaseBlueScore, _, _, err := v.coinbaseManager.ExtractCoinbaseDataBlueScoreAndSubsidy(block.Transactions[transactionhelper.CoinbaseTransactionIndex], block.Header.DAAScore() >= v.hfDAAScore) if err != nil { return err } @@ -151,7 +162,7 @@ func (v *blockValidator) checkTransactionsInIsolation(block *externalapi.DomainB } func (v *blockValidator) checkBlockHashMerkleRoot(block *externalapi.DomainBlock) error { - calculatedHashMerkleRoot := merkle.CalculateHashMerkleRoot(block.Transactions) + calculatedHashMerkleRoot := merkle.CalculateHashMerkleRoot(block.Transactions, block.Header.DAAScore() >= v.hfDAAScore) if !block.Header.HashMerkleRoot().Equal(calculatedHashMerkleRoot) { return errors.Wrapf(ruleerrors.ErrBadMerkleRoot, "block hash merkle root is invalid - block "+ "header indicates %s, but calculated value is %s", @@ -221,7 +232,7 @@ func (v *blockValidator) validateGasLimit(block *externalapi.DomainBlock) error func (v *blockValidator) checkBlockMass(block *externalapi.DomainBlock) error { mass := uint64(0) for _, transaction := range block.Transactions { - v.transactionValidator.PopulateMass(transaction) + v.transactionValidator.PopulateMass(transaction, block.Header.DAAScore()) massBefore := mass mass += transaction.Mass diff --git a/domain/consensus/processes/blockvalidator/block_body_in_isolation_test.go b/domain/consensus/processes/blockvalidator/block_body_in_isolation_test.go index 56394a0b0..21397887c 100644 --- a/domain/consensus/processes/blockvalidator/block_body_in_isolation_test.go +++ b/domain/consensus/processes/blockvalidator/block_body_in_isolation_test.go @@ -34,6 +34,7 @@ func TestBlockValidator_ValidateBodyInIsolation(t *testing.T) { CheckFirstBlockTransactionIsCoinbase, } testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) { + consensusConfig.HFDAAScore = 10 tc, teardown, err := consensus.NewFactory().NewTestConsensus(consensusConfig, "TestChainedTransactions") if err != nil { t.Fatalf("Error setting up consensus: %+v", err) @@ -1309,7 +1310,7 @@ func initBlockWithFirstTransactionDifferentThanCoinbase(consensusConfig *consens Header: blockheader.NewImmutableBlockHeader( constants.BlockVersion, []externalapi.BlockLevelParents{[]*externalapi.DomainHash{consensusConfig.GenesisHash}}, - merkle.CalculateHashMerkleRoot([]*externalapi.DomainTransaction{tx}), + merkle.CalculateHashMerkleRoot([]*externalapi.DomainTransaction{tx}, consensusConfig.HFDAAScore == 0), &externalapi.DomainHash{}, externalapi.NewDomainHashFromByteArray(&[externalapi.DomainHashSize]byte{ 0x80, 0xf7, 0x00, 0xe3, 0x16, 0x3d, 0x04, 0x95, diff --git a/domain/consensus/processes/blockvalidator/blockvalidator.go b/domain/consensus/processes/blockvalidator/blockvalidator.go index 7788a49b5..e4eefe9b3 100644 --- a/domain/consensus/processes/blockvalidator/blockvalidator.go +++ b/domain/consensus/processes/blockvalidator/blockvalidator.go @@ -25,6 +25,7 @@ type blockValidator struct { timestampDeviationTolerance int targetTimePerBlock time.Duration maxBlockLevel int + hfDAAScore uint64 databaseContext model.DBReader difficultyManager model.DifficultyManager @@ -64,6 +65,7 @@ func New(powMax *big.Int, timestampDeviationTolerance int, targetTimePerBlock time.Duration, maxBlockLevel int, + hfDAAScore uint64, databaseContext model.DBReader, @@ -103,6 +105,7 @@ func New(powMax *big.Int, mergeSetSizeLimit: mergeSetSizeLimit, maxBlockParents: maxBlockParents, maxBlockLevel: maxBlockLevel, + hfDAAScore: hfDAAScore, timestampDeviationTolerance: timestampDeviationTolerance, targetTimePerBlock: targetTimePerBlock, 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 4ca2ff479..1f1f8178f 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 @@ -181,7 +181,7 @@ func TestCheckParentHeadersExist(t *testing.T) { invalidBlock.Header = blockheader.NewImmutableBlockHeader( invalidBlock.Header.Version(), invalidBlock.Header.Parents(), - merkle.CalculateHashMerkleRoot(invalidBlock.Transactions), + merkle.CalculateHashMerkleRoot(invalidBlock.Transactions, orphanBlock.Header.DAAScore() >= consensusConfig.HFDAAScore), orphanBlock.Header.AcceptedIDMerkleRoot(), orphanBlock.Header.UTXOCommitment(), orphanBlock.Header.TimeInMilliseconds(), diff --git a/domain/consensus/processes/coinbasemanager/coinbasemanager.go b/domain/consensus/processes/coinbasemanager/coinbasemanager.go index 3d187a3e1..873e554ec 100644 --- a/domain/consensus/processes/coinbasemanager/coinbasemanager.go +++ b/domain/consensus/processes/coinbasemanager/coinbasemanager.go @@ -19,6 +19,7 @@ type coinbaseManager struct { genesisHash *externalapi.DomainHash deflationaryPhaseDaaScore uint64 deflationaryPhaseBaseSubsidy uint64 + hfDAAScore uint64 databaseContext model.DBReader dagTraversalManager model.DAGTraversalManager @@ -127,7 +128,8 @@ func (c *coinbaseManager) coinbaseOutputForBlueBlock(stagingArea *model.StagingA } // the ScriptPublicKey for the coinbase is parsed from the coinbase payload - _, coinbaseData, _, err := c.ExtractCoinbaseDataBlueScoreAndSubsidy(blockAcceptanceData.TransactionAcceptanceData[0].Transaction) + // We pass postHF=true since it only affects the deserialization of the subsidy, which is not used in this context. + _, coinbaseData, _, err := c.ExtractCoinbaseDataBlueScoreAndSubsidy(blockAcceptanceData.TransactionAcceptanceData[0].Transaction, true) if err != nil { return nil, false, err } @@ -265,7 +267,7 @@ func (c *coinbaseManager) calcMergedBlockReward(stagingArea *model.StagingArea, return 0, err } - _, _, subsidy, err := c.ExtractCoinbaseDataBlueScoreAndSubsidy(block.Transactions[transactionhelper.CoinbaseTransactionIndex]) + _, _, subsidy, err := c.ExtractCoinbaseDataBlueScoreAndSubsidy(block.Transactions[transactionhelper.CoinbaseTransactionIndex], block.Header.DAAScore() >= c.hfDAAScore) if err != nil { return 0, err } @@ -283,6 +285,7 @@ func New( genesisHash *externalapi.DomainHash, deflationaryPhaseDaaScore uint64, deflationaryPhaseBaseSubsidy uint64, + hfDAAScore uint64, dagTraversalManager model.DAGTraversalManager, ghostdagDataStore model.GHOSTDAGDataStore, @@ -301,6 +304,7 @@ func New( genesisHash: genesisHash, deflationaryPhaseDaaScore: deflationaryPhaseDaaScore, deflationaryPhaseBaseSubsidy: deflationaryPhaseBaseSubsidy, + hfDAAScore: hfDAAScore, dagTraversalManager: dagTraversalManager, ghostdagDataStore: ghostdagDataStore, diff --git a/domain/consensus/processes/coinbasemanager/coinbasemanager_external_test.go b/domain/consensus/processes/coinbasemanager/coinbasemanager_external_test.go new file mode 100644 index 000000000..dc66fc167 --- /dev/null +++ b/domain/consensus/processes/coinbasemanager/coinbasemanager_external_test.go @@ -0,0 +1,69 @@ +package coinbasemanager_test + +import ( + "github.com/kaspanet/kaspad/domain/consensus" + "github.com/kaspanet/kaspad/domain/consensus/model" + "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" + "github.com/kaspanet/kaspad/domain/consensus/utils/testutils" + "testing" +) + +func TestExtractCoinbaseDataBlueScoreAndSubsidy(t *testing.T) { + testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) { + factory := consensus.NewFactory() + tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestBlockStatus") + if err != nil { + t.Fatalf("Error setting up consensus: %+v", err) + } + defer teardown(false) + + tests := []struct { + name string + scriptPublicKeyVersion uint16 + expectedScriptPublicKeyVersionBeforeHF uint16 + }{ + { + name: "below 255", + scriptPublicKeyVersion: 100, + expectedScriptPublicKeyVersionBeforeHF: 100, + }, + { + name: "above 255", + scriptPublicKeyVersion: 300, + expectedScriptPublicKeyVersionBeforeHF: 44, + }, + } + + for _, test := range tests { + coinbaseTx, _, err := tc.CoinbaseManager().ExpectedCoinbaseTransaction(model.NewStagingArea(), model.VirtualBlockHash, &externalapi.DomainCoinbaseData{ + ScriptPublicKey: &externalapi.ScriptPublicKey{ + Script: nil, + Version: test.scriptPublicKeyVersion, + }, + ExtraData: nil, + }) + if err != nil { + t.Fatal(err) + } + + _, cbData, _, err := tc.CoinbaseManager().ExtractCoinbaseDataBlueScoreAndSubsidy(coinbaseTx, false) + if err != nil { + t.Fatal(err) + } + + if cbData.ScriptPublicKey.Version != test.expectedScriptPublicKeyVersionBeforeHF { + t.Fatalf("test %s pre HF expected %d but got %d", test.name, test.expectedScriptPublicKeyVersionBeforeHF, cbData.ScriptPublicKey.Version) + } + + _, cbData, _, err = tc.CoinbaseManager().ExtractCoinbaseDataBlueScoreAndSubsidy(coinbaseTx, true) + if err != nil { + t.Fatal(err) + } + + if cbData.ScriptPublicKey.Version != test.scriptPublicKeyVersion { + t.Fatalf("test %s post HF expected %d but got %d", test.name, test.scriptPublicKeyVersion, cbData.ScriptPublicKey.Version) + } + } + + }) +} diff --git a/domain/consensus/processes/coinbasemanager/coinbasemanager_test.go b/domain/consensus/processes/coinbasemanager/coinbasemanager_test.go index a5e658c08..f0e7b9c55 100644 --- a/domain/consensus/processes/coinbasemanager/coinbasemanager_test.go +++ b/domain/consensus/processes/coinbasemanager/coinbasemanager_test.go @@ -21,6 +21,7 @@ func TestCalcDeflationaryPeriodBlockSubsidy(t *testing.T) { &externalapi.DomainHash{}, deflationaryPhaseDaaScore, deflationaryPhaseBaseSubsidy, + 0, nil, nil, nil, @@ -96,6 +97,7 @@ func TestBuildSubsidyTable(t *testing.T) { &externalapi.DomainHash{}, 0, deflationaryPhaseBaseSubsidy, + 0, nil, nil, nil, diff --git a/domain/consensus/processes/coinbasemanager/payload.go b/domain/consensus/processes/coinbasemanager/payload.go index 120a55147..f67374abc 100644 --- a/domain/consensus/processes/coinbasemanager/payload.go +++ b/domain/consensus/processes/coinbasemanager/payload.go @@ -28,7 +28,7 @@ func (c *coinbaseManager) serializeCoinbasePayload(blueScore uint64, binary.LittleEndian.PutUint64(payload[:uint64Len], blueScore) binary.LittleEndian.PutUint64(payload[uint64Len:], subsidy) - payload[uint64Len+lengthOfSubsidy] = uint8(coinbaseData.ScriptPublicKey.Version) + binary.LittleEndian.PutUint16(payload[uint64Len+lengthOfSubsidy:], coinbaseData.ScriptPublicKey.Version) payload[uint64Len+lengthOfSubsidy+lengthOfVersionScriptPubKey] = uint8(len(coinbaseData.ScriptPublicKey.Script)) copy(payload[uint64Len+lengthOfSubsidy+lengthOfVersionScriptPubKey+lengthOfScriptPubKeyLength:], coinbaseData.ScriptPublicKey.Script) copy(payload[uint64Len+lengthOfSubsidy+lengthOfVersionScriptPubKey+lengthOfScriptPubKeyLength+scriptLengthOfScriptPubKey:], coinbaseData.ExtraData) @@ -52,7 +52,7 @@ func ModifyCoinbasePayload(payload []byte, coinbaseData *externalapi.DomainCoinb payload = newPayload } - payload[uint64Len+lengthOfSubsidy] = uint8(coinbaseData.ScriptPublicKey.Version) + binary.LittleEndian.PutUint16(payload[uint64Len+lengthOfSubsidy:uint64Len+lengthOfSubsidy+lengthOfVersionScriptPubKey], coinbaseData.ScriptPublicKey.Version) payload[uint64Len+lengthOfSubsidy+lengthOfVersionScriptPubKey] = uint8(len(coinbaseData.ScriptPublicKey.Script)) copy(payload[uint64Len+lengthOfSubsidy+lengthOfVersionScriptPubKey+lengthOfScriptPubKeyLength:], coinbaseData.ScriptPublicKey.Script) copy(payload[uint64Len+lengthOfSubsidy+lengthOfVersionScriptPubKey+lengthOfScriptPubKeyLength+scriptLengthOfScriptPubKey:], coinbaseData.ExtraData) @@ -61,7 +61,7 @@ func ModifyCoinbasePayload(payload []byte, coinbaseData *externalapi.DomainCoinb } // ExtractCoinbaseDataBlueScoreAndSubsidy deserializes the coinbase payload to its component (scriptPubKey, extra data, and subsidy). -func (c *coinbaseManager) ExtractCoinbaseDataBlueScoreAndSubsidy(coinbaseTx *externalapi.DomainTransaction) ( +func (c *coinbaseManager) ExtractCoinbaseDataBlueScoreAndSubsidy(coinbaseTx *externalapi.DomainTransaction, postHF bool) ( blueScore uint64, coinbaseData *externalapi.DomainCoinbaseData, subsidy uint64, err error) { minLength := uint64Len + lengthOfSubsidy + lengthOfVersionScriptPubKey + lengthOfScriptPubKeyLength @@ -74,6 +74,10 @@ func (c *coinbaseManager) ExtractCoinbaseDataBlueScoreAndSubsidy(coinbaseTx *ext subsidy = binary.LittleEndian.Uint64(coinbaseTx.Payload[uint64Len:]) scriptPubKeyVersion := uint16(coinbaseTx.Payload[uint64Len+lengthOfSubsidy]) + if postHF { + scriptPubKeyVersion = binary.LittleEndian.Uint16(coinbaseTx.Payload[uint64Len+lengthOfSubsidy : uint64Len+lengthOfSubsidy+uint16Len]) + } + scriptPubKeyScriptLength := coinbaseTx.Payload[uint64Len+lengthOfSubsidy+lengthOfVersionScriptPubKey] if scriptPubKeyScriptLength > c.coinbasePayloadScriptPublicKeyMaxLength { diff --git a/domain/consensus/processes/consensusstatemanager/consensus_state_manager.go b/domain/consensus/processes/consensusstatemanager/consensus_state_manager.go index 8f4514392..37a5684de 100644 --- a/domain/consensus/processes/consensusstatemanager/consensus_state_manager.go +++ b/domain/consensus/processes/consensusstatemanager/consensus_state_manager.go @@ -10,6 +10,7 @@ type consensusStateManager struct { maxBlockParents externalapi.KType mergeSetSizeLimit uint64 genesisHash *externalapi.DomainHash + hfDAAScore uint64 databaseContext model.DBManager ghostdagManager model.GHOSTDAGManager @@ -44,6 +45,7 @@ func New( maxBlockParents externalapi.KType, mergeSetSizeLimit uint64, genesisHash *externalapi.DomainHash, + hfDAAScore uint64, ghostdagManager model.GHOSTDAGManager, dagTopologyManager model.DAGTopologyManager, @@ -72,7 +74,9 @@ func New( maxBlockParents: maxBlockParents, mergeSetSizeLimit: mergeSetSizeLimit, genesisHash: genesisHash, - databaseContext: databaseContext, + hfDAAScore: hfDAAScore, + + databaseContext: databaseContext, ghostdagManager: ghostdagManager, dagTopologyManager: dagTopologyManager, diff --git a/domain/consensus/processes/consensusstatemanager/verify_and_build_utxo.go b/domain/consensus/processes/consensusstatemanager/verify_and_build_utxo.go index f1958cdbe..7160d413c 100644 --- a/domain/consensus/processes/consensusstatemanager/verify_and_build_utxo.go +++ b/domain/consensus/processes/consensusstatemanager/verify_and_build_utxo.go @@ -39,7 +39,7 @@ func (csm *consensusStateManager) verifyUTXO(stagingArea *model.StagingArea, blo coinbaseTransaction := block.Transactions[0] log.Debugf("Validating coinbase transaction %s for block %s", consensushashing.TransactionID(coinbaseTransaction), blockHash) - err = csm.validateCoinbaseTransaction(stagingArea, blockHash, coinbaseTransaction) + err = csm.validateCoinbaseTransaction(stagingArea, blockHash, coinbaseTransaction, block.Header.DAAScore() >= csm.hfDAAScore) if err != nil { return err } @@ -153,14 +153,14 @@ func calculateAcceptedIDMerkleRoot(multiblockAcceptanceData externalapi.Acceptan } func (csm *consensusStateManager) validateCoinbaseTransaction(stagingArea *model.StagingArea, - blockHash *externalapi.DomainHash, coinbaseTransaction *externalapi.DomainTransaction) error { + blockHash *externalapi.DomainHash, coinbaseTransaction *externalapi.DomainTransaction, postHF bool) error { log.Tracef("validateCoinbaseTransaction start for block %s", blockHash) defer log.Tracef("validateCoinbaseTransaction end for block %s", blockHash) log.Tracef("Extracting coinbase data for coinbase transaction %s in block %s", consensushashing.TransactionID(coinbaseTransaction), blockHash) - _, coinbaseData, _, err := csm.coinbaseManager.ExtractCoinbaseDataBlueScoreAndSubsidy(coinbaseTransaction) + _, coinbaseData, _, err := csm.coinbaseManager.ExtractCoinbaseDataBlueScoreAndSubsidy(coinbaseTransaction, postHF) if err != nil { return err } @@ -172,8 +172,8 @@ func (csm *consensusStateManager) validateCoinbaseTransaction(stagingArea *model return err } - coinbaseTransactionHash := consensushashing.TransactionHash(coinbaseTransaction) - expectedCoinbaseTransactionHash := consensushashing.TransactionHash(expectedCoinbaseTransaction) + coinbaseTransactionHash := consensushashing.TransactionHash(coinbaseTransaction, true) + expectedCoinbaseTransactionHash := consensushashing.TransactionHash(expectedCoinbaseTransaction, true) log.Tracef("given coinbase hash: %s, expected coinbase hash: %s", coinbaseTransactionHash, expectedCoinbaseTransactionHash) diff --git a/domain/consensus/processes/transactionvalidator/mass.go b/domain/consensus/processes/transactionvalidator/mass.go index d99410470..918fd9048 100644 --- a/domain/consensus/processes/transactionvalidator/mass.go +++ b/domain/consensus/processes/transactionvalidator/mass.go @@ -5,9 +5,9 @@ import ( ) // PopulateMass calculates and populates the mass of the given transaction -func (v *transactionValidator) PopulateMass(transaction *externalapi.DomainTransaction) { +func (v *transactionValidator) PopulateMass(transaction *externalapi.DomainTransaction, daaScore uint64) { if transaction.Mass != 0 { return } - transaction.Mass = v.txMassCalculator.CalculateTransactionMass(transaction) + transaction.Mass = v.txMassCalculator.CalculateTransactionMass(transaction, daaScore >= v.hfDAAScore) } diff --git a/domain/consensus/processes/transactionvalidator/transaction_in_context.go b/domain/consensus/processes/transactionvalidator/transaction_in_context.go index 159c2e74c..bbff8e076 100644 --- a/domain/consensus/processes/transactionvalidator/transaction_in_context.go +++ b/domain/consensus/processes/transactionvalidator/transaction_in_context.go @@ -90,7 +90,12 @@ func (v *transactionValidator) ValidateTransactionInContextAndPopulateFee(stagin return err } - err = v.validateTransactionSigOpCounts(tx) + daaScore, err := v.daaBlocksStore.DAAScore(v.databaseContext, stagingArea, povBlockHash) + if err != nil { + return err + } + + err = v.validateTransactionSigOpCounts(tx, daaScore >= v.hfDAAScore) if err != nil { return err } @@ -341,7 +346,7 @@ func (v *transactionValidator) sequenceLockActive(sequenceLock *sequenceLock, bl return true } -func (v *transactionValidator) validateTransactionSigOpCounts(tx *externalapi.DomainTransaction) error { +func (v *transactionValidator) validateTransactionSigOpCounts(tx *externalapi.DomainTransaction, postHF bool) error { for i, input := range tx.Inputs { utxoEntry := input.UTXOEntry @@ -350,10 +355,20 @@ func (v *transactionValidator) validateTransactionSigOpCounts(tx *externalapi.Do sigScript := input.SignatureScript isP2SH := txscript.IsPayToScriptHash(utxoEntry.ScriptPublicKey()) sigOpCount := txscript.GetPreciseSigOpCount(sigScript, utxoEntry.ScriptPublicKey(), isP2SH) - if sigOpCount != int(input.SigOpCount) { - return errors.Wrapf(ruleerrors.ErrWrongSigOpCount, - "input %d specifies SigOpCount %d while actual SigOpCount is %d", - i, input.SigOpCount, sigOpCount) + + if postHF { + if sigOpCount != int(input.SigOpCount) { + return errors.Wrapf(ruleerrors.ErrWrongSigOpCount, + "input %d specifies SigOpCount %d while actual SigOpCount is %d", + i, input.SigOpCount, sigOpCount) + } + } else { + const sigOpCountLimit = 10 + if sigOpCount > sigOpCountLimit { + return errors.Wrapf(ruleerrors.ErrWrongSigOpCount, + "input %d is using SigOpCount %d while the limit is %d", + i, sigOpCount, sigOpCountLimit) + } } } return nil diff --git a/domain/consensus/processes/transactionvalidator/transaction_in_isolation.go b/domain/consensus/processes/transactionvalidator/transaction_in_isolation.go index ef04f9b0b..5eca8c2e0 100644 --- a/domain/consensus/processes/transactionvalidator/transaction_in_isolation.go +++ b/domain/consensus/processes/transactionvalidator/transaction_in_isolation.go @@ -23,7 +23,7 @@ func (v *transactionValidator) ValidateTransactionInIsolation(tx *externalapi.Do if err != nil { return err } - err = v.checkCoinbaseLength(tx) + err = v.checkCoinbaseInIsolation(tx, povDAAScore >= v.hfDAAScore) if err != nil { return err } @@ -114,7 +114,7 @@ func (v *transactionValidator) checkDuplicateTransactionInputs(tx *externalapi.D return nil } -func (v *transactionValidator) checkCoinbaseLength(tx *externalapi.DomainTransaction) error { +func (v *transactionValidator) checkCoinbaseInIsolation(tx *externalapi.DomainTransaction, postHF bool) error { if !transactionhelper.IsCoinBase(tx) { return nil } @@ -127,6 +127,24 @@ func (v *transactionValidator) checkCoinbaseLength(tx *externalapi.DomainTransac payloadLen, v.maxCoinbasePayloadLength) } + if postHF { + if len(tx.Inputs) != 0 { + return errors.Wrap(ruleerrors.ErrCoinbaseWithInputs, "coinbase has inputs") + } + + outputsLimit := uint64(v.ghostdagK) + 2 + if uint64(len(tx.Outputs)) > outputsLimit { + return errors.Wrapf(ruleerrors.ErrCoinbaseTooManyOutputs, "coinbase has too many outputs: got %d where the limit is %d", len(tx.Outputs), outputsLimit) + } + + for i, output := range tx.Outputs { + if len(output.ScriptPublicKey.Script) > int(v.coinbasePayloadScriptPublicKeyMaxLength) { + return errors.Wrapf(ruleerrors.ErrCoinbaseTooLongScriptPublicKey, "coinbase output %d has a too long script public key", i) + + } + } + } + return nil } diff --git a/domain/consensus/processes/transactionvalidator/transaction_in_isolation_test.go b/domain/consensus/processes/transactionvalidator/transaction_in_isolation_test.go index e95f714c2..7a0842225 100644 --- a/domain/consensus/processes/transactionvalidator/transaction_in_isolation_test.go +++ b/domain/consensus/processes/transactionvalidator/transaction_in_isolation_test.go @@ -22,6 +22,7 @@ type txSubnetworkData struct { func TestValidateTransactionInIsolationAndPopulateMass(t *testing.T) { testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) { cfg := *consensusConfig + cfg.HFDAAScore = 1000 factory := consensus.NewFactory() tc, teardown, err := factory.NewTestConsensus(&cfg, "TestValidateTransactionInIsolationAndPopulateMass") @@ -69,7 +70,7 @@ func TestValidateTransactionInIsolationAndPopulateMass(t *testing.T) { nil, func(tx *externalapi.DomainTransaction) { tx.Inputs[1].PreviousOutpoint.Index = 0 }, ruleerrors.ErrDuplicateTxInputs, 0}, - {"1 input coinbase", + {"1 input coinbase - pre HF", 1, 1, 1, @@ -77,6 +78,14 @@ func TestValidateTransactionInIsolationAndPopulateMass(t *testing.T) { &txSubnetworkData{subnetworks.SubnetworkIDCoinbase, 0, nil}, nil, nil, 0}, + {"1 input coinbase - post HF", + 1, + 1, + 1, + subnetworks.SubnetworkIDNative, + &txSubnetworkData{subnetworks.SubnetworkIDCoinbase, 0, nil}, + nil, + ruleerrors.ErrCoinbaseWithInputs, cfg.HFDAAScore}, {"no inputs coinbase", 0, 1, diff --git a/domain/consensus/processes/transactionvalidator/transactionvalidator.go b/domain/consensus/processes/transactionvalidator/transactionvalidator.go index 73621e756..1c33ef22c 100644 --- a/domain/consensus/processes/transactionvalidator/transactionvalidator.go +++ b/domain/consensus/processes/transactionvalidator/transactionvalidator.go @@ -2,6 +2,7 @@ package transactionvalidator import ( "github.com/kaspanet/kaspad/domain/consensus/model" + "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" "github.com/kaspanet/kaspad/domain/consensus/utils/txscript" "github.com/kaspanet/kaspad/util/txmass" ) @@ -11,22 +12,28 @@ const sigCacheSize = 10_000 // transactionValidator exposes a set of validation classes, after which // it's possible to determine whether either a transaction is valid type transactionValidator struct { - blockCoinbaseMaturity uint64 - databaseContext model.DBReader - pastMedianTimeManager model.PastMedianTimeManager - ghostdagDataStore model.GHOSTDAGDataStore - daaBlocksStore model.DAABlocksStore - enableNonNativeSubnetworks bool - maxCoinbasePayloadLength uint64 - sigCache *txscript.SigCache - sigCacheECDSA *txscript.SigCacheECDSA - txMassCalculator *txmass.Calculator + blockCoinbaseMaturity uint64 + databaseContext model.DBReader + pastMedianTimeManager model.PastMedianTimeManager + ghostdagDataStore model.GHOSTDAGDataStore + daaBlocksStore model.DAABlocksStore + enableNonNativeSubnetworks bool + maxCoinbasePayloadLength uint64 + hfDAAScore uint64 + ghostdagK externalapi.KType + coinbasePayloadScriptPublicKeyMaxLength uint8 + sigCache *txscript.SigCache + sigCacheECDSA *txscript.SigCacheECDSA + txMassCalculator *txmass.Calculator } // New instantiates a new TransactionValidator func New(blockCoinbaseMaturity uint64, enableNonNativeSubnetworks bool, maxCoinbasePayloadLength uint64, + hfDAAScore uint64, + ghostdagK externalapi.KType, + coinbasePayloadScriptPublicKeyMaxLength uint8, databaseContext model.DBReader, pastMedianTimeManager model.PastMedianTimeManager, ghostdagDataStore model.GHOSTDAGDataStore, @@ -34,15 +41,18 @@ func New(blockCoinbaseMaturity uint64, txMassCalculator *txmass.Calculator) model.TransactionValidator { return &transactionValidator{ - blockCoinbaseMaturity: blockCoinbaseMaturity, - enableNonNativeSubnetworks: enableNonNativeSubnetworks, - maxCoinbasePayloadLength: maxCoinbasePayloadLength, - databaseContext: databaseContext, - pastMedianTimeManager: pastMedianTimeManager, - ghostdagDataStore: ghostdagDataStore, - daaBlocksStore: daaBlocksStore, - sigCache: txscript.NewSigCache(sigCacheSize), - sigCacheECDSA: txscript.NewSigCacheECDSA(sigCacheSize), - txMassCalculator: txMassCalculator, + blockCoinbaseMaturity: blockCoinbaseMaturity, + enableNonNativeSubnetworks: enableNonNativeSubnetworks, + maxCoinbasePayloadLength: maxCoinbasePayloadLength, + hfDAAScore: hfDAAScore, + ghostdagK: ghostdagK, + coinbasePayloadScriptPublicKeyMaxLength: coinbasePayloadScriptPublicKeyMaxLength, + databaseContext: databaseContext, + pastMedianTimeManager: pastMedianTimeManager, + ghostdagDataStore: ghostdagDataStore, + daaBlocksStore: daaBlocksStore, + sigCache: txscript.NewSigCache(sigCacheSize), + sigCacheECDSA: txscript.NewSigCacheECDSA(sigCacheSize), + txMassCalculator: txMassCalculator, } } diff --git a/domain/consensus/processes/transactionvalidator/transactionvalidator_test.go b/domain/consensus/processes/transactionvalidator/transactionvalidator_test.go index f5db9a468..f9cdbdbcc 100644 --- a/domain/consensus/processes/transactionvalidator/transactionvalidator_test.go +++ b/domain/consensus/processes/transactionvalidator/transactionvalidator_test.go @@ -109,18 +109,6 @@ func TestValidateTransactionInContextAndPopulateFee(t *testing.T) { 0), } - txInputWithBadSigOpCount := externalapi.DomainTransactionInput{ - PreviousOutpoint: prevOutPoint, - SignatureScript: []byte{}, - Sequence: constants.MaxTxInSequenceNum, - SigOpCount: 2, - UTXOEntry: utxo.NewUTXOEntry( - 100_000_000, // 1 KAS - scriptPublicKey, - true, - uint64(5)), - } - txOutput := externalapi.DomainTransactionOutput{ Value: 100000000, // 1 KAS ScriptPublicKey: scriptPublicKey, @@ -193,13 +181,6 @@ func TestValidateTransactionInContextAndPopulateFee(t *testing.T) { SubnetworkID: subnetworks.SubnetworkIDRegistry, Gas: 0, LockTime: 0} - txWithBadSigOpCount := externalapi.DomainTransaction{ - Version: constants.MaxTransactionVersion, - Inputs: []*externalapi.DomainTransactionInput{&txInputWithBadSigOpCount}, - Outputs: []*externalapi.DomainTransactionOutput{&txOutput}, - SubnetworkID: subnetworks.SubnetworkIDRegistry, - Gas: 0, - LockTime: 0} stagingArea := model.NewStagingArea() @@ -266,13 +247,6 @@ func TestValidateTransactionInContextAndPopulateFee(t *testing.T) { isValid: false, expectedError: ruleerrors.ErrScriptValidation, }, - { // the SigOpCount in the input is wrong, and hence invalid - name: "checkTransactionSigOpCounts", - tx: &txWithBadSigOpCount, - povBlockHash: povBlockHash, - isValid: false, - expectedError: ruleerrors.ErrWrongSigOpCount, - }, } for _, test := range tests { diff --git a/domain/consensus/ruleerrors/rule_error.go b/domain/consensus/ruleerrors/rule_error.go index eb0515997..0da7ef2e9 100644 --- a/domain/consensus/ruleerrors/rule_error.go +++ b/domain/consensus/ruleerrors/rule_error.go @@ -241,6 +241,10 @@ var ( ErrPruningProofEmpty = newRuleError("ErrPruningProofEmpty") ErrWrongCoinbaseSubsidy = newRuleError("ErrWrongCoinbaseSubsidy") ErrWrongBlockVersion = newRuleError("ErrWrongBlockVersion") + ErrCoinbaseWithInputs = newRuleError("ErrCoinbaseWithInputs") + ErrCoinbaseTooManyOutputs = newRuleError("ErrCoinbaseTooManyOutputs") + ErrCoinbaseTooLongScriptPublicKey = newRuleError("ErrCoinbaseTooLongScriptPublicKey") + ErrOverMaxBlockInputsPreHF = newRuleError("ErrOverMaxBlockInputsPreHF") ) // RuleError identifies a rule violation. It is used to indicate that diff --git a/domain/consensus/utils/consensushashing/transaction.go b/domain/consensus/utils/consensushashing/transaction.go index a364a7ae6..5012abdcf 100644 --- a/domain/consensus/utils/consensushashing/transaction.go +++ b/domain/consensus/utils/consensushashing/transaction.go @@ -23,11 +23,11 @@ const ( ) // TransactionHash returns the transaction hash. -func TransactionHash(tx *externalapi.DomainTransaction) *externalapi.DomainHash { +func TransactionHash(tx *externalapi.DomainTransaction, postHF bool) *externalapi.DomainHash { // Encode the header and hash everything prior to the number of // transactions. writer := hashes.NewTransactionHashWriter() - err := serializeTransaction(writer, tx, txEncodingFull) + err := serializeTransaction(writer, tx, txEncodingFull, postHF) if err != nil { // It seems like this could only happen if the writer returned an error. // and this writer should never return an error (no allocations or possible failures) @@ -52,7 +52,7 @@ func TransactionID(tx *externalapi.DomainTransaction) *externalapi.DomainTransac encodingFlags = txEncodingExcludeSignatureScript } writer := hashes.NewTransactionIDWriter() - err := serializeTransaction(writer, tx, encodingFlags) + err := serializeTransaction(writer, tx, encodingFlags, true) if err != nil { // this writer never return errors (no allocations or possible failures) so errors can only come from validity checks, // and we assume we never construct malformed transactions. @@ -74,7 +74,7 @@ func TransactionIDs(txs []*externalapi.DomainTransaction) []*externalapi.DomainT return txIDs } -func serializeTransaction(w io.Writer, tx *externalapi.DomainTransaction, encodingFlags txEncoding) error { +func serializeTransaction(w io.Writer, tx *externalapi.DomainTransaction, encodingFlags txEncoding, postHF bool) error { err := binaryserializer.PutUint16(w, tx.Version) if err != nil { return err @@ -87,7 +87,7 @@ func serializeTransaction(w io.Writer, tx *externalapi.DomainTransaction, encodi } for _, ti := range tx.Inputs { - err = writeTransactionInput(w, ti, encodingFlags) + err = writeTransactionInput(w, ti, encodingFlags, postHF) if err != nil { return err } @@ -131,7 +131,7 @@ func serializeTransaction(w io.Writer, tx *externalapi.DomainTransaction, encodi // writeTransactionInput encodes ti to the kaspa protocol encoding for a transaction // input to w. -func writeTransactionInput(w io.Writer, ti *externalapi.DomainTransactionInput, encodingFlags txEncoding) error { +func writeTransactionInput(w io.Writer, ti *externalapi.DomainTransactionInput, encodingFlags txEncoding, postHF bool) error { err := writeOutpoint(w, &ti.PreviousOutpoint) if err != nil { return err @@ -139,11 +139,21 @@ func writeTransactionInput(w io.Writer, ti *externalapi.DomainTransactionInput, if encodingFlags&txEncodingExcludeSignatureScript != txEncodingExcludeSignatureScript { err = writeVarBytes(w, ti.SignatureScript) + if err != nil { + return err + } + + if postHF { + _, err = w.Write([]byte{ti.SigOpCount}) + if err != nil { + return err + } + } } else { err = writeVarBytes(w, []byte{}) - } - if err != nil { - return err + if err != nil { + return err + } } return binaryserializer.PutUint64(w, ti.Sequence) diff --git a/domain/consensus/utils/constants/constants.go b/domain/consensus/utils/constants/constants.go index ab9b3fafd..ef030ac6b 100644 --- a/domain/consensus/utils/constants/constants.go +++ b/domain/consensus/utils/constants/constants.go @@ -38,4 +38,7 @@ const ( // UnacceptedDAAScore is used to for UTXOEntries that were created by transactions in the mempool, or otherwise // not-yet-accepted transactions. UnacceptedDAAScore = math.MaxUint64 + + // MaxBlockInputsPreHF is the maximum number of inputs a block can hold before the HF + MaxBlockInputsPreHF = 900 ) diff --git a/domain/consensus/utils/merkle/merkle.go b/domain/consensus/utils/merkle/merkle.go index c53a0ef88..a6599e2f4 100644 --- a/domain/consensus/utils/merkle/merkle.go +++ b/domain/consensus/utils/merkle/merkle.go @@ -37,10 +37,10 @@ func hashMerkleBranches(left, right *externalapi.DomainHash) *externalapi.Domain // CalculateHashMerkleRoot calculates the merkle root of a tree consisted of the given transaction hashes. // See `merkleRoot` for more info. -func CalculateHashMerkleRoot(transactions []*externalapi.DomainTransaction) *externalapi.DomainHash { +func CalculateHashMerkleRoot(transactions []*externalapi.DomainTransaction, postHF bool) *externalapi.DomainHash { txHashes := make([]*externalapi.DomainHash, len(transactions)) for i, tx := range transactions { - txHashes[i] = consensushashing.TransactionHash(tx) + txHashes[i] = consensushashing.TransactionHash(tx, postHF) } return merkleRoot(txHashes) } diff --git a/domain/dagconfig/params.go b/domain/dagconfig/params.go index 67e20c16f..34f75a456 100644 --- a/domain/dagconfig/params.go +++ b/domain/dagconfig/params.go @@ -188,6 +188,8 @@ type Params struct { MaxBlockLevel int MergeDepth uint64 + + HFDAAScore uint64 } // NormalizeRPCServerAddress returns addr with the current network default @@ -288,6 +290,8 @@ var MainnetParams = Params{ // This means that any block that has a level lower or equal to genesis will be level 0. MaxBlockLevel: 225, MergeDepth: defaultMergeDepth, + + HFDAAScore: 27905000, } // TestnetParams defines the network parameters for the test Kaspa network. @@ -350,6 +354,7 @@ var TestnetParams = Params{ MaxBlockLevel: 250, MergeDepth: defaultMergeDepth, + HFDAAScore: 14106400, } // SimnetParams defines the network parameters for the simulation test Kaspa diff --git a/domain/miningmanager/blocktemplatebuilder/blocktemplatebuilder.go b/domain/miningmanager/blocktemplatebuilder/blocktemplatebuilder.go index 564542db8..c40c35146 100644 --- a/domain/miningmanager/blocktemplatebuilder/blocktemplatebuilder.go +++ b/domain/miningmanager/blocktemplatebuilder/blocktemplatebuilder.go @@ -37,17 +37,19 @@ type blockTemplateBuilder struct { policy policy coinbasePayloadScriptPublicKeyMaxLength uint8 + hfDAAScore uint64 } // New creates a new blockTemplateBuilder func New(consensusReference consensusreference.ConsensusReference, mempool miningmanagerapi.Mempool, - blockMaxMass uint64, coinbasePayloadScriptPublicKeyMaxLength uint8) miningmanagerapi.BlockTemplateBuilder { + blockMaxMass uint64, coinbasePayloadScriptPublicKeyMaxLength uint8, hfDAAScore uint64) miningmanagerapi.BlockTemplateBuilder { return &blockTemplateBuilder{ consensusReference: consensusReference, mempool: mempool, policy: policy{BlockMaxMass: blockMaxMass}, coinbasePayloadScriptPublicKeyMaxLength: coinbasePayloadScriptPublicKeyMaxLength, + hfDAAScore: hfDAAScore, } } @@ -190,7 +192,7 @@ func (btb *blockTemplateBuilder) ModifyBlockTemplate(newCoinbaseData *consensuse // Update the hash merkle root according to the modified transactions mutableHeader := blockTemplateToModify.Block.Header.ToMutable() // TODO: can be optimized to O(log(#transactions)) by caching the whole merkle tree in BlockTemplate and changing only the relevant path - mutableHeader.SetHashMerkleRoot(merkle.CalculateHashMerkleRoot(blockTemplateToModify.Block.Transactions)) + mutableHeader.SetHashMerkleRoot(merkle.CalculateHashMerkleRoot(blockTemplateToModify.Block.Transactions, mutableHeader.DAAScore() >= btb.hfDAAScore)) newTimestamp := mstime.Now().UnixMilliseconds() if newTimestamp >= mutableHeader.TimeInMilliseconds() { diff --git a/domain/miningmanager/blocktemplatebuilder/txselection.go b/domain/miningmanager/blocktemplatebuilder/txselection.go index 6ebfec689..1bf64bc1b 100644 --- a/domain/miningmanager/blocktemplatebuilder/txselection.go +++ b/domain/miningmanager/blocktemplatebuilder/txselection.go @@ -1,6 +1,7 @@ package blocktemplatebuilder import ( + "github.com/kaspanet/kaspad/domain/consensus/utils/constants" "math" "math/rand" "sort" @@ -74,6 +75,8 @@ func (btb *blockTemplateBuilder) selectTransactions(candidateTxs []*candidateTx) usedP += candidateTx.p } + totalInputs := 0 + selectedTxs := make([]*candidateTx, 0) for len(candidateTxs)-usedCount > 0 { // Rebalance the candidates if it's required @@ -99,6 +102,10 @@ func (btb *blockTemplateBuilder) selectTransactions(candidateTxs []*candidateTx) } tx := selectedTx.DomainTransaction + if totalInputs+len(tx.Inputs) > maxBlockInputsPreHF(btb.hfDAAScore) { + continue + } + // Enforce maximum transaction mass per block. Also check // for overflow. if txsForBlockTemplate.totalMass+selectedTx.Mass < txsForBlockTemplate.totalMass || @@ -143,6 +150,7 @@ func (btb *blockTemplateBuilder) selectTransactions(candidateTxs []*candidateTx) // save the masses, fees, and signature operation counts to the // result. selectedTxs = append(selectedTxs, selectedTx) + totalInputs += len(selectedTx.Inputs) txsForBlockTemplate.totalMass += selectedTx.Mass txsForBlockTemplate.totalFees += selectedTx.Fee @@ -150,6 +158,10 @@ func (btb *blockTemplateBuilder) selectTransactions(candidateTxs []*candidateTx) consensushashing.TransactionID(tx), selectedTx.Fee*1e6/selectedTx.Mass) markCandidateTxForDeletion(selectedTx) + + if totalInputs == maxBlockInputsPreHF(btb.hfDAAScore) { + break + } } sort.Slice(selectedTxs, func(i, j int) bool { @@ -163,6 +175,10 @@ func (btb *blockTemplateBuilder) selectTransactions(candidateTxs []*candidateTx) return txsForBlockTemplate } +func maxBlockInputsPreHF(hfDAAScore uint64) int { + return constants.MaxBlockInputsPreHF +} + func rebalanceCandidates(oldCandidateTxs []*candidateTx, isFirstRun bool) ( candidateTxs []*candidateTx, totalP float64) { diff --git a/domain/miningmanager/factory.go b/domain/miningmanager/factory.go index 16b50fb29..ab5fc537d 100644 --- a/domain/miningmanager/factory.go +++ b/domain/miningmanager/factory.go @@ -21,7 +21,7 @@ func (f *factory) NewMiningManager(consensusReference consensusreference.Consens mempoolConfig *mempoolpkg.Config) MiningManager { mempool := mempoolpkg.New(mempoolConfig, consensusReference) - blockTemplateBuilder := blocktemplatebuilder.New(consensusReference, mempool, params.MaxBlockMass, params.CoinbasePayloadScriptPublicKeyMaxLength) + blockTemplateBuilder := blocktemplatebuilder.New(consensusReference, mempool, params.MaxBlockMass, params.CoinbasePayloadScriptPublicKeyMaxLength, params.HFDAAScore) return &miningManager{ consensusReference: consensusReference, diff --git a/infrastructure/network/netadapter/server/grpcserver/connection_loops.go b/infrastructure/network/netadapter/server/grpcserver/connection_loops.go index 63df69ffc..0f5c6359e 100644 --- a/infrastructure/network/netadapter/server/grpcserver/connection_loops.go +++ b/infrastructure/network/netadapter/server/grpcserver/connection_loops.go @@ -2,8 +2,12 @@ package grpcserver import ( "github.com/davecgh/go-spew/spew" + "github.com/kaspanet/kaspad/app/appmessage" "github.com/kaspanet/kaspad/infrastructure/logger" "io" + "os" + "strconv" + "sync" "time" routerpkg "github.com/kaspanet/kaspad/infrastructure/network/netadapter/router" @@ -25,6 +29,9 @@ func (c *gRPCConnection) connectionLoops() error { return err } +var blockDelayOnce sync.Once +var blockDelay = 0 + func (c *gRPCConnection) sendLoop() error { outgoingRoute := c.router.OutgoingRoute() for c.IsConnected() { @@ -36,6 +43,20 @@ func (c *gRPCConnection) sendLoop() error { return err } + blockDelayOnce.Do(func() { + experimentalDelayEnv := os.Getenv("KASPA_EXPERIMENTAL_DELAY") + if experimentalDelayEnv != "" { + blockDelay, err = strconv.Atoi(experimentalDelayEnv) + if err != nil { + panic(err) + } + } + }) + + if blockDelay != 0 && message.Command() == appmessage.CmdBlock { + time.Sleep(time.Duration(blockDelay) * time.Second) + } + log.Debugf("outgoing '%s' message to %s", message.Command(), c) log.Tracef("outgoing '%s' message to %s: %s", message.Command(), c, logger.NewLogClosure(func() string { return spew.Sdump(message) diff --git a/infrastructure/network/netadapter/server/grpcserver/protowire/rpc_get_connected_peer_info.go b/infrastructure/network/netadapter/server/grpcserver/protowire/rpc_get_connected_peer_info.go index 0ae4abeae..89841678b 100644 --- a/infrastructure/network/netadapter/server/grpcserver/protowire/rpc_get_connected_peer_info.go +++ b/infrastructure/network/netadapter/server/grpcserver/protowire/rpc_get_connected_peer_info.go @@ -35,7 +35,7 @@ func (x *KaspadMessage_GetConnectedPeerInfoResponse) fromAppMessage(message *app TimeOffset: info.TimeOffset, UserAgent: info.UserAgent, AdvertisedProtocolVersion: info.AdvertisedProtocolVersion, - TimeConnected: info.TimeOffset, + TimeConnected: info.TimeConnected, IsIbdPeer: info.IsIBDPeer, } } diff --git a/util/txmass/calculator.go b/util/txmass/calculator.go index 5206f7fe5..4dc286965 100644 --- a/util/txmass/calculator.go +++ b/util/txmass/calculator.go @@ -31,7 +31,7 @@ func (c *Calculator) MassPerScriptPubKeyByte() uint64 { return c.massPerScriptPu func (c *Calculator) MassPerSigOp() uint64 { return c.massPerSigOp } // CalculateTransactionMass calculates the mass of the given transaction -func (c *Calculator) CalculateTransactionMass(transaction *externalapi.DomainTransaction) uint64 { +func (c *Calculator) CalculateTransactionMass(transaction *externalapi.DomainTransaction, postHF bool) uint64 { if transactionhelper.IsCoinBase(transaction) { return 0 } @@ -49,11 +49,14 @@ func (c *Calculator) CalculateTransactionMass(transaction *externalapi.DomainTra massForScriptPubKey := totalScriptPubKeySize * c.massPerScriptPubKeyByte // calculate mass for SigOps - totalSigOpCount := uint64(0) - for _, input := range transaction.Inputs { - totalSigOpCount += uint64(input.SigOpCount) + massForSigOps := uint64(0) + if postHF { + totalSigOpCount := uint64(0) + for _, input := range transaction.Inputs { + totalSigOpCount += uint64(input.SigOpCount) + } + massForSigOps = totalSigOpCount * c.massPerSigOp } - massForSigOps := totalSigOpCount * c.massPerSigOp // Sum all components of mass return massForSize + massForScriptPubKey + massForSigOps diff --git a/version/version.go b/version/version.go index cd27ef67d..bc80ce20e 100644 --- a/version/version.go +++ b/version/version.go @@ -11,7 +11,7 @@ const validCharacters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrs const ( appMajor uint = 0 appMinor uint = 12 - appPatch uint = 6 + appPatch uint = 7 ) // appBuild is defined as a variable so it can be overridden during the build