Skip to content

Commit 8f8ae38

Browse files
aaronbuchwaldDarioush Jalali
andauthored
Precompile pre post handling (#524)
* Re-apply warp precompile interface changes * Address nits * Separate predicate storage slot preparation into separate function in statedb * fix lint * improve miner enforcePredicates comment * Add HashSliceToBytes test case for empty slice * Address comments * Address comments WIP * Pre+post handling diff for shared mem precompile * Separate proposer and general precompile predicates * Update ShouldVerifyWithContext to return true iff proposer predicate is specified * Add checkPredicates unit test * Update .gitignore * goimports * update * goimports config * Address PR review comments and improve comments * Fix typo * Address PR comments * Add rules into PrepareAccessList * Only copy bytes in preparePredicates if predicate precompile is active * Address PR comments --------- Co-authored-by: Darioush Jalali <[email protected]>
1 parent 8a28b23 commit 8f8ae38

File tree

21 files changed

+790
-46
lines changed

21 files changed

+790
-46
lines changed

.gitignore

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,3 @@ cmd/simulator/.simulator/*
4949

5050
# goreleaser
5151
dist/
52-
53-
# generator rpc file for e2e tests
54-
contract-examples/dynamic_rpc.json

accounts/abi/abi.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) {
9595
// Returns the topics for the event including the event signature (if non-anonymous event) and
9696
// hashes derived from indexed arguments and the packed data of non-indexed args according to
9797
// the event ABI specification.
98+
// The order of arguments must match the order of the event definition.
9899
// https://docs.soliditylang.org/en/v0.8.17/abi-spec.html#indexed-event-encoding.
99100
// Note: PackEvent does not support array (fixed or dynamic-size) or struct types.
100101
func (abi ABI) PackEvent(name string, args ...interface{}) ([]common.Hash, []byte, error) {

core/predicate_check.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// (c) 2023, Ava Labs, Inc. All rights reserved.
2+
// See the file LICENSE for licensing terms.
3+
4+
package core
5+
6+
import (
7+
"errors"
8+
"fmt"
9+
10+
"github.com/ava-labs/subnet-evm/core/types"
11+
"github.com/ava-labs/subnet-evm/params"
12+
"github.com/ava-labs/subnet-evm/precompile/precompileconfig"
13+
"github.com/ava-labs/subnet-evm/utils"
14+
"github.com/ethereum/go-ethereum/common"
15+
)
16+
17+
var errNilProposerVMBlockCtxWithProposerPredicate = errors.New("engine cannot specify nil ProposerVM block context with non-empty proposer predicates")
18+
19+
// CheckPredicates checks that all precompile predicates are satisfied within the current [predicateContext] for [tx]
20+
func CheckPredicates(rules params.Rules, predicateContext *precompileconfig.ProposerPredicateContext, tx *types.Transaction) error {
21+
if err := checkPrecompilePredicates(rules, &predicateContext.PrecompilePredicateContext, tx); err != nil {
22+
return err
23+
}
24+
return checkProposerPrecompilePredicates(rules, predicateContext, tx)
25+
}
26+
27+
func checkPrecompilePredicates(rules params.Rules, predicateContext *precompileconfig.PrecompilePredicateContext, tx *types.Transaction) error {
28+
// Short circuit early if there are no precompile predicates to verify
29+
if len(rules.PredicatePrecompiles) == 0 {
30+
return nil
31+
}
32+
precompilePredicates := rules.PredicatePrecompiles
33+
// Track addresses that we've performed a predicate check for
34+
precompileAddressChecks := make(map[common.Address]struct{})
35+
for _, accessTuple := range tx.AccessList() {
36+
address := accessTuple.Address
37+
predicater, ok := precompilePredicates[address]
38+
if !ok {
39+
continue
40+
}
41+
// Return an error if we've already checked a predicate for this address
42+
if _, ok := precompileAddressChecks[address]; ok {
43+
return fmt.Errorf("predicate %s failed verification for tx %s: specified %s in access list multiple times", address, tx.Hash(), address)
44+
}
45+
precompileAddressChecks[address] = struct{}{}
46+
if err := predicater.VerifyPredicate(predicateContext, utils.HashSliceToBytes(accessTuple.StorageKeys)); err != nil {
47+
return fmt.Errorf("predicate %s failed verification for tx %s: %w", address, tx.Hash(), err)
48+
}
49+
}
50+
51+
return nil
52+
}
53+
54+
func checkProposerPrecompilePredicates(rules params.Rules, predicateContext *precompileconfig.ProposerPredicateContext, tx *types.Transaction) error {
55+
// Short circuit early if there are no precompile predicates to verify
56+
if len(rules.ProposerPredicates) == 0 {
57+
return nil
58+
}
59+
// If a proposer predicate is specified, reuqire that the ProposerVMBlockCtx is non-nil.
60+
if predicateContext.ProposerVMBlockCtx == nil {
61+
return errNilProposerVMBlockCtxWithProposerPredicate
62+
}
63+
precompilePredicates := rules.ProposerPredicates
64+
// Track addresses that we've performed a predicate check for
65+
precompileAddressChecks := make(map[common.Address]struct{})
66+
for _, accessTuple := range tx.AccessList() {
67+
address := accessTuple.Address
68+
predicater, ok := precompilePredicates[address]
69+
if !ok {
70+
continue
71+
}
72+
// Return an error if we've already checked a predicate for this address
73+
if _, ok := precompileAddressChecks[address]; ok {
74+
return fmt.Errorf("predicate %s failed verification for tx %s: specified %s in access list multiple times", address, tx.Hash(), address)
75+
}
76+
precompileAddressChecks[address] = struct{}{}
77+
if err := predicater.VerifyPredicate(predicateContext, utils.HashSliceToBytes(accessTuple.StorageKeys)); err != nil {
78+
return fmt.Errorf("predicate %s failed verification for tx %s: %w", address, tx.Hash(), err)
79+
}
80+
}
81+
82+
return nil
83+
}

core/predicate_check_test.go

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
// (c) 2023, Ava Labs, Inc. All rights reserved.
2+
// See the file LICENSE for licensing terms.
3+
4+
package core
5+
6+
import (
7+
"bytes"
8+
"fmt"
9+
"testing"
10+
11+
"github.com/ava-labs/avalanchego/snow/engine/snowman/block"
12+
"github.com/ava-labs/subnet-evm/core/types"
13+
"github.com/ava-labs/subnet-evm/params"
14+
"github.com/ava-labs/subnet-evm/precompile/precompileconfig"
15+
"github.com/ethereum/go-ethereum/common"
16+
"github.com/stretchr/testify/require"
17+
)
18+
19+
var (
20+
_ precompileconfig.PrecompilePredicater = (*mockPredicater)(nil)
21+
_ precompileconfig.ProposerPredicater = (*mockProposerPredicater)(nil)
22+
)
23+
24+
type mockPredicater struct {
25+
predicateFunc func(*precompileconfig.PrecompilePredicateContext, []byte) error
26+
}
27+
28+
func (m *mockPredicater) VerifyPredicate(predicateContext *precompileconfig.PrecompilePredicateContext, b []byte) error {
29+
return m.predicateFunc(predicateContext, b)
30+
}
31+
32+
type mockProposerPredicater struct {
33+
predicateFunc func(*precompileconfig.ProposerPredicateContext, []byte) error
34+
}
35+
36+
func (m *mockProposerPredicater) VerifyPredicate(predicateContext *precompileconfig.ProposerPredicateContext, b []byte) error {
37+
return m.predicateFunc(predicateContext, b)
38+
}
39+
40+
type predicateCheckTest struct {
41+
address common.Address
42+
predicater precompileconfig.PrecompilePredicater
43+
proposerPredicater precompileconfig.ProposerPredicater
44+
accessList types.AccessList
45+
emptyProposerBlockCtx bool
46+
expectedErr error
47+
}
48+
49+
func TestCheckPredicate(t *testing.T) {
50+
for name, test := range map[string]predicateCheckTest{
51+
"no predicates, no access list passes": {
52+
expectedErr: nil,
53+
},
54+
"no predicates, with access list passes": {
55+
accessList: types.AccessList([]types.AccessTuple{
56+
{
57+
Address: common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"),
58+
StorageKeys: []common.Hash{
59+
{1},
60+
},
61+
},
62+
}),
63+
expectedErr: nil,
64+
},
65+
"proposer predicate, no access list passes": {
66+
address: common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"),
67+
proposerPredicater: &mockProposerPredicater{predicateFunc: func(*precompileconfig.ProposerPredicateContext, []byte) error { return nil }},
68+
expectedErr: nil,
69+
},
70+
"predicate, no access list passes": {
71+
address: common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"),
72+
predicater: &mockPredicater{predicateFunc: func(*precompileconfig.PrecompilePredicateContext, []byte) error { return nil }},
73+
expectedErr: nil,
74+
},
75+
"predicate with valid access list passes": {
76+
address: common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"),
77+
predicater: &mockPredicater{predicateFunc: func(_ *precompileconfig.PrecompilePredicateContext, b []byte) error {
78+
if bytes.Equal(b, common.Hash{1}.Bytes()) {
79+
return nil
80+
} else {
81+
return fmt.Errorf("unexpected bytes: 0x%x", b)
82+
}
83+
}},
84+
accessList: types.AccessList([]types.AccessTuple{
85+
{
86+
Address: common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"),
87+
StorageKeys: []common.Hash{
88+
{1},
89+
},
90+
},
91+
}),
92+
expectedErr: nil,
93+
},
94+
"proposer predicate with valid access list passes": {
95+
address: common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"),
96+
proposerPredicater: &mockProposerPredicater{predicateFunc: func(_ *precompileconfig.ProposerPredicateContext, b []byte) error {
97+
if bytes.Equal(b, common.Hash{1}.Bytes()) {
98+
return nil
99+
} else {
100+
return fmt.Errorf("unexpected bytes: 0x%x", b)
101+
}
102+
}},
103+
accessList: types.AccessList([]types.AccessTuple{
104+
{
105+
Address: common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"),
106+
StorageKeys: []common.Hash{
107+
{1},
108+
},
109+
},
110+
}),
111+
expectedErr: nil,
112+
},
113+
"predicate with invalid access list errors": {
114+
address: common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"),
115+
predicater: &mockPredicater{predicateFunc: func(_ *precompileconfig.PrecompilePredicateContext, b []byte) error {
116+
if bytes.Equal(b, common.Hash{1}.Bytes()) {
117+
return nil
118+
} else {
119+
return fmt.Errorf("unexpected bytes: 0x%x", b)
120+
}
121+
}},
122+
accessList: types.AccessList([]types.AccessTuple{
123+
{
124+
Address: common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"),
125+
StorageKeys: []common.Hash{
126+
{2},
127+
},
128+
},
129+
}),
130+
expectedErr: fmt.Errorf("unexpected bytes: 0x%x", common.Hash{2}.Bytes()),
131+
},
132+
"proposer predicate with invalid access list errors": {
133+
address: common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"),
134+
proposerPredicater: &mockProposerPredicater{predicateFunc: func(_ *precompileconfig.ProposerPredicateContext, b []byte) error {
135+
if bytes.Equal(b, common.Hash{1}.Bytes()) {
136+
return nil
137+
} else {
138+
return fmt.Errorf("unexpected bytes: 0x%x", b)
139+
}
140+
}},
141+
accessList: types.AccessList([]types.AccessTuple{
142+
{
143+
Address: common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"),
144+
StorageKeys: []common.Hash{
145+
{2},
146+
},
147+
},
148+
}),
149+
expectedErr: fmt.Errorf("unexpected bytes: 0x%x", common.Hash{2}.Bytes()),
150+
},
151+
"proposer predicate with empty proposer block ctx": {
152+
address: common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"),
153+
proposerPredicater: &mockProposerPredicater{predicateFunc: func(_ *precompileconfig.ProposerPredicateContext, b []byte) error { return nil }},
154+
emptyProposerBlockCtx: true,
155+
expectedErr: errNilProposerVMBlockCtxWithProposerPredicate,
156+
},
157+
} {
158+
test := test
159+
t.Run(name, func(t *testing.T) {
160+
// Create the rules from TestChainConfig and update the predicates based on the test params
161+
rules := params.TestChainConfig.AvalancheRules(common.Big0, common.Big0)
162+
if test.proposerPredicater != nil {
163+
rules.ProposerPredicates[test.address] = test.proposerPredicater
164+
}
165+
if test.predicater != nil {
166+
rules.PredicatePrecompiles[test.address] = test.predicater
167+
}
168+
169+
// Specify only the access list, since this test should not depend on any other values
170+
tx := types.NewTx(&types.DynamicFeeTx{
171+
AccessList: test.accessList,
172+
})
173+
predicateContext := &precompileconfig.ProposerPredicateContext{}
174+
if !test.emptyProposerBlockCtx {
175+
predicateContext.ProposerVMBlockCtx = &block.Context{}
176+
}
177+
err := CheckPredicates(rules, predicateContext, tx)
178+
if test.expectedErr == nil {
179+
require.NoError(t, err)
180+
} else {
181+
require.ErrorContains(t, err, test.expectedErr.Error())
182+
}
183+
})
184+
}
185+
}

0 commit comments

Comments
 (0)