Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 9 additions & 69 deletions cmd/workflow/simulate/capabilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,13 @@ package simulate

import (
"context"
"crypto/ecdsa"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"

chaintype "github.com/smartcontractkit/chainlink-common/keystore/corekeys"
corekeys "github.com/smartcontractkit/chainlink-common/keystore/corekeys"
"github.com/smartcontractkit/chainlink-common/keystore/corekeys/ocr2key"
confhttpserver "github.com/smartcontractkit/chainlink-common/pkg/capabilities/v2/actions/confidentialhttp/server"
httpserver "github.com/smartcontractkit/chainlink-common/pkg/capabilities/v2/actions/http/server"
evmserver "github.com/smartcontractkit/chainlink-common/pkg/capabilities/v2/chain-capabilities/evm/server"
consensusserver "github.com/smartcontractkit/chainlink-common/pkg/capabilities/v2/consensus/server"
crontrigger "github.com/smartcontractkit/chainlink-common/pkg/capabilities/v2/triggers/cron/server"
httptrigger "github.com/smartcontractkit/chainlink-common/pkg/capabilities/v2/triggers/http/server"
Expand All @@ -21,79 +18,34 @@ import (
"github.com/smartcontractkit/chainlink/v2/core/capabilities/fakes"
)

type ManualTriggerCapabilitiesConfig struct {
Clients map[uint64]*ethclient.Client
Forwarders map[uint64]common.Address
PrivateKey *ecdsa.PrivateKey
}

// ManualTriggers holds chain-agnostic trigger services used in simulation.
type ManualTriggers struct {
ManualCronTrigger *fakes.ManualCronTriggerService
ManualHTTPTrigger *fakes.ManualHTTPTriggerService
ManualEVMChains map[uint64]*fakes.FakeEVMChain
}

func NewManualTriggerCapabilities(
ctx context.Context,
lggr logger.Logger,
registry *capabilities.Registry,
cfg ManualTriggerCapabilitiesConfig,
dryRunChainWrite bool,
limits *SimulationLimits,
) (*ManualTriggers, error) {
// Cron
// NewManualTriggerCapabilities creates and registers cron and HTTP trigger capabilities.
// These are chain-agnostic and shared across all chain types.
func NewManualTriggerCapabilities(ctx context.Context, lggr logger.Logger, registry *capabilities.Registry) (*ManualTriggers, error) {
manualCronTrigger := fakes.NewManualCronTriggerService(lggr)
manualCronTriggerServer := crontrigger.NewCronServer(manualCronTrigger)
if err := registry.Add(ctx, manualCronTriggerServer); err != nil {
return nil, err
}

// HTTP
manualHTTPTrigger := fakes.NewManualHTTPTriggerService(lggr)
manualHTTPTriggerServer := httptrigger.NewHTTPServer(manualHTTPTrigger)
if err := registry.Add(ctx, manualHTTPTriggerServer); err != nil {
return nil, err
}

// EVM
evmChains := make(map[uint64]*fakes.FakeEVMChain)
for sel, client := range cfg.Clients {
fwd, ok := cfg.Forwarders[sel]
if !ok {
lggr.Infow("Forwarder not found for chain", "selector", sel)
continue
}

evm := fakes.NewFakeEvmChain(
lggr,
client,
cfg.PrivateKey,
fwd,
sel,
dryRunChainWrite,
)

// Wrap with limits enforcement if limits are enabled
var evmCap evmserver.ClientCapability = evm
if limits != nil {
evmCap = NewLimitedEVMChain(evm, limits)
}

evmServer := evmserver.NewClientServer(evmCap)
if err := registry.Add(ctx, evmServer); err != nil {
return nil, err
}

evmChains[sel] = evm
}

return &ManualTriggers{
ManualCronTrigger: manualCronTrigger,
ManualHTTPTrigger: manualHTTPTrigger,
ManualEVMChains: evmChains,
}, nil
}

// Start starts cron and HTTP trigger services.
func (m *ManualTriggers) Start(ctx context.Context) error {
err := m.ManualCronTrigger.Start(ctx)
if err != nil {
Expand All @@ -105,16 +57,10 @@ func (m *ManualTriggers) Start(ctx context.Context) error {
return err
}

// Start all configured EVM chains
for _, evm := range m.ManualEVMChains {
if err := evm.Start(ctx); err != nil {
return err
}
}

return nil
}

// Close closes cron and HTTP trigger services.
func (m *ManualTriggers) Close() error {
err := m.ManualCronTrigger.Close()
if err != nil {
Expand All @@ -126,16 +72,10 @@ func (m *ManualTriggers) Close() error {
return err
}

// Close all EVM chains
for _, evm := range m.ManualEVMChains {
if err := evm.Close(); err != nil {
return err
}
}
return nil
}

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

Expand All @@ -144,7 +84,7 @@ func NewFakeActionCapabilities(ctx context.Context, lggr logger.Logger, registry
nSigners := 4
signers := []ocr2key.KeyBundle{}
for i := 0; i < nSigners; i++ {
signer := ocr2key.MustNewInsecure(fakes.SeedForKeys(), chaintype.EVM)
signer := ocr2key.MustNewInsecure(fakes.SeedForKeys(), corekeys.EVM)
lggr.Infow("Generated new consensus signer", "address", common.BytesToAddress(signer.PublicKey()))
signers = append(signers, signer)
}
Expand Down
80 changes: 80 additions & 0 deletions cmd/workflow/simulate/chain/aptos/capabilities.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package aptos

import (
"context"
"fmt"

"github.com/aptos-labs/aptos-go-sdk"
"github.com/aptos-labs/aptos-go-sdk/crypto"

Check failure on line 9 in cmd/workflow/simulate/chain/aptos/capabilities.go

View workflow job for this annotation

GitHub Actions / ci-lint

File is not properly formatted (gci)
aptosserver "github.com/smartcontractkit/chainlink-common/pkg/capabilities/v2/chain-capabilities/aptos/server"
"github.com/smartcontractkit/chainlink-common/pkg/logger"
"github.com/smartcontractkit/chainlink/v2/core/capabilities"

aptosfakes "github.com/smartcontractkit/chainlink-aptos/fakes"
)

// AptosChainCapabilities holds the per-selector FakeAptosChain instances
// created for simulation.
type AptosChainCapabilities struct {
AptosChains map[uint64]*aptosfakes.FakeAptosChain
}

// NewAptosChainCapabilities builds FakeAptosChain instances for every
// (selector -> client) pair, optionally wraps them with LimitedAptosChain,
// and registers each with the capability registry.
func NewAptosChainCapabilities(
ctx context.Context,
lggr logger.Logger,
registry *capabilities.Registry,
clients map[uint64]aptosfakes.AptosClient,
forwarders map[uint64]string,
privateKey *crypto.Ed25519PrivateKey,
dryRunChainWrite bool,
limits AptosChainLimits,
) (*AptosChainCapabilities, error) {
chains := make(map[uint64]*aptosfakes.FakeAptosChain)
for sel, client := range clients {
fwdStr, ok := forwarders[sel]
if !ok {
lggr.Infow("Forwarder not found for chain", "selector", sel)
continue
}
var fwd aptos.AccountAddress
if err := fwd.ParseStringRelaxed(fwdStr); err != nil {
return nil, fmt.Errorf("parse forwarder for selector %d: %w", sel, err)
}
fc, err := aptosfakes.NewFakeAptosChain(lggr, client, privateKey, fwd, sel, dryRunChainWrite)
if err != nil {
return nil, fmt.Errorf("new FakeAptosChain for selector %d: %w", sel, err)
}
var capability aptosserver.ClientCapability = fc
if limits != nil {
capability = NewLimitedAptosChain(fc, limits)
}
server := aptosserver.NewClientServer(capability)
if err := registry.Add(ctx, server); err != nil {
return nil, fmt.Errorf("register aptos capability for selector %d: %w", sel, err)
}
chains[sel] = fc
}
return &AptosChainCapabilities{AptosChains: chains}, nil
}

func (c *AptosChainCapabilities) Start(ctx context.Context) error {
for _, fc := range c.AptosChains {
if err := fc.Start(ctx); err != nil {
return err
}
}
return nil
}

func (c *AptosChainCapabilities) Close() error {
for _, fc := range c.AptosChains {
if err := fc.Close(); err != nil {
return err
}
}
return nil
}
169 changes: 169 additions & 0 deletions cmd/workflow/simulate/chain/aptos/chaintype.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
package aptos

import (
"context"
"encoding/hex"
"fmt"
"strconv"
"strings"

"github.com/aptos-labs/aptos-go-sdk/crypto"
"github.com/rs/zerolog"
"github.com/spf13/viper"

"github.com/smartcontractkit/chainlink-common/pkg/services"

Check failure on line 14 in cmd/workflow/simulate/chain/aptos/chaintype.go

View workflow job for this annotation

GitHub Actions / ci-lint

File is not properly formatted (gci)

aptosfakes "github.com/smartcontractkit/chainlink-aptos/fakes"

"github.com/smartcontractkit/cre-cli/cmd/workflow/simulate/chain"
"github.com/smartcontractkit/cre-cli/internal/settings"
"github.com/smartcontractkit/cre-cli/internal/ui"
)

const defaultSentinelAptosSeed = "0000000000000000000000000000000000000000000000000000000000000001"

func init() {
chain.Register("aptos", func(lggr *zerolog.Logger) chain.ChainType {
return &AptosChainType{log: lggr}
}, nil)
}

// AptosChainType implements chain.ChainType for Aptos.
type AptosChainType struct {
log *zerolog.Logger
aptosChains *AptosChainCapabilities
}

var _ chain.ChainType = (*AptosChainType)(nil)

func (ct *AptosChainType) Name() string { return "aptos" }
func (ct *AptosChainType) SupportedChains() []chain.ChainConfig { return SupportedChains }

func (ct *AptosChainType) ResolveClients(v *viper.Viper) (chain.ResolvedChains, error) {
clients := make(map[uint64]chain.ChainClient)
forwarders := make(map[uint64]string)
for _, c := range SupportedChains {
name, err := settings.GetChainNameByChainSelector(c.Selector)
if err != nil {
ct.log.Error().Msgf("Invalid Aptos chain selector %d; skipping", c.Selector)
continue
}
rpcURL, err := settings.GetRpcUrlSettings(v, name)
if err != nil || strings.TrimSpace(rpcURL) == "" {
ct.log.Debug().Msgf("RPC not provided for %s; skipping", name)
continue
}
ct.log.Debug().Msgf("Using RPC for %s: %s", name, chain.RedactURL(rpcURL))
client, err := aptosfakes.NewAptosClient(rpcURL)
if err != nil {
ui.Warning(fmt.Sprintf("Failed to build Aptos client for %s: %v", name, err))
continue
}
clients[c.Selector] = client
if strings.TrimSpace(c.Forwarder) != "" {
forwarders[c.Selector] = c.Forwarder
}
}
return chain.ResolvedChains{Clients: clients, Forwarders: forwarders}, nil
}

func (ct *AptosChainType) ResolveKey(s *settings.Settings, broadcast bool) (interface{}, error) {
seed := strings.TrimPrefix(strings.ToLower(strings.TrimSpace(s.User.AptosPrivateKey)), "0x")
bytes, err := hex.DecodeString(seed)
if err != nil || len(bytes) != 32 {
if broadcast {
if err != nil {
return nil, fmt.Errorf("failed to parse private key, required to broadcast. Please check CRE_APTOS_PRIVATE_KEY in your .env file or system environment: %w", err)
}
return nil, fmt.Errorf("CRE_APTOS_PRIVATE_KEY must be 32 hex bytes (64 chars); got len=%d", len(bytes))
}
bytes, _ = hex.DecodeString(defaultSentinelAptosSeed)
ui.Warning("Using default Aptos private key for dry-run simulation. Set CRE_APTOS_PRIVATE_KEY to broadcast.")
}
sentinel, _ := hex.DecodeString(defaultSentinelAptosSeed)
if broadcast && hex.EncodeToString(bytes) == hex.EncodeToString(sentinel) {
return nil, fmt.Errorf("CRE_APTOS_PRIVATE_KEY must not be the sentinel seed under --broadcast")
}
k := &crypto.Ed25519PrivateKey{}
if err := k.FromBytes(bytes); err != nil {
return nil, fmt.Errorf("build Ed25519 key: %w", err)
}
return k, nil
}

func (ct *AptosChainType) ResolveTriggerData(_ context.Context, _ uint64, _ chain.TriggerParams) (interface{}, error) {
return nil, fmt.Errorf("aptos: no trigger surface")
}

func (ct *AptosChainType) RegisterCapabilities(ctx context.Context, cfg chain.CapabilityConfig) ([]services.Service, error) {
typedClients := make(map[uint64]aptosfakes.AptosClient, len(cfg.Clients))
for sel, c := range cfg.Clients {
ac, ok := c.(aptosfakes.AptosClient)
if !ok {
return nil, fmt.Errorf("aptos: client for selector %d is not aptosfakes.AptosClient (got %T)", sel, c)
}
typedClients[sel] = ac
}
var pk *crypto.Ed25519PrivateKey
if cfg.PrivateKey != nil {
var ok bool
pk, ok = cfg.PrivateKey.(*crypto.Ed25519PrivateKey)
if !ok {
return nil, fmt.Errorf("aptos: private key is not *crypto.Ed25519PrivateKey (got %T)", cfg.PrivateKey)
}
}
var lim AptosChainLimits
if cfg.Limits != nil {
al, ok := cfg.Limits.(AptosChainLimits)
if !ok {
return nil, fmt.Errorf("aptos: limits does not implement AptosChainLimits (got %T)", cfg.Limits)
}
lim = al
}
caps, err := NewAptosChainCapabilities(ctx, cfg.Logger, cfg.Registry, typedClients, cfg.Forwarders, pk, !cfg.Broadcast, lim)
if err != nil {
return nil, err
}
if err := caps.Start(ctx); err != nil {
return nil, fmt.Errorf("aptos: failed to start: %w", err)
}
ct.aptosChains = caps
out := make([]services.Service, 0, len(caps.AptosChains))
for _, fc := range caps.AptosChains {
out = append(out, fc)
}
return out, nil
}

func (ct *AptosChainType) ExecuteTrigger(_ context.Context, _ uint64, _ string, _ interface{}) error {
return fmt.Errorf("aptos: no trigger surface")
}

func (ct *AptosChainType) HasSelector(selector uint64) bool {
if ct.aptosChains == nil {
return false
}
return ct.aptosChains.AptosChains[selector] != nil
}

func (ct *AptosChainType) ParseTriggerChainSelector(triggerID string) (uint64, bool) {
const prefix = "aptos:ChainSelector:"
const suffix = "@1.0.0"
if !strings.HasPrefix(triggerID, prefix) || !strings.HasSuffix(triggerID, suffix) {
return 0, false
}
mid := triggerID[len(prefix) : len(triggerID)-len(suffix)]
sel, err := strconv.ParseUint(mid, 10, 64)
if err != nil {
return 0, false
}
return sel, true
}

func (ct *AptosChainType) RunHealthCheck(resolved chain.ResolvedChains) error {
return RunRPCHealthCheck(resolved.Clients, resolved.ExperimentalSelectors)
}

func (ct *AptosChainType) CollectCLIInputs(_ *viper.Viper) map[string]string {
return map[string]string{}
}
Loading
Loading