raw tx binary

This commit is contained in:
thloyi
2026-04-29 17:14:26 +08:00
parent 43659ea4e4
commit d46e8b651c
6 changed files with 696 additions and 95 deletions

189
rawtx.go
View File

@@ -1,8 +1,11 @@
package pump_parser
import (
"encoding/base64"
"encoding/json"
"fmt"
"strconv"
"strings"
"time"
bin "github.com/gagliardetto/binary"
@@ -105,10 +108,11 @@ func (tx *RawTx) GetBlockTime() *pgtype.Timestamptz {
}
type Instruction struct {
Accounts []int `json:"accounts"`
Data solana.Base58 `json:"data"`
ProgramIDIndex int `json:"programIdIndex"`
StackHeight *int `json:"stackHeight"`
Accounts []int `json:"accounts"`
Data solana.Base58 `json:"data"`
ProgramIDIndex int `json:"programIdIndex"`
StackHeight *int `json:"stackHeight"`
LogEvents []solana.Base64 `json:"logEvents,omitempty"`
}
type InnerInstructions struct {
Index int `json:"index"`
@@ -180,6 +184,11 @@ type Transaction struct {
Signatures []solana.Signature `json:"signatures"`
}
type RawTxConvertOptions struct {
IgnoreLogMessages bool
ParseLogEvents bool
}
func (tx *Transaction) UnmarshalJSON(data []byte) error {
if len(data) == 0 || (len(data) == 4 && string(data) == "null") {
// TODO: is this an error?
@@ -308,7 +317,8 @@ func marshalRpcTransactionErr(err any) string {
return string(e)
}
func FromRpcTransactionWithMeta(tx rpc.TransactionWithMeta, blockTime *uint64, slot uint64, index int64) (*RawTx, error) {
func FromRpcTransactionWithMeta(tx rpc.TransactionWithMeta, blockTime *uint64, slot uint64, index int64, options ...RawTxConvertOptions) (*RawTx, error) {
option := rawTxConvertOption(options)
created := int64(0)
if blockTime != nil {
created = int64(*blockTime)
@@ -523,6 +533,8 @@ func FromRpcTransactionWithMeta(tx rpc.TransactionWithMeta, blockTime *uint64, s
})
}
applyRawTxConvertLogOptions(sTx, option)
return sTx, nil
}
@@ -833,7 +845,8 @@ func isAccountOwner(account, owner, mint solana.PublicKey) (bool, error) {
return account == ata, nil
}
func ConvertYellowstoneGrpcTransactionToSolanaTransaction(y *pb.SubscribeUpdateTransaction, created int64) (*RawTx, error) {
func ConvertYellowstoneGrpcTransactionToSolanaTransaction(y *pb.SubscribeUpdateTransaction, created int64, options ...RawTxConvertOptions) (*RawTx, error) {
option := rawTxConvertOption(options)
sTx := &RawTx{
BlockTime: created,
Slot: y.Slot,
@@ -1002,6 +1015,8 @@ func ConvertYellowstoneGrpcTransactionToSolanaTransaction(y *pb.SubscribeUpdateT
})
}
applyRawTxConvertLogOptions(sTx, option)
// resolve the lookups
//{
// if sTx.Transaction.Message.IsVersioned() {
@@ -1021,6 +1036,168 @@ func newInt16(x uint16) *int {
return &y
}
func rawTxConvertOption(options []RawTxConvertOptions) RawTxConvertOptions {
out := RawTxConvertOptions{ParseLogEvents: true}
if len(options) > 0 {
out = options[0]
}
return out
}
func applyRawTxConvertLogOptions(tx *RawTx, option RawTxConvertOptions) {
if tx == nil {
return
}
if option.ParseLogEvents {
attachLogEventsToInstructions(tx, tx.Meta.LogMessages)
}
if option.IgnoreLogMessages {
tx.Meta.LogMessages = nil
}
}
type instructionLogFrame struct {
program string
instr *Instruction
}
type instructionLogTarget struct {
program string
stackHeight int
instr *Instruction
}
func attachLogEventsToInstructions(tx *RawTx, logMessages []string) {
if tx == nil || len(logMessages) == 0 {
return
}
targets := rawTxInstructionLogTargets(tx)
if len(targets) == 0 {
return
}
nextTarget := 0
var stack []instructionLogFrame
for _, logMessage := range logMessages {
if program, stackHeight, ok := parseProgramInvokeLog(logMessage); ok {
var instr *Instruction
for nextTarget < len(targets) {
target := targets[nextTarget]
nextTarget++
if target.program != program {
continue
}
if target.stackHeight != 0 && target.stackHeight != stackHeight {
continue
}
instr = target.instr
break
}
stack = append(stack, instructionLogFrame{program: program, instr: instr})
continue
}
if data, ok := parseProgramDataLog(logMessage); ok {
if len(stack) == 0 {
continue
}
top := stack[len(stack)-1]
if top.instr != nil {
top.instr.LogEvents = append(top.instr.LogEvents, data)
}
continue
}
if program, ok := parseProgramFinishedLog(logMessage); ok {
for i := len(stack) - 1; i >= 0; i-- {
if stack[i].program == program {
stack = stack[:i]
break
}
}
}
}
}
func rawTxInstructionLogTargets(tx *RawTx) []instructionLogTarget {
accountList := tx.getAccountList()
innerByOuter := make(map[int]*InnerInstructions, len(tx.Meta.InnerInstructions))
for i := range tx.Meta.InnerInstructions {
inner := &tx.Meta.InnerInstructions[i]
innerByOuter[inner.Index] = inner
}
out := make([]instructionLogTarget, 0, len(tx.Transaction.Message.Instructions))
for i := range tx.Transaction.Message.Instructions {
instr := &tx.Transaction.Message.Instructions[i]
if instr.ProgramIDIndex >= 0 && instr.ProgramIDIndex < len(accountList) {
out = append(out, instructionLogTarget{
program: accountList[instr.ProgramIDIndex].String(),
stackHeight: 1,
instr: instr,
})
}
if inner := innerByOuter[i]; inner != nil {
for j := range inner.Instructions {
innerInstr := &inner.Instructions[j]
if innerInstr.ProgramIDIndex < 0 || innerInstr.ProgramIDIndex >= len(accountList) {
continue
}
stackHeight := 0
if innerInstr.StackHeight != nil {
stackHeight = *innerInstr.StackHeight
}
out = append(out, instructionLogTarget{
program: accountList[innerInstr.ProgramIDIndex].String(),
stackHeight: stackHeight,
instr: innerInstr,
})
}
}
}
return out
}
func parseProgramInvokeLog(logMessage string) (string, int, bool) {
if !strings.HasPrefix(logMessage, "Program ") {
return "", 0, false
}
rest := strings.TrimPrefix(logMessage, "Program ")
program, suffix, ok := strings.Cut(rest, " invoke [")
if !ok {
return "", 0, false
}
suffix = strings.TrimSuffix(suffix, "]")
stackHeight, err := strconv.Atoi(suffix)
if err != nil {
return "", 0, false
}
return program, stackHeight, true
}
func parseProgramDataLog(logMessage string) (solana.Base64, bool) {
if !strings.HasPrefix(logMessage, "Program data: ") {
return nil, false
}
data, err := base64.StdEncoding.DecodeString(strings.TrimSpace(strings.TrimPrefix(logMessage, "Program data: ")))
if err != nil {
return nil, false
}
return solana.Base64(data), true
}
func parseProgramFinishedLog(logMessage string) (string, bool) {
if !strings.HasPrefix(logMessage, "Program ") {
return "", false
}
if !strings.HasSuffix(logMessage, " success") && !strings.Contains(logMessage, " failed:") {
return "", false
}
rest := strings.TrimPrefix(logMessage, "Program ")
program, _, ok := strings.Cut(rest, " ")
return program, ok
}
func newInt(x *uint32) *int {
if x == nil {
return nil