package pump_parser import ( "bytes" "io" "testing" "github.com/gagliardetto/solana-go" "github.com/shopspring/decimal" ) func TestTxBinaryRoundTrip(t *testing.T) { txHash := [64]byte{} for i := range txHash { txHash[i] = byte(i + 1) } original := &Tx{ Signer: mustPubKey("So11111111111111111111111111111111111111112"), Block: 123456789, BlockIndex: 42, TxHash: &txHash, CuFee: decimal.NewFromInt(5000), CUPrice: decimal.RequireFromString("0.123456"), BeforeSolBalance: decimal.RequireFromString("1.500000000"), AfterSOLBalance: decimal.RequireFromString("1.234567890"), ComputeUnitsConsumed: 345678, CuLimit: 400000, Platform: map[string]platformInfo{ PlatformGMGN: { Platform: PlatformGMGN, PlatformFee: decimal.RequireFromString("0.010000000"), }, PlatformPhoton: { Platform: PlatformPhoton, PlatformFee: decimal.RequireFromString("0.020000000"), }, }, MevAgent: map[string]mevInfo{ MevAgentJito: { MevAgent: MevAgentJito, MevAgentFee: decimal.RequireFromString("0.030000000"), }, }, Swaps: []Swap{ { Program: SolProgramPump, Event: TxEventBuy, TxIndex: 7, InstrIdx: 2, InnerIdx: 1, Pool: mustPubKey("11111111111111111111111111111111"), BaseMint: mustPubKey("3wyAj7RtG72wM1Wv9DkYfL7RAx9X3Jx1sC6E6mN4jWeL"), QuoteMint: solana.WrappedSol, BaseTokenProgram: solana.TokenProgramID, QuoteTokenProgram: solana.TokenProgramID, Creator: mustPubKey("BPFLoader1111111111111111111111111111111111"), BaseMintDecimals: 6, QuoteMintDecimals: 9, User: mustPubKey("SysvarRent111111111111111111111111111111111"), BaseAmount: decimal.NewFromInt(1200), QuoteAmount: decimal.NewFromInt(3400), SwapMode: SwapModeExactIn, FixedAmount: decimal.NewFromInt(3400), FixedAmountSide: SwapAmountSideQuote, FixedMint: solana.WrappedSol, LimitAmountType: SwapLimitTypeMinOut, LimitAmount: decimal.NewFromInt(1000), LimitAmountSide: SwapAmountSideBase, LimitMint: mustPubKey("3wyAj7RtG72wM1Wv9DkYfL7RAx9X3Jx1sC6E6mN4jWeL"), ActualLimitAmount: decimal.NewFromInt(1200), ActualLimitAmountSide: SwapAmountSideBase, SlippageBps: decimal.RequireFromString("833.3333"), BaseReserve: decimal.NewFromInt(5555), QuoteReserve: decimal.NewFromInt(9999), Mayhem: true, Cashback: false, UserBaseBalance: decimal.NewFromInt(777), UserQuoteBalance: decimal.NewFromInt(888), EntryContract: mustPubKey("ComputeBudget111111111111111111111111111111"), MigrateToPool: mustPubKey("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"), MigrateTopProgram: mustPubKey("AddressLookupTab1e1111111111111111111111111"), LpMint: mustPubKey("4Nd1mJf8JQhRVTfJxW2YxXLNQKhPYo1JzN1u2KAPY1Hn"), AfterSOLBalance: decimal.RequireFromString("0.321000000"), ActiveBinId: 11, FeeAmount: decimal.NewFromInt(99), FeeBps: "123", FeeSide: "base", ConsumeUnit: 9999, }, }, } encoded, err := EncodeTxBinary(original) if err != nil { t.Fatalf("EncodeTxBinary() error = %v", err) } decoded, err := DecodeTxBinary(encoded) if err != nil { t.Fatalf("DecodeTxBinary() error = %v", err) } if decoded.Signer != original.Signer { t.Fatalf("Signer = %s, want %s", decoded.Signer, original.Signer) } if decoded.Block != original.Block { t.Fatalf("Block = %d, want %d", decoded.Block, original.Block) } if decoded.BlockIndex != original.BlockIndex { t.Fatalf("BlockIndex = %d, want %d", decoded.BlockIndex, original.BlockIndex) } if decoded.TxHash == nil { t.Fatal("TxHash = nil, want non-nil") } if *decoded.TxHash != *original.TxHash { t.Fatalf("TxHash mismatch") } if !decoded.CuFee.Equal(original.CuFee) { t.Fatalf("CuFee = %s, want %s", decoded.CuFee, original.CuFee) } if !decoded.CUPrice.Equal(original.CUPrice) { t.Fatalf("CUPrice = %s, want %s", decoded.CUPrice, original.CUPrice) } if decoded.BeforeSolBalance.StringFixed(9) != original.BeforeSolBalance.StringFixed(9) { t.Fatalf("BeforeSolBalance = %s, want %s", decoded.BeforeSolBalance, original.BeforeSolBalance) } if decoded.AfterSOLBalance.StringFixed(9) != original.AfterSOLBalance.StringFixed(9) { t.Fatalf("AfterSOLBalance = %s, want %s", decoded.AfterSOLBalance, original.AfterSOLBalance) } if decoded.CuLimit != original.CuLimit { t.Fatalf("CuLimit = %d, want %d", decoded.CuLimit, original.CuLimit) } if decoded.ComputeUnitsConsumed != original.ComputeUnitsConsumed { t.Fatalf("ComputeUnitsConsumed = %d, want %d", decoded.ComputeUnitsConsumed, original.ComputeUnitsConsumed) } if len(decoded.Platform) != len(original.Platform) { t.Fatalf("Platform len = %d, want %d", len(decoded.Platform), len(original.Platform)) } if !decoded.Platform[PlatformGMGN].PlatformFee.Equal(original.Platform[PlatformGMGN].PlatformFee) { t.Fatalf("Platform fee mismatch") } if len(decoded.MevAgent) != len(original.MevAgent) { t.Fatalf("MevAgent len = %d, want %d", len(decoded.MevAgent), len(original.MevAgent)) } if !decoded.MevAgent[MevAgentJito].MevAgentFee.Equal(original.MevAgent[MevAgentJito].MevAgentFee) { t.Fatalf("MevAgent fee mismatch") } if len(decoded.Swaps) != 1 { t.Fatalf("Swaps len = %d, want 1", len(decoded.Swaps)) } swap := decoded.Swaps[0] if swap.Program != original.Swaps[0].Program { t.Fatalf("swap.Program = %s, want %s", swap.Program, original.Swaps[0].Program) } if swap.Event != original.Swaps[0].Event { t.Fatalf("swap.Event = %s, want %s", swap.Event, original.Swaps[0].Event) } if swap.TxIndex != original.Swaps[0].TxIndex { t.Fatalf("swap.TxIndex = %d, want %d", swap.TxIndex, original.Swaps[0].TxIndex) } if !swap.BaseAmount.Equal(original.Swaps[0].BaseAmount) { t.Fatalf("swap.BaseAmount = %s, want %s", swap.BaseAmount, original.Swaps[0].BaseAmount) } if !swap.QuoteAmount.Equal(original.Swaps[0].QuoteAmount) { t.Fatalf("swap.QuoteAmount = %s, want %s", swap.QuoteAmount, original.Swaps[0].QuoteAmount) } if !swap.FixedAmount.Equal(original.Swaps[0].FixedAmount) { t.Fatalf("swap.FixedAmount = %s, want %s", swap.FixedAmount, original.Swaps[0].FixedAmount) } if !swap.LimitAmount.Equal(original.Swaps[0].LimitAmount) { t.Fatalf("swap.LimitAmount = %s, want %s", swap.LimitAmount, original.Swaps[0].LimitAmount) } if !swap.ActualLimitAmount.Equal(original.Swaps[0].ActualLimitAmount) { t.Fatalf("swap.ActualLimitAmount = %s, want %s", swap.ActualLimitAmount, original.Swaps[0].ActualLimitAmount) } if swap.SlippageBps.String() != "833" { t.Fatalf("swap.SlippageBps = %s, want 833", swap.SlippageBps) } if !swap.BaseReserve.Equal(original.Swaps[0].BaseReserve) { t.Fatalf("swap.BaseReserve = %s, want %s", swap.BaseReserve, original.Swaps[0].BaseReserve) } if !swap.QuoteReserve.Equal(original.Swaps[0].QuoteReserve) { t.Fatalf("swap.QuoteReserve = %s, want %s", swap.QuoteReserve, original.Swaps[0].QuoteReserve) } if !swap.UserBaseBalance.Equal(original.Swaps[0].UserBaseBalance) { t.Fatalf("swap.UserBaseBalance = %s, want %s", swap.UserBaseBalance, original.Swaps[0].UserBaseBalance) } if !swap.UserQuoteBalance.Equal(original.Swaps[0].UserQuoteBalance) { t.Fatalf("swap.UserQuoteBalance = %s, want %s", swap.UserQuoteBalance, original.Swaps[0].UserQuoteBalance) } if swap.AfterSOLBalance.StringFixed(9) != original.Swaps[0].AfterSOLBalance.StringFixed(9) { t.Fatalf("swap.AfterSOLBalance = %s, want %s", swap.AfterSOLBalance, original.Swaps[0].AfterSOLBalance) } if swap.ActiveBinId != 0 { t.Fatalf("swap.ActiveBinId = %d, want 0", swap.ActiveBinId) } if !swap.FeeAmount.IsZero() { t.Fatalf("swap.FeeAmount = %s, want 0", swap.FeeAmount) } if swap.FeeBps != "" { t.Fatalf("swap.FeeBps = %q, want empty", swap.FeeBps) } if swap.FeeSide != "" { t.Fatalf("swap.FeeSide = %q, want empty", swap.FeeSide) } if swap.ConsumeUnit != 0 { t.Fatalf("swap.ConsumeUnit = %d, want 0", swap.ConsumeUnit) } } func TestTxBinaryRejectsUnknownProgramEnum(t *testing.T) { txBinary := &TxBinary{ SchemaVersion: txBinarySchemaVersionCurrent, EnumVersion: txBinaryEnumVersionV1, Swaps: []SwapBinary{ {Program: "unknown_program"}, }, } if _, err := txBinary.MarshalBinary(); err == nil { t.Fatal("MarshalBinary() error = nil, want error") } } func TestTxsBinaryRoundTripWithSharedAddressTable(t *testing.T) { tx1 := Tx{ Signer: mustPubKey("So11111111111111111111111111111111111111112"), Block: 1, BlockIndex: 1, CuFee: decimal.NewFromInt(1000), CUPrice: decimal.RequireFromString("0.123456"), BeforeSolBalance: decimal.RequireFromString("1.000000000"), AfterSOLBalance: decimal.RequireFromString("0.900000000"), ComputeUnitsConsumed: 100, CuLimit: 200000, Swaps: []Swap{ { Program: SolProgramPump, Event: TxEventBuy, TxIndex: 1, InstrIdx: 0, InnerIdx: 0, Pool: mustPubKey("11111111111111111111111111111111"), BaseMint: mustPubKey("3wyAj7RtG72wM1Wv9DkYfL7RAx9X3Jx1sC6E6mN4jWeL"), QuoteMint: solana.WrappedSol, BaseTokenProgram: solana.TokenProgramID, QuoteTokenProgram: solana.TokenProgramID, Creator: mustPubKey("BPFLoader1111111111111111111111111111111111"), BaseMintDecimals: 6, QuoteMintDecimals: 9, User: mustPubKey("SysvarRent111111111111111111111111111111111"), BaseAmount: decimal.NewFromInt(10), QuoteAmount: decimal.NewFromInt(20), SwapMode: SwapModeExactIn, FixedAmount: decimal.NewFromInt(20), FixedAmountSide: SwapAmountSideQuote, FixedMint: solana.WrappedSol, LimitAmountType: SwapLimitTypeMinOut, LimitAmount: decimal.NewFromInt(9), LimitAmountSide: SwapAmountSideBase, LimitMint: mustPubKey("3wyAj7RtG72wM1Wv9DkYfL7RAx9X3Jx1sC6E6mN4jWeL"), ActualLimitAmount: decimal.NewFromInt(10), ActualLimitAmountSide: SwapAmountSideBase, SlippageBps: decimal.RequireFromString("100.2"), BaseReserve: decimal.NewFromInt(100), QuoteReserve: decimal.NewFromInt(200), UserBaseBalance: decimal.NewFromInt(1), UserQuoteBalance: decimal.NewFromInt(2), EntryContract: solana.PublicKey{}, MigrateToPool: solana.PublicKey{}, MigrateTopProgram: solana.PublicKey{}, LpMint: solana.PublicKey{}, AfterSOLBalance: decimal.RequireFromString("0.800000000"), }, }, } tx2 := tx1 tx2.Block = 2 tx2.BlockIndex = 2 tx2.CuFee = decimal.NewFromInt(2000) tx2.AfterSOLBalance = decimal.RequireFromString("0.700000000") tx2.Swaps = []Swap{tx1.Swaps[0]} tx2.Swaps[0].TxIndex = 2 tx2.Swaps[0].BaseAmount = decimal.NewFromInt(30) tx2.Swaps[0].QuoteAmount = decimal.NewFromInt(40) batchEncoded, err := EncodeTxsBinary([]Tx{tx1, tx2}) if err != nil { t.Fatalf("EncodeTxsBinary() error = %v", err) } decoded, err := DecodeTxsBinary(batchEncoded) if err != nil { t.Fatalf("DecodeTxsBinary() error = %v", err) } if len(decoded) != 2 { t.Fatalf("decoded len = %d, want 2", len(decoded)) } if decoded[0].Signer != tx1.Signer || decoded[1].Signer != tx2.Signer { t.Fatalf("decoded signer mismatch") } if decoded[0].Swaps[0].Pool != tx1.Swaps[0].Pool || decoded[1].Swaps[0].Pool != tx2.Swaps[0].Pool { t.Fatalf("decoded shared address mismatch") } single1, err := EncodeTxBinary(&tx1) if err != nil { t.Fatalf("EncodeTxBinary(tx1) error = %v", err) } single2, err := EncodeTxBinary(&tx2) if err != nil { t.Fatalf("EncodeTxBinary(tx2) error = %v", err) } if len(batchEncoded) >= len(single1)+len(single2) { t.Fatalf("batch encoded = %d, want smaller than singles sum %d", len(batchEncoded), len(single1)+len(single2)) } } func TestDecodeTxsBinaryReader(t *testing.T) { tx1 := Tx{ Signer: mustPubKey("So11111111111111111111111111111111111111112"), Block: 100, BlockIndex: 7, CuFee: decimal.NewFromInt(111), CUPrice: decimal.RequireFromString("0.123456"), BeforeSolBalance: decimal.RequireFromString("1.000000000"), AfterSOLBalance: decimal.RequireFromString("0.500000000"), ComputeUnitsConsumed: 1234, CuLimit: 250000, Swaps: []Swap{ { Program: SolProgramPump, Event: TxEventBuy, TxIndex: 3, InstrIdx: 1, InnerIdx: 2, Pool: mustPubKey("11111111111111111111111111111111"), BaseMint: mustPubKey("3wyAj7RtG72wM1Wv9DkYfL7RAx9X3Jx1sC6E6mN4jWeL"), QuoteMint: solana.WrappedSol, BaseTokenProgram: solana.TokenProgramID, QuoteTokenProgram: solana.TokenProgramID, Creator: mustPubKey("BPFLoader1111111111111111111111111111111111"), BaseMintDecimals: 6, QuoteMintDecimals: 9, User: mustPubKey("SysvarRent111111111111111111111111111111111"), BaseAmount: decimal.NewFromInt(100), QuoteAmount: decimal.NewFromInt(200), SwapMode: SwapModeExactIn, FixedAmount: decimal.NewFromInt(200), FixedAmountSide: SwapAmountSideQuote, FixedMint: solana.WrappedSol, LimitAmountType: SwapLimitTypeMinOut, LimitAmount: decimal.NewFromInt(90), LimitAmountSide: SwapAmountSideBase, LimitMint: mustPubKey("3wyAj7RtG72wM1Wv9DkYfL7RAx9X3Jx1sC6E6mN4jWeL"), ActualLimitAmount: decimal.NewFromInt(100), ActualLimitAmountSide: SwapAmountSideBase, SlippageBps: decimal.RequireFromString("99.6"), BaseReserve: decimal.NewFromInt(1000), QuoteReserve: decimal.NewFromInt(2000), UserBaseBalance: decimal.NewFromInt(10), UserQuoteBalance: decimal.NewFromInt(20), AfterSOLBalance: decimal.RequireFromString("0.400000000"), }, }, } tx2 := tx1 tx2.Block = 101 tx2.BlockIndex = 8 tx2.CuFee = decimal.NewFromInt(222) tx2.AfterSOLBalance = decimal.RequireFromString("0.300000000") tx2.Swaps = []Swap{tx1.Swaps[0]} tx2.Swaps[0].TxIndex = 4 tx2.Swaps[0].BaseAmount = decimal.NewFromInt(300) encoded, err := EncodeTxsBinary([]Tx{tx1, tx2}) if err != nil { t.Fatalf("EncodeTxsBinary() error = %v", err) } var decoded []*Tx for tx, err := range DecodeTxsBinaryReader(bytes.NewReader(encoded)) { if err != nil { t.Fatalf("DecodeTxsBinaryReader() error = %v", err) } decoded = append(decoded, tx) } if len(decoded) != 2 { t.Fatalf("decoded len = %d, want 2", len(decoded)) } if decoded[0].Signer != tx1.Signer || decoded[1].Signer != tx2.Signer { t.Fatalf("decoded signer mismatch") } if decoded[0].Block != tx1.Block || decoded[1].Block != tx2.Block { t.Fatalf("decoded block mismatch") } if decoded[0].Swaps[0].BaseAmount.Cmp(tx1.Swaps[0].BaseAmount) != 0 { t.Fatalf("decoded tx1 swap base amount = %s, want %s", decoded[0].Swaps[0].BaseAmount, tx1.Swaps[0].BaseAmount) } if decoded[1].Swaps[0].BaseAmount.Cmp(tx2.Swaps[0].BaseAmount) != 0 { t.Fatalf("decoded tx2 swap base amount = %s, want %s", decoded[1].Swaps[0].BaseAmount, tx2.Swaps[0].BaseAmount) } } func TestDecodeTxsBinaryReaderEarlyStop(t *testing.T) { tx := Tx{ Signer: mustPubKey("So11111111111111111111111111111111111111112"), Block: 1, BlockIndex: 1, CuFee: decimal.NewFromInt(1), CUPrice: decimal.RequireFromString("0.000001"), BeforeSolBalance: decimal.RequireFromString("1.000000000"), AfterSOLBalance: decimal.RequireFromString("0.999999999"), ComputeUnitsConsumed: 1, CuLimit: 1, } encoded, err := EncodeTxsBinary([]Tx{tx, tx, tx}) if err != nil { t.Fatalf("EncodeTxsBinary() error = %v", err) } count := 0 for decodedTx, err := range DecodeTxsBinaryReader(bytes.NewReader(encoded)) { if err != nil { t.Fatalf("DecodeTxsBinaryReader() error = %v", err) } if decodedTx == nil { t.Fatal("decoded tx is nil") } count++ break } if count != 1 { t.Fatalf("count = %d, want 1", count) } } func TestMergeTxsBinaryBytes(t *testing.T) { tx1 := Tx{ Signer: mustPubKey("So11111111111111111111111111111111111111112"), Block: 11, BlockIndex: 1, CuFee: decimal.NewFromInt(10), CUPrice: decimal.RequireFromString("0.000123"), BeforeSolBalance: decimal.RequireFromString("1.100000000"), AfterSOLBalance: decimal.RequireFromString("1.000000000"), ComputeUnitsConsumed: 10, CuLimit: 100, Swaps: []Swap{ { Program: SolProgramPump, Event: TxEventBuy, TxIndex: 1, Pool: mustPubKey("11111111111111111111111111111111"), BaseMint: mustPubKey("3wyAj7RtG72wM1Wv9DkYfL7RAx9X3Jx1sC6E6mN4jWeL"), QuoteMint: solana.WrappedSol, BaseTokenProgram: solana.TokenProgramID, QuoteTokenProgram: solana.TokenProgramID, Creator: mustPubKey("BPFLoader1111111111111111111111111111111111"), User: mustPubKey("SysvarRent111111111111111111111111111111111"), FixedMint: solana.WrappedSol, LimitMint: mustPubKey("3wyAj7RtG72wM1Wv9DkYfL7RAx9X3Jx1sC6E6mN4jWeL"), EntryContract: solana.PublicKey{}, MigrateToPool: solana.PublicKey{}, MigrateTopProgram: solana.PublicKey{}, LpMint: solana.PublicKey{}, }, }, } tx2 := Tx{ Signer: mustPubKey("SysvarRent111111111111111111111111111111111"), Block: 12, BlockIndex: 2, CuFee: decimal.NewFromInt(20), CUPrice: decimal.RequireFromString("0.000456"), BeforeSolBalance: decimal.RequireFromString("2.200000000"), AfterSOLBalance: decimal.RequireFromString("2.000000000"), ComputeUnitsConsumed: 20, CuLimit: 200, Swaps: []Swap{ { Program: SolProgramPump, Event: TxEventSell, TxIndex: 2, Pool: mustPubKey("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"), BaseMint: mustPubKey("4Nd1mJf8JQhRVTfJxW2YxXLNQKhPYo1JzN1u2KAPY1Hn"), QuoteMint: solana.WrappedSol, BaseTokenProgram: solana.TokenProgramID, QuoteTokenProgram: solana.TokenProgramID, Creator: mustPubKey("ComputeBudget111111111111111111111111111111"), User: mustPubKey("So11111111111111111111111111111111111111112"), FixedMint: solana.WrappedSol, LimitMint: mustPubKey("4Nd1mJf8JQhRVTfJxW2YxXLNQKhPYo1JzN1u2KAPY1Hn"), EntryContract: solana.PublicKey{}, MigrateToPool: solana.PublicKey{}, MigrateTopProgram: solana.PublicKey{}, LpMint: solana.PublicKey{}, }, }, } batch1, err := EncodeTxsBinary([]Tx{tx1}) if err != nil { t.Fatalf("EncodeTxsBinary(batch1) error = %v", err) } batch2, err := EncodeTxsBinary([]Tx{tx2}) if err != nil { t.Fatalf("EncodeTxsBinary(batch2) error = %v", err) } merged, err := MergeTxsBinaryBytes([][]byte{batch1, batch2}) if err != nil { t.Fatalf("MergeTxsBinaryBytes() error = %v", err) } var mergedBinary TxsBinary if err := mergedBinary.UnmarshalBinary(merged); err != nil { t.Fatalf("UnmarshalBinary(merged) error = %v", err) } if len(mergedBinary.Txs) != 2 { t.Fatalf("merged tx count = %d, want 2", len(mergedBinary.Txs)) } if len(mergedBinary.AddressTable) >= len(mustTxBinary(t, batch1).AddressTable)+len(mustTxBinary(t, batch2).AddressTable) { t.Fatalf("merged address table was not deduplicated") } decoded, err := DecodeTxsBinary(merged) if err != nil { t.Fatalf("DecodeTxsBinary(merged) error = %v", err) } if len(decoded) != 2 { t.Fatalf("decoded len = %d, want 2", len(decoded)) } if decoded[0].Block != tx1.Block || decoded[1].Block != tx2.Block { t.Fatalf("decoded block mismatch") } } func TestMergeTxsBinarySourcesToWriterWithConcatenatedBatches(t *testing.T) { tx1 := Tx{ Signer: mustPubKey("So11111111111111111111111111111111111111112"), Block: 21, BlockIndex: 1, CuFee: decimal.NewFromInt(1), CUPrice: decimal.RequireFromString("0.000001"), BeforeSolBalance: decimal.RequireFromString("1.000000000"), AfterSOLBalance: decimal.RequireFromString("0.900000000"), ComputeUnitsConsumed: 11, CuLimit: 111, } tx2 := tx1 tx2.Block = 22 tx2.BlockIndex = 2 tx2.Signer = mustPubKey("SysvarRent111111111111111111111111111111111") tx3 := tx1 tx3.Block = 23 tx3.BlockIndex = 3 tx3.Signer = mustPubKey("ComputeBudget111111111111111111111111111111") batch1, err := EncodeTxsBinary([]Tx{tx1}) if err != nil { t.Fatalf("EncodeTxsBinary(batch1) error = %v", err) } batch2, err := EncodeTxsBinary([]Tx{tx2}) if err != nil { t.Fatalf("EncodeTxsBinary(batch2) error = %v", err) } batch3, err := EncodeTxsBinary([]Tx{tx3}) if err != nil { t.Fatalf("EncodeTxsBinary(batch3) error = %v", err) } source1 := &testTxsBinarySource{ data: append(append([]byte{}, batch1...), batch2...), } source2 := &testTxsBinarySource{ data: batch3, } var out bytes.Buffer if err := MergeTxsBinarySourcesToWriter([]TxsBinaryReaderSource{source1, source2}, &out); err != nil { t.Fatalf("MergeTxsBinarySourcesToWriter() error = %v", err) } if source1.opens != 2 || source2.opens != 2 { t.Fatalf("source opens = (%d, %d), want (2, 2)", source1.opens, source2.opens) } decoded, err := DecodeTxsBinary(out.Bytes()) if err != nil { t.Fatalf("DecodeTxsBinary(merged) error = %v", err) } if len(decoded) != 3 { t.Fatalf("decoded len = %d, want 3", len(decoded)) } if decoded[0].Block != tx1.Block || decoded[1].Block != tx2.Block || decoded[2].Block != tx3.Block { t.Fatalf("decoded block order mismatch") } } func TestMergeTxsBinarySourcesToWriterWithBatchHeaderFuncSkip(t *testing.T) { tx1 := Tx{ Signer: mustPubKey("So11111111111111111111111111111111111111112"), Block: 31, BlockIndex: 1, CuFee: decimal.NewFromInt(1), CUPrice: decimal.RequireFromString("0.000001"), BeforeSolBalance: decimal.RequireFromString("1.000000000"), AfterSOLBalance: decimal.RequireFromString("0.900000000"), ComputeUnitsConsumed: 11, CuLimit: 111, } tx2 := tx1 tx2.Block = 32 tx2.BlockIndex = 2 tx2.Signer = mustPubKey("SysvarRent111111111111111111111111111111111") tx3 := tx1 tx3.Block = 33 tx3.BlockIndex = 3 tx3.Signer = mustPubKey("ComputeBudget111111111111111111111111111111") batch1, err := EncodeTxsBinary([]Tx{tx1}) if err != nil { t.Fatalf("EncodeTxsBinary(batch1) error = %v", err) } batch2, err := EncodeTxsBinary([]Tx{tx2}) if err != nil { t.Fatalf("EncodeTxsBinary(batch2) error = %v", err) } batch3, err := EncodeTxsBinary([]Tx{tx3}) if err != nil { t.Fatalf("EncodeTxsBinary(batch3) error = %v", err) } source := &testTxsBinarySource{ data: append( append( append([]byte{}, testBatchHeader(false)...), batch1..., ), append( append(testBatchHeader(true), batch2...), append(testBatchHeader(false), batch3...)..., )..., ), } var out bytes.Buffer err = MergeTxsBinarySourcesToWriterWithOptions( []TxsBinaryReaderSource{source}, &out, TxsBinaryMergeOptions{ BatchHeaderFunc: func(ctx *TxsBinaryBatchHeaderContext) (bool, error) { header := make([]byte, 5) if _, err := io.ReadFull(ctx.Reader, header); err != nil { return false, err } if !bytes.Equal(header[:4], []byte("BHDR")) { return false, io.ErrUnexpectedEOF } return header[4] == 1, nil }, }, ) if err != nil { t.Fatalf("MergeTxsBinarySourcesToWriterWithOptions() error = %v", err) } decoded, err := DecodeTxsBinary(out.Bytes()) if err != nil { t.Fatalf("DecodeTxsBinary(merged) error = %v", err) } if len(decoded) != 2 { t.Fatalf("decoded len = %d, want 2", len(decoded)) } if decoded[0].Block != tx1.Block || decoded[1].Block != tx3.Block { t.Fatalf("decoded block order mismatch after skip") } if source.opens != 2 { t.Fatalf("source.opens = %d, want 2", source.opens) } } func mustPubKey(value string) solana.PublicKey { return solana.MustPublicKeyFromBase58(value) } func mustTxBinary(t *testing.T, data []byte) *TxsBinary { t.Helper() var txsBinary TxsBinary if err := txsBinary.UnmarshalBinary(data); err != nil { t.Fatalf("UnmarshalBinary() error = %v", err) } return &txsBinary } type testTxsBinarySource struct { data []byte opens int } func (s *testTxsBinarySource) OpenTxsBinaryReader() (io.ReadCloser, error) { s.opens++ return io.NopCloser(bytes.NewReader(s.data)), nil } func testBatchHeader(skip bool) []byte { header := []byte("BHDR\x00") if skip { header[4] = 1 } return header }