Skip to content

Commit

Permalink
Add compute unit limit to relayer transactions (#865)
Browse files Browse the repository at this point in the history
* validate: setting 200_000 compute unit limit + F = 6 signatures

* can build SetComputeUnitLimit instruction

* add ComputeUnitLimitDefault to config + config cleanup

* assemble transaction with configurable parameters

* add test for transferring SOl from node

* fix: proper test txm shutdown

* fix ambiguous SetDefaults selector on TOML config

* can disable ComputeUnitLimitDefault if needed

* trigger build
  • Loading branch information
aalu1418 authored Sep 27, 2024
1 parent f318966 commit 7e527aa
Show file tree
Hide file tree
Showing 19 changed files with 651 additions and 203 deletions.
5 changes: 2 additions & 3 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
nodejs 18.20.2
yarn 1.22.19
rust 1.59.0
golang 1.21.7
golangci-lint 1.55.2
pulumi 3.40.1
golang 1.22.5
golangci-lint 1.60.1
actionlint 1.6.22
shellcheck 0.8.0
helm 3.9.4
Expand Down
7 changes: 6 additions & 1 deletion contracts/tests/ocr2.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,11 @@ describe("ocr2", () => {
]),
})
);
tx.add(
ComputeBudgetProgram.setComputeUnitLimit({
units: 200_000, // use default limit
})
);

try {
return await provider.sendAndConfirm(tx, [transmitter]);
Expand Down Expand Up @@ -914,7 +919,7 @@ describe("ocr2", () => {

it("Transmit a bunch of rounds to check ringbuffer wraparound", async () => {
for (let i = 2; i <= rounds; i++) {
let transmitTx = await transmit(feed.publicKey, i, i, new BN(i));
let transmitTx = await transmit(feed.publicKey, i, i, new BN(i), i);

await provider.connection.confirmTransaction(transmitTx, "confirmed");
let t = await provider.connection.getTransaction(transmitTx, {
Expand Down
107 changes: 85 additions & 22 deletions contracts/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# yarn lockfile v1


"@babel/runtime@^7.17.2", "@babel/runtime@^7.23.4", "@babel/runtime@^7.24.8":
"@babel/runtime@^7.17.2", "@babel/runtime@^7.23.4":
version "7.25.0"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.0.tgz#3af9a91c1b739c569d5d80cc917280919c544ecb"
integrity sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==
Expand All @@ -16,6 +16,13 @@
dependencies:
regenerator-runtime "^0.14.0"

"@babel/runtime@^7.24.8", "@babel/runtime@^7.25.0":
version "7.25.6"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.6.tgz#9afc3289f7184d8d7f98b099884c26317b9264d2"
integrity sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==
dependencies:
regenerator-runtime "^0.14.0"

"@chainlink/solana-sdk@../ts":
version "0.2.2"
dependencies:
Expand Down Expand Up @@ -50,7 +57,7 @@
bn.js "^5.1.2"
buffer-layout "^1.2.0"

"@noble/curves@^1.2.0", "@noble/curves@^1.4.2":
"@noble/curves@^1.2.0":
version "1.5.0"
resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.5.0.tgz#7a9b9b507065d516e6dce275a1e31db8d2a100dd"
integrity sha512-J5EKamIHnKPyClwVrzmaf5wSdQXgdHcPZIZLu3bwnbeCx8/7NPK5q2ZBWF+5FvYGByjiQQsJYX6jfgB2wDPn3A==
Expand All @@ -64,11 +71,23 @@
dependencies:
"@noble/hashes" "1.4.0"

"@noble/[email protected]", "@noble/hashes@^1.3.1", "@noble/hashes@^1.3.3", "@noble/hashes@^1.4.0":
"@noble/curves@^1.4.2":
version "1.6.0"
resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.6.0.tgz#be5296ebcd5a1730fccea4786d420f87abfeb40b"
integrity sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ==
dependencies:
"@noble/hashes" "1.5.0"

"@noble/[email protected]", "@noble/hashes@^1.3.1", "@noble/hashes@^1.3.3":
version "1.4.0"
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426"
integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==

"@noble/[email protected]", "@noble/hashes@^1.4.0":
version "1.5.0"
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.5.0.tgz#abadc5ca20332db2b1b2aa3e496e9af1213570b0"
integrity sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==

"@solana/buffer-layout-utils@^0.2.0":
version "0.2.0"
resolved "https://registry.yarnpkg.com/@solana/buffer-layout-utils/-/buffer-layout-utils-0.2.0.tgz#b45a6cab3293a2eb7597cceb474f229889d875ca"
Expand Down Expand Up @@ -172,12 +191,12 @@
rpc-websockets "^7.5.1"
superstruct "^0.14.2"

"@solana/web3.js@^1.50.1", "@solana/web3.js@^1.68.0":
version "1.95.2"
resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.95.2.tgz#6f8a0362fa75886a21550dbec49aad54481463a6"
integrity sha512-SjlHp0G4qhuhkQQc+YXdGkI8EerCqwxvgytMgBpzMUQTafrkNant3e7pgilBGgjy/iM40ICvWBLgASTPMrQU7w==
"@solana/web3.js@^1.50.1":
version "1.95.3"
resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.95.3.tgz#70b5f4d76823f56b5af6403da51125fffeb65ff3"
integrity sha512-O6rPUN0w2fkNqx/Z3QJMB9L225Ex10PRDH8bTaIUPZXMPV0QP8ZpPvjQnXK+upUczlRgzHzd6SjKIha1p+I6og==
dependencies:
"@babel/runtime" "^7.24.8"
"@babel/runtime" "^7.25.0"
"@noble/curves" "^1.4.2"
"@noble/hashes" "^1.4.0"
"@solana/buffer-layout" "^4.0.1"
Expand Down Expand Up @@ -214,10 +233,31 @@
rpc-websockets "^8.0.1"
superstruct "^1.0.4"

"@solana/web3.js@^1.68.0":
version "1.95.2"
resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.95.2.tgz#6f8a0362fa75886a21550dbec49aad54481463a6"
integrity sha512-SjlHp0G4qhuhkQQc+YXdGkI8EerCqwxvgytMgBpzMUQTafrkNant3e7pgilBGgjy/iM40ICvWBLgASTPMrQU7w==
dependencies:
"@babel/runtime" "^7.24.8"
"@noble/curves" "^1.4.2"
"@noble/hashes" "^1.4.0"
"@solana/buffer-layout" "^4.0.1"
agentkeepalive "^4.5.0"
bigint-buffer "^1.1.5"
bn.js "^5.2.1"
borsh "^0.7.0"
bs58 "^4.0.1"
buffer "6.0.3"
fast-stable-stringify "^1.0.0"
jayson "^4.1.1"
node-fetch "^2.7.0"
rpc-websockets "^9.0.2"
superstruct "^2.0.2"

"@swc/helpers@^0.5.11":
version "0.5.12"
resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.12.tgz#37aaca95284019eb5d2207101249435659709f4b"
integrity sha512-KMZNXiGibsW9kvZAO1Pam2JPTDBm+KSHMMHWdsyI/1DbIZjT2A6Gy3hblVXUMEDvUAKq+e0vL0X0o54owWji7g==
version "0.5.13"
resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.13.tgz#33e63ff3cd0cade557672bd7888a39ce7d115a8c"
integrity sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w==
dependencies:
tslib "^2.4.0"

Expand All @@ -244,9 +284,9 @@
integrity sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==

"@types/node@*":
version "22.4.0"
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.4.0.tgz#c295fe1d6f5f58916cc61dbef8cf65b5b9b71de9"
integrity sha512-49AbMDwYUz7EXxKU/r7mXOsxwFr4BYbvB7tWYxVuLdb2ibd30ijjXINSMAHiEEZk5PCRBmW1gUeisn2VMKt3cQ==
version "22.6.0"
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.6.0.tgz#b604c9a628760221905c1b272fd6aee661f45042"
integrity sha512-QyR8d5bmq+eR72TwQDfujwShHMcIrWIYsaQFtXRE58MHPTEKUNxjxvl0yS0qPMds5xbSDWtp7ZpvGFtd7dfMdQ==
dependencies:
undici-types "~6.19.2"

Expand Down Expand Up @@ -933,7 +973,7 @@ isomorphic-ws@^4.0.1:
resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc"
integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==

jayson@^4.1.0, jayson@^4.1.1:
jayson@^4.1.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/jayson/-/jayson-4.1.1.tgz#282ff13d3cea09776db684b7eeca98c47b2fa99a"
integrity sha512-5ZWm4Q/0DHPyeMfAsrwViwUS2DMVsQgWh8bEEIVTkfb3DzHZ2L3G5WUnF+AKmGjjM9r1uAv73SaqC1/U4RL45w==
Expand All @@ -951,6 +991,24 @@ jayson@^4.1.0, jayson@^4.1.1:
uuid "^8.3.2"
ws "^7.5.10"

jayson@^4.1.1:
version "4.1.2"
resolved "https://registry.yarnpkg.com/jayson/-/jayson-4.1.2.tgz#443c26a8658703e0b2e881117b09395d88b6982e"
integrity sha512-5nzMWDHy6f+koZOuYsArh2AXs73NfWYVlFyJJuCedr93GpY+Ku8qq10ropSXVfHK+H0T6paA88ww+/dV+1fBNA==
dependencies:
"@types/connect" "^3.4.33"
"@types/node" "^12.12.54"
"@types/ws" "^7.4.4"
JSONStream "^1.3.5"
commander "^2.20.3"
delay "^5.0.0"
es6-promisify "^5.0.0"
eyes "^0.1.8"
isomorphic-ws "^4.0.1"
json-stringify-safe "^5.0.1"
uuid "^8.3.2"
ws "^7.5.10"

[email protected]:
version "4.1.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
Expand Down Expand Up @@ -1134,9 +1192,9 @@ node-gyp-build@^4.2.0:
integrity sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==

node-gyp-build@^4.3.0:
version "4.8.1"
resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.8.1.tgz#976d3ad905e71b76086f4f0b0d3637fe79b6cda5"
integrity sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==
version "4.8.2"
resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.8.2.tgz#4f802b71c1ab2ca16af830e6c1ea7dd1ad9496fa"
integrity sha512-IRUxE4BVsHWXkV/SFOut4qTlagw2aM8T5/vnTsmrHJvVoKueJHRc/JaFND7QDDc61kLYUJ6qlZM3sqTSyx2dTw==

normalize-path@^3.0.0, normalize-path@~3.0.0:
version "3.0.0"
Expand Down Expand Up @@ -1486,11 +1544,16 @@ tsconfig-paths@^3.5.0:
minimist "^1.2.6"
strip-bom "^3.0.0"

tslib@^2.0.3, tslib@^2.4.0:
tslib@^2.0.3:
version "2.6.3"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0"
integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==

tslib@^2.4.0:
version "2.7.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01"
integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==

type-detect@^4.0.0, type-detect@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
Expand All @@ -1502,9 +1565,9 @@ typescript@^4.5.4:
integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==

undici-types@~6.19.2:
version "6.19.6"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.6.tgz#e218c3df0987f4c0e0008ca00d6b6472d9b89b36"
integrity sha512-e/vggGopEfTKSvj4ihnOLTsqhrKRN3LeO6qSN/GxohhuRv8qH9bNQ4B8W7e/vFL+0XTnmHPB4/kegunZGA4Org==
version "6.19.8"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02"
integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==

utf-8-validate@^5.0.2:
version "5.0.10"
Expand Down
2 changes: 1 addition & 1 deletion pkg/monitoring/types/txdetails.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ func ParseTx(tx *solanaGo.Transaction, programAddr solanaGo.PublicKey) (TxDetail
}

// find compute budget program instruction
if tx.Message.AccountKeys[instruction.ProgramIDIndex] == solanaGo.MustPublicKeyFromBase58(fees.ComputeBudgetProgram) {
if tx.Message.AccountKeys[instruction.ProgramIDIndex] == fees.ComputeBudgetProgram {
// parsing compute unit price
var err error
txDetails.ComputeUnitPrice, err = fees.ParseComputeUnitPrice(instruction.Data)
Expand Down
11 changes: 9 additions & 2 deletions pkg/solana/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -561,8 +561,15 @@ func (c *chain) sendTx(ctx context.Context, from, to string, amount *big.Int, ba
}
}

txm := c.TxManager()
err = txm.Enqueue("", tx)
chainTxm := c.TxManager()
err = chainTxm.Enqueue("", tx,
txm.SetComputeUnitLimit(500), // reduce from default 200K limit - should only take 450 compute units
// no fee bumping and no additional fee - makes validating balance accurate
txm.SetComputeUnitPriceMax(0),
txm.SetComputeUnitPriceMin(0),
txm.SetBaseComputeUnitPrice(0),
txm.SetFeeBumpPeriod(0),
)
if err != nil {
return fmt.Errorf("transaction failed: %w", err)
}
Expand Down
73 changes: 73 additions & 0 deletions pkg/solana/chain_test.go
Original file line number Diff line number Diff line change
@@ -1,25 +1,33 @@
package solana

import (
"context"
"errors"
"fmt"
"io"
"math/big"
"net/http"
"net/http/httptest"
"strings"
"sync"
"testing"

"github.com/gagliardetto/solana-go"
"github.com/gagliardetto/solana-go/rpc"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"go.uber.org/zap/zapcore"

"github.com/smartcontractkit/chainlink-common/pkg/config"
"github.com/smartcontractkit/chainlink-common/pkg/logger"
"github.com/smartcontractkit/chainlink-common/pkg/utils/tests"

"github.com/smartcontractkit/chainlink-solana/pkg/solana/client"
solcfg "github.com/smartcontractkit/chainlink-solana/pkg/solana/config"
"github.com/smartcontractkit/chainlink-solana/pkg/solana/fees"
"github.com/smartcontractkit/chainlink-solana/pkg/solana/txm/mocks"
)

const TestSolanaGenesisHashTemplate = `{"jsonrpc":"2.0","result":"%s","id":1}`
Expand Down Expand Up @@ -238,3 +246,68 @@ func TestSolanaChain_VerifiedClient_ParallelClients(t *testing.T) {
func ptr[T any](t T) *T {
return &t
}

func TestChain_Transact(t *testing.T) {
ctx := tests.Context(t)
url := client.SetupLocalSolNode(t)
lgr, logs := logger.TestObserved(t, zapcore.DebugLevel)

// transaction parameters
sender, err := solana.NewRandomPrivateKey()
require.NoError(t, err)
receiver, err := solana.NewRandomPrivateKey()
require.NoError(t, err)
amount := big.NewInt(100_000_000_000 - 5_000) // total balance - tx fee
client.FundTestAccounts(t, solana.PublicKeySlice{sender.PublicKey()}, url)

// configuration
cfg := solcfg.NewDefault()
cfg.Nodes = append(cfg.Nodes, &solcfg.Node{
Name: ptr("localnet-" + t.Name()),
URL: config.MustParseURL(url),
SendOnly: false,
})

// mocked keystore
mkey := mocks.NewSimpleKeystore(t)
mkey.On("Sign", mock.Anything, sender.PublicKey().String(), mock.Anything).Return(func(_ context.Context, _ string, data []byte) []byte {
sig, _ := sender.Sign(data)
return sig[:]
}, nil)

c, err := newChain("localnet", cfg, mkey, lgr)
require.NoError(t, err)
require.NoError(t, c.txm.Start(ctx))

require.NoError(t, c.Transact(ctx, sender.PublicKey().String(), receiver.PublicKey().String(), amount, true))
tests.AssertLogEventually(t, logs, "tx state: confirmed")
tests.AssertLogEventually(t, logs, "stopped tx retry")
require.NoError(t, c.txm.Close())

filteredLogs := logs.FilterMessage("tx state: confirmed").All()
require.Len(t, filteredLogs, 1)
sig, ok := filteredLogs[0].ContextMap()["signature"]
require.True(t, ok)

// inspect transaction
solClient := rpc.New(url)
res, err := solClient.GetTransaction(ctx, solana.MustSignatureFromBase58(sig.(string)), &rpc.GetTransactionOpts{Commitment: "confirmed"})
require.NoError(t, err)
require.Nil(t, res.Meta.Err) // no error

// validate balances change as expected
require.Equal(t, amount.Uint64()+5_000, res.Meta.PreBalances[0])
require.Zero(t, res.Meta.PostBalances[0])
require.Zero(t, res.Meta.PreBalances[1])
require.Equal(t, amount.Uint64(), res.Meta.PostBalances[1])

tx, err := res.Transaction.GetTransaction()
require.NoError(t, err)
require.Len(t, tx.Message.Instructions, 3)
price, err := fees.ParseComputeUnitPrice(tx.Message.Instructions[0].Data)
require.NoError(t, err)
assert.Equal(t, fees.ComputeUnitPrice(0), price)
limit, err := fees.ParseComputeUnitLimit(tx.Message.Instructions[2].Data)
require.NoError(t, err)
assert.Equal(t, fees.ComputeUnitLimit(500), limit)
}
Loading

0 comments on commit 7e527aa

Please sign in to comment.