Skip to content

Commit 8817bb8

Browse files
authored
CCIP - ChainAccessor add test for Sync call state persistence across restarts (#1552)
1 parent 04a897d commit 8817bb8

File tree

2 files changed

+126
-1
lines changed

2 files changed

+126
-1
lines changed

pkg/loop/ccip_provider_test.go

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package loop_test
2+
3+
import (
4+
"os/exec"
5+
"testing"
6+
"time"
7+
8+
"github.com/google/uuid"
9+
"github.com/stretchr/testify/assert"
10+
"github.com/stretchr/testify/require"
11+
"go.uber.org/zap/zapcore"
12+
"go.uber.org/zap/zaptest/observer"
13+
14+
"github.com/smartcontractkit/chainlink-common/pkg/logger"
15+
"github.com/smartcontractkit/chainlink-common/pkg/loop"
16+
keystoretest "github.com/smartcontractkit/chainlink-common/pkg/loop/internal/core/services/keystore/test"
17+
"github.com/smartcontractkit/chainlink-common/pkg/loop/internal/goplugin"
18+
ccipocr3client "github.com/smartcontractkit/chainlink-common/pkg/loop/internal/relayer/pluginprovider/ext/ccipocr3"
19+
"github.com/smartcontractkit/chainlink-common/pkg/loop/internal/test"
20+
"github.com/smartcontractkit/chainlink-common/pkg/services/servicetest"
21+
"github.com/smartcontractkit/chainlink-common/pkg/types"
22+
"github.com/smartcontractkit/chainlink-common/pkg/types/ccipocr3"
23+
)
24+
25+
// TestCCIPSyncPersistence tests the persistence of sync requests across relayer restarts. This test is testing
26+
// logic from chainlink-common/pkg/loop/internal/relayer/pluginprovider/ext/ccipocr3/chainaccessor.go but we need
27+
// the full CCIPProvider and Relayer to properly test the persistence across restarts.
28+
func TestCCIPChainAccessorSyncPersistence(t *testing.T) {
29+
t.Parallel()
30+
31+
ctx := t.Context()
32+
33+
// Observed logger for confirming PIDs
34+
lggr, logs := logger.TestObserved(t, zapcore.DebugLevel)
35+
36+
// Relayer service (client side)
37+
relayerService := loop.NewRelayerService(
38+
lggr,
39+
loop.GRPCOpts{},
40+
func() *exec.Cmd {
41+
return NewHelperProcessCommand(loop.PluginRelayerName, false, 0)
42+
},
43+
test.ConfigTOML,
44+
keystoretest.Keystore,
45+
keystoretest.Keystore,
46+
nil,
47+
)
48+
49+
// Kill hook is defined on the relayer client (service) because the client spawns the server child process
50+
hook := relayerService.XXXTestHook()
51+
servicetest.Run(t, relayerService)
52+
53+
// Create CCIPProvider client and issue first Sync() call. This client should persist and reattach
54+
// to the new server after the kill hook is run.
55+
ccipProvider, err := relayerService.NewCCIPProvider(ctx, types.CCIPProviderArgs{
56+
ExternalJobID: uuid.New(),
57+
ContractReaderConfig: []byte("asdf"),
58+
ChainWriterConfig: []byte("asdf"),
59+
OffRampAddress: "0x1234123412341234123412341234123412341234",
60+
PluginType: 0,
61+
})
62+
require.NoError(t, err)
63+
require.NotNil(t, ccipProvider)
64+
65+
firstContractNameToSync := "OnRamp"
66+
firstContractAddressToSync := ccipocr3.UnknownAddress("0x123412341234")
67+
68+
// Perform first Sync() call
69+
err = ccipProvider.ChainAccessor().Sync(ctx, firstContractNameToSync, firstContractAddressToSync)
70+
require.NoError(t, err)
71+
72+
// Confirm first sync call was stored in the c.syncs map
73+
ccipProviderClient, ok := ccipProvider.(*ccipocr3client.CCIPProviderClient)
74+
require.True(t, ok)
75+
firstSyncs := ccipProviderClient.GetSyncRequests()
76+
require.Len(t, firstSyncs, 1, "Should have one sync request in ChainAccessorClient c.syncs")
77+
78+
// Capture initial server side process ID before kill
79+
initialPID := extractLatestPluginPID(logs)
80+
require.NotZero(t, initialPID)
81+
82+
// Kill the server process (RelayerService should auto-restart it)
83+
hook.Kill()
84+
85+
// Give some time for the keep alive to kick in
86+
time.Sleep(2 * goplugin.KeepAliveTickDuration)
87+
88+
// Capture process ID again after restart and verify it's different
89+
restartedPID := extractLatestPluginPID(logs)
90+
require.NotZero(t, restartedPID)
91+
assert.NotEqual(t, initialPID, restartedPID, "Server should have restarted with different process ID")
92+
93+
// Verify new Sync() call still works and now the client map should have two
94+
secondContractNameToSync := "OffRamp"
95+
newContractAddress := ccipocr3.UnknownAddress("0x567856785678")
96+
err = ccipProvider.ChainAccessor().Sync(ctx, secondContractNameToSync, newContractAddress)
97+
require.NoError(t, err)
98+
finalSyncs := ccipProviderClient.GetSyncRequests()
99+
require.Len(t, finalSyncs, 2, "Should have both first and second sync requests in client memory")
100+
101+
// Verify first sync entry persisted through restart
102+
assert.Contains(t, finalSyncs, firstContractNameToSync)
103+
assert.Equal(t, []byte(firstContractAddressToSync), finalSyncs[firstContractNameToSync])
104+
105+
// Verify second sync entry was added
106+
assert.Contains(t, finalSyncs, secondContractNameToSync)
107+
assert.Equal(t, []byte(newContractAddress), finalSyncs[secondContractNameToSync])
108+
}
109+
110+
// extractLatestPluginPID extracts the most recent plugin process ID from the logs using the `plugin started` log
111+
func extractLatestPluginPID(logs *observer.ObservedLogs) int {
112+
var latestPID int
113+
for _, entry := range logs.All() {
114+
if entry.Message == "plugin started" {
115+
for _, field := range entry.Context {
116+
if field.Key == "pid" {
117+
latestPID = int(field.Integer)
118+
}
119+
}
120+
}
121+
}
122+
123+
return latestPID
124+
}

pkg/loop/internal/relayer/pluginprovider/ext/ccipocr3/chainaccessor.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ func NewChainAccessorClient(broker *net.BrokerExt, cc grpc.ClientConnInterface)
3232
return &ChainAccessorClient{
3333
BrokerExt: broker,
3434
grpc: ccipocr3pb.NewChainAccessorClient(cc),
35+
syncs: make(map[string]ccipocr3.UnknownAddress),
3536
}
3637
}
3738

@@ -95,7 +96,7 @@ func (c *ChainAccessorClient) Sync(ctx context.Context, contractName string, con
9596
_, err := c.grpc.Sync(ctx, req)
9697

9798
// If grpc call succeeded, store the most recent address for this given contract address.
98-
if err != nil {
99+
if err == nil {
99100
c.mu.Lock()
100101
c.syncs[contractName] = contractAddress
101102
c.mu.Unlock()

0 commit comments

Comments
 (0)