From 8c98ec7875cd40c8cb8660919445a87f59873cd2 Mon Sep 17 00:00:00 2001 From: thloyi Date: Mon, 5 Jan 2026 14:38:02 +0800 Subject: [PATCH] cache address table --- go.mod | 1 + go.sum | 2 + pkg/shreder/addresstables.go | 13 +++--- pkg/shreder/txparser.go | 88 ++++++++++-------------------------- 4 files changed, 33 insertions(+), 71 deletions(-) diff --git a/go.mod b/go.mod index 745fb57..5257f9c 100644 --- a/go.mod +++ b/go.mod @@ -22,6 +22,7 @@ require ( github.com/gagliardetto/binary v0.8.0 // indirect github.com/gagliardetto/treeout v0.1.4 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.13.6 // indirect github.com/logrusorgru/aurora v2.0.3+incompatible // indirect diff --git a/go.sum b/go.sum index 478afe3..21e80a7 100644 --- a/go.sum +++ b/go.sum @@ -36,6 +36,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= diff --git a/pkg/shreder/addresstables.go b/pkg/shreder/addresstables.go index 7bded4a..8e2e575 100644 --- a/pkg/shreder/addresstables.go +++ b/pkg/shreder/addresstables.go @@ -7,13 +7,14 @@ import ( "github.com/gagliardetto/solana-go" "github.com/gagliardetto/solana-go/rpc" + lru "github.com/hashicorp/golang-lru/v2" "github.com/panjf2000/ants/v2" ) type AddressTables struct { rpcClient *rpc.Client mux sync.RWMutex - tables map[solana.PublicKey][]solana.PublicKey + tables *lru.Cache[solana.PublicKey, []solana.PublicKey] loading map[solana.PublicKey]struct{} pool *ants.Pool @@ -21,9 +22,10 @@ type AddressTables struct { func NewAddressTables(rpcClient *rpc.Client) *AddressTables { pool, _ := ants.NewPool(10, ants.WithPreAlloc(true), ants.WithNonblocking(true)) + cache, _ := lru.New[solana.PublicKey, []solana.PublicKey](10000) return &AddressTables{ rpcClient: rpcClient, - tables: make(map[solana.PublicKey][]solana.PublicKey), + tables: cache, loading: make(map[solana.PublicKey]struct{}), pool: pool, } @@ -53,9 +55,8 @@ func (at *AddressTables) loadAddressTable(tablePubkey solana.PublicKey) ([]solan } func (at *AddressTables) GetAddressTable(tablePubkey solana.PublicKey, idx []uint8) []solana.PublicKey { - at.mux.RLock() - addresses, ok := at.tables[tablePubkey] + addresses, ok := at.tables.Get(tablePubkey) if !ok { at.mux.RUnlock() _ = at.pool.Submit(func() { @@ -79,8 +80,8 @@ func (at *AddressTables) GetAddressTable(tablePubkey solana.PublicKey, idx []uin return } at.mux.Lock() - at.tables[tablePubkey] = table - total := len(at.tables) + at.tables.Add(tablePubkey, table) + total := at.tables.Len() delete(at.loading, tablePubkey) at.mux.Unlock() logger.Info("loadAddressTable", "table", tablePubkey.String(), "table count:", total) diff --git a/pkg/shreder/txparser.go b/pkg/shreder/txparser.go index 0a0b1d5..ba7fdec 100644 --- a/pkg/shreder/txparser.go +++ b/pkg/shreder/txparser.go @@ -80,11 +80,6 @@ var ( terminalAmmSellTokensIX = []byte{0x40, 0x64, 0x97, 0xb9, 0x16, 0xfa, 0xec, 0xb1} ) -// table lookups -const ( - photonTableLookup = "3r6paeFSLpeUVmWtShb5uZtXYpcBE3729kUxkUS7xKi1" -) - type compiledInstruction struct { ProgramIDIndex uint8 Accounts []uint8 @@ -406,7 +401,7 @@ func parsePumpCreateV2(tx *versionedTransaction, instruction *compiledInstructio return nil, fmt.Errorf("accounts too short") } if len(instruction.Data) < 8 { - return nil, fmt.Errorf("data too short for create v2 args") + return nil, fmt.Errorf("data too short for pump create v2 args, len=%d", len(instruction.Data)) } staticKeys := tx.Message.StaticAccountKeys @@ -443,7 +438,7 @@ func parsePumpCreateV2(tx *versionedTransaction, instruction *compiledInstructio func decodePumpBuyArgs(data []byte) (uint64, uint64, error) { if len(data) < 9 { - return 0, 0, fmt.Errorf("data too short for buy args") + return 0, 0, fmt.Errorf("data too short for pump buy buy args, len=%d", len(data)) } var args pumpBuyArgs @@ -507,7 +502,7 @@ func parsePumpBuy(tx *versionedTransaction, instruction *compiledInstruction) (* func decodePumpSellArgs(data []byte) (uint64, uint64, error) { if len(data) < 9 { - return 0, 0, fmt.Errorf("data too short for sell args") + return 0, 0, fmt.Errorf("data too short for pump sell sell args, len=%d", len(data)) } var args pumpExtendedSellArgs @@ -597,7 +592,7 @@ func parseAzczAmmBuy(tx *versionedTransaction, instructionIndex int) (*TxSignal, } if len(instruction.Data) < 17 { - return nil, fmt.Errorf("data too short for buy args") + return nil, fmt.Errorf("data too short for azcz amm buy args, len=%d", len(instruction.Data)) } solAmount := binary.LittleEndian.Uint64(instruction.Data[1:9]) @@ -637,7 +632,7 @@ func parseAzczBuy(tx *versionedTransaction, instructionIndex int) (*TxSignal, er } if len(instruction.Data) < 2 { - return nil, fmt.Errorf("data too short for buy args") + return nil, fmt.Errorf("data too short for azcz buy args len=%d", len(instruction.Data)) } var args azczBuyArgs @@ -691,7 +686,7 @@ func parseF5tfInstruction(tx *versionedTransaction, instructionIndex int) (*TxSi } if len(instruction.Data) < 2 { - return nil, fmt.Errorf("data too short for buy args") + return nil, fmt.Errorf("data too short for f5tf buy args len=%d", len(instruction.Data)) } var args f5tfBuyArgs @@ -726,8 +721,11 @@ func parseFlasInstruction(tx *versionedTransaction, instructionIndex int) (*TxSi if len(instruction.Data) == 0 { return nil, fmt.Errorf("data is empty") } + if len(instruction.Data) == 10 && instruction.Data[0] == 1 { + return nil, nil + } if len(instruction.Data) < 20 { - return nil, fmt.Errorf("data too short for args") + return nil, fmt.Errorf("data too short for args flas instruction, len: %d", len(instruction.Data)) } methodData := instruction.Data[17:20] if !matchMethod(methodData, flasBuyTokensIX) { @@ -929,7 +927,7 @@ func parsePhotonBuy(tx *versionedTransaction, instruction *compiledInstruction) return nil, fmt.Errorf("accounts too short") } if len(instruction.Data) < 16 { - return nil, fmt.Errorf("data too short for buy args") + return nil, fmt.Errorf("data too short for photon buy args, len=%d", len(instruction.Data)) } staticKeys := tx.Message.StaticAccountKeys @@ -970,7 +968,7 @@ func parsePhotonSwap(tx *versionedTransaction, instruction *compiledInstruction) return nil, fmt.Errorf("accounts too short") } if len(instruction.Data) < 16 { - return nil, fmt.Errorf("data too short for swap args") + return nil, fmt.Errorf("data too short for swap args for photon. len=%d", len(instruction.Data)) } staticKeys := tx.Message.StaticAccountKeys @@ -979,12 +977,11 @@ func parsePhotonSwap(tx *versionedTransaction, instruction *compiledInstruction) return nil, err } - quoteIndex := int(instruction.Accounts[4]) - quote, err := resolveQuoteAccount(tx, quoteIndex, []string{photonTableLookup}, 0) + quote, err := getStaticKey(staticKeys, int(instruction.Accounts[4])) if err != nil { return nil, err } - if quote != wsolMint { + if !quote.Equals(solana.WrappedSol) { return nil, nil } @@ -1173,7 +1170,7 @@ func parseTermSell(tx *versionedTransaction, instruction *compiledInstruction) ( func decodePumpAmmBuyArgs(data []byte) (uint64, uint64, error) { if len(data) < 9 { - return 0, 0, fmt.Errorf("data too short for buy args") + return 0, 0, fmt.Errorf("data too short for pump amm buy args, len=%d", len(data)) } var args pumpAmmBuyArgs @@ -1212,12 +1209,11 @@ func parsePumpAmmBuy(tx *versionedTransaction, instruction *compiledInstruction) if err != nil { return nil, err } - quoteIndex := int(instruction.Accounts[4]) - quote, err := resolveQuoteAccount(tx, quoteIndex, nil, 0) + quote, err := getStaticKey(staticKeys, int(instruction.Accounts[4])) if err != nil { return nil, err } - if quote != wsolMint { + if !quote.Equals(solana.WrappedSol) { return nil, nil } @@ -1259,12 +1255,11 @@ func parsePumpAmmSell(tx *versionedTransaction, instruction *compiledInstruction if err != nil { return nil, err } - quoteIndex := int(instruction.Accounts[4]) - quote, err := resolveQuoteAccount(tx, quoteIndex, nil, 0) + quote, err := getStaticKey(staticKeys, int(instruction.Accounts[4])) if err != nil { return nil, err } - if quote != wsolMint { + if !quote.Equals(solana.WrappedSol) { return nil, nil } @@ -1308,7 +1303,7 @@ func parseBoboInstruction(tx *versionedTransaction, instructionIndex int) (*TxSi return nil, fmt.Errorf("accounts too short") } if len(instruction.Data) < 16 { - return nil, fmt.Errorf("data too short for buy args") + return nil, fmt.Errorf("data too short for bobo buy args, len=%d", len(instruction.Data)) } staticKeys := tx.Message.StaticAccountKeys @@ -1371,7 +1366,7 @@ func parseQtkvSell(tx *versionedTransaction, instructionIndex int) (*TxSignal, e return nil, fmt.Errorf("accounts too short") } if len(instruction.Data) < 24 { - return nil, fmt.Errorf("data too short for sell args") + return nil, fmt.Errorf("data too short for qtkv sell args, len=%d", len(instruction.Data)) } staticKeys := tx.Message.StaticAccountKeys @@ -1409,7 +1404,7 @@ func parseQtkvAmmSell(tx *versionedTransaction, instructionIndex int) (*TxSignal return nil, fmt.Errorf("accounts too short") } if len(instruction.Data) < 24 { - return nil, fmt.Errorf("data too short for sell args") + return nil, fmt.Errorf("data too short for qtkv amm sell args, len=%d", len(instruction.Data)) } staticKeys := tx.Message.StaticAccountKeys @@ -1497,7 +1492,7 @@ func parseFjszInstruction(tx *versionedTransaction, instructionIndex int) (*TxSi return nil, fmt.Errorf("accounts too short") } if len(instruction.Data) < 16 { - return nil, fmt.Errorf("data too short for buy args") + return nil, fmt.Errorf("data too short for fjzs buy args, len=%d", len(instruction.Data)) } staticKeys := tx.Message.StaticAccountKeys @@ -1553,43 +1548,6 @@ func parseTerminalInstruction(tx *versionedTransaction, instructionIndex int) (* return nil, nil } -func resolveQuoteAccount(tx *versionedTransaction, quoteIndex int, expectedTableKeys []string, targetIndex uint8) (string, error) { - staticKeys := tx.Message.StaticAccountKeys - if quoteIndex < len(staticKeys) { - quoteKey := staticKeys[quoteIndex].String() - return quoteKey, nil - } - - // attempt to load from address table lookup - if len(expectedTableKeys) == 0 || len(tx.Message.AddressTableLookups) != 1 { - return "", fmt.Errorf("parse quote from table lookup failed") - } - - table := tx.Message.AddressTableLookups[0] - match := false - for _, key := range expectedTableKeys { - if table.AccountKey.String() == key { - match = true - break - } - } - if !match { - return "", fmt.Errorf("parse quote from table lookup failed") - } - - indexOfTarget := indexOf(table.ReadonlyIndexes, targetIndex) - if indexOfTarget < 0 { - return "", fmt.Errorf("parse quote from table lookup failed") - } - - expectedIndex := len(staticKeys) + len(table.WritableIndexes) + indexOfTarget - if quoteIndex != expectedIndex { - return "", fmt.Errorf("parse quote from table lookup failed") - } - - return wsolMint, nil -} - func indexOf(haystack []uint8, needle uint8) int { for i, v := range haystack { if v == needle {