Files
evm/predicate/predicate_results.go
2026-08-21 11:51:32 -07:00

228 lines
6.2 KiB
Go

// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package predicate
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"sort"
"strings"
"github.com/luxfi/constants"
"github.com/luxfi/evm/plugin/evm/upgrade/feewindow"
"github.com/luxfi/geth/common"
)
const (
Version = uint16(0)
MaxResultsSize = constants.MiB
hashLen = 32 // common.Hash
addrLen = 20 // common.Address
)
var (
errShortBuffer = errors.New("predicate: short buffer")
errBadVersion = errors.New("predicate: invalid version")
errTooLarge = errors.New("predicate: results exceed MaxResultsSize")
)
// TxResults is a map of results for each precompile address to the resulting byte array.
type TxResults map[common.Address][]byte
// Results encodes the precompile predicate results included in a block on a per transaction basis.
// Results is not thread-safe.
type Results struct {
Results map[common.Hash]TxResults
}
func (r Results) GetPredicateResults(txHash common.Hash, address common.Address) []byte {
results, ok := r.Results[txHash]
if !ok {
return nil
}
return results[address]
}
// NewResults returns an empty predicate results.
func NewResults() *Results {
return &Results{
Results: make(map[common.Hash]TxResults),
}
}
func NewResultsFromMap(results map[common.Hash]TxResults) *Results {
return &Results{
Results: results,
}
}
// ParseResults parses [b] into predicate results.
//
// Wire format (big-endian throughout):
//
// u16 version
// u32 outer_count
// per outer: 32B txHash, u32 inner_count
// per inner: 20B address, u32 bytes_len, bytes
//
// Hand-rolled binary, no codec.Manager dependency. Forward-only —
// any incompatible schema change requires a Version bump.
func ParseResults(b []byte) (*Results, error) {
if len(b) < 2 {
return nil, errShortBuffer
}
if uint64(len(b)) > uint64(MaxResultsSize) {
return nil, errTooLarge
}
ver := binary.BigEndian.Uint16(b[0:2])
if ver != Version {
return nil, fmt.Errorf("%w: got %d want %d", errBadVersion, ver, Version)
}
if len(b) < 6 {
return nil, errShortBuffer
}
outerN := binary.BigEndian.Uint32(b[2:6])
off := uint64(6)
res := &Results{Results: make(map[common.Hash]TxResults, outerN)}
for i := uint32(0); i < outerN; i++ {
if uint64(len(b)) < off+uint64(hashLen)+4 {
return nil, errShortBuffer
}
var hash common.Hash
copy(hash[:], b[off:off+hashLen])
off += hashLen
innerN := binary.BigEndian.Uint32(b[off : off+4])
off += 4
txRes := make(TxResults, innerN)
for j := uint32(0); j < innerN; j++ {
if uint64(len(b)) < off+uint64(addrLen)+4 {
return nil, errShortBuffer
}
var addr common.Address
copy(addr[:], b[off:off+addrLen])
off += addrLen
bytesLen := binary.BigEndian.Uint32(b[off : off+4])
off += 4
if uint64(len(b)) < off+uint64(bytesLen) {
return nil, errShortBuffer
}
val := make([]byte, bytesLen)
copy(val, b[off:off+uint64(bytesLen)])
off += uint64(bytesLen)
txRes[addr] = val
}
res.Results[hash] = txRes
}
return res, nil
}
// GetResults returns the byte array results for [txHash] from precompile [address] if available.
func (r *Results) GetResults(txHash common.Hash, address common.Address) []byte {
txResults, ok := r.Results[txHash]
if !ok {
return nil
}
return txResults[address]
}
// SetTxResults sets the predicate results for the given [txHash]. Overrides results if present.
func (r *Results) SetTxResults(txHash common.Hash, txResults TxResults) {
// If there are no tx results, don't store an entry in the map
if len(txResults) == 0 {
delete(r.Results, txHash)
return
}
r.Results[txHash] = txResults
}
// DeleteTxResults deletes the predicate results for the given [txHash].
func (r *Results) DeleteTxResults(txHash common.Hash) {
delete(r.Results, txHash)
}
// Bytes marshals the current state of predicate results.
func (r *Results) Bytes() ([]byte, error) {
// pre-size: 2 (ver) + 4 (outer) + per-entry overhead.
size := uint64(6)
for _, inner := range r.Results {
size += hashLen + 4
for _, v := range inner {
size += addrLen + 4 + uint64(len(v))
}
}
if size > uint64(MaxResultsSize) {
return nil, errTooLarge
}
out := make([]byte, size)
binary.BigEndian.PutUint16(out[0:2], Version)
binary.BigEndian.PutUint32(out[2:6], uint32(len(r.Results)))
off := uint64(6)
// Sort outer keys for deterministic wire output (matches legacy
// linearcodec map-entry sort behavior — load-bearing for block
// hash determinism across validators).
hashes := make([]common.Hash, 0, len(r.Results))
for h := range r.Results {
hashes = append(hashes, h)
}
sort.Slice(hashes, func(i, j int) bool {
return bytes.Compare(hashes[i][:], hashes[j][:]) < 0
})
for _, hash := range hashes {
inner := r.Results[hash]
copy(out[off:off+hashLen], hash[:])
off += hashLen
binary.BigEndian.PutUint32(out[off:off+4], uint32(len(inner)))
off += 4
addrs := make([]common.Address, 0, len(inner))
for a := range inner {
addrs = append(addrs, a)
}
sort.Slice(addrs, func(i, j int) bool {
return bytes.Compare(addrs[i][:], addrs[j][:]) < 0
})
for _, addr := range addrs {
val := inner[addr]
copy(out[off:off+addrLen], addr[:])
off += addrLen
binary.BigEndian.PutUint32(out[off:off+4], uint32(len(val)))
off += 4
copy(out[off:off+uint64(len(val))], val)
off += uint64(len(val))
}
}
return out, nil
}
func (r *Results) String() string {
sb := strings.Builder{}
sb.WriteString(fmt.Sprintf("PredicateResults: (Size = %d)", len(r.Results)))
for txHash, results := range r.Results {
for address, result := range results {
sb.WriteString(fmt.Sprintf("\n%s %s: %x", txHash, address, result))
}
}
return sb.String()
}
// ParseResultsFromHeaderExtra parses predicate results from a block header's extra data.
// The extra data format is: [windowData (WindowSize bytes)][predicateResultsData...]
// Returns nil if there are no predicate results (extra data <= WindowSize).
func ParseResultsFromHeaderExtra(extra []byte) (*Results, error) {
// Check if extra data has predicate results beyond the window
if len(extra) <= feewindow.WindowSize {
return nil, nil
}
predicateBytes := extra[feewindow.WindowSize:]
return ParseResults(predicateBytes)
}