Skip to content

Commit

Permalink
Precompile pre post handling (#524)
Browse files Browse the repository at this point in the history
* 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 <darioush.jalali@avalabs.org>
  • Loading branch information
aaronbuchwald and darioush committed Mar 16, 2023
1 parent 8a28b23 commit 8f8ae38
Show file tree
Hide file tree
Showing 21 changed files with 790 additions and 46 deletions.
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 {
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
}
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

0 comments on commit 8f8ae38

Please sign in to comment.