From a7b8b8b045dc4b35ea94240cb466108c955afdb9 Mon Sep 17 00:00:00 2001 From: Silas Lenihan Date: Wed, 20 Nov 2024 10:35:39 -0500 Subject: [PATCH] Moved lookups logic to separate file --- pkg/solana/chainwriter/chain_writer.go | 289 ------------------------ pkg/solana/chainwriter/lookups.go | 296 +++++++++++++++++++++++++ 2 files changed, 296 insertions(+), 289 deletions(-) diff --git a/pkg/solana/chainwriter/chain_writer.go b/pkg/solana/chainwriter/chain_writer.go index a8714c715..93be11058 100644 --- a/pkg/solana/chainwriter/chain_writer.go +++ b/pkg/solana/chainwriter/chain_writer.go @@ -7,7 +7,6 @@ import ( "math/big" "github.com/gagliardetto/solana-go" - "github.com/gagliardetto/solana-go/rpc" commoncodec "github.com/smartcontractkit/chainlink-common/pkg/codec" "github.com/smartcontractkit/chainlink-common/pkg/codec/encodings/binary" @@ -47,60 +46,6 @@ type MethodConfig struct { DebugIDLocation string } -type Lookup interface { - Resolve(ctx context.Context, args any, derivedTableMap map[string]map[string][]*solana.AccountMeta, debugID string) ([]*solana.AccountMeta, error) -} - -// AccountConstant represents a fixed address, provided in Base58 format, converted into a `solana.PublicKey`. -type AccountConstant struct { - Name string - Address string - IsSigner bool - IsWritable bool -} - -// AccountLookup dynamically derives an account address from args using a specified location path. -type AccountLookup struct { - Name string - Location string - IsSigner bool - IsWritable bool -} - -// PDALookups generates Program Derived Addresses (PDA) by combining a derived public key with one or more seeds. -type PDALookups struct { - Name string - // The public key of the PDA to be combined with seeds. If there are multiple PublicKeys - // 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 - IsSigner bool - IsWritable bool -} - -type ValueLookup struct { - Location string -} - -// LookupTables represents a list of lookup tables that are used to derive addresses for a program. -type LookupTables struct { - DerivedLookupTables []DerivedLookupTable - StaticLookupTables []string -} - -// DerivedLookupTable represents a lookup table that is used to derive addresses for a program. -type DerivedLookupTable struct { - Name string - Accounts Lookup -} - -// AccountsFromLookupTable extracts accounts from a lookup table that was previously read and stored in memory. -type AccountsFromLookupTable struct { - LookupTablesName string - IncludeIndexes []int -} - func NewSolanaChainWriterService(reader client.Reader, txm txm.Txm, ge fees.Estimator, config ChainWriterConfig) (*SolanaChainWriterService, error) { codecs, err := parseIDLCodecs(config) if err != nil { @@ -198,240 +143,6 @@ func GetAddresses(ctx context.Context, args any, accounts []Lookup, derivedTable return addresses, nil } -func (ac AccountConstant) Resolve(_ context.Context, _ any, _ map[string]map[string][]*solana.AccountMeta, debugID string) ([]*solana.AccountMeta, error) { - address, err := solana.PublicKeyFromBase58(ac.Address) - if err != nil { - return nil, errorWithDebugID(fmt.Errorf("error getting account from constant: %w", err), debugID) - } - return []*solana.AccountMeta{ - { - PublicKey: address, - IsSigner: ac.IsSigner, - IsWritable: ac.IsWritable, - }, - }, nil -} - -func (al AccountLookup) Resolve(_ context.Context, args any, _ map[string]map[string][]*solana.AccountMeta, debugID string) ([]*solana.AccountMeta, error) { - derivedAddresses, err := GetAddressAtLocation(args, al.Location, debugID) - if err != nil { - return nil, errorWithDebugID(fmt.Errorf("error getting account from lookup: %w", err), debugID) - } - - var metas []*solana.AccountMeta - for _, address := range derivedAddresses { - metas = append(metas, &solana.AccountMeta{ - PublicKey: address, - IsSigner: al.IsSigner, - IsWritable: al.IsWritable, - }) - } - return metas, nil -} - -func (alt AccountsFromLookupTable) Resolve(_ context.Context, _ any, derivedTableMap map[string]map[string][]*solana.AccountMeta, debugID string) ([]*solana.AccountMeta, error) { - // Fetch the inner map for the specified lookup table name - innerMap, ok := derivedTableMap[alt.LookupTablesName] - if !ok { - return nil, errorWithDebugID(fmt.Errorf("lookup table not found: %s", alt.LookupTablesName), debugID) - } - - var result []*solana.AccountMeta - - // If no indices are specified, include all addresses - if len(alt.IncludeIndexes) == 0 { - for _, metas := range innerMap { - result = append(result, metas...) - } - return result, nil - } - - // Otherwise, include only addresses at the specified indices - for publicKey, metas := range innerMap { - for _, index := range alt.IncludeIndexes { - if index < 0 || index >= len(metas) { - return nil, errorWithDebugID(fmt.Errorf("invalid index %d for account %s in lookup table %s", index, publicKey, alt.LookupTablesName), debugID) - } - result = append(result, metas[index]) - } - } - - return result, nil -} - -func (pda PDALookups) Resolve(ctx context.Context, args any, derivedTableMap map[string]map[string][]*solana.AccountMeta, debugID string) ([]*solana.AccountMeta, error) { - publicKeys, err := GetAddresses(ctx, args, []Lookup{pda.PublicKey}, derivedTableMap, debugID) - if err != nil { - return nil, errorWithDebugID(fmt.Errorf("error getting public key for PDALookups: %w", err), debugID) - } - - seeds, err := getSeedBytes(ctx, pda, args, derivedTableMap, debugID) - if err != nil { - return nil, errorWithDebugID(fmt.Errorf("error getting seeds for PDALookups: %w", err), debugID) - } - - return generatePDAs(publicKeys, seeds, pda, debugID) -} - -// getSeedBytes extracts the seeds for the PDALookups. -// It handles both AddressSeeds (which are public keys) and ValueSeeds (which are byte arrays from input args). -func getSeedBytes(ctx context.Context, lookup PDALookups, args any, derivedTableMap map[string]map[string][]*solana.AccountMeta, debugID string) ([][]byte, error) { - var seedBytes [][]byte - - // Process AddressSeeds first (e.g., public keys) - for _, seed := range lookup.Seeds { - // Get the address(es) at the seed location - seedAddresses, err := GetAddresses(ctx, args, []Lookup{seed}, derivedTableMap, debugID) - if err != nil { - return nil, errorWithDebugID(fmt.Errorf("error getting address seed: %w", err), debugID) - } - - // Add each address seed as bytes - for _, address := range seedAddresses { - seedBytes = append(seedBytes, address.PublicKey.Bytes()) - } - } - - return seedBytes, nil -} - -// generatePDAs generates program-derived addresses (PDAs) from public keys and seeds. -func generatePDAs(publicKeys []*solana.AccountMeta, seeds [][]byte, lookup PDALookups, debugID string) ([]*solana.AccountMeta, error) { - if len(seeds) > 1 && len(publicKeys) > 1 { - return nil, errorWithDebugID(fmt.Errorf("multiple public keys and multiple seeds are not allowed"), debugID) - } - - var addresses []*solana.AccountMeta - for _, publicKeyMeta := range publicKeys { - address, _, err := solana.FindProgramAddress(seeds, publicKeyMeta.PublicKey) - if err != nil { - return nil, errorWithDebugID(fmt.Errorf("error finding program address: %w", err), debugID) - } - addresses = append(addresses, &solana.AccountMeta{ - PublicKey: address, - IsSigner: lookup.IsSigner, - IsWritable: lookup.IsWritable, - }) - } - return addresses, nil -} - -func (s *SolanaChainWriterService) getDerivedTableMap(ctx context.Context, lookupTables LookupTables, debugID string) (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 { - lookupTableMap, _, err := s.LoadTable(derivedLookup, ctx, s.reader, derivedTableMap, debugID) - if err != nil { - return nil, nil, errorWithDebugID(fmt.Errorf("error loading derived lookup table: %w", err), debugID) - } - - // 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 { - // Parse the static table address - tableAddress, err := solana.PublicKeyFromBase58(staticTable) - if err != nil { - return nil, nil, errorWithDebugID(fmt.Errorf("invalid static lookup table address: %s, error: %w", staticTable, err), debugID) - } - - // Fetch the account info for the static table - accountInfo, err := s.reader.GetAccountInfoWithOpts(ctx, tableAddress, &rpc.GetAccountInfoOpts{ - Encoding: "base64", - Commitment: rpc.CommitmentConfirmed, - }) - if err != nil || accountInfo == nil || accountInfo.Value == nil { - return nil, nil, errorWithDebugID(fmt.Errorf("error fetching account info for static table: %s, error: %w", staticTable, err), debugID) - } - - // Decode the account data into an array of public keys - addresses, err := decodeLookupTable(accountInfo.Value.Data.GetBinary()) - if err != nil { - return nil, nil, errorWithDebugID(fmt.Errorf("error decoding static lookup table data for %s: %w", staticTable, err), debugID) - } - - // Add the static lookup table to the map - staticTableMap[tableAddress] = addresses - } - - return derivedTableMap, staticTableMap, nil -} - -func (s *SolanaChainWriterService) LoadTable(rlt DerivedLookupTable, ctx context.Context, reader client.Reader, derivedTableMap map[string]map[string][]*solana.AccountMeta, debugID string) (map[string]map[string][]*solana.AccountMeta, []*solana.AccountMeta, error) { - // Resolve all addresses specified by the identifier - lookupTableAddresses, err := GetAddresses(ctx, nil, []Lookup{rlt.Accounts}, nil, debugID) - if err != nil { - return nil, nil, errorWithDebugID(fmt.Errorf("error resolving addresses for lookup table: %w", err), debugID) - } - - resultMap := make(map[string]map[string][]*solana.AccountMeta) - var lookupTableMetas []*solana.AccountMeta - - // Iterate over each address of the lookup table - for _, addressMeta := range lookupTableAddresses { - // Fetch account info - accountInfo, err := reader.GetAccountInfoWithOpts(ctx, addressMeta.PublicKey, &rpc.GetAccountInfoOpts{ - Encoding: "base64", - Commitment: rpc.CommitmentConfirmed, - }) - if err != nil || accountInfo == nil || accountInfo.Value == nil { - return nil, nil, errorWithDebugID(fmt.Errorf("error fetching account info for address %s: %w", addressMeta.PublicKey.String(), err), debugID) - } - - // Decode the account data into an array of public keys - addresses, err := decodeLookupTable(accountInfo.Value.Data.GetBinary()) - if err != nil { - return nil, nil, errorWithDebugID(fmt.Errorf("error decoding lookup table data for address %s: %w", addressMeta.PublicKey.String(), err), debugID) - } - - // 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][addr.String()] = append(resultMap[rlt.Name][addr.String()], &solana.AccountMeta{ - PublicKey: addr, - IsSigner: false, - IsWritable: false, - }) - } - - // Add the current lookup table address to the list of metas - lookupTableMetas = append(lookupTableMetas, addressMeta) - } - - return resultMap, lookupTableMetas, nil -} - -func decodeLookupTable(data []byte) (solana.PublicKeySlice, error) { - // Example logic to decode lookup table data; you may need to adjust based on the actual format of the data. - var addresses solana.PublicKeySlice - - // Assuming the data is a list of 32-byte public keys in binary format: - for i := 0; i < len(data); i += solana.PublicKeyLength { - if i+solana.PublicKeyLength > len(data) { - return nil, fmt.Errorf("invalid lookup table data length") - } - address := solana.PublicKeyFromBytes(data[i : i+solana.PublicKeyLength]) - addresses = append(addresses, address) - } - - return addresses, nil -} - func (s *SolanaChainWriterService) FilterLookupTableAddresses( accounts []*solana.AccountMeta, derivedTableMap map[string]map[string][]*solana.AccountMeta, diff --git a/pkg/solana/chainwriter/lookups.go b/pkg/solana/chainwriter/lookups.go index 313c18c45..520345104 100644 --- a/pkg/solana/chainwriter/lookups.go +++ b/pkg/solana/chainwriter/lookups.go @@ -1,2 +1,298 @@ package chainwriter +import ( + "context" + "fmt" + + "github.com/gagliardetto/solana-go" + "github.com/gagliardetto/solana-go/rpc" + "github.com/smartcontractkit/chainlink-solana/pkg/solana/client" +) + +type Lookup interface { + Resolve(ctx context.Context, args any, derivedTableMap map[string]map[string][]*solana.AccountMeta, debugID string) ([]*solana.AccountMeta, error) +} + +// AccountConstant represents a fixed address, provided in Base58 format, converted into a `solana.PublicKey`. +type AccountConstant struct { + Name string + Address string + IsSigner bool + IsWritable bool +} + +// AccountLookup dynamically derives an account address from args using a specified location path. +type AccountLookup struct { + Name string + Location string + IsSigner bool + IsWritable bool +} + +// PDALookups generates Program Derived Addresses (PDA) by combining a derived public key with one or more seeds. +type PDALookups struct { + Name string + // The public key of the PDA to be combined with seeds. If there are multiple PublicKeys + // 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 + IsSigner bool + IsWritable bool +} + +type ValueLookup struct { + Location string +} + +// LookupTables represents a list of lookup tables that are used to derive addresses for a program. +type LookupTables struct { + DerivedLookupTables []DerivedLookupTable + StaticLookupTables []string +} + +// DerivedLookupTable represents a lookup table that is used to derive addresses for a program. +type DerivedLookupTable struct { + Name string + Accounts Lookup +} + +// AccountsFromLookupTable extracts accounts from a lookup table that was previously read and stored in memory. +type AccountsFromLookupTable struct { + LookupTablesName string + IncludeIndexes []int +} + +func (ac AccountConstant) Resolve(_ context.Context, _ any, _ map[string]map[string][]*solana.AccountMeta, debugID string) ([]*solana.AccountMeta, error) { + address, err := solana.PublicKeyFromBase58(ac.Address) + if err != nil { + return nil, errorWithDebugID(fmt.Errorf("error getting account from constant: %w", err), debugID) + } + return []*solana.AccountMeta{ + { + PublicKey: address, + IsSigner: ac.IsSigner, + IsWritable: ac.IsWritable, + }, + }, nil +} + +func (al AccountLookup) Resolve(_ context.Context, args any, _ map[string]map[string][]*solana.AccountMeta, debugID string) ([]*solana.AccountMeta, error) { + derivedAddresses, err := GetAddressAtLocation(args, al.Location, debugID) + if err != nil { + return nil, errorWithDebugID(fmt.Errorf("error getting account from lookup: %w", err), debugID) + } + + var metas []*solana.AccountMeta + for _, address := range derivedAddresses { + metas = append(metas, &solana.AccountMeta{ + PublicKey: address, + IsSigner: al.IsSigner, + IsWritable: al.IsWritable, + }) + } + return metas, nil +} + +func (alt AccountsFromLookupTable) Resolve(_ context.Context, _ any, derivedTableMap map[string]map[string][]*solana.AccountMeta, debugID string) ([]*solana.AccountMeta, error) { + // Fetch the inner map for the specified lookup table name + innerMap, ok := derivedTableMap[alt.LookupTablesName] + if !ok { + return nil, errorWithDebugID(fmt.Errorf("lookup table not found: %s", alt.LookupTablesName), debugID) + } + + var result []*solana.AccountMeta + + // If no indices are specified, include all addresses + if len(alt.IncludeIndexes) == 0 { + for _, metas := range innerMap { + result = append(result, metas...) + } + return result, nil + } + + // Otherwise, include only addresses at the specified indices + for publicKey, metas := range innerMap { + for _, index := range alt.IncludeIndexes { + if index < 0 || index >= len(metas) { + return nil, errorWithDebugID(fmt.Errorf("invalid index %d for account %s in lookup table %s", index, publicKey, alt.LookupTablesName), debugID) + } + result = append(result, metas[index]) + } + } + + return result, nil +} + +func (pda PDALookups) Resolve(ctx context.Context, args any, derivedTableMap map[string]map[string][]*solana.AccountMeta, debugID string) ([]*solana.AccountMeta, error) { + publicKeys, err := GetAddresses(ctx, args, []Lookup{pda.PublicKey}, derivedTableMap, debugID) + if err != nil { + return nil, errorWithDebugID(fmt.Errorf("error getting public key for PDALookups: %w", err), debugID) + } + + seeds, err := getSeedBytes(ctx, pda, args, derivedTableMap, debugID) + if err != nil { + return nil, errorWithDebugID(fmt.Errorf("error getting seeds for PDALookups: %w", err), debugID) + } + + return generatePDAs(publicKeys, seeds, pda, debugID) +} + +// getSeedBytes extracts the seeds for the PDALookups. +// It handles both AddressSeeds (which are public keys) and ValueSeeds (which are byte arrays from input args). +func getSeedBytes(ctx context.Context, lookup PDALookups, args any, derivedTableMap map[string]map[string][]*solana.AccountMeta, debugID string) ([][]byte, error) { + var seedBytes [][]byte + + // Process AddressSeeds first (e.g., public keys) + for _, seed := range lookup.Seeds { + // Get the address(es) at the seed location + seedAddresses, err := GetAddresses(ctx, args, []Lookup{seed}, derivedTableMap, debugID) + if err != nil { + return nil, errorWithDebugID(fmt.Errorf("error getting address seed: %w", err), debugID) + } + + // Add each address seed as bytes + for _, address := range seedAddresses { + seedBytes = append(seedBytes, address.PublicKey.Bytes()) + } + } + + return seedBytes, nil +} + +// generatePDAs generates program-derived addresses (PDAs) from public keys and seeds. +func generatePDAs(publicKeys []*solana.AccountMeta, seeds [][]byte, lookup PDALookups, debugID string) ([]*solana.AccountMeta, error) { + if len(seeds) > 1 && len(publicKeys) > 1 { + return nil, errorWithDebugID(fmt.Errorf("multiple public keys and multiple seeds are not allowed"), debugID) + } + + var addresses []*solana.AccountMeta + for _, publicKeyMeta := range publicKeys { + address, _, err := solana.FindProgramAddress(seeds, publicKeyMeta.PublicKey) + if err != nil { + return nil, errorWithDebugID(fmt.Errorf("error finding program address: %w", err), debugID) + } + addresses = append(addresses, &solana.AccountMeta{ + PublicKey: address, + IsSigner: lookup.IsSigner, + IsWritable: lookup.IsWritable, + }) + } + return addresses, nil +} + +func (s *SolanaChainWriterService) getDerivedTableMap(ctx context.Context, lookupTables LookupTables, debugID string) (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 { + lookupTableMap, _, err := s.LoadTable(derivedLookup, ctx, s.reader, derivedTableMap, debugID) + if err != nil { + return nil, nil, errorWithDebugID(fmt.Errorf("error loading derived lookup table: %w", err), debugID) + } + + // 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 { + // Parse the static table address + tableAddress, err := solana.PublicKeyFromBase58(staticTable) + if err != nil { + return nil, nil, errorWithDebugID(fmt.Errorf("invalid static lookup table address: %s, error: %w", staticTable, err), debugID) + } + + // Fetch the account info for the static table + accountInfo, err := s.reader.GetAccountInfoWithOpts(ctx, tableAddress, &rpc.GetAccountInfoOpts{ + Encoding: "base64", + Commitment: rpc.CommitmentConfirmed, + }) + if err != nil || accountInfo == nil || accountInfo.Value == nil { + return nil, nil, errorWithDebugID(fmt.Errorf("error fetching account info for static table: %s, error: %w", staticTable, err), debugID) + } + + // Decode the account data into an array of public keys + addresses, err := decodeLookupTable(accountInfo.Value.Data.GetBinary()) + if err != nil { + return nil, nil, errorWithDebugID(fmt.Errorf("error decoding static lookup table data for %s: %w", staticTable, err), debugID) + } + + // Add the static lookup table to the map + staticTableMap[tableAddress] = addresses + } + + return derivedTableMap, staticTableMap, nil +} + +func (s *SolanaChainWriterService) LoadTable(rlt DerivedLookupTable, ctx context.Context, reader client.Reader, derivedTableMap map[string]map[string][]*solana.AccountMeta, debugID string) (map[string]map[string][]*solana.AccountMeta, []*solana.AccountMeta, error) { + // Resolve all addresses specified by the identifier + lookupTableAddresses, err := GetAddresses(ctx, nil, []Lookup{rlt.Accounts}, nil, debugID) + if err != nil { + return nil, nil, errorWithDebugID(fmt.Errorf("error resolving addresses for lookup table: %w", err), debugID) + } + + resultMap := make(map[string]map[string][]*solana.AccountMeta) + var lookupTableMetas []*solana.AccountMeta + + // Iterate over each address of the lookup table + for _, addressMeta := range lookupTableAddresses { + // Fetch account info + accountInfo, err := reader.GetAccountInfoWithOpts(ctx, addressMeta.PublicKey, &rpc.GetAccountInfoOpts{ + Encoding: "base64", + Commitment: rpc.CommitmentConfirmed, + }) + if err != nil || accountInfo == nil || accountInfo.Value == nil { + return nil, nil, errorWithDebugID(fmt.Errorf("error fetching account info for address %s: %w", addressMeta.PublicKey.String(), err), debugID) + } + + // Decode the account data into an array of public keys + addresses, err := decodeLookupTable(accountInfo.Value.Data.GetBinary()) + if err != nil { + return nil, nil, errorWithDebugID(fmt.Errorf("error decoding lookup table data for address %s: %w", addressMeta.PublicKey.String(), err), debugID) + } + + // 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][addr.String()] = append(resultMap[rlt.Name][addr.String()], &solana.AccountMeta{ + PublicKey: addr, + IsSigner: false, + IsWritable: false, + }) + } + + // Add the current lookup table address to the list of metas + lookupTableMetas = append(lookupTableMetas, addressMeta) + } + + return resultMap, lookupTableMetas, nil +} + +func decodeLookupTable(data []byte) (solana.PublicKeySlice, error) { + // Example logic to decode lookup table data; you may need to adjust based on the actual format of the data. + var addresses solana.PublicKeySlice + + // Assuming the data is a list of 32-byte public keys in binary format: + for i := 0; i < len(data); i += solana.PublicKeyLength { + if i+solana.PublicKeyLength > len(data) { + return nil, fmt.Errorf("invalid lookup table data length") + } + address := solana.PublicKeyFromBytes(data[i : i+solana.PublicKeyLength]) + addresses = append(addresses, address) + } + + return addresses, nil +}