mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-06-04 13:16:43 +00:00
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:
parent
c731d74bc0
commit
aba44e7bfb
@ -87,17 +87,12 @@ func (s *consensus) ValidateTransactionAndPopulateWithConsensusData(transaction
|
||||
return err
|
||||
}
|
||||
|
||||
virtualSelectedParentMedianTime, err := s.pastMedianTimeManager.PastMedianTime(stagingArea, model.VirtualBlockHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.transactionValidator.ValidateTransactionInContextIgnoringUTXO(stagingArea, transaction, model.VirtualBlockHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.transactionValidator.ValidateTransactionInContextAndPopulateFee(
|
||||
stagingArea, transaction, model.VirtualBlockHash, virtualSelectedParentMedianTime)
|
||||
stagingArea, transaction, model.VirtualBlockHash)
|
||||
}
|
||||
|
||||
func (s *consensus) GetBlock(blockHash *externalapi.DomainHash) (*externalapi.DomainBlock, error) {
|
||||
|
@ -11,6 +11,6 @@ type TransactionValidator interface {
|
||||
ValidateTransactionInContextIgnoringUTXO(stagingArea *StagingArea, tx *externalapi.DomainTransaction,
|
||||
povBlockHash *externalapi.DomainHash) error
|
||||
ValidateTransactionInContextAndPopulateFee(stagingArea *StagingArea,
|
||||
tx *externalapi.DomainTransaction, povBlockHash *externalapi.DomainHash, selectedParentMedianTime int64) error
|
||||
tx *externalapi.DomainTransaction, povBlockHash *externalapi.DomainHash) error
|
||||
PopulateMass(transaction *externalapi.DomainTransaction)
|
||||
}
|
||||
|
@ -150,13 +150,7 @@ func (bb *blockBuilder) validateTransaction(
|
||||
return err
|
||||
}
|
||||
|
||||
virtualSelectedParentMedianTime, err := bb.pastMedianTimeManager.PastMedianTime(stagingArea, model.VirtualBlockHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return bb.transactionValidator.ValidateTransactionInContextAndPopulateFee(
|
||||
stagingArea, transaction, model.VirtualBlockHash, virtualSelectedParentMedianTime)
|
||||
return bb.transactionValidator.ValidateTransactionInContextAndPopulateFee(stagingArea, transaction, model.VirtualBlockHash)
|
||||
}
|
||||
|
||||
func (bb *blockBuilder) newBlockCoinbaseTransaction(stagingArea *model.StagingArea,
|
||||
|
@ -238,7 +238,7 @@ func (csm *consensusStateManager) maybeAcceptTransaction(stagingArea *model.Stag
|
||||
} else {
|
||||
log.Tracef("Validating transaction %s in block %s", transactionID, blockHash)
|
||||
err = csm.transactionValidator.ValidateTransactionInContextAndPopulateFee(
|
||||
stagingArea, transaction, blockHash, selectedParentPastMedianTime)
|
||||
stagingArea, transaction, blockHash)
|
||||
if err != nil {
|
||||
if !errors.As(err, &(ruleerrors.RuleError{})) {
|
||||
return false, 0, err
|
||||
|
@ -119,7 +119,7 @@ func (csm *consensusStateManager) importPruningPoint(
|
||||
}
|
||||
log.Tracef("Validating transaction %s and populating it with mass and fee", transactionID)
|
||||
err = csm.transactionValidator.ValidateTransactionInContextAndPopulateFee(
|
||||
stagingArea, transaction, newPruningPointHash, newPruningPointSelectedParentMedianTime)
|
||||
stagingArea, transaction, newPruningPointHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -85,7 +85,7 @@ func (csm *consensusStateManager) validateBlockTransactionsAgainstPastUTXO(stagi
|
||||
|
||||
log.Tracef("Validating transaction %s and populating it with fee", transactionID)
|
||||
err = csm.transactionValidator.ValidateTransactionInContextAndPopulateFee(
|
||||
stagingArea, transaction, blockHash, selectedParentMedianTime)
|
||||
stagingArea, transaction, blockHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -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.
|
||||
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)
|
||||
if err != nil {
|
||||
@ -89,7 +89,7 @@ func (v *transactionValidator) ValidateTransactionInContextAndPopulateFee(stagin
|
||||
|
||||
tx.Fee = totalSompiIn - totalSompiOut
|
||||
|
||||
err = v.checkTransactionSequenceLock(stagingArea, povBlockHash, tx, selectedParentMedianTime)
|
||||
err = v.checkTransactionSequenceLock(stagingArea, povBlockHash, tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -204,7 +204,7 @@ func (v *transactionValidator) checkTransactionOutputAmounts(tx *externalapi.Dom
|
||||
}
|
||||
|
||||
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
|
||||
// once the sequence locks of *all* its inputs are
|
||||
@ -219,7 +219,7 @@ func (v *transactionValidator) checkTransactionSequenceLock(stagingArea *model.S
|
||||
return err
|
||||
}
|
||||
|
||||
if !v.sequenceLockActive(sequenceLock, daaScore, medianTime) {
|
||||
if !v.sequenceLockActive(sequenceLock, daaScore) {
|
||||
return errors.Wrapf(ruleerrors.ErrUnfinalizedTx, "block contains "+
|
||||
"transaction whose input sequence "+
|
||||
"locks are not met")
|
||||
@ -271,14 +271,13 @@ func (v *transactionValidator) validateTransactionScripts(tx *externalapi.Domain
|
||||
func (v *transactionValidator) calcTxSequenceLockFromReferencedUTXOEntries(stagingArea *model.StagingArea,
|
||||
povBlockHash *externalapi.DomainHash, tx *externalapi.DomainTransaction) (*sequenceLock, error) {
|
||||
|
||||
// A value of -1 for each relative lock type represents a relative time
|
||||
// lock value that will allow a transaction to be included in a block
|
||||
// at any given height or time.
|
||||
sequenceLock := &sequenceLock{Milliseconds: -1, BlockDAAScore: -1}
|
||||
// A value of -1 represents a relative timelock value that will allow a transaction to be
|
||||
//included in a block at any given DAA score.
|
||||
sequenceLock := &sequenceLock{BlockDAAScore: -1}
|
||||
|
||||
// Sequence locks don't apply to coinbase transactions Therefore, we
|
||||
// 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) {
|
||||
return sequenceLock, nil
|
||||
}
|
||||
@ -299,70 +298,19 @@ func (v *transactionValidator) calcTxSequenceLockFromReferencedUTXOEntries(stagi
|
||||
sequenceNum := input.Sequence
|
||||
relativeLock := int64(sequenceNum & constants.SequenceLockTimeMask)
|
||||
|
||||
switch {
|
||||
// Relative time locks are disabled for this input, so we can
|
||||
// skip any further calculation.
|
||||
case sequenceNum&constants.SequenceLockTimeDisabled == constants.SequenceLockTimeDisabled:
|
||||
if sequenceNum&constants.SequenceLockTimeDisabled == constants.SequenceLockTimeDisabled {
|
||||
continue
|
||||
case sequenceNum&constants.SequenceLockTimeIsSeconds == constants.SequenceLockTimeIsSeconds:
|
||||
// This input requires a relative time lock expressed
|
||||
// in seconds before it can be spent. Therefore, we
|
||||
// need to query for the block prior to the one in
|
||||
// which this input was accepted within so we can
|
||||
// compute the past median time for the block prior to
|
||||
// the one which accepted this referenced output.
|
||||
baseGHOSTDAGData, err := v.ghostdagDataStore.Get(v.databaseContext, stagingArea, povBlockHash)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
}
|
||||
// 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 {
|
||||
@ -372,28 +320,24 @@ func (v *transactionValidator) calcTxSequenceLockFromReferencedUTXOEntries(stagi
|
||||
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.
|
||||
// According to sequenceLock, after the referenced input has been confirmed
|
||||
// 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
|
||||
// 'BlockDAAScore' has been reached.
|
||||
// block either after the 'BlockDAAScore' has been reached.
|
||||
type sequenceLock struct {
|
||||
Milliseconds int64
|
||||
BlockDAAScore int64
|
||||
}
|
||||
|
||||
// sequenceLockActive determines if a transaction's sequence locks have been
|
||||
// met, meaning that all the inputs of a given transaction have reached a
|
||||
// DAA score or time sufficient for their relative lock-time maturity.
|
||||
func (v *transactionValidator) sequenceLockActive(sequenceLock *sequenceLock, blockDAAScore uint64,
|
||||
medianTimePast int64) bool {
|
||||
// DAA score sufficient for their relative lock-time maturity.
|
||||
func (v *transactionValidator) sequenceLockActive(sequenceLock *sequenceLock, blockDAAScore uint64) 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
|
||||
// sequence locks.
|
||||
if sequenceLock.Milliseconds >= medianTimePast ||
|
||||
sequenceLock.BlockDAAScore >= int64(blockDAAScore) {
|
||||
if sequenceLock.BlockDAAScore >= int64(blockDAAScore) {
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
package transactionvalidator
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@ -11,32 +10,22 @@ func TestSequenceLocksActive(t *testing.T) {
|
||||
tests := []struct {
|
||||
seqLock sequenceLock
|
||||
blockDAAScore uint64
|
||||
mtp mstime.Time
|
||||
|
||||
want bool
|
||||
}{
|
||||
// Block based sequence lock with equal block DAA score.
|
||||
{seqLock: sequenceLock{-1, 1000}, blockDAAScore: 1001, mtp: mstime.UnixMilliseconds(9), want: true},
|
||||
|
||||
// Time based sequence lock with mtp past the absolute time.
|
||||
{seqLock: sequenceLock{30, -1}, blockDAAScore: 2, mtp: mstime.UnixMilliseconds(31), want: true},
|
||||
{seqLock: sequenceLock{1000}, blockDAAScore: 1001, want: true},
|
||||
|
||||
// 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},
|
||||
|
||||
// Time based sequence lock with current time before lock time.
|
||||
{seqLock: sequenceLock{30, -1}, blockDAAScore: 2, mtp: mstime.UnixMilliseconds(29), want: false},
|
||||
{seqLock: sequenceLock{1000}, blockDAAScore: 90, want: false},
|
||||
|
||||
// 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},
|
||||
|
||||
// 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},
|
||||
{seqLock: sequenceLock{1000}, blockDAAScore: 1000, want: false},
|
||||
}
|
||||
|
||||
validator := transactionValidator{}
|
||||
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 {
|
||||
t.Fatalf("SequenceLockActive #%d got %v want %v", i, got, test.want)
|
||||
}
|
||||
|
@ -19,28 +19,10 @@ import (
|
||||
"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) {
|
||||
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
|
||||
|
||||
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,
|
||||
"TestValidateTransactionInContextAndPopulateFee")
|
||||
if err != nil {
|
||||
@ -48,7 +30,6 @@ func TestValidateTransactionInContextAndPopulateFee(t *testing.T) {
|
||||
}
|
||||
defer tearDown(false)
|
||||
|
||||
pastMedianManager.pastMedianTimeForTest = 1
|
||||
privateKey, err := secp256k1.GenerateSchnorrKeyPair()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate a private key: %v", err)
|
||||
@ -83,7 +64,17 @@ func TestValidateTransactionInContextAndPopulateFee(t *testing.T) {
|
||||
true,
|
||||
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,
|
||||
SignatureScript: []byte{},
|
||||
Sequence: constants.MaxTxInSequenceNum,
|
||||
@ -94,17 +85,6 @@ func TestValidateTransactionInContextAndPopulateFee(t *testing.T) {
|
||||
true,
|
||||
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{
|
||||
PreviousOutpoint: prevOutPoint,
|
||||
SignatureScript: []byte{},
|
||||
@ -128,19 +108,19 @@ func TestValidateTransactionInContextAndPopulateFee(t *testing.T) {
|
||||
uint64(5)),
|
||||
}
|
||||
|
||||
txOut := externalapi.DomainTransactionOutput{
|
||||
txOutput := externalapi.DomainTransactionOutput{
|
||||
Value: 100000000, // 1 KAS
|
||||
ScriptPublicKey: scriptPublicKey,
|
||||
}
|
||||
txOutBigValue := externalapi.DomainTransactionOutput{
|
||||
txOutputBigValue := externalapi.DomainTransactionOutput{
|
||||
Value: 200_000_000, // 2 KAS
|
||||
ScriptPublicKey: scriptPublicKey,
|
||||
}
|
||||
|
||||
validTx := externalapi.DomainTransaction{
|
||||
Version: constants.MaxTransactionVersion,
|
||||
Inputs: []*externalapi.DomainTransactionInput{&txInputWithSequenceLockTimeIsSeconds},
|
||||
Outputs: []*externalapi.DomainTransactionOutput{&txOut},
|
||||
Inputs: []*externalapi.DomainTransactionInput{&txInput},
|
||||
Outputs: []*externalapi.DomainTransactionOutput{&txOutput},
|
||||
SubnetworkID: subnetworks.SubnetworkIDRegistry,
|
||||
Gas: 0,
|
||||
LockTime: 0}
|
||||
@ -156,36 +136,36 @@ func TestValidateTransactionInContextAndPopulateFee(t *testing.T) {
|
||||
|
||||
txWithImmatureCoinbase := externalapi.DomainTransaction{
|
||||
Version: constants.MaxTransactionVersion,
|
||||
Inputs: []*externalapi.DomainTransactionInput{&immatureInput},
|
||||
Outputs: []*externalapi.DomainTransactionOutput{&txOut},
|
||||
Inputs: []*externalapi.DomainTransactionInput{&immatureCoinbaseInput},
|
||||
Outputs: []*externalapi.DomainTransactionOutput{&txOutput},
|
||||
SubnetworkID: subnetworks.SubnetworkIDRegistry,
|
||||
Gas: 0,
|
||||
LockTime: 0}
|
||||
txWithLargeAmount := externalapi.DomainTransaction{
|
||||
Version: constants.MaxTransactionVersion,
|
||||
Inputs: []*externalapi.DomainTransactionInput{&txInput, &txInputWithLargeAmount},
|
||||
Outputs: []*externalapi.DomainTransactionOutput{&txOut},
|
||||
Outputs: []*externalapi.DomainTransactionOutput{&txOutput},
|
||||
SubnetworkID: subnetworks.SubnetworkIDRegistry,
|
||||
Gas: 0,
|
||||
LockTime: 0}
|
||||
txWithBigValue := externalapi.DomainTransaction{
|
||||
Version: constants.MaxTransactionVersion,
|
||||
Inputs: []*externalapi.DomainTransactionInput{&txInput},
|
||||
Outputs: []*externalapi.DomainTransactionOutput{&txOutBigValue},
|
||||
Outputs: []*externalapi.DomainTransactionOutput{&txOutputBigValue},
|
||||
SubnetworkID: subnetworks.SubnetworkIDRegistry,
|
||||
Gas: 0,
|
||||
LockTime: 0}
|
||||
txWithInvalidSignature := externalapi.DomainTransaction{
|
||||
Version: constants.MaxTransactionVersion,
|
||||
Inputs: []*externalapi.DomainTransactionInput{&txInput},
|
||||
Outputs: []*externalapi.DomainTransactionOutput{&txOut},
|
||||
Inputs: []*externalapi.DomainTransactionInput{&txInputWrongSignature},
|
||||
Outputs: []*externalapi.DomainTransactionOutput{&txOutput},
|
||||
SubnetworkID: subnetworks.SubnetworkIDRegistry,
|
||||
Gas: 0,
|
||||
LockTime: 0}
|
||||
txWithBadSigOpCount := externalapi.DomainTransaction{
|
||||
Version: constants.MaxTransactionVersion,
|
||||
Inputs: []*externalapi.DomainTransactionInput{&txInputWithBadSigOpCount},
|
||||
Outputs: []*externalapi.DomainTransactionOutput{&txOut},
|
||||
Outputs: []*externalapi.DomainTransactionOutput{&txOutput},
|
||||
SubnetworkID: subnetworks.SubnetworkIDRegistry,
|
||||
Gas: 0,
|
||||
LockTime: 0}
|
||||
@ -205,75 +185,60 @@ func TestValidateTransactionInContextAndPopulateFee(t *testing.T) {
|
||||
nil))
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
tx *externalapi.DomainTransaction
|
||||
povBlockHash *externalapi.DomainHash
|
||||
selectedParentMedianTime int64
|
||||
isValid bool
|
||||
expectedError error
|
||||
name string
|
||||
tx *externalapi.DomainTransaction
|
||||
povBlockHash *externalapi.DomainHash
|
||||
isValid bool
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "Valid transaction",
|
||||
tx: &validTx,
|
||||
povBlockHash: povBlockHash,
|
||||
selectedParentMedianTime: 1,
|
||||
isValid: true,
|
||||
expectedError: nil,
|
||||
name: "Valid transaction",
|
||||
tx: &validTx,
|
||||
povBlockHash: povBlockHash,
|
||||
isValid: true,
|
||||
expectedError: nil,
|
||||
},
|
||||
{ // 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
|
||||
// them will yield a smaller result than the required CoinbaseMaturity (currently set to 100).
|
||||
name: "checkTransactionCoinbaseMaturity",
|
||||
tx: &txWithImmatureCoinbase,
|
||||
povBlockHash: povBlockHash,
|
||||
selectedParentMedianTime: 1,
|
||||
isValid: false,
|
||||
expectedError: ruleerrors.ErrImmatureSpend,
|
||||
name: "checkTransactionCoinbaseMaturity",
|
||||
tx: &txWithImmatureCoinbase,
|
||||
povBlockHash: povBlockHash,
|
||||
isValid: false,
|
||||
expectedError: ruleerrors.ErrImmatureSpend,
|
||||
},
|
||||
{ // The total inputs amount is bigger than the allowed maximum (constants.MaxSompi)
|
||||
name: "checkTransactionInputAmounts",
|
||||
tx: &txWithLargeAmount,
|
||||
povBlockHash: povBlockHash,
|
||||
selectedParentMedianTime: 1,
|
||||
isValid: false,
|
||||
expectedError: ruleerrors.ErrBadTxOutValue,
|
||||
name: "checkTransactionInputAmounts",
|
||||
tx: &txWithLargeAmount,
|
||||
povBlockHash: povBlockHash,
|
||||
isValid: false,
|
||||
expectedError: ruleerrors.ErrBadTxOutValue,
|
||||
},
|
||||
{ // The total SompiIn (sum of inputs amount) is smaller than the total SompiOut (sum of outputs value) and hence invalid.
|
||||
name: "checkTransactionOutputAmounts",
|
||||
tx: &txWithBigValue,
|
||||
povBlockHash: povBlockHash,
|
||||
selectedParentMedianTime: 1,
|
||||
isValid: false,
|
||||
expectedError: ruleerrors.ErrSpendTooHigh,
|
||||
name: "checkTransactionOutputAmounts",
|
||||
tx: &txWithBigValue,
|
||||
povBlockHash: povBlockHash,
|
||||
isValid: false,
|
||||
expectedError: ruleerrors.ErrSpendTooHigh,
|
||||
},
|
||||
{ // the selectedParentMedianTime is negative and hence invalid.
|
||||
name: "checkTransactionSequenceLock",
|
||||
tx: &validTx,
|
||||
povBlockHash: povBlockHash,
|
||||
selectedParentMedianTime: -1,
|
||||
isValid: false,
|
||||
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 SignatureScript is wrong
|
||||
name: "checkTransactionScripts",
|
||||
tx: &txWithInvalidSignature,
|
||||
povBlockHash: povBlockHash,
|
||||
isValid: false,
|
||||
expectedError: ruleerrors.ErrScriptValidation,
|
||||
},
|
||||
{ // the SigOpCount in the input is wrong, and hence invalid
|
||||
name: "checkTransactionSigOpCounts",
|
||||
tx: &txWithBadSigOpCount,
|
||||
povBlockHash: povBlockHash,
|
||||
selectedParentMedianTime: 1,
|
||||
isValid: false,
|
||||
expectedError: ruleerrors.ErrWrongSigOpCount,
|
||||
name: "checkTransactionSigOpCounts",
|
||||
tx: &txWithBadSigOpCount,
|
||||
povBlockHash: povBlockHash,
|
||||
isValid: false,
|
||||
expectedError: ruleerrors.ErrWrongSigOpCount,
|
||||
},
|
||||
}
|
||||
|
||||
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 err != nil {
|
||||
|
@ -28,22 +28,11 @@ const (
|
||||
// as a relative locktime.
|
||||
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
|
||||
// when masked against the transaction input sequence number.
|
||||
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
|
||||
// interpreted to be a block number.
|
||||
// interpreted to be a DAA score.
|
||||
LockTimeThreshold = 5e11 // Tue Nov 5 00:53:20 1985 UTC
|
||||
)
|
||||
|
@ -1068,17 +1068,16 @@ func opcodeReturn(op *parsedOpcode, vm *Engine) error {
|
||||
return scriptError(ErrEarlyReturn, "script returned early")
|
||||
}
|
||||
|
||||
// verifyLockTime is a helper function used to validate locktimes.
|
||||
func verifyLockTime(txLockTime, threshold, lockTime uint64) error {
|
||||
// The lockTimes in both the script and transaction must be of the same
|
||||
// type.
|
||||
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)
|
||||
func verifyLockTimeWithThreshold(txLockTime, threshold, lockTime uint64) error {
|
||||
err := verifyLockTimeThreshold(txLockTime, threshold, lockTime)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return verifyLockTime(txLockTime, lockTime)
|
||||
}
|
||||
|
||||
// checkLockTimeRequirement is a helper function used to validate locktimes.
|
||||
func verifyLockTime(txLockTime, lockTime uint64) error {
|
||||
if lockTime > txLockTime {
|
||||
str := fmt.Sprintf("locktime requirement not satisfied -- "+
|
||||
"locktime is greater than the transaction locktime: "+
|
||||
@ -1089,6 +1088,18 @@ func verifyLockTime(txLockTime, threshold, lockTime uint64) error {
|
||||
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
|
||||
// LockTime field of the transaction containing the script signature
|
||||
// validating if the transaction outputs are spendable yet.
|
||||
@ -1111,12 +1122,11 @@ func opcodeCheckLockTimeVerify(op *parsedOpcode, vm *Engine) error {
|
||||
lockTimeBytes = paddedLockTimeBytes
|
||||
}
|
||||
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
|
||||
// value is before the constants.LockTimeThreshold. When it is under the
|
||||
// threshold it is a block height.
|
||||
err = verifyLockTime(vm.tx.LockTime, constants.LockTimeThreshold,
|
||||
stackLockTime)
|
||||
// threshold it is a DAA score.
|
||||
err = verifyLockTimeWithThreshold(vm.tx.LockTime, constants.LockTimeThreshold, stackLockTime)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -1188,10 +1198,9 @@ func opcodeCheckSequenceVerify(op *parsedOpcode, vm *Engine) error {
|
||||
}
|
||||
|
||||
// Mask off non-consensus bits before doing comparisons.
|
||||
lockTimeMask := constants.SequenceLockTimeIsSeconds | constants.SequenceLockTimeMask
|
||||
maskedTxSequence := txSequence & lockTimeMask
|
||||
maskedStackSequence := stackSequence & lockTimeMask
|
||||
return verifyLockTime(maskedTxSequence, constants.SequenceLockTimeIsSeconds, maskedStackSequence)
|
||||
maskedTxSequence := txSequence & constants.SequenceLockTimeMask
|
||||
maskedStackSequence := stackSequence & constants.SequenceLockTimeMask
|
||||
return verifyLockTime(maskedTxSequence, maskedStackSequence)
|
||||
}
|
||||
|
||||
// opcodeToAltStack removes the top item from the main data stack and pushes it
|
||||
|
@ -21,8 +21,6 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const blockMaxMass uint64 = 10000000
|
||||
|
||||
// TestValidateAndInsertTransaction verifies that valid transactions were successfully inserted into the mempool.
|
||||
func TestValidateAndInsertTransaction(t *testing.T) {
|
||||
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 {
|
||||
t.Fatalf("PayToScriptHashSignatureScript: %v", err)
|
||||
}
|
||||
txInputWithMaxSequence := externalapi.DomainTransactionInput{
|
||||
txInput := externalapi.DomainTransactionInput{
|
||||
PreviousOutpoint: prevOutPoint,
|
||||
SignatureScript: signatureScript,
|
||||
Sequence: constants.SequenceLockTimeIsSeconds,
|
||||
Sequence: constants.MaxTxInSequenceNum,
|
||||
UTXOEntry: utxo.NewUTXOEntry(
|
||||
100000000, // 1 KAS
|
||||
scriptPublicKey,
|
||||
@ -569,7 +567,7 @@ func createTransactionWithUTXOEntry(t *testing.T, i int) *externalapi.DomainTran
|
||||
}
|
||||
tx := externalapi.DomainTransaction{
|
||||
Version: constants.MaxTransactionVersion,
|
||||
Inputs: []*externalapi.DomainTransactionInput{&txInputWithMaxSequence},
|
||||
Inputs: []*externalapi.DomainTransactionInput{&txInput},
|
||||
Outputs: []*externalapi.DomainTransactionOutput{&txOut},
|
||||
SubnetworkID: subnetworks.SubnetworkIDNative,
|
||||
Gas: 0,
|
||||
|
Loading…
x
Reference in New Issue
Block a user