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 }