From 8022e4cbea2cd24cf8ea79459d84c65089f4a87d Mon Sep 17 00:00:00 2001 From: talelbaz <63008512+talelbaz@users.noreply.github.com> Date: Wed, 14 Jul 2021 11:00:03 +0300 Subject: [PATCH] Validate locktime when admitted into mempool and when building a block. (#1794) * Validate locktime when admitted into mempool and when build a block. Also, fix isFinalized to use DAAscore instead of blue score. * Change the function name:ValidateTransactionInContextIgnoringUTXO Co-authored-by: tal --- domain/consensus/consensus.go | 5 ++ ...nterface_processes_transactionvalidator.go | 2 + .../processes/blockbuilder/block_builder.go | 5 ++ .../blockvalidator/block_body_in_context.go | 57 ++----------------- .../block_body_in_context_test.go | 13 +++-- .../transaction_in_context.go | 54 ++++++++++++++++++ domain/consensus/timelock_CLTV_test.go | 3 +- 7 files changed, 79 insertions(+), 60 deletions(-) diff --git a/domain/consensus/consensus.go b/domain/consensus/consensus.go index 8116027f4..9d7647957 100644 --- a/domain/consensus/consensus.go +++ b/domain/consensus/consensus.go @@ -92,6 +92,11 @@ func (s *consensus) ValidateTransactionAndPopulateWithConsensusData(transaction return err } + err = s.transactionValidator.ValidateTransactionInContextIgnoringUTXO(stagingArea, transaction, model.VirtualBlockHash) + if err != nil { + return err + } + return s.transactionValidator.ValidateTransactionInContextAndPopulateMassAndFee( stagingArea, transaction, model.VirtualBlockHash, virtualSelectedParentMedianTime) } diff --git a/domain/consensus/model/interface_processes_transactionvalidator.go b/domain/consensus/model/interface_processes_transactionvalidator.go index 26b590d68..e59821c2f 100644 --- a/domain/consensus/model/interface_processes_transactionvalidator.go +++ b/domain/consensus/model/interface_processes_transactionvalidator.go @@ -8,6 +8,8 @@ import ( // it's possible to determine whether a transaction is valid type TransactionValidator interface { ValidateTransactionInIsolation(transaction *externalapi.DomainTransaction) error + ValidateTransactionInContextIgnoringUTXO(stagingArea *StagingArea, tx *externalapi.DomainTransaction, + povBlockHash *externalapi.DomainHash) error ValidateTransactionInContextAndPopulateMassAndFee(stagingArea *StagingArea, tx *externalapi.DomainTransaction, povBlockHash *externalapi.DomainHash, selectedParentMedianTime int64) error } diff --git a/domain/consensus/processes/blockbuilder/block_builder.go b/domain/consensus/processes/blockbuilder/block_builder.go index 7e3ff4d4d..95f6ee027 100644 --- a/domain/consensus/processes/blockbuilder/block_builder.go +++ b/domain/consensus/processes/blockbuilder/block_builder.go @@ -145,6 +145,11 @@ func (bb *blockBuilder) validateTransaction( return err } + err = bb.transactionValidator.ValidateTransactionInContextIgnoringUTXO(stagingArea, transaction, model.VirtualBlockHash) + if err != nil { + return err + } + virtualSelectedParentMedianTime, err := bb.pastMedianTimeManager.PastMedianTime(stagingArea, model.VirtualBlockHash) if err != nil { return err diff --git a/domain/consensus/processes/blockvalidator/block_body_in_context.go b/domain/consensus/processes/blockvalidator/block_body_in_context.go index 8596d55b4..404f19d97 100644 --- a/domain/consensus/processes/blockvalidator/block_body_in_context.go +++ b/domain/consensus/processes/blockvalidator/block_body_in_context.go @@ -1,13 +1,9 @@ package blockvalidator import ( - "math" - "github.com/kaspanet/kaspad/domain/consensus/model" "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/infrastructure/logger" "github.com/pkg/errors" ) @@ -23,7 +19,7 @@ func (v *blockValidator) ValidateBodyInContext(stagingArea *model.StagingArea, b return err } - err = v.checkBlockTransactionsFinalized(stagingArea, blockHash) + err = v.checkBlockTransactions(stagingArea, blockHash) if err != nil { return err } @@ -117,7 +113,7 @@ func (v *blockValidator) checkParentBlockBodiesExist( return nil } -func (v *blockValidator) checkBlockTransactionsFinalized( +func (v *blockValidator) checkBlockTransactions( stagingArea *model.StagingArea, blockHash *externalapi.DomainHash) error { block, err := v.blockStore.Block(v.databaseContext, stagingArea, blockHash) @@ -125,57 +121,12 @@ func (v *blockValidator) checkBlockTransactionsFinalized( return err } - ghostdagData, err := v.ghostdagDataStore.Get(v.databaseContext, stagingArea, blockHash) - if err != nil { - return err - } - - blockTime, err := v.pastMedianTimeManager.PastMedianTime(stagingArea, blockHash) - if err != nil { - return err - } - // Ensure all transactions in the block are finalized. for _, tx := range block.Transactions { - if !v.isFinalizedTransaction(tx, ghostdagData.BlueScore(), blockTime) { - txID := consensushashing.TransactionID(tx) - return errors.Wrapf(ruleerrors.ErrUnfinalizedTx, "block contains unfinalized "+ - "transaction %s", txID) + if err = v.transactionValidator.ValidateTransactionInContextIgnoringUTXO(stagingArea, tx, blockHash); err != nil { + return err } } return nil } - -// IsFinalizedTransaction determines whether or not a transaction is finalized. -func (v *blockValidator) isFinalizedTransaction(tx *externalapi.DomainTransaction, blockBlueScore uint64, blockTime int64) bool { - // Lock time of zero means the transaction is finalized. - lockTime := tx.LockTime - if lockTime == 0 { - return true - } - - // The lock time field of a transaction is either a block blue score at - // which the transaction is finalized or a timestamp depending on if the - // value is before the constants.LockTimeThreshold. When it is under the - // threshold it is a block blue score. - blockTimeOrBlueScore := uint64(0) - if lockTime < constants.LockTimeThreshold { - blockTimeOrBlueScore = blockBlueScore - } else { - blockTimeOrBlueScore = uint64(blockTime) - } - if lockTime < blockTimeOrBlueScore { - return true - } - - // At this point, the transaction's lock time hasn't occurred yet, but - // the transaction might still be finalized if the sequence number - // for all transaction inputs is maxed out. - for _, input := range tx.Inputs { - if input.Sequence != math.MaxUint64 { - return false - } - } - return true -} diff --git a/domain/consensus/processes/blockvalidator/block_body_in_context_test.go b/domain/consensus/processes/blockvalidator/block_body_in_context_test.go index 8222ed7ed..c17d688fd 100644 --- a/domain/consensus/processes/blockvalidator/block_body_in_context_test.go +++ b/domain/consensus/processes/blockvalidator/block_body_in_context_test.go @@ -187,9 +187,10 @@ func TestIsFinalizedTransaction(t *testing.T) { if err != nil { t.Fatalf("Error Inserting block: %+v", err) } - blockGhostDAG, err := tc.GHOSTDAGDataStore().Get(tc.DatabaseContext(), stagingArea, consensushashing.BlockHash(block)) + blockHash := consensushashing.BlockHash(block) + blockDAAScore, err := tc.DAABlocksStore().DAAScore(tc.DatabaseContext(), stagingArea, blockHash) if err != nil { - t.Fatalf("Error getting GhostDAG Data: %+v", err) + t.Fatalf("Error getting block DAA score : %+v", err) } blockParents := block.Header.ParentHashes() parentToSpend, err := tc.GetBlock(blockParents[0]) @@ -212,10 +213,10 @@ func TestIsFinalizedTransaction(t *testing.T) { } } - // Check that the same blueScore or higher fails, but lower passes. - checkForLockTimeAndSequence(blockGhostDAG.BlueScore()+1, 0, false) - checkForLockTimeAndSequence(blockGhostDAG.BlueScore(), 0, false) - checkForLockTimeAndSequence(blockGhostDAG.BlueScore()-1, 0, true) + // Check that the same DAAScore or higher fails, but lower passes. + checkForLockTimeAndSequence(blockDAAScore+1, 0, false) + checkForLockTimeAndSequence(blockDAAScore, 0, false) + checkForLockTimeAndSequence(blockDAAScore-1, 0, true) pastMedianTime, err := tc.PastMedianTimeManager().PastMedianTime(stagingArea, consensushashing.BlockHash(block)) if err != nil { diff --git a/domain/consensus/processes/transactionvalidator/transaction_in_context.go b/domain/consensus/processes/transactionvalidator/transaction_in_context.go index 8eea98c3f..41dc53d2c 100644 --- a/domain/consensus/processes/transactionvalidator/transaction_in_context.go +++ b/domain/consensus/processes/transactionvalidator/transaction_in_context.go @@ -1,6 +1,8 @@ package transactionvalidator import ( + "math" + "github.com/kaspanet/kaspad/domain/consensus/model" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" "github.com/kaspanet/kaspad/domain/consensus/ruleerrors" @@ -11,6 +13,58 @@ import ( "github.com/pkg/errors" ) +// IsFinalizedTransaction determines whether or not a transaction is finalized. +func (v *transactionValidator) IsFinalizedTransaction(tx *externalapi.DomainTransaction, blockDAAScore uint64, blockTime int64) bool { + // Lock time of zero means the transaction is finalized. + lockTime := tx.LockTime + if lockTime == 0 { + return true + } + + // The lock time field of a transaction is either a block DAA score at + // which the transaction is finalized or a timestamp depending on if the + // value is before the constants.LockTimeThreshold. When it is under the + // threshold it is a DAA score. + blockTimeOrBlueScore := uint64(0) + if lockTime < constants.LockTimeThreshold { + blockTimeOrBlueScore = blockDAAScore + } else { + blockTimeOrBlueScore = uint64(blockTime) + } + if lockTime < blockTimeOrBlueScore { + return true + } + + // At this point, the transaction's lock time hasn't occurred yet, but + // the transaction might still be finalized if the sequence number + // for all transaction inputs is maxed out. + for _, input := range tx.Inputs { + if input.Sequence != math.MaxUint64 { + return false + } + } + return true +} + +// ValidateTransactionInContextIgnoringUTXO validates the transaction with consensus context but ignoring UTXO +func (v *transactionValidator) ValidateTransactionInContextIgnoringUTXO(stagingArea *model.StagingArea, tx *externalapi.DomainTransaction, + povBlockHash *externalapi.DomainHash) error { + + povBlockDAAScore, err := v.daaBlocksStore.DAAScore(v.databaseContext, stagingArea, povBlockHash) + if err != nil { + return err + } + povBlockPastMedianTime, err := v.pastMedianTimeManager.PastMedianTime(stagingArea, povBlockHash) + if err != nil { + return err + } + if isFinalized := v.IsFinalizedTransaction(tx, povBlockDAAScore, povBlockPastMedianTime); !isFinalized { + return errors.Wrapf(ruleerrors.ErrUnfinalizedTx, "unfinalized transaction %v", tx) + } + + return nil +} + // ValidateTransactionInContextAndPopulateMassAndFee validates the transaction against its referenced UTXO, and // populates its mass and fee fields. // diff --git a/domain/consensus/timelock_CLTV_test.go b/domain/consensus/timelock_CLTV_test.go index e947ed6c3..4b651e55d 100644 --- a/domain/consensus/timelock_CLTV_test.go +++ b/domain/consensus/timelock_CLTV_test.go @@ -2,6 +2,8 @@ package consensus_test import ( "errors" + "testing" + "github.com/kaspanet/kaspad/domain/consensus" "github.com/kaspanet/kaspad/domain/consensus/model" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" @@ -11,7 +13,6 @@ import ( "github.com/kaspanet/kaspad/domain/consensus/utils/testutils" "github.com/kaspanet/kaspad/domain/consensus/utils/transactionhelper" "github.com/kaspanet/kaspad/domain/consensus/utils/txscript" - "testing" ) // TestCheckLockTimeVerifyConditionedByDAAScore verifies that an output locked by the CLTV script is spendable only after