From 585fff542a4805c0513ee55c6b4c801953d41029 Mon Sep 17 00:00:00 2001 From: Evan Gray Date: Thu, 30 Nov 2023 15:49:32 -0500 Subject: [PATCH] node/solana: consistency defense-in-depth check --- node/hack/solana/account_lookup.go | 77 ++++++++++++++++++++++++++++++ node/pkg/watchers/solana/client.go | 28 ++++++++++- 2 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 node/hack/solana/account_lookup.go diff --git a/node/hack/solana/account_lookup.go b/node/hack/solana/account_lookup.go new file mode 100644 index 0000000000..ce10ee92fc --- /dev/null +++ b/node/hack/solana/account_lookup.go @@ -0,0 +1,77 @@ +package main + +import ( + "context" + "fmt" + "log" + + "github.com/gagliardetto/solana-go" + lookup "github.com/gagliardetto/solana-go/programs/address-lookup-table" + "github.com/gagliardetto/solana-go/rpc" +) + +func populateLookupTableAccounts(ctx context.Context, tx *solana.Transaction, rpcClient *rpc.Client) error { + if !tx.Message.IsVersioned() { + return nil + } + + tblKeys := tx.Message.GetAddressTableLookups().GetTableIDs() + if len(tblKeys) == 0 { + return nil + } + + resolutions := make(map[solana.PublicKey]solana.PublicKeySlice) + for _, key := range tblKeys { + fmt.Println(key) + info, err := rpcClient.GetAccountInfo(ctx, key) + if err != nil { + fmt.Println("We errored here!") + return err + } + + tableContent, err := lookup.DecodeAddressLookupTableState(info.GetBinary()) + if err != nil { + return err + } + + resolutions[key] = tableContent.Addresses + } + + err := tx.Message.SetAddressTables(resolutions) + if err != nil { + return err + } + + err = tx.Message.ResolveLookups() + if err != nil { + return err + } + + return nil +} + +func main() { + ctx := context.Background() + testTx, err := solana.SignatureFromBase58("2Jr3bAuEKwYBKmaqSmmFQ2R7xxxQpmjY8g3N3gMH49C62kBaweUgc9UCEcFhqcewAVnDLcBGWUSQrKZ7vdxpBbq4") + if (err != nil) { + log.Fatal("SignatureFromBase58 errored", err) + } + rpcClient := rpc.New("https://api.devnet.solana.com") + maxSupportedTransactionVersion := uint64(0) + tx, err := rpcClient.GetTransaction(ctx, testTx, &rpc.GetTransactionOpts{ + Encoding: solana.EncodingBase64, + MaxSupportedTransactionVersion: &maxSupportedTransactionVersion, + }) + if (err != nil) { + log.Fatal("getTransaction errored", err) + } + realTx, err := tx.Transaction.GetTransaction() + if (err != nil) { + log.Fatal("GetTransaction errored", err) + } + err = populateLookupTableAccounts(ctx, realTx, rpcClient) + if (err != nil) { + log.Fatal("populateLookupTableAccounts errored", err) + } + +} diff --git a/node/pkg/watchers/solana/client.go b/node/pkg/watchers/solana/client.go index 827e995dd3..3b80277b5a 100644 --- a/node/pkg/watchers/solana/client.go +++ b/node/pkg/watchers/solana/client.go @@ -93,8 +93,7 @@ type ( } MessagePublicationAccount struct { - VaaVersion uint8 - // Borsh does not seem to support booleans, so 0=false / 1=true + VaaVersion uint8 ConsistencyLevel uint8 EmitterAuthority vaa.Address MessageStatus uint8 @@ -166,6 +165,17 @@ func (c ConsistencyLevel) Commitment() (rpc.CommitmentType, error) { } } +func accountConsistencyLevelToCommitment(c uint8) (rpc.CommitmentType, error) { + switch c { + case 1: + return rpc.CommitmentConfirmed, nil + case 32: + return rpc.CommitmentFinalized, nil + default: + return "", fmt.Errorf("unsupported consistency level: %d", c) + } +} + const ( postMessageInstructionMinNumAccounts = 8 postMessageInstructionID = 0x01 @@ -802,6 +812,20 @@ func (s *SolanaWatcher) processMessageAccount(logger *zap.Logger, data []byte, a return } + // SECURITY: defense-in-depth, ensure the consistency level in the account matches the consistency level of the watcher + commitment, err := accountConsistencyLevelToCommitment(proposal.ConsistencyLevel) + if err != nil { + logger.Error( + "failed to parse proposal consistency level", + zap.Any("proposal", proposal), + zap.Error(err)) + return + } + if commitment != s.commitment { + logger.Debug("skipping message which does not match the watcher commitment", zap.Stringer("account", acc), zap.String("message commitment", string(commitment)), zap.String("watcher commitment", string(s.commitment))) + return + } + // As of 2023-11-09, Pythnet has a bug which is not zeroing out these fields appropriately. This carve out should be removed after a fix is deployed. if s.chainID != vaa.ChainIDPythNet { // SECURITY: ensure these fields are zeroed out. in the legacy solana program they were always zero, and in the 2023 rewrite they are zeroed once the account is finalized