parallel parsing
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user