diff --git a/pkg/solana/chain.go b/pkg/solana/chain.go index 0ccdf0e46..d137e75de 100644 --- a/pkg/solana/chain.go +++ b/pkg/solana/chain.go @@ -95,8 +95,9 @@ type chain struct { lggr logger.Logger // if multiNode is enabled, the clientCache will not be used - multiNode *mn.MultiNode[mn.StringID, *client.MultiNodeClient] - txSender *mn.TransactionSender[*solanago.Transaction, *client.SendTxResult, mn.StringID, *client.MultiNodeClient] + multiNode *mn.MultiNode[mn.StringID, *client.MultiNodeClient] + txSender *mn.TransactionSender[*solanago.Transaction, *client.SendTxResult, mn.StringID, *client.MultiNodeClient] + multiClient *client.MultiClient // tracking node chain id for verification clientCache map[string]*verifiedCachedClient // map URL -> {client, chainId} [mainnet/testnet/devnet/localnet] @@ -238,6 +239,8 @@ func newChain(id string, cfg *config.TOMLConfig, ks core.Keystore, lggr logger.L var tc internal.Loader[client.ReaderWriter] = utils.NewLazyLoad(func() (client.ReaderWriter, error) { return ch.getClient() }) var bc internal.Loader[monitor.BalanceClient] = utils.NewLazyLoad(func() (monitor.BalanceClient, error) { return ch.getClient() }) + // getClient returns random client or if MultiNodeEnabled RPC picked and controlled by MultiNode + ch.multiClient = client.NewMultiClient(ch.getClient) // txm will default to sending transactions using a single RPC client if sendTx is nil var sendTx func(ctx context.Context, tx *solanago.Transaction) (solanago.Signature, error) diff --git a/pkg/solana/client/multi_client.go b/pkg/solana/client/multi_client.go new file mode 100644 index 000000000..eb159e114 --- /dev/null +++ b/pkg/solana/client/multi_client.go @@ -0,0 +1,168 @@ +package client + +import ( + "context" + + "github.com/gagliardetto/solana-go" + "github.com/gagliardetto/solana-go/rpc" + + mn "github.com/smartcontractkit/chainlink-solana/pkg/solana/client/multinode" +) + +var _ ReaderWriter = (*MultiClient)(nil) + +// MultiClient - wrapper over multiple RPCs, underlying provider can be MultiNode or LazyLoader. +// Main purpose is to eliminate need for frequent error handling on selection of a client. +type MultiClient struct { + getClient func() (ReaderWriter, error) +} + +func NewMultiClient(getClient func() (ReaderWriter, error)) *MultiClient { + return &MultiClient{ + getClient: getClient, + } +} + +func (m *MultiClient) GetLatestBlockHeight(ctx context.Context) (uint64, error) { + r, err := m.getClient() + if err != nil { + return 0, err + } + + return r.GetLatestBlockHeight(ctx) +} + +func (m *MultiClient) SendTx(ctx context.Context, tx *solana.Transaction) (solana.Signature, error) { + r, err := m.getClient() + if err != nil { + return solana.Signature{}, err + } + + return r.SendTx(ctx, tx) +} + +func (m *MultiClient) SimulateTx(ctx context.Context, tx *solana.Transaction, opts *rpc.SimulateTransactionOpts) (*rpc.SimulateTransactionResult, error) { + r, err := m.getClient() + if err != nil { + return nil, err + } + + return r.SimulateTx(ctx, tx, opts) +} + +func (m *MultiClient) SignatureStatuses(ctx context.Context, sigs []solana.Signature) ([]*rpc.SignatureStatusesResult, error) { + r, err := m.getClient() + if err != nil { + return nil, err + } + + return r.SignatureStatuses(ctx, sigs) +} + +func (m *MultiClient) GetAccountInfoWithOpts(ctx context.Context, addr solana.PublicKey, opts *rpc.GetAccountInfoOpts) (*rpc.GetAccountInfoResult, error) { + r, err := m.getClient() + if err != nil { + return nil, err + } + + return r.GetAccountInfoWithOpts(ctx, addr, opts) +} + +func (m *MultiClient) Balance(ctx context.Context, addr solana.PublicKey) (uint64, error) { + r, err := m.getClient() + if err != nil { + return 0, err + } + + return r.Balance(ctx, addr) +} + +func (m *MultiClient) SlotHeight(ctx context.Context) (uint64, error) { + r, err := m.getClient() + if err != nil { + return 0, err + } + + return r.SlotHeight(ctx) +} + +func (m *MultiClient) LatestBlockhash(ctx context.Context) (*rpc.GetLatestBlockhashResult, error) { + r, err := m.getClient() + if err != nil { + return nil, err + } + + return r.LatestBlockhash(ctx) +} + +func (m *MultiClient) ChainID(ctx context.Context) (mn.StringID, error) { + r, err := m.getClient() + if err != nil { + return "", err + } + + return r.ChainID(ctx) +} + +func (m *MultiClient) GetFeeForMessage(ctx context.Context, msg string) (uint64, error) { + r, err := m.getClient() + if err != nil { + return 0, err + } + + return r.GetFeeForMessage(ctx, msg) +} + +func (m *MultiClient) GetLatestBlock(ctx context.Context) (*rpc.GetBlockResult, error) { + r, err := m.getClient() + if err != nil { + return nil, err + } + + return r.GetLatestBlock(ctx) +} + +func (m *MultiClient) GetTransaction(ctx context.Context, txHash solana.Signature, opts *rpc.GetTransactionOpts) (*rpc.GetTransactionResult, error) { + r, err := m.getClient() + if err != nil { + return nil, err + } + + return r.GetTransaction(ctx, txHash, opts) +} + +func (m *MultiClient) GetBlocks(ctx context.Context, startSlot uint64, endSlot *uint64) (rpc.BlocksResult, error) { + r, err := m.getClient() + if err != nil { + return nil, err + } + + return r.GetBlocks(ctx, startSlot, endSlot) +} + +func (m *MultiClient) GetBlocksWithLimit(ctx context.Context, startSlot uint64, limit uint64) (*rpc.BlocksResult, error) { + r, err := m.getClient() + if err != nil { + return nil, err + } + + return r.GetBlocksWithLimit(ctx, startSlot, limit) +} + +func (m *MultiClient) GetBlock(ctx context.Context, slot uint64) (*rpc.GetBlockResult, error) { + r, err := m.getClient() + if err != nil { + return nil, err + } + + return r.GetBlock(ctx, slot) +} + +func (m *MultiClient) GetSignaturesForAddressWithOpts(ctx context.Context, addr solana.PublicKey, opts *rpc.GetSignaturesForAddressOpts) ([]*rpc.TransactionSignature, error) { + r, err := m.getClient() + if err != nil { + return nil, err + } + + return r.GetSignaturesForAddressWithOpts(ctx, addr, opts) +}