Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Precompile pre post handling #524

Merged
merged 28 commits into from
Mar 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
6a3ebe1
Re-apply warp precompile interface changes
aaronbuchwald Feb 22, 2023
9474829
Address nits
aaronbuchwald Feb 22, 2023
0d2c92b
Merge branch 'master' into precompile-pre-post-handling
aaronbuchwald Feb 23, 2023
29277c5
Separate predicate storage slot preparation into separate function in…
aaronbuchwald Feb 23, 2023
83036b7
fix lint
aaronbuchwald Feb 23, 2023
7e10491
improve miner enforcePredicates comment
aaronbuchwald Feb 23, 2023
dcfd120
Merge branch 'master' into precompile-pre-post-handling
aaronbuchwald Feb 23, 2023
584bcaa
Add HashSliceToBytes test case for empty slice
aaronbuchwald Feb 23, 2023
73aee4b
Address comments
aaronbuchwald Feb 27, 2023
2eff72e
Address comments WIP
aaronbuchwald Feb 27, 2023
e3c0899
Pre+post handling diff for shared mem precompile
darioush Feb 27, 2023
cd79963
Separate proposer and general precompile predicates
aaronbuchwald Mar 7, 2023
be31bea
Update ShouldVerifyWithContext to return true iff proposer predicate …
aaronbuchwald Mar 7, 2023
67b7f7c
Add checkPredicates unit test
aaronbuchwald Mar 7, 2023
0d4ad9f
Merge branch 'master' into precompile-pre-post-handling
aaronbuchwald Mar 7, 2023
cde76df
Merge branch 'precompile-pre-post-handling' into precompile-pre-post-…
aaronbuchwald Mar 7, 2023
ffdda86
Update .gitignore
aaronbuchwald Mar 7, 2023
e6d6d78
goimports
aaronbuchwald Mar 7, 2023
794122d
update
aaronbuchwald Mar 7, 2023
eb2bb88
goimports config
aaronbuchwald Mar 7, 2023
2edac77
Address PR review comments and improve comments
aaronbuchwald Mar 7, 2023
15bcbe8
Merge branch 'master' into precompile-pre-post-handling
aaronbuchwald Mar 15, 2023
485ed59
Fix typo
aaronbuchwald Mar 15, 2023
5fa5bb5
Address PR comments
aaronbuchwald Mar 16, 2023
67116e0
Add rules into PrepareAccessList
aaronbuchwald Mar 16, 2023
972e486
Only copy bytes in preparePredicates if predicate precompile is active
aaronbuchwald Mar 16, 2023
9d5246c
Merge branch 'master' into precompile-pre-post-handling
aaronbuchwald Mar 16, 2023
336f0cc
Address PR comments
aaronbuchwald Mar 16, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,3 @@ cmd/simulator/.simulator/*

# goreleaser
dist/

# generator rpc file for e2e tests
contract-examples/dynamic_rpc.json
1 change: 1 addition & 0 deletions accounts/abi/abi.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) {
// Returns the topics for the event including the event signature (if non-anonymous event) and
// hashes derived from indexed arguments and the packed data of non-indexed args according to
// the event ABI specification.
// The order of arguments must match the order of the event definition.
// https://docs.soliditylang.org/en/v0.8.17/abi-spec.html#indexed-event-encoding.
// Note: PackEvent does not support array (fixed or dynamic-size) or struct types.
func (abi ABI) PackEvent(name string, args ...interface{}) ([]common.Hash, []byte, error) {
Expand Down
83 changes: 83 additions & 0 deletions core/predicate_check.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// (c) 2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package core

import (
"errors"
"fmt"

"github.com/ava-labs/subnet-evm/core/types"
"github.com/ava-labs/subnet-evm/params"
"github.com/ava-labs/subnet-evm/precompile/precompileconfig"
"github.com/ava-labs/subnet-evm/utils"
"github.com/ethereum/go-ethereum/common"
)

var errNilProposerVMBlockCtxWithProposerPredicate = errors.New("engine cannot specify nil ProposerVM block context with non-empty proposer predicates")

// CheckPredicates checks that all precompile predicates are satisfied within the current [predicateContext] for [tx]
func CheckPredicates(rules params.Rules, predicateContext *precompileconfig.ProposerPredicateContext, tx *types.Transaction) error {
if err := checkPrecompilePredicates(rules, &predicateContext.PrecompilePredicateContext, tx); err != nil {
return err
}
return checkProposerPrecompilePredicates(rules, predicateContext, tx)
}

func checkPrecompilePredicates(rules params.Rules, predicateContext *precompileconfig.PrecompilePredicateContext, tx *types.Transaction) error {
// Short circuit early if there are no precompile predicates to verify
if len(rules.PredicatePrecompiles) == 0 {
return nil
}
precompilePredicates := rules.PredicatePrecompiles
// Track addresses that we've performed a predicate check for
precompileAddressChecks := make(map[common.Address]struct{})
for _, accessTuple := range tx.AccessList() {
address := accessTuple.Address
predicater, ok := precompilePredicates[address]
if !ok {
continue
}
// Return an error if we've already checked a predicate for this address
if _, ok := precompileAddressChecks[address]; ok {
return fmt.Errorf("predicate %s failed verification for tx %s: specified %s in access list multiple times", address, tx.Hash(), address)
}
precompileAddressChecks[address] = struct{}{}
if err := predicater.VerifyPredicate(predicateContext, utils.HashSliceToBytes(accessTuple.StorageKeys)); err != nil {
return fmt.Errorf("predicate %s failed verification for tx %s: %w", address, tx.Hash(), err)
}
}

return nil
}

func checkProposerPrecompilePredicates(rules params.Rules, predicateContext *precompileconfig.ProposerPredicateContext, tx *types.Transaction) error {
// Short circuit early if there are no precompile predicates to verify
if len(rules.ProposerPredicates) == 0 {
return nil
}
// If a proposer predicate is specified, reuqire that the ProposerVMBlockCtx is non-nil.
if predicateContext.ProposerVMBlockCtx == nil {
return errNilProposerVMBlockCtxWithProposerPredicate
}
precompilePredicates := rules.ProposerPredicates
// Track addresses that we've performed a predicate check for
precompileAddressChecks := make(map[common.Address]struct{})
for _, accessTuple := range tx.AccessList() {
address := accessTuple.Address
predicater, ok := precompilePredicates[address]
if !ok {
continue
}
// Return an error if we've already checked a predicate for this address
if _, ok := precompileAddressChecks[address]; ok {
anusha-ctrl marked this conversation as resolved.
Show resolved Hide resolved
return fmt.Errorf("predicate %s failed verification for tx %s: specified %s in access list multiple times", address, tx.Hash(), address)
}
precompileAddressChecks[address] = struct{}{}
if err := predicater.VerifyPredicate(predicateContext, utils.HashSliceToBytes(accessTuple.StorageKeys)); err != nil {
aaronbuchwald marked this conversation as resolved.
Show resolved Hide resolved
return fmt.Errorf("predicate %s failed verification for tx %s: %w", address, tx.Hash(), err)
}
}

return nil
}
185 changes: 185 additions & 0 deletions core/predicate_check_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
// (c) 2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package core

import (
"bytes"
"fmt"
"testing"

"github.com/ava-labs/avalanchego/snow/engine/snowman/block"
"github.com/ava-labs/subnet-evm/core/types"
"github.com/ava-labs/subnet-evm/params"
"github.com/ava-labs/subnet-evm/precompile/precompileconfig"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)

var (
_ precompileconfig.PrecompilePredicater = (*mockPredicater)(nil)
_ precompileconfig.ProposerPredicater = (*mockProposerPredicater)(nil)
)

type mockPredicater struct {
predicateFunc func(*precompileconfig.PrecompilePredicateContext, []byte) error
}

func (m *mockPredicater) VerifyPredicate(predicateContext *precompileconfig.PrecompilePredicateContext, b []byte) error {
return m.predicateFunc(predicateContext, b)
}

type mockProposerPredicater struct {
predicateFunc func(*precompileconfig.ProposerPredicateContext, []byte) error
}

func (m *mockProposerPredicater) VerifyPredicate(predicateContext *precompileconfig.ProposerPredicateContext, b []byte) error {
return m.predicateFunc(predicateContext, b)
}

type predicateCheckTest struct {
address common.Address
predicater precompileconfig.PrecompilePredicater
proposerPredicater precompileconfig.ProposerPredicater
accessList types.AccessList
emptyProposerBlockCtx bool
expectedErr error
}

func TestCheckPredicate(t *testing.T) {
for name, test := range map[string]predicateCheckTest{
"no predicates, no access list passes": {
expectedErr: nil,
},
"no predicates, with access list passes": {
accessList: types.AccessList([]types.AccessTuple{
{
Address: common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"),
StorageKeys: []common.Hash{
{1},
},
},
}),
expectedErr: nil,
},
"proposer predicate, no access list passes": {
address: common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"),
proposerPredicater: &mockProposerPredicater{predicateFunc: func(*precompileconfig.ProposerPredicateContext, []byte) error { return nil }},
expectedErr: nil,
},
"predicate, no access list passes": {
address: common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"),
predicater: &mockPredicater{predicateFunc: func(*precompileconfig.PrecompilePredicateContext, []byte) error { return nil }},
expectedErr: nil,
},
"predicate with valid access list passes": {
address: common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"),
predicater: &mockPredicater{predicateFunc: func(_ *precompileconfig.PrecompilePredicateContext, b []byte) error {
if bytes.Equal(b, common.Hash{1}.Bytes()) {
return nil
} else {
return fmt.Errorf("unexpected bytes: 0x%x", b)
}
}},
accessList: types.AccessList([]types.AccessTuple{
{
Address: common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"),
StorageKeys: []common.Hash{
{1},
},
},
}),
expectedErr: nil,
},
"proposer predicate with valid access list passes": {
address: common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"),
proposerPredicater: &mockProposerPredicater{predicateFunc: func(_ *precompileconfig.ProposerPredicateContext, b []byte) error {
if bytes.Equal(b, common.Hash{1}.Bytes()) {
return nil
} else {
return fmt.Errorf("unexpected bytes: 0x%x", b)
}
}},
accessList: types.AccessList([]types.AccessTuple{
{
Address: common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"),
StorageKeys: []common.Hash{
{1},
},
},
}),
expectedErr: nil,
},
"predicate with invalid access list errors": {
address: common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"),
predicater: &mockPredicater{predicateFunc: func(_ *precompileconfig.PrecompilePredicateContext, b []byte) error {
if bytes.Equal(b, common.Hash{1}.Bytes()) {
return nil
} else {
return fmt.Errorf("unexpected bytes: 0x%x", b)
}
}},
accessList: types.AccessList([]types.AccessTuple{
{
Address: common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"),
StorageKeys: []common.Hash{
{2},
},
},
}),
expectedErr: fmt.Errorf("unexpected bytes: 0x%x", common.Hash{2}.Bytes()),
},
"proposer predicate with invalid access list errors": {
address: common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"),
proposerPredicater: &mockProposerPredicater{predicateFunc: func(_ *precompileconfig.ProposerPredicateContext, b []byte) error {
if bytes.Equal(b, common.Hash{1}.Bytes()) {
return nil
} else {
return fmt.Errorf("unexpected bytes: 0x%x", b)
}
}},
accessList: types.AccessList([]types.AccessTuple{
{
Address: common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"),
StorageKeys: []common.Hash{
{2},
},
},
}),
expectedErr: fmt.Errorf("unexpected bytes: 0x%x", common.Hash{2}.Bytes()),
},
"proposer predicate with empty proposer block ctx": {
address: common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"),
proposerPredicater: &mockProposerPredicater{predicateFunc: func(_ *precompileconfig.ProposerPredicateContext, b []byte) error { return nil }},
emptyProposerBlockCtx: true,
expectedErr: errNilProposerVMBlockCtxWithProposerPredicate,
},
} {
test := test
t.Run(name, func(t *testing.T) {
// Create the rules from TestChainConfig and update the predicates based on the test params
rules := params.TestChainConfig.AvalancheRules(common.Big0, common.Big0)
if test.proposerPredicater != nil {
rules.ProposerPredicates[test.address] = test.proposerPredicater
}
if test.predicater != nil {
rules.PredicatePrecompiles[test.address] = test.predicater
}

// Specify only the access list, since this test should not depend on any other values
tx := types.NewTx(&types.DynamicFeeTx{
AccessList: test.accessList,
})
predicateContext := &precompileconfig.ProposerPredicateContext{}
if !test.emptyProposerBlockCtx {
predicateContext.ProposerVMBlockCtx = &block.Context{}
}
err := CheckPredicates(rules, predicateContext, tx)
if test.expectedErr == nil {
require.NoError(t, err)
} else {
require.ErrorContains(t, err, test.expectedErr.Error())
}
})
}
}
Loading