Skip to content

Commit dd03c7e

Browse files
feat: log from, to and raw data in forktests
1 parent ac2279d commit dd03c7e

File tree

36 files changed

+1937
-192
lines changed

36 files changed

+1937
-192
lines changed

.changeset/public-mugs-agree.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"chainlink-deployments-framework": patch
3+
---
4+
5+
feat: log from, to and raw data in forktests

chain/evm/provider/ctf_anvil_provider.go

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ type CTFAnvilChainProvider struct {
339339

340340
chain *evm.Chain
341341
httpURL string
342-
container testcontainers.Container
342+
Container testcontainers.Container
343343
}
344344

345345
// NewCTFAnvilChainProvider creates a new CTFAnvilChainProvider with the given selector and
@@ -370,23 +370,23 @@ func (p *CTFAnvilChainProvider) Initialize(ctx context.Context) (chain.BlockChai
370370

371371
err := p.config.validate()
372372
if err != nil {
373-
return nil, err
373+
return nil, fmt.Errorf("failed to validate config: %w", err)
374374
}
375375

376376
chainID, err := chainsel.GetChainIDFromSelector(p.selector)
377377
if err != nil {
378-
return nil, err
378+
return nil, fmt.Errorf("failed to get chain id fron selector: %w", err)
379379
}
380380

381381
httpURL, err := p.startContainer(ctx, chainID)
382382
if err != nil {
383-
return nil, err
383+
return nil, fmt.Errorf("failed to start container: %w", err)
384384
}
385385
p.httpURL = httpURL
386386

387387
lggr, err := logger.New()
388388
if err != nil {
389-
return nil, err
389+
return nil, fmt.Errorf("failed to create new logger: %w", err)
390390
}
391391

392392
client, err := rpcclient.NewMultiClient(lggr, rpcclient.RPCConfig{
@@ -418,7 +418,7 @@ func (p *CTFAnvilChainProvider) Initialize(ctx context.Context) (chain.BlockChai
418418
// Use custom deployer transactor generator
419419
deployerKey, err = p.config.DeployerTransactorGen.Generate(chainIDBigInt)
420420
if err != nil {
421-
return nil, err
421+
return nil, fmt.Errorf("failed to generate deployer transactor: %w", err)
422422
}
423423

424424
signHashFunc = func(hash []byte) ([]byte, error) {
@@ -428,12 +428,12 @@ func (p *CTFAnvilChainProvider) Initialize(ctx context.Context) (chain.BlockChai
428428
// Use default Anvil deployer account
429429
deployerPrivateKey, parseErr := crypto.HexToECDSA(anvilTestPrivateKeys[0])
430430
if parseErr != nil {
431-
return nil, parseErr
431+
return nil, fmt.Errorf("failed to parse anvil test private key: %w", parseErr)
432432
}
433433

434434
deployerKey, err = bind.NewKeyedTransactorWithChainID(deployerPrivateKey, chainIDBigInt)
435435
if err != nil {
436-
return nil, err
436+
return nil, fmt.Errorf("failed to create eth transactor: %w", parseErr)
437437
}
438438

439439
signHashFunc = func(hash []byte) ([]byte, error) {
@@ -449,14 +449,14 @@ func (p *CTFAnvilChainProvider) Initialize(ctx context.Context) (chain.BlockChai
449449
// Build additional user transactors from the default Anvil accounts
450450
userTransactors, err := p.getUserTransactors(chainID)
451451
if err != nil {
452-
return nil, err
452+
return nil, fmt.Errorf("failed to get user transactors: %w", err)
453453
}
454454

455455
confirmFunc, err := p.config.ConfirmFunctor.Generate(
456456
ctx, p.selector, client, deployerKey.From,
457457
)
458458
if err != nil {
459-
return nil, err
459+
return nil, fmt.Errorf("failed to generate confirm function: %w", err)
460460
}
461461

462462
p.chain = &evm.Chain{
@@ -515,12 +515,12 @@ func (p *CTFAnvilChainProvider) GetNodeHTTPURL() string {
515515
//
516516
// Returns an error if the container termination fails.
517517
func (p *CTFAnvilChainProvider) Cleanup(ctx context.Context) error {
518-
if p.container != nil {
519-
err := p.container.Terminate(ctx)
518+
if p.Container != nil {
519+
err := p.Container.Terminate(ctx)
520520
if err != nil {
521521
return fmt.Errorf("failed to terminate Anvil container: %w", err)
522522
}
523-
p.container = nil // Clear the reference after successful termination
523+
p.Container = nil // Clear the reference after successful termination
524524
}
525525

526526
return nil
@@ -537,9 +537,7 @@ func (p *CTFAnvilChainProvider) Cleanup(ctx context.Context) error {
537537
//
538538
// Returns the external HTTP URL that can be used to connect to the Anvil node.
539539
func (p *CTFAnvilChainProvider) startContainer(ctx context.Context, chainID string) (string, error) {
540-
var (
541-
attempts = uint(10)
542-
)
540+
attempts := uint(10)
543541

544542
err := framework.DefaultNetwork(p.config.Once)
545543
if err != nil {
@@ -587,7 +585,7 @@ func (p *CTFAnvilChainProvider) startContainer(ctx context.Context, chainID stri
587585
}
588586

589587
// Store container reference for manual cleanup
590-
p.container = output.Container
588+
p.Container = output.Container
591589

592590
// Only register cleanup if T is available (for test cleanup)
593591
if p.config.T != nil {

chain/evm/provider/ctf_anvil_provider_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,7 @@ func TestCTFAnvilChainProvider_Cleanup(t *testing.T) {
452452
require.NoError(t, err, "Cleanup should succeed even when no container exists")
453453

454454
// Verify container is still nil
455-
assert.Nil(t, provider.container, "Container reference should remain nil")
455+
assert.Nil(t, provider.Container, "Container reference should remain nil")
456456

457457
ctx, cancel := context.WithTimeout(t.Context(), 5*time.Minute)
458458
defer cancel()
@@ -461,12 +461,12 @@ func TestCTFAnvilChainProvider_Cleanup(t *testing.T) {
461461
require.NoError(t, err)
462462
require.NotNil(t, blockchain)
463463

464-
assert.NotNil(t, provider.container, "Container reference should be stored after initialization")
464+
assert.NotNil(t, provider.Container, "Container reference should be stored after initialization")
465465

466466
err = provider.Cleanup(ctx)
467467
require.NoError(t, err, "Cleanup should succeed")
468468

469-
assert.Nil(t, provider.container, "Container reference should be cleared after cleanup")
469+
assert.Nil(t, provider.Container, "Container reference should be cleared after cleanup")
470470
})
471471

472472
t.Run("cleanup after initialization with fixed port (T=nil)", func(t *testing.T) {
@@ -504,16 +504,16 @@ func TestCTFAnvilChainProvider_Cleanup(t *testing.T) {
504504
require.NoError(t, err)
505505
require.NotNil(t, blockchain)
506506

507-
assert.NotNil(t, provider.container, "Container reference should be stored after initialization")
507+
assert.NotNil(t, provider.Container, "Container reference should be stored after initialization")
508508

509509
err = provider.Cleanup(ctx)
510510
require.NoError(t, err, "Cleanup should succeed even when T is nil")
511511

512-
assert.Nil(t, provider.container, "Container reference should be cleared after cleanup")
512+
assert.Nil(t, provider.Container, "Container reference should be cleared after cleanup")
513513

514514
// Second cleanup - should be a no-op
515515
err = provider.Cleanup(ctx)
516516
require.NoError(t, err, "Second cleanup should succeed (no-op)")
517-
assert.Nil(t, provider.container, "Container reference should remain nil after second cleanup")
517+
assert.Nil(t, provider.Container, "Container reference should remain nil after second cleanup")
518518
})
519519
}
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
package mcmsv2
2+
3+
import (
4+
"context"
5+
"crypto/ecdsa"
6+
"errors"
7+
"fmt"
8+
"maps"
9+
"time"
10+
11+
"github.com/ethereum/go-ethereum/accounts/abi/bind"
12+
"github.com/ethereum/go-ethereum/common"
13+
gethtypes "github.com/ethereum/go-ethereum/core/types"
14+
"github.com/ethereum/go-ethereum/crypto"
15+
chainsel "github.com/smartcontractkit/chain-selectors"
16+
"github.com/smartcontractkit/chainlink-testing-framework/framework/components/blockchain"
17+
"github.com/smartcontractkit/chainlink-testing-framework/framework/rpc"
18+
"github.com/smartcontractkit/mcms"
19+
mcmssdk "github.com/smartcontractkit/mcms/sdk"
20+
mcmstypes "github.com/smartcontractkit/mcms/types"
21+
22+
"github.com/smartcontractkit/chainlink-deployments-framework/chain"
23+
cldf_evm "github.com/smartcontractkit/chainlink-deployments-framework/chain/evm"
24+
"github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/legacy/cli/mcmsv2/layout"
25+
"github.com/smartcontractkit/chainlink-deployments-framework/pkg/logger"
26+
)
27+
28+
func executeFork(
29+
ctx context.Context, lggr logger.Logger, cfg *cfgv2, testSigner bool,
30+
) error {
31+
family, err := chainsel.GetSelectorFamily(cfg.chainSelector)
32+
if err != nil {
33+
return fmt.Errorf("failed to get selector family: %w", err)
34+
}
35+
if family != chainsel.FamilyEVM {
36+
lggr.Infof("Skipping fork execution: chain selector %d is not EVM. Family is %s", cfg.chainSelector, family)
37+
return nil // don’t fail, just exit cleanly
38+
}
39+
40+
logTransactions(lggr, cfg)
41+
42+
if len(cfg.forkedEnv.ChainConfigs[cfg.chainSelector].HTTPRPCs) == 0 {
43+
return fmt.Errorf("no rpcs loaded in forked environment for chain %d (fork tests require public RPCs)", cfg.chainSelector)
44+
}
45+
46+
// get the chain URL, chain ID and MCM contract address
47+
url := cfg.forkedEnv.ChainConfigs[cfg.chainSelector].HTTPRPCs[0].External
48+
anvilClient := rpc.New(url, nil)
49+
chainID := cfg.forkedEnv.ChainConfigs[cfg.chainSelector].ChainID
50+
mcmsAddr := cfg.proposal.ChainMetadata[mcmstypes.ChainSelector(cfg.chainSelector)].MCMAddress
51+
52+
ctx, cancel := context.WithTimeout(ctx, 300*time.Second)
53+
defer cancel()
54+
if testSigner {
55+
if lerr := layout.SetMCMSigner(
56+
ctx,
57+
lggr,
58+
layout.MCMSLayout,
59+
blockchain.DefaultAnvilPrivateKey,
60+
blockchain.DefaultAnvilPublicKey,
61+
blockchain.DefaultAnvilPublicKey,
62+
url,
63+
chainID,
64+
mcmsAddr,
65+
); lerr != nil {
66+
return fmt.Errorf("failed to set signer: %w", lerr)
67+
}
68+
}
69+
// Override signatures for proposal
70+
privKey, err := crypto.HexToECDSA(blockchain.DefaultAnvilPrivateKey)
71+
if err != nil {
72+
return fmt.Errorf("failed to create private key: %w", err)
73+
}
74+
timelockAddress := common.HexToAddress(cfg.timelockProposal.TimelockAddresses[mcmstypes.ChainSelector(cfg.chainSelector)])
75+
76+
if err = overwriteProposalSignatureWithTestKey(ctx, cfg, privKey); err != nil {
77+
return fmt.Errorf("failed to overwrite proposal signature: %w", err)
78+
}
79+
80+
// set root
81+
// TODO: improve error decoding on the mcms lib for "set root".
82+
err = setRootCommand(ctx, lggr, cfg)
83+
if err != nil {
84+
return fmt.Errorf("MCM.setRoot() - failure: %w", err)
85+
}
86+
lggr.Info("MCM.setRoot() - success")
87+
88+
// TODO: improve error decoding on the mcms lib for "execute chain".
89+
err = executeChainCommand(ctx, lggr, cfg, true)
90+
if err != nil {
91+
return fmt.Errorf("MCM.execute() - failure: %w", err)
92+
}
93+
lggr.Info("MCM.execute() - success")
94+
95+
lggr.Info("Wait for the chain to be mined before executing timelock chain command")
96+
if err = anvilClient.EVMIncreaseTime(uint64(cfg.timelockProposal.Delay.Seconds())); err != nil {
97+
return fmt.Errorf("failed to increase time: %w", err)
98+
}
99+
if err = anvilClient.AnvilMine([]interface{}{1}); err != nil {
100+
return fmt.Errorf("failed to mine block: %w", err)
101+
}
102+
103+
if cfg.timelockProposal.Action != mcmstypes.TimelockActionSchedule {
104+
lggr.Infof("Proposal has type %s, skipping executing timelock chain command", cfg.timelockProposal.Action)
105+
return nil
106+
}
107+
108+
lggr.Info("Executing timelock chain command")
109+
err = timelockExecuteChainCommand(ctx, lggr, cfg)
110+
if err != nil {
111+
lggr.Warnw("Timelock.execute() - failure; starting calling individual ops for debugging", "err", err)
112+
if derr := diagnoseTimelockRevert(ctx, lggr, anvilClient.URL, cfg.chainSelector, cfg.timelockProposal.Operations,
113+
timelockAddress, cfg.env.ExistingAddresses, cfg.proposalCtx); derr != nil { //nolint:staticcheck
114+
lggr.Errorw("Diagnosis results", "err", derr)
115+
return fmt.Errorf("failed to timelock execute chain: %w", derr)
116+
}
117+
118+
return fmt.Errorf("failed to timelock execute chain: %w", err)
119+
}
120+
lggr.Info("Timelock.execute() - success")
121+
122+
return nil
123+
}
124+
125+
// --- helper types and functions ---
126+
127+
func logTransactions(lggr logger.Logger, cfg *cfgv2) {
128+
lggr.Infof("logging transactions sent to forked chain %v", cfg.chainSelector)
129+
130+
chains := maps.Collect(cfg.blockchains.All())
131+
132+
evmChain, ok := chains[cfg.chainSelector].(cldf_evm.Chain)
133+
if !ok {
134+
lggr.Warnf("failed to configure transaction logging for chain selector %v (not evm: %T)", cfg.chainSelector, chains[cfg.chainSelector])
135+
return
136+
}
137+
138+
evmChain.Client = &loggingRpcClient{OnchainClient: evmChain.Client, txOpts: evmChain.DeployerKey, lggr: lggr}
139+
chains[cfg.chainSelector] = evmChain
140+
cfg.blockchains = chain.NewBlockChains(chains)
141+
}
142+
143+
// overwriteProposalSignatureWithTestKey overwrites the proposal's signature with a test key signature.
144+
func overwriteProposalSignatureWithTestKey(ctx context.Context, cfg *cfgv2, testKey *ecdsa.PrivateKey) error {
145+
p := &cfg.proposal
146+
// Override the proposal fields that are used in the signing hash to ensure no errors occur related to those.
147+
p.ValidUntil = uint32(time.Now().Add(5 * time.Hour).Unix()) //nolint:gosec // G404: time-based validity is acceptable for test signatures
148+
p.Signatures = nil
149+
p.OverridePreviousRoot = true
150+
151+
inspector, err := getInspectorFromChainSelector(*cfg)
152+
if err != nil {
153+
return fmt.Errorf("error getting inspector from chain selector: %w", err)
154+
}
155+
signable, errSignable := mcms.NewSignable(p, map[mcmstypes.ChainSelector]mcmssdk.Inspector{
156+
mcmstypes.ChainSelector(cfg.chainSelector): inspector,
157+
})
158+
if errSignable != nil {
159+
return fmt.Errorf("error creating signable: %w", errSignable)
160+
}
161+
162+
signature, err := signable.SignAndAppend(mcms.NewPrivateKeySigner(testKey))
163+
p.Signatures = []mcmstypes.Signature{signature}
164+
if err != nil {
165+
return fmt.Errorf("error creating signable: %w", err)
166+
}
167+
168+
quorumMet, err := signable.CheckQuorum(ctx, mcmstypes.ChainSelector(cfg.chainSelector))
169+
if err != nil {
170+
return fmt.Errorf("failed to check quorum: %w", err)
171+
}
172+
if !quorumMet {
173+
return errors.New("quorum not met")
174+
}
175+
176+
return nil
177+
}
178+
179+
type loggingRpcClient struct {
180+
cldf_evm.OnchainClient
181+
txOpts *bind.TransactOpts
182+
lggr logger.Logger
183+
}
184+
185+
func (c *loggingRpcClient) SendTransaction(ctx context.Context, tx *gethtypes.Transaction) error {
186+
c.lggr.Infow("sending on-chain transaction", "from", c.txOpts.From, "to", tx.To(), "value", tx.Value(),
187+
"data", common.Bytes2Hex(tx.Data()))
188+
189+
return c.OnchainClient.SendTransaction(ctx, tx)
190+
}

0 commit comments

Comments
 (0)