diff --git a/integration-tests/relayinterface/lookups_test.go b/integration-tests/relayinterface/lookups_test.go index 7333b2e8d..fd148abff 100644 --- a/integration-tests/relayinterface/lookups_test.go +++ b/integration-tests/relayinterface/lookups_test.go @@ -145,8 +145,8 @@ func TestPDALookups(t *testing.T) { pdaLookup := chainwriter.PDALookups{ Name: "TestPDA", PublicKey: chainwriter.AccountConstant{Name: "ProgramID", Address: programID.String()}, - Seeds: []chainwriter.Lookup{ - chainwriter.AccountConstant{Name: "seed", Address: seed.String()}, + Seeds: []chainwriter.Seed{ + {Dynamic: chainwriter.AccountConstant{Name: "seed", Address: seed.String()}}, }, IsSigner: false, IsWritable: true, @@ -175,9 +175,9 @@ func TestPDALookups(t *testing.T) { pdaLookup := chainwriter.PDALookups{ Name: "TestPDA", PublicKey: chainwriter.AccountConstant{Name: "ProgramID", Address: programID.String()}, - Seeds: []chainwriter.Lookup{ - chainwriter.AccountLookup{Name: "seed1", Location: "test_seed"}, - chainwriter.AccountLookup{Name: "seed2", Location: "another_seed"}, + Seeds: []chainwriter.Seed{ + {Dynamic: chainwriter.AccountLookup{Name: "seed1", Location: "test_seed"}}, + {Dynamic: chainwriter.AccountLookup{Name: "seed2", Location: "another_seed"}}, }, IsSigner: false, IsWritable: true, @@ -198,8 +198,8 @@ func TestPDALookups(t *testing.T) { pdaLookup := chainwriter.PDALookups{ Name: "TestPDA", PublicKey: chainwriter.AccountConstant{Name: "ProgramID", Address: programID.String()}, - Seeds: []chainwriter.Lookup{ - chainwriter.AccountLookup{Name: "seed1", Location: "MissingSeed"}, + Seeds: []chainwriter.Seed{ + {Dynamic: chainwriter.AccountLookup{Name: "seed1", Location: "MissingSeed"}}, }, IsSigner: false, IsWritable: true, @@ -233,9 +233,9 @@ func TestPDALookups(t *testing.T) { pdaLookup := chainwriter.PDALookups{ Name: "TestPDA", PublicKey: chainwriter.AccountConstant{Name: "ProgramID", Address: programID.String()}, - Seeds: []chainwriter.Lookup{ - chainwriter.AccountLookup{Name: "seed1", Location: "test_seed"}, - chainwriter.AccountLookup{Name: "seed2", Location: "another_seed"}, + Seeds: []chainwriter.Seed{ + {Dynamic: chainwriter.AccountLookup{Name: "seed1", Location: "test_seed"}}, + {Dynamic: chainwriter.AccountLookup{Name: "seed2", Location: "another_seed"}}, }, IsSigner: false, IsWritable: true, @@ -403,8 +403,8 @@ func TestLookupTables(t *testing.T) { Accounts: chainwriter.PDALookups{ Name: "DataAccountPDA", PublicKey: chainwriter.AccountConstant{Name: "WriteTest", Address: programID.String()}, - Seeds: []chainwriter.Lookup{ - chainwriter.AccountLookup{Name: "seed1", Location: "seed1"}, + Seeds: []chainwriter.Seed{ + {Dynamic: chainwriter.AccountLookup{Name: "seed1", Location: "seed1"}}, }, IsSigner: false, IsWritable: false, diff --git a/pkg/solana/chainwriter/ccip_example_config.go b/pkg/solana/chainwriter/ccip_example_config.go index f277935d9..adbd4d324 100644 --- a/pkg/solana/chainwriter/ccip_example_config.go +++ b/pkg/solana/chainwriter/ccip_example_config.go @@ -47,8 +47,8 @@ func TestConfig() { IsWritable: false, }, // Seeds would be used if the user needed to look up addresses to use as seeds, which isn't the case here. - Seeds: []Lookup{ - AccountLookup{Location: "Message.TokenAmounts.DestTokenAddress"}, + Seeds: []Seed{ + {Dynamic: AccountLookup{Location: "Message.TokenAmounts.DestTokenAddress"}}, }, IsSigner: false, IsWritable: false, @@ -86,9 +86,9 @@ func TestConfig() { IsWritable: false, }, // Similar to the RegistryTokenState above, the user is looking up PDA accounts based on the dest tokens. - Seeds: []Lookup{ - AccountLookup{Location: "Message.TokenAmounts.DestTokenAddress"}, - AccountLookup{Location: "Message.Header.DestChainSelector"}, + Seeds: []Seed{ + {Dynamic: AccountLookup{Location: "Message.TokenAmounts.DestTokenAddress"}}, + {Dynamic: AccountLookup{Location: "Message.Header.DestChainSelector"}}, }, IsSigner: false, IsWritable: false, @@ -120,13 +120,13 @@ func TestConfig() { IsWritable: false, }, // The seed is the receiver address. - Seeds: []Lookup{ - AccountLookup{ + Seeds: []Seed{ + {Dynamic: AccountLookup{ Name: "Receiver", Location: "Message.Receiver", IsSigner: false, IsWritable: false, - }, + }}, }, }, // Account constant @@ -146,8 +146,8 @@ func TestConfig() { IsWritable: false, }, // The seed, once again, is the destination token address. - Seeds: []Lookup{ - AccountLookup{Location: "Message.TokenAmounts.DestTokenAddress"}, + Seeds: []Seed{ + {Dynamic: AccountLookup{Location: "Message.TokenAmounts.DestTokenAddress"}}, }, IsSigner: false, IsWritable: false, @@ -175,9 +175,9 @@ func TestConfig() { IsSigner: false, IsWritable: false, }, - Seeds: []Lookup{ - AccountLookup{Location: "Message.Header.DestChainSelector"}, - AccountLookup{Location: "Message.Header.SourceChainSelector"}, + Seeds: []Seed{ + {Dynamic: AccountLookup{Location: "Message.Header.DestChainSelector"}}, + {Dynamic: AccountLookup{Location: "Message.Header.SourceChainSelector"}}, }, IsSigner: false, IsWritable: false, @@ -191,11 +191,11 @@ func TestConfig() { IsSigner: false, IsWritable: false, }, - Seeds: []Lookup{ - AccountLookup{ + Seeds: []Seed{ + {Dynamic: AccountLookup{ // The seed is the merkle root of the report, as passed into the input params. Location: "args.MerkleRoot", - }, + }}, }, IsSigner: false, IsWritable: false, @@ -211,9 +211,9 @@ func TestConfig() { }, // In this case, the user configured multiple seeds. These will be used in conjunction // with the public key to generate one or multiple PDA accounts. - Seeds: []Lookup{ - AccountLookup{Location: "Message.Receiver"}, - AccountLookup{Location: "Message.Header.DestChainSelector"}, + Seeds: []Seed{ + {Dynamic: AccountLookup{Location: "Message.Receiver"}}, + {Dynamic: AccountLookup{Location: "Message.Header.DestChainSelector"}}, }, }, // Account constant @@ -284,11 +284,11 @@ func TestConfig() { IsSigner: false, IsWritable: false, }, - Seeds: []Lookup{ - AccountLookup{ + Seeds: []Seed{ + {Dynamic: AccountLookup{ // The seed is the merkle root of the report, as passed into the input params. Location: "args.MerkleRoots", - }, + }}, }, IsSigner: false, IsWritable: false, diff --git a/pkg/solana/chainwriter/chain_writer.go b/pkg/solana/chainwriter/chain_writer.go index e16a55e60..9ed8daca1 100644 --- a/pkg/solana/chainwriter/chain_writer.go +++ b/pkg/solana/chainwriter/chain_writer.go @@ -7,6 +7,8 @@ import ( "math/big" "github.com/gagliardetto/solana-go" + addresslookuptable "github.com/gagliardetto/solana-go/programs/address-lookup-table" + "github.com/gagliardetto/solana-go/rpc" commoncodec "github.com/smartcontractkit/chainlink-common/pkg/codec" "github.com/smartcontractkit/chainlink-common/pkg/codec/encodings/binary" @@ -80,22 +82,22 @@ func parseIDLCodecs(config ChainWriterConfig) (map[string]types.Codec, error) { for program, programConfig := range config.Programs { var idl codec.IDL if err := json.Unmarshal([]byte(programConfig.IDL), &idl); err != nil { - return nil, fmt.Errorf("failed to unmarshal IDL: %w", err) + return nil, fmt.Errorf("failed to unmarshal IDL for program: %s, error: %w", program, err) } idlCodec, err := codec.NewIDLInstructionsCodec(idl, binary.LittleEndian()) if err != nil { - return nil, fmt.Errorf("failed to create codec from IDL: %w", err) + return nil, fmt.Errorf("failed to create codec from IDL for program: %s, error: %w", program, err) } for method, methodConfig := range programConfig.Methods { if methodConfig.InputModifications != nil { modConfig, err := methodConfig.InputModifications.ToModifier(codec.DecoderHooks...) if err != nil { - return nil, fmt.Errorf("failed to create input modifications: %w", err) + return nil, fmt.Errorf("failed to create input modifications for method %s.%s, error: %w", program, method, err) } // add mods to codec idlCodec, err = codec.NewNamedModifierCodec(idlCodec, method, modConfig) if err != nil { - return nil, fmt.Errorf("failed to create named codec: %w", err) + return nil, fmt.Errorf("failed to create named codec for method %s.%s, error: %w", program, method, err) } } } @@ -250,6 +252,10 @@ func (s *SolanaChainWriterService) SubmitTransaction(ctx context.Context, contra codec := s.codecs[contractName] encodedPayload, err := codec.Encode(ctx, args, method) + + discriminator := GetDiscriminator(methodConfig.ChainSpecificName) + encodedPayload = append(discriminator[:], encodedPayload...) + if err != nil { return errorWithDebugID(fmt.Errorf("error encoding transaction payload: %w", err), debugID) } @@ -302,7 +308,7 @@ func (s *SolanaChainWriterService) SubmitTransaction(ctx context.Context, contra } // Enqueue transaction - if err = s.txm.Enqueue(ctx, accounts[0].PublicKey.String(), tx, &transactionID, blockhash.Value.LastValidBlockHeight); err != nil { + if err = s.txm.Enqueue(ctx, methodConfig.FromAddress, tx, &transactionID, blockhash.Value.LastValidBlockHeight); err != nil { return errorWithDebugID(fmt.Errorf("error enqueuing transaction: %w", err), debugID) } @@ -327,6 +333,99 @@ func (s *SolanaChainWriterService) GetFeeComponents(ctx context.Context) (*types }, nil } +func (s *SolanaChainWriterService) ResolveLookupTables(ctx context.Context, args any, lookupTables LookupTables) (map[string]map[string][]*solana.AccountMeta, map[solana.PublicKey]solana.PublicKeySlice, error) { + derivedTableMap := make(map[string]map[string][]*solana.AccountMeta) + staticTableMap := make(map[solana.PublicKey]solana.PublicKeySlice) + + // Read derived lookup tables + for _, derivedLookup := range lookupTables.DerivedLookupTables { + // 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 err != nil { + return nil, nil, fmt.Errorf("error loading derived lookup table: %w", err) + } + + // Merge the loaded table map into the result + for tableName, innerMap := range lookupTableMap { + if derivedTableMap[tableName] == nil { + derivedTableMap[tableName] = make(map[string][]*solana.AccountMeta) + } + for accountKey, metas := range innerMap { + derivedTableMap[tableName][accountKey] = metas + } + } + } + + // Read static lookup tables + for _, staticTable := range lookupTables.StaticLookupTables { + addressses, err := getLookupTableAddresses(ctx, s.reader, staticTable) + if err != nil { + return nil, nil, fmt.Errorf("error fetching static lookup table address: %w", err) + } + staticTableMap[staticTable] = addressses + } + + return derivedTableMap, staticTableMap, nil +} + +func (s *SolanaChainWriterService) loadTable(ctx context.Context, args any, rlt DerivedLookupTable) (map[string]map[string][]*solana.AccountMeta, []*solana.AccountMeta, error) { + // Resolve all addresses specified by the identifier + lookupTableAddresses, err := GetAddresses(ctx, args, []Lookup{rlt.Accounts}, nil, s.reader) + if err != nil { + return nil, nil, fmt.Errorf("error resolving addresses for lookup table: %w", err) + } + + // Nested map in case the lookup table resolves to multiple addresses + resultMap := make(map[string]map[string][]*solana.AccountMeta) + var lookupTableMetas []*solana.AccountMeta + + // Iterate over each address of the lookup table + for _, addressMeta := range lookupTableAddresses { + // Read the full list of addresses from the lookup table + addresses, err := getLookupTableAddresses(ctx, s.reader, addressMeta.PublicKey) + if err != nil { + return nil, nil, fmt.Errorf("error fetching lookup table address: %s, error: %w", addressMeta.PublicKey, err) + } + + // Create the inner map for this lookup table + if resultMap[rlt.Name] == nil { + resultMap[rlt.Name] = make(map[string][]*solana.AccountMeta) + } + + // Populate the inner map (keyed by the account public key) + for _, addr := range addresses { + resultMap[rlt.Name][addressMeta.PublicKey.String()] = append(resultMap[rlt.Name][addressMeta.PublicKey.String()], &solana.AccountMeta{ + PublicKey: addr, + IsSigner: addressMeta.IsSigner, + IsWritable: addressMeta.IsWritable, + }) + } + + // Add the current lookup table address to the list of metas + lookupTableMetas = append(lookupTableMetas, addressMeta) + } + + return resultMap, lookupTableMetas, nil +} + +func getLookupTableAddresses(ctx context.Context, reader client.Reader, tableAddress solana.PublicKey) (solana.PublicKeySlice, error) { + // Fetch the account info for the static table + accountInfo, err := reader.GetAccountInfoWithOpts(ctx, tableAddress, &rpc.GetAccountInfoOpts{ + Encoding: "base64", + Commitment: rpc.CommitmentFinalized, + }) + + if err != nil || accountInfo == nil || accountInfo.Value == nil { + return nil, fmt.Errorf("error fetching account info for table: %s, error: %w", tableAddress.String(), err) + } + alt, err := addresslookuptable.DecodeAddressLookupTableState(accountInfo.GetBinary()) + if err != nil { + return nil, fmt.Errorf("error decoding address lookup table state: %w", err) + } + return alt.Addresses, nil +} + func (s *SolanaChainWriterService) Start(_ context.Context) error { return s.StartOnce(ServiceName, func() error { return nil diff --git a/pkg/solana/chainwriter/chain_writer_test.go b/pkg/solana/chainwriter/chain_writer_test.go index 9798bdb4c..ff1d357c2 100644 --- a/pkg/solana/chainwriter/chain_writer_test.go +++ b/pkg/solana/chainwriter/chain_writer_test.go @@ -85,9 +85,9 @@ func TestChainWriter_GetAddresses(t *testing.T) { Accounts: chainwriter.PDALookups{ Name: "DataAccountPDA", PublicKey: chainwriter.AccountConstant{Name: "WriteTest", Address: programID.String()}, - Seeds: []chainwriter.Lookup{ + Seeds: []chainwriter.Seed{ // extract seed2 for PDA lookup - chainwriter.AccountLookup{Name: "seed2", Location: "seed2"}, + {Dynamic: chainwriter.AccountLookup{Name: "seed2", Location: "seed2"}}, }, IsSigner: derivedTablePdaLookupMeta.IsSigner, IsWritable: derivedTablePdaLookupMeta.IsWritable, @@ -129,9 +129,9 @@ func TestChainWriter_GetAddresses(t *testing.T) { chainwriter.PDALookups{ Name: "DataAccountPDA", PublicKey: chainwriter.AccountConstant{Name: "WriteTest", Address: solana.SystemProgramID.String()}, - Seeds: []chainwriter.Lookup{ + Seeds: []chainwriter.Seed{ // extract seed1 for PDA lookup - chainwriter.AccountLookup{Name: "seed1", Location: "seed1"}, + {Dynamic: chainwriter.AccountLookup{Name: "seed1", Location: "seed1"}}, }, IsSigner: pdaLookupMeta.IsSigner, IsWritable: pdaLookupMeta.IsWritable, @@ -272,9 +272,9 @@ func TestChainWriter_FilterLookupTableAddresses(t *testing.T) { Accounts: chainwriter.PDALookups{ Name: "DataAccountPDA", PublicKey: chainwriter.AccountConstant{Name: "WriteTest", Address: programID.String()}, - Seeds: []chainwriter.Lookup{ + Seeds: []chainwriter.Seed{ // extract seed1 for PDA lookup - chainwriter.AccountLookup{Name: "seed1", Location: "seed1"}, + {Dynamic: chainwriter.AccountLookup{Name: "seed1", Location: "seed1"}}, }, IsSigner: true, IsWritable: true, @@ -289,9 +289,9 @@ func TestChainWriter_FilterLookupTableAddresses(t *testing.T) { Accounts: chainwriter.PDALookups{ Name: "MiscPDA", PublicKey: chainwriter.AccountConstant{Name: "UnusedAccount", Address: unusedProgramID.String()}, - Seeds: []chainwriter.Lookup{ + Seeds: []chainwriter.Seed{ // extract seed2 for PDA lookup - chainwriter.AccountLookup{Name: "seed2", Location: "seed2"}, + {Dynamic: chainwriter.AccountLookup{Name: "seed2", Location: "seed2"}}, }, IsSigner: true, IsWritable: true, @@ -428,9 +428,9 @@ func TestChainWriter_SubmitTransaction(t *testing.T) { Accounts: chainwriter.PDALookups{ Name: "DataAccountPDA", PublicKey: chainwriter.AccountConstant{Name: "WriteTest", Address: programID.String()}, - Seeds: []chainwriter.Lookup{ + Seeds: []chainwriter.Seed{ // extract seed2 for PDA lookup - chainwriter.AccountLookup{Name: "seed2", Location: "seed2"}, + {Dynamic: chainwriter.AccountLookup{Name: "seed2", Location: "seed2"}}, }, IsSigner: false, IsWritable: false, @@ -459,9 +459,9 @@ func TestChainWriter_SubmitTransaction(t *testing.T) { chainwriter.PDALookups{ Name: "DataAccountPDA", PublicKey: chainwriter.AccountConstant{Name: "WriteTest", Address: solana.SystemProgramID.String()}, - Seeds: []chainwriter.Lookup{ + Seeds: []chainwriter.Seed{ // extract seed1 for PDA lookup - chainwriter.AccountLookup{Name: "seed1", Location: "seed1"}, + {Dynamic: chainwriter.AccountLookup{Name: "seed1", Location: "seed1"}}, }, IsSigner: false, IsWritable: false, diff --git a/pkg/solana/chainwriter/helpers.go b/pkg/solana/chainwriter/helpers.go index 6f78c7a63..a4b18e4d5 100644 --- a/pkg/solana/chainwriter/helpers.go +++ b/pkg/solana/chainwriter/helpers.go @@ -3,6 +3,7 @@ package chainwriter import ( "context" "crypto/sha256" + "encoding/binary" "errors" "fmt" "reflect" @@ -41,12 +42,15 @@ func GetValuesAtLocation(args any, location string) ([][]byte, error) { if err != nil { return nil, err } - for _, value := range addressList { if byteArray, ok := value.([]byte); ok { vals = append(vals, byteArray) } else if address, ok := value.(solana.PublicKey); ok { vals = append(vals, address.Bytes()) + } else if num, ok := value.(uint64); ok { + buf := make([]byte, 8) + binary.LittleEndian.PutUint64(buf, num) + vals = append(vals, buf) } else { return nil, fmt.Errorf("invalid value format at path: %s", location) } diff --git a/pkg/solana/chainwriter/lookups.go b/pkg/solana/chainwriter/lookups.go index b9d3ca7cd..fb8c9cd98 100644 --- a/pkg/solana/chainwriter/lookups.go +++ b/pkg/solana/chainwriter/lookups.go @@ -7,7 +7,6 @@ import ( ag_binary "github.com/gagliardetto/binary" "github.com/gagliardetto/solana-go" - addresslookuptable "github.com/gagliardetto/solana-go/programs/address-lookup-table" "github.com/gagliardetto/solana-go/rpc" "github.com/smartcontractkit/chainlink-solana/pkg/solana/client" @@ -33,6 +32,11 @@ type AccountLookup struct { IsWritable bool } +type Seed struct { + Static []byte // Static seed value + Dynamic Lookup // Dynamic lookup for seed +} + // PDALookups generates Program Derived Addresses (PDA) by combining a derived public key with one or more seeds. type PDALookups struct { Name string @@ -40,7 +44,7 @@ type PDALookups struct { // there will be multiple PDAs generated by combining each PublicKey with the seeds. PublicKey Lookup // Seeds to be derived from an additional lookup - Seeds []Lookup + Seeds []Seed IsSigner bool IsWritable bool // OPTIONAL: On-chain location and type of desired data from PDA (e.g. a sub-account of the data account) @@ -210,29 +214,35 @@ func getSeedBytes(ctx context.Context, lookup PDALookups, args any, derivedTable var seedBytes [][]byte for _, seed := range lookup.Seeds { - if lookupSeed, ok := seed.(AccountLookup); ok { - // 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: %w", err) - } - // validate seed length - for _, b := range bytes { - if len(b) > solana.MaxSeedLength { - return nil, fmt.Errorf("seed byte array exceeds maximum length of %d: got %d bytes", solana.MaxSeedLength, len(b)) + if seed.Static != nil { + seedBytes = append(seedBytes, seed.Static) + } + if seed.Dynamic != nil { + dynamicSeed := seed.Dynamic + if lookupSeed, ok := dynamicSeed.(AccountLookup); ok { + // Get value from a location (This doens'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: %w", err) + } + // validate seed length + for _, b := range bytes { + if len(b) > solana.MaxSeedLength { + return nil, fmt.Errorf("seed byte array exceeds maximum length of %d: got %d bytes", solana.MaxSeedLength, len(b)) + } + seedBytes = append(seedBytes, b) + } + } else { + // Get address seeds from the lookup + seedAddresses, err := GetAddresses(ctx, args, []Lookup{dynamicSeed}, derivedTableMap, reader) + if err != nil { + return nil, fmt.Errorf("error getting address seed: %w", err) } - seedBytes = append(seedBytes, b) - } - } else { - // Get address seeds from the lookup - seedAddresses, err := GetAddresses(ctx, args, []Lookup{seed}, derivedTableMap, reader) - if err != nil { - return nil, fmt.Errorf("error getting address seed: %w", err) - } - // Add each address seed as bytes - for _, address := range seedAddresses { - seedBytes = append(seedBytes, address.PublicKey.Bytes()) + // Add each address seed as bytes + for _, address := range seedAddresses { + seedBytes = append(seedBytes, address.PublicKey.Bytes()) + } } } } @@ -259,96 +269,3 @@ func generatePDAs(publicKeys []*solana.AccountMeta, seeds [][]byte, lookup PDALo } return addresses, nil } - -func (s *SolanaChainWriterService) ResolveLookupTables(ctx context.Context, args any, lookupTables LookupTables) (map[string]map[string][]*solana.AccountMeta, map[solana.PublicKey]solana.PublicKeySlice, error) { - derivedTableMap := make(map[string]map[string][]*solana.AccountMeta) - staticTableMap := make(map[solana.PublicKey]solana.PublicKeySlice) - - // Read derived lookup tables - for _, derivedLookup := range lookupTables.DerivedLookupTables { - // Load the lookup table - note: This could be multiple tables if the lookup is a PDALookups that resovles to more - // than one address - lookupTableMap, _, err := s.LoadTable(ctx, args, derivedLookup, s.reader, derivedTableMap) - if err != nil { - return nil, nil, fmt.Errorf("error loading derived lookup table: %w", err) - } - - // Merge the loaded table map into the result - for tableName, innerMap := range lookupTableMap { - if derivedTableMap[tableName] == nil { - derivedTableMap[tableName] = make(map[string][]*solana.AccountMeta) - } - for accountKey, metas := range innerMap { - derivedTableMap[tableName][accountKey] = metas - } - } - } - - // Read static lookup tables - for _, staticTable := range lookupTables.StaticLookupTables { - addressses, err := getLookupTableAddresses(ctx, s.reader, staticTable) - if err != nil { - return nil, nil, fmt.Errorf("error fetching static lookup table address: %w", err) - } - staticTableMap[staticTable] = addressses - } - - return derivedTableMap, staticTableMap, nil -} - -func (s *SolanaChainWriterService) LoadTable(ctx context.Context, args any, rlt DerivedLookupTable, reader client.Reader, derivedTableMap map[string]map[string][]*solana.AccountMeta) (map[string]map[string][]*solana.AccountMeta, []*solana.AccountMeta, error) { - // Resolve all addresses specified by the identifier - lookupTableAddresses, err := GetAddresses(ctx, args, []Lookup{rlt.Accounts}, nil, reader) - if err != nil { - return nil, nil, fmt.Errorf("error resolving addresses for lookup table: %w", err) - } - - // Nested map in case the lookup table resolves to multiple addresses - resultMap := make(map[string]map[string][]*solana.AccountMeta) - var lookupTableMetas []*solana.AccountMeta - - // Iterate over each address of the lookup table - for _, addressMeta := range lookupTableAddresses { - // Read the full list of addresses from the lookup table - addresses, err := getLookupTableAddresses(ctx, reader, addressMeta.PublicKey) - if err != nil { - return nil, nil, fmt.Errorf("error fetching lookup table address: %s, error: %w", addressMeta.PublicKey, err) - } - - // Create the inner map for this lookup table - if resultMap[rlt.Name] == nil { - resultMap[rlt.Name] = make(map[string][]*solana.AccountMeta) - } - - // Populate the inner map (keyed by the account public key) - for _, addr := range addresses { - resultMap[rlt.Name][addressMeta.PublicKey.String()] = append(resultMap[rlt.Name][addressMeta.PublicKey.String()], &solana.AccountMeta{ - PublicKey: addr, - IsSigner: addressMeta.IsSigner, - IsWritable: addressMeta.IsWritable, - }) - } - - // Add the current lookup table address to the list of metas - lookupTableMetas = append(lookupTableMetas, addressMeta) - } - - return resultMap, lookupTableMetas, nil -} - -func getLookupTableAddresses(ctx context.Context, reader client.Reader, tableAddress solana.PublicKey) (solana.PublicKeySlice, error) { - // Fetch the account info for the static table - accountInfo, err := reader.GetAccountInfoWithOpts(ctx, tableAddress, &rpc.GetAccountInfoOpts{ - Encoding: "base64", - Commitment: rpc.CommitmentFinalized, - }) - - if err != nil || accountInfo == nil || accountInfo.Value == nil { - return nil, fmt.Errorf("error fetching account info for table: %s, error: %w", tableAddress.String(), err) - } - alt, err := addresslookuptable.DecodeAddressLookupTableState(accountInfo.GetBinary()) - if err != nil { - return nil, fmt.Errorf("error decoding address lookup table state: %w", err) - } - return alt.Addresses, nil -}