mirror of
https://github.com/luxfi/evm.git
synced 2026-09-24 00:27:44 +00:00
228 lines
6.2 KiB
Go
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)
|
|
}
|