-
Notifications
You must be signed in to change notification settings - Fork 21
test: added a test for checking double spend transaction states on lo… #141
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
c2d66e0
e0be407
5495bf7
516b755
4cf8dad
e95f6c3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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()) | ||
| } | ||
|
|
||
| // VerifyNotInUtxoStore verifies that the transaction does not exist in the UTXO store. | ||
|
|
@@ -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) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
|
|
||
|
|
@@ -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 | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This change modifies the test blockchain store from in-memory SQLite ( 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:
|
||
| 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 | ||
|
|
||
| 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) | ||
| }) | ||
| } | ||
|
|
||
|
|
||
| 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 (*) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,7 +13,7 @@ import ( | |
| ) | ||
|
|
||
| var ( | ||
| blockWait = 5 * time.Second | ||
| blockWait = 30 * time.Second | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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:
|
||
| ) | ||
|
|
||
| func setupLongestChainTest(t *testing.T, utxoStoreOverride string) (td *daemon.TestDaemon, block3 *model.Block) { | ||
|
|
||
There was a problem hiding this comment.
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".