335 lines
10 KiB
Go
335 lines
10 KiB
Go
|
|
package shreder
|
||
|
|
|
||
|
|
import (
|
||
|
|
"encoding/binary"
|
||
|
|
"fmt"
|
||
|
|
"io"
|
||
|
|
)
|
||
|
|
|
||
|
|
type constArray struct {
|
||
|
|
data []byte
|
||
|
|
size int
|
||
|
|
|
||
|
|
offset int
|
||
|
|
}
|
||
|
|
|
||
|
|
func newConstArray(data []byte) constArray {
|
||
|
|
return constArray{
|
||
|
|
data: data,
|
||
|
|
size: len(data),
|
||
|
|
offset: 0,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c *constArray) Len() int {
|
||
|
|
return c.size
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c *constArray) Offset() int {
|
||
|
|
return c.offset
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c *constArray) Read(cap int) ([]byte, error) {
|
||
|
|
if c.offset+cap > c.size {
|
||
|
|
return nil, io.EOF
|
||
|
|
}
|
||
|
|
c.offset += cap
|
||
|
|
return c.data[c.offset-cap : c.offset], nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c *constArray) ReadBytes() (byte, error) {
|
||
|
|
if c.offset >= c.size {
|
||
|
|
return 0, io.EOF
|
||
|
|
}
|
||
|
|
c.offset++
|
||
|
|
return c.data[c.offset-1], nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c *constArray) PeekBytes() (byte, error) {
|
||
|
|
if c.offset >= c.size {
|
||
|
|
return 0, io.EOF
|
||
|
|
}
|
||
|
|
return c.data[c.offset], nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c *constArray) ReadU64() (uint64, error) {
|
||
|
|
if c.offset+8 > c.size {
|
||
|
|
return 0, io.EOF
|
||
|
|
}
|
||
|
|
c.offset += 8
|
||
|
|
return binary.LittleEndian.Uint64(c.data[c.offset-8 : c.offset]), nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c *constArray) ReadCompactU16() (uint16, error) {
|
||
|
|
ln := 0
|
||
|
|
size := 0
|
||
|
|
for i := 0; i < 3; i++ {
|
||
|
|
if len(c.data[c.offset:]) == 0 {
|
||
|
|
return 0, fmt.Errorf("unable to decode compact u16 at %d: zero byte", i)
|
||
|
|
}
|
||
|
|
elem := int(c.data[c.offset+i])
|
||
|
|
if elem == 0 && i != 0 {
|
||
|
|
return 0, fmt.Errorf("alias")
|
||
|
|
}
|
||
|
|
if i == 2 && (elem&0x80) != 0 {
|
||
|
|
return 0, fmt.Errorf("byte three continues")
|
||
|
|
}
|
||
|
|
ln |= (elem & 0x7f) << (size * 7)
|
||
|
|
size++
|
||
|
|
if (elem & 0x80) == 0 {
|
||
|
|
break
|
||
|
|
}
|
||
|
|
}
|
||
|
|
c.offset += size
|
||
|
|
return uint16(ln), nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c *constArray) Skip(size int) error {
|
||
|
|
if c.offset+size > c.size {
|
||
|
|
return io.EOF
|
||
|
|
}
|
||
|
|
c.offset += size
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// entriesToVersionedTransaction converts raw entry bytes to versioned transactions.
|
||
|
|
func entriesToVersionedTransaction(slot uint64, b constArray) ([]*versionedTransaction, error) {
|
||
|
|
b.offset = 0
|
||
|
|
|
||
|
|
entriesNum, err := b.ReadU64()
|
||
|
|
if err != nil {
|
||
|
|
return nil, fmt.Errorf("unable to read entries num: %w", err)
|
||
|
|
}
|
||
|
|
if entriesNum == 0 {
|
||
|
|
return nil, nil
|
||
|
|
}
|
||
|
|
//preCap := b.Len() / 256
|
||
|
|
//if preCap < int(entriesNum) {
|
||
|
|
// preCap = int(entriesNum)
|
||
|
|
//}
|
||
|
|
vs := make([]*versionedTransaction, 0, entriesNum)
|
||
|
|
|
||
|
|
// logger.Debug("parsing entries", "count", entriesNum, "data len", b.Len(), "data", base64.StdEncoding.EncodeToString(b.data))
|
||
|
|
for i := uint64(0); i < entriesNum; i++ {
|
||
|
|
err = b.Skip(40)
|
||
|
|
if err != nil {
|
||
|
|
return vs, fmt.Errorf("failed to skip num_hashes + hash of entry %d: %w", i, err)
|
||
|
|
}
|
||
|
|
numTx, err := b.ReadU64()
|
||
|
|
if err != nil {
|
||
|
|
return vs, fmt.Errorf("failed to read num_transactions of entry %d: %w", i, err)
|
||
|
|
}
|
||
|
|
for j := 0; j < int(numTx); j++ {
|
||
|
|
numSignatures, err := b.ReadCompactU16()
|
||
|
|
if err != nil {
|
||
|
|
return vs, fmt.Errorf("failed to read numSignatures in entry %d, txn %d: %w", i, j, err)
|
||
|
|
}
|
||
|
|
|
||
|
|
// todo : enforce a maximum number of signatures to prevent OOM
|
||
|
|
if numSignatures > 16 {
|
||
|
|
return vs, fmt.Errorf("numSignatures %d exceeds maximum in entry %d, txn %d", numSignatures, i, j)
|
||
|
|
}
|
||
|
|
if numSignatures == 0 {
|
||
|
|
return vs, fmt.Errorf("numSignatures is zero in entry %d, txn %d", i, j)
|
||
|
|
}
|
||
|
|
|
||
|
|
versioned := requireVersionedPool() // get a versioned transaction from the pool
|
||
|
|
vs = append(vs, versioned)
|
||
|
|
versioned.block = slot
|
||
|
|
versioned.bindArray = b.data
|
||
|
|
versioned.signatures = int(numSignatures)
|
||
|
|
versioned.signaturesOffset = b.Offset()
|
||
|
|
err = b.Skip(64 * int(numSignatures))
|
||
|
|
if err != nil {
|
||
|
|
return vs, fmt.Errorf("unable to read signature in entry %d, txn %d: %w", i, j, err)
|
||
|
|
}
|
||
|
|
|
||
|
|
msgVersion, err := b.PeekBytes()
|
||
|
|
if err != nil {
|
||
|
|
return vs, fmt.Errorf("unable to read message version in entry %d, txn %d: %w", i, j, err)
|
||
|
|
}
|
||
|
|
msgVersion = msgVersion & 0x80 >> 7 // mask to get only the version bits
|
||
|
|
legacy := msgVersion == 0
|
||
|
|
headerSkip := 3
|
||
|
|
if !legacy {
|
||
|
|
headerSkip = 4
|
||
|
|
}
|
||
|
|
// skip msg version, mx.Header+3
|
||
|
|
|
||
|
|
err = b.Skip(headerSkip)
|
||
|
|
if err != nil {
|
||
|
|
return vs, fmt.Errorf("unable to skip message header in entry %d, txn %d: %w", i, j, err)
|
||
|
|
}
|
||
|
|
|
||
|
|
// read mx.AccountKeys
|
||
|
|
// _, err = r.Read(u16[:])
|
||
|
|
|
||
|
|
numAccountKeys, err := b.ReadCompactU16()
|
||
|
|
// logger.Info("tx", "hash", versioned.Signatures[0].String(), "version", msgVersion)
|
||
|
|
if err != nil {
|
||
|
|
return vs, fmt.Errorf("unable to decode numAccountKeys in entry %d, txn %d: %w", i, j, err)
|
||
|
|
}
|
||
|
|
// todo : enforce a maximum number of account keys to prevent OOM
|
||
|
|
if numAccountKeys > 255 {
|
||
|
|
return vs, fmt.Errorf("numAccountKeys %d exceeds maximum in entry %d, txn %d", numAccountKeys, i, j)
|
||
|
|
}
|
||
|
|
versioned.staticAccountKeys = uint8(numAccountKeys)
|
||
|
|
|
||
|
|
versioned.staticAccountKeysOffset = b.Offset()
|
||
|
|
err = b.Skip(32 * int(numAccountKeys))
|
||
|
|
if err != nil {
|
||
|
|
return vs, fmt.Errorf("unable to read accountKey in entry %d, txn %d: %w", i, j, err)
|
||
|
|
}
|
||
|
|
|
||
|
|
//skip solana hash
|
||
|
|
err = b.Skip(32)
|
||
|
|
if err != nil {
|
||
|
|
return vs, fmt.Errorf("unable to skip recentBlockhash in entry %d, txn %d: %w", i, j, err)
|
||
|
|
}
|
||
|
|
|
||
|
|
// read mx.Instructions
|
||
|
|
numInstructions, err := b.ReadCompactU16()
|
||
|
|
if err != nil {
|
||
|
|
return vs, fmt.Errorf("unable to decode numInstructions in entry %d, txn %d: %w", i, j, err)
|
||
|
|
}
|
||
|
|
|
||
|
|
// todo : enforce a maximum number of instructions to prevent OOM
|
||
|
|
if numInstructions >= 256 {
|
||
|
|
return vs, fmt.Errorf("numInstructions %d exceeds maximum in entry %d, txn %d, txHash: %s", numInstructions, i, j, versioned.Signatures())
|
||
|
|
}
|
||
|
|
versioned.instructions = int(numInstructions)
|
||
|
|
if cap(versioned.Instrs) < int(numInstructions) {
|
||
|
|
versioned.Instrs = make([]compiledInstruction, numInstructions)
|
||
|
|
} else {
|
||
|
|
versioned.Instrs = versioned.Instrs[:numInstructions]
|
||
|
|
}
|
||
|
|
for k := 0; k < int(numInstructions); k++ {
|
||
|
|
versioned.Instrs[k].ProgramIDIndex, err = b.ReadBytes()
|
||
|
|
if err != nil {
|
||
|
|
return vs, fmt.Errorf("unable to read mx.Instructions[%d].ProgramIDIndex in entry %d, txn %d: %w", k, i, j, err)
|
||
|
|
}
|
||
|
|
numAccounts, err := b.ReadCompactU16()
|
||
|
|
if err != nil {
|
||
|
|
return vs, fmt.Errorf("unable to decode numAccounts for ix[%d] in entry %d, txn %d: %w", k, i, j, err)
|
||
|
|
}
|
||
|
|
|
||
|
|
// todo : enforce a maximum number of accounts to prevent OOM
|
||
|
|
if numAccounts >= 256 {
|
||
|
|
return vs, fmt.Errorf("numAccounts %d exceeds maximum for ix[%d] in entry %d, txn %d", numAccounts, k, i, j)
|
||
|
|
}
|
||
|
|
versioned.Instrs[k].AccountsLen = int(numAccounts)
|
||
|
|
if numAccounts != 0 {
|
||
|
|
versioned.Instrs[k].AccountsOffset = b.Offset()
|
||
|
|
err = b.Skip(int(numAccounts))
|
||
|
|
if err != nil {
|
||
|
|
return vs, fmt.Errorf("unable to read mx.Instructions[%d].Accounts in entry %d, txn %d: %w", k, i, j, err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// _, err = r.Read(u16[:])
|
||
|
|
dataLen, err := b.ReadCompactU16()
|
||
|
|
if err != nil {
|
||
|
|
return vs, fmt.Errorf("unable to decode mx.Instructions[%d].Data length in entry %d, txn %d: %w", k, i, j, err)
|
||
|
|
}
|
||
|
|
//todo : enforce a maximum data length to prevent OOM
|
||
|
|
if dataLen > 2048 {
|
||
|
|
return vs, fmt.Errorf("mx.Instructions[%d].Data length %d exceeds maximum in entry %d, txn %d, txHash: %s", k, dataLen, i, j, versioned.Signatures())
|
||
|
|
}
|
||
|
|
versioned.Instrs[k].DataLen = int(dataLen)
|
||
|
|
if dataLen > 0 {
|
||
|
|
versioned.Instrs[k].DataOffset = b.Offset()
|
||
|
|
err = b.Skip(int(dataLen))
|
||
|
|
if err != nil {
|
||
|
|
return vs, fmt.Errorf("unable to read mx.Instructions[%d].Data in entry %d, txn %d: %w", k, i, j, err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if !legacy {
|
||
|
|
// read mx.AddressTableLookups
|
||
|
|
numLookups, err := b.ReadBytes()
|
||
|
|
if err != nil {
|
||
|
|
return vs, fmt.Errorf("unable to read numAddressTableLookups in entry %d, txn %d: %w", i, j, err)
|
||
|
|
}
|
||
|
|
|
||
|
|
if cap(versioned.ATL) < int(numLookups) {
|
||
|
|
versioned.ATL = make([]addressTableLookup, numLookups)
|
||
|
|
} else {
|
||
|
|
versioned.ATL = versioned.ATL[:numLookups]
|
||
|
|
}
|
||
|
|
versioned.addressTableLookups = int(numLookups)
|
||
|
|
|
||
|
|
for k := uint8(0); k < numLookups; k++ {
|
||
|
|
versioned.ATL[k].AccountKeyOffset = b.Offset()
|
||
|
|
err = b.Skip(32)
|
||
|
|
if err != nil {
|
||
|
|
return vs, fmt.Errorf("unable to read address table account key for lookup[%d] in entry %d, txn %d: %w", k, i, j, err)
|
||
|
|
}
|
||
|
|
numWritable, err := b.ReadCompactU16()
|
||
|
|
if err != nil {
|
||
|
|
return vs, fmt.Errorf("unable to decode numWritableIndexes for lookup[%d] in entry %d, txn %d: %w", k, i, j, err)
|
||
|
|
}
|
||
|
|
// todo : enforce a maximum number of writable indexes to prevent OOM
|
||
|
|
if numWritable >= 256 {
|
||
|
|
return vs, fmt.Errorf("numWritableIndexes %d exceeds maximum for lookup[%d] in entry %d, txn %d", numWritable, k, i, j)
|
||
|
|
}
|
||
|
|
versioned.ATL[k].WriteLen = int(numWritable)
|
||
|
|
if numWritable > 0 {
|
||
|
|
versioned.ATL[k].WriteOffset = b.Offset()
|
||
|
|
err = b.Skip(int(numWritable))
|
||
|
|
if err != nil {
|
||
|
|
return vs, fmt.Errorf("unable to read writableIndexes for lookup[%d] in entry %d, txn %d: %w", k, i, j, err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// _, err = r.Read(u16[:])
|
||
|
|
|
||
|
|
numReadonly, err := b.ReadCompactU16()
|
||
|
|
if err != nil {
|
||
|
|
return vs, fmt.Errorf("unable to decode numReadonlyIndexes for lookup[%d] in entry %d, txn %d: %w", k, i, j, err)
|
||
|
|
}
|
||
|
|
// todo : enforce a maximum number of readonly indexes to prevent OOM
|
||
|
|
if numReadonly > 256 {
|
||
|
|
return vs, fmt.Errorf("numReadonlyIndexes %d exceeds maximum for lookup[%d] in entry %d, txn %d", numReadonly, k, i, j)
|
||
|
|
}
|
||
|
|
versioned.ATL[k].ReadLen = int(numReadonly)
|
||
|
|
|
||
|
|
if numReadonly > 0 {
|
||
|
|
versioned.ATL[k].ReadOffset = b.Offset()
|
||
|
|
err = b.Skip(int(numReadonly))
|
||
|
|
if err != nil {
|
||
|
|
return vs, fmt.Errorf("unable to read readonlyIndexes for lookup[%d] in entry %d, txn %d: %w", k, i, j, err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// logger.Debug("parsing num_transactions of entry", "slot", slot, "count", entriesNum, "data len", b.Len(), "num_tx", uint32(len(vs)))
|
||
|
|
|
||
|
|
//logger.Debug("parsing entries", "slot", slot, "count", entriesNum, "data len", b.Len(), "txns", len(vs))
|
||
|
|
return vs, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func decodeCompactU16(b []byte) (int, uint16, error) {
|
||
|
|
ln := 0
|
||
|
|
size := 0
|
||
|
|
for i := 0; i < 3; i++ {
|
||
|
|
if len(b) == 0 {
|
||
|
|
return 0, 0, fmt.Errorf("unable to decode compact u16 at %d: zero byte", i)
|
||
|
|
}
|
||
|
|
elem := int(b[0])
|
||
|
|
b = b[1:]
|
||
|
|
if elem == 0 && i != 0 {
|
||
|
|
return 0, 0, fmt.Errorf("alias")
|
||
|
|
}
|
||
|
|
if i == 2 && (elem&0x80) != 0 {
|
||
|
|
return 0, 0, fmt.Errorf("byte three continues")
|
||
|
|
}
|
||
|
|
ln |= (elem & 0x7f) << (size * 7)
|
||
|
|
size++
|
||
|
|
if (elem & 0x80) == 0 {
|
||
|
|
break
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return size, uint16(ln), nil
|
||
|
|
}
|