Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion daemon/test_daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -757,7 +757,7 @@ func (td *TestDaemon) VerifyOnLongestChainInUtxoStore(t *testing.T, tx *bt.Tx) {
func (td *TestDaemon) VerifyNotOnLongestChainInUtxoStore(t *testing.T, tx *bt.Tx) {
readTx, err := td.UtxoStore.Get(td.Ctx, tx.TxIDChainHash(), fields.UnminedSince)
require.NoError(t, err, "Failed to get transaction %s", tx.String())
assert.Greater(t, readTx.UnminedSince, uint32(0), "Expected transaction %s to be on the longest chain", tx.TxIDChainHash().String())
assert.Greater(t, readTx.UnminedSince, uint32(0), "Expected transaction %s to be not on the longest chain", tx.TxIDChainHash().String())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good fix! The error message now correctly states "not on the longest chain" instead of the contradictory "to be on the longest chain".

}

// VerifyNotInUtxoStore verifies that the transaction does not exist in the UTXO store.
Expand Down Expand Up @@ -1273,6 +1273,13 @@ finished:
}

func (td *TestDaemon) WaitForBlockStateChange(t *testing.T, expectedBlock *model.Block, timeout time.Duration) {
// First check if the expected block is already the current best block
state, err := td.BlockAssemblyClient.GetBlockAssemblyState(td.Ctx)
if err == nil && state.CurrentHash == expectedBlock.Header.Hash().String() {
t.Logf("Block %s (height %d) is already the current best block", expectedBlock.Header.Hash().String(), expectedBlock.Height)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good optimization! This early return prevents unnecessary waiting when the expected block is already current, improving test efficiency.

return
}

stateChangeCh := make(chan blockassembly.BestBlockInfo)
td.BlockAssembler.SetStateChangeCh(stateChangeCh)

Expand All @@ -1290,6 +1297,7 @@ func (td *TestDaemon) WaitForBlockStateChange(t *testing.T, expectedBlock *model
t.Fatalf("Timeout waiting for block assembly to reach block %s", expectedBlock.Header.Hash().String())
case bestBlockInfo := <-stateChangeCh:
t.Logf("Received BestBlockInfo: Height=%d, Hash=%s", bestBlockInfo.Height, bestBlockInfo.Header.Hash().String())
t.Logf("Expected block: Height=%d, Hash=%s", expectedBlock.Height, expectedBlock.Header.Hash().String())
if bestBlockInfo.Header.Hash().IsEqual(expectedBlock.Header.Hash()) {
return
}
Expand Down
2 changes: 1 addition & 1 deletion settings.conf
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ blockchainDBUserPwd.docker.teranode3 = miner3

blockchain_store = sqlite:///blockchain
blockchain_store.dev = postgres://teranode:teranode@localhost:${POSTGRES_PORT}/teranode
blockchain_store.dev.system.test = sqlitememory:///blockchain
blockchain_store.dev.system.test = sqlite:///blockchain
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change modifies the test blockchain store from in-memory SQLite (sqlitememory:///blockchain) to file-based SQLite (sqlite:///blockchain).

Is this intentional? The in-memory store is typically faster for tests and avoids filesystem I/O. If this change is needed for test persistence or debugging, consider:

  1. Adding a comment explaining why
  2. Ensuring test cleanup properly handles the database file

blockchain_store.docker.ci.chainintegrity.teranode1 = postgres://miner1:miner1@localhost:${POSTGRES_PORT}/teranode1
blockchain_store.docker.ci.chainintegrity.teranode2 = postgres://miner2:miner2@localhost:${POSTGRES_PORT}/teranode2
blockchain_store.docker.ci.chainintegrity.teranode3 = postgres://miner3:miner3@localhost:${POSTGRES_PORT}/teranode3
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package longest_chain

import (
"testing"

"github.com/bsv-blockchain/teranode/test/utils/aerospike"
"github.com/bsv-blockchain/teranode/test/utils/transactions"
"github.com/stretchr/testify/require"
)

func TestLongestChainAerospikeInvalidateFork(t *testing.T) {
// start an aerospike container
utxoStore, teardown, err := aerospike.InitAerospikeContainer()
require.NoError(t, err)

t.Cleanup(func() {
_ = teardown()
})

t.Run("invalid block with old tx", func(t *testing.T) {
testLongestChainInvalidateFork(t, utxoStore)
})
}


Check failure on line 25 in test/sequentialtest/longest_chain/03_longest_chain_invalidate_fork_test.go

View workflow job for this annotation

GitHub Actions / golangci-lint

File is not properly formatted (gci)
func testLongestChainInvalidateFork(t *testing.T, utxoStore string) {
// Setup test environment
td, block3 := setupLongestChainTest(t, utxoStore)
defer func() {
td.Stop(t)
}()

td.Settings.BlockValidation.OptimisticMining = true

block1, err := td.BlockchainClient.GetBlockByHeight(td.Ctx, 1)
require.NoError(t, err)

parentTxWith3Outputs := td.CreateTransactionWithOptions(t, transactions.WithInput(block1.CoinbaseTx, 0), transactions.WithP2PKHOutputs(3, 100000) )
require.NoError(t, td.PropagationClient.ProcessTransaction(td.Ctx, parentTxWith3Outputs))

childTx1 := td.CreateTransactionWithOptions(t, transactions.WithInput(parentTxWith3Outputs, 0), transactions.WithP2PKHOutputs(1, 100000) )
childTx2 := td.CreateTransactionWithOptions(t, transactions.WithInput(parentTxWith3Outputs, 1), transactions.WithP2PKHOutputs(1, 100000) )
childTx3 := td.CreateTransactionWithOptions(t, transactions.WithInput(parentTxWith3Outputs, 2), transactions.WithP2PKHOutputs(1, 100000) )
// create a double spend of tx3
childTx3DS := td.CreateTransactionWithOptions(t, transactions.WithInput(parentTxWith3Outputs, 2), transactions.WithP2PKHOutputs(2, 50000) )

require.NoError(t, td.PropagationClient.ProcessTransaction(td.Ctx, childTx1))
require.NoError(t, td.PropagationClient.ProcessTransaction(td.Ctx, childTx2))
require.NoError(t, td.PropagationClient.ProcessTransaction(td.Ctx, childTx3))

_, block4a := td.CreateTestBlock(t, block3, 4001, parentTxWith3Outputs, childTx1, childTx2)
require.NoError(t, td.BlockValidation.ValidateBlock(td.Ctx, block4a, "legacy", nil, false, true), "Failed to process block")
td.WaitForBlockBeingMined(t, block4a)
t.Logf("WaitForBlock(t, block4a, blockWait): %s", block4a.Hash().String())
td.WaitForBlock(t, block4a, blockWait)


// 0 -> 1 ... 2 -> 3 -> 4a (*)

td.VerifyNotInBlockAssembly(t, parentTxWith3Outputs)
td.VerifyNotInBlockAssembly(t, childTx1)
td.VerifyNotInBlockAssembly(t, childTx2)
td.VerifyInBlockAssembly(t, childTx3)
td.VerifyOnLongestChainInUtxoStore(t, parentTxWith3Outputs)
td.VerifyOnLongestChainInUtxoStore(t, childTx1)
td.VerifyOnLongestChainInUtxoStore(t, childTx2)
td.VerifyNotOnLongestChainInUtxoStore(t, childTx3)

// create a block with tx1 and tx2 that will be invalid as tx2 is already on block4a
_, block4b := td.CreateTestBlock(t, block3, 4002, parentTxWith3Outputs, childTx2, childTx3)
require.NoError(t, td.BlockValidation.ValidateBlock(td.Ctx, block4b, "legacy", nil, true), "Failed to process block")
t.Logf("WaitForBlockBeingMined(t, block4b): %s", block4b.Hash().String())
td.WaitForBlockBeingMined(t, block4b)

_, block5b := td.CreateTestBlock(t, block4b, 5001)
require.NoError(t, td.BlockValidation.ValidateBlock(td.Ctx, block5b, "legacy", nil, true), "Failed to process block")
t.Logf("WaitForBlockBeingMined(t, block5b): %s", block5b.Hash().String())
td.WaitForBlockBeingMined(t, block5b)
t.Logf("WaitForBlock(t, block5b, blockWait): %s", block5b.Hash().String())
td.WaitForBlock(t, block5b, blockWait)

// 0 -> 1 ... 2 -> 3 -> 4b -> 5b (*)
td.VerifyInBlockAssembly(t, childTx1)
td.VerifyNotInBlockAssembly(t, childTx2)
td.VerifyNotInBlockAssembly(t, childTx3)
td.VerifyNotOnLongestChainInUtxoStore(t, childTx1)
td.VerifyOnLongestChainInUtxoStore(t, childTx2)
td.VerifyOnLongestChainInUtxoStore(t, childTx3)

_, err = td.BlockchainClient.InvalidateBlock(t.Context(), block5b.Hash())
require.NoError(t, err)

td.WaitForBlock(t, block4a, blockWait)

// 0 -> 1 ... 2 -> 3 -> 4a

td.VerifyNotInBlockAssembly(t, childTx1)
td.VerifyNotInBlockAssembly(t, childTx2)
td.VerifyInBlockAssembly(t, childTx3)
td.VerifyOnLongestChainInUtxoStore(t, childTx1)
td.VerifyOnLongestChainInUtxoStore(t, childTx2)
td.VerifyNotOnLongestChainInUtxoStore(t, childTx3)

// create a new block on 4a with tx3 in it
_, block5a := td.CreateTestBlock(t, block4a, 6001, childTx3DS)
require.NoError(t, td.BlockValidation.ValidateBlock(td.Ctx, block5a, "legacy", nil, false, true), "Failed to process block")
t.Logf("WaitForBlockBeingMined(t, block5a): %s", block5a.Hash().String())
td.WaitForBlockBeingMined(t, block5a)
t.Logf("WaitForBlock(t, block5a, blockWait): %s", block5a.Hash().String())
td.WaitForBlock(t, block5a, blockWait)

td.VerifyNotInBlockAssembly(t, childTx1)
td.VerifyNotInBlockAssembly(t, childTx2)
td.VerifyInBlockAssembly(t, childTx3)
td.VerifyNotInBlockAssembly(t, childTx3DS)
td.VerifyOnLongestChainInUtxoStore(t, childTx1)
td.VerifyOnLongestChainInUtxoStore(t, childTx2)
td.VerifyNotOnLongestChainInUtxoStore(t, childTx3)
td.VerifyOnLongestChainInUtxoStore(t, childTx3DS)// 0 -> 1 ... 2 -> 3 -> 4a -> 6a (*)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space before inline comment. Should be:

td.VerifyOnLongestChainInUtxoStore(t, childTx3DS) // 0 -> 1 ... 2 -> 3 -> 4a -> 6a (*)


_, err = td.BlockchainClient.InvalidateBlock(t.Context(), block4b.Hash())
require.NoError(t, err)

// create a new block on 5a with tx3 in it
_, block6a := td.CreateTestBlock(t, block5a, 7001, childTx3)
require.NoError(t, td.BlockValidation.ValidateBlock(td.Ctx, block6a, "legacy", nil, false, true), "Failed to process block")
t.Logf("WaitForBlockBeingMined(t, block6a): %s", block6a.Hash().String())
td.WaitForBlockBeingMined(t, block6a)
t.Logf("WaitForBlock(t, block6a, blockWait): %s", block6a.Hash().String())
td.WaitForBlock(t, block6a, blockWait)

t.Logf("FINAL VERIFICATIONS:")
td.VerifyNotInBlockAssembly(t, childTx1)
td.VerifyNotInBlockAssembly(t, childTx2)
td.VerifyNotInBlockAssembly(t, childTx3)
td.VerifyNotInBlockAssembly(t, childTx3DS)
td.VerifyOnLongestChainInUtxoStore(t, childTx1)
td.VerifyOnLongestChainInUtxoStore(t, childTx2)
td.VerifyOnLongestChainInUtxoStore(t, childTx3)
td.VerifyOnLongestChainInUtxoStore(t, childTx3DS)
}
2 changes: 1 addition & 1 deletion test/sequentialtest/longest_chain/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
)

var (
blockWait = 5 * time.Second
blockWait = 30 * time.Second
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The block wait timeout has been increased from 5s to 30s. This is a 6x increase.

While this may help prevent flaky tests, it could also mask underlying timing or synchronization issues. Consider:

  1. Is there a specific race condition or slow operation that requires this increase?
  2. Would it be better to fix the root cause rather than increase the timeout?
  3. Does this impact CI/CD pipeline duration significantly?

)

func setupLongestChainTest(t *testing.T, utxoStoreOverride string) (td *daemon.TestDaemon, block3 *model.Block) {
Expand Down
Loading
Loading