Repository: kaspanet/kaspad
Pull request #1531 (2021-02-16 08:51:05)

Adds tests for transaction validator and block validators 
part of #1314 
Old tests that not merge yet and change them to fit the new design( #1008).
There are two parts:
1. Tests for transaction validator.
2. Tests for blockValidator - there were tests that already been covered in the current version, and those that weren't, I relocated them into existing file in the current version: block_body_in_isolation_test. I put the old file test all in comments.

**There is a test that I not finished yet and ill open a new PR for him (validateDifficulty on ValidatePruningPointViolationAndProofOfWorkAndDifficulty_test).

codecov[bot]: # [Codecov](https://codecov.io/gh/kaspanet/kaspad/pull/1531?src=pr&el=h1) Report
> Merging [#1531](https://codecov.io/gh/kaspanet/kaspad/pull/1531?src=pr&el=desc) (6fb5991) into [v0.9.0-dev](https://codecov.io/gh/kaspanet/kaspad/commit/d835f72e74af66629874f2522fbb5110685fc845?el=desc) (d835f72) will **increase** coverage by `0.25%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/kaspanet/kaspad/pull/1531/graphs/tree.svg?width=650&height=150&src=pr&token=2FBIEEJTNU)](https://codecov.io/gh/kaspanet/kaspad/pull/1531?src=pr&el=tree)

```diff
@@              Coverage Diff               @@
##           v0.9.0-dev    #1531      +/-   ##
==============================================
+ Coverage       61.80%   62.06%   +0.25%     
==============================================
  Files             507      507              
  Lines           19238    19252      +14     
==============================================
+ Hits            11890    11948      +58     
+ Misses           5642     5606      -36     
+ Partials         1706     1698       -8     
```


| [Impacted Files](https://codecov.io/gh/kaspanet/kaspad/pull/1531?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [...ses/transactionvalidator/transaction\_in\_context.go](https://codecov.io/gh/kaspanet/kaspad/pull/1531/diff?src=pr&el=tree#diff-ZG9tYWluL2NvbnNlbnN1cy9wcm9jZXNzZXMvdHJhbnNhY3Rpb252YWxpZGF0b3IvdHJhbnNhY3Rpb25faW5fY29udGV4dC5nbw==) | `71.15% <ø> (+26.92%)` | :arrow_up: |
| [domain/consensus/factory.go](https://codecov.io/gh/kaspanet/kaspad/pull/1531/diff?src=pr&el=tree#diff-ZG9tYWluL2NvbnNlbnN1cy9mYWN0b3J5Lmdv) | `90.82% <100.00%> (+0.05%)` | :arrow_up: |
| [app/rpc/rpccontext/verbosedata.go](https://codecov.io/gh/kaspanet/kaspad/pull/1531/diff?src=pr&el=tree#diff-YXBwL3JwYy9ycGNjb250ZXh0L3ZlcmJvc2VkYXRhLmdv) | `73.26% <0.00%> (-2.25%)` | :arrow_down: |
| [app/protocol/flows/blockrelay/handle\_relay\_invs.go](https://codecov.io/gh/kaspanet/kaspad/pull/1531/diff?src=pr&el=tree#diff-YXBwL3Byb3RvY29sL2Zsb3dzL2Jsb2NrcmVsYXkvaGFuZGxlX3JlbGF5X2ludnMuZ28=) | `67.32% <0.00%> (-1.35%)` | :arrow_down: |
| [app/rpc/rpchandlers/get\_block.go](https://codecov.io/gh/kaspanet/kaspad/pull/1531/diff?src=pr&el=tree#diff-YXBwL3JwYy9ycGNoYW5kbGVycy9nZXRfYmxvY2suZ28=) | `0.00% <0.00%> (ø)` | |
| [...onsensus/processes/blockprocessor/validateblock.go](https://codecov.io/gh/kaspanet/kaspad/pull/1531/diff?src=pr&el=tree#diff-ZG9tYWluL2NvbnNlbnN1cy9wcm9jZXNzZXMvYmxvY2twcm9jZXNzb3IvdmFsaWRhdGVibG9jay5nbw==) | `88.88% <0.00%> (+0.65%)` | :arrow_up: |
| [util/address.go](https://codecov.io/gh/kaspanet/kaspad/pull/1531/diff?src=pr&el=tree#diff-dXRpbC9hZGRyZXNzLmdv) | `96.42% <0.00%> (+3.57%)` | :arrow_up: |
| [...rocesses/blockvalidator/block\_body\_in\_isolation.go](https://codecov.io/gh/kaspanet/kaspad/pull/1531/diff?src=pr&el=tree#diff-ZG9tYWluL2NvbnNlbnN1cy9wcm9jZXNzZXMvYmxvY2t2YWxpZGF0b3IvYmxvY2tfYm9keV9pbl9pc29sYXRpb24uZ28=) | `84.21% <0.00%> (+8.77%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/kaspanet/kaspad/pull/1531?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/kaspanet/kaspad/pull/1531?src=pr&el=footer). Last update [d835f72...6fb5991](https://codecov.io/gh/kaspanet/kaspad/pull/1531?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/domain/consensus/constructors.go b/domain/consensus/constructors.go
index 81cab390..38b91ed8 100644
--- a/domain/consensus/constructors.go
+++ b/domain/consensus/constructors.go
@@ -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
diff --git a/domain/consensus/factory.go b/domain/consensus/factory.go
index 1836a3bf..c609159e 100644
--- a/domain/consensus/factory.go
+++ b/domain/consensus/factory.go
@@ -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


Test patch:
diff --git a/domain/consensus/processes/blockvalidator/block_body_in_isolation_test.go b/domain/consensus/processes/blockvalidator/block_body_in_isolation_test.go
index 847ab3a0..b7e37562 100644
--- a/domain/consensus/processes/blockvalidator/block_body_in_isolation_test.go
+++ b/domain/consensus/processes/blockvalidator/block_body_in_isolation_test.go
@@ -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},
+	}
+}
diff --git a/domain/consensus/processes/transactionvalidator/transactionvalidator_test.go b/domain/consensus/processes/transactionvalidator/transactionvalidator_test.go
new file mode 100644
index 00000000..4b8bf841
--- /dev/null
+++ b/domain/consensus/processes/transactionvalidator/transactionvalidator_test.go
@@ -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)
+				}
+			}
+		}
+	})
+}