Skip to content
Draft
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
32 changes: 32 additions & 0 deletions .github/workflows/cre-system-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ jobs:

# Add list of tests with certain topologies
PER_TEST_TOPOLOGIES_JSON=${PER_TEST_TOPOLOGIES_JSON:-'{
"Test_CRE_V2_Aptos_Suite": [
{"topology":"workflow-gateway-aptos","configs":"configs/workflow-gateway-don-aptos.toml"}
],
"Test_CRE_V2_Solana_Suite": [
{"topology":"workflow","configs":"configs/workflow-don-solana.toml"}
],
Expand Down Expand Up @@ -213,6 +216,35 @@ jobs:
chmod +x bin/ctf
echo "::endgroup::"

- name: Install Aptos CLI
if: ${{ matrix.tests.test_name == 'Test_CRE_V2_Aptos_Suite' }}
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
APTOS_CLI_TAG: "aptos-cli-v7.8.0"
run: |
echo "::startgroup::Install Aptos CLI"
bin_dir="$HOME/.local/bin"
mkdir -p "$bin_dir"

gh release download "${APTOS_CLI_TAG}" \
--pattern "aptos-cli-*-Ubuntu-24.04-x86_64.zip" \
--clobber \
--repo aptos-labs/aptos-core \
-O aptos-cli.zip

unzip -o aptos-cli.zip -d aptos-cli-extract >/dev/null
aptos_path="$(find aptos-cli-extract -type f -name aptos | head -n1)"
if [[ -z "$aptos_path" ]]; then
echo "failed to locate aptos binary in release archive"
exit 1
fi

install -m 0755 "$aptos_path" "$bin_dir/aptos"
echo "$bin_dir" >> "$GITHUB_PATH"
"$bin_dir/aptos" --version
echo "::endgroup::"

- name: Start local CRE${{ matrix.tests.cre_version }}
shell: bash
id: start-local-cre
Expand Down
20 changes: 20 additions & 0 deletions core/capabilities/fakes/register.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package fakes

import (
"context"
"fmt"

"github.com/smartcontractkit/chainlink-common/pkg/logger"
"github.com/smartcontractkit/chainlink-common/pkg/types/core"
)

const EnableFakeStreamsTriggerEnvVar = "CL_ENABLE_FAKE_STREAMS_TRIGGER"

func RegisterFakeStreamsTrigger(ctx context.Context, lggr logger.Logger, registry core.CapabilitiesRegistry, nSigners int) (*fakeStreamsTrigger, error) {
trigger := NewFakeStreamsTrigger(lggr, nSigners)
if err := registry.Add(ctx, trigger); err != nil {
return nil, fmt.Errorf("add fake streams trigger: %w", err)
}

return trigger, nil
}
33 changes: 33 additions & 0 deletions core/capabilities/fakes/register_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package fakes

import (
"testing"

"github.com/stretchr/testify/require"

"github.com/smartcontractkit/chainlink-common/pkg/logger"
corecaps "github.com/smartcontractkit/chainlink/v2/core/capabilities"
)

func TestRegisterFakeStreamsTrigger(t *testing.T) {
registry := corecaps.NewRegistry(logger.Test(t))

trigger, err := RegisterFakeStreamsTrigger(t.Context(), logger.Test(t), registry, 4)
require.NoError(t, err)
require.NotNil(t, trigger)

capability, err := registry.Get(t.Context(), "streams-trigger@1.0.0")
require.NoError(t, err)

info, err := capability.Info(t.Context())
require.NoError(t, err)
require.Equal(t, "streams-trigger@1.0.0", info.ID)
}

func TestNewFakeStreamsTrigger_UsesDeterministicSigners(t *testing.T) {
triggerA := NewFakeStreamsTrigger(logger.Test(t), 4)
triggerB := NewFakeStreamsTrigger(logger.Test(t), 4)

require.Equal(t, triggerA.meta.Signers, triggerB.meta.Signers)
require.Equal(t, triggerA.meta.MinRequiredSignatures, triggerB.meta.MinRequiredSignatures)
}
35 changes: 30 additions & 5 deletions core/capabilities/fakes/streams_trigger.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,20 @@ package fakes

import (
"context"
"crypto/ecdsa"
"encoding/hex"
"errors"
"fmt"
"math/big"
"sync"
"time"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/smartcontractkit/libocr/offchainreporting2/chains/evmutil"

ocrTypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types"

"github.com/smartcontractkit/chainlink-common/keystore/corekeys"
commonCap "github.com/smartcontractkit/chainlink-common/pkg/capabilities"
"github.com/smartcontractkit/chainlink-common/pkg/capabilities/datastreams"
"github.com/smartcontractkit/chainlink-common/pkg/capabilities/triggers"
Expand All @@ -23,7 +25,6 @@ import (
v3 "github.com/smartcontractkit/chainlink-common/pkg/types/mercury/v3"
"github.com/smartcontractkit/chainlink-evm/pkg/mercury/v3/reportcodec"

"github.com/smartcontractkit/chainlink-common/keystore/corekeys/ocr2key"
"github.com/smartcontractkit/chainlink/v2/core/capabilities/streams"
)

Expand All @@ -32,7 +33,7 @@ type fakeStreamsTrigger struct {
eng *services.Engine
lggr logger.Logger

signers []ocr2key.KeyBundle
signers []fakeStreamsTriggerSigner
codec datastreams.ReportCodec
meta datastreams.Metadata

Expand All @@ -47,6 +48,10 @@ type regState struct {
eventCh chan commonCap.TriggerResponse
}

type fakeStreamsTriggerSigner struct {
privateKey *ecdsa.PrivateKey
}

var _ services.Service = (*fakeStreamsTrigger)(nil)
var _ commonCap.TriggerCapability = (*fakeStreamsTrigger)(nil)

Expand Down Expand Up @@ -108,10 +113,16 @@ func (st *fakeStreamsTrigger) UnregisterTrigger(ctx context.Context, request com
}

func NewFakeStreamsTrigger(lggr logger.Logger, nSigners int) *fakeStreamsTrigger {
signers := make([]ocr2key.KeyBundle, nSigners)
signers := make([]fakeStreamsTriggerSigner, nSigners)
rawSigners := make([][]byte, nSigners)
for i := range nSigners {
signers[i], _ = ocr2key.New(corekeys.EVM)
keyMaterial := make([]byte, 32)
keyMaterial[31] = byte(i + 1)
privateKey, err := crypto.ToECDSA(keyMaterial)
if err != nil {
panic(err)
}
signers[i] = fakeStreamsTriggerSigner{privateKey: privateKey}
rawSigners[i] = signers[i].PublicKey()
}

Expand Down Expand Up @@ -225,6 +236,20 @@ func newReport(ctx context.Context, lggr logger.Logger, feedID [32]byte, price i
return raw
}

func (s fakeStreamsTriggerSigner) PublicKey() ocrTypes.OnchainPublicKey {
address := crypto.PubkeyToAddress(s.privateKey.PublicKey)
return common.CopyBytes(address[:])
}

func (s fakeStreamsTriggerSigner) Sign(reportCtx ocrTypes.ReportContext, report ocrTypes.Report) ([]byte, error) {
rawReportContext := evmutil.RawReportContext(reportCtx)
sigData := crypto.Keccak256(report)
sigData = append(sigData, rawReportContext[0][:]...)
sigData = append(sigData, rawReportContext[1][:]...)
sigData = append(sigData, rawReportContext[2][:]...)
return crypto.Sign(crypto.Keccak256(sigData), s.privateKey)
}

func rawReportContext(reportCtx ocrTypes.ReportContext) []byte {
rc := evmutil.RawReportContext(reportCtx)
flat := []byte{}
Expand Down
17 changes: 17 additions & 0 deletions core/cmd/shell_local.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"github.com/smartcontractkit/chainlink-common/pkg/custmsg"
"github.com/smartcontractkit/chainlink-common/pkg/logger/otelzap"
"github.com/smartcontractkit/chainlink-common/pkg/sqlutil"
coreconfig "github.com/smartcontractkit/chainlink/v2/core/config"
"github.com/smartcontractkit/chainlink/v2/core/services/chainlink"

"github.com/smartcontractkit/chainlink-evm/pkg/assets"
Expand Down Expand Up @@ -505,6 +506,10 @@ func (s *Shell) runNode(c *cli.Context) error {
}
}

type importedAptosKeyConfig interface {
ImportedAptosKey() coreconfig.ImportableKey
}

if s.Config.P2P().Enabled() {
if s.Config.ImportedP2PKey().JSON() != "" {
lggr.Debugf("Importing p2p key %s", s.Config.ImportedP2PKey().JSON())
Expand Down Expand Up @@ -552,6 +557,18 @@ func (s *Shell) runNode(c *cli.Context) error {
}
}
if s.Config.AptosEnabled() {
if cfg, ok := s.Config.(importedAptosKeyConfig); ok {
if k := cfg.ImportedAptosKey(); k != nil && k.JSON() != "" {
lggr.Debug("Importing aptos key")
_, err2 := app.GetKeyStore().Aptos().Import(rootCtx, []byte(k.JSON()), k.Password())
if errors.Is(err2, keystore.ErrKeyExists) {
lggr.Debugf("Aptos key already exists %s", k.JSON())
} else if err2 != nil {
return s.errorOut(fmt.Errorf("error importing aptos key: %w", err2))
}
}
}

err2 := app.GetKeyStore().Aptos().EnsureKey(rootCtx)
if err2 != nil {
return fmt.Errorf("failed to ensure aptos key: %w", err2)
Expand Down
37 changes: 37 additions & 0 deletions core/config/toml/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ type Secrets struct {
Threshold ThresholdKeyShareSecrets `toml:",omitempty"`
EVM EthKeys `toml:",omitempty"` // choose EVM as the TOML field name to align with relayer config convention
Solana SolKeys `toml:",omitempty"` // choose Solana as the TOML field name to align with relayer config convention
Aptos AptosKey `toml:",omitempty"`

P2PKey P2PKey `toml:",omitempty"`
DKGRecipientKey DKGRecipientKey `toml:",omitempty"`
Expand All @@ -164,6 +165,11 @@ type SolKey struct {
Password *models.Secret
}

type AptosKey struct {
JSON *models.Secret
Password *models.Secret
}

func (s *SolKeys) SetFrom(f *SolKeys) error {
err := s.validateMerge(f)
if err != nil {
Expand Down Expand Up @@ -245,6 +251,37 @@ func (e *SolKey) ValidateConfig() (err error) {
return err
}

func (p *AptosKey) SetFrom(f *AptosKey) (err error) {
err = p.validateMerge(f)
if err != nil {
return err
}
if v := f.JSON; v != nil {
p.JSON = v
}
if v := f.Password; v != nil {
p.Password = v
}
return nil
}

func (p *AptosKey) validateMerge(f *AptosKey) (err error) {
if p.JSON != nil && f.JSON != nil {
err = errors.Join(err, configutils.ErrOverride{Name: "JSON"})
}
if p.Password != nil && f.Password != nil {
err = errors.Join(err, configutils.ErrOverride{Name: "Password"})
}
return err
}

func (p *AptosKey) ValidateConfig() (err error) {
if (p.JSON != nil) != (p.Password != nil) {
err = errors.Join(err, configutils.ErrInvalid{Name: "AptosKey", Value: p.JSON, Msg: "all fields must be nil or non-nil"})
}
return err
}

type EthKeys struct {
Keys []*EthKey
}
Expand Down
10 changes: 10 additions & 0 deletions core/scripts/cre/environment/configs/capability_defaults.toml
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,16 @@
# FromAddress = "0x0000000000000000000000000000000000000000"
# ForwarderAddress = "0x0000000000000000000000000000000000000000"

# Aptos chain capability plugin (View + WriteReport). Runtime values are injected per chain.
[capability_configs.write-aptos]
binary_name = "aptos"

[capability_configs.write-aptos.values]
# ChainID and forwarder address are injected at job proposal time.
RequestTimeout = "30s"
TransmissionSchedule = "allAtOnce"
DeltaStage = "1500ms"

[capability_configs.solana.values]
TxAcceptanceState = 3
TxRetentonTimeout = "120s"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Same as workflow-gateway-don.toml but with Aptos chain and Aptos read capability plumbing.
# Anvil 1337: registry and gateway. Aptos: local devnet (chain_id 4). Run: env config path <this file>, then env start.

[[blockchains]]
type = "anvil"
chain_id = "1337"
container_name = "anvil-1337"
docker_cmd_params = ["-b", "0.5", "--mixed-mining"]

[[blockchains]]
type = "aptos"
chain_id = "4"

[jd]
csa_encryption_key = "d1093c0060d50a3c89c189b2e485da5a3ce57f3dcb38ab7e2c0d5f0bb2314a44"
# change to your version
image = "job-distributor:0.22.1"

#[s3provider]
# # use all defaults
# port = 9000
# console_port = 9001

[infra]
# either "docker" or "kubernetes"
type = "docker"

[[nodesets]]
nodes = 4
name = "workflow"
don_types = ["workflow"]
override_mode = "all"
http_port_range_start = 10100

supported_evm_chains = [1337]
env_vars = { CL_CRE_SETTINGS_DEFAULT = '{"PerWorkflow":{"CapabilityCallTimeout":"5m0s","ChainAllowed":{"Default":"false","Values":{"1337":"true","4457093679053095497":"true"}},"ChainWrite":{"EVM":{"GasLimit":{"Default":"5000000","Values":{"1337":"10000000"}}}}}}' }
capabilities = ["cron", "consensus", "read-contract-4", "write-aptos-4"]
registry_based_launch_allowlist = ["cron-trigger@1.0.0"]

[nodesets.db]
image = "postgres:12.0"
port = 13000

[[nodesets.node_specs]]
roles = ["plugin"]
[nodesets.node_specs.node]
docker_ctx = "../../../.."
docker_file = "core/chainlink.Dockerfile"
docker_build_args = { "CL_IS_PROD_BUILD" = "false" }
# image = "chainlink-tmp:latest"
user_config_overrides = ""

[[nodesets]]
nodes = 1
name = "bootstrap-gateway"
don_types = ["bootstrap", "gateway"]
override_mode = "each"
http_port_range_start = 10300

supported_evm_chains = [1337]

[nodesets.db]
image = "postgres:12.0"
port = 13200

[[nodesets.node_specs]]
roles = ["bootstrap", "gateway"]
[nodesets.node_specs.node]
docker_ctx = "../../../.."
docker_file = "core/chainlink.Dockerfile"
docker_build_args = { "CL_IS_PROD_BUILD" = "false" }
# 5002 is the web API capabilities port for incoming requests
# 15002 is the vault port for incoming requests
custom_ports = ["5002:5002", "15002:15002"]
# image = "chainlink-tmp:latest"
user_config_overrides = ""
10 changes: 9 additions & 1 deletion core/scripts/cre/environment/environment/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -330,8 +330,16 @@ func startCmd() *cobra.Command {
}

features := feature_set.New()
extraAllowedPorts := append([]int(nil), extraAllowedGatewayPorts...)
if in.Fake != nil {
extraAllowedPorts = append(extraAllowedPorts, in.Fake.Port)
}
if in.FakeHTTP != nil {
extraAllowedPorts = append(extraAllowedPorts, in.FakeHTTP.Port)
}

gatewayWhitelistConfig := gateway.WhitelistConfig{
ExtraAllowedPorts: append(extraAllowedGatewayPorts, in.Fake.Port, in.FakeHTTP.Port),
ExtraAllowedPorts: extraAllowedPorts,
ExtraAllowedIPsCIDR: []string{"0.0.0.0/0"},
}
output, startErr := StartCLIEnvironment(cmdContext, relativePathToRepoRoot, in, nil, features, nil, envDependencies, gatewayWhitelistConfig)
Expand Down
Loading
Loading