273 lines
9.1 KiB
Go
273 lines
9.1 KiB
Go
package shreder
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
"time"
|
|
)
|
|
|
|
type wrapperReader struct {
|
|
io.Reader
|
|
}
|
|
|
|
func (wr *wrapperReader) ReadU64() (uint64, error) {
|
|
var buf [8]byte
|
|
_, err := io.ReadFull(wr, buf[:])
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return uint64(buf[0]) | uint64(buf[1])<<8 | uint64(buf[2])<<16 | uint64(buf[3])<<24 |
|
|
uint64(buf[4])<<32 | uint64(buf[5])<<40 | uint64(buf[6])<<48 | uint64(buf[7])<<56, nil
|
|
}
|
|
|
|
func (wr *wrapperReader) Skip(n int) error {
|
|
_, err := io.CopyN(io.Discard, wr, int64(n))
|
|
return err
|
|
}
|
|
|
|
func (wr *wrapperReader) ReadCompactU16() (uint16, error) {
|
|
ln := 0
|
|
size := 0
|
|
var buf [1]byte
|
|
for i := 0; i < 3; i++ {
|
|
_, err := io.ReadFull(wr, buf[:])
|
|
if err != nil {
|
|
return 0, fmt.Errorf("unable to decode compact u16 at %d: %w", i, err)
|
|
}
|
|
elem := int(buf[0])
|
|
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
|
|
}
|
|
}
|
|
return uint16(ln), nil
|
|
}
|
|
|
|
func (wr *wrapperReader) ReadByte() (uint8, error) {
|
|
var buf [1]byte
|
|
_, err := io.ReadFull(wr, buf[:])
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return buf[0], nil
|
|
}
|
|
|
|
func ResizeSlice[T any](slice []T, newSize int) []T {
|
|
if cap(slice) < newSize {
|
|
slice = append(slice, make([]T, newSize-len(slice))...)
|
|
}
|
|
return slice[:newSize]
|
|
}
|
|
|
|
// entriesToVersionedTransaction converts raw entry bytes to versioned transactions.
|
|
func entriesToVersionedTransaction(slot uint64, data io.Reader, callback func(tx VersionedTransaction)) error {
|
|
b := &wrapperReader{data}
|
|
var entriesNumBuf [8]byte
|
|
n, err := io.ReadFull(b, entriesNumBuf[:])
|
|
if err != nil {
|
|
if err == io.EOF && n == 0 {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("unable to read entries num: %w", err)
|
|
}
|
|
entriesNum := binary.LittleEndian.Uint64(entriesNumBuf[:])
|
|
//if entriesNum == 0 {
|
|
// return nil, nil
|
|
//}
|
|
if entriesNum > 2048 {
|
|
return fmt.Errorf("entries num is too large: %d > %d", entriesNum, 2048)
|
|
}
|
|
|
|
for i := uint64(0); i < entriesNum; i++ {
|
|
err = b.Skip(40)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to skip num_hashes + hash of entry %d: %w", i, err)
|
|
}
|
|
numTx, err := b.ReadU64()
|
|
if err != nil {
|
|
return 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 fmt.Errorf("failed to read numSignatures in entry %d, txn %d: %w", i, j, err)
|
|
}
|
|
|
|
// enforce a maximum number of signatures to prevent OOM
|
|
if numSignatures > 32 {
|
|
return fmt.Errorf("numSignatures %d exceeds maximum in entry %d, txn %d", numSignatures, i, j)
|
|
}
|
|
if numSignatures == 0 {
|
|
return fmt.Errorf("numSignatures is zero in entry %d, txn %d", i, j)
|
|
}
|
|
|
|
versioned := VersionedTransaction{}
|
|
versioned.Block = slot
|
|
versioned.Time = time.Now()
|
|
versioned.Signatures = ResizeSlice(versioned.Signatures, int(numSignatures))
|
|
for k := 0; k < int(numSignatures); k++ {
|
|
_, err = io.ReadFull(b, versioned.Signatures[k][:])
|
|
if err != nil {
|
|
return fmt.Errorf("unable to read signature in entry %d, txn %d, sig: %d, %w", i, j, k, err)
|
|
}
|
|
}
|
|
|
|
msgVersion, err := b.ReadByte()
|
|
if err != nil {
|
|
return 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 := 2
|
|
if !legacy {
|
|
headerSkip = 3
|
|
}
|
|
// skip msg version, mx.Header+3
|
|
|
|
err = b.Skip(headerSkip)
|
|
if err != nil {
|
|
return 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 fmt.Errorf("unable to decode numAccountKeys in entry %d, txn %d: %w", i, j, err)
|
|
}
|
|
// enforce a maximum number of account keys to prevent OOM
|
|
if numAccountKeys > 255 {
|
|
return fmt.Errorf("numAccountKeys %d exceeds maximum in entry %d, txn %d", numAccountKeys, i, j)
|
|
}
|
|
|
|
versioned.StaticAccountKeys = ResizeSlice(versioned.StaticAccountKeys, int(numAccountKeys))
|
|
|
|
for k := 0; k < int(numAccountKeys); k++ {
|
|
_, err = io.ReadFull(b, versioned.StaticAccountKeys[k][:])
|
|
if err != nil {
|
|
return fmt.Errorf("unable to read accountKey[%d] in entry %d, txn %d: %w", k, i, j, err)
|
|
}
|
|
}
|
|
|
|
//skip solana hash
|
|
err = b.Skip(32)
|
|
if err != nil {
|
|
return 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 fmt.Errorf("unable to decode numInstructions in entry %d, txn %d: %w", i, j, err)
|
|
}
|
|
|
|
// enforce a maximum number of instructions to prevent OOM
|
|
if numInstructions >= 256 {
|
|
return fmt.Errorf("numInstructions %d exceeds maximum in entry %d, txn %d, txHash: %s", numInstructions, i, j, versioned.GetSignature())
|
|
}
|
|
versioned.Instructions = ResizeSlice(versioned.Instructions, int(numInstructions))
|
|
for k := 0; k < int(numInstructions); k++ {
|
|
versioned.Instructions[k].ProgramIDIndex, err = b.ReadByte()
|
|
if err != nil {
|
|
return 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 fmt.Errorf("unable to decode numAccounts for ix[%d] in entry %d, txn %d: %w", k, i, j, err)
|
|
}
|
|
|
|
// enforce a maximum number of accounts to prevent OOM
|
|
if numAccounts >= 256 {
|
|
return fmt.Errorf("numAccounts %d exceeds maximum for ix[%d] in entry %d, txn %d", numAccounts, k, i, j)
|
|
}
|
|
versioned.Instructions[k].Accounts = ResizeSlice(versioned.Instructions[k].Accounts, int(numAccounts))
|
|
|
|
//.AccountsLen = int(numAccounts)
|
|
if numAccounts != 0 {
|
|
_, err = io.ReadFull(b, versioned.Instructions[k].Accounts)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to read mx.Instructions[%d].Accounts in entry %d, txn %d: %w", k, i, j, err)
|
|
}
|
|
}
|
|
dataLen, err := b.ReadCompactU16()
|
|
if err != nil {
|
|
return fmt.Errorf("unable to decode mx.Instructions[%d].Data length in entry %d, txn %d: %w", k, i, j, err)
|
|
}
|
|
// enforce a maximum data length to prevent OOM
|
|
if dataLen > 2048 {
|
|
return fmt.Errorf("mx.Instructions[%d].Data length %d exceeds maximum in entry %d, txn %d, txHash: %s", k, dataLen, i, j, versioned.GetSignature())
|
|
}
|
|
versioned.Instructions[k].Data = ResizeSlice(versioned.Instructions[k].Data, int(dataLen))
|
|
if dataLen > 0 {
|
|
_, err = io.ReadFull(b, versioned.Instructions[k].Data)
|
|
if err != nil {
|
|
return 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.ReadByte()
|
|
if err != nil {
|
|
return fmt.Errorf("unable to read numAddressTableLookups in entry %d, txn %d: %w", i, j, err)
|
|
}
|
|
if numLookups >= 32 {
|
|
return fmt.Errorf("numLookups %d exceeds maximum in entry %d, txn %d", numLookups, i, j)
|
|
}
|
|
versioned.AddressTableLookups = ResizeSlice(versioned.AddressTableLookups, int(numLookups))
|
|
for k := uint8(0); k < numLookups; k++ {
|
|
_, err = io.ReadFull(b, versioned.AddressTableLookups[k].AccountKey[:])
|
|
if err != nil {
|
|
return 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 fmt.Errorf("unable to decode numWritableIndexes for lookup[%d] in entry %d, txn %d: %w", k, i, j, err)
|
|
}
|
|
// enforce a maximum number of writable indexes to prevent OOM
|
|
if numWritable >= 256 {
|
|
return fmt.Errorf("numWritableIndexes %d exceeds maximum for lookup[%d] in entry %d, txn %d", numWritable, k, i, j)
|
|
}
|
|
versioned.AddressTableLookups[k].WritableIndexes = ResizeSlice(versioned.AddressTableLookups[k].WritableIndexes, int(numWritable))
|
|
if numWritable > 0 {
|
|
_, err = io.ReadFull(b, versioned.AddressTableLookups[k].WritableIndexes)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to read writableIndexes for lookup[%d] in entry %d, txn %d: %w", k, i, j, err)
|
|
}
|
|
}
|
|
|
|
numReadonly, err := b.ReadCompactU16()
|
|
if err != nil {
|
|
return fmt.Errorf("unable to decode numReadonlyIndexes for lookup[%d] in entry %d, txn %d: %w", k, i, j, err)
|
|
}
|
|
// enforce a maximum number of readonly indexes to prevent OOM
|
|
if numReadonly > 256 {
|
|
return fmt.Errorf("numReadonlyIndexes %d exceeds maximum for lookup[%d] in entry %d, txn %d", numReadonly, k, i, j)
|
|
}
|
|
versioned.AddressTableLookups[k].ReadonlyIndexes = ResizeSlice(versioned.AddressTableLookups[k].ReadonlyIndexes, int(numReadonly))
|
|
if numReadonly > 0 {
|
|
_, err = io.ReadFull(b, versioned.AddressTableLookups[k].ReadonlyIndexes)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to read readonlyIndexes for lookup[%d] in entry %d, txn %d: %w", k, i, j, err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
callback(versioned)
|
|
}
|
|
}
|
|
return nil
|
|
}
|