From 3333854258e84f322f74388e7470e4873c52c52b Mon Sep 17 00:00:00 2001 From: Silas Lenihan <32529249+silaslenihan@users.noreply.github.com> Date: Thu, 20 Feb 2025 04:09:55 -0500 Subject: [PATCH] Specify Ignorable Errors for Optional Lookups (#1091) Co-authored-by: Jonghyeon Park --- .../relayinterface/lookups_test.go | 42 +++++++++++++++---- pkg/solana/chainwriter/chain_writer.go | 11 ++++- pkg/solana/chainwriter/lookups.go | 12 ++++-- 3 files changed, 53 insertions(+), 12 deletions(-) diff --git a/integration-tests/relayinterface/lookups_test.go b/integration-tests/relayinterface/lookups_test.go index 910b2cf69..bc0e3eb6e 100644 --- a/integration-tests/relayinterface/lookups_test.go +++ b/integration-tests/relayinterface/lookups_test.go @@ -150,7 +150,7 @@ func TestAccountLookups(t *testing.T) { IsWritable: chainwriter.MetaBool{Value: true}, }} _, err := lookupConfig.AccountLookup.Resolve(testArgs) - require.Error(t, err) + require.ErrorIs(t, err, chainwriter.ErrLookupNotFoundAtLocation) }) t.Run("AccountLookup works with MetaBool bitmap lookups", func(t *testing.T) { @@ -360,8 +360,7 @@ func TestPDALookups(t *testing.T) { } _, err := pdaLookup.Resolve(ctx, args, nil, client.MultiClient{}) - require.Error(t, err) - require.Contains(t, err.Error(), "key not found") + require.ErrorIs(t, err, chainwriter.ErrGettingSeedAtLocation) }) t.Run("PDALookup resolves valid PDA with address lookup seeds", func(t *testing.T) { @@ -569,6 +568,21 @@ func TestLookupTables(t *testing.T) { require.Contains(t, err.Error(), "error fetching account info for table") // Example error message }) + t.Run("Derived lookup table fails with invalid table name", func(t *testing.T) { + derivedTableMap := map[string]map[string][]*solana.AccountMeta{ + "DerivedTable": {}, + } + accountsFromLookupTable := chainwriter.Lookup{ + AccountsFromLookupTable: &chainwriter.AccountsFromLookupTable{ + LookupTableName: "InvalidTable", + IncludeIndexes: []int{}, + }, + } + + _, err = accountsFromLookupTable.Resolve(ctx, nil, derivedTableMap, multiClient) + require.ErrorIs(t, err, chainwriter.ErrLookupTableNotFound) + }) + t.Run("Static lookup table fails with invalid address", func(t *testing.T) { invalidTable := chainwriter.GetRandomPubKey(t) @@ -608,8 +622,15 @@ func TestLookupTables(t *testing.T) { derivedTableMap, _, err := cw.ResolveLookupTables(ctx, testArgs, lookupConfig) require.NoError(t, err) - addresses, ok := derivedTableMap["DerivedTable"][table.String()] - require.True(t, ok) + accountsFromLookupTable := chainwriter.Lookup{ + AccountsFromLookupTable: &chainwriter.AccountsFromLookupTable{ + LookupTableName: "DerivedTable", + IncludeIndexes: []int{}, + }, + } + + addresses, err := accountsFromLookupTable.Resolve(ctx, nil, derivedTableMap, multiClient) + require.NoError(t, err) for i, address := range addresses { require.Equal(t, pubKeys[i], address.PublicKey) } @@ -654,8 +675,15 @@ func TestLookupTables(t *testing.T) { derivedTableMap, _, err := cw.ResolveLookupTables(ctx, args, lookupConfig) require.NoError(t, err) - addresses, ok := derivedTableMap["DerivedTable"][lookupTable.String()] - require.True(t, ok) + accountsFromLookupTable := chainwriter.Lookup{ + AccountsFromLookupTable: &chainwriter.AccountsFromLookupTable{ + LookupTableName: "DerivedTable", + IncludeIndexes: []int{}, + }, + } + + addresses, err := accountsFromLookupTable.Resolve(ctx, args, derivedTableMap, multiClient) + require.NoError(t, err) for i, address := range addresses { require.Equal(t, lookupKeys[i], address.PublicKey) } diff --git a/pkg/solana/chainwriter/chain_writer.go b/pkg/solana/chainwriter/chain_writer.go index 0931406ba..9ba2c1e5f 100644 --- a/pkg/solana/chainwriter/chain_writer.go +++ b/pkg/solana/chainwriter/chain_writer.go @@ -155,7 +155,7 @@ func GetAddresses(ctx context.Context, args any, accounts []Lookup, derivedTable var addresses []*solana.AccountMeta for _, accountConfig := range accounts { meta, err := accountConfig.Resolve(ctx, args, derivedTableMap, client) - if accountConfig.Optional && err != nil { + if accountConfig.Optional && err != nil && isIgnorableError(err) { // skip optional accounts if they are not found continue } @@ -167,6 +167,13 @@ func GetAddresses(ctx context.Context, args any, accounts []Lookup, derivedTable return addresses, nil } +// These errors are ignorable if the lookup is optional. +func isIgnorableError(err error) bool { + return errors.Is(err, ErrLookupNotFoundAtLocation) || + errors.Is(err, ErrLookupTableNotFound) || + errors.Is(err, ErrGettingSeedAtLocation) +} + // FilterLookupTableAddresses takes a list of accounts and two lookup table maps // (one for derived tables, one for static tables) and filters out any addresses that are // not used by the accounts. It returns a map of only those lookup table @@ -460,7 +467,7 @@ func (s *SolanaChainWriterService) ResolveLookupTables(ctx context.Context, args // Load the lookup table - note: This could be multiple tables if the lookup is a PDALookups that resolves to more // than one address lookupTableMap, err := s.loadTable(ctx, args, derivedLookup) - if derivedLookup.Optional && err != nil { + if derivedLookup.Optional && err != nil && isIgnorableError(err) { continue } if err != nil { diff --git a/pkg/solana/chainwriter/lookups.go b/pkg/solana/chainwriter/lookups.go index 2ae81b16f..e3782b56d 100644 --- a/pkg/solana/chainwriter/lookups.go +++ b/pkg/solana/chainwriter/lookups.go @@ -13,6 +13,12 @@ import ( "github.com/smartcontractkit/chainlink-solana/pkg/solana/codec" ) +var ( + ErrLookupNotFoundAtLocation = fmt.Errorf("error getting account from lookup") + ErrLookupTableNotFound = fmt.Errorf("lookup table not found") + ErrGettingSeedAtLocation = fmt.Errorf("error getting address seed for location") +) + type Lookup struct { Optional bool AccountConstant *AccountConstant `json:"accountConstant,omitempty"` @@ -146,7 +152,7 @@ func (ac AccountConstant) Resolve() ([]*solana.AccountMeta, error) { func (al AccountLookup) Resolve(args any) ([]*solana.AccountMeta, error) { derivedValues, err := GetValuesAtLocation(args, al.Location) if err != nil { - return nil, fmt.Errorf("error getting account from lookup: %w", err) + return nil, fmt.Errorf("%w: %v", ErrLookupNotFoundAtLocation, err) } var metas []*solana.AccountMeta @@ -206,7 +212,7 @@ func (alt AccountsFromLookupTable) Resolve(derivedTableMap map[string]map[string // Fetch the inner map for the specified lookup table name innerMap, ok := derivedTableMap[alt.LookupTableName] if !ok { - return nil, fmt.Errorf("lookup table not found: %s", alt.LookupTableName) + return nil, fmt.Errorf("%w: %s", ErrLookupTableNotFound, alt.LookupTableName) } var result []*solana.AccountMeta @@ -332,7 +338,7 @@ func getSeedBytesCombinations( // Get value from a location (This doesn't have to be an address, it can be any value) bytes, err := GetValuesAtLocation(args, lookupSeed.Location) if err != nil { - return nil, fmt.Errorf("error getting address seed for location %q: %w", lookupSeed.Location, err) + return nil, fmt.Errorf("%w %q: %w", ErrGettingSeedAtLocation, lookupSeed.Location, err) } // append each byte array to the expansions for _, b := range bytes {