mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-06-06 06:06:49 +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
|
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) {
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
)
|
)
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user