kaspad/domain/consensus/processes/transactionvalidator/transactionvalidator_test.go
2022-11-16 23:48:05 +02:00

521 lines
17 KiB
Go

package transactionvalidator_test
import (
"github.com/kaspanet/go-secp256k1"
"github.com/kaspanet/kaspad/domain/consensus"
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
"github.com/kaspanet/kaspad/domain/consensus/utils/testutils"
"github.com/kaspanet/kaspad/domain/consensus/utils/txscript"
"github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
"github.com/kaspanet/kaspad/util"
"testing"
"github.com/kaspanet/kaspad/domain/consensus/model"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
"github.com/kaspanet/kaspad/domain/consensus/utils/subnetworks"
"github.com/pkg/errors"
)
func TestValidateTransactionInContextAndPopulateFee(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
factory := consensus.NewFactory()
tc, tearDown, err := factory.NewTestConsensus(consensusConfig,
"TestValidateTransactionInContextAndPopulateFee")
if err != nil {
t.Fatalf("Failed create a NewTestConsensus: %s", err)
}
defer tearDown(false)
privateKey, err := secp256k1.GenerateSchnorrKeyPair()
if err != nil {
t.Fatalf("Failed to generate a private key: %v", err)
}
publicKey, err := privateKey.SchnorrPublicKey()
if err != nil {
t.Fatalf("Failed to generate a public key: %v", err)
}
publicKeySerialized, err := publicKey.Serialize()
if err != nil {
t.Fatalf("Failed to serialize public key: %v", err)
}
addr, err := util.NewAddressPublicKey(publicKeySerialized[:], consensusConfig.Prefix)
if err != nil {
t.Fatalf("Failed to generate p2pk address: %v", err)
}
scriptPublicKey, err := txscript.PayToAddrScript(addr)
if err != nil {
t.Fatalf("PayToAddrScript: unexpected error: %v", err)
}
prevOutTxID := &externalapi.DomainTransactionID{}
prevOutPoint := externalapi.DomainOutpoint{TransactionID: *prevOutTxID, Index: 1}
txInput := externalapi.DomainTransactionInput{
PreviousOutpoint: prevOutPoint,
SignatureScript: []byte{},
Sequence: constants.MaxTxInSequenceNum,
SigOpCount: 1,
UTXOEntry: utxo.NewUTXOEntry(
100_000_000, // 1 KAS
scriptPublicKey,
true,
uint64(5)),
}
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,
SigOpCount: 1,
UTXOEntry: utxo.NewUTXOEntry(
100_000_000, // 1 KAS
scriptPublicKey,
true,
uint64(6)),
}
txInputWithLargeAmount := externalapi.DomainTransactionInput{
PreviousOutpoint: prevOutPoint,
SignatureScript: []byte{},
Sequence: constants.MaxTxInSequenceNum,
SigOpCount: 1,
UTXOEntry: utxo.NewUTXOEntry(
21e14+1,
scriptPublicKey,
false,
0),
}
txInputWithLargeAmountAfterHF := externalapi.DomainTransactionInput{
PreviousOutpoint: prevOutPoint,
SignatureScript: []byte{},
Sequence: constants.MaxTxInSequenceNum,
SigOpCount: 1,
UTXOEntry: utxo.NewUTXOEntry(
constants.MaxSompi+1,
scriptPublicKey,
false,
0),
}
txOutput := externalapi.DomainTransactionOutput{
Value: 100000000, // 1 KAS
ScriptPublicKey: scriptPublicKey,
}
txOutputBigValue := externalapi.DomainTransactionOutput{
Value: 200_000_000, // 2 KAS
ScriptPublicKey: scriptPublicKey,
}
validTx := externalapi.DomainTransaction{
Version: constants.MaxTransactionVersion,
Inputs: []*externalapi.DomainTransactionInput{&txInput},
Outputs: []*externalapi.DomainTransactionOutput{&txOutput},
SubnetworkID: subnetworks.SubnetworkIDRegistry,
Gas: 0,
LockTime: 0}
for i, input := range validTx.Inputs {
signatureScript, err := txscript.SignatureScript(&validTx, i, consensushashing.SigHashAll, privateKey,
&consensushashing.SighashReusedValues{})
if err != nil {
t.Fatalf("Failed to create a sigScript: %v", err)
}
input.SignatureScript = signatureScript
}
txWithImmatureCoinbase := externalapi.DomainTransaction{
Version: constants.MaxTransactionVersion,
Inputs: []*externalapi.DomainTransactionInput{&immatureCoinbaseInput},
Outputs: []*externalapi.DomainTransactionOutput{&txOutput},
SubnetworkID: subnetworks.SubnetworkIDRegistry,
Gas: 0,
LockTime: 0}
txWithLargeAmountBeforeHF := externalapi.DomainTransaction{
Version: constants.MaxTransactionVersion,
Inputs: []*externalapi.DomainTransactionInput{&txInputWithLargeAmount},
Outputs: []*externalapi.DomainTransactionOutput{&txOutput},
SubnetworkID: subnetworks.SubnetworkIDRegistry,
Gas: 0,
LockTime: 0}
txWithInvalidAmount := externalapi.DomainTransaction{
Version: constants.MaxTransactionVersion,
Inputs: []*externalapi.DomainTransactionInput{&txInputWithLargeAmountAfterHF},
Outputs: []*externalapi.DomainTransactionOutput{&txOutput},
SubnetworkID: subnetworks.SubnetworkIDRegistry,
Gas: 0,
LockTime: 0}
for i, input := range txWithLargeAmountBeforeHF.Inputs {
signatureScript, err := txscript.SignatureScript(&txWithLargeAmountBeforeHF, i, consensushashing.SigHashAll, privateKey,
&consensushashing.SighashReusedValues{})
if err != nil {
t.Fatalf("Failed to create a sigScript: %v", err)
}
input.SignatureScript = signatureScript
}
txWithBigValue := externalapi.DomainTransaction{
Version: constants.MaxTransactionVersion,
Inputs: []*externalapi.DomainTransactionInput{&txInput},
Outputs: []*externalapi.DomainTransactionOutput{&txOutputBigValue},
SubnetworkID: subnetworks.SubnetworkIDRegistry,
Gas: 0,
LockTime: 0}
txWithInvalidSignature := externalapi.DomainTransaction{
Version: constants.MaxTransactionVersion,
Inputs: []*externalapi.DomainTransactionInput{&txInputWrongSignature},
Outputs: []*externalapi.DomainTransactionOutput{&txOutput},
SubnetworkID: subnetworks.SubnetworkIDRegistry,
Gas: 0,
LockTime: 0}
stagingArea := model.NewStagingArea()
povBlockHash := externalapi.NewDomainHashFromByteArray(&[32]byte{0x01})
tc.DAABlocksStore().StageDAAScore(stagingArea, povBlockHash, consensusConfig.BlockCoinbaseMaturity+txInput.UTXOEntry.BlockDAAScore())
// Just use some stub ghostdag data
tc.GHOSTDAGDataStore().Stage(stagingArea, povBlockHash, externalapi.NewBlockGHOSTDAGData(
0,
nil,
consensusConfig.GenesisHash,
nil,
nil,
nil), false)
tests := []struct {
name string
tx *externalapi.DomainTransaction
povBlockHash *externalapi.DomainHash
isValid bool
expectedError error
}{
{
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,
isValid: false,
expectedError: ruleerrors.ErrImmatureSpend,
},
{ // The total inputs amount is bigger than the allowed maximum before the HF (21e14)
name: "checkTransactionInputAmounts - valid",
tx: &txWithLargeAmountBeforeHF,
povBlockHash: povBlockHash,
isValid: true,
expectedError: nil,
},
{ // The total inputs amount is bigger than the allowed maximum (constants.MaxSompi)
name: "checkTransactionInputAmounts - invalid - after HF",
tx: &txWithInvalidAmount,
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,
isValid: false,
expectedError: ruleerrors.ErrSpendTooHigh,
},
{ // The SignatureScript is wrong
name: "checkTransactionScripts",
tx: &txWithInvalidSignature,
povBlockHash: povBlockHash,
isValid: false,
expectedError: ruleerrors.ErrScriptValidation,
},
}
for _, test := range tests {
err := tc.TransactionValidator().ValidateTransactionInContextAndPopulateFee(stagingArea, test.tx, test.povBlockHash)
if test.isValid {
if err != nil {
t.Fatalf("Unexpected error on TestValidateTransactionInContextAndPopulateFee"+
" on test '%v': %+v", test.name, err)
}
} else {
if err == nil || !errors.Is(err, test.expectedError) {
t.Fatalf("TestValidateTransactionInContextAndPopulateFee: test %v:"+
" Unexpected error: Expected to: %v, but got : %+v", test.name, test.expectedError, err)
}
}
}
})
}
func TestSigningTwoInputs(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
consensusConfig.BlockCoinbaseMaturity = 0
factory := consensus.NewFactory()
tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestSigningTwoInputs")
if err != nil {
t.Fatalf("Error setting up consensus: %+v", err)
}
defer teardown(false)
privateKey, err := secp256k1.GenerateSchnorrKeyPair()
if err != nil {
t.Fatalf("Failed to generate a private key: %v", err)
}
publicKey, err := privateKey.SchnorrPublicKey()
if err != nil {
t.Fatalf("Failed to generate a public key: %v", err)
}
publicKeySerialized, err := publicKey.Serialize()
if err != nil {
t.Fatalf("Failed to serialize public key: %v", err)
}
addr, err := util.NewAddressPublicKey(publicKeySerialized[:], consensusConfig.Prefix)
if err != nil {
t.Fatalf("Failed to generate p2pk address: %v", err)
}
scriptPublicKey, err := txscript.PayToAddrScript(addr)
if err != nil {
t.Fatalf("PayToAddrScript: unexpected error: %v", err)
}
coinbaseData := &externalapi.DomainCoinbaseData{
ScriptPublicKey: scriptPublicKey,
}
block1Hash, _, err := tc.AddBlock([]*externalapi.DomainHash{consensusConfig.GenesisHash}, coinbaseData, nil)
if err != nil {
t.Fatalf("AddBlock: %+v", err)
}
block2Hash, _, err := tc.AddBlock([]*externalapi.DomainHash{block1Hash}, coinbaseData, nil)
if err != nil {
t.Fatalf("AddBlock: %+v", err)
}
block3Hash, _, err := tc.AddBlock([]*externalapi.DomainHash{block2Hash}, coinbaseData, nil)
if err != nil {
t.Fatalf("AddBlock: %+v", err)
}
block2, _, err := tc.GetBlock(block2Hash)
if err != nil {
t.Fatalf("Error getting block2: %+v", err)
}
block3, _, err := tc.GetBlock(block3Hash)
if err != nil {
t.Fatalf("Error getting block3: %+v", err)
}
block2Tx := block2.Transactions[0]
block2TxOut := block2Tx.Outputs[0]
block3Tx := block3.Transactions[0]
block3TxOut := block3Tx.Outputs[0]
tx := &externalapi.DomainTransaction{
Version: constants.MaxTransactionVersion,
Inputs: []*externalapi.DomainTransactionInput{
{
PreviousOutpoint: externalapi.DomainOutpoint{
TransactionID: *consensushashing.TransactionID(block2.Transactions[0]),
Index: 0,
},
Sequence: constants.MaxTxInSequenceNum,
SigOpCount: 1,
UTXOEntry: utxo.NewUTXOEntry(block2TxOut.Value, block2TxOut.ScriptPublicKey, true, 0),
},
{
PreviousOutpoint: externalapi.DomainOutpoint{
TransactionID: *consensushashing.TransactionID(block3.Transactions[0]),
Index: 0,
},
Sequence: constants.MaxTxInSequenceNum,
SigOpCount: 1,
UTXOEntry: utxo.NewUTXOEntry(block3TxOut.Value, block3TxOut.ScriptPublicKey, true, 0),
},
},
Outputs: []*externalapi.DomainTransactionOutput{{
Value: 1,
ScriptPublicKey: &externalapi.ScriptPublicKey{
Script: nil,
Version: 0,
},
}},
SubnetworkID: subnetworks.SubnetworkIDNative,
Gas: 0,
LockTime: 0,
}
sighashReusedValues := &consensushashing.SighashReusedValues{}
for i, input := range tx.Inputs {
signatureScript, err := txscript.SignatureScript(tx, i, consensushashing.SigHashAll, privateKey,
sighashReusedValues)
if err != nil {
t.Fatalf("Failed to create a sigScript: %v", err)
}
input.SignatureScript = signatureScript
}
_, virtualChangeSet, err := tc.AddBlock([]*externalapi.DomainHash{block3Hash}, nil, []*externalapi.DomainTransaction{tx})
if err != nil {
t.Fatalf("AddBlock: %+v", err)
}
txOutpoint := &externalapi.DomainOutpoint{
TransactionID: *consensushashing.TransactionID(tx),
Index: 0,
}
if !virtualChangeSet.VirtualUTXODiff.ToAdd().Contains(txOutpoint) {
t.Fatalf("tx was not accepted by the DAG")
}
})
}
func TestSigningTwoInputsECDSA(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
consensusConfig.BlockCoinbaseMaturity = 0
factory := consensus.NewFactory()
tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestSigningTwoInputsECDSA")
if err != nil {
t.Fatalf("Error setting up consensus: %+v", err)
}
defer teardown(false)
privateKey, err := secp256k1.GenerateECDSAPrivateKey()
if err != nil {
t.Fatalf("Failed to generate a private key: %v", err)
}
publicKey, err := privateKey.ECDSAPublicKey()
if err != nil {
t.Fatalf("Failed to generate a public key: %v", err)
}
publicKeySerialized, err := publicKey.Serialize()
if err != nil {
t.Fatalf("Failed to serialize public key: %v", err)
}
addr, err := util.NewAddressPublicKeyECDSA(publicKeySerialized[:], consensusConfig.Prefix)
if err != nil {
t.Fatalf("Failed to generate p2pk address: %v", err)
}
scriptPublicKey, err := txscript.PayToAddrScript(addr)
if err != nil {
t.Fatalf("PayToAddrScript: unexpected error: %v", err)
}
coinbaseData := &externalapi.DomainCoinbaseData{
ScriptPublicKey: scriptPublicKey,
}
block1Hash, _, err := tc.AddBlock([]*externalapi.DomainHash{consensusConfig.GenesisHash}, coinbaseData, nil)
if err != nil {
t.Fatalf("AddBlock: %+v", err)
}
block2Hash, _, err := tc.AddBlock([]*externalapi.DomainHash{block1Hash}, coinbaseData, nil)
if err != nil {
t.Fatalf("AddBlock: %+v", err)
}
block3Hash, _, err := tc.AddBlock([]*externalapi.DomainHash{block2Hash}, coinbaseData, nil)
if err != nil {
t.Fatalf("AddBlock: %+v", err)
}
block2, _, err := tc.GetBlock(block2Hash)
if err != nil {
t.Fatalf("Error getting block2: %+v", err)
}
block3, _, err := tc.GetBlock(block3Hash)
if err != nil {
t.Fatalf("Error getting block3: %+v", err)
}
block2Tx := block2.Transactions[0]
block2TxOut := block2Tx.Outputs[0]
block3Tx := block3.Transactions[0]
block3TxOut := block3Tx.Outputs[0]
tx := &externalapi.DomainTransaction{
Version: constants.MaxTransactionVersion,
Inputs: []*externalapi.DomainTransactionInput{
{
PreviousOutpoint: externalapi.DomainOutpoint{
TransactionID: *consensushashing.TransactionID(block2.Transactions[0]),
Index: 0,
},
Sequence: constants.MaxTxInSequenceNum,
SigOpCount: 1,
UTXOEntry: utxo.NewUTXOEntry(block2TxOut.Value, block2TxOut.ScriptPublicKey, true, 0),
},
{
PreviousOutpoint: externalapi.DomainOutpoint{
TransactionID: *consensushashing.TransactionID(block3.Transactions[0]),
Index: 0,
},
Sequence: constants.MaxTxInSequenceNum,
SigOpCount: 1,
UTXOEntry: utxo.NewUTXOEntry(block3TxOut.Value, block3TxOut.ScriptPublicKey, true, 0),
},
},
Outputs: []*externalapi.DomainTransactionOutput{{
Value: 1,
ScriptPublicKey: &externalapi.ScriptPublicKey{
Script: nil,
Version: 0,
},
}},
SubnetworkID: subnetworks.SubnetworkIDNative,
Gas: 0,
LockTime: 0,
}
sighashReusedValues := &consensushashing.SighashReusedValues{}
for i, input := range tx.Inputs {
signatureScript, err := txscript.SignatureScriptECDSA(tx, i, consensushashing.SigHashAll, privateKey,
sighashReusedValues)
if err != nil {
t.Fatalf("Failed to create a sigScript: %v", err)
}
input.SignatureScript = signatureScript
}
_, virtualChangeSet, err := tc.AddBlock([]*externalapi.DomainHash{block3Hash}, nil, []*externalapi.DomainTransaction{tx})
if err != nil {
t.Fatalf("AddBlock: %+v", err)
}
txOutpoint := &externalapi.DomainOutpoint{
TransactionID: *consensushashing.TransactionID(tx),
Index: 0,
}
if !virtualChangeSet.VirtualUTXODiff.ToAdd().Contains(txOutpoint) {
t.Fatalf("tx was not accepted by the DAG")
}
})
}