diff --git a/node/pkg/watchers/solana/client.go b/node/pkg/watchers/solana/client.go index e9dea6ab98..df3e240c71 100644 --- a/node/pkg/watchers/solana/client.go +++ b/node/pkg/watchers/solana/client.go @@ -242,7 +242,7 @@ func NewSolanaWatcher( wsUrl: wsUrl, contract: contractAddress, rawContract: rawContract, - whLogPrefix: fmt.Sprintf("Program %s", rawContract), + whLogPrefix: createWhLogPrefix(rawContract), msgObservedLogLevel: msgObservedLogLevel, msgC: msgC, obsvReqC: obsvReqC, @@ -593,13 +593,10 @@ func (s *SolanaWatcher) fetchBlock(ctx context.Context, logger *zap.Logger, slot continue } - // If the logs don't contain the contract address, skip the transaction. - // ex: "Program 3u8hJUVTA4jH1wYAyUur7FFZVQ8H635K3tSHHF4ssjQ5 invoke [2]", - var possiblyWormhole bool - for i := 0; i < len(txRpc.Meta.LogMessages) && !possiblyWormhole; i++ { - possiblyWormhole = strings.HasPrefix(txRpc.Meta.LogMessages[i], s.whLogPrefix) - } - if !possiblyWormhole { + // If the logs don't contain the contract address followed by a sequence number, skip the transaction. + // ex: "Program 3u8hJUVTA4jH1wYAyUur7FFZVQ8H635K3tSHHF4ssjQ5 invoke [2]", "Program log: Sequence: 937184". + logger.Info("DEBUG_SOLANA", zap.Bool("isPossibleWormholeMessage", isPossibleWormholeMessage(s.whLogPrefix, txRpc.Meta.LogMessages)), zap.Any("logs", txRpc.Meta.LogMessages)) + if !isPossibleWormholeMessage(s.whLogPrefix, txRpc.Meta.LogMessages) { continue } @@ -668,6 +665,21 @@ func (s *SolanaWatcher) fetchBlock(ctx context.Context, logger *zap.Logger, slot return true } +func isPossibleWormholeMessage(whLogPrefix string, logMessages []string) bool { + for idx := 0; idx < len(logMessages); idx++ { + if strings.HasPrefix(logMessages[idx], whLogPrefix) { + if idx < len(logMessages)-1 && strings.HasPrefix(logMessages[idx+1], "Program log: Sequence:") { + return true + } + } + } + return false +} + +func createWhLogPrefix(rawContract string) string { + return fmt.Sprintf("Program %s invoke", rawContract) +} + func (s *SolanaWatcher) processParsedTransaction(ctx context.Context, logger *zap.Logger, parsedTxResult *rpc.GetParsedTransactionResult, signature solana.Signature, slot uint64, isReobservation bool) { foundContract := false for _, key := range parsedTxResult.Transaction.Message.AccountKeys { @@ -899,7 +911,7 @@ func (s *SolanaWatcher) processAccountSubscriptionData(_ context.Context, logger if value.Account.Owner != s.rawContract { // We got a message for the wrong contract on the websocket... uncomfortable... solanaConnectionErrors.WithLabelValues(s.networkName, string(s.commitment), "invalid_websocket_account").Inc() - return errors.New("Update for account with wrong owner") + return errors.New("update for account with wrong owner") } data, err = base64.StdEncoding.DecodeString(value.Account.Data[0]) diff --git a/node/pkg/watchers/solana/client_test.go b/node/pkg/watchers/solana/client_test.go index 15d41cae05..c9aff6a89e 100644 --- a/node/pkg/watchers/solana/client_test.go +++ b/node/pkg/watchers/solana/client_test.go @@ -13,3 +13,127 @@ func TestVerifyConstants(t *testing.T) { assert.Equal(t, SolanaAccountLen, solana.PublicKeyLength) assert.Equal(t, SolanaSignatureLen, len(solana.Signature{})) } + +var testWhLogPrefix = createWhLogPrefix("worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth") + +func TestIsPossibleWormholeMessageSuccess(t *testing.T) { + // These are actual logs see in mainnet on 8/29/2024. + logs := []string{ + "Program 3vxKRPwUTiEkeUVyoZ9MXFe1V71sRLbLqu1gRYaWmehQ invoke [1]", + "Program log: Instruction: TransferWrappedTokensWithRelay", + "Program 11111111111111111111111111111111 invoke [2]", + "Program 11111111111111111111111111111111 success", + "Program 11111111111111111111111111111111 invoke [2]", + "Program 11111111111111111111111111111111 success", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]", + "Program log: Instruction: InitializeAccount3", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4214 of 189385 compute units", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]", + "Program log: Instruction: Transfer", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4645 of 162844 compute units", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]", + "Program log: Instruction: Approve", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 2904 of 153570 compute units", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", + "Program wormDTUJ6AWPNvk59vGQbDvGJmqbDTdgWgAqcLBCgUb invoke [2]", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]", + "Program log: Instruction: Burn", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4790 of 91730 compute units", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth invoke [3]", + "Program log: Sequence: 937184", + "Program 11111111111111111111111111111111 invoke [4]", + "Program 11111111111111111111111111111111 success", + "Program 11111111111111111111111111111111 invoke [4]", + "Program 11111111111111111111111111111111 success", + "Program 11111111111111111111111111111111 invoke [4]", + "Program 11111111111111111111111111111111 success", + "Program worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth consumed 27141 of 74067 compute units", + "Program worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth success", + "Program wormDTUJ6AWPNvk59vGQbDvGJmqbDTdgWgAqcLBCgUb consumed 87537 of 133116 compute units", + "Program wormDTUJ6AWPNvk59vGQbDvGJmqbDTdgWgAqcLBCgUb success", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]", + "Program log: Instruction: CloseAccount", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 3015 of 42346 compute units", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", + "Program 3vxKRPwUTiEkeUVyoZ9MXFe1V71sRLbLqu1gRYaWmehQ consumed 186778 of 224134 compute units", + "Program 3vxKRPwUTiEkeUVyoZ9MXFe1V71sRLbLqu1gRYaWmehQ success", + "Program ComputeBudget111111111111111111111111111111 invoke [1]", + "Program ComputeBudget111111111111111111111111111111 success", + "Program ComputeBudget111111111111111111111111111111 invoke [1]", + "Program ComputeBudget111111111111111111111111111111 success", + } + + assert.True(t, isPossibleWormholeMessage(testWhLogPrefix, logs)) +} + +func TestIsPossibleWormholeMessageFailNoLogs(t *testing.T) { + // These are actual logs see in mainnet on 8/29/2024. + logs := []string{} + + assert.False(t, isPossibleWormholeMessage(testWhLogPrefix, logs)) +} + +func TestIsPossibleWormholeMessageFailNoWormhole(t *testing.T) { + // These are actual logs see in mainnet on 8/29/2024. + logs := []string{ + "Program 11111111111111111111111111111111 invoke [2]", + "Program 11111111111111111111111111111111 success", + "Program 11111111111111111111111111111111 invoke [2]", + "Program 11111111111111111111111111111111 success", + "Program 11111111111111111111111111111111 invoke [2]", + "Program 11111111111111111111111111111111 success", + "Program ComputeBudget111111111111111111111111111111 invoke [1]", + "Program ComputeBudget111111111111111111111111111111 success", + "Program ComputeBudget111111111111111111111111111111 invoke [1]", + "Program ComputeBudget111111111111111111111111111111 success", + } + + assert.False(t, isPossibleWormholeMessage(testWhLogPrefix, logs)) +} + +func TestIsPossibleWormholeMessageFailNoSequence(t *testing.T) { + // These are actual logs see in mainnet on 8/29/2024. + logs := []string{ + "Program worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth invoke [1]", + "Program 11111111111111111111111111111111 invoke [2]", + "Program 11111111111111111111111111111111 success", + "Program 11111111111111111111111111111111 invoke [2]", + "Program 11111111111111111111111111111111 success", + "Program 11111111111111111111111111111111 invoke [2]", + "Program 11111111111111111111111111111111 success", + "Program worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth consumed 37058 of 500000 compute units", + "Program worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth success", + "Program ComputeBudget111111111111111111111111111111 invoke [1]", + "Program ComputeBudget111111111111111111111111111111 success", + "Program ComputeBudget111111111111111111111111111111 invoke [1]", + "Program ComputeBudget111111111111111111111111111111 success", + } + + assert.False(t, isPossibleWormholeMessage(testWhLogPrefix, logs)) +} + +func TestIsPossibleWormholeMessageFailAtEnd(t *testing.T) { + // Note: I altered these logs to create this test. I don't know if this could ever happen. + logs := []string{ + "Program 11111111111111111111111111111111 invoke [2]", + "Program 11111111111111111111111111111111 success", + "Program 11111111111111111111111111111111 invoke [2]", + "Program 11111111111111111111111111111111 success", + "Program 11111111111111111111111111111111 invoke [2]", + "Program 11111111111111111111111111111111 success", + "Program worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth consumed 37058 of 500000 compute units", + "Program worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth success", + "Program ComputeBudget111111111111111111111111111111 invoke [1]", + "Program ComputeBudget111111111111111111111111111111 success", + "Program ComputeBudget111111111111111111111111111111 invoke [1]", + "Program ComputeBudget111111111111111111111111111111 success", + "Program worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth invoke [1]", + } + + assert.False(t, isPossibleWormholeMessage(testWhLogPrefix, logs)) +}