Skip to content

Commit 55d42cb

Browse files
committed
Modularise EVM chain family in simulate package
Extract EVM-specific simulation code into a chainfamily/evm/ subpackage with an adapter pattern. Introduce a chainfamily.Registry for pluggable chain families instead of hardcoded EVM logic. Simplify simulate.go by removing inline EVM client setup, RPC health checks, and chain-specific flag handling. Includes simulation limits enforcement via limitedEVMChain wrapper in the adapter, and proper service lifecycle management to avoid double-close of chain runtimes.
1 parent caf7908 commit 55d42cb

15 files changed

Lines changed: 1593 additions & 831 deletions

cmd/workflow/simulate/capabilities.go

Lines changed: 4 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,13 @@ package simulate
22

33
import (
44
"context"
5-
"crypto/ecdsa"
65

76
"github.com/ethereum/go-ethereum/common"
8-
"github.com/ethereum/go-ethereum/ethclient"
97

108
chaintype "github.com/smartcontractkit/chainlink-common/keystore/corekeys"
119
"github.com/smartcontractkit/chainlink-common/keystore/corekeys/ocr2key"
1210
confhttpserver "github.com/smartcontractkit/chainlink-common/pkg/capabilities/v2/actions/confidentialhttp/server"
1311
httpserver "github.com/smartcontractkit/chainlink-common/pkg/capabilities/v2/actions/http/server"
14-
evmserver "github.com/smartcontractkit/chainlink-common/pkg/capabilities/v2/chain-capabilities/evm/server"
1512
consensusserver "github.com/smartcontractkit/chainlink-common/pkg/capabilities/v2/consensus/server"
1613
crontrigger "github.com/smartcontractkit/chainlink-common/pkg/capabilities/v2/triggers/cron/server"
1714
httptrigger "github.com/smartcontractkit/chainlink-common/pkg/capabilities/v2/triggers/http/server"
@@ -21,25 +18,18 @@ import (
2118
"github.com/smartcontractkit/chainlink/v2/core/capabilities/fakes"
2219
)
2320

24-
type ManualTriggerCapabilitiesConfig struct {
25-
Clients map[uint64]*ethclient.Client
26-
Forwarders map[uint64]common.Address
27-
PrivateKey *ecdsa.PrivateKey
28-
}
29-
21+
// ManualTriggers holds chain-agnostic trigger capabilities (cron + HTTP).
22+
// Chain-specific trigger capabilities are managed by their respective chain family adapters.
3023
type ManualTriggers struct {
3124
ManualCronTrigger *fakes.ManualCronTriggerService
3225
ManualHTTPTrigger *fakes.ManualHTTPTriggerService
33-
ManualEVMChains map[uint64]*fakes.FakeEVMChain
3426
}
3527

28+
// NewManualTriggerCapabilities creates and registers chain-agnostic trigger capabilities.
3629
func NewManualTriggerCapabilities(
3730
ctx context.Context,
3831
lggr logger.Logger,
3932
registry *capabilities.Registry,
40-
cfg ManualTriggerCapabilitiesConfig,
41-
dryRunChainWrite bool,
42-
limits *SimulationLimits,
4333
) (*ManualTriggers, error) {
4434
// Cron
4535
manualCronTrigger := fakes.NewManualCronTriggerService(lggr)
@@ -55,42 +45,9 @@ func NewManualTriggerCapabilities(
5545
return nil, err
5646
}
5747

58-
// EVM
59-
evmChains := make(map[uint64]*fakes.FakeEVMChain)
60-
for sel, client := range cfg.Clients {
61-
fwd, ok := cfg.Forwarders[sel]
62-
if !ok {
63-
lggr.Infow("Forwarder not found for chain", "selector", sel)
64-
continue
65-
}
66-
67-
evm := fakes.NewFakeEvmChain(
68-
lggr,
69-
client,
70-
cfg.PrivateKey,
71-
fwd,
72-
sel,
73-
dryRunChainWrite,
74-
)
75-
76-
// Wrap with limits enforcement if limits are enabled
77-
var evmCap evmserver.ClientCapability = evm
78-
if limits != nil {
79-
evmCap = NewLimitedEVMChain(evm, limits)
80-
}
81-
82-
evmServer := evmserver.NewClientServer(evmCap)
83-
if err := registry.Add(ctx, evmServer); err != nil {
84-
return nil, err
85-
}
86-
87-
evmChains[sel] = evm
88-
}
89-
9048
return &ManualTriggers{
9149
ManualCronTrigger: manualCronTrigger,
9250
ManualHTTPTrigger: manualHTTPTrigger,
93-
ManualEVMChains: evmChains,
9451
}, nil
9552
}
9653

@@ -105,13 +62,6 @@ func (m *ManualTriggers) Start(ctx context.Context) error {
10562
return err
10663
}
10764

108-
// Start all configured EVM chains
109-
for _, evm := range m.ManualEVMChains {
110-
if err := evm.Start(ctx); err != nil {
111-
return err
112-
}
113-
}
114-
11565
return nil
11666
}
11767

@@ -126,16 +76,10 @@ func (m *ManualTriggers) Close() error {
12676
return err
12777
}
12878

129-
// Close all EVM chains
130-
for _, evm := range m.ManualEVMChains {
131-
if err := evm.Close(); err != nil {
132-
return err
133-
}
134-
}
13579
return nil
13680
}
13781

138-
// NewFakeCapabilities builds faked capabilities, then registers them with the capability registry.
82+
// NewFakeActionCapabilities builds faked capabilities, then registers them with the capability registry.
13983
func NewFakeActionCapabilities(ctx context.Context, lggr logger.Logger, registry *capabilities.Registry, secretsPath string, limits *SimulationLimits) ([]services.Service, error) {
14084
caps := make([]services.Service, 0)
14185

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
// Package chainfamily defines the adapter interface for multi-chain simulation support.
2+
// Each chain family (EVM, Aptos, Solana, etc.) implements the Adapter interface and
3+
// self-registers via init(). The core simulate flow iterates registered adapters
4+
// instead of hardcoding chain-specific logic.
5+
package chainfamily
6+
7+
import (
8+
"context"
9+
"io"
10+
11+
"github.com/spf13/cobra"
12+
13+
"github.com/smartcontractkit/chainlink-common/pkg/logger"
14+
"github.com/smartcontractkit/chainlink-common/pkg/services"
15+
"github.com/smartcontractkit/chainlink/v2/core/capabilities"
16+
)
17+
18+
// ChainSelector is a uint64 chain selector from the chain-selectors package.
19+
type ChainSelector = uint64
20+
21+
// SupportedChain describes a chain known to the simulator with its default config.
22+
type SupportedChain struct {
23+
Selector ChainSelector
24+
Forwarder string // hex address, family-specific format
25+
}
26+
27+
// ExperimentalChainConfig holds per-chain overrides from experimental config,
28+
// already filtered to a specific adapter's family by the core.
29+
type ExperimentalChainConfig struct {
30+
ChainSelector ChainSelector
31+
RPCURL string
32+
Forwarder string
33+
}
34+
35+
// SetupConfig contains everything an adapter needs to set itself up.
36+
// The core populates this once; no viper dependency leaks into adapters.
37+
// Capability registration happens separately via ChainRuntime.RegisterCapabilities.
38+
type SetupConfig struct {
39+
Logger logger.Logger
40+
41+
// RPCURLs maps chain name -> RPC URL, scoped to this adapter's family.
42+
RPCURLs map[string]string
43+
44+
// ExperimentalChains filtered to THIS adapter's family by the core.
45+
ExperimentalChains []ExperimentalChainConfig
46+
47+
// DryRun is true when chain writes should be simulated (default behavior).
48+
DryRun bool
49+
50+
// SecretsPath is the path to the secrets file, if any.
51+
SecretsPath string
52+
53+
// FlagValues provides access to family-specific flag values registered via AddFlags.
54+
FlagValues func(name string) string
55+
56+
// ChainWriteReportSizeLimit is the max report size in bytes (0 = no limit).
57+
ChainWriteReportSizeLimit int
58+
59+
// ChainWriteGasLimit is the max gas limit for chain writes (0 = no limit).
60+
ChainWriteGasLimit uint64
61+
}
62+
63+
// TriggerRequest contains everything an adapter needs to build a trigger function.
64+
type TriggerRequest struct {
65+
TriggerID string // e.g. "evm:ChainSelector:123@1.0.0 LogTrigger"
66+
TriggerRegistrationID string
67+
ChainSelector ChainSelector
68+
PromptUser bool // true = interactive mode
69+
FlagValues func(name string) string
70+
}
71+
72+
// ChainRuntime is the live handle returned by Setup(). The adapter owns its
73+
// internal state (clients, fake chains) behind this interface. The core code
74+
// never inspects chain internals - it just calls BuildTriggerFunc and Close.
75+
type ChainRuntime interface {
76+
// RegisterCapabilities registers chain-specific capabilities with the
77+
// capability registry. Called during simulator initialization when the
78+
// registry is available, not during Setup().
79+
RegisterCapabilities(ctx context.Context, registry *capabilities.Registry) error
80+
81+
// BuildTriggerFunc creates a function that fires the given chain trigger.
82+
BuildTriggerFunc(ctx context.Context, req TriggerRequest) (func() error, error)
83+
84+
// OwnedSelectors returns all chain selectors this runtime manages.
85+
// Used for trigger routing without relying on GetSelectorFamily.
86+
OwnedSelectors() []ChainSelector
87+
88+
// Services returns all services managed by this runtime for lifecycle management.
89+
Services() []services.Service
90+
91+
// Close releases all resources (RPC clients, fake chains, etc.)
92+
io.Closer
93+
}
94+
95+
// Adapter is the contract each chain family implements. Adapters self-register
96+
// via init() + Register().
97+
type Adapter interface {
98+
// Family returns the chain family identifier (e.g. "evm", "aptos", "solana").
99+
Family() string
100+
101+
// SupportedChains returns default chain configs for this family.
102+
SupportedChains() []SupportedChain
103+
104+
// AddFlags registers family-specific CLI flags on the simulate command.
105+
AddFlags(cmd *cobra.Command)
106+
107+
// Setup dials RPCs, health-checks, and returns a ChainRuntime.
108+
// Returns (nil, nil) if no chains of this family are configured.
109+
Setup(ctx context.Context, cfg SetupConfig) (ChainRuntime, error)
110+
}

0 commit comments

Comments
 (0)