From bb2d7f72acb5b66bfc8cf773ac47f342f19755e5 Mon Sep 17 00:00:00 2001 From: Ori Newman Date: Mon, 23 Nov 2020 06:28:59 -0800 Subject: [PATCH] [NOD-1560] Add TestValidateTransactionInIsolation (#1140) * [NOD-1560] Add TestValidateTransactionInIsolation * [NOD-1560] Make ForAllNets copy the params before mutating them * [NOD-1560] Remove redundant continue * [NOD-1560] Don't change finality duration --- .../transaction_in_isolation_test.go | 162 ++++++++++++++++++ .../consensus/utils/testutils/for_all_nets.go | 5 +- .../consensus/utils/transactionhelper/new.go | 17 ++ 3 files changed, 182 insertions(+), 2 deletions(-) create mode 100644 domain/consensus/processes/transactionvalidator/transaction_in_isolation_test.go diff --git a/domain/consensus/processes/transactionvalidator/transaction_in_isolation_test.go b/domain/consensus/processes/transactionvalidator/transaction_in_isolation_test.go new file mode 100644 index 000000000..3c2051c96 --- /dev/null +++ b/domain/consensus/processes/transactionvalidator/transaction_in_isolation_test.go @@ -0,0 +1,162 @@ +package transactionvalidator_test + +import ( + "github.com/kaspanet/kaspad/domain/consensus" + "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" + "github.com/kaspanet/kaspad/domain/consensus/ruleerrors" + "github.com/kaspanet/kaspad/domain/consensus/utils/constants" + "github.com/kaspanet/kaspad/domain/consensus/utils/hashes" + "github.com/kaspanet/kaspad/domain/consensus/utils/subnetworks" + "github.com/kaspanet/kaspad/domain/consensus/utils/testutils" + "github.com/kaspanet/kaspad/domain/consensus/utils/transactionhelper" + "github.com/kaspanet/kaspad/domain/dagconfig" + "github.com/kaspanet/kaspad/util" + "github.com/pkg/errors" + "testing" +) + +type txSubnetworkData struct { + subnetworkID externalapi.DomainSubnetworkID + gas uint64 + payload []byte +} + +func TestValidateTransactionInIsolation(t *testing.T) { + testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) { + factory := consensus.NewFactory() + tc, teardown, err := factory.NewTestConsensus(params, "TestValidateTransactionInIsolation") + if err != nil { + t.Fatalf("Error setting up consensus: %+v", err) + } + defer teardown() + + tests := []struct { + name string + numInputs uint32 + numOutputs uint32 + outputValue uint64 + nodeSubnetworkID externalapi.DomainSubnetworkID + txSubnetworkData *txSubnetworkData + extraModificationsFunc func(*externalapi.DomainTransaction) + expectedErr error + }{ + {"good one", 1, 1, 1, subnetworks.SubnetworkIDNative, nil, nil, nil}, + {"no inputs", 0, 1, 1, subnetworks.SubnetworkIDNative, nil, nil, ruleerrors.ErrNoTxInputs}, + {"no outputs", 1, 0, 1, subnetworks.SubnetworkIDNative, nil, nil, nil}, + {"too much sompi in one output", 1, 1, util.MaxSompi + 1, + subnetworks.SubnetworkIDNative, + nil, + nil, + ruleerrors.ErrBadTxOutValue}, + {"too much sompi in total outputs", 1, 2, util.MaxSompi - 1, + subnetworks.SubnetworkIDNative, + nil, + nil, + ruleerrors.ErrBadTxOutValue}, + {"duplicate inputs", 2, 1, 1, + subnetworks.SubnetworkIDNative, + nil, + func(tx *externalapi.DomainTransaction) { tx.Inputs[1].PreviousOutpoint.Index = 0 }, + ruleerrors.ErrDuplicateTxInputs}, + {"1 input coinbase", + 1, + 1, + 1, + subnetworks.SubnetworkIDNative, + &txSubnetworkData{subnetworks.SubnetworkIDCoinbase, 0, nil}, + nil, + nil}, + {"no inputs coinbase", + 0, + 1, + 1, + subnetworks.SubnetworkIDNative, + &txSubnetworkData{subnetworks.SubnetworkIDCoinbase, 0, nil}, + nil, + nil}, + {"too long payload coinbase", + 1, + 1, + 1, + subnetworks.SubnetworkIDNative, + &txSubnetworkData{subnetworks.SubnetworkIDCoinbase, 0, make([]byte, constants.MaxCoinbasePayloadLength+1)}, + nil, + ruleerrors.ErrBadCoinbasePayloadLen}, + {"non-zero gas in Kaspa", 1, 1, 0, + subnetworks.SubnetworkIDNative, + nil, + func(tx *externalapi.DomainTransaction) { + tx.Gas = 1 + }, + ruleerrors.ErrInvalidGas}, + {"non-zero gas in subnetwork registry", 1, 1, 0, + subnetworks.SubnetworkIDRegistry, + &txSubnetworkData{subnetworks.SubnetworkIDRegistry, 1, []byte{}}, + nil, + ruleerrors.ErrInvalidGas}, + {"non-zero payload in Kaspa", 1, 1, 0, + subnetworks.SubnetworkIDNative, + nil, + func(tx *externalapi.DomainTransaction) { + tx.Payload = []byte{1} + }, + ruleerrors.ErrInvalidPayload}, + {"invalid payload hash", 1, 1, 0, + externalapi.DomainSubnetworkID{123}, + &txSubnetworkData{externalapi.DomainSubnetworkID{123}, 0, []byte{1}}, + func(tx *externalapi.DomainTransaction) { + tx.PayloadHash = externalapi.DomainHash{} + }, + ruleerrors.ErrInvalidPayloadHash}, + {"invalid payload hash in native subnetwork", 1, 1, 0, + subnetworks.SubnetworkIDNative, + nil, + func(tx *externalapi.DomainTransaction) { + tx.PayloadHash = *hashes.HashData(tx.Payload) + }, + ruleerrors.ErrInvalidPayloadHash}, + } + + for _, test := range tests { + tx := createTxForTest(test.numInputs, test.numOutputs, test.outputValue, test.txSubnetworkData) + + if test.extraModificationsFunc != nil { + test.extraModificationsFunc(tx) + } + + err := tc.TransactionValidator().ValidateTransactionInIsolation(tx) + if !errors.Is(err, test.expectedErr) { + t.Errorf("TestValidateTransactionInIsolation: '%s': unexpected error %+v", test.name, err) + } + } + }) +} + +func createTxForTest(numInputs uint32, numOutputs uint32, outputValue uint64, subnetworkData *txSubnetworkData) *externalapi.DomainTransaction { + txIns := []*externalapi.DomainTransactionInput{} + txOuts := []*externalapi.DomainTransactionOutput{} + + for i := uint32(0); i < numInputs; i++ { + txIns = append(txIns, &externalapi.DomainTransactionInput{ + PreviousOutpoint: externalapi.DomainOutpoint{ + TransactionID: externalapi.DomainTransactionID{}, + Index: i, + }, + SignatureScript: []byte{}, + Sequence: constants.MaxTxInSequenceNum, + }) + } + + for i := uint32(0); i < numOutputs; i++ { + txOuts = append(txOuts, &externalapi.DomainTransactionOutput{ + ScriptPublicKey: []byte{}, + Value: outputValue, + }) + } + + if subnetworkData != nil { + return transactionhelper.NewSubnetworkTransaction(constants.TransactionVersion, txIns, txOuts, &subnetworkData.subnetworkID, subnetworkData.gas, subnetworkData.payload) + } + + return transactionhelper.NewNativeTransaction(constants.TransactionVersion, txIns, txOuts) +} diff --git a/domain/consensus/utils/testutils/for_all_nets.go b/domain/consensus/utils/testutils/for_all_nets.go index ad73ad9f2..689cf4053 100644 --- a/domain/consensus/utils/testutils/for_all_nets.go +++ b/domain/consensus/utils/testutils/for_all_nets.go @@ -17,8 +17,9 @@ func ForAllNets(t *testing.T, skipPow bool, testFunc func(*testing.T, *dagconfig } for _, params := range allParams { - params.SkipProofOfWork = skipPow + paramsCopy := params + paramsCopy.SkipProofOfWork = skipPow t.Logf("Running test for %s", params.Name) - testFunc(t, ¶ms) + testFunc(t, ¶msCopy) } } diff --git a/domain/consensus/utils/transactionhelper/new.go b/domain/consensus/utils/transactionhelper/new.go index 2c42bd035..927d3c032 100644 --- a/domain/consensus/utils/transactionhelper/new.go +++ b/domain/consensus/utils/transactionhelper/new.go @@ -3,6 +3,7 @@ package transactionhelper import ( "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" "github.com/kaspanet/kaspad/domain/consensus/utils/hashes" + "github.com/kaspanet/kaspad/domain/consensus/utils/subnetworks" ) // NewSubnetworkTransaction returns a new trsnactions in the specified subnetwork with specified gas and payload @@ -24,3 +25,19 @@ func NewSubnetworkTransaction(version int32, inputs []*externalapi.DomainTransac Mass: 0, } } + +// NewNativeTransaction returns a new native transaction +func NewNativeTransaction(version int32, inputs []*externalapi.DomainTransactionInput, + outputs []*externalapi.DomainTransactionOutput) *externalapi.DomainTransaction { + return &externalapi.DomainTransaction{ + Version: version, + Inputs: inputs, + Outputs: outputs, + LockTime: 0, + SubnetworkID: subnetworks.SubnetworkIDNative, + Gas: 0, + Payload: []byte{}, + Fee: 0, + Mass: 0, + } +}