Skip to content

Commit 4f5acfc

Browse files
JonathanOppenheimerCopilotStephenButtolphjoshua-kim
authored
Migrate predicate package from evm repos (#4147)
Signed-off-by: Jonathan Oppenheimer <147infiniti@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Stephen Buttolph <stephen@avalabs.org> Co-authored-by: Joshua Kim <20001595+joshua-kim@users.noreply.github.com>
1 parent 335e79f commit 4f5acfc

File tree

5 files changed

+1033
-0
lines changed

5 files changed

+1033
-0
lines changed

vms/evm/predicate/README.md

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# Predicate
2+
3+
This package contains the predicate data structure and its encoding and helper functions to unpack/pack the data structure.
4+
5+
## Encoding
6+
7+
A byte slice of size N is encoded as:
8+
9+
1. Slice of N bytes
10+
2. Delimiter byte `0xff`
11+
3. Appended 0s to the nearest multiple of 32 bytes
12+
13+
14+
## Results
15+
16+
This defines how to encode `PredicateResults` within the block header's `Extra` data field.
17+
18+
For more information on the motivation for encoding the results of predicate verification within a block, see [here](../../../vms/platformvm/warp/README.md#processing-historical-avalanche-interchain-messages).
19+
20+
### Serialization
21+
22+
Results have a maximum size of 1MB enforced by the codec. The actual size depends on how much data the Precompile predicates may put into the results, the gas cost they charge, and the block gas limit. PredicateResults are encoded using the AvalancheGo codec, which serializes a map by serializing the length of the map as a `uint32` and then serializes each key-value pair sequentially.
23+
24+
PredicateResults:
25+
26+
| Field | Type | Size |
27+
|-------|------|------|
28+
| codecID | uint16 | 2 bytes |
29+
| results | map[[32]byte]TxPredicateResults | 4 + size(results) bytes |
30+
| **Total** | | **6 + size(results)** |
31+
32+
- `codecID` is the codec version used to serialize the payload and is hardcoded to `0x0000`
33+
- `results` is a map of transaction hashes to the corresponding `TxPredicateResults`
34+
35+
TxPredicateResults
36+
37+
| Field | Type | Size |
38+
|-------|------|------|
39+
| txPredicateResults | map[[20]byte][]byte | 4 + size(txPredicateResults) bytes |
40+
41+
- `txPredicateResults` is a map of precompile addresses to the corresponding byte array returned by the predicate
42+
43+
#### Examples
44+
45+
##### Empty Predicate Results Map
46+
47+
```
48+
// codecID
49+
0x00, 0x00,
50+
// results length
51+
0x00, 0x00, 0x00, 0x00
52+
```
53+
54+
##### Predicate Map with a Single Transaction Result
55+
56+
```
57+
// codecID
58+
0x00, 0x00,
59+
// Results length
60+
0x00, 0x00, 0x00, 0x01,
61+
// txHash (key in results map)
62+
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
63+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
64+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
65+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
66+
// TxPredicateResults (value in results map)
67+
// TxPredicateResults length
68+
0x00, 0x00, 0x00, 0x01,
69+
// precompile address (key in TxPredicateResults map)
70+
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
71+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
72+
0x00, 0x00, 0x00, 0x00,
73+
// Byte array results (value in TxPredicateResults map)
74+
// Length of bytes result
75+
0x00, 0x00, 0x00, 0x03,
76+
// bytes
77+
0x01, 0x02, 0x03
78+
```
79+
80+
##### Predicate Map with Two Transaction Results
81+
82+
```
83+
// codecID
84+
0x00, 0x00,
85+
// Results length
86+
0x00, 0x00, 0x00, 0x02,
87+
// txHash (key in results map)
88+
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
89+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
90+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
91+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
92+
// TxPredicateResults (value in results map)
93+
// TxPredicateResults length
94+
0x00, 0x00, 0x00, 0x01,
95+
// precompile address (key in TxPredicateResults map)
96+
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
97+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
98+
0x00, 0x00, 0x00, 0x00,
99+
// Byte array results (value in TxPredicateResults map)
100+
// Length of bytes result
101+
0x00, 0x00, 0x00, 0x03,
102+
// bytes
103+
0x01, 0x02, 0x03
104+
// txHash2 (key in results map)
105+
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
106+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
107+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
108+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
109+
// TxPredicateResults (value in results map)
110+
// TxPredicateResults length
111+
0x00, 0x00, 0x00, 0x01,
112+
// precompile address (key in TxPredicateResults map)
113+
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
114+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
115+
0x00, 0x00, 0x00, 0x00,
116+
// Byte array results (value in TxPredicateResults map)
117+
// Length of bytes result
118+
0x00, 0x00, 0x00, 0x03,
119+
// bytes
120+
0x01, 0x02, 0x03
121+
```

vms/evm/predicate/predicate.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved.
2+
// See the file LICENSE for licensing terms.
3+
4+
package predicate
5+
6+
import (
7+
"errors"
8+
"fmt"
9+
10+
"github.com/ava-labs/libevm/common"
11+
"github.com/ava-labs/libevm/core/types"
12+
)
13+
14+
// delimiter separates the actual predicate bytes from the padded zero bytes.
15+
//
16+
// Predicates are encoded in the Access List of transactions by using in the
17+
// access tuples. This means that the length must be a multiple of
18+
// [common.HashLength].
19+
//
20+
// Even if the original predicate bytes is a multiple of [common.HashLength],
21+
// the delimiter must be appended to support decoding.
22+
const delimiter = 0xff
23+
24+
var (
25+
errMissingDelimiter = errors.New("no delimiter found")
26+
errExcessPadding = errors.New("predicate included excess padding")
27+
errWrongDelimiter = errors.New("wrong delimiter")
28+
)
29+
30+
// Predicate is a message padded with the delimiter and zeros and chunked into
31+
// 32-byte chunks.
32+
type Predicate []common.Hash
33+
34+
// New constructs a predicate from raw predicate bytes.
35+
//
36+
// It chunks the predicate by appending [predicate.Delimiter] and zero-padding
37+
// to a multiple of 32 bytes.
38+
func New(b []byte) Predicate {
39+
numUnpaddedChunks := len(b) / common.HashLength
40+
chunks := make([]common.Hash, numUnpaddedChunks+1)
41+
// Copy over chunks that don't require padding.
42+
for i := range chunks[:numUnpaddedChunks] {
43+
chunks[i] = common.Hash(b[common.HashLength*i:])
44+
}
45+
46+
// Add the delimiter and required padding to the last chunk.
47+
copy(chunks[numUnpaddedChunks][:], b[common.HashLength*numUnpaddedChunks:])
48+
chunks[numUnpaddedChunks][len(b)%common.HashLength] = delimiter
49+
return chunks
50+
}
51+
52+
// Bytes converts the chunked predicate into the original message.
53+
//
54+
// Returns an error if it finds an incorrect encoding.
55+
func (p Predicate) Bytes() ([]byte, error) {
56+
padded := make([]byte, common.HashLength*len(p))
57+
for i, chunk := range p {
58+
copy(padded[common.HashLength*i:], chunk[:])
59+
}
60+
trimmed := common.TrimRightZeroes(padded)
61+
if len(trimmed) == 0 {
62+
return nil, fmt.Errorf("%w: length (%d)", errMissingDelimiter, len(p))
63+
}
64+
65+
expectedLen := (len(trimmed) + common.HashLength - 1) / common.HashLength
66+
if expectedLen != len(p) {
67+
return nil, fmt.Errorf("%w: got length (%d), expected length (%d)", errExcessPadding, len(p), expectedLen)
68+
}
69+
70+
delimiterIndex := len(trimmed) - 1
71+
if trimmed[delimiterIndex] != delimiter {
72+
return nil, errWrongDelimiter
73+
}
74+
75+
return trimmed[:delimiterIndex], nil
76+
}
77+
78+
type Predicates interface {
79+
HasPredicate(address common.Address) bool
80+
}
81+
82+
// FromAccessList extracts predicates from a transaction's access list.
83+
//
84+
// If an address is specified multiple times in the access list, each set of
85+
// storage keys for that address is considered an individual predicate.
86+
func FromAccessList(rules Predicates, list types.AccessList) map[common.Address][]Predicate {
87+
predicates := make(map[common.Address][]Predicate)
88+
for _, el := range list {
89+
if !rules.HasPredicate(el.Address) {
90+
continue
91+
}
92+
predicates[el.Address] = append(predicates[el.Address], el.StorageKeys)
93+
}
94+
95+
return predicates
96+
}

0 commit comments

Comments
 (0)