batch encode
This commit is contained in:
134
cmd/measure_tx_binary_block/main.go
Normal file
134
cmd/measure_tx_binary_block/main.go
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
pump_parser "github.com/thloyi/pump-parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
type blockResponse struct {
|
||||||
|
Result blockResult `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type blockResult struct {
|
||||||
|
BlockTime *int64 `json:"blockTime"`
|
||||||
|
Transactions []pump_parser.RawTx `json:"transactions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var (
|
||||||
|
filePath = flag.String("file", "", "path to getBlock payload json")
|
||||||
|
slot = flag.Uint64("slot", 0, "block slot")
|
||||||
|
swapsOnly = flag.Bool("swaps-only", false, "only include transactions with swaps > 0")
|
||||||
|
)
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if *filePath == "" || *slot == 0 {
|
||||||
|
fmt.Fprintln(os.Stderr, "usage: measure_tx_binary_block -file /path/block.json -slot 413539056")
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
raw, err := os.ReadFile(*filePath)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "read file: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var response blockResponse
|
||||||
|
if err := json.Unmarshal(raw, &response); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "unmarshal block payload: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var blockTime *uint64
|
||||||
|
if response.Result.BlockTime != nil {
|
||||||
|
bt := uint64(*response.Result.BlockTime)
|
||||||
|
blockTime = &bt
|
||||||
|
}
|
||||||
|
|
||||||
|
total := len(response.Result.Transactions)
|
||||||
|
converted := 0
|
||||||
|
parsed := 0
|
||||||
|
convertFailures := 0
|
||||||
|
parseFailures := 0
|
||||||
|
encodeFailures := 0
|
||||||
|
filteredOutNoSwaps := 0
|
||||||
|
var totalRawTxBytes int
|
||||||
|
var totalSingleEncoded int
|
||||||
|
minSingleEncoded := -1
|
||||||
|
maxSingleEncoded := 0
|
||||||
|
|
||||||
|
parsedTxs := make([]pump_parser.Tx, 0, total)
|
||||||
|
for i, rawTx := range response.Result.Transactions {
|
||||||
|
transactionJSON, err := json.Marshal(rawTx.Transaction)
|
||||||
|
if err == nil {
|
||||||
|
totalRawTxBytes += len(transactionJSON)
|
||||||
|
}
|
||||||
|
rawTx.BlockTime = 0
|
||||||
|
if blockTime != nil {
|
||||||
|
rawTx.BlockTime = int64(*blockTime)
|
||||||
|
}
|
||||||
|
rawTx.Slot = *slot
|
||||||
|
rawTx.IndexWithinBlock = int64(i)
|
||||||
|
converted++
|
||||||
|
|
||||||
|
tx, err := pump_parser.ParseRawTx(&rawTx)
|
||||||
|
if err != nil {
|
||||||
|
parseFailures++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if *swapsOnly && len(tx.Swaps) == 0 {
|
||||||
|
filteredOutNoSwaps++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parsed++
|
||||||
|
|
||||||
|
encoded, err := pump_parser.EncodeTxBinary(tx)
|
||||||
|
if err != nil {
|
||||||
|
encodeFailures++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
size := len(encoded)
|
||||||
|
totalSingleEncoded += size
|
||||||
|
if minSingleEncoded == -1 || size < minSingleEncoded {
|
||||||
|
minSingleEncoded = size
|
||||||
|
}
|
||||||
|
if size > maxSingleEncoded {
|
||||||
|
maxSingleEncoded = size
|
||||||
|
}
|
||||||
|
parsedTxs = append(parsedTxs, *tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
batchEncoded, err := pump_parser.EncodeTxsBinary(parsedTxs)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "encode txs binary: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
avgSingleEncoded := 0
|
||||||
|
if parsed > 0 {
|
||||||
|
avgSingleEncoded = totalSingleEncoded / parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("block_slot=%d\n", *slot)
|
||||||
|
fmt.Printf("payload_json_bytes=%d\n", len(raw))
|
||||||
|
fmt.Printf("transactions_total=%d\n", total)
|
||||||
|
fmt.Printf("transactions_converted=%d\n", converted)
|
||||||
|
fmt.Printf("transactions_parsed=%d\n", parsed)
|
||||||
|
fmt.Printf("transactions_filtered_no_swaps=%d\n", filteredOutNoSwaps)
|
||||||
|
fmt.Printf("convert_failures=%d\n", convertFailures)
|
||||||
|
fmt.Printf("parse_failures=%d\n", parseFailures)
|
||||||
|
fmt.Printf("encode_failures=%d\n", encodeFailures)
|
||||||
|
fmt.Printf("raw_tx_total_bytes=%d\n", totalRawTxBytes)
|
||||||
|
fmt.Printf("single_txbinary_total_bytes=%d\n", totalSingleEncoded)
|
||||||
|
fmt.Printf("single_txbinary_avg_bytes=%d\n", avgSingleEncoded)
|
||||||
|
fmt.Printf("single_txbinary_min_bytes=%d\n", minSingleEncoded)
|
||||||
|
fmt.Printf("single_txbinary_max_bytes=%d\n", maxSingleEncoded)
|
||||||
|
fmt.Printf("batch_shared_table_bytes=%d\n", len(batchEncoded))
|
||||||
|
if totalSingleEncoded > 0 {
|
||||||
|
fmt.Printf("batch_vs_single_saved_bytes=%d\n", totalSingleEncoded-len(batchEncoded))
|
||||||
|
}
|
||||||
|
}
|
||||||
12
enum.go
12
enum.go
@@ -119,9 +119,11 @@ func GetConditionByProgram(program string) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TxEventAddLP = "add"
|
TxEventAddLP = "add"
|
||||||
TxEventRemoveLP = "remove"
|
TxEventRemoveLP = "remove"
|
||||||
TxEventBuy = "buy"
|
TxEventBuy = "buy"
|
||||||
TxEventSell = "sell"
|
TxEventSell = "sell"
|
||||||
TxEventBurn = "burn"
|
TxEventBuyFailed = "buy_failed"
|
||||||
|
TxEventSellFailed = "sell_failed"
|
||||||
|
TxEventBurn = "burn"
|
||||||
)
|
)
|
||||||
|
|||||||
2100
tx_binary.go
Normal file
2100
tx_binary.go
Normal file
File diff suppressed because it is too large
Load Diff
146
tx_binary_realdata_test.go
Normal file
146
tx_binary_realdata_test.go
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
package pump_parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gagliardetto/solana-go/rpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTxBinaryRealFixtureSizes(t *testing.T) {
|
||||||
|
fixtures, err := filepath.Glob(filepath.Join("testdata", "rpc", "*.json"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("glob fixtures: %v", err)
|
||||||
|
}
|
||||||
|
if len(fixtures) == 0 {
|
||||||
|
t.Fatal("no rpc fixtures found")
|
||||||
|
}
|
||||||
|
sort.Strings(fixtures)
|
||||||
|
|
||||||
|
type sizeResult struct {
|
||||||
|
name string
|
||||||
|
swaps int
|
||||||
|
platforms int
|
||||||
|
mevAgents int
|
||||||
|
addresses int
|
||||||
|
encodedBytes int
|
||||||
|
fixtureBytes int
|
||||||
|
txBinaryBytes int
|
||||||
|
}
|
||||||
|
|
||||||
|
results := make([]sizeResult, 0, len(fixtures))
|
||||||
|
totalEncoded := 0
|
||||||
|
|
||||||
|
for _, fixture := range fixtures {
|
||||||
|
tx, rawTxBytesLen, fixtureBytesLen := mustParseRPCFixtureTxForBinarySize(t, fixture)
|
||||||
|
binaryTx, err := NewTxBinary(tx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("build tx binary fixture %s: %v", fixture, err)
|
||||||
|
}
|
||||||
|
encoded, err := binaryTx.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("encode fixture %s: %v", fixture, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := sizeResult{
|
||||||
|
name: strings.TrimSuffix(filepath.Base(fixture), filepath.Ext(fixture)),
|
||||||
|
swaps: len(tx.Swaps),
|
||||||
|
platforms: len(tx.Platform),
|
||||||
|
mevAgents: len(tx.MevAgent),
|
||||||
|
addresses: len(binaryTx.AddressTable),
|
||||||
|
encodedBytes: len(encoded),
|
||||||
|
fixtureBytes: fixtureBytesLen,
|
||||||
|
txBinaryBytes: rawTxBytesLen,
|
||||||
|
}
|
||||||
|
results = append(results, result)
|
||||||
|
totalEncoded += result.encodedBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, result := range results {
|
||||||
|
t.Logf(
|
||||||
|
"%s encoded=%dB swaps=%d platforms=%d mev=%d addresses=%d fixture_json=%dB raw_tx=%dB",
|
||||||
|
result.name,
|
||||||
|
result.encodedBytes,
|
||||||
|
result.swaps,
|
||||||
|
result.platforms,
|
||||||
|
result.mevAgents,
|
||||||
|
result.addresses,
|
||||||
|
result.fixtureBytes,
|
||||||
|
result.txBinaryBytes,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
minResult := results[0]
|
||||||
|
maxResult := results[0]
|
||||||
|
for _, result := range results[1:] {
|
||||||
|
if result.encodedBytes < minResult.encodedBytes {
|
||||||
|
minResult = result
|
||||||
|
}
|
||||||
|
if result.encodedBytes > maxResult.encodedBytes {
|
||||||
|
maxResult = result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf(
|
||||||
|
"summary fixtures=%d avg=%dB min=%dB(%s) max=%dB(%s)",
|
||||||
|
len(results),
|
||||||
|
totalEncoded/len(results),
|
||||||
|
minResult.encodedBytes,
|
||||||
|
minResult.name,
|
||||||
|
maxResult.encodedBytes,
|
||||||
|
maxResult.name,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustParseRPCFixtureTxForBinarySize(t *testing.T, fixturePath string) (*Tx, int, int) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
raw, err := os.ReadFile(fixturePath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("read fixture %s: %v", fixturePath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var response struct {
|
||||||
|
Result *rpc.GetTransactionResult `json:"result"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(raw, &response); err != nil {
|
||||||
|
t.Fatalf("unmarshal fixture %s: %v", fixturePath, err)
|
||||||
|
}
|
||||||
|
if response.Result == nil || response.Result.Transaction == nil || response.Result.Meta == nil {
|
||||||
|
t.Fatalf("fixture %s is missing transaction data", fixturePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
rawBinary := response.Result.Transaction.GetBinary()
|
||||||
|
if len(rawBinary) == 0 {
|
||||||
|
t.Fatalf("fixture %s has empty transaction bytes", fixturePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
txWithMeta := rpc.TransactionWithMeta{
|
||||||
|
Slot: response.Result.Slot,
|
||||||
|
BlockTime: response.Result.BlockTime,
|
||||||
|
Transaction: rpc.DataBytesOrJSONFromBytes(rawBinary),
|
||||||
|
Meta: response.Result.Meta,
|
||||||
|
Version: response.Result.Version,
|
||||||
|
}
|
||||||
|
|
||||||
|
var blockTime *uint64
|
||||||
|
if response.Result.BlockTime != nil {
|
||||||
|
bt := uint64(*response.Result.BlockTime)
|
||||||
|
blockTime = &bt
|
||||||
|
}
|
||||||
|
|
||||||
|
rawTx, err := FromRpcTransactionWithMeta(txWithMeta, blockTime, response.Result.Slot, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("convert fixture %s: %v", fixturePath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tx, err := ParseRawTx(rawTx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parse fixture %s: %v", fixturePath, err)
|
||||||
|
}
|
||||||
|
return tx, len(rawBinary), len(raw)
|
||||||
|
}
|
||||||
627
tx_binary_test.go
Normal file
627
tx_binary_test.go
Normal file
@@ -0,0 +1,627 @@
|
|||||||
|
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 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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user