Skip to content

Commit

Permalink
BCF-3055 Add RPC Opts to Config and Pass to Binding (#627)
Browse files Browse the repository at this point in the history
Solana RPC options include commitment level, data encoding type, and data slice opts.
Providing these options in the ChainReader config will allow more control over RPC
behavior.
  • Loading branch information
EasterTheBunny authored Mar 18, 2024
1 parent 5b3b9fb commit e583587
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 15 deletions.
11 changes: 7 additions & 4 deletions pkg/solana/chainreader/account_read_binding.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ import (
"fmt"

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

"github.com/smartcontractkit/chainlink-common/pkg/types"
)

// BinaryDataReader provides an interface for reading bytes from a source. This is likely a wrapper
// for a solana client.
type BinaryDataReader interface {
ReadAll(context.Context, solana.PublicKey) ([]byte, error)
ReadAll(context.Context, solana.PublicKey, *rpc.GetAccountInfoOpts) ([]byte, error)
}

// accountReadBinding provides decoding and reading Solana Account data using a defined codec. The
Expand All @@ -22,13 +23,15 @@ type accountReadBinding struct {
account solana.PublicKey
codec types.RemoteCodec
reader BinaryDataReader
opts *rpc.GetAccountInfoOpts
}

func newAccountReadBinding(acct string, codec types.RemoteCodec, reader BinaryDataReader) *accountReadBinding {
func newAccountReadBinding(acct string, codec types.RemoteCodec, reader BinaryDataReader, opts *rpc.GetAccountInfoOpts) *accountReadBinding {
return &accountReadBinding{
idlAccount: acct,
codec: codec,
reader: reader,
opts: opts,
}
}

Expand All @@ -39,7 +42,7 @@ func (b *accountReadBinding) PreLoad(ctx context.Context, result *loadedResult)
return
}

bts, err := b.reader.ReadAll(ctx, b.account)
bts, err := b.reader.ReadAll(ctx, b.account, b.opts)
if err != nil {
result.err <- fmt.Errorf("%w: failed to get binary data", err)

Expand Down Expand Up @@ -76,7 +79,7 @@ func (b *accountReadBinding) GetLatestValue(ctx context.Context, _ any, outVal a
return err
}
} else {
if bts, err = b.reader.ReadAll(ctx, b.account); err != nil {
if bts, err = b.reader.ReadAll(ctx, b.account, b.opts); err != nil {
return fmt.Errorf("%w: failed to get binary data", err)
}
}
Expand Down
15 changes: 8 additions & 7 deletions pkg/solana/chainreader/account_read_binding_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"time"

"github.com/gagliardetto/solana-go"
"github.com/gagliardetto/solana-go/rpc"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
Expand All @@ -25,14 +26,14 @@ func TestPreload(t *testing.T) {
t.Parallel()

reader := new(mockReader)
binding := newAccountReadBinding(testCodecKey, testCodec, reader)
binding := newAccountReadBinding(testCodecKey, testCodec, reader, nil)

expected := testStruct{A: true, B: 42}
bts, err := testCodec.Encode(context.Background(), expected, testCodecKey)

require.NoError(t, err)

reader.On("ReadAll", mock.Anything, mock.Anything).Return(bts, nil).After(time.Second)
reader.On("ReadAll", mock.Anything, mock.Anything, mock.Anything).Return(bts, nil).After(time.Second)

ctx := context.Background()
start := time.Now()
Expand All @@ -58,12 +59,12 @@ func TestPreload(t *testing.T) {
t.Parallel()

reader := new(mockReader)
binding := newAccountReadBinding(testCodecKey, testCodec, reader)
binding := newAccountReadBinding(testCodecKey, testCodec, reader, nil)

ctx, cancel := context.WithCancelCause(context.Background())

// make the readall pause until after the context is cancelled
reader.On("ReadAll", mock.Anything, mock.Anything).
reader.On("ReadAll", mock.Anything, mock.Anything, mock.Anything).
Return([]byte{}, nil).
After(600 * time.Millisecond)

Expand Down Expand Up @@ -94,11 +95,11 @@ func TestPreload(t *testing.T) {
t.Parallel()

reader := new(mockReader)
binding := newAccountReadBinding(testCodecKey, testCodec, reader)
binding := newAccountReadBinding(testCodecKey, testCodec, reader, nil)
ctx := context.Background()
expectedErr := errors.New("test error")

reader.On("ReadAll", mock.Anything, mock.Anything).
reader.On("ReadAll", mock.Anything, mock.Anything, mock.Anything).
Return([]byte{}, expectedErr)

loaded := &loadedResult{
Expand All @@ -118,7 +119,7 @@ type mockReader struct {
mock.Mock
}

func (_m *mockReader) ReadAll(ctx context.Context, pk solana.PublicKey) ([]byte, error) {
func (_m *mockReader) ReadAll(ctx context.Context, pk solana.PublicKey, opts *rpc.GetAccountInfoOpts) ([]byte, error) {
ret := _m.Called(ctx, pk)

var r0 []byte
Expand Down
25 changes: 23 additions & 2 deletions pkg/solana/chainreader/chain_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ func (s *SolanaChainReaderService) init(namespaces map[string]config.ChainReader
procedure.IDLAccount,
codecWithModifiers,
s.client,
createRPCOpts(procedure.RPCOpts),
))
}
}
Expand All @@ -215,6 +216,26 @@ func (s *SolanaChainReaderService) init(namespaces map[string]config.ChainReader
return nil
}

func createRPCOpts(opts *config.RPCOpts) *rpc.GetAccountInfoOpts {
if opts == nil {
return nil
}

result := &rpc.GetAccountInfoOpts{
DataSlice: opts.DataSlice,
}

if opts.Encoding != nil {
result.Encoding = *opts.Encoding
}

if opts.Commitment != nil {
result.Commitment = *opts.Commitment
}

return result
}

type accountDataReader struct {
client *rpc.Client
}
Expand All @@ -223,8 +244,8 @@ func NewAccountDataReader(client *rpc.Client) *accountDataReader {
return &accountDataReader{client: client}
}

func (r *accountDataReader) ReadAll(ctx context.Context, pk ag_solana.PublicKey) ([]byte, error) {
result, err := r.client.GetAccountInfo(ctx, pk)
func (r *accountDataReader) ReadAll(ctx context.Context, pk ag_solana.PublicKey, opts *rpc.GetAccountInfoOpts) ([]byte, error) {
result, err := r.client.GetAccountInfoWithOpts(ctx, pk, opts)
if err != nil {
return nil, err
}
Expand Down
17 changes: 16 additions & 1 deletion pkg/solana/chainreader/chain_reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import (
"testing"
"time"

"github.com/gagliardetto/solana-go"
ag_solana "github.com/gagliardetto/solana-go"
"github.com/gagliardetto/solana-go/rpc"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

Expand Down Expand Up @@ -333,7 +335,7 @@ type mockedRPCClient struct {
sequence []mockedRPCCall
}

func (_m *mockedRPCClient) ReadAll(_ context.Context, pk ag_solana.PublicKey) ([]byte, error) {
func (_m *mockedRPCClient) ReadAll(_ context.Context, pk ag_solana.PublicKey, _ *rpc.GetAccountInfoOpts) ([]byte, error) {
_m.mu.Lock()
defer _m.mu.Unlock()

Expand Down Expand Up @@ -414,6 +416,11 @@ func (r *chainReaderInterfaceTester) Setup(t *testing.T) {
r.address[idx] = ag_solana.NewWallet().PublicKey().String()
}

encodingBase64 := solana.EncodingBase64
commitment := rpc.CommitmentConfirmed
offset := uint64(1)
length := uint64(1)

r.conf = config.ChainReader{
Namespaces: map[string]config.ChainReaderMethods{
AnyContractName: {
Expand All @@ -424,6 +431,14 @@ func (r *chainReaderInterfaceTester) Setup(t *testing.T) {
Procedures: []config.ChainReaderProcedure{
{
IDLAccount: "TestStructB",
RPCOpts: &config.RPCOpts{
Encoding: &encodingBase64,
Commitment: &commitment,
DataSlice: &rpc.DataSlice{
Offset: &offset,
Length: &length,
},
},
},
{
IDLAccount: "TestStructA",
Expand Down
12 changes: 12 additions & 0 deletions pkg/solana/config/chain_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import (
"encoding/json"
"fmt"

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

"github.com/smartcontractkit/chainlink-common/pkg/codec"
"github.com/smartcontractkit/chainlink-common/pkg/codec/encodings"
"github.com/smartcontractkit/chainlink-common/pkg/codec/encodings/binary"
Expand Down Expand Up @@ -66,6 +69,12 @@ func (t *EncodingType) UnmarshalJSON(data []byte) error {
return nil
}

type RPCOpts struct {
Encoding *solana.EncodingType `json:"encoding,omitempty"`
Commitment *rpc.CommitmentType `json:"commitment,omitempty"`
DataSlice *rpc.DataSlice `json:"dataSlice,omitempty"`
}

type ChainReaderProcedure chainDataProcedureFields

type chainDataProcedureFields struct {
Expand All @@ -74,6 +83,9 @@ type chainDataProcedureFields struct {
// OutputModifications provides modifiers to convert chain data format to custom
// output formats.
OutputModifications codec.ModifiersConfig `json:"outputModifications,omitempty"`
// RPCOpts provides optional configurations for commitment, encoding, and data
// slice offsets.
RPCOpts *RPCOpts `json:"rpcOpts,omitempty"`
}

// BuilderForEncoding returns a builder for the encoding configuration. Defaults to little endian.
Expand Down
17 changes: 17 additions & 0 deletions pkg/solana/config/chain_reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"encoding/json"
"testing"

"github.com/gagliardetto/solana-go"
"github.com/gagliardetto/solana-go/rpc"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

Expand Down Expand Up @@ -74,6 +76,13 @@ func TestBuilderForEncoding_Default(t *testing.T) {
require.Equal(t, binary.LittleEndian(), builder)
}

var (
encodingBase64 = solana.EncodingBase64
commitment = rpc.CommitmentFinalized
offset = uint64(10)
length = uint64(10)
)

var validChainReaderConfig = config.ChainReader{
Namespaces: map[string]config.ChainReaderMethods{
"Contract": {
Expand All @@ -96,6 +105,14 @@ var validChainReaderConfig = config.ChainReader{
OutputModifications: codeccommon.ModifiersConfig{
&codeccommon.PropertyExtractorConfig{FieldName: "DurationVal"},
},
RPCOpts: &config.RPCOpts{
Encoding: &encodingBase64,
Commitment: &commitment,
DataSlice: &rpc.DataSlice{
Offset: &offset,
Length: &length,
},
},
},
},
},
Expand Down
10 changes: 9 additions & 1 deletion pkg/solana/config/testChainReader_valid.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,15 @@
"outputModifications": [{
"Type": "extract property",
"FieldName": "DurationVal"
}]
}],
"rpcOpts": {
"encoding": "base64",
"commitment": "finalized",
"dataSlice": {
"offset": 10,
"length": 10
}
}
}]
}
}
Expand Down

0 comments on commit e583587

Please sign in to comment.