Skip to content

Commit

Permalink
Batch Contract Read
Browse files Browse the repository at this point in the history
Batch contract reads are added and multiple address support was removed. All tests now pass.
  • Loading branch information
EasterTheBunny committed Nov 13, 2024
1 parent 65ae137 commit 27a3c1b
Show file tree
Hide file tree
Showing 9 changed files with 305 additions and 717 deletions.
78 changes: 10 additions & 68 deletions pkg/solana/chainreader/account_read_binding.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,102 +2,44 @@ package chainreader

import (
"context"
"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, *rpc.GetAccountInfoOpts) ([]byte, error)
}

// accountReadBinding provides decoding and reading Solana Account data using a defined codec. The
// `idlAccount` refers to the account name in the IDL for which the codec has a type mapping.
type accountReadBinding struct {
idlAccount string
codec types.RemoteCodec
reader BinaryDataReader
key solana.PublicKey
opts *rpc.GetAccountInfoOpts
}

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

var _ readBinding = &accountReadBinding{}

func (b *accountReadBinding) PreLoad(ctx context.Context, address string, result *loadedResult) {
if result == nil {
return
}

account, err := solana.PublicKeyFromBase58(address)
if err != nil {
result.err <- err

return
}

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

return
}

select {
case <-ctx.Done():
result.err <- ctx.Err()
default:
result.value <- bts
}
func (b *accountReadBinding) SetAddress(key solana.PublicKey) {
b.key = key
}

func (b *accountReadBinding) GetLatestValue(ctx context.Context, address string, _ any, outVal any, result *loadedResult) error {
var (
bts []byte
err error
)

if result != nil {
// when preloading, the process will wait for one of three conditions:
// 1. the context ends and returns an error
// 2. bytes were loaded in the bytes channel
// 3. an error was loaded in the err channel
select {
case <-ctx.Done():
err = ctx.Err()
case bts = <-result.value:
case err = <-result.err:
}

if err != nil {
return err
}
} else {
account, err := solana.PublicKeyFromBase58(address)
if err != nil {
return err
}

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

return b.codec.Decode(ctx, bts, outVal, b.idlAccount)
func (b *accountReadBinding) GetAddress() solana.PublicKey {
return b.key
}

func (b *accountReadBinding) CreateType(_ bool) (any, error) {
return b.codec.CreateType(b.idlAccount, false)
}

func (b *accountReadBinding) Decode(ctx context.Context, bts []byte, outVal any) error {
return b.codec.Decode(ctx, bts, outVal, b.idlAccount)
}
164 changes: 0 additions & 164 deletions pkg/solana/chainreader/account_read_binding_test.go

This file was deleted.

107 changes: 107 additions & 0 deletions pkg/solana/chainreader/batch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package chainreader

import (
"context"
"errors"

"github.com/gagliardetto/solana-go"

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

type call struct {
ContractName, ReadName string
Params, ReturnVal any
}

type batchResultWithErr struct {
address string
contractName, readName string
returnVal any
err error
}

var (
ErrMissingAccountData = errors.New("account data not found")
)

type MultipleAccountGetter interface {
GetMultipleAccountData(context.Context, ...solana.PublicKey) ([][]byte, error)
}

func doMethodBatchCall(ctx context.Context, client MultipleAccountGetter, bindings namespaceBindings, batch []call) ([]batchResultWithErr, error) {
// Create the list of public keys to fetch
keys := make([]solana.PublicKey, len(batch))
for idx, call := range batch {
binding, err := bindings.GetReadBinding(call.ContractName, call.ReadName)
if err != nil {
return nil, err
}

keys[idx] = binding.GetAddress()
}

// Fetch the account data
data, err := client.GetMultipleAccountData(ctx, keys...)
if err != nil {
return nil, err
}

results := make([]batchResultWithErr, len(batch))

// decode batch call results
for idx, call := range batch {
results[idx] = batchResultWithErr{
address: keys[idx].String(),
contractName: call.ContractName,
readName: call.ReadName,
returnVal: call.ReturnVal,
}

if data[idx] == nil || len(data[idx]) == 0 {
results[idx].err = ErrMissingAccountData

continue
}

binding, err := bindings.GetReadBinding(results[idx].contractName, results[idx].readName)
if err != nil {
results[idx].err = err

continue
}

ptrToValue, isValue := call.ReturnVal.(*values.Value)
if !isValue {
results[idx].err = errors.Join(
results[idx].err,
binding.Decode(ctx, data[idx], results[idx].returnVal),
)

continue
}

contractType, err := binding.CreateType(false)
if err != nil {
results[idx].err = err

continue
}

results[idx].err = errors.Join(
results[idx].err,
binding.Decode(ctx, data[idx], contractType),
)

value, err := values.Wrap(contractType)
if err != nil {
results[idx].err = errors.Join(results[idx].err, err)

continue
}

*ptrToValue = value
}

return results, nil
}
Loading

0 comments on commit 27a3c1b

Please sign in to comment.