-
Notifications
You must be signed in to change notification settings - Fork 21
test: add multi-node daemon test infrastructure with peer injection #196
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
Changes from all commits
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 |
|---|---|---|
|
|
@@ -480,6 +480,23 @@ | |
| } | ||
| } | ||
|
|
||
| // InjectPeerForTesting directly injects a peer into the registry for testing purposes. | ||
| // This method allows deterministic peer setup without requiring actual P2P network connections. | ||
| func (s *Server) InjectPeerForTesting(peerID peer.ID, clientName, dataHubURL string, height uint32, blockHash string) { | ||
| s.addConnectedPeer(peerID, clientName) | ||
|
Check failure on line 486 in services/p2p/server_helpers.go
|
||
|
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. Critical Bug: Incorrect function call parameters The func (s *Server) addConnectedPeer(peerID peer.ID, clientName string, height uint32, blockHash *chainhash.Hash, dataHubURL string)But this call only passes 2 parameters. This will cause a compilation error. Fix: The call should include all required parameters or the function should be updated to match the new signature. |
||
| s.updateDataHubURL(peerID, dataHubURL) | ||
|
Check failure on line 487 in services/p2p/server_helpers.go
|
||
| s.updateBlockHash(peerID, blockHash) | ||
|
Check failure on line 488 in services/p2p/server_helpers.go
|
||
| s.updatePeerHeight(peerID, int32(height)) | ||
|
Check failure on line 489 in services/p2p/server_helpers.go
|
||
|
|
||
| if s.peerRegistry != nil { | ||
| s.peerRegistry.UpdateStorage(peerID, "full") | ||
| } | ||
|
|
||
| if s.syncCoordinator != nil { | ||
| s.syncCoordinator.UpdatePeerInfo(peerID, int32(height), blockHash, dataHubURL) | ||
|
Check failure on line 496 in services/p2p/server_helpers.go
|
||
| } | ||
| } | ||
|
|
||
| func (s *Server) removePeer(peerID peer.ID) { | ||
| if s.peerRegistry != nil { | ||
| // Mark as disconnected before removing | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,124 @@ | ||
| package smoke | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "net/url" | ||
| "testing" | ||
| "time" | ||
|
|
||
| "github.com/bsv-blockchain/teranode/daemon" | ||
| "github.com/bsv-blockchain/teranode/services/blockchain" | ||
| "github.com/bsv-blockchain/teranode/settings" | ||
| "github.com/bsv-blockchain/teranode/test/utils/aerospike" | ||
| "github.com/stretchr/testify/require" | ||
| ) | ||
|
|
||
| func getAerospikeInstance(t *testing.T) *url.URL { | ||
| urlStr, teardownFn, err := aerospike.InitAerospikeContainer() | ||
| require.NoError(t, err, "Failed to setup Aerospike container") | ||
|
|
||
| url, err := url.Parse(urlStr) | ||
| require.NoError(t, err, "Failed to parse UTXO store URL") | ||
|
|
||
| t.Cleanup(func() { | ||
| _ = teardownFn() | ||
| }) | ||
|
|
||
| return url | ||
| } | ||
|
|
||
| func getTestDaemon(t *testing.T, settingsContext string, aerospikeURL *url.URL) *daemon.TestDaemon { | ||
| d := daemon.NewTestDaemon(t, daemon.TestOptions{ | ||
| EnableRPC: true, | ||
| EnableP2P: true, | ||
| EnableValidator: true, | ||
| SettingsContext: settingsContext, | ||
| SettingsOverrideFunc: func(s *settings.Settings) { | ||
| s.P2P.PeerCacheDir = t.TempDir() | ||
| s.UtxoStore.UtxoStore = aerospikeURL | ||
| s.ChainCfgParams.CoinbaseMaturity = 2 | ||
| s.P2P.SyncCoordinatorPeriodicEvaluationInterval = 1 * time.Second | ||
| }, | ||
| FSMState: blockchain.FSMStateRUNNING, | ||
| // EnableFullLogging: true, | ||
| }) | ||
|
|
||
| t.Cleanup(func() { | ||
| d.Stop(t) | ||
| }) | ||
|
|
||
| return d | ||
| } | ||
|
|
||
| func printPeerRegistry(t *testing.T, td *daemon.TestDaemon) { | ||
| registry, err := td.P2PClient.GetPeerRegistry(t.Context()) | ||
| require.NoError(t, err) | ||
|
|
||
| fmt.Printf("\nPeer %s (%s) registry:\n", td.Settings.ClientName, td.Settings.P2P.PeerID) | ||
|
|
||
| for _, peerInfo := range registry { | ||
| fmt.Printf("\tName: %s (%s): Height=%d, BlockHash=%s, DataHubURL=%s", peerInfo.ClientName, peerInfo.ID, peerInfo.Height, peerInfo.BlockHash, peerInfo.DataHubURL) | ||
| } | ||
|
|
||
| fmt.Println() | ||
| fmt.Println() | ||
| } | ||
|
|
||
| // This test creates 2 nodes, and nodeA mines 3 blocks. Then we inject nodeA into nodeB, and nodeB should sync up to nodeA's height. | ||
| func Test_NodeB_Inject_After_NodeA_Mined(t *testing.T) { | ||
| SharedTestLock.Lock() | ||
| defer SharedTestLock.Unlock() | ||
|
|
||
| sharedAerospike := getAerospikeInstance(t) | ||
| nodeA := getTestDaemon(t, "docker.host.teranode1.daemon", sharedAerospike) | ||
| nodeB := getTestDaemon(t, "docker.host.teranode2.daemon", sharedAerospike) | ||
|
|
||
| t.Log(" Creating initial blockchain: [Genesis] -> [Block1] -> [Block2] -> [Block3]") | ||
| coinbaseTx := nodeA.MineToMaturityAndGetSpendableCoinbaseTx(t, nodeA.Ctx) | ||
| t.Logf(" Coinbase transaction available for spending: %s", coinbaseTx.TxIDChainHash().String()) | ||
|
|
||
| // nodeA.InjectPeer(t, nodeB) | ||
|
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. Code cleanup: Commented-out code This test file contains multiple instances of commented-out code (lines 80, 101, 106-111). Per project conventions, commented-out code should be removed before merging. If this code is needed for debugging during development, consider using build tags or test helper flags instead. |
||
| nodeB.InjectPeer(t, nodeA) | ||
|
|
||
| printPeerRegistry(t, nodeB) | ||
|
|
||
| nodeABestBlockHeader, _, err := nodeA.BlockchainClient.GetBestBlockHeader(nodeA.Ctx) | ||
| require.NoError(t, err) | ||
|
|
||
| nodeB.WaitForBlockhash(t, nodeABestBlockHeader.Hash(), 10*time.Second) | ||
|
|
||
| } | ||
|
|
||
| // This test creates 2 nodes, and nodeB injects nodeA before nodeA mines any blocks. Then we mine 3 blocks on nodeA, and nodeB should sync up to nodeA's height. | ||
| func Test_NodeB_Inject_Before_NodeA_Mined(t *testing.T) { | ||
| SharedTestLock.Lock() | ||
| defer SharedTestLock.Unlock() | ||
|
|
||
| sharedAerospike := getAerospikeInstance(t) | ||
| nodeA := getTestDaemon(t, "docker.host.teranode1.daemon", sharedAerospike) | ||
| nodeB := getTestDaemon(t, "docker.host.teranode2.daemon", sharedAerospike) | ||
|
|
||
| // nodeA.InjectPeer(t, nodeB) | ||
| nodeB.InjectPeer(t, nodeA) | ||
|
|
||
| printPeerRegistry(t, nodeB) | ||
|
|
||
| // go func() { | ||
| // for { | ||
| // time.Sleep(5 * time.Second) | ||
| // printPeerRegistry(t, nodeB) | ||
| // } | ||
| // }() | ||
|
|
||
| t.Log(" Creating initial blockchain: [Genesis] -> [Block1] -> [Block2] -> [Block3]") | ||
| coinbaseTx := nodeA.MineToMaturityAndGetSpendableCoinbaseTx(t, nodeA.Ctx) | ||
| t.Logf(" Coinbase transaction available for spending: %s", coinbaseTx.TxIDChainHash().String()) | ||
|
|
||
| printPeerRegistry(t, nodeB) | ||
|
|
||
| nodeABestBlockHeader, _, err := nodeA.BlockchainClient.GetBestBlockHeader(nodeA.Ctx) | ||
| require.NoError(t, err) | ||
|
|
||
| nodeB.WaitForBlockhash(t, nodeABestBlockHeader.Hash(), 26*time.Second) | ||
|
|
||
| } | ||
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.
Test error handling: Use
t.Fatalfinstead oft.ErrorfWhen a timeout occurs in test wait functions, the test should fail immediately and stop execution. Using
t.Errorfcontinues execution after logging the error, which can lead to confusing follow-up failures.Suggested fix:
This matches the pattern used in other wait functions like
WaitForBlock(line 1332).