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 <tal@daglabs.com>
This commit is contained in:
talelbaz 2021-07-14 11:00:03 +03:00 committed by GitHub
parent 28ac77b202
commit 8022e4cbea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 79 additions and 60 deletions

View File

@ -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)
}

View File

@ -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
}

View File

@ -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

View File

@ -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
}

View File

@ -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 {

View File

@ -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.
//

View File

@ -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