Disable relative time lock by time (#1800)

* ignore type flag

* Ignore type flag of relative time lock - interpret as DAA score

* Split verifyLockTime to functions with and without threshold.relative lockTimes dont need threshold check

* Change function name and order of the functions calls

Co-authored-by: tal <tal@daglabs.com>
This commit is contained in:
talelbaz 2021-07-18 17:52:16 +03:00 committed by GitHub
parent c731d74bc0
commit aba44e7bfb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 123 additions and 240 deletions

View File

@ -87,17 +87,12 @@ func (s *consensus) ValidateTransactionAndPopulateWithConsensusData(transaction
return err return err
} }
virtualSelectedParentMedianTime, err := s.pastMedianTimeManager.PastMedianTime(stagingArea, model.VirtualBlockHash)
if err != nil {
return err
}
err = s.transactionValidator.ValidateTransactionInContextIgnoringUTXO(stagingArea, transaction, model.VirtualBlockHash) err = s.transactionValidator.ValidateTransactionInContextIgnoringUTXO(stagingArea, transaction, model.VirtualBlockHash)
if err != nil { if err != nil {
return err return err
} }
return s.transactionValidator.ValidateTransactionInContextAndPopulateFee( return s.transactionValidator.ValidateTransactionInContextAndPopulateFee(
stagingArea, transaction, model.VirtualBlockHash, virtualSelectedParentMedianTime) stagingArea, transaction, model.VirtualBlockHash)
} }
func (s *consensus) GetBlock(blockHash *externalapi.DomainHash) (*externalapi.DomainBlock, error) { func (s *consensus) GetBlock(blockHash *externalapi.DomainHash) (*externalapi.DomainBlock, error) {

View File

@ -11,6 +11,6 @@ type TransactionValidator interface {
ValidateTransactionInContextIgnoringUTXO(stagingArea *StagingArea, tx *externalapi.DomainTransaction, ValidateTransactionInContextIgnoringUTXO(stagingArea *StagingArea, tx *externalapi.DomainTransaction,
povBlockHash *externalapi.DomainHash) error povBlockHash *externalapi.DomainHash) error
ValidateTransactionInContextAndPopulateFee(stagingArea *StagingArea, ValidateTransactionInContextAndPopulateFee(stagingArea *StagingArea,
tx *externalapi.DomainTransaction, povBlockHash *externalapi.DomainHash, selectedParentMedianTime int64) error tx *externalapi.DomainTransaction, povBlockHash *externalapi.DomainHash) error
PopulateMass(transaction *externalapi.DomainTransaction) PopulateMass(transaction *externalapi.DomainTransaction)
} }

View File

@ -150,13 +150,7 @@ func (bb *blockBuilder) validateTransaction(
return err return err
} }
virtualSelectedParentMedianTime, err := bb.pastMedianTimeManager.PastMedianTime(stagingArea, model.VirtualBlockHash) return bb.transactionValidator.ValidateTransactionInContextAndPopulateFee(stagingArea, transaction, model.VirtualBlockHash)
if err != nil {
return err
}
return bb.transactionValidator.ValidateTransactionInContextAndPopulateFee(
stagingArea, transaction, model.VirtualBlockHash, virtualSelectedParentMedianTime)
} }
func (bb *blockBuilder) newBlockCoinbaseTransaction(stagingArea *model.StagingArea, func (bb *blockBuilder) newBlockCoinbaseTransaction(stagingArea *model.StagingArea,

View File

@ -238,7 +238,7 @@ func (csm *consensusStateManager) maybeAcceptTransaction(stagingArea *model.Stag
} else { } else {
log.Tracef("Validating transaction %s in block %s", transactionID, blockHash) log.Tracef("Validating transaction %s in block %s", transactionID, blockHash)
err = csm.transactionValidator.ValidateTransactionInContextAndPopulateFee( err = csm.transactionValidator.ValidateTransactionInContextAndPopulateFee(
stagingArea, transaction, blockHash, selectedParentPastMedianTime) stagingArea, transaction, blockHash)
if err != nil { if err != nil {
if !errors.As(err, &(ruleerrors.RuleError{})) { if !errors.As(err, &(ruleerrors.RuleError{})) {
return false, 0, err return false, 0, err

View File

@ -119,7 +119,7 @@ func (csm *consensusStateManager) importPruningPoint(
} }
log.Tracef("Validating transaction %s and populating it with mass and fee", transactionID) log.Tracef("Validating transaction %s and populating it with mass and fee", transactionID)
err = csm.transactionValidator.ValidateTransactionInContextAndPopulateFee( err = csm.transactionValidator.ValidateTransactionInContextAndPopulateFee(
stagingArea, transaction, newPruningPointHash, newPruningPointSelectedParentMedianTime) stagingArea, transaction, newPruningPointHash)
if err != nil { if err != nil {
return err return err
} }

View File

@ -85,7 +85,7 @@ func (csm *consensusStateManager) validateBlockTransactionsAgainstPastUTXO(stagi
log.Tracef("Validating transaction %s and populating it with fee", transactionID) log.Tracef("Validating transaction %s and populating it with fee", transactionID)
err = csm.transactionValidator.ValidateTransactionInContextAndPopulateFee( err = csm.transactionValidator.ValidateTransactionInContextAndPopulateFee(
stagingArea, transaction, blockHash, selectedParentMedianTime) stagingArea, transaction, blockHash)
if err != nil { if err != nil {
return err return err
} }

View File

@ -70,7 +70,7 @@ func (v *transactionValidator) ValidateTransactionInContextIgnoringUTXO(stagingA
// //
// Note: if the function fails, there's no guarantee that the transaction fee field will remain unaffected. // Note: if the function fails, there's no guarantee that the transaction fee field will remain unaffected.
func (v *transactionValidator) ValidateTransactionInContextAndPopulateFee(stagingArea *model.StagingArea, func (v *transactionValidator) ValidateTransactionInContextAndPopulateFee(stagingArea *model.StagingArea,
tx *externalapi.DomainTransaction, povBlockHash *externalapi.DomainHash, selectedParentMedianTime int64) error { tx *externalapi.DomainTransaction, povBlockHash *externalapi.DomainHash) error {
err := v.checkTransactionCoinbaseMaturity(stagingArea, povBlockHash, tx) err := v.checkTransactionCoinbaseMaturity(stagingArea, povBlockHash, tx)
if err != nil { if err != nil {
@ -89,7 +89,7 @@ func (v *transactionValidator) ValidateTransactionInContextAndPopulateFee(stagin
tx.Fee = totalSompiIn - totalSompiOut tx.Fee = totalSompiIn - totalSompiOut
err = v.checkTransactionSequenceLock(stagingArea, povBlockHash, tx, selectedParentMedianTime) err = v.checkTransactionSequenceLock(stagingArea, povBlockHash, tx)
if err != nil { if err != nil {
return err return err
} }
@ -204,7 +204,7 @@ func (v *transactionValidator) checkTransactionOutputAmounts(tx *externalapi.Dom
} }
func (v *transactionValidator) checkTransactionSequenceLock(stagingArea *model.StagingArea, func (v *transactionValidator) checkTransactionSequenceLock(stagingArea *model.StagingArea,
povBlockHash *externalapi.DomainHash, tx *externalapi.DomainTransaction, medianTime int64) error { povBlockHash *externalapi.DomainHash, tx *externalapi.DomainTransaction) error {
// A transaction can only be included within a block // A transaction can only be included within a block
// once the sequence locks of *all* its inputs are // once the sequence locks of *all* its inputs are
@ -219,7 +219,7 @@ func (v *transactionValidator) checkTransactionSequenceLock(stagingArea *model.S
return err return err
} }
if !v.sequenceLockActive(sequenceLock, daaScore, medianTime) { if !v.sequenceLockActive(sequenceLock, daaScore) {
return errors.Wrapf(ruleerrors.ErrUnfinalizedTx, "block contains "+ return errors.Wrapf(ruleerrors.ErrUnfinalizedTx, "block contains "+
"transaction whose input sequence "+ "transaction whose input sequence "+
"locks are not met") "locks are not met")
@ -271,14 +271,13 @@ func (v *transactionValidator) validateTransactionScripts(tx *externalapi.Domain
func (v *transactionValidator) calcTxSequenceLockFromReferencedUTXOEntries(stagingArea *model.StagingArea, func (v *transactionValidator) calcTxSequenceLockFromReferencedUTXOEntries(stagingArea *model.StagingArea,
povBlockHash *externalapi.DomainHash, tx *externalapi.DomainTransaction) (*sequenceLock, error) { povBlockHash *externalapi.DomainHash, tx *externalapi.DomainTransaction) (*sequenceLock, error) {
// A value of -1 for each relative lock type represents a relative time // A value of -1 represents a relative timelock value that will allow a transaction to be
// lock value that will allow a transaction to be included in a block //included in a block at any given DAA score.
// at any given height or time. sequenceLock := &sequenceLock{BlockDAAScore: -1}
sequenceLock := &sequenceLock{Milliseconds: -1, BlockDAAScore: -1}
// Sequence locks don't apply to coinbase transactions Therefore, we // Sequence locks don't apply to coinbase transactions Therefore, we
// return sequence lock values of -1 indicating that this transaction // return sequence lock values of -1 indicating that this transaction
// can be included within a block at any given height or time. // can be included within a block at any given DAA score.
if transactionhelper.IsCoinBase(tx) { if transactionhelper.IsCoinBase(tx) {
return sequenceLock, nil return sequenceLock, nil
} }
@ -299,70 +298,19 @@ func (v *transactionValidator) calcTxSequenceLockFromReferencedUTXOEntries(stagi
sequenceNum := input.Sequence sequenceNum := input.Sequence
relativeLock := int64(sequenceNum & constants.SequenceLockTimeMask) relativeLock := int64(sequenceNum & constants.SequenceLockTimeMask)
switch {
// Relative time locks are disabled for this input, so we can // Relative time locks are disabled for this input, so we can
// skip any further calculation. // skip any further calculation.
case sequenceNum&constants.SequenceLockTimeDisabled == constants.SequenceLockTimeDisabled: if sequenceNum&constants.SequenceLockTimeDisabled == constants.SequenceLockTimeDisabled {
continue continue
case sequenceNum&constants.SequenceLockTimeIsSeconds == constants.SequenceLockTimeIsSeconds: }
// This input requires a relative time lock expressed // The relative lock-time for this input is expressed
// in seconds before it can be spent. Therefore, we // in blocks so we calculate the relative offset from
// need to query for the block prior to the one in // the input's DAA score as its converted absolute
// which this input was accepted within so we can // lock-time. We subtract one from the relative lock in
// compute the past median time for the block prior to // order to maintain the original lockTime semantics.
// the one which accepted this referenced output. blockDAAScore := int64(inputDAAScore) + relativeLock - 1
baseGHOSTDAGData, err := v.ghostdagDataStore.Get(v.databaseContext, stagingArea, povBlockHash) if blockDAAScore > sequenceLock.BlockDAAScore {
if err != nil { sequenceLock.BlockDAAScore = blockDAAScore
return nil, err
}
baseHash := povBlockHash
for {
selectedParentDAAScore, err := v.daaBlocksStore.DAAScore(v.databaseContext, stagingArea, baseHash)
if err != nil {
return nil, err
}
if selectedParentDAAScore <= inputDAAScore {
break
}
selectedParentGHOSTDAGData, err := v.ghostdagDataStore.Get(
v.databaseContext, stagingArea, baseGHOSTDAGData.SelectedParent())
if err != nil {
return nil, err
}
baseHash = baseGHOSTDAGData.SelectedParent()
baseGHOSTDAGData = selectedParentGHOSTDAGData
}
medianTime, err := v.pastMedianTimeManager.PastMedianTime(stagingArea, baseHash)
if err != nil {
return nil, err
}
// Time based relative time-locks have a time granularity of
// constants.SequenceLockTimeGranularity, so we shift left by this
// amount to convert to the proper relative time-lock. We also
// subtract one from the relative lock to maintain the original
// lockTime semantics.
timeLockMilliseconds := (relativeLock * constants.SequenceLockTimeGranularity) - 1
timeLock := medianTime + timeLockMilliseconds
if timeLock > sequenceLock.Milliseconds {
sequenceLock.Milliseconds = timeLock
}
default:
// The relative lock-time for this input is expressed
// in blocks so we calculate the relative offset from
// the input's DAA score as its converted absolute
// lock-time. We subtract one from the relative lock in
// order to maintain the original lockTime semantics.
blockDAAScore := int64(inputDAAScore) + relativeLock - 1
if blockDAAScore > sequenceLock.BlockDAAScore {
sequenceLock.BlockDAAScore = blockDAAScore
}
} }
} }
if len(missingOutpoints) > 0 { if len(missingOutpoints) > 0 {
@ -372,28 +320,24 @@ func (v *transactionValidator) calcTxSequenceLockFromReferencedUTXOEntries(stagi
return sequenceLock, nil return sequenceLock, nil
} }
// sequenceLock represents the converted relative lock-time in seconds, and // sequenceLock represents the converted relative lock-time in
// absolute block-daa-score for a transaction input's relative lock-times. // absolute block-daa-score for a transaction input's relative lock-times.
// According to sequenceLock, after the referenced input has been confirmed // According to sequenceLock, after the referenced input has been confirmed
// within a block, a transaction spending that input can be included into a // within a block, a transaction spending that input can be included into a
// block either after 'seconds' (according to past median time), or once the // block either after the 'BlockDAAScore' has been reached.
// 'BlockDAAScore' has been reached.
type sequenceLock struct { type sequenceLock struct {
Milliseconds int64
BlockDAAScore int64 BlockDAAScore int64
} }
// sequenceLockActive determines if a transaction's sequence locks have been // sequenceLockActive determines if a transaction's sequence locks have been
// met, meaning that all the inputs of a given transaction have reached a // met, meaning that all the inputs of a given transaction have reached a
// DAA score or time sufficient for their relative lock-time maturity. // DAA score sufficient for their relative lock-time maturity.
func (v *transactionValidator) sequenceLockActive(sequenceLock *sequenceLock, blockDAAScore uint64, func (v *transactionValidator) sequenceLockActive(sequenceLock *sequenceLock, blockDAAScore uint64) bool {
medianTimePast int64) bool {
// If either the milliseconds, or DAA score relative-lock time has not yet // If (DAA score) relative-lock time has not yet
// reached, then the transaction is not yet mature according to its // reached, then the transaction is not yet mature according to its
// sequence locks. // sequence locks.
if sequenceLock.Milliseconds >= medianTimePast || if sequenceLock.BlockDAAScore >= int64(blockDAAScore) {
sequenceLock.BlockDAAScore >= int64(blockDAAScore) {
return false return false
} }

View File

@ -1,7 +1,6 @@
package transactionvalidator package transactionvalidator
import ( import (
"github.com/kaspanet/kaspad/util/mstime"
"testing" "testing"
) )
@ -11,32 +10,22 @@ func TestSequenceLocksActive(t *testing.T) {
tests := []struct { tests := []struct {
seqLock sequenceLock seqLock sequenceLock
blockDAAScore uint64 blockDAAScore uint64
mtp mstime.Time
want bool want bool
}{ }{
// Block based sequence lock with equal block DAA score. // Block based sequence lock with equal block DAA score.
{seqLock: sequenceLock{-1, 1000}, blockDAAScore: 1001, mtp: mstime.UnixMilliseconds(9), want: true}, {seqLock: sequenceLock{1000}, blockDAAScore: 1001, want: true},
// Time based sequence lock with mtp past the absolute time.
{seqLock: sequenceLock{30, -1}, blockDAAScore: 2, mtp: mstime.UnixMilliseconds(31), want: true},
// Block based sequence lock with current DAA score below seq lock block DAA score. // Block based sequence lock with current DAA score below seq lock block DAA score.
{seqLock: sequenceLock{-1, 1000}, blockDAAScore: 90, mtp: mstime.UnixMilliseconds(9), want: false}, {seqLock: sequenceLock{1000}, blockDAAScore: 90, want: false},
// Time based sequence lock with current time before lock time.
{seqLock: sequenceLock{30, -1}, blockDAAScore: 2, mtp: mstime.UnixMilliseconds(29), want: false},
// Block based sequence lock at the same DAA score, so shouldn't yet be active. // Block based sequence lock at the same DAA score, so shouldn't yet be active.
{seqLock: sequenceLock{-1, 1000}, blockDAAScore: 1000, mtp: mstime.UnixMilliseconds(9), want: false}, {seqLock: sequenceLock{1000}, blockDAAScore: 1000, want: false},
// Time based sequence lock with current time equal to lock time, so shouldn't yet be active.
{seqLock: sequenceLock{30, -1}, blockDAAScore: 2, mtp: mstime.UnixMilliseconds(30), want: false},
} }
validator := transactionValidator{} validator := transactionValidator{}
for i, test := range tests { for i, test := range tests {
got := validator.sequenceLockActive(&test.seqLock, test.blockDAAScore, test.mtp.UnixMilliseconds()) got := validator.sequenceLockActive(&test.seqLock, test.blockDAAScore)
if got != test.want { if got != test.want {
t.Fatalf("SequenceLockActive #%d got %v want %v", i, got, test.want) t.Fatalf("SequenceLockActive #%d got %v want %v", i, got, test.want)
} }

View File

@ -19,28 +19,10 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
) )
type mocPastMedianTimeManager struct {
pastMedianTimeForTest int64
}
func (mdf *mocPastMedianTimeManager) InvalidateVirtualPastMedianTimeCache() {
// do nothing
}
// PastMedianTime returns the past median time for the test.
func (mdf *mocPastMedianTimeManager) PastMedianTime(*model.StagingArea, *externalapi.DomainHash) (int64, error) {
return mdf.pastMedianTimeForTest, nil
}
func TestValidateTransactionInContextAndPopulateFee(t *testing.T) { func TestValidateTransactionInContextAndPopulateFee(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) { testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
factory := consensus.NewFactory() factory := consensus.NewFactory()
pastMedianManager := &mocPastMedianTimeManager{}
factory.SetTestPastMedianTimeManager(func(int, model.DBReader, model.DAGTraversalManager, model.BlockHeaderStore,
model.GHOSTDAGDataStore, *externalapi.DomainHash) model.PastMedianTimeManager {
return pastMedianManager
})
tc, tearDown, err := factory.NewTestConsensus(consensusConfig, tc, tearDown, err := factory.NewTestConsensus(consensusConfig,
"TestValidateTransactionInContextAndPopulateFee") "TestValidateTransactionInContextAndPopulateFee")
if err != nil { if err != nil {
@ -48,7 +30,6 @@ func TestValidateTransactionInContextAndPopulateFee(t *testing.T) {
} }
defer tearDown(false) defer tearDown(false)
pastMedianManager.pastMedianTimeForTest = 1
privateKey, err := secp256k1.GenerateSchnorrKeyPair() privateKey, err := secp256k1.GenerateSchnorrKeyPair()
if err != nil { if err != nil {
t.Fatalf("Failed to generate a private key: %v", err) t.Fatalf("Failed to generate a private key: %v", err)
@ -83,7 +64,17 @@ func TestValidateTransactionInContextAndPopulateFee(t *testing.T) {
true, true,
uint64(5)), uint64(5)),
} }
immatureInput := externalapi.DomainTransactionInput{ txInputWrongSignature := externalapi.DomainTransactionInput{
PreviousOutpoint: prevOutPoint,
SignatureScript: []byte{},
SigOpCount: 1,
UTXOEntry: utxo.NewUTXOEntry(
100_000_000, // 1 KAS
scriptPublicKey,
true,
uint64(5)),
}
immatureCoinbaseInput := externalapi.DomainTransactionInput{
PreviousOutpoint: prevOutPoint, PreviousOutpoint: prevOutPoint,
SignatureScript: []byte{}, SignatureScript: []byte{},
Sequence: constants.MaxTxInSequenceNum, Sequence: constants.MaxTxInSequenceNum,
@ -94,17 +85,6 @@ func TestValidateTransactionInContextAndPopulateFee(t *testing.T) {
true, true,
uint64(6)), uint64(6)),
} }
txInputWithSequenceLockTimeIsSeconds := externalapi.DomainTransactionInput{
PreviousOutpoint: prevOutPoint,
SignatureScript: []byte{},
Sequence: constants.SequenceLockTimeIsSeconds,
SigOpCount: 1,
UTXOEntry: utxo.NewUTXOEntry(
100000000, // 1 KAS
scriptPublicKey,
true,
uint64(5)),
}
txInputWithLargeAmount := externalapi.DomainTransactionInput{ txInputWithLargeAmount := externalapi.DomainTransactionInput{
PreviousOutpoint: prevOutPoint, PreviousOutpoint: prevOutPoint,
SignatureScript: []byte{}, SignatureScript: []byte{},
@ -128,19 +108,19 @@ func TestValidateTransactionInContextAndPopulateFee(t *testing.T) {
uint64(5)), uint64(5)),
} }
txOut := externalapi.DomainTransactionOutput{ txOutput := externalapi.DomainTransactionOutput{
Value: 100000000, // 1 KAS Value: 100000000, // 1 KAS
ScriptPublicKey: scriptPublicKey, ScriptPublicKey: scriptPublicKey,
} }
txOutBigValue := externalapi.DomainTransactionOutput{ txOutputBigValue := externalapi.DomainTransactionOutput{
Value: 200_000_000, // 2 KAS Value: 200_000_000, // 2 KAS
ScriptPublicKey: scriptPublicKey, ScriptPublicKey: scriptPublicKey,
} }
validTx := externalapi.DomainTransaction{ validTx := externalapi.DomainTransaction{
Version: constants.MaxTransactionVersion, Version: constants.MaxTransactionVersion,
Inputs: []*externalapi.DomainTransactionInput{&txInputWithSequenceLockTimeIsSeconds}, Inputs: []*externalapi.DomainTransactionInput{&txInput},
Outputs: []*externalapi.DomainTransactionOutput{&txOut}, Outputs: []*externalapi.DomainTransactionOutput{&txOutput},
SubnetworkID: subnetworks.SubnetworkIDRegistry, SubnetworkID: subnetworks.SubnetworkIDRegistry,
Gas: 0, Gas: 0,
LockTime: 0} LockTime: 0}
@ -156,36 +136,36 @@ func TestValidateTransactionInContextAndPopulateFee(t *testing.T) {
txWithImmatureCoinbase := externalapi.DomainTransaction{ txWithImmatureCoinbase := externalapi.DomainTransaction{
Version: constants.MaxTransactionVersion, Version: constants.MaxTransactionVersion,
Inputs: []*externalapi.DomainTransactionInput{&immatureInput}, Inputs: []*externalapi.DomainTransactionInput{&immatureCoinbaseInput},
Outputs: []*externalapi.DomainTransactionOutput{&txOut}, Outputs: []*externalapi.DomainTransactionOutput{&txOutput},
SubnetworkID: subnetworks.SubnetworkIDRegistry, SubnetworkID: subnetworks.SubnetworkIDRegistry,
Gas: 0, Gas: 0,
LockTime: 0} LockTime: 0}
txWithLargeAmount := externalapi.DomainTransaction{ txWithLargeAmount := externalapi.DomainTransaction{
Version: constants.MaxTransactionVersion, Version: constants.MaxTransactionVersion,
Inputs: []*externalapi.DomainTransactionInput{&txInput, &txInputWithLargeAmount}, Inputs: []*externalapi.DomainTransactionInput{&txInput, &txInputWithLargeAmount},
Outputs: []*externalapi.DomainTransactionOutput{&txOut}, Outputs: []*externalapi.DomainTransactionOutput{&txOutput},
SubnetworkID: subnetworks.SubnetworkIDRegistry, SubnetworkID: subnetworks.SubnetworkIDRegistry,
Gas: 0, Gas: 0,
LockTime: 0} LockTime: 0}
txWithBigValue := externalapi.DomainTransaction{ txWithBigValue := externalapi.DomainTransaction{
Version: constants.MaxTransactionVersion, Version: constants.MaxTransactionVersion,
Inputs: []*externalapi.DomainTransactionInput{&txInput}, Inputs: []*externalapi.DomainTransactionInput{&txInput},
Outputs: []*externalapi.DomainTransactionOutput{&txOutBigValue}, Outputs: []*externalapi.DomainTransactionOutput{&txOutputBigValue},
SubnetworkID: subnetworks.SubnetworkIDRegistry, SubnetworkID: subnetworks.SubnetworkIDRegistry,
Gas: 0, Gas: 0,
LockTime: 0} LockTime: 0}
txWithInvalidSignature := externalapi.DomainTransaction{ txWithInvalidSignature := externalapi.DomainTransaction{
Version: constants.MaxTransactionVersion, Version: constants.MaxTransactionVersion,
Inputs: []*externalapi.DomainTransactionInput{&txInput}, Inputs: []*externalapi.DomainTransactionInput{&txInputWrongSignature},
Outputs: []*externalapi.DomainTransactionOutput{&txOut}, Outputs: []*externalapi.DomainTransactionOutput{&txOutput},
SubnetworkID: subnetworks.SubnetworkIDRegistry, SubnetworkID: subnetworks.SubnetworkIDRegistry,
Gas: 0, Gas: 0,
LockTime: 0} LockTime: 0}
txWithBadSigOpCount := externalapi.DomainTransaction{ txWithBadSigOpCount := externalapi.DomainTransaction{
Version: constants.MaxTransactionVersion, Version: constants.MaxTransactionVersion,
Inputs: []*externalapi.DomainTransactionInput{&txInputWithBadSigOpCount}, Inputs: []*externalapi.DomainTransactionInput{&txInputWithBadSigOpCount},
Outputs: []*externalapi.DomainTransactionOutput{&txOut}, Outputs: []*externalapi.DomainTransactionOutput{&txOutput},
SubnetworkID: subnetworks.SubnetworkIDRegistry, SubnetworkID: subnetworks.SubnetworkIDRegistry,
Gas: 0, Gas: 0,
LockTime: 0} LockTime: 0}
@ -205,75 +185,60 @@ func TestValidateTransactionInContextAndPopulateFee(t *testing.T) {
nil)) nil))
tests := []struct { tests := []struct {
name string name string
tx *externalapi.DomainTransaction tx *externalapi.DomainTransaction
povBlockHash *externalapi.DomainHash povBlockHash *externalapi.DomainHash
selectedParentMedianTime int64 isValid bool
isValid bool expectedError error
expectedError error
}{ }{
{ {
name: "Valid transaction", name: "Valid transaction",
tx: &validTx, tx: &validTx,
povBlockHash: povBlockHash, povBlockHash: povBlockHash,
selectedParentMedianTime: 1, isValid: true,
isValid: true, expectedError: nil,
expectedError: nil,
}, },
{ // The calculated block coinbase maturity is smaller than the minimum expected blockCoinbaseMaturity. { // The calculated block coinbase maturity is smaller than the minimum expected blockCoinbaseMaturity.
// The povBlockHash DAA score is 10 and the UTXO DAA score is 5, hence the The subtraction between // The povBlockHash DAA score is 10 and the UTXO DAA score is 5, hence the The subtraction between
// them will yield a smaller result than the required CoinbaseMaturity (currently set to 100). // them will yield a smaller result than the required CoinbaseMaturity (currently set to 100).
name: "checkTransactionCoinbaseMaturity", name: "checkTransactionCoinbaseMaturity",
tx: &txWithImmatureCoinbase, tx: &txWithImmatureCoinbase,
povBlockHash: povBlockHash, povBlockHash: povBlockHash,
selectedParentMedianTime: 1, isValid: false,
isValid: false, expectedError: ruleerrors.ErrImmatureSpend,
expectedError: ruleerrors.ErrImmatureSpend,
}, },
{ // The total inputs amount is bigger than the allowed maximum (constants.MaxSompi) { // The total inputs amount is bigger than the allowed maximum (constants.MaxSompi)
name: "checkTransactionInputAmounts", name: "checkTransactionInputAmounts",
tx: &txWithLargeAmount, tx: &txWithLargeAmount,
povBlockHash: povBlockHash, povBlockHash: povBlockHash,
selectedParentMedianTime: 1, isValid: false,
isValid: false, expectedError: ruleerrors.ErrBadTxOutValue,
expectedError: ruleerrors.ErrBadTxOutValue,
}, },
{ // The total SompiIn (sum of inputs amount) is smaller than the total SompiOut (sum of outputs value) and hence invalid. { // The total SompiIn (sum of inputs amount) is smaller than the total SompiOut (sum of outputs value) and hence invalid.
name: "checkTransactionOutputAmounts", name: "checkTransactionOutputAmounts",
tx: &txWithBigValue, tx: &txWithBigValue,
povBlockHash: povBlockHash, povBlockHash: povBlockHash,
selectedParentMedianTime: 1, isValid: false,
isValid: false, expectedError: ruleerrors.ErrSpendTooHigh,
expectedError: ruleerrors.ErrSpendTooHigh,
}, },
{ // the selectedParentMedianTime is negative and hence invalid. { // The SignatureScript is wrong
name: "checkTransactionSequenceLock", name: "checkTransactionScripts",
tx: &validTx, tx: &txWithInvalidSignature,
povBlockHash: povBlockHash, povBlockHash: povBlockHash,
selectedParentMedianTime: -1, isValid: false,
isValid: false, expectedError: ruleerrors.ErrScriptValidation,
expectedError: ruleerrors.ErrUnfinalizedTx,
},
{ // The SignatureScript (in the immatureInput) is empty and hence invalid.
name: "checkTransactionScripts",
tx: &txWithInvalidSignature,
povBlockHash: povBlockHash,
selectedParentMedianTime: 1,
isValid: false,
expectedError: ruleerrors.ErrScriptValidation,
}, },
{ // the SigOpCount in the input is wrong, and hence invalid { // the SigOpCount in the input is wrong, and hence invalid
name: "checkTransactionSigOpCounts", name: "checkTransactionSigOpCounts",
tx: &txWithBadSigOpCount, tx: &txWithBadSigOpCount,
povBlockHash: povBlockHash, povBlockHash: povBlockHash,
selectedParentMedianTime: 1, isValid: false,
isValid: false, expectedError: ruleerrors.ErrWrongSigOpCount,
expectedError: ruleerrors.ErrWrongSigOpCount,
}, },
} }
for _, test := range tests { for _, test := range tests {
err := tc.TransactionValidator().ValidateTransactionInContextAndPopulateFee(stagingArea, test.tx, test.povBlockHash, test.selectedParentMedianTime) err := tc.TransactionValidator().ValidateTransactionInContextAndPopulateFee(stagingArea, test.tx, test.povBlockHash)
if test.isValid { if test.isValid {
if err != nil { if err != nil {

View File

@ -28,22 +28,11 @@ const (
// as a relative locktime. // as a relative locktime.
SequenceLockTimeDisabled uint64 = 1 << 63 SequenceLockTimeDisabled uint64 = 1 << 63
// SequenceLockTimeIsSeconds is a flag that if set on a transaction
// input's sequence number, the relative locktime has units of 1 second.
// If the flag is not set, the relative lockatime is according to DAA score.
SequenceLockTimeIsSeconds uint64 = 1 << 62
// SequenceLockTimeMask is a mask that extracts the relative locktime // SequenceLockTimeMask is a mask that extracts the relative locktime
// when masked against the transaction input sequence number. // when masked against the transaction input sequence number.
SequenceLockTimeMask uint64 = 0x00000000ffffffff SequenceLockTimeMask uint64 = 0x00000000ffffffff
// SequenceLockTimeGranularity is the defined time based granularity
// for milliseconds-based relative time locks. When converting from milliseconds
// to a sequence number, the value is multiplied by this amount,
// therefore the granularity of relative time locks is 1000 milliseconds or 1 second.
SequenceLockTimeGranularity = 1000
// LockTimeThreshold is the number below which a lock time is // LockTimeThreshold is the number below which a lock time is
// interpreted to be a block number. // interpreted to be a DAA score.
LockTimeThreshold = 5e11 // Tue Nov 5 00:53:20 1985 UTC LockTimeThreshold = 5e11 // Tue Nov 5 00:53:20 1985 UTC
) )

View File

@ -1068,17 +1068,16 @@ func opcodeReturn(op *parsedOpcode, vm *Engine) error {
return scriptError(ErrEarlyReturn, "script returned early") return scriptError(ErrEarlyReturn, "script returned early")
} }
// verifyLockTime is a helper function used to validate locktimes. func verifyLockTimeWithThreshold(txLockTime, threshold, lockTime uint64) error {
func verifyLockTime(txLockTime, threshold, lockTime uint64) error { err := verifyLockTimeThreshold(txLockTime, threshold, lockTime)
// The lockTimes in both the script and transaction must be of the same if err != nil {
// type. return err
if !((txLockTime < threshold && lockTime < threshold) ||
(txLockTime >= threshold && lockTime >= threshold)) {
str := fmt.Sprintf("mismatched locktime types -- tx locktime "+
"%d, stack locktime %d", txLockTime, lockTime)
return scriptError(ErrUnsatisfiedLockTime, str)
} }
return verifyLockTime(txLockTime, lockTime)
}
// checkLockTimeRequirement is a helper function used to validate locktimes.
func verifyLockTime(txLockTime, lockTime uint64) error {
if lockTime > txLockTime { if lockTime > txLockTime {
str := fmt.Sprintf("locktime requirement not satisfied -- "+ str := fmt.Sprintf("locktime requirement not satisfied -- "+
"locktime is greater than the transaction locktime: "+ "locktime is greater than the transaction locktime: "+
@ -1089,6 +1088,18 @@ func verifyLockTime(txLockTime, threshold, lockTime uint64) error {
return nil return nil
} }
// verifyLockTimeThreshold is a helper function used to verify the lockTimes in both the script and transaction have the same type.
func verifyLockTimeThreshold(txLockTime, threshold, lockTime uint64) error {
if !((txLockTime < threshold && lockTime < threshold) ||
(txLockTime >= threshold && lockTime >= threshold)) {
str := fmt.Sprintf("mismatched locktime types -- tx locktime "+
"%d, stack locktime %d", txLockTime, lockTime)
return scriptError(ErrUnsatisfiedLockTime, str)
}
return nil
}
// opcodeCheckLockTimeVerify compares the top item on the data stack to the // opcodeCheckLockTimeVerify compares the top item on the data stack to the
// LockTime field of the transaction containing the script signature // LockTime field of the transaction containing the script signature
// validating if the transaction outputs are spendable yet. // validating if the transaction outputs are spendable yet.
@ -1111,12 +1122,11 @@ func opcodeCheckLockTimeVerify(op *parsedOpcode, vm *Engine) error {
lockTimeBytes = paddedLockTimeBytes lockTimeBytes = paddedLockTimeBytes
} }
stackLockTime := binary.LittleEndian.Uint64(lockTimeBytes) stackLockTime := binary.LittleEndian.Uint64(lockTimeBytes)
// The lock time field of a transaction is either a block height at // The lock time field of a transaction is either a DAA score at
// which the transaction is finalized or a timestamp depending on if the // which the transaction is finalized or a timestamp depending on if the
// value is before the constants.LockTimeThreshold. When it is under the // value is before the constants.LockTimeThreshold. When it is under the
// threshold it is a block height. // threshold it is a DAA score.
err = verifyLockTime(vm.tx.LockTime, constants.LockTimeThreshold, err = verifyLockTimeWithThreshold(vm.tx.LockTime, constants.LockTimeThreshold, stackLockTime)
stackLockTime)
if err != nil { if err != nil {
return err return err
} }
@ -1188,10 +1198,9 @@ func opcodeCheckSequenceVerify(op *parsedOpcode, vm *Engine) error {
} }
// Mask off non-consensus bits before doing comparisons. // Mask off non-consensus bits before doing comparisons.
lockTimeMask := constants.SequenceLockTimeIsSeconds | constants.SequenceLockTimeMask maskedTxSequence := txSequence & constants.SequenceLockTimeMask
maskedTxSequence := txSequence & lockTimeMask maskedStackSequence := stackSequence & constants.SequenceLockTimeMask
maskedStackSequence := stackSequence & lockTimeMask return verifyLockTime(maskedTxSequence, maskedStackSequence)
return verifyLockTime(maskedTxSequence, constants.SequenceLockTimeIsSeconds, maskedStackSequence)
} }
// opcodeToAltStack removes the top item from the main data stack and pushes it // opcodeToAltStack removes the top item from the main data stack and pushes it

View File

@ -21,8 +21,6 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
) )
const blockMaxMass uint64 = 10000000
// TestValidateAndInsertTransaction verifies that valid transactions were successfully inserted into the mempool. // TestValidateAndInsertTransaction verifies that valid transactions were successfully inserted into the mempool.
func TestValidateAndInsertTransaction(t *testing.T) { func TestValidateAndInsertTransaction(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) { testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
@ -553,10 +551,10 @@ func createTransactionWithUTXOEntry(t *testing.T, i int) *externalapi.DomainTran
if err != nil { if err != nil {
t.Fatalf("PayToScriptHashSignatureScript: %v", err) t.Fatalf("PayToScriptHashSignatureScript: %v", err)
} }
txInputWithMaxSequence := externalapi.DomainTransactionInput{ txInput := externalapi.DomainTransactionInput{
PreviousOutpoint: prevOutPoint, PreviousOutpoint: prevOutPoint,
SignatureScript: signatureScript, SignatureScript: signatureScript,
Sequence: constants.SequenceLockTimeIsSeconds, Sequence: constants.MaxTxInSequenceNum,
UTXOEntry: utxo.NewUTXOEntry( UTXOEntry: utxo.NewUTXOEntry(
100000000, // 1 KAS 100000000, // 1 KAS
scriptPublicKey, scriptPublicKey,
@ -569,7 +567,7 @@ func createTransactionWithUTXOEntry(t *testing.T, i int) *externalapi.DomainTran
} }
tx := externalapi.DomainTransaction{ tx := externalapi.DomainTransaction{
Version: constants.MaxTransactionVersion, Version: constants.MaxTransactionVersion,
Inputs: []*externalapi.DomainTransactionInput{&txInputWithMaxSequence}, Inputs: []*externalapi.DomainTransactionInput{&txInput},
Outputs: []*externalapi.DomainTransactionOutput{&txOut}, Outputs: []*externalapi.DomainTransactionOutput{&txOut},
SubnetworkID: subnetworks.SubnetworkIDNative, SubnetworkID: subnetworks.SubnetworkIDNative,
Gas: 0, Gas: 0,