Files
coreth/predicate/predicate_results.go

207 lines
5.3 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/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.
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 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. Sorted by hash
// then address for deterministic output across validators.
func (r *Results) Bytes() ([]byte, error) {
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)
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()
}