瀏覽代碼

Node/Solana: Shim watcher cleanup (#4269)

* Node/Solana: Shim watcher cleanup

* Code review rework
bruce-riley 8 月之前
父節點
當前提交
4e4eaa6f81
共有 2 個文件被更改,包括 1344 次插入34 次删除
  1. 49 16
      node/pkg/watchers/solana/shim.go
  2. 1295 18
      node/pkg/watchers/solana/shim_test.go

+ 49 - 16
node/pkg/watchers/solana/shim.go

@@ -29,6 +29,7 @@ package solana
 import (
 	"bytes"
 	"encoding/hex"
+	"errors"
 	"fmt"
 	"time"
 
@@ -175,6 +176,9 @@ func (s *SolanaWatcher) shimProcessTopLevelInstruction(
 	isReobservation bool,
 ) (bool, error) {
 	topLevelIdx := uint16(topLevelIndex)
+	if topLevelIdx >= uint16(len(tx.Message.Instructions)) {
+		return false, fmt.Errorf("topLevelIndex %d is greater than the total number of instructions in the tx message, %d", topLevelIdx, len(tx.Message.Instructions))
+	}
 	inst := tx.Message.Instructions[topLevelIdx]
 
 	// The only top-level instruction generated by the shim contract is the PostMessage event. Parse that to get
@@ -192,7 +196,7 @@ func (s *SolanaWatcher) shimProcessTopLevelInstruction(
 	outerIdx := -1
 	for idx, inner := range innerInstructions {
 		if inner.Index == topLevelIdx {
-			outerIdx = int(idx)
+			outerIdx = idx
 			break
 		}
 	}
@@ -202,7 +206,7 @@ func (s *SolanaWatcher) shimProcessTopLevelInstruction(
 	}
 
 	// Process the inner instructions associated with this shim top-level instruction and produce an observation event.
-	err = s.shimProcessRest(logger, whProgramIndex, shimProgramIndex, tx, innerInstructions[outerIdx].Instructions, outerIdx, 0, postMessage, alreadyProcessed, isReobservation)
+	err = s.shimProcessRest(logger, whProgramIndex, shimProgramIndex, tx, innerInstructions[outerIdx].Instructions, outerIdx, 0, postMessage, alreadyProcessed, isReobservation, true)
 	if err != nil {
 		return false, fmt.Errorf("failed to process inner instructions for top-level shim instruction %d: %w", topLevelIdx, err)
 	}
@@ -226,6 +230,10 @@ func (s *SolanaWatcher) shimProcessInnerInstruction(
 	alreadyProcessed ShimAlreadyProcessed,
 	isReobservation bool,
 ) (bool, error) {
+	if startIdx >= len(innerInstructions) {
+		return false, fmt.Errorf("startIdx %d is out of bounds of slice innerInstructions (length: %d)", startIdx, len(innerInstructions))
+	}
+
 	// See if this is a PostMessage event from the shim contract. If so, parse it. If not, bail out now.
 	postMessage, err := shimParsePostMessage(s.shimPostMessageDiscriminator, innerInstructions[startIdx].Data)
 	if err != nil {
@@ -238,7 +246,7 @@ func (s *SolanaWatcher) shimProcessInnerInstruction(
 
 	alreadyProcessed.add(outerIdx, startIdx)
 
-	err = s.shimProcessRest(logger, whProgramIndex, shimProgramIndex, tx, innerInstructions, outerIdx, startIdx+1, postMessage, alreadyProcessed, isReobservation)
+	err = s.shimProcessRest(logger, whProgramIndex, shimProgramIndex, tx, innerInstructions, outerIdx, startIdx+1, postMessage, alreadyProcessed, isReobservation, false)
 	if err != nil {
 		return false, fmt.Errorf("failed to process inner instructions for inner shim instruction %d: %w", outerIdx, err)
 	}
@@ -260,7 +268,12 @@ func (s *SolanaWatcher) shimProcessRest(
 	postMessage *ShimPostMessageData,
 	alreadyProcessed ShimAlreadyProcessed,
 	isReobservation bool,
+	isTopLevel bool,
 ) error {
+	if postMessage == nil {
+		return errors.New("postMessage is nil")
+	}
+
 	// Loop through the inner instructions after the shim PostMessage and do the following:
 	// 1) Find the core event and verify it is unreliable with an empty payload.
 	// 2) Find the shim MessageEvent to get the rest of the fields we need for the observation.
@@ -269,37 +282,55 @@ func (s *SolanaWatcher) shimProcessRest(
 	var verifiedCoreEvent bool
 	var messageEvent *ShimMessageEventData
 	var err error
-	coreEventFound := false
 	for idx := startIdx; idx < len(innerInstructions); idx++ {
 		inst := innerInstructions[idx]
 		if inst.ProgramIDIndex == whProgramIndex {
-			if verifiedCoreEvent, err = shimVerifyCoreMessage(inst.Data); err != nil {
+			foundIt, err := shimVerifyCoreMessage(inst.Data)
+			if err != nil {
 				return fmt.Errorf("failed to verify inner core instruction for shim instruction %d, %d: %w", outerIdx, idx, err)
 			}
-			alreadyProcessed.add(outerIdx, idx)
-			coreEventFound = true
-		} else if inst.ProgramIDIndex == shimProgramIndex {
-			if !coreEventFound {
-				return fmt.Errorf("detected an inner shim message event instruction before the core event for shim instruction %d, %d: %w", outerIdx, idx, err)
+			if foundIt {
+				if verifiedCoreEvent {
+					return fmt.Errorf("detected multiple inner core instructions when there should not be at instruction %d, %d", outerIdx, idx)
+				}
+				alreadyProcessed.add(outerIdx, idx)
+				verifiedCoreEvent = true
 			}
-			messageEvent, err = shimParseMessageEvent(s.shimMessageEventDiscriminator, inst.Data)
+		} else if inst.ProgramIDIndex == shimProgramIndex {
+			thisEvent, err := shimParseMessageEvent(s.shimMessageEventDiscriminator, inst.Data)
 			if err != nil {
 				return fmt.Errorf("failed to parse inner shim message event instruction for shim instruction %d, %d: %w", outerIdx, idx, err)
 			}
-			alreadyProcessed.add(outerIdx, idx)
+
+			if thisEvent != nil {
+				if !verifiedCoreEvent {
+					return fmt.Errorf("detected an inner shim message event instruction before the core event for shim instruction %d, %d: %w", outerIdx, idx, err)
+				}
+
+				if messageEvent != nil {
+					return fmt.Errorf("detected multiple shim message event instructions when there should not be at instruction %d, %d", outerIdx, idx)
+				}
+
+				messageEvent = thisEvent
+				alreadyProcessed.add(outerIdx, idx)
+			}
 		}
 
 		if verifiedCoreEvent && messageEvent != nil {
-			break
+			// In the direct (top level instruction) case, we need to keep looking to make sure there are no other shim publications in this inner instruction set,
+			// but in the integration (inner instruction) case, that is valid, so we can stop looking. Any additional shim publications will be handled separately.
+			if !isTopLevel {
+				break
+			}
 		}
 	}
 
 	if !verifiedCoreEvent {
-		return fmt.Errorf("failed to find inner core instruction for shim instruction %d", outerIdx)
+		return fmt.Errorf("failed to find inner core instruction for shim instruction %d, %d", outerIdx, startIdx)
 	}
 
 	if messageEvent == nil {
-		return fmt.Errorf("failed to find inner shim message event instruction for shim instruction %d", outerIdx)
+		return fmt.Errorf("failed to find inner shim message event instruction for shim instruction %d, %d", outerIdx, startIdx)
 	}
 
 	commitment, err := postMessage.ConsistencyLevel.Commitment()
@@ -328,7 +359,9 @@ func (s *SolanaWatcher) shimProcessRest(
 		Payload:          postMessage.Payload,
 		ConsistencyLevel: uint8(postMessage.ConsistencyLevel),
 		IsReobservation:  isReobservation,
-		Unreliable:       false,
+
+		// Shim messages are always reliable.
+		Unreliable: false,
 	}
 
 	solanaMessagesConfirmed.WithLabelValues(s.networkName).Inc()

+ 1295 - 18
node/pkg/watchers/solana/shim_test.go

@@ -330,14 +330,13 @@ func TestShimDirect(t *testing.T) {
 	msg := <-msgC
 	require.NotNil(t, msg)
 
-	// TODO: Can't check this until we switch MessagePublication.TxHash to be a byte array rather than a hash.
-	// expectedTxHash, err := vaa.StringToHash("7647cd98fd14c6e3cdfe35bc64bbc476abcdb5ab12e8d31e3151d132ed1e0eeb4595fda4779f69dbe00ff14aadad3fdcf537b88a22f48f3acb7b31f340670506")
-	// require.NoError(t, err)
+	expectedTxID, err := hex.DecodeString("7647cd98fd14c6e3cdfe35bc64bbc476abcdb5ab12e8d31e3151d132ed1e0eeb4595fda4779f69dbe00ff14aadad3fdcf537b88a22f48f3acb7b31f340670506")
+	require.NoError(t, err)
 
 	expectedEmitterAddress, err := vaa.StringToAddress("041c657e845d65d009d59ceeb1dda172bd6bc9e7ee5a19e56573197cf7fdffde")
 	require.NoError(t, err)
 
-	// assert.Equal(t, expectedTxHash, msg.TxHash)
+	assert.Equal(t, expectedTxID, msg.TxID)
 	assert.Equal(t, time.Unix(int64(1736530812), 0), msg.Timestamp)
 	assert.Equal(t, uint32(42), msg.Nonce)
 	assert.Equal(t, uint64(0), msg.Sequence)
@@ -507,14 +506,13 @@ func TestShimFromIntegrator(t *testing.T) {
 	msg := <-msgC
 	require.NotNil(t, msg)
 
-	// TODO: Can't check this until we switch MessagePublication.TxHash to be a byte array rather than a hash.
-	// expectedTxHash, err := vaa.StringToHash("0cfdad68fdee85b49aea65e48c0d8def74f0968e7e1cf2c33305cfc33fec02a4742895c1d32f7c4093f75133104e70bd126fbbf8b71e5d8cb723a390cd976305")
-	// require.NoError(t, err)
+	expectedTxID, err := hex.DecodeString("0cfdad68fdee85b49aea65e48c0d8def74f0968e7e1cf2c33305cfc33fec02a4742895c1d32f7c4093f75133104e70bd126fbbf8b71e5d8cb723a390cd976305")
+	require.NoError(t, err)
 
 	expectedEmitterAddress, err := vaa.StringToAddress("0726d66bf942e942332ddf34a2edb7b83c4cdfd25b15d4247e2e15057cdfc3cf")
 	require.NoError(t, err)
 
-	// assert.Equal(t, expectedTxHash, msg.TxHash)
+	assert.Equal(t, expectedTxID, msg.TxID)
 	assert.Equal(t, time.Unix(int64(1736542615), 0), msg.Timestamp)
 	assert.Equal(t, uint32(0), msg.Nonce)
 	assert.Equal(t, uint64(1), msg.Sequence)
@@ -739,9 +737,8 @@ func TestShimDirectWithMultipleShimTransactions(t *testing.T) {
 	require.True(t, shimFound)
 	require.Equal(t, uint16(6), shimProgramIndex)
 
-	// TODO: Can't check this until we switch MessagePublication.TxHash to be a byte array rather than a hash.
-	// expectedTxHash, err := vaa.StringToHash("7647cd98fd14c6e3cdfe35bc64bbc476abcdb5ab12e8d31e3151d132ed1e0eeb4595fda4779f69dbe00ff14aadad3fdcf537b88a22f48f3acb7b31f340670506")
-	// require.NoError(t, err)
+	expectedTxID, err := hex.DecodeString("7647cd98fd14c6e3cdfe35bc64bbc476abcdb5ab12e8d31e3151d132ed1e0eeb4595fda4779f69dbe00ff14aadad3fdcf537b88a22f48f3acb7b31f340670506")
+	require.NoError(t, err)
 
 	expectedEmitterAddress, err := vaa.StringToAddress("041c657e845d65d009d59ceeb1dda172bd6bc9e7ee5a19e56573197cf7fdffde")
 	require.NoError(t, err)
@@ -757,7 +754,7 @@ func TestShimDirectWithMultipleShimTransactions(t *testing.T) {
 	msg := <-msgC
 	require.NotNil(t, msg)
 
-	// assert.Equal(t, expectedTxHash, msg.TxHash)
+	assert.Equal(t, expectedTxID, msg.TxID)
 	assert.Equal(t, time.Unix(int64(1736530812), 0), msg.Timestamp)
 	assert.Equal(t, uint32(42), msg.Nonce)
 	assert.Equal(t, uint64(0), msg.Sequence)
@@ -778,7 +775,7 @@ func TestShimDirectWithMultipleShimTransactions(t *testing.T) {
 	msg = <-msgC
 	require.NotNil(t, msg)
 
-	// assert.Equal(t, expectedTxHash, msg.TxHash)
+	assert.Equal(t, expectedTxID, msg.TxID)
 	assert.Equal(t, time.Unix(int64(1736530813), 0), msg.Timestamp)
 	assert.Equal(t, uint32(43), msg.Nonce)
 	assert.Equal(t, uint64(1), msg.Sequence)
@@ -957,9 +954,8 @@ func TestShimFromIntegratorWithMultipleShimTransactions(t *testing.T) {
 	require.True(t, shimFound)
 	require.Equal(t, uint16(7), shimProgramIndex)
 
-	// TODO: Can't check this until we switch MessagePublication.TxHash to be a byte array rather than a hash.
-	// expectedTxHash, err := vaa.StringToHash("0cfdad68fdee85b49aea65e48c0d8def74f0968e7e1cf2c33305cfc33fec02a4742895c1d32f7c4093f75133104e70bd126fbbf8b71e5d8cb723a390cd976305")
-	// require.NoError(t, err)
+	expectedTxID, err := hex.DecodeString("0cfdad68fdee85b49aea65e48c0d8def74f0968e7e1cf2c33305cfc33fec02a4742895c1d32f7c4093f75133104e70bd126fbbf8b71e5d8cb723a390cd976305")
+	require.NoError(t, err)
 
 	expectedEmitterAddress, err := vaa.StringToAddress("0726d66bf942e942332ddf34a2edb7b83c4cdfd25b15d4247e2e15057cdfc3cf")
 	require.NoError(t, err)
@@ -975,7 +971,7 @@ func TestShimFromIntegratorWithMultipleShimTransactions(t *testing.T) {
 	msg := <-msgC
 	require.NotNil(t, msg)
 
-	// assert.Equal(t, expectedTxHash, msg.TxHash)
+	assert.Equal(t, expectedTxID, msg.TxID)
 	assert.Equal(t, time.Unix(int64(1736542615), 0), msg.Timestamp)
 	assert.Equal(t, uint32(0), msg.Nonce)
 	assert.Equal(t, uint64(1), msg.Sequence)
@@ -996,7 +992,7 @@ func TestShimFromIntegratorWithMultipleShimTransactions(t *testing.T) {
 	msg = <-msgC
 	require.NotNil(t, msg)
 
-	// assert.Equal(t, expectedTxHash, msg.TxHash)
+	assert.Equal(t, expectedTxID, msg.TxID)
 	assert.Equal(t, time.Unix(int64(1736542616), 0), msg.Timestamp)
 	assert.Equal(t, uint32(42), msg.Nonce)
 	assert.Equal(t, uint64(2), msg.Sequence)
@@ -1007,3 +1003,1284 @@ func TestShimFromIntegratorWithMultipleShimTransactions(t *testing.T) {
 	assert.False(t, msg.IsReobservation)
 	assert.False(t, msg.Unreliable)
 }
+
+func TestShimDirectWithExtraWhEventBeforeShimEventShouldFail(t *testing.T) {
+	eventJson := `
+	{
+		"blockTime": 1736530812,
+		"meta": {
+			"computeUnitsConsumed": 84252,
+			"err": null,
+			"fee": 5000,
+			"innerInstructions": [
+				{
+					"index": 1,
+					"instructions": [
+						{
+							"accounts": [1, 3, 0, 4, 0, 2, 8, 5, 9],
+							"data": "TbyPDfUoyRxsr",
+							"programIdIndex": 10,
+							"stackHeight": 2
+						},
+						{
+							"accounts": [0, 4],
+							"data": "3Bxs4NLhqXb3ofom",
+							"programIdIndex": 5,
+							"stackHeight": 3
+						},
+						{
+							"accounts": [4],
+							"data": "9krTD1mFP1husSVM",
+							"programIdIndex": 5,
+							"stackHeight": 3
+						},
+						{
+							"accounts": [4],
+							"data": "SYXsBvR59WTsF4KEVN8LCQ1X9MekXCGPPNo3Af36taxCQBED",
+							"programIdIndex": 5,
+							"stackHeight": 3
+						},
+						{
+							"accounts": [0, 3],
+							"data": "3Bxs4bm7oSCPMeKR",
+							"programIdIndex": 5,
+							"stackHeight": 3
+						},
+						{
+							"accounts": [3],
+							"data": "9krTDGKFuDw9nLmM",
+							"programIdIndex": 5,
+							"stackHeight": 3
+						},
+						{
+							"accounts": [3],
+							"data": "SYXsBvR59WTsF4KEVN8LCQ1X9MekXCGPPNo3Af36taxCQBED",
+							"programIdIndex": 5,
+							"stackHeight": 3
+						},
+						{
+							"accounts": [1, 3, 0, 4, 0, 2, 8, 5, 9],
+							"data": "TbyPDfUoyRxsr",
+							"programIdIndex": 10,
+							"stackHeight": 2
+						},
+						{
+							"accounts": [7],
+							"data": "hTEY7jEqBPdDRkTWweeDPgyCUykRXEQVCUwrYmn4HZo84DdQrTJT2nBMiJFB3jXUVxHVd9mGq7BX9htuAN",
+							"programIdIndex": 6,
+							"stackHeight": 2
+						}
+					]
+				}
+			],
+			"loadedAddresses": {
+				"readonly": [],
+				"writable": []
+			},
+			"logMessages": [
+				"Program 11111111111111111111111111111111 invoke [1]",
+				"Program 11111111111111111111111111111111 success",
+				"Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX invoke [1]",
+				"Program log: Instruction: PostMessage",
+				"Program worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth invoke [2]",
+				"Program 11111111111111111111111111111111 invoke [3]",
+				"Program 11111111111111111111111111111111 success",
+				"Program 11111111111111111111111111111111 invoke [3]",
+				"Program 11111111111111111111111111111111 success",
+				"Program 11111111111111111111111111111111 invoke [3]",
+				"Program 11111111111111111111111111111111 success",
+				"Program log: Sequence: 0",
+				"Program 11111111111111111111111111111111 invoke [3]",
+				"Program 11111111111111111111111111111111 success",
+				"Program 11111111111111111111111111111111 invoke [3]",
+				"Program 11111111111111111111111111111111 success",
+				"Program 11111111111111111111111111111111 invoke [3]",
+				"Program 11111111111111111111111111111111 success",
+				"Program worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth consumed 60384 of 380989 compute units",
+				"Program worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth success",
+				"Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX invoke [2]",
+				"Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX consumed 2000 of 318068 compute units",
+				"Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX success",
+				"Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX consumed 84102 of 399850 compute units",
+				"Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX success"
+			],
+			"postBalances": [
+				499999999997496260, 1057920, 2350640170, 1552080, 946560, 1, 1141440, 0,
+				1169280, 1009200, 1141440
+			],
+			"postTokenBalances": [],
+			"preBalances": [
+				500000000000000000, 1057920, 2350640070, 0, 0, 1, 1141440, 0, 1169280,
+				1009200, 1141440
+			],
+			"preTokenBalances": [],
+			"rewards": [],
+			"status": {
+				"Ok": null
+			}
+		},
+		"slot": 3,
+		"transaction": {
+			"message": {
+				"header": {
+					"numReadonlySignedAccounts": 0,
+					"numReadonlyUnsignedAccounts": 6,
+					"numRequiredSignatures": 1
+				},
+				"accountKeys": [
+					"H3kCPjpQDT4hgwWHr9E9pC99rZT2yHAwiwSwku6Bne9",
+					"2yVjuQwpsvdsrywzsJJVs9Ueh4zayyo5DYJbBNc3DDpn",
+					"9bFNrXNb2WTx8fMHXCheaZqkLZ3YCCaiqTftHxeintHy",
+					"9vohBn118ZEctRmuTRvoUZg1B1HGfSH8C5QX6twtUFrJ",
+					"HeccUHmoyMi5S6nuTcyUBh4w4me3FP541a52ErYJRT8a",
+					"11111111111111111111111111111111",
+					"EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX",
+					"HQS31aApX3DDkuXgSpV9XyDUNtFgQ31pUn5BNWHG2PSp",
+					"SysvarC1ock11111111111111111111111111111111",
+					"SysvarRent111111111111111111111111111111111",
+					"worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth"
+				],
+				"recentBlockhash": "CMqPGm4icRdNuHsWJUK4Kgu4Cbe2nDQkYNqugQkKPa4Y",
+				"instructions": [
+					{
+						"accounts": [0, 2],
+						"data": "3Bxs4HanWsHUZCbH",
+						"programIdIndex": 5,
+						"stackHeight": null
+					},
+					{
+						"accounts": [1, 3, 0, 4, 0, 2, 8, 5, 9, 10, 7, 6],
+						"data": "3Cn8VBJReY7Bku3RduhBfYpk7tiw1R6pKcTWv9R",
+						"programIdIndex": 6,
+						"stackHeight": null
+					}
+				],
+				"indexToProgramIds": {}
+			},
+			"signatures": [
+				"3NACxoZLehbdKGjTWZKTTXJPuovyqAih1AD1BrkYj8nzDAtjiQUEaNmhkoU1jcFfoPTAjrvnaLFgTafNWr3fBrdB"
+			]
+		},
+		"version": "legacy"
+	}
+	`
+
+	///////// A bunch of checks to verify we parsed the JSON correctly.
+	var txRpc rpc.TransactionWithMeta
+	err := json.Unmarshal([]byte(eventJson), &txRpc)
+	require.NoError(t, err)
+
+	tx, err := txRpc.GetParsedTransaction()
+	require.NoError(t, err)
+
+	require.Equal(t, 2, len(tx.Message.Instructions))
+	require.Equal(t, 1, len(txRpc.Meta.InnerInstructions))
+
+	///////// Now we start the real test.
+
+	logger := zap.NewNop()
+	msgC := make(chan *common.MessagePublication, 10)
+	s := shimNewWatcherForTest(t, msgC)
+	require.True(t, s.shimEnabled)
+
+	var whProgramIndex uint16
+	var shimProgramIndex uint16
+	var shimFound bool
+	for n, key := range tx.Message.AccountKeys {
+		if key.Equals(s.contract) {
+			whProgramIndex = uint16(n)
+		}
+		if key.Equals(s.shimContractAddr) {
+			shimProgramIndex = uint16(n)
+			shimFound = true
+		}
+	}
+
+	require.Equal(t, uint16(10), whProgramIndex)
+	require.True(t, shimFound)
+	require.Equal(t, uint16(6), shimProgramIndex)
+
+	alreadyProcessed := ShimAlreadyProcessed{}
+	found, err := s.shimProcessTopLevelInstruction(logger, whProgramIndex, shimProgramIndex, tx, txRpc.Meta.InnerInstructions, 1, alreadyProcessed, false)
+	require.ErrorContains(t, err, "detected multiple inner core instructions when there should not be")
+	require.False(t, found)
+	require.Equal(t, 0, len(s.msgC))
+	require.Equal(t, 1, len(alreadyProcessed)) // The first core event will have been added.
+}
+
+func TestShimDirectWithExtraShimEventsShouldFail(t *testing.T) {
+	eventJson := `
+	{
+		"blockTime": 1736530812,
+		"meta": {
+			"computeUnitsConsumed": 84252,
+			"err": null,
+			"fee": 5000,
+			"innerInstructions": [
+				{
+					"index": 1,
+					"instructions": [
+						{
+							"accounts": [1, 3, 0, 4, 0, 2, 8, 5, 9],
+							"data": "TbyPDfUoyRxsr",
+							"programIdIndex": 10,
+							"stackHeight": 2
+						},
+						{
+							"accounts": [0, 4],
+							"data": "3Bxs4NLhqXb3ofom",
+							"programIdIndex": 5,
+							"stackHeight": 3
+						},
+						{
+							"accounts": [4],
+							"data": "9krTD1mFP1husSVM",
+							"programIdIndex": 5,
+							"stackHeight": 3
+						},
+						{
+							"accounts": [4],
+							"data": "SYXsBvR59WTsF4KEVN8LCQ1X9MekXCGPPNo3Af36taxCQBED",
+							"programIdIndex": 5,
+							"stackHeight": 3
+						},
+						{
+							"accounts": [0, 3],
+							"data": "3Bxs4bm7oSCPMeKR",
+							"programIdIndex": 5,
+							"stackHeight": 3
+						},
+						{
+							"accounts": [3],
+							"data": "9krTDGKFuDw9nLmM",
+							"programIdIndex": 5,
+							"stackHeight": 3
+						},
+						{
+							"accounts": [3],
+							"data": "SYXsBvR59WTsF4KEVN8LCQ1X9MekXCGPPNo3Af36taxCQBED",
+							"programIdIndex": 5,
+							"stackHeight": 3
+						},
+						{
+							"accounts": [7],
+							"data": "hTEY7jEqBPdDRkTWweeDPgyCUykRXEQVCUwrYmn4HZo84DdQrTJT2nBMiJFB3jXUVxHVd9mGq7BX9htuAN",
+							"programIdIndex": 6,
+							"stackHeight": 2
+						},
+						{
+							"accounts": [7],
+							"data": "hTEY7jEqBPdDRkTWweeDPgyCUykRXEQVCUwrYmn4HZo84DdQrTJT2nBMiJFB3jXUVxHVd9mGq7BX9htuAN",
+							"programIdIndex": 6,
+							"stackHeight": 2
+						}
+					]
+				}
+			],
+			"loadedAddresses": {
+				"readonly": [],
+				"writable": []
+			},
+			"logMessages": [
+				"Program 11111111111111111111111111111111 invoke [1]",
+				"Program 11111111111111111111111111111111 success",
+				"Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX invoke [1]",
+				"Program log: Instruction: PostMessage",
+				"Program worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth invoke [2]",
+				"Program 11111111111111111111111111111111 invoke [3]",
+				"Program 11111111111111111111111111111111 success",
+				"Program 11111111111111111111111111111111 invoke [3]",
+				"Program 11111111111111111111111111111111 success",
+				"Program 11111111111111111111111111111111 invoke [3]",
+				"Program 11111111111111111111111111111111 success",
+				"Program log: Sequence: 0",
+				"Program 11111111111111111111111111111111 invoke [3]",
+				"Program 11111111111111111111111111111111 success",
+				"Program 11111111111111111111111111111111 invoke [3]",
+				"Program 11111111111111111111111111111111 success",
+				"Program 11111111111111111111111111111111 invoke [3]",
+				"Program 11111111111111111111111111111111 success",
+				"Program worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth consumed 60384 of 380989 compute units",
+				"Program worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth success",
+				"Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX invoke [2]",
+				"Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX consumed 2000 of 318068 compute units",
+				"Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX success",
+				"Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX consumed 84102 of 399850 compute units",
+				"Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX success"
+			],
+			"postBalances": [
+				499999999997496260, 1057920, 2350640170, 1552080, 946560, 1, 1141440, 0,
+				1169280, 1009200, 1141440
+			],
+			"postTokenBalances": [],
+			"preBalances": [
+				500000000000000000, 1057920, 2350640070, 0, 0, 1, 1141440, 0, 1169280,
+				1009200, 1141440
+			],
+			"preTokenBalances": [],
+			"rewards": [],
+			"status": {
+				"Ok": null
+			}
+		},
+		"slot": 3,
+		"transaction": {
+			"message": {
+				"header": {
+					"numReadonlySignedAccounts": 0,
+					"numReadonlyUnsignedAccounts": 6,
+					"numRequiredSignatures": 1
+				},
+				"accountKeys": [
+					"H3kCPjpQDT4hgwWHr9E9pC99rZT2yHAwiwSwku6Bne9",
+					"2yVjuQwpsvdsrywzsJJVs9Ueh4zayyo5DYJbBNc3DDpn",
+					"9bFNrXNb2WTx8fMHXCheaZqkLZ3YCCaiqTftHxeintHy",
+					"9vohBn118ZEctRmuTRvoUZg1B1HGfSH8C5QX6twtUFrJ",
+					"HeccUHmoyMi5S6nuTcyUBh4w4me3FP541a52ErYJRT8a",
+					"11111111111111111111111111111111",
+					"EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX",
+					"HQS31aApX3DDkuXgSpV9XyDUNtFgQ31pUn5BNWHG2PSp",
+					"SysvarC1ock11111111111111111111111111111111",
+					"SysvarRent111111111111111111111111111111111",
+					"worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth"
+				],
+				"recentBlockhash": "CMqPGm4icRdNuHsWJUK4Kgu4Cbe2nDQkYNqugQkKPa4Y",
+				"instructions": [
+					{
+						"accounts": [0, 2],
+						"data": "3Bxs4HanWsHUZCbH",
+						"programIdIndex": 5,
+						"stackHeight": null
+					},
+					{
+						"accounts": [1, 3, 0, 4, 0, 2, 8, 5, 9, 10, 7, 6],
+						"data": "3Cn8VBJReY7Bku3RduhBfYpk7tiw1R6pKcTWv9R",
+						"programIdIndex": 6,
+						"stackHeight": null
+					}
+				],
+				"indexToProgramIds": {}
+			},
+			"signatures": [
+				"3NACxoZLehbdKGjTWZKTTXJPuovyqAih1AD1BrkYj8nzDAtjiQUEaNmhkoU1jcFfoPTAjrvnaLFgTafNWr3fBrdB"
+			]
+		},
+		"version": "legacy"
+	}
+	`
+
+	///////// A bunch of checks to verify we parsed the JSON correctly.
+	var txRpc rpc.TransactionWithMeta
+	err := json.Unmarshal([]byte(eventJson), &txRpc)
+	require.NoError(t, err)
+
+	tx, err := txRpc.GetParsedTransaction()
+	require.NoError(t, err)
+
+	require.Equal(t, 2, len(tx.Message.Instructions))
+	require.Equal(t, 1, len(txRpc.Meta.InnerInstructions))
+
+	///////// Now we start the real test.
+
+	logger := zap.NewNop()
+	msgC := make(chan *common.MessagePublication, 10)
+	s := shimNewWatcherForTest(t, msgC)
+	require.True(t, s.shimEnabled)
+
+	var whProgramIndex uint16
+	var shimProgramIndex uint16
+	var shimFound bool
+	for n, key := range tx.Message.AccountKeys {
+		if key.Equals(s.contract) {
+			whProgramIndex = uint16(n)
+		}
+		if key.Equals(s.shimContractAddr) {
+			shimProgramIndex = uint16(n)
+			shimFound = true
+		}
+	}
+
+	require.Equal(t, uint16(10), whProgramIndex)
+	require.True(t, shimFound)
+	require.Equal(t, uint16(6), shimProgramIndex)
+
+	alreadyProcessed := ShimAlreadyProcessed{}
+	found, err := s.shimProcessTopLevelInstruction(logger, whProgramIndex, shimProgramIndex, tx, txRpc.Meta.InnerInstructions, 1, alreadyProcessed, false)
+	require.ErrorContains(t, err, "detected multiple shim message event instructions when there should not be")
+	require.False(t, found)
+	require.Equal(t, 0, len(s.msgC))
+	require.Equal(t, 2, len(alreadyProcessed)) // The first core and shim events will have been added.
+}
+
+func TestShimDirectWithExtraCoreEventShouldFail(t *testing.T) {
+	eventJson := `
+	{
+		"blockTime": 1736530812,
+		"meta": {
+			"computeUnitsConsumed": 84252,
+			"err": null,
+			"fee": 5000,
+			"innerInstructions": [
+				{
+					"index": 1,
+					"instructions": [
+						{
+							"accounts": [1, 3, 0, 4, 0, 2, 8, 5, 9],
+							"data": "TbyPDfUoyRxsr",
+							"programIdIndex": 10,
+							"stackHeight": 2
+						},
+						{
+							"accounts": [0, 4],
+							"data": "3Bxs4NLhqXb3ofom",
+							"programIdIndex": 5,
+							"stackHeight": 3
+						},
+						{
+							"accounts": [4],
+							"data": "9krTD1mFP1husSVM",
+							"programIdIndex": 5,
+							"stackHeight": 3
+						},
+						{
+							"accounts": [4],
+							"data": "SYXsBvR59WTsF4KEVN8LCQ1X9MekXCGPPNo3Af36taxCQBED",
+							"programIdIndex": 5,
+							"stackHeight": 3
+						},
+						{
+							"accounts": [0, 3],
+							"data": "3Bxs4bm7oSCPMeKR",
+							"programIdIndex": 5,
+							"stackHeight": 3
+						},
+						{
+							"accounts": [3],
+							"data": "9krTDGKFuDw9nLmM",
+							"programIdIndex": 5,
+							"stackHeight": 3
+						},
+						{
+							"accounts": [3],
+							"data": "SYXsBvR59WTsF4KEVN8LCQ1X9MekXCGPPNo3Af36taxCQBED",
+							"programIdIndex": 5,
+							"stackHeight": 3
+						},
+						{
+							"accounts": [7],
+							"data": "hTEY7jEqBPdDRkTWweeDPgyCUykRXEQVCUwrYmn4HZo84DdQrTJT2nBMiJFB3jXUVxHVd9mGq7BX9htuAN",
+							"programIdIndex": 6,
+							"stackHeight": 2
+						},
+						{
+							"accounts": [1, 3, 0, 4, 0, 2, 8, 5, 9],
+							"data": "TbyPDfUoyRxsr",
+							"programIdIndex": 10,
+							"stackHeight": 2
+						}						
+					]
+				}
+			],
+			"loadedAddresses": {
+				"readonly": [],
+				"writable": []
+			},
+			"logMessages": [
+				"Program 11111111111111111111111111111111 invoke [1]",
+				"Program 11111111111111111111111111111111 success",
+				"Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX invoke [1]",
+				"Program log: Instruction: PostMessage",
+				"Program worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth invoke [2]",
+				"Program 11111111111111111111111111111111 invoke [3]",
+				"Program 11111111111111111111111111111111 success",
+				"Program 11111111111111111111111111111111 invoke [3]",
+				"Program 11111111111111111111111111111111 success",
+				"Program 11111111111111111111111111111111 invoke [3]",
+				"Program 11111111111111111111111111111111 success",
+				"Program log: Sequence: 0",
+				"Program 11111111111111111111111111111111 invoke [3]",
+				"Program 11111111111111111111111111111111 success",
+				"Program 11111111111111111111111111111111 invoke [3]",
+				"Program 11111111111111111111111111111111 success",
+				"Program 11111111111111111111111111111111 invoke [3]",
+				"Program 11111111111111111111111111111111 success",
+				"Program worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth consumed 60384 of 380989 compute units",
+				"Program worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth success",
+				"Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX invoke [2]",
+				"Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX consumed 2000 of 318068 compute units",
+				"Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX success",
+				"Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX consumed 84102 of 399850 compute units",
+				"Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX success"
+			],
+			"postBalances": [
+				499999999997496260, 1057920, 2350640170, 1552080, 946560, 1, 1141440, 0,
+				1169280, 1009200, 1141440
+			],
+			"postTokenBalances": [],
+			"preBalances": [
+				500000000000000000, 1057920, 2350640070, 0, 0, 1, 1141440, 0, 1169280,
+				1009200, 1141440
+			],
+			"preTokenBalances": [],
+			"rewards": [],
+			"status": {
+				"Ok": null
+			}
+		},
+		"slot": 3,
+		"transaction": {
+			"message": {
+				"header": {
+					"numReadonlySignedAccounts": 0,
+					"numReadonlyUnsignedAccounts": 6,
+					"numRequiredSignatures": 1
+				},
+				"accountKeys": [
+					"H3kCPjpQDT4hgwWHr9E9pC99rZT2yHAwiwSwku6Bne9",
+					"2yVjuQwpsvdsrywzsJJVs9Ueh4zayyo5DYJbBNc3DDpn",
+					"9bFNrXNb2WTx8fMHXCheaZqkLZ3YCCaiqTftHxeintHy",
+					"9vohBn118ZEctRmuTRvoUZg1B1HGfSH8C5QX6twtUFrJ",
+					"HeccUHmoyMi5S6nuTcyUBh4w4me3FP541a52ErYJRT8a",
+					"11111111111111111111111111111111",
+					"EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX",
+					"HQS31aApX3DDkuXgSpV9XyDUNtFgQ31pUn5BNWHG2PSp",
+					"SysvarC1ock11111111111111111111111111111111",
+					"SysvarRent111111111111111111111111111111111",
+					"worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth"
+				],
+				"recentBlockhash": "CMqPGm4icRdNuHsWJUK4Kgu4Cbe2nDQkYNqugQkKPa4Y",
+				"instructions": [
+					{
+						"accounts": [0, 2],
+						"data": "3Bxs4HanWsHUZCbH",
+						"programIdIndex": 5,
+						"stackHeight": null
+					},
+					{
+						"accounts": [1, 3, 0, 4, 0, 2, 8, 5, 9, 10, 7, 6],
+						"data": "3Cn8VBJReY7Bku3RduhBfYpk7tiw1R6pKcTWv9R",
+						"programIdIndex": 6,
+						"stackHeight": null
+					}
+				],
+				"indexToProgramIds": {}
+			},
+			"signatures": [
+				"3NACxoZLehbdKGjTWZKTTXJPuovyqAih1AD1BrkYj8nzDAtjiQUEaNmhkoU1jcFfoPTAjrvnaLFgTafNWr3fBrdB"
+			]
+		},
+		"version": "legacy"
+	}
+	`
+
+	///////// A bunch of checks to verify we parsed the JSON correctly.
+	var txRpc rpc.TransactionWithMeta
+	err := json.Unmarshal([]byte(eventJson), &txRpc)
+	require.NoError(t, err)
+
+	tx, err := txRpc.GetParsedTransaction()
+	require.NoError(t, err)
+
+	require.Equal(t, 2, len(tx.Message.Instructions))
+	require.Equal(t, 1, len(txRpc.Meta.InnerInstructions))
+
+	///////// Now we start the real test.
+
+	logger := zap.NewNop()
+	msgC := make(chan *common.MessagePublication, 10)
+	s := shimNewWatcherForTest(t, msgC)
+	require.True(t, s.shimEnabled)
+
+	var whProgramIndex uint16
+	var shimProgramIndex uint16
+	var shimFound bool
+	for n, key := range tx.Message.AccountKeys {
+		if key.Equals(s.contract) {
+			whProgramIndex = uint16(n)
+		}
+		if key.Equals(s.shimContractAddr) {
+			shimProgramIndex = uint16(n)
+			shimFound = true
+		}
+	}
+
+	require.Equal(t, uint16(10), whProgramIndex)
+	require.True(t, shimFound)
+	require.Equal(t, uint16(6), shimProgramIndex)
+
+	alreadyProcessed := ShimAlreadyProcessed{}
+	found, err := s.shimProcessTopLevelInstruction(logger, whProgramIndex, shimProgramIndex, tx, txRpc.Meta.InnerInstructions, 1, alreadyProcessed, false)
+	require.ErrorContains(t, err, "detected multiple inner core instructions when there should not be")
+	require.False(t, found)
+	require.Equal(t, 0, len(s.msgC))
+	require.Equal(t, 2, len(alreadyProcessed)) // The first core and shim events will have been added.
+}
+
+func TestShimTopLevelEmptyInstructionsShouldFail(t *testing.T) {
+	eventJson := `
+	{
+		"blockTime": 1736530812,
+		"meta": {
+			"computeUnitsConsumed": 84252,
+			"err": null,
+			"fee": 5000,
+			"innerInstructions": [
+				{
+					"index": 1,
+					"instructions": [
+						{
+							"accounts": [1, 3, 0, 4, 0, 2, 8, 5, 9],
+							"data": "TbyPDfUoyRxsr",
+							"programIdIndex": 10,
+							"stackHeight": 2
+						},
+						{
+							"accounts": [0, 4],
+							"data": "3Bxs4NLhqXb3ofom",
+							"programIdIndex": 5,
+							"stackHeight": 3
+						},
+						{
+							"accounts": [4],
+							"data": "9krTD1mFP1husSVM",
+							"programIdIndex": 5,
+							"stackHeight": 3
+						},
+						{
+							"accounts": [4],
+							"data": "SYXsBvR59WTsF4KEVN8LCQ1X9MekXCGPPNo3Af36taxCQBED",
+							"programIdIndex": 5,
+							"stackHeight": 3
+						},
+						{
+							"accounts": [0, 3],
+							"data": "3Bxs4bm7oSCPMeKR",
+							"programIdIndex": 5,
+							"stackHeight": 3
+						},
+						{
+							"accounts": [3],
+							"data": "9krTDGKFuDw9nLmM",
+							"programIdIndex": 5,
+							"stackHeight": 3
+						},
+						{
+							"accounts": [3],
+							"data": "SYXsBvR59WTsF4KEVN8LCQ1X9MekXCGPPNo3Af36taxCQBED",
+							"programIdIndex": 5,
+							"stackHeight": 3
+						},
+						{
+							"accounts": [7],
+							"data": "hTEY7jEqBPdDRkTWweeDPgyCUykRXEQVCUwrYmn4HZo84DdQrTJT2nBMiJFB3jXUVxHVd9mGq7BX9htuAN",
+							"programIdIndex": 6,
+							"stackHeight": 2
+						},
+						{
+							"accounts": [1, 3, 0, 4, 0, 2, 8, 5, 9],
+							"data": "TbyPDfUoyRxsr",
+							"programIdIndex": 10,
+							"stackHeight": 2
+						}						
+					]
+				}
+			],
+			"loadedAddresses": {
+				"readonly": [],
+				"writable": []
+			},
+			"logMessages": [
+				"Program 11111111111111111111111111111111 invoke [1]",
+				"Program 11111111111111111111111111111111 success",
+				"Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX invoke [1]",
+				"Program log: Instruction: PostMessage",
+				"Program worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth invoke [2]",
+				"Program 11111111111111111111111111111111 invoke [3]",
+				"Program 11111111111111111111111111111111 success",
+				"Program 11111111111111111111111111111111 invoke [3]",
+				"Program 11111111111111111111111111111111 success",
+				"Program 11111111111111111111111111111111 invoke [3]",
+				"Program 11111111111111111111111111111111 success",
+				"Program log: Sequence: 0",
+				"Program 11111111111111111111111111111111 invoke [3]",
+				"Program 11111111111111111111111111111111 success",
+				"Program 11111111111111111111111111111111 invoke [3]",
+				"Program 11111111111111111111111111111111 success",
+				"Program 11111111111111111111111111111111 invoke [3]",
+				"Program 11111111111111111111111111111111 success",
+				"Program worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth consumed 60384 of 380989 compute units",
+				"Program worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth success",
+				"Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX invoke [2]",
+				"Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX consumed 2000 of 318068 compute units",
+				"Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX success",
+				"Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX consumed 84102 of 399850 compute units",
+				"Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX success"
+			],
+			"postBalances": [
+				499999999997496260, 1057920, 2350640170, 1552080, 946560, 1, 1141440, 0,
+				1169280, 1009200, 1141440
+			],
+			"postTokenBalances": [],
+			"preBalances": [
+				500000000000000000, 1057920, 2350640070, 0, 0, 1, 1141440, 0, 1169280,
+				1009200, 1141440
+			],
+			"preTokenBalances": [],
+			"rewards": [],
+			"status": {
+				"Ok": null
+			}
+		},
+		"slot": 3,
+		"transaction": {
+			"message": {
+				"header": {
+					"numReadonlySignedAccounts": 0,
+					"numReadonlyUnsignedAccounts": 6,
+					"numRequiredSignatures": 1
+				},
+				"accountKeys": [
+					"H3kCPjpQDT4hgwWHr9E9pC99rZT2yHAwiwSwku6Bne9",
+					"2yVjuQwpsvdsrywzsJJVs9Ueh4zayyo5DYJbBNc3DDpn",
+					"9bFNrXNb2WTx8fMHXCheaZqkLZ3YCCaiqTftHxeintHy",
+					"9vohBn118ZEctRmuTRvoUZg1B1HGfSH8C5QX6twtUFrJ",
+					"HeccUHmoyMi5S6nuTcyUBh4w4me3FP541a52ErYJRT8a",
+					"11111111111111111111111111111111",
+					"EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX",
+					"HQS31aApX3DDkuXgSpV9XyDUNtFgQ31pUn5BNWHG2PSp",
+					"SysvarC1ock11111111111111111111111111111111",
+					"SysvarRent111111111111111111111111111111111",
+					"worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth"
+				],
+				"recentBlockhash": "CMqPGm4icRdNuHsWJUK4Kgu4Cbe2nDQkYNqugQkKPa4Y",
+				"instructions": [
+				],
+				"indexToProgramIds": {}
+			},
+			"signatures": [
+				"3NACxoZLehbdKGjTWZKTTXJPuovyqAih1AD1BrkYj8nzDAtjiQUEaNmhkoU1jcFfoPTAjrvnaLFgTafNWr3fBrdB"
+			]
+		},
+		"version": "legacy"
+	}
+	`
+
+	///////// A bunch of checks to verify we parsed the JSON correctly.
+	var txRpc rpc.TransactionWithMeta
+	err := json.Unmarshal([]byte(eventJson), &txRpc)
+	require.NoError(t, err)
+
+	tx, err := txRpc.GetParsedTransaction()
+	require.NoError(t, err)
+
+	require.Equal(t, 0, len(tx.Message.Instructions))
+	require.Equal(t, 1, len(txRpc.Meta.InnerInstructions))
+
+	///////// Now we start the real test.
+
+	logger := zap.NewNop()
+	msgC := make(chan *common.MessagePublication, 10)
+	s := shimNewWatcherForTest(t, msgC)
+	require.True(t, s.shimEnabled)
+
+	var whProgramIndex uint16
+	var shimProgramIndex uint16
+	var shimFound bool
+	for n, key := range tx.Message.AccountKeys {
+		if key.Equals(s.contract) {
+			whProgramIndex = uint16(n)
+		}
+		if key.Equals(s.shimContractAddr) {
+			shimProgramIndex = uint16(n)
+			shimFound = true
+		}
+	}
+
+	require.Equal(t, uint16(10), whProgramIndex)
+	require.True(t, shimFound)
+	require.Equal(t, uint16(6), shimProgramIndex)
+
+	alreadyProcessed := ShimAlreadyProcessed{}
+	found, err := s.shimProcessTopLevelInstruction(logger, whProgramIndex, shimProgramIndex, tx, txRpc.Meta.InnerInstructions, 1, alreadyProcessed, false)
+	require.ErrorContains(t, err, "topLevelIndex 1 is greater than the total number of instructions in the tx message, 0")
+	require.False(t, found)
+	require.Equal(t, 0, len(s.msgC))
+	require.Equal(t, 0, len(alreadyProcessed))
+}
+
+func TestShimProcessInnerInstructions_OutOfBoundsStartIndexShouldFail(t *testing.T) {
+	eventJson := `
+	{
+		"blockTime": 1736542615,
+		"meta": {
+			"computeUnitsConsumed": 48958,
+			"err": null,
+			"fee": 5000,
+			"innerInstructions": [
+				{
+					"index": 1,
+					"instructions": [
+						{
+							"accounts": [1, 4, 11, 3, 0, 2, 9, 5, 10, 12, 8, 7],
+							"data": "BeHixXyfSZ8dzFJzxTYRV18L6KSgTuqcTjaqeXgDVbXHC7mCjAgSyhz",
+							"programIdIndex": 7,
+							"stackHeight": 2
+						},
+						{
+							"accounts": [1, 4, 11, 3, 0, 2, 9, 5, 10],
+							"data": "T4xyMHqZi66JU",
+							"programIdIndex": 12,
+							"stackHeight": 3
+						},
+						{
+							"accounts": [8],
+							"data": "hTEY7jEqBPdDRkTWweeDPgzBpsiybJCHnVTVt8aCDem8p58yeQcQLJWk7hgGHrX79qZyKmCM89vCgPY7SE",
+							"programIdIndex": 7,
+							"stackHeight": 3
+						}
+					]
+				}
+			],
+			"loadedAddresses": { "readonly": [], "writable": [] },
+			"logMessages": [
+				"Program 11111111111111111111111111111111 invoke [1]",
+				"Program 11111111111111111111111111111111 success",
+				"Program AEwubmehHNvkMXoH2C5MgDSemZgQ3HUSYpeaF3UrNZdQ invoke [1]",
+				"Program log: Instruction: PostMessage",
+				"Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX invoke [2]",
+				"Program log: Instruction: PostMessage",
+				"Program worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth invoke [3]",
+				"Program log: Sequence: 1",
+				"Program worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth consumed 18679 of 375180 compute units",
+				"Program worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth success",
+				"Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX invoke [3]",
+				"Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX consumed 2000 of 353964 compute units",
+				"Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX success",
+				"Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX consumed 33649 of 385286 compute units",
+				"Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX success",
+				"Program AEwubmehHNvkMXoH2C5MgDSemZgQ3HUSYpeaF3UrNZdQ consumed 48808 of 399850 compute units",
+				"Program AEwubmehHNvkMXoH2C5MgDSemZgQ3HUSYpeaF3UrNZdQ success"
+			],
+			"postBalances": [
+				499999999997491140, 1057920, 2350640270, 946560, 1552080, 1, 1141440,
+				1141440, 0, 1169280, 1009200, 0, 1141440
+			],
+			"postTokenBalances": [],
+			"preBalances": [
+				499999999997496260, 1057920, 2350640170, 946560, 1552080, 1, 1141440,
+				1141440, 0, 1169280, 1009200, 0, 1141440
+			],
+			"preTokenBalances": [],
+			"rewards": [],
+			"status": { "Ok": null }
+		},
+		"slot": 5,
+		"transaction": {
+			"message": {
+				"header": {
+					"numReadonlySignedAccounts": 0,
+					"numReadonlyUnsignedAccounts": 8,
+					"numRequiredSignatures": 1
+				},
+				"accountKeys": [
+					"H3kCPjpQDT4hgwWHr9E9pC99rZT2yHAwiwSwku6Bne9",
+					"2yVjuQwpsvdsrywzsJJVs9Ueh4zayyo5DYJbBNc3DDpn",
+					"9bFNrXNb2WTx8fMHXCheaZqkLZ3YCCaiqTftHxeintHy",
+					"G4zDzQLktwvU4rn6A4dSAy9eU76cJxppCaumZhjjhXjv",
+					"GXUAWs1h6Nh1KLByvfeEyig9yn92LmKMjXDNxHGddyXR",
+					"11111111111111111111111111111111",
+					"AEwubmehHNvkMXoH2C5MgDSemZgQ3HUSYpeaF3UrNZdQ",
+					"EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX",
+					"HQS31aApX3DDkuXgSpV9XyDUNtFgQ31pUn5BNWHG2PSp",
+					"SysvarC1ock11111111111111111111111111111111",
+					"SysvarRent111111111111111111111111111111111",
+					"UvCifi1D8qj5FSJQdWL3KENnmaZjm62XUMa7NReceer",
+					"worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth"
+				],
+				"recentBlockhash": "EqNQXbHebHwD1Vs4BSStmUVh2y6GjMxF3NBsDXsYuvRh",
+				"instructions": [
+					{
+						"accounts": [0, 2],
+						"data": "3Bxs4HanWsHUZCbH",
+						"programIdIndex": 5,
+						"stackHeight": null
+					},
+					{
+						"accounts": [0, 7, 1, 4, 11, 3, 2, 9, 5, 10, 12, 8],
+						"data": "cpyiD6CEaBD",
+						"programIdIndex": 6,
+						"stackHeight": null
+					}
+				],
+				"indexToProgramIds": {}
+			},
+			"signatures": [
+				"G4jVHcH6F4Np1NRvYC6ridv5jGfPSVGgiEVZrjprpMdBFhJH7eVxUuxsvkDF2rkx4JseUftz3HnWoSomGt3czSY"
+			]
+		},
+		"version": "legacy"
+	}
+	`
+
+	///////// A bunch of checks to verify we parsed the JSON correctly.
+	var txRpc rpc.TransactionWithMeta
+	err := json.Unmarshal([]byte(eventJson), &txRpc)
+	require.NoError(t, err)
+
+	tx, err := txRpc.GetParsedTransaction()
+	require.NoError(t, err)
+
+	require.Equal(t, 2, len(tx.Message.Instructions))
+	require.Equal(t, 1, len(txRpc.Meta.InnerInstructions))
+
+	///////// Now we start the real test.
+
+	logger := zap.NewNop()
+	msgC := make(chan *common.MessagePublication, 10)
+	s := shimNewWatcherForTest(t, msgC)
+	require.True(t, s.shimEnabled)
+
+	var whProgramIndex uint16
+	var shimProgramIndex uint16
+	var shimFound bool
+	for n, key := range tx.Message.AccountKeys {
+		if key.Equals(s.contract) {
+			whProgramIndex = uint16(n)
+		}
+		if key.Equals(s.shimContractAddr) {
+			shimProgramIndex = uint16(n)
+			shimFound = true
+		}
+	}
+
+	require.Equal(t, uint16(12), whProgramIndex)
+	require.True(t, shimFound)
+	require.Equal(t, uint16(7), shimProgramIndex)
+
+	alreadyProcessed := ShimAlreadyProcessed{}
+	found, err := s.shimProcessInnerInstruction(logger, whProgramIndex, shimProgramIndex, tx, txRpc.Meta.InnerInstructions[0].Instructions, 0, len(txRpc.Meta.InnerInstructions[0].Instructions), alreadyProcessed, false)
+	require.ErrorContains(t, err, "startIdx 3 is out of bounds of slice innerInstructions (length: 3)")
+	require.False(t, found)
+	require.Equal(t, 0, len(s.msgC))
+	require.Equal(t, 0, len(alreadyProcessed))
+}
+
+func TestShimWhPostMessageInUnexpectedFormatShouldNotBeCountedAsShimMessage(t *testing.T) {
+	// The WH instruction in the first slot is `012a0000000000000001` which is a reliable with no payload.
+	// The WH instruction for a shim event should be `082a0000000000000001` which is unreliable with no payload.
+	// So this instruction should not be counted as part of a shim event.
+	eventJson := `
+	{
+		"meta": {
+			"innerInstructions": [
+				{
+					"index": 1,
+					"instructions": [
+						{
+							"accounts": [1, 3, 0, 4, 0, 2, 8, 5, 9],
+							"data": "4o1AAQkKMUkzL",
+							"programIdIndex": 10,
+							"stackHeight": 2
+						},
+						{
+							"accounts": [7],
+							"data": "hTEY7jEqBPdDRkTWweeDPgyCUykRXEQVCUwrYmn4HZo84DdQrTJT2nBMiJFB3jXUVxHVd9mGq7BX9htuAN",
+							"programIdIndex": 6,
+							"stackHeight": 2
+						},
+						{
+							"accounts": [1, 3, 0, 4, 0, 2, 8, 5, 9],
+							"data": "TbyPDfUoyRxsr",
+							"programIdIndex": 10,
+							"stackHeight": 2
+						}
+					]
+				}
+			]
+		},
+		"transaction": {
+			"message": {
+				"accountKeys": [
+					"H3kCPjpQDT4hgwWHr9E9pC99rZT2yHAwiwSwku6Bne9",
+					"2yVjuQwpsvdsrywzsJJVs9Ueh4zayyo5DYJbBNc3DDpn",
+					"9bFNrXNb2WTx8fMHXCheaZqkLZ3YCCaiqTftHxeintHy",
+					"9vohBn118ZEctRmuTRvoUZg1B1HGfSH8C5QX6twtUFrJ",
+					"HeccUHmoyMi5S6nuTcyUBh4w4me3FP541a52ErYJRT8a",
+					"11111111111111111111111111111111",
+					"EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX",
+					"HQS31aApX3DDkuXgSpV9XyDUNtFgQ31pUn5BNWHG2PSp",
+					"SysvarC1ock11111111111111111111111111111111",
+					"SysvarRent111111111111111111111111111111111",
+					"worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth"
+				],
+				"instructions": [
+					{
+						"accounts": [0, 2],
+						"data": "3Bxs4HanWsHUZCbH",
+						"programIdIndex": 5,
+						"stackHeight": null
+					},
+					{
+						"accounts": [1, 3, 0, 4, 0, 2, 8, 5, 9, 10, 7, 6],
+						"data": "3Cn8VBJReY7Bku3RduhBfYpk7tiw1R6pKcTWv9R",
+						"programIdIndex": 6,
+						"stackHeight": null
+					}
+				]
+			},
+			"signatures": [
+				"3NACxoZLehbdKGjTWZKTTXJPuovyqAih1AD1BrkYj8nzDAtjiQUEaNmhkoU1jcFfoPTAjrvnaLFgTafNWr3fBrdB"
+			]
+		}
+	}
+	`
+
+	///////// A bunch of checks to verify we parsed the JSON correctly.
+	var txRpc rpc.TransactionWithMeta
+	err := json.Unmarshal([]byte(eventJson), &txRpc)
+	require.NoError(t, err)
+
+	tx, err := txRpc.GetParsedTransaction()
+	require.NoError(t, err)
+
+	require.Equal(t, 2, len(tx.Message.Instructions))
+	require.Equal(t, 1, len(txRpc.Meta.InnerInstructions))
+
+	///////// Set up the watcher and do the one-time transaction processing.
+
+	logger := zap.NewNop()
+	msgC := make(chan *common.MessagePublication, 10)
+	s := shimNewWatcherForTest(t, msgC)
+	require.True(t, s.shimEnabled)
+
+	var whProgramIndex uint16
+	var shimProgramIndex uint16
+	var shimFound bool
+	for n, key := range tx.Message.AccountKeys {
+		if key.Equals(s.contract) {
+			whProgramIndex = uint16(n)
+		}
+		if key.Equals(s.shimContractAddr) {
+			shimProgramIndex = uint16(n)
+			shimFound = true
+		}
+	}
+
+	require.Equal(t, uint16(10), whProgramIndex)
+	require.True(t, shimFound)
+	require.Equal(t, uint16(6), shimProgramIndex)
+
+	alreadyProcessed := ShimAlreadyProcessed{}
+	found, err := s.shimProcessTopLevelInstruction(
+		logger,
+		whProgramIndex,
+		shimProgramIndex,
+		tx,
+		txRpc.Meta.InnerInstructions,
+		1,
+		alreadyProcessed,
+		false,
+	)
+
+	require.ErrorContains(t, err, "detected an inner shim message event instruction before the core event for shim instruction")
+	require.False(t, found)
+	require.Equal(t, 0, len(s.msgC))
+	require.Equal(t, 0, len(alreadyProcessed))
+}
+
+func TestShimProcessRestWithNullEventShouldFail(t *testing.T) {
+	eventJson := `
+	{
+		"blockTime": 1736530812,
+		"meta": {
+			"computeUnitsConsumed": 84252,
+			"err": null,
+			"fee": 5000,
+			"innerInstructions": [
+				{
+					"index": 1,
+					"instructions": [
+						{
+							"accounts": [1, 3, 0, 4, 0, 2, 8, 5, 9],
+							"data": "TbyPDfUoyRxsr",
+							"programIdIndex": 10,
+							"stackHeight": 2
+						},
+						{
+							"accounts": [0, 4],
+							"data": "3Bxs4NLhqXb3ofom",
+							"programIdIndex": 5,
+							"stackHeight": 3
+						},
+						{
+							"accounts": [4],
+							"data": "9krTD1mFP1husSVM",
+							"programIdIndex": 5,
+							"stackHeight": 3
+						},
+						{
+							"accounts": [4],
+							"data": "SYXsBvR59WTsF4KEVN8LCQ1X9MekXCGPPNo3Af36taxCQBED",
+							"programIdIndex": 5,
+							"stackHeight": 3
+						},
+						{
+							"accounts": [0, 3],
+							"data": "3Bxs4bm7oSCPMeKR",
+							"programIdIndex": 5,
+							"stackHeight": 3
+						},
+						{
+							"accounts": [3],
+							"data": "9krTDGKFuDw9nLmM",
+							"programIdIndex": 5,
+							"stackHeight": 3
+						},
+						{
+							"accounts": [3],
+							"data": "SYXsBvR59WTsF4KEVN8LCQ1X9MekXCGPPNo3Af36taxCQBED",
+							"programIdIndex": 5,
+							"stackHeight": 3
+						},
+						{
+							"accounts": [7],
+							"data": "hTEY7jEqBPdDRkTWweeDPgyCUykRXEQVCUwrYmn4HZo84DdQrTJT2nBMiJFB3jXUVxHVd9mGq7BX9htuAN",
+							"programIdIndex": 6,
+							"stackHeight": 2
+						}
+					]
+				}
+			],
+			"loadedAddresses": {
+				"readonly": [],
+				"writable": []
+			},
+			"logMessages": [
+				"Program 11111111111111111111111111111111 invoke [1]",
+				"Program 11111111111111111111111111111111 success",
+				"Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX invoke [1]",
+				"Program log: Instruction: PostMessage",
+				"Program worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth invoke [2]",
+				"Program 11111111111111111111111111111111 invoke [3]",
+				"Program 11111111111111111111111111111111 success",
+				"Program 11111111111111111111111111111111 invoke [3]",
+				"Program 11111111111111111111111111111111 success",
+				"Program 11111111111111111111111111111111 invoke [3]",
+				"Program 11111111111111111111111111111111 success",
+				"Program log: Sequence: 0",
+				"Program 11111111111111111111111111111111 invoke [3]",
+				"Program 11111111111111111111111111111111 success",
+				"Program 11111111111111111111111111111111 invoke [3]",
+				"Program 11111111111111111111111111111111 success",
+				"Program 11111111111111111111111111111111 invoke [3]",
+				"Program 11111111111111111111111111111111 success",
+				"Program worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth consumed 60384 of 380989 compute units",
+				"Program worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth success",
+				"Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX invoke [2]",
+				"Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX consumed 2000 of 318068 compute units",
+				"Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX success",
+				"Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX consumed 84102 of 399850 compute units",
+				"Program EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX success"
+			],
+			"postBalances": [
+				499999999997496260, 1057920, 2350640170, 1552080, 946560, 1, 1141440, 0,
+				1169280, 1009200, 1141440
+			],
+			"postTokenBalances": [],
+			"preBalances": [
+				500000000000000000, 1057920, 2350640070, 0, 0, 1, 1141440, 0, 1169280,
+				1009200, 1141440
+			],
+			"preTokenBalances": [],
+			"rewards": [],
+			"status": {
+				"Ok": null
+			}
+		},
+		"slot": 3,
+		"transaction": {
+			"message": {
+				"header": {
+					"numReadonlySignedAccounts": 0,
+					"numReadonlyUnsignedAccounts": 6,
+					"numRequiredSignatures": 1
+				},
+				"accountKeys": [
+					"H3kCPjpQDT4hgwWHr9E9pC99rZT2yHAwiwSwku6Bne9",
+					"2yVjuQwpsvdsrywzsJJVs9Ueh4zayyo5DYJbBNc3DDpn",
+					"9bFNrXNb2WTx8fMHXCheaZqkLZ3YCCaiqTftHxeintHy",
+					"9vohBn118ZEctRmuTRvoUZg1B1HGfSH8C5QX6twtUFrJ",
+					"HeccUHmoyMi5S6nuTcyUBh4w4me3FP541a52ErYJRT8a",
+					"11111111111111111111111111111111",
+					"EtZMZM22ViKMo4r5y4Anovs3wKQ2owUmDpjygnMMcdEX",
+					"HQS31aApX3DDkuXgSpV9XyDUNtFgQ31pUn5BNWHG2PSp",
+					"SysvarC1ock11111111111111111111111111111111",
+					"SysvarRent111111111111111111111111111111111",
+					"worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth"
+				],
+				"recentBlockhash": "CMqPGm4icRdNuHsWJUK4Kgu4Cbe2nDQkYNqugQkKPa4Y",
+				"instructions": [
+					{
+						"accounts": [0, 2],
+						"data": "3Bxs4HanWsHUZCbH",
+						"programIdIndex": 5,
+						"stackHeight": null
+					},
+					{
+						"accounts": [1, 3, 0, 4, 0, 2, 8, 5, 9, 10, 7, 6],
+						"data": "3Cn8VBJReY7Bku3RduhBfYpk7tiw1R6pKcTWv9R",
+						"programIdIndex": 6,
+						"stackHeight": null
+					}
+				],
+				"indexToProgramIds": {}
+			},
+			"signatures": [
+				"3NACxoZLehbdKGjTWZKTTXJPuovyqAih1AD1BrkYj8nzDAtjiQUEaNmhkoU1jcFfoPTAjrvnaLFgTafNWr3fBrdB"
+			]
+		},
+		"version": "legacy"
+	}
+	`
+
+	///////// A bunch of checks to verify we parsed the JSON correctly.
+	var txRpc rpc.TransactionWithMeta
+	err := json.Unmarshal([]byte(eventJson), &txRpc)
+	require.NoError(t, err)
+
+	tx, err := txRpc.GetParsedTransaction()
+	require.NoError(t, err)
+
+	require.Equal(t, 2, len(tx.Message.Instructions))
+	require.Equal(t, 1, len(txRpc.Meta.InnerInstructions))
+
+	///////// Now we start the real test.
+
+	logger := zap.NewNop()
+	msgC := make(chan *common.MessagePublication, 10)
+	s := shimNewWatcherForTest(t, msgC)
+	require.True(t, s.shimEnabled)
+
+	var whProgramIndex uint16
+	var shimProgramIndex uint16
+	var shimFound bool
+	for n, key := range tx.Message.AccountKeys {
+		if key.Equals(s.contract) {
+			whProgramIndex = uint16(n)
+		}
+		if key.Equals(s.shimContractAddr) {
+			shimProgramIndex = uint16(n)
+			shimFound = true
+		}
+	}
+
+	require.Equal(t, uint16(10), whProgramIndex)
+	require.True(t, shimFound)
+	require.Equal(t, uint16(6), shimProgramIndex)
+
+	alreadyProcessed := ShimAlreadyProcessed{}
+	err = s.shimProcessRest(logger, whProgramIndex, shimProgramIndex, tx, txRpc.Meta.InnerInstructions[0].Instructions, 0, 10, nil, alreadyProcessed, false, true)
+	require.ErrorContains(t, err, "postMessage is nil")
+	require.Equal(t, 0, len(s.msgC))
+	require.Equal(t, 0, len(alreadyProcessed))
+}