Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement relayer chain components constructors #1025

Merged
merged 9 commits into from
Jan 25, 2025
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ func (h *helper) Init(t *testing.T) {
}

func (h *helper) RPCClient() *chainreader.RPCClientWrapper {
return &chainreader.RPCClientWrapper{Client: h.rpcClient}
return &chainreader.RPCClientWrapper{AccountReader: h.rpcClient}
}

func (h *helper) Context(t *testing.T) context.Context {
Expand Down
7 changes: 7 additions & 0 deletions pkg/solana/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import (
"github.com/smartcontractkit/chainlink-common/pkg/utils"
mn "github.com/smartcontractkit/chainlink-framework/multinode"

"github.com/smartcontractkit/chainlink-solana/pkg/solana/fees"

"github.com/smartcontractkit/chainlink-solana/pkg/solana/client"
"github.com/smartcontractkit/chainlink-solana/pkg/solana/config"
"github.com/smartcontractkit/chainlink-solana/pkg/solana/internal"
Expand All @@ -48,6 +50,7 @@ type Chain interface {
Config() config.Config
LogPoller() LogPoller
TxManager() TxManager
FeeEstimator() fees.Estimator
// Reader returns a new Reader from the available list of nodes (if there are multiple, it will randomly select one)
Reader() (client.Reader, error)
}
Expand Down Expand Up @@ -421,6 +424,10 @@ func (c *chain) TxManager() TxManager {
return c.txm
}

func (c *chain) FeeEstimator() fees.Estimator {
return c.txm.FeeEstimator()
}

func (c *chain) Reader() (client.Reader, error) {
return c.getClient()
}
Expand Down
16 changes: 9 additions & 7 deletions pkg/solana/chainreader/client_wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,19 @@ import (

"github.com/gagliardetto/solana-go"
"github.com/gagliardetto/solana-go/rpc"

"github.com/smartcontractkit/chainlink-solana/pkg/solana/client"
)

// RPCClientWrapper is a wrapper for an RPC client. This was necessary due to the solana RPC interface not
// providing directly mockable components in the GetMultipleAccounts response.
type RPCClientWrapper struct {
*rpc.Client
client.AccountReader
}

// GetMultipleAccountData is a helper function that extracts byte data from a GetMultipleAccounts rpc call.
func (w *RPCClientWrapper) GetMultipleAccountData(ctx context.Context, keys ...solana.PublicKey) ([][]byte, error) {
result, err := w.Client.GetMultipleAccountsWithOpts(ctx, keys, &rpc.GetMultipleAccountsOpts{
result, err := w.GetMultipleAccountsWithOpts(ctx, keys, &rpc.GetMultipleAccountsOpts{
Encoding: solana.EncodingBase64,
Commitment: rpc.CommitmentFinalized,
})
Expand All @@ -25,20 +27,20 @@ func (w *RPCClientWrapper) GetMultipleAccountData(ctx context.Context, keys ...s

bts := make([][]byte, len(result.Value))

for idx, result := range result.Value {
if result == nil {
for idx, res := range result.Value {
if res == nil {
return nil, rpc.ErrNotFound
}

if result.Data == nil {
if res.Data == nil {
return nil, rpc.ErrNotFound
}

if result.Data.GetBinary() == nil {
if res.Data.GetBinary() == nil {
return nil, rpc.ErrNotFound
}

bts[idx] = result.Data.GetBinary()
bts[idx] = res.Data.GetBinary()
}

return bts, nil
Expand Down
11 changes: 11 additions & 0 deletions pkg/solana/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ type Reader interface {
// AccountReader is an interface that allows users to pass either the solana rpc client or the relay client
type AccountReader interface {
GetAccountInfoWithOpts(ctx context.Context, addr solana.PublicKey, opts *rpc.GetAccountInfoOpts) (*rpc.GetAccountInfoResult, error)
GetMultipleAccountsWithOpts(ctx context.Context, accounts []solana.PublicKey, opts *rpc.GetMultipleAccountsOpts) (out *rpc.GetMultipleAccountsResult, err error)
}

type Writer interface {
Expand Down Expand Up @@ -177,6 +178,16 @@ func (c *Client) GetAccountInfoWithOpts(ctx context.Context, addr solana.PublicK
return c.rpc.GetAccountInfoWithOpts(ctx, addr, opts)
}

func (c *Client) GetMultipleAccountsWithOpts(ctx context.Context, accounts []solana.PublicKey, opts *rpc.GetMultipleAccountsOpts) (out *rpc.GetMultipleAccountsResult, err error) {
done := c.latency("multiple_account_info")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does prometheus need to be configured for this to work?

defer done()

ctx, cancel := context.WithTimeout(ctx, c.contextDuration)
defer cancel()
opts.Commitment = c.commitment // overrides passed in value - use defined client commitment type
return c.rpc.GetMultipleAccountsWithOpts(ctx, accounts, opts)
}

func (c *Client) GetBlocks(ctx context.Context, startSlot uint64, endSlot *uint64) (out rpc.BlocksResult, err error) {
done := c.latency("blocks")
defer done()
Expand Down
60 changes: 60 additions & 0 deletions pkg/solana/client/mocks/reader_writer.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions pkg/solana/client/multi_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,15 @@ func (m *MultiClient) GetAccountInfoWithOpts(ctx context.Context, addr solana.Pu
return r.GetAccountInfoWithOpts(ctx, addr, opts)
}

func (m *MultiClient) GetMultipleAccountsWithOpts(ctx context.Context, accounts []solana.PublicKey, opts *rpc.GetMultipleAccountsOpts) (out *rpc.GetMultipleAccountsResult, err error) {
r, err := m.getClient()
if err != nil {
return nil, err
}

return r.GetMultipleAccountsWithOpts(ctx, accounts, opts)
}

func (m *MultiClient) Balance(ctx context.Context, addr solana.PublicKey) (uint64, error) {
r, err := m.getClient()
if err != nil {
Expand Down
35 changes: 31 additions & 4 deletions pkg/solana/relay.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ import (
relaytypes "github.com/smartcontractkit/chainlink-common/pkg/types"
"github.com/smartcontractkit/chainlink-common/pkg/types/core"

"github.com/smartcontractkit/chainlink-solana/pkg/solana/chainreader"
"github.com/smartcontractkit/chainlink-solana/pkg/solana/chainwriter"
"github.com/smartcontractkit/chainlink-solana/pkg/solana/config"

"github.com/smartcontractkit/chainlink-solana/pkg/solana/client"
"github.com/smartcontractkit/chainlink-solana/pkg/solana/txm"
txmutils "github.com/smartcontractkit/chainlink-solana/pkg/solana/txm/utils"
Expand All @@ -25,6 +29,8 @@ import (
var _ TxManager = (*txm.Txm)(nil)

type TxManager interface {
services.Service

// Enqueue adds a tx to the txm queue for processing and submitting to the Solana network.
// An error is returned if the txm is not ready, if the tx is invalid, or if the queue is full.
//
Expand All @@ -34,6 +40,7 @@ type TxManager interface {
// - If a txID is provided, it will be used to identify the tx. Otherwise, a random UUID will be generated.
// - The caller needs to set the tx.Message.RecentBlockhash and provide the corresponding lastValidBlockHeight. These values are obtained from the GetLatestBlockhash RPC call.
Enqueue(ctx context.Context, accountID string, tx *solana.Transaction, txID *string, lastValidBlockHeight uint64, txCfgs ...txmutils.SetTxConfig) error
GetTransactionStatus(ctx context.Context, transactionID string) (relaytypes.TransactionStatus, error)
}

var _ relaytypes.Relayer = &Relayer{} //nolint:staticcheck
Expand Down Expand Up @@ -130,12 +137,32 @@ func (r *Relayer) NewConfigProvider(ctx context.Context, args relaytypes.RelayAr
return configWatcher, err
}

func (r *Relayer) NewContractWriter(_ context.Context, _ []byte) (relaytypes.ContractWriter, error) {
return nil, errors.New("contract writer is not supported for solana")
func (r *Relayer) NewContractWriter(_ context.Context, config []byte) (relaytypes.ContractWriter, error) {
cfg := chainwriter.ChainWriterConfig{}
if err := json.Unmarshal(config, &cfg); err != nil {
return nil, fmt.Errorf("failed to unmarshall chain writer config err: %s", err)
}

solanaReader, err := r.chain.Reader()
Copy link
Contributor

@silaslenihan silaslenihan Jan 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Realizing now we should be using the MultiClient here - but I can add that in later. ticket: https://smartcontract-it.atlassian.net/jira/software/c/projects/NONEVM/boards/357?assignee=61a6f090ef18ca00717f84db&selectedIssue=NONEVM-1213

if err != nil {
return nil, fmt.Errorf("failed to init solana reader err: %s", err)
}

return chainwriter.NewSolanaChainWriterService(r.lggr, solanaReader, r.chain.TxManager(), r.chain.FeeEstimator(), cfg)
}

func (r *Relayer) NewContractReader(ctx context.Context, _ []byte) (relaytypes.ContractReader, error) {
return nil, errors.New("contract reader is not supported for solana")
func (r *Relayer) NewContractReader(_ context.Context, chainReaderConfig []byte) (relaytypes.ContractReader, error) {
cfg := config.ContractReader{}
if err := json.Unmarshal(chainReaderConfig, &cfg); err != nil {
return nil, fmt.Errorf("failed to unmarshall chain reader config err: %s", err)
}

accountReader, err := r.chain.Reader()
if err != nil {
return nil, fmt.Errorf("failed to init account reader err: %s", err)
}

return chainreader.NewChainReaderService(r.lggr, &chainreader.RPCClientWrapper{AccountReader: accountReader}, cfg)
}

func (r *Relayer) NewMedianProvider(ctx context.Context, rargs relaytypes.RelayArgs, pargs relaytypes.PluginArgs) (relaytypes.MedianProvider, error) {
Expand Down
25 changes: 25 additions & 0 deletions pkg/solana/transmitter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/stretchr/testify/require"

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

"github.com/smartcontractkit/chainlink-solana/pkg/solana/client"
Expand All @@ -27,6 +28,30 @@ type verifyTxSize struct {
s *solana.PrivateKey
}

func (txm verifyTxSize) Start(ctx context.Context) error {
panic("implement me")
}

func (txm verifyTxSize) Close() error {
panic("implement me")
}

func (txm verifyTxSize) Ready() error {
panic("implement me")
}

func (txm verifyTxSize) HealthReport() map[string]error {
panic("implement me")
}

func (txm verifyTxSize) Name() string {
panic("implement me")
}

func (txm verifyTxSize) GetTransactionStatus(ctx context.Context, transactionID string) (clcommontypes.TransactionStatus, error) {
panic("implement me")
}

func (txm verifyTxSize) Enqueue(_ context.Context, _ string, tx *solana.Transaction, txID *string, _ uint64, _ ...txmutils.SetTxConfig) error {
// additional components that transaction manager adds to the transaction
require.NoError(txm.t, fees.SetComputeUnitPrice(tx, 0))
Expand Down
4 changes: 4 additions & 0 deletions pkg/solana/txm/txm.go
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,10 @@ func (txm *Txm) reap() {
}
}

func (txm *Txm) FeeEstimator() fees.Estimator {
return txm.fee
}

// Enqueue enqueues a msg destined for the solana chain.
func (txm *Txm) Enqueue(ctx context.Context, accountID string, tx *solanaGo.Transaction, txID *string, txLastValidBlockHeight uint64, txCfgs ...txmutils.SetTxConfig) error {
if err := txm.Ready(); err != nil {
Expand Down
Loading