Skip to content

Commit 9c03a87

Browse files
Add predicate packing helper (#710)
* Add predicate packing helper * move predicate code and add readme * fix moved import * fix merge
1 parent 038694c commit 9c03a87

File tree

8 files changed

+160
-62
lines changed

8 files changed

+160
-62
lines changed

core/predicate_check.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99
"github.com/ava-labs/subnet-evm/core/types"
1010
"github.com/ava-labs/subnet-evm/params"
1111
"github.com/ava-labs/subnet-evm/precompile/precompileconfig"
12-
"github.com/ava-labs/subnet-evm/utils"
12+
predicateutils "github.com/ava-labs/subnet-evm/utils/predicate"
1313
"github.com/ethereum/go-ethereum/common"
1414
)
1515

@@ -49,7 +49,7 @@ func checkPrecompilePredicates(rules params.Rules, predicateContext *precompilec
4949
return fmt.Errorf("predicate %s failed verification for tx %s: specified %s in access list multiple times", address, tx.Hash(), address)
5050
}
5151
precompileAddressChecks[address] = struct{}{}
52-
predicateBytes := utils.HashSliceToBytes(accessTuple.StorageKeys)
52+
predicateBytes := predicateutils.HashSliceToBytes(accessTuple.StorageKeys)
5353
if err := predicater.VerifyPredicate(predicateContext, predicateBytes); err != nil {
5454
return fmt.Errorf("predicate %s failed verification for tx %s: %w", address, tx.Hash(), err)
5555
}
@@ -77,7 +77,7 @@ func checkProposerPrecompilePredicates(rules params.Rules, predicateContext *pre
7777
return fmt.Errorf("predicate %s failed verification for tx %s: specified %s in access list multiple times", address, tx.Hash(), address)
7878
}
7979
precompileAddressChecks[address] = struct{}{}
80-
predicateBytes := utils.HashSliceToBytes(accessTuple.StorageKeys)
80+
predicateBytes := predicateutils.HashSliceToBytes(accessTuple.StorageKeys)
8181
if err := predicater.VerifyPredicate(predicateContext, predicateBytes); err != nil {
8282
return fmt.Errorf("predicate %s failed verification for tx %s: %w", address, tx.Hash(), err)
8383
}

core/state/statedb.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ import (
4040
"github.com/ava-labs/subnet-evm/metrics"
4141
"github.com/ava-labs/subnet-evm/params"
4242
"github.com/ava-labs/subnet-evm/trie"
43-
"github.com/ava-labs/subnet-evm/utils"
43+
predicateutils "github.com/ava-labs/subnet-evm/utils/predicate"
4444
"github.com/ethereum/go-ethereum/common"
4545
"github.com/ethereum/go-ethereum/crypto"
4646
"github.com/ethereum/go-ethereum/log"
@@ -1109,7 +1109,7 @@ func (s *StateDB) preparePredicateStorageSlots(rules params.Rules, list types.Ac
11091109
if !rules.PredicateExists(el.Address) {
11101110
continue
11111111
}
1112-
s.predicateStorageSlots[el.Address] = utils.HashSliceToBytes(el.StorageKeys)
1112+
s.predicateStorageSlots[el.Address] = predicateutils.HashSliceToBytes(el.StorageKeys)
11131113
}
11141114
}
11151115

core/state_transition.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import (
3737
"github.com/ava-labs/subnet-evm/core/vm"
3838
"github.com/ava-labs/subnet-evm/params"
3939
"github.com/ava-labs/subnet-evm/precompile/contracts/txallowlist"
40-
"github.com/ava-labs/subnet-evm/utils"
40+
predicateutils "github.com/ava-labs/subnet-evm/utils/predicate"
4141
"github.com/ava-labs/subnet-evm/vmerrs"
4242
"github.com/ethereum/go-ethereum/common"
4343
commonMath "github.com/ethereum/go-ethereum/common/math"
@@ -219,13 +219,13 @@ func accessListGas(rules params.Rules, accessList types.AccessList) (uint64, err
219219
func applyPredicateGas(rules params.Rules, accessTuple types.AccessTuple) (uint64, error) {
220220
predicate, ok := rules.PredicatePrecompiles[accessTuple.Address]
221221
if ok {
222-
return predicate.PredicateGas(utils.HashSliceToBytes(accessTuple.StorageKeys))
222+
return predicate.PredicateGas(predicateutils.HashSliceToBytes(accessTuple.StorageKeys))
223223
}
224224
proposerPredicate, ok := rules.ProposerPredicates[accessTuple.Address]
225225
if !ok {
226226
return 0, nil
227227
}
228-
return proposerPredicate.PredicateGas(utils.HashSliceToBytes(accessTuple.StorageKeys))
228+
return proposerPredicate.PredicateGas(predicateutils.HashSliceToBytes(accessTuple.StorageKeys))
229229
}
230230

231231
// NewStateTransition initialises and returns a new state transition object.

utils/bytes.go

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33

44
package utils
55

6-
import "github.com/ethereum/go-ethereum/common"
7-
86
// IncrOne increments bytes value by one
97
func IncrOne(bytes []byte) {
108
index := len(bytes) - 1
@@ -18,27 +16,3 @@ func IncrOne(bytes []byte) {
1816
}
1917
}
2018
}
21-
22-
// HashSliceToBytes serializes a []common.Hash into a tightly packed byte array.
23-
func HashSliceToBytes(hashes []common.Hash) []byte {
24-
bytes := make([]byte, common.HashLength*len(hashes))
25-
for i, hash := range hashes {
26-
copy(bytes[i*common.HashLength:], hash[:])
27-
}
28-
return bytes
29-
}
30-
31-
// BytesToHashSlice packs [b] into a slice of hash values with zero padding
32-
// to the right if the length of b is not a multiple of 32.
33-
func BytesToHashSlice(b []byte) []common.Hash {
34-
var (
35-
numHashes = (len(b) + 31) / 32
36-
hashes = make([]common.Hash, numHashes)
37-
)
38-
39-
for i := range hashes {
40-
start := i * common.HashLength
41-
copy(hashes[i][:], b[start:])
42-
}
43-
return hashes
44-
}

utils/bytes_test.go

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,10 @@
44
package utils
55

66
import (
7-
"bytes"
87
"testing"
98

10-
"github.com/ava-labs/avalanchego/utils"
119
"github.com/ethereum/go-ethereum/common"
1210
"github.com/stretchr/testify/assert"
13-
"github.com/stretchr/testify/require"
1411
)
1512

1613
func TestIncrOne(t *testing.T) {
@@ -39,28 +36,3 @@ func TestIncrOne(t *testing.T) {
3936
})
4037
}
4138
}
42-
43-
func testBytesToHashSlice(t testing.TB, b []byte) {
44-
hashSlice := BytesToHashSlice(b)
45-
46-
copiedBytes := HashSliceToBytes(hashSlice)
47-
48-
if len(b)%32 == 0 {
49-
require.Equal(t, b, copiedBytes)
50-
} else {
51-
require.Equal(t, b, copiedBytes[:len(b)])
52-
// Require that any additional padding is all zeroes
53-
padding := copiedBytes[len(b):]
54-
require.Equal(t, bytes.Repeat([]byte{0x00}, len(padding)), padding)
55-
}
56-
}
57-
58-
func FuzzHashSliceToBytes(f *testing.F) {
59-
for i := 0; i < 100; i++ {
60-
f.Add(utils.RandomBytes(i))
61-
}
62-
63-
f.Fuzz(func(t *testing.T, a []byte) {
64-
testBytesToHashSlice(t, a)
65-
})
66-
}

utils/predicate/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Predicate Utils
2+
3+
This package provides simple helpers to pack/unpack byte slices for a predicate transaction, where a byte slice of size N is encoded in the access list of a transaction.
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

utils/predicate/predicate_bytes.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// (c) 2023, Ava Labs, Inc. All rights reserved.
2+
// See the file LICENSE for licensing terms.
3+
4+
package predicateutils
5+
6+
import (
7+
"fmt"
8+
9+
"github.com/ethereum/go-ethereum/common"
10+
)
11+
12+
// PredicateEndByte is used as a delimiter for the bytes packed into a precompile predicate.
13+
// Precompile predicates are encoded in the Access List of transactions in the access tuples
14+
// which means that its length must be a multiple of 32 (common.HashLength).
15+
// For messages with a length that does not comply to that, this delimiter is used to
16+
// append/remove padding.
17+
var PredicateEndByte = byte(0xff)
18+
19+
// PackPredicate packs [predicate] by delimiting the actual message with [PredicateEndByte]
20+
// and zero padding to reach a length that is a multiple of 32.
21+
func PackPredicate(predicate []byte) []byte {
22+
predicate = append(predicate, PredicateEndByte)
23+
return common.RightPadBytes(predicate, (len(predicate)+31)/32*32)
24+
}
25+
26+
// UnpackPredicate unpacks a predicate by stripping right padded zeroes, checking for the delimter,
27+
// ensuring there is not excess padding, and returning the original message.
28+
// Returns an error if it finds an incorrect encoding.
29+
func UnpackPredicate(paddedPredicate []byte) ([]byte, error) {
30+
trimmedPredicateBytes := common.TrimRightZeroes(paddedPredicate)
31+
if len(trimmedPredicateBytes) == 0 {
32+
return nil, fmt.Errorf("predicate specified invalid all zero bytes: 0x%x", paddedPredicate)
33+
}
34+
35+
if expectedPaddedLength := (len(trimmedPredicateBytes) + 31) / 32 * 32; expectedPaddedLength != len(paddedPredicate) {
36+
return nil, fmt.Errorf("predicate specified invalid padding with length (%d), expected length (%d)", len(paddedPredicate), expectedPaddedLength)
37+
}
38+
39+
if trimmedPredicateBytes[len(trimmedPredicateBytes)-1] != PredicateEndByte {
40+
return nil, fmt.Errorf("invalid end delimiter")
41+
}
42+
43+
return trimmedPredicateBytes[:len(trimmedPredicateBytes)-1], nil
44+
}
45+
46+
// HashSliceToBytes serializes a []common.Hash into a tightly packed byte array.
47+
func HashSliceToBytes(hashes []common.Hash) []byte {
48+
bytes := make([]byte, common.HashLength*len(hashes))
49+
for i, hash := range hashes {
50+
copy(bytes[i*common.HashLength:], hash[:])
51+
}
52+
return bytes
53+
}
54+
55+
// BytesToHashSlice packs [b] into a slice of hash values with zero padding
56+
// to the right if the length of b is not a multiple of 32.
57+
func BytesToHashSlice(b []byte) []common.Hash {
58+
var (
59+
numHashes = (len(b) + 31) / 32
60+
hashes = make([]common.Hash, numHashes)
61+
)
62+
63+
for i := range hashes {
64+
start := i * common.HashLength
65+
copy(hashes[i][:], b[start:])
66+
}
67+
return hashes
68+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// (c) 2023, Ava Labs, Inc. All rights reserved.
2+
// See the file LICENSE for licensing terms.
3+
4+
package predicateutils
5+
6+
import (
7+
"bytes"
8+
"testing"
9+
10+
"github.com/ava-labs/avalanchego/utils"
11+
"github.com/stretchr/testify/require"
12+
)
13+
14+
func testBytesToHashSlice(t testing.TB, b []byte) {
15+
hashSlice := BytesToHashSlice(b)
16+
17+
copiedBytes := HashSliceToBytes(hashSlice)
18+
19+
if len(b)%32 == 0 {
20+
require.Equal(t, b, copiedBytes)
21+
} else {
22+
require.Equal(t, b, copiedBytes[:len(b)])
23+
// Require that any additional padding is all zeroes
24+
padding := copiedBytes[len(b):]
25+
require.Equal(t, bytes.Repeat([]byte{0x00}, len(padding)), padding)
26+
}
27+
}
28+
29+
func FuzzHashSliceToBytes(f *testing.F) {
30+
for i := 0; i < 100; i++ {
31+
f.Add(utils.RandomBytes(i))
32+
}
33+
34+
f.Fuzz(func(t *testing.T, b []byte) {
35+
testBytesToHashSlice(t, b)
36+
})
37+
}
38+
39+
func testPackPredicate(t testing.TB, b []byte) {
40+
packedPredicate := PackPredicate(b)
41+
unpackedPredicated, err := UnpackPredicate(packedPredicate)
42+
require.NoError(t, err)
43+
require.Equal(t, b, unpackedPredicated)
44+
}
45+
46+
func FuzzPackPredicate(f *testing.F) {
47+
for i := 0; i < 100; i++ {
48+
f.Add(utils.RandomBytes(i))
49+
}
50+
51+
f.Fuzz(func(t *testing.T, b []byte) {
52+
testPackPredicate(t, b)
53+
})
54+
}
55+
56+
func FuzzUnpackInvalidPredicate(f *testing.F) {
57+
// Seed the fuzzer with non-zero length padding of zeroes or non-zeroes.
58+
for i := 1; i < 100; i++ {
59+
f.Add(utils.RandomBytes(i))
60+
f.Add(make([]byte, i))
61+
}
62+
63+
f.Fuzz(func(t *testing.T, b []byte) {
64+
// Ensure that adding the invalid padding to any length correctly packed predicate
65+
// results in failing to unpack it.
66+
for _, l := range []int{0, 1, 31, 32, 33, 63, 64, 65} {
67+
validPredicate := PackPredicate(utils.RandomBytes(l))
68+
invalidPredicate := append(validPredicate, b...)
69+
_, err := UnpackPredicate(invalidPredicate)
70+
require.Error(t, err)
71+
}
72+
})
73+
}

0 commit comments

Comments
 (0)