mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-03-30 15:08:33 +00:00
Adds tests for transaction validator and block validators (#1531)
* [NOD-1453] cover failing block validation * [NOD-1453] Complete covering test for invalid block * [NOD-1453] Fix validator tests after rebase * [NOD-1453] Cover tests for valid blocks * [NOD-1453] Implement unit tests for ValidateTransactionInIsolation * [NOD-1453] Add tests for ValidateTransactionInContextAndPopulateMassAndFee * [NOD-1453] Cover ValidateHeaderInContext test * [NOD-1453] Fix after rebase * not finish * commited for update the branch. * Adds new tests to block_body_in_isolation_test.go according to (and instead of ) blockvalisator_test.go * Adds a comment to type MEDIAN. * Fixes according to the review notes: add notes and change variables name. * Fix comment. * Remove an unused test( all the tests in this file were passed to other test files). * Change a variable name(txWithAnEmptyInvalidScript to txWithInvalidSignature). * adds missing '}'. * Change spaces to tab Co-authored-by: karim1king <karimkaspersky@yahoo.com> Co-authored-by: Karim A <karim.a@it-dimension.com> Co-authored-by: tal <tal@daglabs.com>
This commit is contained in:
parent
35e555e959
commit
a7bb1853f9
@ -15,3 +15,7 @@ type GHOSTDAGManagerConstructor func(model.DBReader, model.DAGTopologyManager,
|
||||
type DifficultyManagerConstructor func(model.DBReader, model.GHOSTDAGManager, model.GHOSTDAGDataStore,
|
||||
model.BlockHeaderStore, model.DAGTopologyManager, model.DAGTraversalManager, *big.Int, int, bool, time.Duration,
|
||||
*externalapi.DomainHash) model.DifficultyManager
|
||||
|
||||
// PastMedianTimeManagerConstructor is the function signature for a constructor of a type implementing model.PastMedianTimeManager
|
||||
type PastMedianTimeManagerConstructor func(int, model.DBReader, model.DAGTraversalManager, model.BlockHeaderStore,
|
||||
model.GHOSTDAGDataStore) model.PastMedianTimeManager
|
||||
|
@ -63,22 +63,25 @@ type Factory interface {
|
||||
SetTestGHOSTDAGManager(ghostdagConstructor GHOSTDAGManagerConstructor)
|
||||
SetTestLevelDBCacheSize(cacheSizeMiB int)
|
||||
SetTestPreAllocateCache(preallocateCaches bool)
|
||||
SetTestPastMedianTimeManager(medianTimeConstructor PastMedianTimeManagerConstructor)
|
||||
SetTestDifficultyManager(difficultyConstructor DifficultyManagerConstructor)
|
||||
}
|
||||
|
||||
type factory struct {
|
||||
dataDir string
|
||||
ghostdagConstructor GHOSTDAGManagerConstructor
|
||||
difficultyConstructor DifficultyManagerConstructor
|
||||
cacheSizeMiB *int
|
||||
preallocateCaches *bool
|
||||
dataDir string
|
||||
ghostdagConstructor GHOSTDAGManagerConstructor
|
||||
pastMedianTimeConsructor PastMedianTimeManagerConstructor
|
||||
difficultyConstructor DifficultyManagerConstructor
|
||||
cacheSizeMiB *int
|
||||
preallocateCaches *bool
|
||||
}
|
||||
|
||||
// NewFactory creates a new Consensus factory
|
||||
func NewFactory() Factory {
|
||||
return &factory{
|
||||
ghostdagConstructor: ghostdagmanager.New,
|
||||
difficultyConstructor: difficultymanager.New,
|
||||
ghostdagConstructor: ghostdagmanager.New,
|
||||
pastMedianTimeConsructor: pastmediantimemanager.New,
|
||||
difficultyConstructor: difficultymanager.New,
|
||||
}
|
||||
}
|
||||
|
||||
@ -147,7 +150,7 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat
|
||||
reachabilityDataStore,
|
||||
ghostdagManager,
|
||||
consensusStateStore)
|
||||
pastMedianTimeManager := pastmediantimemanager.New(
|
||||
pastMedianTimeManager := f.pastMedianTimeConsructor(
|
||||
dagParams.TimestampDeviationTolerance,
|
||||
dbManager,
|
||||
dagTraversalManager,
|
||||
@ -469,6 +472,10 @@ func (f *factory) SetTestGHOSTDAGManager(ghostdagConstructor GHOSTDAGManagerCons
|
||||
f.ghostdagConstructor = ghostdagConstructor
|
||||
}
|
||||
|
||||
func (f *factory) SetTestPastMedianTimeManager(medianTimeConstructor PastMedianTimeManagerConstructor) {
|
||||
f.pastMedianTimeConsructor = medianTimeConstructor
|
||||
}
|
||||
|
||||
// SetTestDifficultyManager is a setter for the difficultyManager field on the factory.
|
||||
func (f *factory) SetTestDifficultyManager(difficultyConstructor DifficultyManagerConstructor) {
|
||||
f.difficultyConstructor = difficultyConstructor
|
||||
|
@ -1,6 +1,12 @@
|
||||
package blockvalidator_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/testapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/merkle"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
@ -84,7 +90,7 @@ func TestChainedTransactions(t *testing.T) {
|
||||
func TestCheckBlockSanity(t *testing.T) {
|
||||
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
|
||||
factory := consensus.NewFactory()
|
||||
consensus, teardown, err := factory.NewTestConsensus(params, false, "TestCheckBlockSanity")
|
||||
tc, teardown, err := factory.NewTestConsensus(params, false, "TestCheckBlockSanity")
|
||||
if err != nil {
|
||||
t.Fatalf("Error setting up consensus: %+v", err)
|
||||
}
|
||||
@ -94,17 +100,17 @@ func TestCheckBlockSanity(t *testing.T) {
|
||||
t.Fatalf("Too few transactions in block, expect at least 3, got %v", len(exampleValidBlock.Transactions))
|
||||
}
|
||||
|
||||
consensus.BlockStore().Stage(blockHash, &exampleValidBlock)
|
||||
tc.BlockStore().Stage(blockHash, &exampleValidBlock)
|
||||
|
||||
err = consensus.BlockValidator().ValidateBodyInIsolation(blockHash)
|
||||
err = tc.BlockValidator().ValidateBodyInIsolation(blockHash)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed validating block in isolation: %v", err)
|
||||
}
|
||||
|
||||
// Test with block with wrong transactions sorting order
|
||||
blockHash = consensushashing.BlockHash(&blockWithWrongTxOrder)
|
||||
consensus.BlockStore().Stage(blockHash, &blockWithWrongTxOrder)
|
||||
err = consensus.BlockValidator().ValidateBodyInIsolation(blockHash)
|
||||
tc.BlockStore().Stage(blockHash, &blockWithWrongTxOrder)
|
||||
err = tc.BlockValidator().ValidateBodyInIsolation(blockHash)
|
||||
if !errors.Is(err, ruleerrors.ErrTransactionsNotSorted) {
|
||||
t.Errorf("CheckBlockSanity: Expected ErrTransactionsNotSorted error, instead got %v", err)
|
||||
}
|
||||
@ -112,8 +118,8 @@ func TestCheckBlockSanity(t *testing.T) {
|
||||
// Test a block with invalid parents order
|
||||
// We no longer require blocks to have ordered parents
|
||||
blockHash = consensushashing.BlockHash(&unOrderedParentsBlock)
|
||||
consensus.BlockStore().Stage(blockHash, &unOrderedParentsBlock)
|
||||
err = consensus.BlockValidator().ValidateBodyInIsolation(blockHash)
|
||||
tc.BlockStore().Stage(blockHash, &unOrderedParentsBlock)
|
||||
err = tc.BlockValidator().ValidateBodyInIsolation(blockHash)
|
||||
if err != nil {
|
||||
t.Errorf("CheckBlockSanity: Expected block to be be body in isolation valid, got error instead: %v", err)
|
||||
}
|
||||
@ -1034,3 +1040,309 @@ func TestCheckBlockHashMerkleRoot(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestBlockSize(t *testing.T) {
|
||||
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
|
||||
|
||||
factory := consensus.NewFactory()
|
||||
tc, teardown, err := factory.NewTestConsensus(params, false, "TestBlockSize")
|
||||
if err != nil {
|
||||
t.Fatalf("Error setting up tc: %+v", err)
|
||||
}
|
||||
defer teardown(false)
|
||||
|
||||
block, _, err := initBlockWithInvalidBlockSize(params, tc)
|
||||
if err != nil {
|
||||
t.Fatalf("Error BuildBlockWithParents : %+v", err)
|
||||
}
|
||||
blockHash := consensushashing.BlockHash(block)
|
||||
tc.BlockStore().Stage(blockHash, block)
|
||||
|
||||
err = tc.BlockValidator().ValidateBodyInIsolation(blockHash)
|
||||
if err == nil || !errors.Is(err, ruleerrors.ErrBlockSizeTooHigh) {
|
||||
t.Fatalf("ValidateBodyInIsolationTest: TestBlockSize:"+
|
||||
" Unexpected error: Expected to: %v, but got : %v", ruleerrors.ErrBlockSizeTooHigh, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func initBlockWithInvalidBlockSize(params *dagconfig.Params, tc testapi.TestConsensus) (*externalapi.DomainBlock, model.UTXODiff, error) {
|
||||
emptyCoinbase := externalapi.DomainCoinbaseData{
|
||||
ScriptPublicKey: &externalapi.ScriptPublicKey{
|
||||
Script: nil,
|
||||
Version: 0,
|
||||
},
|
||||
}
|
||||
prevOutTxID := &externalapi.DomainTransactionID{}
|
||||
prevOutPoint := externalapi.DomainOutpoint{TransactionID: *prevOutTxID, Index: 1}
|
||||
bigSignatureScript := bytes.Repeat([]byte("01"), 25000)
|
||||
txInput := externalapi.DomainTransactionInput{
|
||||
PreviousOutpoint: prevOutPoint,
|
||||
SignatureScript: bigSignatureScript,
|
||||
Sequence: constants.MaxTxInSequenceNum,
|
||||
UTXOEntry: utxo.NewUTXOEntry(
|
||||
100_000_000,
|
||||
&externalapi.ScriptPublicKey{},
|
||||
true,
|
||||
uint64(5)),
|
||||
}
|
||||
tx := &externalapi.DomainTransaction{
|
||||
Version: constants.MaxTransactionVersion,
|
||||
Inputs: []*externalapi.DomainTransactionInput{&txInput},
|
||||
Outputs: []*externalapi.DomainTransactionOutput{{uint64(0xFFFF),
|
||||
&externalapi.ScriptPublicKey{Script: []byte{1, 2}, Version: 0}}, {uint64(0xFFFF),
|
||||
&externalapi.ScriptPublicKey{Script: []byte{1, 3}, Version: 0}}},
|
||||
PayloadHash: *externalapi.NewDomainHashFromByteArray(&[externalapi.DomainHashSize]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}),
|
||||
Payload: []byte{0x01},
|
||||
}
|
||||
|
||||
return tc.BuildBlockWithParents([]*externalapi.DomainHash{params.GenesisHash}, &emptyCoinbase, []*externalapi.DomainTransaction{tx})
|
||||
}
|
||||
|
||||
func TestCheckBlockDuplicateTransactions(t *testing.T) {
|
||||
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
|
||||
|
||||
factory := consensus.NewFactory()
|
||||
tc, teardown, err := factory.NewTestConsensus(params, false, "TestCheckBlockDuplicateTransactions")
|
||||
if err != nil {
|
||||
t.Fatalf("Error setting up tc: %+v", err)
|
||||
}
|
||||
defer teardown(false)
|
||||
|
||||
block, _, err := initBlockWithDuplicateTransaction(params, tc)
|
||||
if err != nil {
|
||||
t.Fatalf("Error BuildBlockWithParents : %+v", err)
|
||||
}
|
||||
blockHash := consensushashing.BlockHash(block)
|
||||
tc.BlockStore().Stage(blockHash, block)
|
||||
|
||||
err = tc.BlockValidator().ValidateBodyInIsolation(blockHash)
|
||||
if err == nil || !errors.Is(err, ruleerrors.ErrDuplicateTx) {
|
||||
t.Fatalf("ValidateBodyInIsolationTest: TestCheckBlockDuplicateTransactions:"+
|
||||
" Unexpected error: Expected to: %v, but got : %v", ruleerrors.ErrDuplicateTx, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func initBlockWithDuplicateTransaction(params *dagconfig.Params, tc testapi.TestConsensus) (*externalapi.DomainBlock, model.UTXODiff, error) {
|
||||
emptyCoinbase := externalapi.DomainCoinbaseData{
|
||||
ScriptPublicKey: &externalapi.ScriptPublicKey{
|
||||
Script: nil,
|
||||
Version: 0,
|
||||
},
|
||||
}
|
||||
prevOutTxID := &externalapi.DomainTransactionID{}
|
||||
prevOutPoint := externalapi.DomainOutpoint{TransactionID: *prevOutTxID, Index: 1}
|
||||
txInput := externalapi.DomainTransactionInput{
|
||||
PreviousOutpoint: prevOutPoint,
|
||||
SignatureScript: bytes.Repeat([]byte("01"), 10),
|
||||
Sequence: constants.MaxTxInSequenceNum,
|
||||
UTXOEntry: utxo.NewUTXOEntry(
|
||||
100_000_000,
|
||||
&externalapi.ScriptPublicKey{},
|
||||
true,
|
||||
uint64(5)),
|
||||
}
|
||||
tx := &externalapi.DomainTransaction{
|
||||
Version: 0,
|
||||
Inputs: []*externalapi.DomainTransactionInput{&txInput},
|
||||
Outputs: []*externalapi.DomainTransactionOutput{{uint64(0xFFFF),
|
||||
&externalapi.ScriptPublicKey{Script: []byte{1, 2}, Version: 0}}, {uint64(0xFFFF),
|
||||
&externalapi.ScriptPublicKey{Script: []byte{1, 3}, Version: 0}}},
|
||||
SubnetworkID: subnetworks.SubnetworkIDNative,
|
||||
}
|
||||
|
||||
return tc.BuildBlockWithParents([]*externalapi.DomainHash{params.GenesisHash}, &emptyCoinbase, []*externalapi.DomainTransaction{tx, tx})
|
||||
}
|
||||
|
||||
func TestCheckBlockContainsOnlyOneCoinbase(t *testing.T) {
|
||||
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
|
||||
|
||||
factory := consensus.NewFactory()
|
||||
tc, teardown, err := factory.NewTestConsensus(params, false, "TestCheckBlockContainsOnlyOneCoinbase")
|
||||
if err != nil {
|
||||
t.Fatalf("Error setting up tc: %+v", err)
|
||||
}
|
||||
defer teardown(false)
|
||||
|
||||
block, _, err := initBlockWithMoreThanOneCoinbase(params, tc)
|
||||
if err != nil {
|
||||
t.Fatalf("Error BuildBlockWithParents : %+v", err)
|
||||
}
|
||||
blockHash := consensushashing.BlockHash(block)
|
||||
tc.BlockStore().Stage(blockHash, block)
|
||||
|
||||
err = tc.BlockValidator().ValidateBodyInIsolation(blockHash)
|
||||
if err == nil || !errors.Is(err, ruleerrors.ErrMultipleCoinbases) {
|
||||
t.Fatalf("ValidateBodyInIsolationTest: TestCheckBlockContainsOnlyOneCoinbase:"+
|
||||
" Unexpected error: Expected to: %v, but got : %v", ruleerrors.ErrMultipleCoinbases, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func initBlockWithMoreThanOneCoinbase(params *dagconfig.Params, tc testapi.TestConsensus) (*externalapi.DomainBlock, model.UTXODiff, error) {
|
||||
emptyCoinbase := externalapi.DomainCoinbaseData{
|
||||
ScriptPublicKey: &externalapi.ScriptPublicKey{
|
||||
Script: nil,
|
||||
Version: 0,
|
||||
},
|
||||
}
|
||||
prevOutTxID := &externalapi.DomainTransactionID{}
|
||||
prevOutPoint := externalapi.DomainOutpoint{TransactionID: *prevOutTxID, Index: 1}
|
||||
txInput := externalapi.DomainTransactionInput{
|
||||
PreviousOutpoint: prevOutPoint,
|
||||
SignatureScript: bytes.Repeat([]byte("01"), 10),
|
||||
Sequence: constants.MaxTxInSequenceNum,
|
||||
UTXOEntry: utxo.NewUTXOEntry(
|
||||
100_000_000,
|
||||
&externalapi.ScriptPublicKey{},
|
||||
true,
|
||||
uint64(5)),
|
||||
}
|
||||
tx := &externalapi.DomainTransaction{
|
||||
Version: 0,
|
||||
Inputs: []*externalapi.DomainTransactionInput{&txInput},
|
||||
Outputs: []*externalapi.DomainTransactionOutput{{uint64(0xFFFF),
|
||||
&externalapi.ScriptPublicKey{Script: []byte{1, 2}, Version: 0}}, {uint64(0xFFFF),
|
||||
&externalapi.ScriptPublicKey{Script: []byte{1, 3}, Version: 0}}},
|
||||
SubnetworkID: subnetworks.SubnetworkIDCoinbase,
|
||||
}
|
||||
|
||||
return tc.BuildBlockWithParents([]*externalapi.DomainHash{params.GenesisHash}, &emptyCoinbase, []*externalapi.DomainTransaction{tx})
|
||||
}
|
||||
|
||||
func TestCheckBlockDoubleSpends(t *testing.T) {
|
||||
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
|
||||
|
||||
factory := consensus.NewFactory()
|
||||
tc, teardown, err := factory.NewTestConsensus(params, false, "TestCheckBlockDoubleSpends")
|
||||
if err != nil {
|
||||
t.Fatalf("Error setting up tc: %+v", err)
|
||||
}
|
||||
defer teardown(false)
|
||||
|
||||
block, _, err := initBlockWithDoubleSpends(params, tc)
|
||||
if err != nil {
|
||||
t.Fatalf("Error BuildBlockWithParents : %+v", err)
|
||||
}
|
||||
blockHash := consensushashing.BlockHash(block)
|
||||
tc.BlockStore().Stage(blockHash, block)
|
||||
|
||||
err = tc.BlockValidator().ValidateBodyInIsolation(blockHash)
|
||||
if err == nil || !errors.Is(err, ruleerrors.ErrDoubleSpendInSameBlock) {
|
||||
t.Fatalf("ValidateBodyInIsolationTest: TestCheckBlockDoubleSpends:"+
|
||||
" Unexpected error: Expected to: %v, but got : %v", ruleerrors.ErrDoubleSpendInSameBlock, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func initBlockWithDoubleSpends(params *dagconfig.Params, tc testapi.TestConsensus) (*externalapi.DomainBlock, model.UTXODiff, error) {
|
||||
emptyCoinbase := externalapi.DomainCoinbaseData{
|
||||
ScriptPublicKey: &externalapi.ScriptPublicKey{
|
||||
Script: nil,
|
||||
Version: 0,
|
||||
},
|
||||
}
|
||||
prevOutTxID := &externalapi.DomainTransactionID{}
|
||||
prevOutPoint := externalapi.DomainOutpoint{TransactionID: *prevOutTxID, Index: 1}
|
||||
txInput := externalapi.DomainTransactionInput{
|
||||
PreviousOutpoint: prevOutPoint,
|
||||
SignatureScript: bytes.Repeat([]byte("01"), 10),
|
||||
Sequence: constants.MaxTxInSequenceNum,
|
||||
UTXOEntry: utxo.NewUTXOEntry(
|
||||
100_000_000,
|
||||
&externalapi.ScriptPublicKey{},
|
||||
true,
|
||||
uint64(5)),
|
||||
}
|
||||
tx := &externalapi.DomainTransaction{
|
||||
Version: 0,
|
||||
Inputs: []*externalapi.DomainTransactionInput{&txInput},
|
||||
Outputs: []*externalapi.DomainTransactionOutput{{uint64(0xFFFF),
|
||||
&externalapi.ScriptPublicKey{Script: []byte{1, 2}, Version: 0}}, {uint64(0xFFFF),
|
||||
&externalapi.ScriptPublicKey{Script: []byte{1, 3}, Version: 0}}},
|
||||
SubnetworkID: subnetworks.SubnetworkIDNative,
|
||||
}
|
||||
txInputSameOutpoint := externalapi.DomainTransactionInput{
|
||||
PreviousOutpoint: prevOutPoint,
|
||||
SignatureScript: bytes.Repeat([]byte("02"), 10),
|
||||
Sequence: constants.MaxTxInSequenceNum,
|
||||
UTXOEntry: utxo.NewUTXOEntry(
|
||||
100_000_000,
|
||||
&externalapi.ScriptPublicKey{},
|
||||
true,
|
||||
uint64(4)),
|
||||
}
|
||||
txSameOutpoint := &externalapi.DomainTransaction{
|
||||
Version: 0,
|
||||
Inputs: []*externalapi.DomainTransactionInput{&txInputSameOutpoint},
|
||||
Outputs: []*externalapi.DomainTransactionOutput{{uint64(0xFF),
|
||||
&externalapi.ScriptPublicKey{Script: []byte{1, 2}, Version: 0}}, {uint64(0xFFFF),
|
||||
&externalapi.ScriptPublicKey{Script: []byte{1, 3}, Version: 0}}},
|
||||
SubnetworkID: subnetworks.SubnetworkIDNative,
|
||||
}
|
||||
|
||||
return tc.BuildBlockWithParents([]*externalapi.DomainHash{params.GenesisHash},
|
||||
&emptyCoinbase, []*externalapi.DomainTransaction{tx, txSameOutpoint})
|
||||
}
|
||||
|
||||
func TestCheckFirstBlockTransactionIsCoinbase(t *testing.T) {
|
||||
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
|
||||
|
||||
factory := consensus.NewFactory()
|
||||
tc, teardown, err := factory.NewTestConsensus(params, false, "TestCheckFirstBlockTransactionIsCoinbase")
|
||||
if err != nil {
|
||||
t.Fatalf("Error setting up tc: %+v", err)
|
||||
}
|
||||
defer teardown(false)
|
||||
|
||||
block := initBlockWithFirstTransactionDifferentThanCoinbase(params)
|
||||
blockHash := consensushashing.BlockHash(block)
|
||||
tc.BlockStore().Stage(blockHash, block)
|
||||
|
||||
err = tc.BlockValidator().ValidateBodyInIsolation(blockHash)
|
||||
if err == nil || !errors.Is(err, ruleerrors.ErrFirstTxNotCoinbase) {
|
||||
t.Fatalf("ValidateBodyInIsolationTest: TestCheckFirstBlockTransactionIsCoinbase:"+
|
||||
" Unexpected error: Expected to: %v, but got : %v", ruleerrors.ErrFirstTxNotCoinbase, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func initBlockWithFirstTransactionDifferentThanCoinbase(params *dagconfig.Params) *externalapi.DomainBlock {
|
||||
prevOutTxID := &externalapi.DomainTransactionID{}
|
||||
prevOutPoint := externalapi.DomainOutpoint{TransactionID: *prevOutTxID, Index: 1}
|
||||
txInput := externalapi.DomainTransactionInput{
|
||||
PreviousOutpoint: prevOutPoint,
|
||||
SignatureScript: bytes.Repeat([]byte("01"), 10),
|
||||
Sequence: constants.MaxTxInSequenceNum,
|
||||
}
|
||||
tx := &externalapi.DomainTransaction{
|
||||
Version: 0,
|
||||
Inputs: []*externalapi.DomainTransactionInput{&txInput},
|
||||
Outputs: []*externalapi.DomainTransactionOutput{{uint64(0xFFFF),
|
||||
&externalapi.ScriptPublicKey{Script: []byte{1, 2}, Version: 0}}, {uint64(0xFFFF),
|
||||
&externalapi.ScriptPublicKey{Script: []byte{1, 3}, Version: 0}}},
|
||||
SubnetworkID: subnetworks.SubnetworkIDNative,
|
||||
}
|
||||
|
||||
return &externalapi.DomainBlock{
|
||||
Header: blockheader.NewImmutableBlockHeader(
|
||||
constants.MaxBlockVersion,
|
||||
[]*externalapi.DomainHash{params.GenesisHash},
|
||||
merkle.CalculateHashMerkleRoot([]*externalapi.DomainTransaction{tx}),
|
||||
&externalapi.DomainHash{},
|
||||
externalapi.NewDomainHashFromByteArray(&[externalapi.DomainHashSize]byte{
|
||||
0x80, 0xf7, 0x00, 0xe3, 0x16, 0x3d, 0x04, 0x95,
|
||||
0x5b, 0x7e, 0xaf, 0x84, 0x7e, 0x1b, 0x6b, 0x06,
|
||||
0x4e, 0x06, 0xba, 0x64, 0xd7, 0x61, 0xda, 0x25,
|
||||
0x1a, 0x0e, 0x21, 0xd4, 0x64, 0x49, 0x02, 0xa2,
|
||||
}),
|
||||
0x5cd18053000,
|
||||
0x207fffff,
|
||||
0x1),
|
||||
Transactions: []*externalapi.DomainTransaction{tx},
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,251 @@
|
||||
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/testutils"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/txscript"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
|
||||
"math/big"
|
||||
|
||||
"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/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type mocPastMedianTimeManager struct {
|
||||
pastMedianTimeForTest int64
|
||||
}
|
||||
|
||||
// PastMedianTime returns the past median time for the test.
|
||||
func (mdf *mocPastMedianTimeManager) PastMedianTime(*externalapi.DomainHash) (int64, error) {
|
||||
return mdf.pastMedianTimeForTest, nil
|
||||
}
|
||||
|
||||
func TestValidateTransactionInContextAndPopulateMassAndFee(t *testing.T) {
|
||||
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
|
||||
|
||||
factory := consensus.NewFactory()
|
||||
pastMedianManager := &mocPastMedianTimeManager{}
|
||||
factory.SetTestPastMedianTimeManager(func(int, model.DBReader, model.DAGTraversalManager, model.BlockHeaderStore,
|
||||
model.GHOSTDAGDataStore) model.PastMedianTimeManager {
|
||||
return pastMedianManager
|
||||
})
|
||||
tc, tearDown, err := factory.NewTestConsensus(params, false,
|
||||
"TestValidateTransactionInContextAndPopulateMassAndFee")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed create a NewTestConsensus: %s", err)
|
||||
}
|
||||
defer tearDown(false)
|
||||
|
||||
pastMedianManager.pastMedianTimeForTest = 1
|
||||
privateKey, err := secp256k1.GeneratePrivateKey()
|
||||
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.NewAddressPubKeyHashFromPublicKey(publicKeySerialized[:], params.Prefix)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate p2pkh 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,
|
||||
UTXOEntry: utxo.NewUTXOEntry(
|
||||
100_000_000, // 1 KAS
|
||||
scriptPublicKey,
|
||||
true,
|
||||
uint64(5)),
|
||||
}
|
||||
txInputWithMaxSequence := externalapi.DomainTransactionInput{
|
||||
PreviousOutpoint: prevOutPoint,
|
||||
SignatureScript: []byte{},
|
||||
Sequence: constants.SequenceLockTimeIsSeconds,
|
||||
UTXOEntry: utxo.NewUTXOEntry(
|
||||
100000000, // 1 KAS
|
||||
scriptPublicKey,
|
||||
true,
|
||||
uint64(5)),
|
||||
}
|
||||
txInputWithLargeAmount := externalapi.DomainTransactionInput{
|
||||
PreviousOutpoint: prevOutPoint,
|
||||
SignatureScript: []byte{},
|
||||
Sequence: constants.MaxTxInSequenceNum,
|
||||
UTXOEntry: utxo.NewUTXOEntry(
|
||||
constants.MaxSompi,
|
||||
scriptPublicKey,
|
||||
true,
|
||||
uint64(5)),
|
||||
}
|
||||
|
||||
txOut := externalapi.DomainTransactionOutput{
|
||||
Value: 100000000, // 1 KAS
|
||||
ScriptPublicKey: scriptPublicKey,
|
||||
}
|
||||
txOutBigValue := externalapi.DomainTransactionOutput{
|
||||
Value: 200_000_000, // 2 KAS
|
||||
ScriptPublicKey: scriptPublicKey,
|
||||
}
|
||||
|
||||
validTx := externalapi.DomainTransaction{
|
||||
Version: constants.MaxTransactionVersion,
|
||||
Inputs: []*externalapi.DomainTransactionInput{&txInputWithMaxSequence},
|
||||
Outputs: []*externalapi.DomainTransactionOutput{&txOut},
|
||||
SubnetworkID: subnetworks.SubnetworkIDRegistry,
|
||||
Gas: 0,
|
||||
LockTime: 0}
|
||||
txWithImmatureCoinbase := externalapi.DomainTransaction{
|
||||
Version: constants.MaxTransactionVersion,
|
||||
Inputs: []*externalapi.DomainTransactionInput{&txInput},
|
||||
Outputs: []*externalapi.DomainTransactionOutput{&txOut},
|
||||
SubnetworkID: subnetworks.SubnetworkIDRegistry,
|
||||
Gas: 0,
|
||||
LockTime: 0}
|
||||
txWithLargeAmount := externalapi.DomainTransaction{
|
||||
Version: constants.MaxTransactionVersion,
|
||||
Inputs: []*externalapi.DomainTransactionInput{&txInput, &txInputWithLargeAmount},
|
||||
Outputs: []*externalapi.DomainTransactionOutput{&txOut},
|
||||
SubnetworkID: subnetworks.SubnetworkIDRegistry,
|
||||
Gas: 0,
|
||||
LockTime: 0}
|
||||
txWithBigValue := externalapi.DomainTransaction{
|
||||
Version: constants.MaxTransactionVersion,
|
||||
Inputs: []*externalapi.DomainTransactionInput{&txInput},
|
||||
Outputs: []*externalapi.DomainTransactionOutput{&txOutBigValue},
|
||||
SubnetworkID: subnetworks.SubnetworkIDRegistry,
|
||||
Gas: 0,
|
||||
LockTime: 0}
|
||||
txWithInvalidSignature := externalapi.DomainTransaction{
|
||||
Version: constants.MaxTransactionVersion,
|
||||
Inputs: []*externalapi.DomainTransactionInput{&txInput},
|
||||
Outputs: []*externalapi.DomainTransactionOutput{&txOut},
|
||||
SubnetworkID: subnetworks.SubnetworkIDRegistry,
|
||||
Gas: 0,
|
||||
LockTime: 0}
|
||||
|
||||
for i, input := range validTx.Inputs {
|
||||
signatureScript, err := txscript.SignatureScript(&validTx, i, scriptPublicKey, txscript.SigHashAll, privateKey)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create a sigScript: %v", err)
|
||||
}
|
||||
input.SignatureScript = signatureScript
|
||||
}
|
||||
|
||||
povBlockHash := externalapi.NewDomainHashFromByteArray(&[32]byte{0x01})
|
||||
genesisHash := params.GenesisHash
|
||||
tc.GHOSTDAGDataStore().Stage(model.VirtualBlockHash, model.NewBlockGHOSTDAGData(
|
||||
params.BlockCoinbaseMaturity+txInput.UTXOEntry.BlockBlueScore(),
|
||||
new(big.Int),
|
||||
genesisHash,
|
||||
make([]*externalapi.DomainHash, 1000),
|
||||
make([]*externalapi.DomainHash, 1),
|
||||
nil))
|
||||
tc.GHOSTDAGDataStore().Stage(povBlockHash, model.NewBlockGHOSTDAGData(
|
||||
10,
|
||||
new(big.Int),
|
||||
genesisHash,
|
||||
make([]*externalapi.DomainHash, 1000),
|
||||
make([]*externalapi.DomainHash, 1),
|
||||
nil))
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
tx *externalapi.DomainTransaction
|
||||
povBlockHash *externalapi.DomainHash
|
||||
selectedParentMedianTime int64
|
||||
isValid bool
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "Valid transaction",
|
||||
tx: &validTx,
|
||||
povBlockHash: model.VirtualBlockHash,
|
||||
selectedParentMedianTime: 1,
|
||||
isValid: true,
|
||||
expectedError: nil,
|
||||
},
|
||||
{ // The calculated block coinbase maturity is smaller than the minimum expected blockCoinbaseMaturity.
|
||||
// The povBlockHash blue score is 10 and the UTXO blue 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,
|
||||
},
|
||||
{ // The total inputs amount is bigger than the allowed maximum (constants.MaxSompi)
|
||||
name: "checkTransactionInputAmounts",
|
||||
tx: &txWithLargeAmount,
|
||||
povBlockHash: model.VirtualBlockHash,
|
||||
selectedParentMedianTime: 1,
|
||||
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: model.VirtualBlockHash,
|
||||
selectedParentMedianTime: 1,
|
||||
isValid: false,
|
||||
expectedError: ruleerrors.ErrSpendTooHigh,
|
||||
},
|
||||
{ // the selectedParentMedianTime is negative and hence invalid.
|
||||
name: "checkTransactionSequenceLock",
|
||||
tx: &validTx,
|
||||
povBlockHash: model.VirtualBlockHash,
|
||||
selectedParentMedianTime: -1,
|
||||
isValid: false,
|
||||
expectedError: ruleerrors.ErrUnfinalizedTx,
|
||||
},
|
||||
{ // The SignatureScript (in the txInput) is empty and hence invalid.
|
||||
name: "checkTransactionScripts",
|
||||
tx: &txWithInvalidSignature,
|
||||
povBlockHash: model.VirtualBlockHash,
|
||||
selectedParentMedianTime: 1,
|
||||
isValid: false,
|
||||
expectedError: ruleerrors.ErrScriptValidation,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
err := tc.TransactionValidator().ValidateTransactionInContextAndPopulateMassAndFee(test.tx,
|
||||
test.povBlockHash, test.selectedParentMedianTime)
|
||||
|
||||
if test.isValid {
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error on TestValidateTransactionInContextAndPopulateMassAndFee"+
|
||||
" on test %v: %v", test.name, err)
|
||||
}
|
||||
} else {
|
||||
if err == nil || !errors.Is(err, test.expectedError) {
|
||||
t.Fatalf("TestValidateTransactionInContextAndPopulateMassAndFee: test %v:"+
|
||||
" Unexpected error: Expected to: %v, but got : %v", test.name, test.expectedError, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user