diff --git a/common/client/node.go b/common/client/node.go index ee9ff5eb1c0..082b1b45f99 100644 --- a/common/client/node.go +++ b/common/client/node.go @@ -240,6 +240,10 @@ func (n *node[CHAIN_ID, HEAD, RPC]) verifyChainID(callerCtx context.Context, lgg st := n.State() switch st { + case nodeStateClosed: + // The node is already closed, and any subsequent transition is invalid. + // To make spotting such transitions a bit easier, return the invalid node state. + return nodeStateLen case nodeStateDialed, nodeStateOutOfSync, nodeStateInvalidChainID, nodeStateSyncing: default: panic(fmt.Sprintf("cannot verify node in state %v", st)) diff --git a/common/client/node_fsm.go b/common/client/node_fsm.go index 4cb020893b5..e9105dcc060 100644 --- a/common/client/node_fsm.go +++ b/common/client/node_fsm.go @@ -243,6 +243,9 @@ func (n *node[CHAIN_ID, HEAD, RPC]) transitionToUnreachable(fn func()) { } func (n *node[CHAIN_ID, HEAD, RPC]) declareState(state nodeState) { + if n.State() == nodeStateClosed { + return + } switch state { case nodeStateInvalidChainID: n.declareInvalidChainID() diff --git a/common/client/node_lifecycle_test.go b/common/client/node_lifecycle_test.go index 7c31c085dd3..437bc4a655b 100644 --- a/common/client/node_lifecycle_test.go +++ b/common/client/node_lifecycle_test.go @@ -808,8 +808,8 @@ func TestUnit_NodeLifecycle_unreachableLoop(t *testing.T) { }) defer func() { assert.NoError(t, node.close()) }() - rpc.On("Dial", mock.Anything).Return(nil).Twice() - rpc.On("ChainID", mock.Anything).Return(nodeChainID, nil).Twice() + rpc.On("Dial", mock.Anything).Return(nil) + rpc.On("ChainID", mock.Anything).Return(nodeChainID, nil) rpc.On("IsSyncing", mock.Anything).Return(true, nil) setupRPCForAliveLoop(t, rpc)