Skip to content

Commit

Permalink
updated comments and slight tweaks
Browse files Browse the repository at this point in the history
  • Loading branch information
silaslenihan committed Dec 27, 2024
1 parent d656123 commit f6302ac
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 47 deletions.
6 changes: 3 additions & 3 deletions integration-tests/relayinterface/lookups_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ func TestAccountLookups(t *testing.T) {
}

func TestPDALookups(t *testing.T) {
programID := solana.SystemProgramID
programID := chainwriter.GetRandomPubKey(t)

t.Run("PDALookup resolves valid PDA with constant address seeds", func(t *testing.T) {
seed := chainwriter.GetRandomPubKey(t)
Expand Down Expand Up @@ -281,7 +281,7 @@ func TestLookupTables(t *testing.T) {
table := chainwriter.CreateTestLookupTable(ctx, t, rpcClient, sender, pubKeys)
lookupConfig := chainwriter.LookupTables{
DerivedLookupTables: nil,
StaticLookupTables: []string{table.String()},
StaticLookupTables: []solana.PublicKey{table},
}
_, staticTableMap, resolveErr := cw.ResolveLookupTables(ctx, nil, lookupConfig)
require.NoError(t, resolveErr)
Expand Down Expand Up @@ -342,7 +342,7 @@ func TestLookupTables(t *testing.T) {

lookupConfig := chainwriter.LookupTables{
DerivedLookupTables: nil,
StaticLookupTables: []string{invalidTable.String()},
StaticLookupTables: []solana.PublicKey{invalidTable},
}

_, _, err = cw.ResolveLookupTables(ctx, nil, lookupConfig)
Expand Down
12 changes: 6 additions & 6 deletions pkg/solana/chainwriter/ccip_example_config.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package chainwriter

import (
"fmt"
"github.com/gagliardetto/solana-go"
)

func TestConfig() {
Expand All @@ -13,8 +13,8 @@ func TestConfig() {
systemProgramAddress := "4Nn9dsYBcSTzRbK9hg9kzCUdrCSkMZq1UR6Vw1Tkaf6E"
computeBudgetProgramAddress := "4Nn9dsYBcSTzRbK9hg9kzCUdrCSkMZq1UR6Vw1Tkaf6F"
sysvarProgramAddress := "4Nn9dsYBcSTzRbK9hg9kzCUdrCSkMZq1UR6Vw1Tkaf6G"
commonAddressesLookupTable := "4Nn9dsYBcSTzRbK9hg9kzCUdrCSkMZq1UR6Vw1Tkaf6H"
routerLookupTable := "4Nn9dsYBcSTzRbK9hg9kzCUdrCSkMZq1UR6Vw1Tkaf6I"
commonAddressesLookupTable := solana.MustPublicKeyFromBase58("4Nn9dsYBcSTzRbK9hg9kzCUdrCSkMZq1UR6Vw1Tkaf6H")
routerLookupTable := solana.MustPublicKeyFromBase58("4Nn9dsYBcSTzRbK9hg9kzCUdrCSkMZq1UR6Vw1Tkaf6I")
userAddress := "4Nn9dsYBcSTzRbK9hg9kzCUdrCSkMZq1UR6Vw1Tkaf6J"

executionReportSingleChainIDL := `{"name":"ExecutionReportSingleChain","type":{"kind":"struct","fields":[{"name":"source_chain_selector","type":"u64"},{"name":"message","type":{"defined":"Any2SolanaRampMessage"}},{"name":"root","type":{"array":["u8",32]}},{"name":"proofs","type":{"vec":{"array":["u8",32]}}}]}},{"name":"Any2SolanaRampMessage","type":{"kind":"struct","fields":[{"name":"header","type":{"defined":"RampMessageHeader"}},{"name":"sender","type":{"vec":"u8"}},{"name":"data","type":{"vec":"u8"}},{"name":"receiver","type":{"array":["u8",32]}},{"name":"extra_args","type":{"defined":"SolanaExtraArgs"}}]}},{"name":"RampMessageHeader","type":{"kind":"struct","fields":[{"name":"message_id","type":{"array":["u8",32]}},{"name":"source_chain_selector","type":"u64"},{"name":"dest_chain_selector","type":"u64"},{"name":"sequence_number","type":"u64"},{"name":"nonce","type":"u64"}]}},{"name":"SolanaExtraArgs","type":{"kind":"struct","fields":[{"name":"compute_units","type":"u32"},{"name":"allow_out_of_order_execution","type":"bool"}]}}`
Expand Down Expand Up @@ -58,7 +58,7 @@ func TestConfig() {
// Static lookup tables are the traditional use case (point 2 above) of Lookup tables. These are lookup
// tables which contain commonly used addresses in all CCIP execute transactions. The ChainWriter reads
// these lookup tables and appends them to the transaction to reduce the size of the transaction.
StaticLookupTables: []string{
StaticLookupTables: []solana.PublicKey{
commonAddressesLookupTable,
routerLookupTable,
},
Expand Down Expand Up @@ -255,7 +255,7 @@ func TestConfig() {
InputModifications: nil,
ChainSpecificName: "commit",
LookupTables: LookupTables{
StaticLookupTables: []string{
StaticLookupTables: []solana.PublicKey{
commonAddressesLookupTable,
routerLookupTable,
},
Expand Down Expand Up @@ -329,5 +329,5 @@ func TestConfig() {
},
},
}
fmt.Println(chainWriterConfig)
_ = chainWriterConfig
}
44 changes: 36 additions & 8 deletions pkg/solana/chainwriter/chain_writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,6 @@ for Solana transactions. It handles constant addresses, dynamic lookups, program
### Error Handling:
- Errors are wrapped with the `debugID` for easier tracing.
*/
// GetAddresses resolves account addresses from various `Lookup` configurations to build the required `solana.AccountMeta` list
// for Solana transactions.
func GetAddresses(ctx context.Context, args any, accounts []Lookup, derivedTableMap map[string]map[string][]*solana.AccountMeta, reader client.Reader) ([]*solana.AccountMeta, error) {
var addresses []*solana.AccountMeta
for _, accountConfig := range accounts {
Expand All @@ -149,6 +147,10 @@ func GetAddresses(ctx context.Context, args any, accounts []Lookup, derivedTable
return addresses, nil
}

// 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
// addresses that match entries in `accounts`.
func (s *SolanaChainWriterService) FilterLookupTableAddresses(
accounts []*solana.AccountMeta,
derivedTableMap map[string]map[string][]*solana.AccountMeta,
Expand Down Expand Up @@ -203,7 +205,30 @@ func (s *SolanaChainWriterService) FilterLookupTableAddresses(
return filteredLookupTables
}

func (s *SolanaChainWriterService) SubmitTransaction(ctx context.Context, contractName, method string, args any, transactionID string, toAddress string, meta *types.TxMeta, value *big.Int) error {
// SubmitTransaction builds, encodes, and enqueues a transaction using the provided program
// configuration and method details. It relies on the configured IDL, account lookups, and
// lookup tables to gather the necessary accounts and data. The function retrieves the latest
// blockhash and assigns it to the transaction, so callers do not need to provide one.
//
// Submissions and retries are handled by the underlying transaction manager. If a “debug ID”
// location is configured, SubmitTransaction extracts it from the provided `args` and attaches
// it to errors for easier troubleshooting. Only the first debug ID it encounters will be used.
//
// Parameters:
// - ctx: The context for cancellation and timeouts.
// - contractName: Identifies which Solana program config to use from `s.config.Programs`.
// - method: Specifies which method config to invoke within the chosen program config.
// - args: Arbitrary arguments that are encoded into the transaction payload and/or used for dynamic address lookups.
// - transactionID: A unique identifier for the transaction, used for tracking within the transaction manager.
// - toAddress: The on-chain address (program ID) to which the transaction is directed.
// - meta: Currently unused; included for interface compatibility.
// - value: Currently unused; included for interface compatibility.
//
// Returns:
//
// An error if any stage of the transaction preparation or enqueueing fails. A nil return
// indicates that the transaction was successfully submitted to the transaction manager.
func (s *SolanaChainWriterService) SubmitTransaction(ctx context.Context, contractName, method string, args any, transactionID string, toAddress string, _ *types.TxMeta, _ *big.Int) error {
programConfig, exists := s.config.Programs[contractName]
if !exists {
return fmt.Errorf("failed to find program config for contract name: %s", contractName)
Expand Down Expand Up @@ -241,6 +266,14 @@ func (s *SolanaChainWriterService) SubmitTransaction(ctx context.Context, contra
return errorWithDebugID(fmt.Errorf("error resolving account addresses: %w", err), debugID)
}

feePayer, err := solana.PublicKeyFromBase58(methodConfig.FromAddress)
if err != nil {
return errorWithDebugID(fmt.Errorf("error parsing fee payer address: %w", err), debugID)
}

accounts = append([]*solana.AccountMeta{solana.Meta(feePayer).SIGNER().WRITE()}, accounts...)
accounts = append(accounts, solana.Meta(solana.SystemProgramID))

// Filter the lookup table addresses based on which accounts are actually used
filteredLookupTableMap := s.FilterLookupTableAddresses(accounts, derivedTableMap, staticTableMap)

Expand All @@ -256,11 +289,6 @@ func (s *SolanaChainWriterService) SubmitTransaction(ctx context.Context, contra
return errorWithDebugID(fmt.Errorf("error parsing program ID: %w", err), debugID)
}

feePayer, err := solana.PublicKeyFromBase58(methodConfig.FromAddress)
if err != nil {
return errorWithDebugID(fmt.Errorf("error parsing fee payer address: %w", err), debugID)
}

tx, err := solana.NewTransaction(
[]solana.Instruction{
solana.NewInstruction(programID, accounts, encodedPayload),
Expand Down
22 changes: 12 additions & 10 deletions pkg/solana/chainwriter/chain_writer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ func TestChainWriter_FilterLookupTableAddresses(t *testing.T) {
Name: "DataAccountPDA",
PublicKey: chainwriter.AccountConstant{Name: "WriteTest", Address: programID.String()},
Seeds: []chainwriter.Lookup{
// extract seed2 for PDA lookup
// extract seed1 for PDA lookup
chainwriter.AccountLookup{Name: "seed1", Location: "seed1"},
},
IsSigner: true,
Expand Down Expand Up @@ -302,7 +302,7 @@ func TestChainWriter_FilterLookupTableAddresses(t *testing.T) {
},
},
},
StaticLookupTables: []string{staticLookupTablePubkey1.String(), staticLookupTablePubkey2.String()},
StaticLookupTables: []solana.PublicKey{staticLookupTablePubkey1, staticLookupTablePubkey2},
}

args := map[string]interface{}{
Expand Down Expand Up @@ -441,7 +441,7 @@ func TestChainWriter_SubmitTransaction(t *testing.T) {
},
},
},
StaticLookupTables: []string{staticLookupTablePubkey.String()},
StaticLookupTables: []solana.PublicKey{staticLookupTablePubkey},
},
Accounts: []chainwriter.Lookup{
chainwriter.AccountConstant{
Expand Down Expand Up @@ -528,16 +528,18 @@ func TestChainWriter_SubmitTransaction(t *testing.T) {
rw.On("LatestBlockhash", mock.Anything).Return(&rpc.GetLatestBlockhashResult{Value: &rpc.LatestBlockhashResult{Blockhash: recentBlockHash, LastValidBlockHeight: uint64(100)}}, nil).Once()
txID := uuid.NewString()

txm.On("Enqueue", mock.Anything, account1.String(), mock.MatchedBy(func(tx *solana.Transaction) bool {
txm.On("Enqueue", mock.Anything, admin.String(), mock.MatchedBy(func(tx *solana.Transaction) bool {
// match transaction fields to ensure it was built as expected
require.Equal(t, recentBlockHash, tx.Message.RecentBlockhash)
require.Len(t, tx.Message.Instructions, 1)
require.Len(t, tx.Message.AccountKeys, 5) // fee payer + derived accounts
require.Equal(t, admin, tx.Message.AccountKeys[0]) // fee payer
require.Equal(t, account1, tx.Message.AccountKeys[1]) // account constant
require.Equal(t, account2, tx.Message.AccountKeys[2]) // account lookup
require.Equal(t, account3, tx.Message.AccountKeys[3]) // pda lookup
require.Equal(t, programID, tx.Message.AccountKeys[4]) // instruction program ID
require.Len(t, tx.Message.AccountKeys, 6) // fee payer + derived accounts
require.Equal(t, admin, tx.Message.AccountKeys[0]) // fee payer
require.Equal(t, account1, tx.Message.AccountKeys[1]) // account constant
require.Equal(t, account2, tx.Message.AccountKeys[2]) // account lookup
require.Equal(t, account3, tx.Message.AccountKeys[3]) // pda lookup
require.Equal(t, solana.SystemProgramID, tx.Message.AccountKeys[4]) // system program ID
require.Equal(t, programID, tx.Message.AccountKeys[5]) // instruction program ID
// instruction program ID
require.Len(t, tx.Message.AddressTableLookups, 1) // address table look contains entry
require.Equal(t, derivedLookupTablePubkey, tx.Message.AddressTableLookups[0].AccountKey) // address table
return true
Expand Down
3 changes: 3 additions & 0 deletions pkg/solana/chainwriter/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ func GetDebugIDAtLocation(args any, location string) (string, error) {
return "", err
}

if len(debugIDList) == 0 {
return "", errors.New("no debug ID found at location: " + location)
}
// there should only be one debug ID, others will be ignored.
debugID := string(debugIDList[0])

Expand Down
32 changes: 12 additions & 20 deletions pkg/solana/chainwriter/lookups.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,10 @@ type InternalField struct {
Location string
}

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
StaticLookupTables []solana.PublicKey
}

// DerivedLookupTable represents a lookup table that is used to derive addresses for a program.
Expand Down Expand Up @@ -212,19 +208,18 @@ func decodeBorshIntoType(data []byte, typ reflect.Type) (interface{}, error) {
// 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, reader client.Reader) ([][]byte, error) {
var seedBytes [][]byte
maxSeedLength := 32

for _, seed := range lookup.Seeds {
if lookupSeed, ok := seed.(AccountLookup); ok {
// Get value from a location (This doens't have to be an address, it can be any value)
// 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) > maxSeedLength {
return nil, fmt.Errorf("seed byte array exceeds maximum length of %d: got %d bytes", maxSeedLength, len(b))
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)
}
Expand All @@ -247,7 +242,7 @@ func getSeedBytes(ctx context.Context, lookup PDALookups, args any, derivedTable

// generatePDAs generates program-derived addresses (PDAs) from public keys and seeds.
func generatePDAs(publicKeys []*solana.AccountMeta, seeds [][]byte, lookup PDALookups) ([]*solana.AccountMeta, error) {
if len(seeds) > 16 {
if len(seeds) > solana.MaxSeeds {
return nil, fmt.Errorf("seed maximum exceeded: %d", len(seeds))
}
var addresses []*solana.AccountMeta
Expand All @@ -271,6 +266,8 @@ func (s *SolanaChainWriterService) ResolveLookupTables(ctx context.Context, args

// 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)
Expand All @@ -289,17 +286,11 @@ func (s *SolanaChainWriterService) ResolveLookupTables(ctx context.Context, args

// 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, fmt.Errorf("invalid static lookup table address: %s, error: %w", staticTable, err)
}

addressses, err := getLookupTableAddresses(ctx, s.reader, tableAddress)
addressses, err := getLookupTableAddresses(ctx, s.reader, staticTable)
if err != nil {
return nil, nil, fmt.Errorf("error fetching static lookup table address: %w", err)
}
staticTableMap[tableAddress] = addressses
staticTableMap[staticTable] = addressses
}

return derivedTableMap, staticTableMap, nil
Expand All @@ -312,15 +303,16 @@ func (s *SolanaChainWriterService) LoadTable(ctx context.Context, args any, rlt
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 {
// Fetch account info
// 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: %w", err)
return nil, nil, fmt.Errorf("error fetching lookup table address: %s, error: %w", addressMeta.PublicKey, err)
}

// Create the inner map for this lookup table
Expand Down

0 comments on commit f6302ac

Please sign in to comment.