parallel parsing

This commit is contained in:
thloyi
2026-01-07 21:15:54 +08:00
parent b82b7d9b0e
commit d9aea3e8d7
8 changed files with 290 additions and 163 deletions

View File

@@ -6,6 +6,8 @@ import (
"fmt"
"math/big"
"strings"
"sync"
"time"
"github.com/gagliardetto/solana-go"
"github.com/mr-tron/base58"
@@ -20,31 +22,40 @@ const (
// program ids
var (
pumpProgramID = solana.MustPublicKeyFromBase58("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P")
pumpProgramID = solana.MustPublicKeyFromBase58("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P")
pumpProgramIDString = pumpProgramID.String()
// has no sell function with pump and pump.amm program
azczProgramID = solana.MustPublicKeyFromBase58("AzcZqCRUQgKEg5FTAgY7JacATABEYCEfMbjXEzspLYFB")
azczProgramID = solana.MustPublicKeyFromBase58("AzcZqCRUQgKEg5FTAgY7JacATABEYCEfMbjXEzspLYFB")
azczProgramIDString = azczProgramID.String()
// only buy function with pump program
f5tfProgramID = solana.MustPublicKeyFromBase58("F5tfvbLog9VdGUPqBDTT8rgXvTTcq7e5UiGnupL1zvBq")
f5tfProgramID = solana.MustPublicKeyFromBase58("F5tfvbLog9VdGUPqBDTT8rgXvTTcq7e5UiGnupL1zvBq")
f5tfProgramIDString = f5tfProgramID.String()
// only pump.fun function
photonProgramID = solana.MustPublicKeyFromBase58("BSfD6SHZigAfDWSjzD5Q41jw8LmKwtmjskPH9XW1mrRW")
photonProgramID = solana.MustPublicKeyFromBase58("BSfD6SHZigAfDWSjzD5Q41jw8LmKwtmjskPH9XW1mrRW")
photonProgramIDString = photonProgramID.String()
pumpAmmProgramID = solana.MustPublicKeyFromBase58("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA")
pumpAmmProgramID = solana.MustPublicKeyFromBase58("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA")
pumpAmmProgramIDString = pumpAmmProgramID.String()
boboProgramID = solana.MustPublicKeyFromBase58("BobogA5N2KN2GG4XN3E3rNNRw3L8H1QPXp7QLxGrNHGM")
boboProgramID = solana.MustPublicKeyFromBase58("BobogA5N2KN2GG4XN3E3rNNRw3L8H1QPXp7QLxGrNHGM")
boboProgramIDString = boboProgramID.String()
qtkvProgramID = solana.MustPublicKeyFromBase58("qtkvapJEvRWWrB7i5K6RaA1kvq5x3qmMKZ98ad71XQ7")
qtkvProgramID = solana.MustPublicKeyFromBase58("qtkvapJEvRWWrB7i5K6RaA1kvq5x3qmMKZ98ad71XQ7")
qtkvProgramIDString = qtkvProgramID.String()
// only buy function with pump program
fjszProgramID = solana.MustPublicKeyFromBase58("FJsZbftBqRLfF7uqUKpm4s2goDr6xsQ5Q3mN7AFJB6hK")
fjszProgramID = solana.MustPublicKeyFromBase58("FJsZbftBqRLfF7uqUKpm4s2goDr6xsQ5Q3mN7AFJB6hK")
fjszProgramIDString = fjszProgramID.String()
flasProgramID = solana.MustPublicKeyFromBase58("FLASHX8DrLbgeR8FcfNV1F5krxYcYMUdBkrP1EPBtxB9")
flasProgramID = solana.MustPublicKeyFromBase58("FLASHX8DrLbgeR8FcfNV1F5krxYcYMUdBkrP1EPBtxB9")
flasProgramIDString = flasProgramID.String()
terminalProgramID = solana.MustPublicKeyFromBase58("term9YPb9mzAsABaqN71A4xdbxHmpBNZavpBiQKZzN3")
terminalProgramID = solana.MustPublicKeyFromBase58("term9YPb9mzAsABaqN71A4xdbxHmpBNZavpBiQKZzN3")
terminalProgramIDString = terminalProgramID.String()
jupiterV6ProgramID = solana.MustPublicKeyFromBase58("JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4")
jupiterV6ProgramID = solana.MustPublicKeyFromBase58("JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4")
jupiterV6ProgramIDString = jupiterV6ProgramID.String()
)
type AccountNotFoundError struct {
@@ -118,6 +129,7 @@ type versionedTransaction struct {
Signatures []solana.Signature
Message versionedMessage
Block uint64
Time time.Time
}
type pumpExtendedSellArgs struct {
@@ -192,15 +204,72 @@ type fjszBuyArgs struct {
TokenAmount uint64
}
var (
versionedPool = sync.Pool{}
accIdxPool = sync.Pool{}
)
func requireAccIdxSlice() []uint8 {
v := accIdxPool.Get()
if v == nil {
return make([]uint8, 0, 16)
}
return v.([]uint8)
}
func releaseAccIdxSlice(s []uint8) {
if s == nil {
return
}
s = s[:0]
accIdxPool.Put(s)
}
func requireVersionedPool() *versionedTransaction {
v := versionedPool.Get()
if v == nil {
return &versionedTransaction{
Signatures: make([]solana.Signature, 0, 10),
Message: versionedMessage{
StaticAccountKeys: make([]solana.PublicKey, 0, 256),
Instructions: make([]compiledInstruction, 0, 16),
AddressTableLookups: make([]addressTableLookup, 0, 10),
},
}
}
return v.(*versionedTransaction)
}
func releaseVersionedPool(v *versionedTransaction) {
if v == nil {
return
}
for i := range v.Message.Instructions {
releaseAccIdxSlice(v.Message.Instructions[i].Accounts)
}
for i := range v.Message.AddressTableLookups {
releaseAccIdxSlice(v.Message.AddressTableLookups[i].WritableIndexes)
releaseAccIdxSlice(v.Message.AddressTableLookups[i].ReadonlyIndexes)
}
versionedPool.Put(v)
}
// ParseTransaction mirrors the Rust parse_transaction entry point.
func ParseTransaction(update *SubscribeUpdateTransaction, loader *AddressTables) []*TxSignal {
func ParseTransaction(update *SubscribeUpdateTransaction, loader *AddressTables, stats bool) []*TxSignal {
var now time.Time
if stats {
now = time.Now()
}
versioned, err := toVersionedTransaction(update)
if err != nil || versioned == nil || len(versioned.Signatures) == 0 {
return nil
}
defer func() {
releaseVersionedPool(versioned)
}()
txHash := versioned.Signatures[0]
staticKeys := versioned.Message.StaticAccountKeys
// staticKeys := versioned.Message.StaticAccountKeys
instructions := versioned.Message.Instructions
if loader != nil && len(versioned.Message.AddressTableLookups) > 0 {
@@ -214,7 +283,7 @@ func ParseTransaction(update *SubscribeUpdateTransaction, loader *AddressTables)
lookupTableOk = false
break
}
staticKeys = append(staticKeys, accounts...)
versioned.Message.StaticAccountKeys = append(versioned.Message.StaticAccountKeys, accounts...)
}
if lookupTableOk {
@@ -226,68 +295,68 @@ func ParseTransaction(update *SubscribeUpdateTransaction, loader *AddressTables)
if len(accounts) != len(lookup.ReadonlyIndexes) {
break
}
staticKeys = append(staticKeys, accounts...)
versioned.Message.StaticAccountKeys = append(versioned.Message.StaticAccountKeys, accounts...)
}
}
versioned.Message.StaticAccountKeys = staticKeys
// versioned.Message.StaticAccountKeys = staticKeys
}
var parsed []*TxSignal
var parsed []*TxSignal = make([]*TxSignal, 0, 3)
for i := range instructions {
inst := instructions[i]
if int(inst.ProgramIDIndex) >= len(staticKeys) {
if int(inst.ProgramIDIndex) >= len(versioned.Message.StaticAccountKeys) {
continue
}
programID := staticKeys[inst.ProgramIDIndex]
programID := versioned.Message.StaticAccountKeys[inst.ProgramIDIndex]
switch programID {
case pumpProgramID:
txRes, err := parsePumpInstruction(versioned, i)
parsed = appendParsed(parsed, txRes, err, txHash, "pump", pumpProgramID.String())
parsed = appendParsed(now, parsed, txRes, err, txHash, "pump", pumpProgramIDString)
case azczProgramID:
txRes, err := parseAzczInstruction(versioned, i)
parsed = appendParsed(parsed, txRes, err, txHash, "azcz", azczProgramID.String())
parsed = appendParsed(now, parsed, txRes, err, txHash, "azcz", azczProgramIDString)
case f5tfProgramID:
txRes, err := parseF5tfInstruction(versioned, i)
parsed = appendParsed(parsed, txRes, err, txHash, "f5tf", f5tfProgramID.String())
parsed = appendParsed(now, parsed, txRes, err, txHash, "f5tf", f5tfProgramIDString)
case flasProgramID:
txRes, err := parseFlasInstruction(versioned, i)
parsed = appendParsed(parsed, txRes, err, txHash, "flas", flasProgramID.String())
parsed = appendParsed(now, parsed, txRes, err, txHash, "flas", flasProgramIDString)
case photonProgramID:
txRes, err := parsePhotonInstruction(versioned, i)
parsed = appendParsed(parsed, txRes, err, txHash, "photon", photonProgramID.String())
parsed = appendParsed(now, parsed, txRes, err, txHash, "photon", photonProgramIDString)
case pumpAmmProgramID:
txRes, err := parsePumpAmmInstruction(versioned, i)
parsed = appendParsed(parsed, txRes, err, txHash, "pumpamm", pumpAmmProgramID.String())
parsed = appendParsed(now, parsed, txRes, err, txHash, "pumpamm", pumpAmmProgramIDString)
case boboProgramID:
txRes, err := parseBoboInstruction(versioned, i)
parsed = appendParsed(parsed, txRes, err, txHash, "bobo", boboProgramID.String())
parsed = appendParsed(now, parsed, txRes, err, txHash, "bobo", boboProgramIDString)
case qtkvProgramID:
txRes, err := parseQtkvInstruction(versioned, i)
parsed = appendParsed(parsed, txRes, err, txHash, "qtkv", qtkvProgramID.String())
parsed = appendParsed(now, parsed, txRes, err, txHash, "qtkv", qtkvProgramIDString)
case fjszProgramID:
txRes, err := parseFjszInstruction(versioned, i)
parsed = appendParsed(parsed, txRes, err, txHash, "fjsz", fjszProgramID.String())
parsed = appendParsed(now, parsed, txRes, err, txHash, "fjsz", fjszProgramIDString)
case terminalProgramID:
txRes, err := parseTermInstruction(versioned, i)
parsed = appendParsed(parsed, txRes, err, txHash, "terminal", terminalProgramID.String())
parsed = appendParsed(now, parsed, txRes, err, txHash, "terminal", terminalProgramIDString)
case jupiterV6ProgramID:
txRes, err := parseJupiterV6Instruction(versioned, i)
parsed = appendParsed(parsed, txRes, err, txHash, "jupiterv6", jupiterV6ProgramID.String())
parsed = appendParsed(now, parsed, txRes, err, txHash, "jupiterv6", jupiterV6ProgramIDString)
case okxDexRouteV2ProgramID:
txRes, err := parseOkxDexRouteV2Instruction(versioned, i)
parsed = appendParsed(parsed, txRes, err, txHash, "okxdexroutev2", okxDexRouteV2ProgramID.String())
parsed = appendParsed(now, parsed, txRes, err, txHash, "okxdexroutev2", okxDexRouteV2ProgramIDString)
case dflowProgramID:
txRes, err := parseDFlowInstruction(versioned, i)
parsed = appendParsed(parsed, txRes, err, txHash, "dflow", dflowProgramID.String())
parsed = appendParsed(now, parsed, txRes, err, txHash, "dflow", dflowProgramString)
}
}
return parsed
}
func appendParsed(list []*TxSignal, parsed *TxSignal, err error, txHash [64]byte, label string, entryContract string) []*TxSignal {
func appendParsed(start time.Time, list []*TxSignal, parsed *TxSignal, err error, txHash [64]byte, label string, entryContract string) []*TxSignal {
if err != nil {
if !strings.HasPrefix(err.Error(), "account index") {
logger.Debug("txparser: failed to parse", "label", label, "instruction", err, "tx_hash", base58.Encode(txHash[:]))
@@ -296,6 +365,11 @@ func appendParsed(list []*TxSignal, parsed *TxSignal, err error, txHash [64]byte
}
if parsed != nil {
parsed.EntryContract = entryContract
parsed.Label = label
if !start.IsZero() {
parsed.ParseEnd = time.Now()
parsed.ParseStart = start
}
list = append(list, parsed)
}
return list
@@ -308,47 +382,42 @@ func toVersionedTransaction(update *SubscribeUpdateTransaction) (*versionedTrans
protoTx := update.Transaction
msg := protoTx.Message
signatures := make([]solana.Signature, len(protoTx.Signatures))
for i, rawSig := range protoTx.Signatures {
signatures[i] = solana.SignatureFromBytes(rawSig)
versioned := requireVersionedPool()
versioned.Signatures = versioned.Signatures[:0]
for _, rawSig := range protoTx.Signatures {
versioned.Signatures = append(versioned.Signatures, solana.SignatureFromBytes(rawSig))
}
versioned.Message.StaticAccountKeys = versioned.Message.StaticAccountKeys[:0]
for _, key := range msg.AccountKeys {
versioned.Message.StaticAccountKeys = append(versioned.Message.StaticAccountKeys, solana.PublicKeyFromBytes(key))
}
versioned.Message.Instructions = versioned.Message.Instructions[:0]
for _, instr := range msg.Instructions {
accounts := requireAccIdxSlice()
accounts = append(accounts, instr.Accounts...)
versioned.Message.Instructions = append(versioned.Message.Instructions,
compiledInstruction{
ProgramIDIndex: uint8(instr.ProgramIdIndex),
Accounts: accounts,
Data: instr.Data,
})
}
staticKeys := make([]solana.PublicKey, len(msg.AccountKeys))
for i, key := range msg.AccountKeys {
staticKeys[i] = solana.PublicKeyFromBytes(key)
}
instructions := make([]compiledInstruction, len(msg.Instructions))
for i, instr := range msg.Instructions {
accounts := append([]uint8(nil), instr.Accounts...)
instructions[i] = compiledInstruction{
ProgramIDIndex: uint8(instr.ProgramIdIndex),
Accounts: accounts,
Data: instr.Data,
}
}
lookups := make([]addressTableLookup, len(msg.AddressTableLookups))
for i, lookup := range msg.AddressTableLookups {
writable := append([]uint8(nil), lookup.WritableIndexes...)
readonly := append([]uint8(nil), lookup.ReadonlyIndexes...)
lookups[i] = addressTableLookup{
versioned.Message.AddressTableLookups = versioned.Message.AddressTableLookups[:0]
for _, lookup := range msg.AddressTableLookups {
writable := requireAccIdxSlice()
writable = append(writable, lookup.WritableIndexes...)
readonly := requireAccIdxSlice()
readonly = append(readonly, lookup.ReadonlyIndexes...)
versioned.Message.AddressTableLookups = append(versioned.Message.AddressTableLookups, addressTableLookup{
AccountKey: solana.PublicKeyFromBytes(lookup.AccountKey),
WritableIndexes: writable,
ReadonlyIndexes: readonly,
}
})
}
return &versionedTransaction{
Signatures: signatures,
Message: versionedMessage{
StaticAccountKeys: staticKeys,
Instructions: instructions,
AddressTableLookups: lookups,
},
Block: update.GetSlot(),
}, nil
versioned.Block = update.GetSlot()
return versioned, nil
}
func formatTokenAmount(amount uint64) decimal.Decimal {