From d8af57f8dfea88544f1e1809be703354003a7636 Mon Sep 17 00:00:00 2001 From: Jordan Krage Date: Wed, 16 Apr 2025 17:55:38 -0500 Subject: [PATCH] pkg/solana/config: config enhancements --- .github/workflows/relay.yml | 13 +- go.mod | 2 +- go.sum | 4 +- integration-tests/go.mod | 11 +- integration-tests/go.sum | 16 +- integration-tests/testconfig/testconfig.go | 1 - pkg/solana/CONFIG.md | 438 ++++++++++++++++++++ pkg/solana/chain_test.go | 20 +- pkg/solana/cmd/config-docs/main.go | 24 ++ pkg/solana/config/config.go | 168 ++------ pkg/solana/config/docs.go | 18 + pkg/solana/config/docs.toml | 127 ++++++ pkg/solana/config/example.toml | 6 + pkg/solana/config/testdata/config-full.toml | 76 ++++ pkg/solana/config/toml.go | 155 +++---- pkg/solana/config/toml_test.go | 121 ++++++ pkg/solana/config_test.go | 22 + pkg/solana/relay.go | 7 +- pkg/solana/txm/mocks/simple_keystore.go | 60 +++ pkg/solana/write_target/target_strategy.go | 34 +- pkg/solana/write_target/write_target.go | 15 +- 21 files changed, 1048 insertions(+), 290 deletions(-) create mode 100644 pkg/solana/CONFIG.md create mode 100644 pkg/solana/cmd/config-docs/main.go create mode 100644 pkg/solana/config/docs.go create mode 100644 pkg/solana/config/docs.toml create mode 100644 pkg/solana/config/example.toml create mode 100644 pkg/solana/config/testdata/config-full.toml create mode 100644 pkg/solana/config/toml_test.go create mode 100644 pkg/solana/config_test.go diff --git a/.github/workflows/relay.yml b/.github/workflows/relay.yml index e7517ea8f..29f074325 100644 --- a/.github/workflows/relay.yml +++ b/.github/workflows/relay.yml @@ -23,8 +23,6 @@ jobs: with: go-version-file: "go.mod" check-latest: true - - name: Install gotestloghelper - run: go install github.com/smartcontractkit/chainlink-testing-framework/tools/gotestloghelper@latest - name: Check go mod tidy run: | make gomodtidy @@ -38,10 +36,8 @@ jobs: git diff --stat --exit-code - name: Install Solana CLI run: ./scripts/install-solana-ci.sh - - name: Compilation check - run: go test -run=xxx ./... # check compilation across tests + relayer / monitoring go code without running - name: Build - run: go build -v ./pkg/... + run: go build ./pkg/... - name: Get core ref id: get-ref uses: ./.github/actions/get-core-ref @@ -55,11 +51,11 @@ jobs: CL_DATABASE_URL: ${{ steps.setup-testdb.outputs.cl-db-url }} run: | set -o pipefail - go test ./pkg/... -json -tags integration -covermode=atomic -coverpkg=./... -coverprofile=integration_coverage.txt 2>&1 | tee /tmp/gotest.log | gotestloghelper -ci + go test ./pkg/... -tags integration -covermode=atomic -coverpkg=./... -coverprofile=integration_coverage.txt 2>&1 | tee /tmp/gotest.log - name: Test with the race detector enabled env: CL_DATABASE_URL: ${{ steps.setup-testdb.outputs.cl-db-url }} - run: go test ./pkg/... -v -race -count=10 -timeout=15m -covermode=atomic -coverpkg=./... -coverprofile=race_coverage.txt + run: GORACE="log_path=$PWD/race" go test ./pkg/... -race -count=5 -timeout=15m -covermode=atomic -coverpkg=./... -coverprofile=race_coverage.txt - name: Upload Go test results if: always() uses: actions/upload-artifact@v4 @@ -67,5 +63,6 @@ jobs: name: go-relay-test-results path: | /tmp/gotest.log - ./race_coverage.txt ./integration_coverage.txt + ./race.* + ./race_coverage.txt diff --git a/go.mod b/go.mod index e490df8ed..ea71ed468 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/smartcontractkit/chainlink-ccip v0.0.0-20250627133416-1d85eec09097 github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20250707132450-d1f5f0be212a github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20250804184440-c0506474fc44 - github.com/smartcontractkit/chainlink-common v0.9.1-0.20250819154659-73aa8dc9bf8c + github.com/smartcontractkit/chainlink-common v0.9.4-0.20250822114026-4186ff61208b github.com/smartcontractkit/chainlink-common/pkg/monitoring v0.0.0-20250415235644-8703639403c7 github.com/smartcontractkit/chainlink-framework/capabilities v0.0.0-20250818175541-3389ac08a563 github.com/smartcontractkit/chainlink-framework/metrics v0.0.0-20250717121125-2350c82883e2 diff --git a/go.sum b/go.sum index 986d2bcc6..49b1b430b 100644 --- a/go.sum +++ b/go.sum @@ -410,8 +410,8 @@ github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20250707132450-d github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20250707132450-d1f5f0be212a/go.mod h1:XEtEV52YIISL1Xp2l9XqyXFonKF9WxuGQQJu/lMozl8= github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20250804184440-c0506474fc44 h1:S00lus9RPu5JuxKRtGEET+aIUfASahHpTRV5RgPARSI= github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20250804184440-c0506474fc44/go.mod h1:xtZNi6pOKdC3sLvokDvXOhgHzT+cyBqH/gWwvxTxqrg= -github.com/smartcontractkit/chainlink-common v0.9.1-0.20250819154659-73aa8dc9bf8c h1:YXFwFoclQBL+B9C3xG07v5GNc31fUcib7hu+MzneFWI= -github.com/smartcontractkit/chainlink-common v0.9.1-0.20250819154659-73aa8dc9bf8c/go.mod h1:0OMQFyxibohHOzskRmEz4wr+w0SdAFsU6CjW/VhRf34= +github.com/smartcontractkit/chainlink-common v0.9.4-0.20250822114026-4186ff61208b h1:SOTBUmlyMVlKPeNAuxB4ZGkhQRXOEWRGbV6PQ+bs5Nw= +github.com/smartcontractkit/chainlink-common v0.9.4-0.20250822114026-4186ff61208b/go.mod h1:0OMQFyxibohHOzskRmEz4wr+w0SdAFsU6CjW/VhRf34= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.1 h1:ca2z5OXgnbBPQRxpwXwBLJsUA1+cAp5ncfW4Ssvd6eY= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.1/go.mod h1:NZv/qKYGFRnkjOYBouajnDfFoZ+WDa6H2KNmSf1dnKc= github.com/smartcontractkit/chainlink-common/pkg/monitoring v0.0.0-20250415235644-8703639403c7 h1:9wh1G+WbXwPVqf0cfSRSgwIcaXTQgvYezylEAfwmrbw= diff --git a/integration-tests/go.mod b/integration-tests/go.mod index 2742d7ee3..294f6ffe2 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -16,15 +16,15 @@ require ( github.com/pelletier/go-toml/v2 v2.2.4 github.com/rs/zerolog v1.33.0 github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20250805210128-7f8a0f403c3a - github.com/smartcontractkit/chainlink-common v0.9.1-0.20250819154659-73aa8dc9bf8c + github.com/smartcontractkit/chainlink-common v0.9.4-0.20250822114026-4186ff61208b github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20250819150450-95ef563f6e6d - github.com/smartcontractkit/chainlink-solana v1.1.2-0.20250820135304-632bebc0e802 + github.com/smartcontractkit/chainlink-solana v1.1.2-0.20250822114658-478d76fce6b9 github.com/smartcontractkit/chainlink-testing-framework/lib v1.54.4 github.com/smartcontractkit/chainlink-testing-framework/parrot v0.6.2 github.com/smartcontractkit/chainlink-testing-framework/seth v1.51.2 - github.com/smartcontractkit/chainlink/deployment v0.0.0-20250821121906-52f5372c5da8 - github.com/smartcontractkit/chainlink/integration-tests v0.0.0-20250821121906-52f5372c5da8 - github.com/smartcontractkit/chainlink/v2 v2.26.0-debug-tracing.0.20250821121906-52f5372c5da8 + github.com/smartcontractkit/chainlink/deployment v0.0.0-20250822122521-7924eae1304f + github.com/smartcontractkit/chainlink/integration-tests v0.0.0-20250822122521-7924eae1304f + github.com/smartcontractkit/chainlink/v2 v2.26.0-debug-tracing.0.20250822122521-7924eae1304f github.com/smartcontractkit/libocr v0.0.0-20250707144819-babe0ec4e358 github.com/stretchr/testify v1.10.0 github.com/testcontainers/testcontainers-go v0.37.0 @@ -273,6 +273,7 @@ require ( github.com/klauspost/cpuid/v2 v2.2.10 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect github.com/leanovate/gopter v0.2.11 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect diff --git a/integration-tests/go.sum b/integration-tests/go.sum index a2709fbb3..5f984b424 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -1253,8 +1253,8 @@ github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20250805210128-7 github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20250805210128-7f8a0f403c3a/go.mod h1:Ve1xD71bl193YIZQEoJMmBqLGQJdNs29bwbuObwvbhQ= github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20250805210128-7f8a0f403c3a h1:38dAlTPRUQHZus5dCnBnQyf/V4oYn0p2svWlbPgHDQ4= github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20250805210128-7f8a0f403c3a/go.mod h1:xtZNi6pOKdC3sLvokDvXOhgHzT+cyBqH/gWwvxTxqrg= -github.com/smartcontractkit/chainlink-common v0.9.1-0.20250819154659-73aa8dc9bf8c h1:YXFwFoclQBL+B9C3xG07v5GNc31fUcib7hu+MzneFWI= -github.com/smartcontractkit/chainlink-common v0.9.1-0.20250819154659-73aa8dc9bf8c/go.mod h1:0OMQFyxibohHOzskRmEz4wr+w0SdAFsU6CjW/VhRf34= +github.com/smartcontractkit/chainlink-common v0.9.4-0.20250822114026-4186ff61208b h1:SOTBUmlyMVlKPeNAuxB4ZGkhQRXOEWRGbV6PQ+bs5Nw= +github.com/smartcontractkit/chainlink-common v0.9.4-0.20250822114026-4186ff61208b/go.mod h1:0OMQFyxibohHOzskRmEz4wr+w0SdAFsU6CjW/VhRf34= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.1 h1:ca2z5OXgnbBPQRxpwXwBLJsUA1+cAp5ncfW4Ssvd6eY= github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.1/go.mod h1:NZv/qKYGFRnkjOYBouajnDfFoZ+WDa6H2KNmSf1dnKc= github.com/smartcontractkit/chainlink-common/pkg/monitoring v0.0.0-20250415235644-8703639403c7 h1:9wh1G+WbXwPVqf0cfSRSgwIcaXTQgvYezylEAfwmrbw= @@ -1305,12 +1305,12 @@ github.com/smartcontractkit/chainlink-tron/relayer v0.0.11-0.20250815105909-7549 github.com/smartcontractkit/chainlink-tron/relayer v0.0.11-0.20250815105909-75499abc4335/go.mod h1:ccjEgNeqOO+bjPddnL4lUrNLzyCvGCxgBjJdhFX3wa8= github.com/smartcontractkit/chainlink-tron/relayer/gotron-sdk v0.0.5-0.20250422175525-b7575d96bd4d h1:qLmSOOtB/Ogn79eIDkuujOu8M5Jd747V1H7Brk/nTvo= github.com/smartcontractkit/chainlink-tron/relayer/gotron-sdk v0.0.5-0.20250422175525-b7575d96bd4d/go.mod h1:4WhGgCA0smBbBud5mK+jnDb2wwndMvoqaWBJ3OV/7Bw= -github.com/smartcontractkit/chainlink/deployment v0.0.0-20250821121906-52f5372c5da8 h1:79o/WUYI136MqwGLGmABUoeyFJ/TJpTatIX65qbyQMg= -github.com/smartcontractkit/chainlink/deployment v0.0.0-20250821121906-52f5372c5da8/go.mod h1:zjv6COe1k/n1cvsAIFFxJbkqKdB5GbMCBiL8Yy6U4l8= -github.com/smartcontractkit/chainlink/integration-tests v0.0.0-20250821121906-52f5372c5da8 h1:LysiZIrcwhsFcnJavuOzwjqHPKondAr9zgRbf3rhIfA= -github.com/smartcontractkit/chainlink/integration-tests v0.0.0-20250821121906-52f5372c5da8/go.mod h1:z+6wW9tJ9fqIKoDpZ5KiqRQYIKHOxCYvXXz/ej8ipW8= -github.com/smartcontractkit/chainlink/v2 v2.26.0-debug-tracing.0.20250821121906-52f5372c5da8 h1:3MxyN53l61mbG5Y6eyK5mamFnQujm0cMjcAlUJmS3co= -github.com/smartcontractkit/chainlink/v2 v2.26.0-debug-tracing.0.20250821121906-52f5372c5da8/go.mod h1:/KP0hw3OuSYybp9IWgfr6gjWhAFr0um5cggj+v6ODyA= +github.com/smartcontractkit/chainlink/deployment v0.0.0-20250822122521-7924eae1304f h1:v6V7rbwxnKQDkV1+bXNySC5+eRDgq5i/Zhilj1Dkg3c= +github.com/smartcontractkit/chainlink/deployment v0.0.0-20250822122521-7924eae1304f/go.mod h1:hOBwMZV5wGR5U6LKTNH2agxDqiM5yJl0i/d0qsIeGek= +github.com/smartcontractkit/chainlink/integration-tests v0.0.0-20250822122521-7924eae1304f h1:Tp/3Op3n08gbzCZaLFUDtS3Pg1477BW8A3/n00ncDwc= +github.com/smartcontractkit/chainlink/integration-tests v0.0.0-20250822122521-7924eae1304f/go.mod h1:EZnjwYZToUpLw1VsFYU64fV2OhbnJFvjsick1804DJA= +github.com/smartcontractkit/chainlink/v2 v2.26.0-debug-tracing.0.20250822122521-7924eae1304f h1:VuA3mpTiqwo9UaCPESYJeS+ONtPdiqZIqa4I30d8QDQ= +github.com/smartcontractkit/chainlink/v2 v2.26.0-debug-tracing.0.20250822122521-7924eae1304f/go.mod h1:FsbzpAb719eV79qc2ixb3BHEW7YCNEVpIU1Q0yajHGo= github.com/smartcontractkit/cre-sdk-go v0.5.1-0.20250818135829-9ea58491207f h1:pgupmqPyAqfl2xgqHaRwfI/Kd6HtGbB8WeMt9XPM5L0= github.com/smartcontractkit/cre-sdk-go v0.5.1-0.20250818135829-9ea58491207f/go.mod h1:C1KXVcxUy89lFVqJ335pEPeeC/wJy0jCF0ZztwWdCmU= github.com/smartcontractkit/freeport v0.1.3-0.20250716200817-cb5dfd0e369e h1:Hv9Mww35LrufCdM9wtS9yVi/rEWGI1UnjHbcKKU0nVY= diff --git a/integration-tests/testconfig/testconfig.go b/integration-tests/testconfig/testconfig.go index 05e64217c..ab073a211 100644 --- a/integration-tests/testconfig/testconfig.go +++ b/integration-tests/testconfig/testconfig.go @@ -281,7 +281,6 @@ func (c *TestConfig) GetNodeConfigTOML() (string, error) { // Increase timeout for TransactionSender TxTimeout: config.MustNewDuration(2 * time.Minute), } - chainCfg.SetDefaults() solConfig := solcfg.TOMLConfig{ Enabled: ptr.Ptr(true), diff --git a/pkg/solana/CONFIG.md b/pkg/solana/CONFIG.md new file mode 100644 index 000000000..95d4dbc66 --- /dev/null +++ b/pkg/solana/CONFIG.md @@ -0,0 +1,438 @@ +[//]: # (Documentation generated from docs.toml - DO NOT EDIT.) +This document describes the TOML format for configuration. +## Example + +```toml +[[Solana]] +ChainID = "mainnet" + +[[Solana.Nodes]] +Name = 'primary' +URL = 'http://solana.web' + +``` + +## Global +```toml +ChainID = 'mainnet' # Example +Enabled = true # Default +BlockTime = '500ms' # Default +BalancePollPeriod = '5s' # Default +ConfirmPollPeriod = '500ms' # Default +OCR2CachePollPeriod = '1s' # Default +OCR2CacheTTL = '1m' # Default +TxTimeout = '1m' # Default +TxRetryTimeout = '10s' # Default +TxConfirmTimeout = '30s' # Default +TxExpirationRebroadcast = false # Default +TxRetentionTimeout = '0s' # Default +SkipPreflight = true # Default +Commitment = 'confirmed' # Default +MaxRetries = 0 # Default +FeeEstimatorMode = 'fixed' # Default +ComputeUnitPriceMax = 1000 # Default +ComputeUnitPriceMin = 0 # Default +ComputeUnitPriceDefault = 0 # Default +FeeBumpPeriod = '3s' # Default +BlockHistoryPollPeriod = '5s' # Default +BlockHistorySize = 1 # Default +BlockHistoryBatchLoadSize = 20 # Default +ComputeUnitLimitDefault = 200_000 # Default +EstimateComputeUnitLimit = false # Default +LogPollerStartingLookback = '24h' # Default +``` + + +### ChainID +```toml +ChainID = 'mainnet' # Example +``` +ChainID is the Solana chain ID. Must be one of: mainnet, testnet, devnet, localnet. Mandatory. + +### Enabled +```toml +Enabled = true # Default +``` +Enabled enables this chain. + +### BlockTime +```toml +BlockTime = '500ms' # Default +``` +BlockTime specifies the average time between blocks on this chain + +### BalancePollPeriod +```toml +BalancePollPeriod = '5s' # Default +``` +BalancePollPeriod is the rate to poll for SOL balance and update Prometheus metrics. + +### ConfirmPollPeriod +```toml +ConfirmPollPeriod = '500ms' # Default +``` +ConfirmPollPeriod is the rate to poll for signature confirmation. + +### OCR2CachePollPeriod +```toml +OCR2CachePollPeriod = '1s' # Default +``` +OCR2CachePollPeriod is the rate to poll for the OCR2 state cache. + +### OCR2CacheTTL +```toml +OCR2CacheTTL = '1m' # Default +``` +OCR2CacheTTL is the stale OCR2 cache deadline. + +### TxTimeout +```toml +TxTimeout = '1m' # Default +``` +TxTimeout is the timeout for sending txes to an RPC endpoint. + +### TxRetryTimeout +```toml +TxRetryTimeout = '10s' # Default +``` +TxRetryTimeout is the duration for tx manager to attempt rebroadcasting to RPC, before giving up. + +### TxConfirmTimeout +```toml +TxConfirmTimeout = '30s' # Default +``` +TxConfirmTimeout is the duration to wait when confirming a tx signature, before discarding as unconfirmed. + +### TxExpirationRebroadcast +```toml +TxExpirationRebroadcast = false # Default +``` +TxExpirationRebroadcast enables or disables transaction rebroadcast if expired. Expiration check is performed every `ConfirmPollPeriod` +A transaction is considered expired if the blockhash it was sent with is 150 blocks older than the latest blockhash. + +### TxRetentionTimeout +```toml +TxRetentionTimeout = '0s' # Default +``` +TxRetentionTimeout is the duration to retain transactions in storage after being marked as finalized or errored. Set to 0 to immediately drop transactions. + +### SkipPreflight +```toml +SkipPreflight = true # Default +``` +SkipPreflight enables or disables preflight checks when sending txs. + +### Commitment +```toml +Commitment = 'confirmed' # Default +``` +Commitment is the confirmation level for solana state and transactions. ([documentation](https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment)) + +### MaxRetries +```toml +MaxRetries = 0 # Default +``` +MaxRetries is the maximum number of times the RPC node will automatically rebroadcast a tx. +The default is 0 for custom txm rebroadcasting method, set to -1 to use the RPC node's default retry strategy. + +### FeeEstimatorMode +```toml +FeeEstimatorMode = 'fixed' # Default +``` +FeeEstimatorMode is the method used to determine the base fee + +### ComputeUnitPriceMax +```toml +ComputeUnitPriceMax = 1000 # Default +``` +ComputeUnitPriceMax is the maximum price per compute unit that a transaction can be bumped to + +### ComputeUnitPriceMin +```toml +ComputeUnitPriceMin = 0 # Default +``` +ComputeUnitPriceMin is the minimum price per compute unit that transaction can have + +### ComputeUnitPriceDefault +```toml +ComputeUnitPriceDefault = 0 # Default +``` +ComputeUnitPriceDefault is the default price per compute unit price, and the starting base fee when FeeEstimatorMode = 'fixed' + +### FeeBumpPeriod +```toml +FeeBumpPeriod = '3s' # Default +``` +FeeBumpPeriod is the amount of time before a tx is retried with a fee bump. WARNING: If FeeBumpPeriod is shorter than blockhash expiration, multiple valid transactions can exist in parallel. This can result in higher costs and can cause unexpected behaviors if contracts do not de-dupe txs + +### BlockHistoryPollPeriod +```toml +BlockHistoryPollPeriod = '5s' # Default +``` +BlockHistoryPollPeriod is the rate to poll for blocks in the block history fee estimator + +### BlockHistorySize +```toml +BlockHistorySize = 1 # Default +``` +BlockHistorySize is the number of blocks to take into consideration when using FeeEstimatorMode = 'blockhistory' to determine compute unit price. +If set to 1, the compute unit price will be determined by the median of the last block's compute unit prices. +If set N > 1, the compute unit price will be determined by the average of the medians of the last N blocks' compute unit prices. +DISCLAIMER: If set to a value greater than BlockHistoryBatchLoadSize, initial estimations during startup would be over smaller block ranges until the cache is filled. + +### BlockHistoryBatchLoadSize +```toml +BlockHistoryBatchLoadSize = 20 # Default +``` +BlockHistoryBatchLoadSize is the number of latest blocks to fetch from the chain to store in the cache every BlockHistoryPollPeriod. +This config is only relevant if BlockHistorySize > 1 and if BlockHistorySize is greater than BlockHistoryBatchLoadSize. +Ensure the value is greater than the number of blocks that would be produced between each BlockHistoryPollPeriod to avoid gaps in block history. + +### ComputeUnitLimitDefault +```toml +ComputeUnitLimitDefault = 200_000 # Default +``` +ComputeUnitLimitDefault is the compute units limit applied to transactions unless overriden during the txm enqueue + +### EstimateComputeUnitLimit +```toml +EstimateComputeUnitLimit = false # Default +``` +EstimateComputeUnitLimit enables or disables compute unit limit estimations per transaction. If estimations return 0 used compute, the ComputeUnitLimitDefault value is used, if set. + +### LogPollerStartingLookback +```toml +LogPollerStartingLookback = '24h' # Default +``` +LogPollerStartingLookback + +## MultiNode +```toml +[MultiNode] +Enabled = false # Default +PollFailureThreshold = 5 # Default +PollInterval = '15s' # Default +SelectionMode = 'PriorityLevel' # Default +SyncThreshold = 10 # Default +NodeIsSyncingEnabled = false # Default +LeaseDuration = '1m' # Default +NewHeadsPollInterval = '5s' # Default +FinalizedBlockPollInterval = '5s' # Default +EnforceRepeatableRead = true # Default +DeathDeclarationDelay = '20s' # Default +VerifyChainID = true # Default +NodeNoNewHeadsThreshold = '20s' # Default +NoNewFinalizedHeadsThreshold = '20s' # Default +FinalityDepth = 0 # Default +FinalityTagEnabled = true # Default +FinalizedBlockOffset = 50 # Default +``` + + +### Enabled +```toml +Enabled = false # Default +``` +Enabled enables the multinode feature. + +### PollFailureThreshold +```toml +PollFailureThreshold = 5 # Default +``` +PollFailureThreshold is the number of consecutive poll failures before a node is considered unhealthy. + +### PollInterval +```toml +PollInterval = '15s' # Default +``` +PollInterval is the rate to poll for node health. + +### SelectionMode +```toml +SelectionMode = 'PriorityLevel' # Default +``` +SelectionMode is the method used to select the next best node to use. + +### SyncThreshold +```toml +SyncThreshold = 10 # Default +``` +SyncThreshold is the number of blocks behind the best node that a node can be before it is considered out of sync. + +### NodeIsSyncingEnabled +```toml +NodeIsSyncingEnabled = false # Default +``` +NodeIsSyncingEnabled enables the feature to avoid sending transactions to nodes that are syncing. Not relavant for Solana. + +### LeaseDuration +```toml +LeaseDuration = '1m' # Default +``` +LeaseDuration is the max duration a node can be leased for. + +### NewHeadsPollInterval +```toml +NewHeadsPollInterval = '5s' # Default +``` +NewHeadsPollInterval is the rate to poll for new heads. + +### FinalizedBlockPollInterval +```toml +FinalizedBlockPollInterval = '5s' # Default +``` +FinalizedBlockPollInterval is the rate to poll for the finalized block. + +### EnforceRepeatableRead +```toml +EnforceRepeatableRead = true # Default +``` +EnforceRepeatableRead enforces the repeatable read guarantee for multinode. + +### DeathDeclarationDelay +```toml +DeathDeclarationDelay = '20s' # Default +``` +DeathDeclarationDelay is the duration to wait before declaring a node dead. + +### VerifyChainID +```toml +VerifyChainID = true # Default +``` +VerifyChainID enforces RPC Client ChainIDs to match configured ChainID + +### NodeNoNewHeadsThreshold +```toml +NodeNoNewHeadsThreshold = '20s' # Default +``` +NodeNoNewHeadsThreshold is the duration to wait before declaring a node unhealthy due to no new heads. + +### NoNewFinalizedHeadsThreshold +```toml +NoNewFinalizedHeadsThreshold = '20s' # Default +``` +NoNewFinalizedHeadsThreshold is the duration to wait before declaring a node unhealthy due to no new finalized heads. + +### FinalityDepth +```toml +FinalityDepth = 0 # Default +``` +FinalityDepth is not used when finality tags are enabled. + +### FinalityTagEnabled +```toml +FinalityTagEnabled = true # Default +``` +FinalityTagEnabled enables the use of finality tags. + +### FinalizedBlockOffset +```toml +FinalizedBlockOffset = 50 # Default +``` +FinalizedBlockOffset is the offset from the finalized block to use for finality tags. + +## Workflow +```toml +[Workflow] +AcceptanceTimeout = '45s' # Default +ForwarderAddress = '14grJpemFaf88c8tiVb77W7TYg2W3ir6pfkKz3YjhhZ5' # Example +ForwarderState = '14grJpemFaf88c8tiVb77W7TYg2W3ir6pfkKz3YjhhZ5' # Example +FromAddress = '4BJXYkfvg37zEmBbsacZjeQDpTNx91KppxFJxRqrz48e' # Example +GasLimitDefault = 300_000 # Default +Local = false # Default +PollPeriod = '3s' # Default +TxAcceptanceState = 3 # Default +``` + + +### AcceptanceTimeout +```toml +AcceptanceTimeout = '45s' # Default +``` +AcceptanceTimeout is the default timeout for a tranmission to be accepted on chain + +### ForwarderAddress +```toml +ForwarderAddress = '14grJpemFaf88c8tiVb77W7TYg2W3ir6pfkKz3YjhhZ5' # Example +``` +ForwarderAddress is the keystone forwarder program address on chain. + +### ForwarderState +```toml +ForwarderState = '14grJpemFaf88c8tiVb77W7TYg2W3ir6pfkKz3YjhhZ5' # Example +``` +ForwarderState is the keystone forwarder program state account on chain. + +### FromAddress +```toml +FromAddress = '4BJXYkfvg37zEmBbsacZjeQDpTNx91KppxFJxRqrz48e' # Example +``` +FromAddress is Address of the transmitter key to use for workflow writes. + +### GasLimitDefault +```toml +GasLimitDefault = 300_000 # Default +``` +GasLimitDefault is the default gas limit for workflow transactions. + +### Local +```toml +Local = false # Default +``` +Local defines if relayer runs against local devnet + +### PollPeriod +```toml +PollPeriod = '3s' # Default +``` +PollPeriod is the default poll period for checking transmission state + +### TxAcceptanceState +```toml +TxAcceptanceState = 3 # Default +``` +TxAcceptanceState is the default acceptance state for writer DON tranmissions. + +## Nodes +```toml +[[Nodes]] +Name = 'primary' # Example +URL = 'http://solana.web' # Example +SendOnly = false # Default +Order = 100 # Default +IsLoadBalancedRPC = false # Default +``` + + +### Name +```toml +Name = 'primary' # Example +``` +Name is a unique (per-chain) identifier for this node. + +### URL +```toml +URL = 'http://solana.web' # Example +``` +URL is the HTTP(S) endpoint for this node. + +### SendOnly +```toml +SendOnly = false # Default +``` +SendOnly is a multinode config that only sends transactions to a node and does not read state + +### Order +```toml +Order = 100 # Default +``` +Order specifies the priority for each node. 1 is highest priority down to 100 being the lowest. + +### IsLoadBalancedRPC +```toml +IsLoadBalancedRPC = false # Default +``` +IsLoadBalancedRPC indicates whether the http/ws url above has multiple rpc's behind it. +If true, we should try reconnecting to the node even when its the only node in the Nodes list. +If false and its the only node in the nodes list, we will mark it alive even when its out of sync, because it might still be able to send txs. + diff --git a/pkg/solana/chain_test.go b/pkg/solana/chain_test.go index 27fe55f87..2065e8919 100644 --- a/pkg/solana/chain_test.go +++ b/pkg/solana/chain_test.go @@ -61,11 +61,8 @@ func TestSolanaChain_GetClient(t *testing.T) { })) defer mockServer.Close() - ch := solcfg.Chain{} - ch.SetDefaults() cfg := &solcfg.TOMLConfig{ ChainID: ptr("devnet"), - Chain: ch, } cfg.SetDefaults() testChain := chain{ @@ -171,11 +168,8 @@ func TestSolanaChain_VerifiedClients(t *testing.T) { })) defer mockServer.Close() - ch := solcfg.Chain{} - ch.SetDefaults() cfg := &solcfg.TOMLConfig{ ChainID: ptr(tc.chainID), - Chain: ch, } cfg.SetDefaults() @@ -222,12 +216,9 @@ func TestSolanaChain_VerifiedClient_ParallelClients(t *testing.T) { })) defer mockServer.Close() - ch := solcfg.Chain{} - ch.SetDefaults() cfg := &solcfg.TOMLConfig{ ChainID: ptr("devnet"), Enabled: ptr(true), - Chain: ch, } cfg.SetDefaults() testChain := chain{ @@ -358,16 +349,11 @@ func TestSolanaChain_MultiNode_GetClient(t *testing.T) { })) defer mockServer.Close() - ch := solcfg.Chain{} - ch.SetDefaults() - mnCfg := solcfg.NewDefaultMultiNodeConfig() - mnCfg.MultiNode.Enabled = ptr(true) - cfg := &solcfg.TOMLConfig{ - ChainID: ptr(client.DevnetGenesisHash), - Chain: ch, - MultiNode: mnCfg, + ChainID: ptr(client.DevnetGenesisHash), } + cfg.MultiNode.MultiNode.Enabled = ptr(true) + cfg.SetDefaults() cfg.Nodes = []*solcfg.Node{ { Name: ptr("devnet"), diff --git a/pkg/solana/cmd/config-docs/main.go b/pkg/solana/cmd/config-docs/main.go new file mode 100644 index 000000000..3989d4514 --- /dev/null +++ b/pkg/solana/cmd/config-docs/main.go @@ -0,0 +1,24 @@ +package main + +import ( + "flag" + "fmt" + "log" + "os" + "path/filepath" + + "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" +) + +var outDir = flag.String("o", "", "output directory") + +func main() { + s, err := config.GenerateDocs() + if err != nil { + log.Fatalln("Failed to generate docs:", err) + } + if err = os.WriteFile(filepath.Join(*outDir, "CONFIG.md"), []byte(s), 0600); err != nil { + fmt.Fprintf(os.Stderr, "failed to write config docs: %v\n", err) + os.Exit(1) + } +} diff --git a/pkg/solana/config/config.go b/pkg/solana/config/config.go index 5700f90ad..ab7440c5c 100644 --- a/pkg/solana/config/config.go +++ b/pkg/solana/config/config.go @@ -2,7 +2,6 @@ package config import ( "errors" - "fmt" "time" "github.com/gagliardetto/solana-go" @@ -12,41 +11,6 @@ import ( commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" ) -// Global solana defaults. -var defaultConfigSet = Chain{ - // general chain properties - BlockTime: config.MustNewDuration(500 * time.Millisecond), // varies from 400-600ms on mainnet & testnet. May need to override for L2 chains - - // tx mgr - BalancePollPeriod: config.MustNewDuration(5 * time.Second), // poll period for balance monitoring - ConfirmPollPeriod: config.MustNewDuration(500 * time.Millisecond), // polling for tx confirmation - OCR2CachePollPeriod: config.MustNewDuration(time.Second), // cache polling rate - OCR2CacheTTL: config.MustNewDuration(time.Minute), // stale cache deadline - TxTimeout: config.MustNewDuration(time.Minute), // timeout for send tx method in client - TxRetryTimeout: config.MustNewDuration(10 * time.Second), // duration for tx rebroadcasting to RPC node - TxConfirmTimeout: config.MustNewDuration(30 * time.Second), // duration before discarding tx as unconfirmed. Set to 0 to disable discarding tx. - TxExpirationRebroadcast: ptr(false), // to enable rebroadcasting of expired transactions - TxRetentionTimeout: config.MustNewDuration(0 * time.Second), // duration to retain transactions after being marked as finalized or errored. Set to 0 to immediately drop transactions. - SkipPreflight: ptr(true), // to enable or disable preflight checks - Commitment: ptr(string(rpc.CommitmentConfirmed)), - MaxRetries: ptr(int64(0)), // max number of retries (default = 0). when config.MaxRetries < 0), interpreted as MaxRetries = nil and rpc node will do a reasonable number of retries - - // fee estimator - FeeEstimatorMode: ptr("fixed"), // "fixed" or "blockhistory" - ComputeUnitPriceMax: ptr(uint64(1_000)), - ComputeUnitPriceMin: ptr(uint64(0)), - ComputeUnitPriceDefault: ptr(uint64(0)), - FeeBumpPeriod: config.MustNewDuration(3 * time.Second), // WARNING: If FeeBumpPeriod is shorter than blockhash expiration, multiple valid transactions can exist in parallel. This can result in higher costs and can cause unexpected behaviors if contracts do not de-dupe txs. Set to 0 to disable fee bumping. - BlockHistoryPollPeriod: config.MustNewDuration(5 * time.Second), - BlockHistorySize: ptr(uint64(1)), // set to the number of blocks estimations should be made over. 1: uses latest block; >1: Uses multiple blocks, where n is number of blocks. - BlockHistoryBatchLoadSize: ptr(uint64(20)), // set to the number of blocks that should be loaded into the cache every poll period if BlockHistorySize > 1. Ensure this value is greater than the number of blocks that would be produced between each BlockHistoryPollPeriod to avoid block gaps. BlockHistorySize is used instead if BlockHistorySize < BlockHistoryBatchLoadSize. - ComputeUnitLimitDefault: ptr(uint32(200_000)), // set to 0 to disable adding compute unit limit - EstimateComputeUnitLimit: ptr(false), // set to false to disable compute unit limit estimation - - // log poller - LogPollerStartingLookback: config.MustNewDuration(24 * time.Hour), -} - type Config interface { // general chain properties BlockTime() time.Duration @@ -85,11 +49,12 @@ type Config interface { } type Workflow interface { + IsEnabled() bool AcceptanceTimeout() time.Duration PollPeriod() time.Duration - ForwarderAddress() string - FromAddress() string - ForwarderState() string + ForwarderAddress() *solana.PublicKey + FromAddress() *solana.PublicKey + ForwarderState() *solana.PublicKey GasLimitDefault() *uint64 TxAcceptanceState() *commontypes.TransactionStatus Local() bool // shows if workflow is run against local network @@ -97,30 +62,44 @@ type Workflow interface { type WorkflowConfig struct { AcceptanceTimeout *config.Duration - PollPeriod *config.Duration - - ForwarderAddress *string - FromAddress *string - ForwarderState *string + ForwarderAddress *solana.PublicKey + ForwarderState *solana.PublicKey + FromAddress *solana.PublicKey GasLimitDefault *uint64 + Local *bool + PollPeriod *config.Duration TxAcceptanceState *commontypes.TransactionStatus - Local bool } -func (w *WorkflowConfig) Validate() error { - var err error - addresses := map[string]string{ - "ForwarderAddress": *w.ForwarderAddress, - "FromAddress": *w.FromAddress, - "ForwarderState": *w.ForwarderState, +func (w *WorkflowConfig) IsEnabled() bool { + return w.ForwarderAddress != nil || w.ForwarderState != nil || w.FromAddress != nil +} + +func (w *WorkflowConfig) SetFrom(f *WorkflowConfig) { + if f.AcceptanceTimeout != nil { + w.AcceptanceTimeout = f.AcceptanceTimeout } - for name, addr := range addresses { - if _, err2 := solana.PublicKeyFromBase58(addr); err2 != nil { - err = errors.Join(err, fmt.Errorf("%s invalid solana address: %w", name, err2)) - } + if f.ForwarderAddress != nil { + w.ForwarderAddress = f.ForwarderAddress + } + if f.ForwarderState != nil { + w.ForwarderState = f.ForwarderState + } + if f.FromAddress != nil { + w.FromAddress = f.FromAddress + } + if f.GasLimitDefault != nil { + w.GasLimitDefault = f.GasLimitDefault + } + if f.Local != nil { + w.Local = f.Local + } + if f.PollPeriod != nil { + w.PollPeriod = f.PollPeriod + } + if f.TxAcceptanceState != nil { + w.TxAcceptanceState = f.TxAcceptanceState } - - return err } type Chain struct { @@ -150,81 +129,6 @@ type Chain struct { LogPollerStartingLookback *config.Duration } -func (c *Chain) SetDefaults() { - if c.BlockTime == nil { - c.BlockTime = defaultConfigSet.BlockTime - } - if c.BalancePollPeriod == nil { - c.BalancePollPeriod = defaultConfigSet.BalancePollPeriod - } - if c.ConfirmPollPeriod == nil { - c.ConfirmPollPeriod = defaultConfigSet.ConfirmPollPeriod - } - if c.OCR2CachePollPeriod == nil { - c.OCR2CachePollPeriod = defaultConfigSet.OCR2CachePollPeriod - } - if c.OCR2CacheTTL == nil { - c.OCR2CacheTTL = defaultConfigSet.OCR2CacheTTL - } - if c.TxTimeout == nil { - c.TxTimeout = defaultConfigSet.TxTimeout - } - if c.TxRetryTimeout == nil { - c.TxRetryTimeout = defaultConfigSet.TxRetryTimeout - } - if c.TxConfirmTimeout == nil { - c.TxConfirmTimeout = defaultConfigSet.TxConfirmTimeout - } - if c.TxExpirationRebroadcast == nil { - c.TxExpirationRebroadcast = defaultConfigSet.TxExpirationRebroadcast - } - if c.TxRetentionTimeout == nil { - c.TxRetentionTimeout = defaultConfigSet.TxRetentionTimeout - } - if c.SkipPreflight == nil { - c.SkipPreflight = defaultConfigSet.SkipPreflight - } - if c.Commitment == nil { - c.Commitment = defaultConfigSet.Commitment - } - if c.MaxRetries == nil { - c.MaxRetries = defaultConfigSet.MaxRetries - } - if c.FeeEstimatorMode == nil { - c.FeeEstimatorMode = defaultConfigSet.FeeEstimatorMode - } - if c.ComputeUnitPriceMax == nil { - c.ComputeUnitPriceMax = defaultConfigSet.ComputeUnitPriceMax - } - if c.ComputeUnitPriceMin == nil { - c.ComputeUnitPriceMin = defaultConfigSet.ComputeUnitPriceMin - } - if c.ComputeUnitPriceDefault == nil { - c.ComputeUnitPriceDefault = defaultConfigSet.ComputeUnitPriceDefault - } - if c.FeeBumpPeriod == nil { - c.FeeBumpPeriod = defaultConfigSet.FeeBumpPeriod - } - if c.BlockHistoryPollPeriod == nil { - c.BlockHistoryPollPeriod = defaultConfigSet.BlockHistoryPollPeriod - } - if c.BlockHistorySize == nil { - c.BlockHistorySize = defaultConfigSet.BlockHistorySize - } - if c.BlockHistoryBatchLoadSize == nil { - c.BlockHistoryBatchLoadSize = defaultConfigSet.BlockHistoryBatchLoadSize - } - if c.ComputeUnitLimitDefault == nil { - c.ComputeUnitLimitDefault = defaultConfigSet.ComputeUnitLimitDefault - } - if c.EstimateComputeUnitLimit == nil { - c.EstimateComputeUnitLimit = defaultConfigSet.EstimateComputeUnitLimit - } - if c.LogPollerStartingLookback == nil { - c.LogPollerStartingLookback = defaultConfigSet.LogPollerStartingLookback - } -} - type Node struct { Name *string URL *config.URL diff --git a/pkg/solana/config/docs.go b/pkg/solana/config/docs.go new file mode 100644 index 000000000..051a41fcd --- /dev/null +++ b/pkg/solana/config/docs.go @@ -0,0 +1,18 @@ +package config + +import ( + _ "embed" + + "github.com/smartcontractkit/chainlink-common/pkg/config/configdoc" +) + +//go:embed docs.toml +var docsTOML string + +//go:embed example.toml +var exampleConfig string + +func GenerateDocs() (string, error) { + return configdoc.Generate(docsTOML, `[//]: # (Documentation generated from docs.toml - DO NOT EDIT.) +This document describes the TOML format for configuration.`, exampleConfig, nil) +} diff --git a/pkg/solana/config/docs.toml b/pkg/solana/config/docs.toml new file mode 100644 index 000000000..b35626374 --- /dev/null +++ b/pkg/solana/config/docs.toml @@ -0,0 +1,127 @@ +# ChainID is the Solana chain ID. Must be one of: mainnet, testnet, devnet, localnet. Mandatory. +ChainID = 'mainnet' # Example +# Enabled enables this chain. +Enabled = true # Default +# BlockTime specifies the average time between blocks on this chain +BlockTime = '500ms' # Default +# BalancePollPeriod is the rate to poll for SOL balance and update Prometheus metrics. +BalancePollPeriod = '5s' # Default +# ConfirmPollPeriod is the rate to poll for signature confirmation. +ConfirmPollPeriod = '500ms' # Default +# OCR2CachePollPeriod is the rate to poll for the OCR2 state cache. +OCR2CachePollPeriod = '1s' # Default +# OCR2CacheTTL is the stale OCR2 cache deadline. +OCR2CacheTTL = '1m' # Default +# TxTimeout is the timeout for sending txes to an RPC endpoint. +TxTimeout = '1m' # Default +# TxRetryTimeout is the duration for tx manager to attempt rebroadcasting to RPC, before giving up. +TxRetryTimeout = '10s' # Default +# TxConfirmTimeout is the duration to wait when confirming a tx signature, before discarding as unconfirmed. +TxConfirmTimeout = '30s' # Default +# TxExpirationRebroadcast enables or disables transaction rebroadcast if expired. Expiration check is performed every `ConfirmPollPeriod` +# A transaction is considered expired if the blockhash it was sent with is 150 blocks older than the latest blockhash. +TxExpirationRebroadcast = false # Default +# TxRetentionTimeout is the duration to retain transactions in storage after being marked as finalized or errored. Set to 0 to immediately drop transactions. +TxRetentionTimeout = '0s' # Default +# SkipPreflight enables or disables preflight checks when sending txs. +SkipPreflight = true # Default +# Commitment is the confirmation level for solana state and transactions. ([documentation](https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment)) +Commitment = 'confirmed' # Default +# MaxRetries is the maximum number of times the RPC node will automatically rebroadcast a tx. +# The default is 0 for custom txm rebroadcasting method, set to -1 to use the RPC node's default retry strategy. +MaxRetries = 0 # Default +# FeeEstimatorMode is the method used to determine the base fee +FeeEstimatorMode = 'fixed' # Default +# ComputeUnitPriceMax is the maximum price per compute unit that a transaction can be bumped to +ComputeUnitPriceMax = 1000 # Default +# ComputeUnitPriceMin is the minimum price per compute unit that transaction can have +ComputeUnitPriceMin = 0 # Default +# ComputeUnitPriceDefault is the default price per compute unit price, and the starting base fee when FeeEstimatorMode = 'fixed' +ComputeUnitPriceDefault = 0 # Default +# FeeBumpPeriod is the amount of time before a tx is retried with a fee bump. WARNING: If FeeBumpPeriod is shorter than blockhash expiration, multiple valid transactions can exist in parallel. This can result in higher costs and can cause unexpected behaviors if contracts do not de-dupe txs +FeeBumpPeriod = '3s' # Default +# BlockHistoryPollPeriod is the rate to poll for blocks in the block history fee estimator +BlockHistoryPollPeriod = '5s' # Default +# BlockHistorySize is the number of blocks to take into consideration when using FeeEstimatorMode = 'blockhistory' to determine compute unit price. +# If set to 1, the compute unit price will be determined by the median of the last block's compute unit prices. +# If set N > 1, the compute unit price will be determined by the average of the medians of the last N blocks' compute unit prices. +# DISCLAIMER: If set to a value greater than BlockHistoryBatchLoadSize, initial estimations during startup would be over smaller block ranges until the cache is filled. +BlockHistorySize = 1 # Default +# BlockHistoryBatchLoadSize is the number of latest blocks to fetch from the chain to store in the cache every BlockHistoryPollPeriod. +# This config is only relevant if BlockHistorySize > 1 and if BlockHistorySize is greater than BlockHistoryBatchLoadSize. +# Ensure the value is greater than the number of blocks that would be produced between each BlockHistoryPollPeriod to avoid gaps in block history. +BlockHistoryBatchLoadSize = 20 # Default +# ComputeUnitLimitDefault is the compute units limit applied to transactions unless overriden during the txm enqueue +ComputeUnitLimitDefault = 200_000 # Default +# EstimateComputeUnitLimit enables or disables compute unit limit estimations per transaction. If estimations return 0 used compute, the ComputeUnitLimitDefault value is used, if set. +EstimateComputeUnitLimit = false # Default +# LogPollerStartingLookback +LogPollerStartingLookback = '24h' # Default + +[MultiNode] +# Enabled enables the multinode feature. +Enabled = false # Default +# PollFailureThreshold is the number of consecutive poll failures before a node is considered unhealthy. +PollFailureThreshold = 5 # Default +# PollInterval is the rate to poll for node health. +PollInterval = '15s' # Default +# SelectionMode is the method used to select the next best node to use. +SelectionMode = 'PriorityLevel' # Default +# SyncThreshold is the number of blocks behind the best node that a node can be before it is considered out of sync. +SyncThreshold = 10 # Default +# NodeIsSyncingEnabled enables the feature to avoid sending transactions to nodes that are syncing. Not relavant for Solana. +NodeIsSyncingEnabled = false # Default +# LeaseDuration is the max duration a node can be leased for. +LeaseDuration = '1m' # Default +# NewHeadsPollInterval is the rate to poll for new heads. +NewHeadsPollInterval = '5s' # Default +# FinalizedBlockPollInterval is the rate to poll for the finalized block. +FinalizedBlockPollInterval = '5s' # Default +# EnforceRepeatableRead enforces the repeatable read guarantee for multinode. +EnforceRepeatableRead = true # Default +# DeathDeclarationDelay is the duration to wait before declaring a node dead. +DeathDeclarationDelay = '20s' # Default +# VerifyChainID enforces RPC Client ChainIDs to match configured ChainID +VerifyChainID = true # Default +# NodeNoNewHeadsThreshold is the duration to wait before declaring a node unhealthy due to no new heads. +NodeNoNewHeadsThreshold = '20s' # Default +# NoNewFinalizedHeadsThreshold is the duration to wait before declaring a node unhealthy due to no new finalized heads. +NoNewFinalizedHeadsThreshold = '20s' # Default +# FinalityDepth is not used when finality tags are enabled. +FinalityDepth = 0 # Default +# FinalityTagEnabled enables the use of finality tags. +FinalityTagEnabled = true # Default +# FinalizedBlockOffset is the offset from the finalized block to use for finality tags. +FinalizedBlockOffset = 50 # Default + +[Workflow] +# AcceptanceTimeout is the default timeout for a tranmission to be accepted on chain +AcceptanceTimeout = '45s' # Default +# ForwarderAddress is the keystone forwarder program address on chain. +ForwarderAddress = '14grJpemFaf88c8tiVb77W7TYg2W3ir6pfkKz3YjhhZ5' # Example +# ForwarderState is the keystone forwarder program state account on chain. +ForwarderState = '14grJpemFaf88c8tiVb77W7TYg2W3ir6pfkKz3YjhhZ5' # Example +# FromAddress is Address of the transmitter key to use for workflow writes. +FromAddress = '4BJXYkfvg37zEmBbsacZjeQDpTNx91KppxFJxRqrz48e' # Example +# GasLimitDefault is the default gas limit for workflow transactions. +GasLimitDefault = 300_000 # Default +# Local defines if relayer runs against local devnet +Local = false # Default +# PollPeriod is the default poll period for checking transmission state +PollPeriod = '3s' # Default +# TxAcceptanceState is the default acceptance state for writer DON tranmissions. +TxAcceptanceState = 3 # Default + +[[Nodes]] +# Name is a unique (per-chain) identifier for this node. +Name = 'primary' # Example +# URL is the HTTP(S) endpoint for this node. +URL = 'http://solana.web' # Example +# SendOnly is a multinode config that only sends transactions to a node and does not read state +SendOnly = false # Default +# Order specifies the priority for each node. 1 is highest priority down to 100 being the lowest. +Order = 100 # Default +# IsLoadBalancedRPC indicates whether the http/ws url above has multiple rpc's behind it. +# If true, we should try reconnecting to the node even when its the only node in the Nodes list. +# If false and its the only node in the nodes list, we will mark it alive even when its out of sync, because it might still be able to send txs. +IsLoadBalancedRPC = false # Default diff --git a/pkg/solana/config/example.toml b/pkg/solana/config/example.toml new file mode 100644 index 000000000..5b9472912 --- /dev/null +++ b/pkg/solana/config/example.toml @@ -0,0 +1,6 @@ +[[Solana]] +ChainID = "mainnet" + +[[Solana.Nodes]] +Name = 'primary' +URL = 'http://solana.web' diff --git a/pkg/solana/config/testdata/config-full.toml b/pkg/solana/config/testdata/config-full.toml new file mode 100644 index 000000000..551c78d82 --- /dev/null +++ b/pkg/solana/config/testdata/config-full.toml @@ -0,0 +1,76 @@ +ChainID = 'fake-chain' +Enabled = true +BlockTime = '1h0m0s' +BalancePollPeriod = '1m0s' +ConfirmPollPeriod = '1s' +OCR2CachePollPeriod = '1m0s' +OCR2CacheTTL = '1h0m0s' +TxTimeout = '1h0m0s' +TxRetryTimeout = '1m0s' +TxConfirmTimeout = '1s' +TxExpirationRebroadcast = false +TxRetentionTimeout = '0s' +SkipPreflight = true +Commitment = 'banana' +MaxRetries = 7 +FeeEstimatorMode = 'fixed' +ComputeUnitPriceMax = 1000 +ComputeUnitPriceMin = 10 +ComputeUnitPriceDefault = 100 +FeeBumpPeriod = '1m0s' +BlockHistoryPollPeriod = '1m0s' +BlockHistorySize = 1 +BlockHistoryBatchLoadSize = 10 +ComputeUnitLimitDefault = 100000 +EstimateComputeUnitLimit = false +LogPollerStartingLookback = '24h0m0s' + +[Workflow] +AcceptanceTimeout = '42s' +ForwarderAddress = '14grJpemFaf88c8tiVb77W7TYg2W3ir6pfkKz3YjhhZ5' +ForwarderState = '4BJXYkfvg37zEmBbsacZjeQDpTNx91KppxFJxRqrz48e' +FromAddress = '14grJpemFaf88c8tiVb77W7TYg2W3ir6pfkKz3YjhhZ5' +GasLimitDefault = 3000000 +Local = true +PollPeriod = '9s' +TxAcceptanceState = 3 + +[MultiNode] +Enabled = false +PollFailureThreshold = 5 +PollInterval = '1s' +SelectionMode = 'HighestHead' +SyncThreshold = 5 +NodeIsSyncingEnabled = false +LeaseDuration = '1m0s' +NewHeadsPollInterval = '2s' +FinalizedBlockPollInterval = '3s' +EnforceRepeatableRead = true +DeathDeclarationDelay = '2m0s' +VerifyChainID = true +NodeNoNewHeadsThreshold = '3m0s' +NoNewFinalizedHeadsThreshold = '1h0m0s' +FinalityDepth = 0 +FinalityTagEnabled = true +FinalizedBlockOffset = 0 + +[[Nodes]] +Name = 'primary' +URL = 'http://solana.web' +SendOnly = false +Order = 1 +IsLoadBalancedRPC = false + +[[Nodes]] +Name = 'foo' +URL = 'http://solana.foo' +SendOnly = true +Order = 2 +IsLoadBalancedRPC = true + +[[Nodes]] +Name = 'bar' +URL = 'http://solana.bar' +SendOnly = true +Order = 2 +IsLoadBalancedRPC = true diff --git a/pkg/solana/config/toml.go b/pkg/solana/config/toml.go index 21e132954..75d1f853c 100644 --- a/pkg/solana/config/toml.go +++ b/pkg/solana/config/toml.go @@ -3,20 +3,35 @@ package config import ( "errors" "fmt" + "log" "net/url" + "strings" "time" + "github.com/gagliardetto/solana-go" "github.com/gagliardetto/solana-go/rpc" "github.com/pelletier/go-toml/v2" "golang.org/x/exp/slices" "github.com/smartcontractkit/chainlink-common/pkg/config" - commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" - relaytypes "github.com/smartcontractkit/chainlink-common/pkg/types" - mn "github.com/smartcontractkit/chainlink-framework/multinode" + "github.com/smartcontractkit/chainlink-common/pkg/config/configtest" + "github.com/smartcontractkit/chainlink-common/pkg/types" mnCfg "github.com/smartcontractkit/chainlink-framework/multinode/config" ) +var defaults TOMLConfig + +func init() { + if err := configtest.DocDefaultsOnly(strings.NewReader(docsTOML), &defaults, config.DecodeTOML); err != nil { + log.Fatalf("Failed to initialize defaults from docs: %v", err) + } +} + +func Defaults() (c TOMLConfig) { + c.SetFrom(&defaults) + return +} + type TOMLConfigs []*TOMLConfig func (cs TOMLConfigs) ValidateConfig() (err error) { @@ -73,13 +88,13 @@ func (cs *TOMLConfigs) SetFrom(fs *TOMLConfigs) (err error) { return } -func NodeStatus(n *Node, id string) (relaytypes.NodeStatus, error) { - var s relaytypes.NodeStatus +func NodeStatus(n *Node, id string) (types.NodeStatus, error) { + var s types.NodeStatus s.ChainID = id s.Name = *n.Name b, err := toml.Marshal(n) if err != nil { - return relaytypes.NodeStatus{}, err + return types.NodeStatus{}, err } s.Config = string(b) return s, nil @@ -96,12 +111,12 @@ func (ns *Nodes) SetFrom(fs *Nodes) { }); i == -1 { *ns = append(*ns, f) } else { - setFromNode((*ns)[i], f) + (*ns)[i].SetFrom(f) } } } -func setFromNode(n, f *Node) { +func (n *Node) SetFrom(f *Node) { if f.Name != nil { n.Name = f.Name } @@ -119,7 +134,7 @@ type TOMLConfig struct { // Do not access directly, use [IsEnabled] Enabled *bool Chain - Workflow *WorkflowConfig `toml:",omitempty"` + Workflow WorkflowConfig `toml:",omitempty"` MultiNode mnCfg.MultiNodeConfig Nodes Nodes } @@ -128,6 +143,12 @@ func (c *TOMLConfig) IsEnabled() bool { return c.Enabled == nil || *c.Enabled } +func (c *TOMLConfig) SetDefaults() { + d := Defaults() + d.SetFrom(c) + *c = d +} + func (c *TOMLConfig) SetFrom(f *TOMLConfig) { if f.ChainID != nil { c.ChainID = f.ChainID @@ -135,12 +156,13 @@ func (c *TOMLConfig) SetFrom(f *TOMLConfig) { if f.Enabled != nil { c.Enabled = f.Enabled } - setFromChain(&c.Chain, &f.Chain) - c.Nodes.SetFrom(&f.Nodes) + c.Chain.SetFrom(&f.Chain) c.MultiNode.SetFrom(&f.MultiNode) + c.Workflow.SetFrom(&f.Workflow) + c.Nodes.SetFrom(&f.Nodes) } -func setFromChain(c, f *Chain) { +func (c *Chain) SetFrom(f *Chain) { if f.BlockTime != nil { c.BlockTime = f.BlockTime } @@ -201,9 +223,6 @@ func setFromChain(c, f *Chain) { if f.BlockHistorySize != nil { c.BlockHistorySize = f.BlockHistorySize } - if f.LogPollerStartingLookback != nil { - c.LogPollerStartingLookback = f.LogPollerStartingLookback - } if f.BlockHistoryBatchLoadSize != nil { c.BlockHistoryBatchLoadSize = f.BlockHistoryBatchLoadSize } @@ -213,6 +232,9 @@ func setFromChain(c, f *Chain) { if f.EstimateComputeUnitLimit != nil { c.EstimateComputeUnitLimit = f.EstimateComputeUnitLimit } + if f.LogPollerStartingLookback != nil { + c.LogPollerStartingLookback = f.LogPollerStartingLookback + } } func (c *TOMLConfig) ValidateConfig() (err error) { @@ -230,10 +252,6 @@ func (c *TOMLConfig) ValidateConfig() (err error) { err = errors.Join(err, config.ErrInvalid{Name: "BlockTime", Msg: "must be greater than 0"}) } - if c.Workflow != nil { - err = errors.Join(c.Workflow.Validate()) - } - return } @@ -248,13 +266,9 @@ func (c *TOMLConfig) TOMLString() (string, error) { var _ Config = &TOMLConfig{} func (c *TOMLConfig) WF() Workflow { - if c.Workflow != nil { - return &workflowConfig{ - conf: c.Workflow, - } + return &workflowConfig{ + conf: c.Workflow, } - - return nil } func (c *TOMLConfig) AcceptanceTimeout() time.Duration { @@ -262,34 +276,38 @@ func (c *TOMLConfig) AcceptanceTimeout() time.Duration { } func (c *TOMLConfig) Local() bool { - return c.Workflow.Local + return *c.Workflow.Local } func (c *TOMLConfig) PollPeriod() time.Duration { return c.Workflow.PollPeriod.Duration() } -func (c *TOMLConfig) ForwarderAddress() string { - return *c.Workflow.ForwarderAddress +func (c *TOMLConfig) ForwarderAddress() *solana.PublicKey { + return c.Workflow.ForwarderAddress } -func (c *TOMLConfig) FromAddress() string { - return *c.Workflow.FromAddress +func (c *TOMLConfig) FromAddress() *solana.PublicKey { + return c.Workflow.FromAddress } -func (c *TOMLConfig) ForwarderState() string { - return *c.Workflow.ForwarderState +func (c *TOMLConfig) ForwarderState() *solana.PublicKey { + return c.Workflow.ForwarderState } func (c *TOMLConfig) GasLimitDefault() *uint64 { return c.Workflow.GasLimitDefault } -func (c *TOMLConfig) TxAcceptanceState() *commontypes.TransactionStatus { +func (c *TOMLConfig) TxAcceptanceState() *types.TransactionStatus { return c.Workflow.TxAcceptanceState } type workflowConfig struct { - conf *WorkflowConfig + conf WorkflowConfig +} + +func (wc *workflowConfig) IsEnabled() bool { + return wc.conf.IsEnabled() } func (wc *workflowConfig) AcceptanceTimeout() time.Duration { @@ -298,23 +316,23 @@ func (wc *workflowConfig) AcceptanceTimeout() time.Duration { func (wc *workflowConfig) PollPeriod() time.Duration { return wc.conf.PollPeriod.Duration() } -func (wc *workflowConfig) ForwarderAddress() string { - return *wc.conf.ForwarderAddress +func (wc *workflowConfig) ForwarderAddress() *solana.PublicKey { + return wc.conf.ForwarderAddress } -func (wc *workflowConfig) FromAddress() string { - return *wc.conf.FromAddress +func (wc *workflowConfig) FromAddress() *solana.PublicKey { + return wc.conf.FromAddress } -func (wc *workflowConfig) ForwarderState() string { - return *wc.conf.ForwarderState +func (wc *workflowConfig) ForwarderState() *solana.PublicKey { + return wc.conf.ForwarderState } func (wc *workflowConfig) GasLimitDefault() *uint64 { return wc.conf.GasLimitDefault } -func (wc *workflowConfig) TxAcceptanceState() *commontypes.TransactionStatus { +func (wc *workflowConfig) TxAcceptanceState() *types.TransactionStatus { return wc.conf.TxAcceptanceState } func (wc *workflowConfig) Local() bool { - return wc.conf.Local + return *wc.conf.Local } func (c *TOMLConfig) BlockTime() time.Duration { @@ -423,61 +441,12 @@ func (c *TOMLConfig) ListNodes() Nodes { return c.Nodes } -func (c *TOMLConfig) SetDefaults() { - c.Chain.SetDefaults() - c.MultiNode.SetFrom(defaultMultiNodeConfig) -} - func NewDefault() *TOMLConfig { cfg := &TOMLConfig{} - cfg.Chain.SetDefaults() - cfg.MultiNode.SetFrom(defaultMultiNodeConfig) + cfg.SetDefaults() return cfg } -var defaultMultiNodeConfig = &mnCfg.MultiNodeConfig{ - MultiNode: mnCfg.MultiNode{ - // Have multinode disabled by default - Enabled: ptr(false), - /* Node Configs */ - // Failure threshold for polling set to 5 to tolerate some polling failures before taking action. - PollFailureThreshold: ptr(uint32(5)), - // Poll interval is set to 15 seconds to ensure timely updates while minimizing resource usage. - PollInterval: config.MustNewDuration(15 * time.Second), - // Selection mode defaults to priority level to enable using node priorities - SelectionMode: ptr(mn.NodeSelectionModePriorityLevel), - // The sync threshold is set to 10 to allow for some flexibility in node synchronization before considering it out of sync. - SyncThreshold: ptr(uint32(10)), - // Lease duration is set to 1 minute by default to allow node locks for a reasonable amount of time. - LeaseDuration: config.MustNewDuration(time.Minute), - // Node syncing is not relevant for Solana and is disabled by default. - NodeIsSyncingEnabled: ptr(false), - // The new heads polling interval is set to 5 seconds to ensure timely updates while minimizing resource usage. - NewHeadsPollInterval: config.MustNewDuration(5 * time.Second), - // The finalized block polling interval is set to 5 seconds to ensure timely updates while minimizing resource usage. - FinalizedBlockPollInterval: config.MustNewDuration(5 * time.Second), - // Repeatable read guarantee should be enforced by default. - EnforceRepeatableRead: ptr(true), - // The delay before declaring a node dead is set to 20 seconds to give nodes time to recover from temporary issues. - DeathDeclarationDelay: config.MustNewDuration(20 * time.Second), - // If set to true nodes will verify configured the ChainID against RPC Client ChainID - VerifyChainID: ptr(true), - /* Chain Configs */ - // Threshold for no new heads is set to 20 seconds, assuming that heads should update at a reasonable pace. - NodeNoNewHeadsThreshold: config.MustNewDuration(20 * time.Second), - // Similar to heads, finalized heads should be updated within 20 seconds. - NoNewFinalizedHeadsThreshold: config.MustNewDuration(20 * time.Second), - // Finality tags are used in Solana and enabled by default. - FinalityTagEnabled: ptr(true), - // Finality depth will not be used since finality tags are enabled. - FinalityDepth: ptr(uint32(0)), - // Finalized block offset allows for RPCs to be slightly behind the finalized block. - FinalizedBlockOffset: ptr(uint32(50)), - }, -} - func NewDefaultMultiNodeConfig() mnCfg.MultiNodeConfig { - cfg := mnCfg.MultiNodeConfig{} - cfg.SetFrom(defaultMultiNodeConfig) - return cfg + return NewDefault().MultiNode } diff --git a/pkg/solana/config/toml_test.go b/pkg/solana/config/toml_test.go new file mode 100644 index 000000000..03337b3b2 --- /dev/null +++ b/pkg/solana/config/toml_test.go @@ -0,0 +1,121 @@ +package config + +import ( + _ "embed" + "testing" + "time" + + "github.com/gagliardetto/solana-go" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-common/pkg/config" + "github.com/smartcontractkit/chainlink-common/pkg/config/configtest" + "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink-framework/multinode" + mnCfg "github.com/smartcontractkit/chainlink-framework/multinode/config" +) + +func TestDefaults_fieldsNotNil(t *testing.T) { + configtest.AssertFieldsNotNil(t, Defaults()) +} + +func TestDocsTOMLComplete(t *testing.T) { + configtest.AssertDocsTOMLComplete[TOMLConfig](t, docsTOML) +} + +//go:embed testdata/config-full.toml +var fullTOML string + +var fullConfig = TOMLConfig{ + ChainID: ptr("fake-chain"), + Enabled: ptr(true), + Chain: Chain{ + BlockTime: config.MustNewDuration(time.Hour), + BalancePollPeriod: config.MustNewDuration(time.Minute), + ConfirmPollPeriod: config.MustNewDuration(time.Second), + OCR2CachePollPeriod: config.MustNewDuration(time.Minute), + OCR2CacheTTL: config.MustNewDuration(time.Hour), + TxTimeout: config.MustNewDuration(time.Hour), + TxRetryTimeout: config.MustNewDuration(time.Minute), + TxConfirmTimeout: config.MustNewDuration(time.Second), + TxExpirationRebroadcast: ptr(false), + TxRetentionTimeout: config.MustNewDuration(0 * time.Second), + SkipPreflight: ptr(true), + Commitment: ptr("banana"), + MaxRetries: ptr[int64](7), + FeeEstimatorMode: ptr("fixed"), + ComputeUnitPriceMax: ptr[uint64](1000), + ComputeUnitPriceMin: ptr[uint64](10), + ComputeUnitPriceDefault: ptr[uint64](100), + FeeBumpPeriod: config.MustNewDuration(time.Minute), + BlockHistoryPollPeriod: config.MustNewDuration(time.Minute), + BlockHistorySize: ptr[uint64](1), + BlockHistoryBatchLoadSize: ptr[uint64](10), + ComputeUnitLimitDefault: ptr[uint32](100_000), + EstimateComputeUnitLimit: ptr(false), + LogPollerStartingLookback: config.MustNewDuration(24 * time.Hour), + }, + Workflow: WorkflowConfig{ + AcceptanceTimeout: config.MustNewDuration(42 * time.Second), + GasLimitDefault: ptr[uint64](3_000_000), + ForwarderAddress: ptr(solana.MustPublicKeyFromBase58("14grJpemFaf88c8tiVb77W7TYg2W3ir6pfkKz3YjhhZ5")), + ForwarderState: ptr(solana.MustPublicKeyFromBase58("4BJXYkfvg37zEmBbsacZjeQDpTNx91KppxFJxRqrz48e")), + FromAddress: ptr(solana.MustPublicKeyFromBase58("14grJpemFaf88c8tiVb77W7TYg2W3ir6pfkKz3YjhhZ5")), + Local: ptr(true), + PollPeriod: config.MustNewDuration(9 * time.Second), + TxAcceptanceState: ptr(types.Finalized), + }, + MultiNode: mnCfg.MultiNodeConfig{ + MultiNode: mnCfg.MultiNode{ + Enabled: ptr(false), + PollFailureThreshold: ptr[uint32](5), + PollInterval: config.MustNewDuration(time.Second), + SelectionMode: ptr(multinode.NodeSelectionModeHighestHead), + SyncThreshold: ptr[uint32](5), + NodeIsSyncingEnabled: ptr(false), + LeaseDuration: config.MustNewDuration(time.Minute), + NewHeadsPollInterval: config.MustNewDuration(2 * time.Second), + FinalizedBlockPollInterval: config.MustNewDuration(3 * time.Second), + EnforceRepeatableRead: ptr(true), + DeathDeclarationDelay: config.MustNewDuration(2 * time.Minute), + VerifyChainID: ptr(true), + NodeNoNewHeadsThreshold: config.MustNewDuration(3 * time.Minute), + NoNewFinalizedHeadsThreshold: config.MustNewDuration(time.Hour), + FinalityDepth: ptr[uint32](0), + FinalityTagEnabled: ptr(true), + FinalizedBlockOffset: ptr[uint32](0), + }, + }, + Nodes: Nodes{ + { + Name: ptr("primary"), + URL: config.MustParseURL("http://solana.web"), + Order: ptr[int32](1), + IsLoadBalancedRPC: ptr(false), + }, + { + Name: ptr("foo"), + URL: config.MustParseURL("http://solana.foo"), + SendOnly: true, + Order: ptr[int32](2), + IsLoadBalancedRPC: ptr(true), + }, + { + Name: ptr("bar"), + URL: config.MustParseURL("http://solana.bar"), + SendOnly: true, + Order: ptr[int32](2), + IsLoadBalancedRPC: ptr(true), + }, + }, +} + +func TestTOMLConfig_FullMarshal(t *testing.T) { + configtest.AssertFullMarshal(t, fullConfig, fullTOML) +} + +func TestTOMLConfig_SetFrom(t *testing.T) { + var config TOMLConfig + config.SetFrom(&fullConfig) + require.Equal(t, fullConfig, config) +} diff --git a/pkg/solana/config_test.go b/pkg/solana/config_test.go new file mode 100644 index 000000000..66f77ebe3 --- /dev/null +++ b/pkg/solana/config_test.go @@ -0,0 +1,22 @@ +package solana + +import ( + _ "embed" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" +) + +var ( + //go:embed CONFIG.md + configMD string +) + +//go:generate go run ./cmd/config-docs -o CONFIG.md +func TestConfigDocs(t *testing.T) { + cfg, err := config.GenerateDocs() + assert.NoError(t, err, "invalid config docs") + assert.Equal(t, configMD, cfg, "CONFIG.md is out of date. Run 'go generate' to regenerate.") +} diff --git a/pkg/solana/relay.go b/pkg/solana/relay.go index 9ef210ea8..9a7b77d21 100644 --- a/pkg/solana/relay.go +++ b/pkg/solana/relay.go @@ -79,7 +79,8 @@ func (r *Relayer) Start(ctx context.Context) error { if err != nil { return err } - if r.chain.Config().WF() != nil { + //TODO only "enabled" check + if wfCfg := r.chain.Config().WF(); wfCfg.IsEnabled() { if r.capabilitiesRegistry == nil { r.lggr.Errorw("workflow config is provided but capabilities registry is not set") return nil @@ -87,7 +88,7 @@ func (r *Relayer) Start(ctx context.Context) error { var info relaytypes.ChainInfo - if r.chain.Config().WF().Local() { + if wfCfg.Local() { info = relaytypes.ChainInfo{ FamilyName: "Solana", ChainID: r.chain.ID(), @@ -106,7 +107,7 @@ func (r *Relayer) Start(ctx context.Context) error { return fmt.Errorf("failed to initialise write target capability: %w", err) } - dr, err := writetarget.NewDeriveRemaining(r.chain, r.chain.MultiClient(), r.chain.Config().WF(), r.lggr) + dr, err := writetarget.NewDeriveRemaining(r.chain, r.chain.MultiClient(), wfCfg, r.lggr) if err != nil { return fmt.Errorf("failed to initialise derive remaining capability: %w", err) } diff --git a/pkg/solana/txm/mocks/simple_keystore.go b/pkg/solana/txm/mocks/simple_keystore.go index d1d4719a9..f258d72d4 100644 --- a/pkg/solana/txm/mocks/simple_keystore.go +++ b/pkg/solana/txm/mocks/simple_keystore.go @@ -79,6 +79,66 @@ func (_c *SimpleKeystore_Accounts_Call) RunAndReturn(run func(context.Context) ( return _c } +// Decrypt provides a mock function with given fields: ctx, account, encrypted +func (_m *SimpleKeystore) Decrypt(ctx context.Context, account string, encrypted []byte) ([]byte, error) { + ret := _m.Called(ctx, account, encrypted) + + if len(ret) == 0 { + panic("no return value specified for Decrypt") + } + + var r0 []byte + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, []byte) ([]byte, error)); ok { + return rf(ctx, account, encrypted) + } + if rf, ok := ret.Get(0).(func(context.Context, string, []byte) []byte); ok { + r0 = rf(ctx, account, encrypted) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, []byte) error); ok { + r1 = rf(ctx, account, encrypted) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SimpleKeystore_Decrypt_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Decrypt' +type SimpleKeystore_Decrypt_Call struct { + *mock.Call +} + +// Decrypt is a helper method to define mock.On call +// - ctx context.Context +// - account string +// - encrypted []byte +func (_e *SimpleKeystore_Expecter) Decrypt(ctx interface{}, account interface{}, encrypted interface{}) *SimpleKeystore_Decrypt_Call { + return &SimpleKeystore_Decrypt_Call{Call: _e.mock.On("Decrypt", ctx, account, encrypted)} +} + +func (_c *SimpleKeystore_Decrypt_Call) Run(run func(ctx context.Context, account string, encrypted []byte)) *SimpleKeystore_Decrypt_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].([]byte)) + }) + return _c +} + +func (_c *SimpleKeystore_Decrypt_Call) Return(decrypted []byte, err error) *SimpleKeystore_Decrypt_Call { + _c.Call.Return(decrypted, err) + return _c +} + +func (_c *SimpleKeystore_Decrypt_Call) RunAndReturn(run func(context.Context, string, []byte) ([]byte, error)) *SimpleKeystore_Decrypt_Call { + _c.Call.Return(run) + return _c +} + // Sign provides a mock function with given fields: ctx, account, data func (_m *SimpleKeystore) Sign(ctx context.Context, account string, data []byte) ([]byte, error) { ret := _m.Called(ctx, account, data) diff --git a/pkg/solana/write_target/target_strategy.go b/pkg/solana/write_target/target_strategy.go index aa304b72f..de02675a0 100644 --- a/pkg/solana/write_target/target_strategy.go +++ b/pkg/solana/write_target/target_strategy.go @@ -3,15 +3,15 @@ package writetarget import ( "context" "crypto/sha256" + "encoding/binary" "errors" "fmt" "math/big" - "encoding/binary" - "github.com/gagliardetto/solana-go" "github.com/google/uuid" "github.com/shopspring/decimal" + "github.com/smartcontractkit/chainlink-common/pkg/capabilities" ocr3types "github.com/smartcontractkit/chainlink-common/pkg/capabilities/consensus/ocr3/types" "github.com/smartcontractkit/chainlink-common/pkg/logger" @@ -449,23 +449,21 @@ func createReportHash(dataID []byte, forwarderAuthority []byte, workflowOwner [] return sha256.Sum256(data) } -func accountsFromConfig(cfg config.Workflow) (accounts, error) { - var ret accounts - var err error - ret.forwarderProgramID, err = solana.PublicKeyFromBase58(cfg.ForwarderAddress()) - if err != nil { - return ret, err +func accountsFromConfig(cfg config.Workflow) (ret accounts, err error) { + if addr := cfg.ForwarderAddress(); addr == nil { + err = errors.Join(err, errors.New("missing forwarder address")) + } else { + ret.forwarderProgramID = *addr } - - ret.forwarderState, err = solana.PublicKeyFromBase58(cfg.ForwarderState()) - if err != nil { - return ret, err + if addr := cfg.ForwarderState(); addr == nil { + err = errors.Join(errors.New("missing forwarder address")) + } else { + ret.forwarderState = *addr } - - ret.transmitter, err = solana.PublicKeyFromBase58(cfg.FromAddress()) - if err != nil { - return ret, err + if addr := cfg.FromAddress(); addr == nil { + err = errors.Join(errors.New("missing from address")) + } else { + ret.transmitter = *addr } - - return ret, nil + return } diff --git a/pkg/solana/write_target/write_target.go b/pkg/solana/write_target/write_target.go index 9b925d9d2..fb8f4e935 100644 --- a/pkg/solana/write_target/write_target.go +++ b/pkg/solana/write_target/write_target.go @@ -4,10 +4,12 @@ import ( "bytes" "context" "encoding/hex" + "errors" "fmt" "strings" chainselectors "github.com/smartcontractkit/chain-selectors" + "github.com/smartcontractkit/chainlink-common/pkg/capabilities" "github.com/smartcontractkit/chainlink-common/pkg/logger" commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" @@ -58,6 +60,15 @@ func New(ctx context.Context, chain Chain, reader client.Reader, txm Txm, chainI return nil, fmt.Errorf("failed to create target strategy: %+w", err) } + fromAddr := cfg.FromAddress() + if fromAddr == nil { + return nil, errors.New("missing from address") + } + fwdrAddr := cfg.ForwarderAddress() + if fwdrAddr == nil { + return nil, errors.New("missing forwarder address") + } + opts := writetarget.WriteTargetOpts{ ID: id, Logger: lggr, @@ -69,8 +80,8 @@ func New(ctx context.Context, chain Chain, reader client.Reader, txm Txm, chainI Beholder: beholder, ChainService: chain, ConfigValidateFn: evaluate, - NodeAddress: cfg.FromAddress(), - ForwarderAddress: cfg.ForwarderAddress(), + NodeAddress: fromAddr.String(), + ForwarderAddress: fwdrAddr.String(), TargetStrategy: ts, WriteAcceptanceState: *cfg.TxAcceptanceState(), }