diff --git a/integration-tests/relayinterface/chain_components_test.go b/integration-tests/relayinterface/chain_components_test.go index d3b85557a..330b6e65d 100644 --- a/integration-tests/relayinterface/chain_components_test.go +++ b/integration-tests/relayinterface/chain_components_test.go @@ -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 { diff --git a/pkg/solana/chain.go b/pkg/solana/chain.go index 3d2f03070..8e69cf2ff 100644 --- a/pkg/solana/chain.go +++ b/pkg/solana/chain.go @@ -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" @@ -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) } @@ -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() } diff --git a/pkg/solana/chainreader/client_wrapper.go b/pkg/solana/chainreader/client_wrapper.go index c08ba1031..4755d93d6 100644 --- a/pkg/solana/chainreader/client_wrapper.go +++ b/pkg/solana/chainreader/client_wrapper.go @@ -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, }) @@ -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 diff --git a/pkg/solana/client/client.go b/pkg/solana/client/client.go index 231e4efd5..1e3a2cc6c 100644 --- a/pkg/solana/client/client.go +++ b/pkg/solana/client/client.go @@ -54,6 +54,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 { @@ -182,6 +183,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") + 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() diff --git a/pkg/solana/client/mocks/reader_writer.go b/pkg/solana/client/mocks/reader_writer.go index f736d6e7a..1c76423fb 100644 --- a/pkg/solana/client/mocks/reader_writer.go +++ b/pkg/solana/client/mocks/reader_writer.go @@ -609,6 +609,66 @@ func (_c *ReaderWriter_GetLatestBlockHeight_Call) RunAndReturn(run func(context. return _c } +// GetMultipleAccountsWithOpts provides a mock function with given fields: ctx, accounts, opts +func (_m *ReaderWriter) GetMultipleAccountsWithOpts(ctx context.Context, accounts []solana.PublicKey, opts *rpc.GetMultipleAccountsOpts) (*rpc.GetMultipleAccountsResult, error) { + ret := _m.Called(ctx, accounts, opts) + + if len(ret) == 0 { + panic("no return value specified for GetMultipleAccountsWithOpts") + } + + var r0 *rpc.GetMultipleAccountsResult + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, []solana.PublicKey, *rpc.GetMultipleAccountsOpts) (*rpc.GetMultipleAccountsResult, error)); ok { + return rf(ctx, accounts, opts) + } + if rf, ok := ret.Get(0).(func(context.Context, []solana.PublicKey, *rpc.GetMultipleAccountsOpts) *rpc.GetMultipleAccountsResult); ok { + r0 = rf(ctx, accounts, opts) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*rpc.GetMultipleAccountsResult) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, []solana.PublicKey, *rpc.GetMultipleAccountsOpts) error); ok { + r1 = rf(ctx, accounts, opts) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ReaderWriter_GetMultipleAccountsWithOpts_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetMultipleAccountsWithOpts' +type ReaderWriter_GetMultipleAccountsWithOpts_Call struct { + *mock.Call +} + +// GetMultipleAccountsWithOpts is a helper method to define mock.On call +// - ctx context.Context +// - accounts []solana.PublicKey +// - opts *rpc.GetMultipleAccountsOpts +func (_e *ReaderWriter_Expecter) GetMultipleAccountsWithOpts(ctx interface{}, accounts interface{}, opts interface{}) *ReaderWriter_GetMultipleAccountsWithOpts_Call { + return &ReaderWriter_GetMultipleAccountsWithOpts_Call{Call: _e.mock.On("GetMultipleAccountsWithOpts", ctx, accounts, opts)} +} + +func (_c *ReaderWriter_GetMultipleAccountsWithOpts_Call) Run(run func(ctx context.Context, accounts []solana.PublicKey, opts *rpc.GetMultipleAccountsOpts)) *ReaderWriter_GetMultipleAccountsWithOpts_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].([]solana.PublicKey), args[2].(*rpc.GetMultipleAccountsOpts)) + }) + return _c +} + +func (_c *ReaderWriter_GetMultipleAccountsWithOpts_Call) Return(out *rpc.GetMultipleAccountsResult, err error) *ReaderWriter_GetMultipleAccountsWithOpts_Call { + _c.Call.Return(out, err) + return _c +} + +func (_c *ReaderWriter_GetMultipleAccountsWithOpts_Call) RunAndReturn(run func(context.Context, []solana.PublicKey, *rpc.GetMultipleAccountsOpts) (*rpc.GetMultipleAccountsResult, error)) *ReaderWriter_GetMultipleAccountsWithOpts_Call { + _c.Call.Return(run) + return _c +} + // GetSignaturesForAddressWithOpts provides a mock function with given fields: ctx, addr, opts func (_m *ReaderWriter) GetSignaturesForAddressWithOpts(ctx context.Context, addr solana.PublicKey, opts *rpc.GetSignaturesForAddressOpts) ([]*rpc.TransactionSignature, error) { ret := _m.Called(ctx, addr, opts) diff --git a/pkg/solana/client/multi_client.go b/pkg/solana/client/multi_client.go index 1cefbe02f..d5c1eaf72 100644 --- a/pkg/solana/client/multi_client.go +++ b/pkg/solana/client/multi_client.go @@ -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 { diff --git a/pkg/solana/codec/decoder.go b/pkg/solana/codec/decoder.go index ec734b36a..c91805f71 100644 --- a/pkg/solana/codec/decoder.go +++ b/pkg/solana/codec/decoder.go @@ -8,31 +8,43 @@ import ( commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" ) -type Decoder struct { - definitions map[string]Entry - lenientFromTypeCodec encodings.LenientCodecFromTypeCodec +// decoder should be initialized with newDecoder +type decoder struct { + definitions map[string]Entry + lenientCodecFromTypeCodec encodings.LenientCodecFromTypeCodec } -var _ commontypes.Decoder = &Decoder{} +func newDecoder(definitions map[string]Entry) commontypes.Decoder { + lenientCodecFromTypeCodec := make(encodings.LenientCodecFromTypeCodec) + for k, v := range definitions { + lenientCodecFromTypeCodec[k] = v + } + + return &decoder{ + definitions: definitions, + lenientCodecFromTypeCodec: lenientCodecFromTypeCodec, + } +} -func (d *Decoder) Decode(ctx context.Context, raw []byte, into any, itemType string) (err error) { +func (d *decoder) Decode(ctx context.Context, raw []byte, into any, itemType string) (err error) { defer func() { if r := recover(); r != nil { err = fmt.Errorf("recovered from: %v, while decoding %q", r, itemType) } }() - if d.lenientFromTypeCodec == nil { - d.lenientFromTypeCodec = make(encodings.LenientCodecFromTypeCodec) - for k, v := range d.definitions { - d.lenientFromTypeCodec[k] = v - } + if d.lenientCodecFromTypeCodec == nil { + return fmt.Errorf("decoder is not properly initialised, underlying lenientCodecFromTypeCodec is nil") } - return d.lenientFromTypeCodec.Decode(ctx, raw, into, itemType) + return d.lenientCodecFromTypeCodec.Decode(ctx, raw, into, itemType) } -func (d *Decoder) GetMaxDecodingSize(_ context.Context, n int, itemType string) (int, error) { +func (d *decoder) GetMaxDecodingSize(_ context.Context, n int, itemType string) (int, error) { + if d.definitions == nil { + return 0, fmt.Errorf("decoder is not properly initialised, type definitions are nil") + } + codecEntry, ok := d.definitions[itemType] if !ok { return 0, fmt.Errorf("%w: nil entry", commontypes.ErrInvalidType) diff --git a/pkg/solana/codec/decoder_test.go b/pkg/solana/codec/decoder_test.go index 0f21c43e8..6576d941d 100644 --- a/pkg/solana/codec/decoder_test.go +++ b/pkg/solana/codec/decoder_test.go @@ -31,24 +31,20 @@ func TestDecoder_Decode_Errors(t *testing.T) { var into interface{} someType := "some-type" t.Run("error when item type not found", func(t *testing.T) { - d := &Decoder{definitions: map[string]Entry{}} - d.definitions[someType] = &entry{} - nonExistentType := "non-existent" - err := d.Decode(tests.Context(t), []byte{}, &into, nonExistentType) + err := newDecoder(map[string]Entry{someType: &entry{}}). + Decode(tests.Context(t), []byte{}, &into, nonExistentType) require.ErrorIs(t, err, fmt.Errorf("%w: cannot find type %s", commontypes.ErrInvalidType, nonExistentType)) }) t.Run("error when underlying entry decode fails", func(t *testing.T) { - d := &Decoder{definitions: map[string]Entry{}} - d.definitions[someType] = &testErrDecodeEntry{} - require.Error(t, d.Decode(tests.Context(t), []byte{}, &into, someType)) + require.Error(t, newDecoder(map[string]Entry{someType: &testErrDecodeEntry{}}). + Decode(tests.Context(t), []byte{}, &into, someType)) }) t.Run("remaining bytes exist after decode is ok", func(t *testing.T) { - d := &Decoder{definitions: map[string]Entry{}} - d.definitions[someType] = &testErrDecodeRemainingBytes{} - require.NoError(t, d.Decode(tests.Context(t), []byte{}, &into, someType)) + require.NoError(t, newDecoder(map[string]Entry{someType: &testErrDecodeRemainingBytes{}}). + Decode(tests.Context(t), []byte{}, &into, someType)) }) } @@ -72,19 +68,15 @@ func TestDecoder_GetMaxDecodingSize_Errors(t *testing.T) { someType := "some-type" t.Run("error when entry for item type is missing", func(t *testing.T) { - d := &Decoder{definitions: map[string]Entry{}} - d.definitions[someType] = &entry{} - nonExistentType := "non-existent" - _, err := d.GetMaxDecodingSize(tests.Context(t), 0, nonExistentType) + _, err := newDecoder(map[string]Entry{someType: &entry{}}). + GetMaxDecodingSize(tests.Context(t), 0, nonExistentType) require.ErrorIs(t, err, fmt.Errorf("%w: cannot find type %s", commontypes.ErrInvalidType, nonExistentType)) }) t.Run("error when underlying entry decode fails", func(t *testing.T) { - d := &Decoder{definitions: map[string]Entry{}} - d.definitions[someType] = &testErrGetMaxDecodingSize{} - - _, err := d.GetMaxDecodingSize(tests.Context(t), 0, someType) + _, err := newDecoder(map[string]Entry{someType: &testErrGetMaxDecodingSize{}}). + GetMaxDecodingSize(tests.Context(t), 0, someType) require.Error(t, err) }) } diff --git a/pkg/solana/codec/encoder.go b/pkg/solana/codec/encoder.go index e3f734768..9bc519bd0 100644 --- a/pkg/solana/codec/encoder.go +++ b/pkg/solana/codec/encoder.go @@ -8,14 +8,25 @@ import ( commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" ) -type Encoder struct { +// encoder should be initialized with newEncoder +type encoder struct { definitions map[string]Entry lenientCodecFromTypeCodec encodings.LenientCodecFromTypeCodec } -var _ commontypes.Encoder = &Encoder{} +func newEncoder(definitions map[string]Entry) commontypes.Encoder { + lenientCodecFromTypeCodec := make(encodings.LenientCodecFromTypeCodec) + for k, v := range definitions { + lenientCodecFromTypeCodec[k] = v + } + + return &encoder{ + lenientCodecFromTypeCodec: lenientCodecFromTypeCodec, + definitions: definitions, + } +} -func (e *Encoder) Encode(ctx context.Context, item any, itemType string) (res []byte, err error) { +func (e *encoder) Encode(ctx context.Context, item any, itemType string) (res []byte, err error) { defer func() { if r := recover(); r != nil { err = fmt.Errorf("recovered from: %v, while encoding %q", r, itemType) @@ -23,16 +34,17 @@ func (e *Encoder) Encode(ctx context.Context, item any, itemType string) (res [] }() if e.lenientCodecFromTypeCodec == nil { - e.lenientCodecFromTypeCodec = make(encodings.LenientCodecFromTypeCodec) - for k, v := range e.definitions { - e.lenientCodecFromTypeCodec[k] = v - } + return nil, fmt.Errorf("encoder is not properly initialised, underlying lenientCodecFromTypeCodec is nil") } return e.lenientCodecFromTypeCodec.Encode(ctx, item, itemType) } -func (e *Encoder) GetMaxEncodingSize(_ context.Context, n int, itemType string) (int, error) { +func (e *encoder) GetMaxEncodingSize(_ context.Context, n int, itemType string) (int, error) { + if e.definitions == nil { + return 0, fmt.Errorf("encoder is not properly initialised, type definitions are nil") + } + entry, ok := e.definitions[itemType] if !ok { return 0, fmt.Errorf("%w: nil entry", commontypes.ErrInvalidType) diff --git a/pkg/solana/codec/encoder_test.go b/pkg/solana/codec/encoder_test.go index fb098d884..105f95dcf 100644 --- a/pkg/solana/codec/encoder_test.go +++ b/pkg/solana/codec/encoder_test.go @@ -38,30 +38,21 @@ func TestEncoder_Encode_Errors(t *testing.T) { someType := "some-type" t.Run("error when item type not found", func(t *testing.T) { - e := &Encoder{definitions: map[string]Entry{}} - _, err := e.Encode(tests.Context(t), nil, "non-existent-type") + _, err := newEncoder(map[string]Entry{}). + Encode(tests.Context(t), nil, "non-existent-type") require.Error(t, err) require.ErrorIs(t, err, commontypes.ErrInvalidType) require.Contains(t, err.Error(), "cannot find type non-existent-type") }) t.Run("error when convert fails because of unexpected type", func(t *testing.T) { - e := &Encoder{ - definitions: map[string]Entry{ - someType: &testErrEncodeEntry{}, - }, - } - _, err := e.Encode(tests.Context(t), nil, someType) + _, err := newEncoder(map[string]Entry{someType: &testErrEncodeEntry{}}).Encode(tests.Context(t), nil, someType) require.Error(t, err) }) t.Run("error when entry encode fails", func(t *testing.T) { - e := &Encoder{ - definitions: map[string]Entry{ - someType: &testErrEncodeEntry{codecType: commonencodings.Empty{}}, - }, - } - _, err := e.Encode(tests.Context(t), make(map[string]interface{}), someType) + _, err := newEncoder(map[string]Entry{someType: &testErrEncodeEntry{codecType: commonencodings.Empty{}}}). + Encode(tests.Context(t), make(map[string]interface{}), someType) require.ErrorContains(t, err, "encode error") }) } @@ -81,8 +72,7 @@ func (t testErrGetSize) Size(_ int) (int, error) { func TestEncoder_GetMaxEncodingSize_Errors(t *testing.T) { t.Run("error when entry for item type is missing", func(t *testing.T) { - e := &Encoder{definitions: map[string]Entry{}} - _, err := e.GetMaxEncodingSize(tests.Context(t), 10, "no-entry-type") + _, err := newEncoder(map[string]Entry{}).GetMaxEncodingSize(tests.Context(t), 10, "no-entry-type") require.Error(t, err) require.ErrorIs(t, err, commontypes.ErrInvalidType) require.Contains(t, err.Error(), "nil entry") @@ -90,13 +80,8 @@ func TestEncoder_GetMaxEncodingSize_Errors(t *testing.T) { t.Run("error when size calculation fails", func(t *testing.T) { someType := "some-type" - e := &Encoder{ - definitions: map[string]Entry{ - someType: &testErrEncodeTypeEntry{tCodec: testErrGetSize{}}, - }, - } - - _, err := e.GetMaxEncodingSize(tests.Context(t), 0, someType) + _, err := newEncoder(map[string]Entry{someType: &testErrEncodeTypeEntry{tCodec: testErrGetSize{}}}). + GetMaxEncodingSize(tests.Context(t), 0, someType) require.Error(t, err) require.Contains(t, err.Error(), "size error") }) diff --git a/pkg/solana/codec/parsed_types.go b/pkg/solana/codec/parsed_types.go index 3144f6dd0..d0d1d4693 100644 --- a/pkg/solana/codec/parsed_types.go +++ b/pkg/solana/codec/parsed_types.go @@ -27,8 +27,8 @@ func (parsed *ParsedTypes) ToCodec() (commontypes.RemoteCodec, error) { return nil, err } underlying := &solanaCodec{ - Encoder: &Encoder{definitions: parsed.EncoderDefs}, - Decoder: &Decoder{definitions: parsed.DecoderDefs}, + Encoder: newEncoder(parsed.EncoderDefs), + Decoder: newDecoder(parsed.DecoderDefs), ParsedTypes: parsed, } return commoncodec.NewModifierCodec(underlying, mod, DecoderHooks...) diff --git a/pkg/solana/codec/solana.go b/pkg/solana/codec/solana.go index ce9e277db..f6556b48d 100644 --- a/pkg/solana/codec/solana.go +++ b/pkg/solana/codec/solana.go @@ -50,8 +50,8 @@ const ( var DecoderHooks = []mapstructure.DecodeHookFunc{commoncodec.EpochToTimeHook, commoncodec.BigIntHook, commoncodec.SliceToArrayVerifySizeHook} type solanaCodec struct { - *Encoder - *Decoder + commontypes.Encoder + commontypes.Decoder *ParsedTypes } diff --git a/pkg/solana/relay.go b/pkg/solana/relay.go index 5dfe8c836..78ff07163 100644 --- a/pkg/solana/relay.go +++ b/pkg/solana/relay.go @@ -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" @@ -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. // @@ -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 @@ -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() + 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) { diff --git a/pkg/solana/transmitter_test.go b/pkg/solana/transmitter_test.go index ba1ec9cc3..24a111369 100644 --- a/pkg/solana/transmitter_test.go +++ b/pkg/solana/transmitter_test.go @@ -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" @@ -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)) diff --git a/pkg/solana/txm/txm.go b/pkg/solana/txm/txm.go index 1c7e4c54e..17f17f093 100644 --- a/pkg/solana/txm/txm.go +++ b/pkg/solana/txm/txm.go @@ -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 {