From f6d19a7ce99eaa325350829154995535cd486af3 Mon Sep 17 00:00:00 2001 From: aalu1418 <50029043+aalu1418@users.noreply.github.com> Date: Thu, 26 Sep 2024 09:48:07 -0600 Subject: [PATCH] add test for transferring SOl from node --- pkg/solana/chain.go | 11 +++- pkg/solana/chain_test.go | 73 +++++++++++++++++++++++++ pkg/solana/txm/mocks/simple_keystore.go | 30 ++++++++++ pkg/solana/txm/txm.go | 4 ++ 4 files changed, 116 insertions(+), 2 deletions(-) diff --git a/pkg/solana/chain.go b/pkg/solana/chain.go index 8b4ad5787..c6bef9680 100644 --- a/pkg/solana/chain.go +++ b/pkg/solana/chain.go @@ -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) } diff --git a/pkg/solana/chain_test.go b/pkg/solana/chain_test.go index 6fb966740..4097e38dd 100644 --- a/pkg/solana/chain_test.go +++ b/pkg/solana/chain_test.go @@ -1,18 +1,24 @@ 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" @@ -20,6 +26,8 @@ import ( "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}` @@ -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) +} diff --git a/pkg/solana/txm/mocks/simple_keystore.go b/pkg/solana/txm/mocks/simple_keystore.go index 86c9d3f17..1c0bd6562 100644 --- a/pkg/solana/txm/mocks/simple_keystore.go +++ b/pkg/solana/txm/mocks/simple_keystore.go @@ -13,6 +13,36 @@ type SimpleKeystore struct { mock.Mock } +// Accounts provides a mock function with given fields: ctx +func (_m *SimpleKeystore) Accounts(ctx context.Context) ([]string, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for Accounts") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) ([]string, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) []string); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // 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/txm/txm.go b/pkg/solana/txm/txm.go index 50d153be3..f3b8dfdea 100644 --- a/pkg/solana/txm/txm.go +++ b/pkg/solana/txm/txm.go @@ -13,6 +13,7 @@ import ( "github.com/google/uuid" "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/loop" "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink-common/pkg/utils" @@ -32,8 +33,11 @@ var _ services.Service = (*Txm)(nil) //go:generate mockery --name SimpleKeystore --output ./mocks/ --case=underscore --filename simple_keystore.go type SimpleKeystore interface { Sign(ctx context.Context, account string, data []byte) (signature []byte, err error) + Accounts(ctx context.Context) (accounts []string, err error) } +var _ loop.Keystore = (SimpleKeystore)(nil) + // Txm manages transactions for the solana blockchain. // simple implementation with no persistently stored txs type Txm struct {