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 20 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: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,4 @@ cmd/simulator/.simulator/*
# goreleaser
dist/

# generator rpc file for e2e tests
contract-examples/dynamic_rpc.json
node_modules
aaronbuchwald marked this conversation as resolved.
Show resolved Hide resolved
aaronbuchwald marked this conversation as resolved.
Show resolved Hide resolved
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
76 changes: 76 additions & 0 deletions core/predicate_check.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// (c) 2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package core

import (
"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"
)

// 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
}
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
}
174 changes: 174 additions & 0 deletions core/predicate_check_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// (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/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
proposerPredicateContext precompileconfig.ProposerPredicateContext
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 passes": {
aaronbuchwald marked this conversation as resolved.
Show resolved Hide resolved
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 passes": {
aaronbuchwald marked this conversation as resolved.
Show resolved Hide resolved
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()),
},
} {
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,
})
err := CheckPredicates(rules, &test.proposerPredicateContext, tx)
if test.expectedErr == nil {
require.NoError(t, err)
} else {
require.ErrorContains(t, err, test.expectedErr.Error())
}
})
}
}
61 changes: 50 additions & 11 deletions core/state/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import (
"github.com/ava-labs/subnet-evm/core/types"
"github.com/ava-labs/subnet-evm/metrics"
"github.com/ava-labs/subnet-evm/trie"
"github.com/ava-labs/subnet-evm/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
Expand Down Expand Up @@ -110,6 +111,9 @@ type StateDB struct {

// Per-transaction access list
accessList *accessList
// Ordered storage slots to be used in predicate verification as set in the tx access list.
// Only set in PrepareAccessList, and un-modified through execution.
predicateStorageSlots map[common.Address][]byte

// Journal of state modifications. This is the backbone of
// Snapshot and RevertToSnapshot.
Expand Down Expand Up @@ -156,17 +160,18 @@ func NewWithSnapshot(root common.Hash, db Database, snap snapshot.Snapshot) (*St
return nil, err
}
sdb := &StateDB{
db: db,
trie: tr,
originalRoot: root,
stateObjects: make(map[common.Address]*stateObject),
stateObjectsPending: make(map[common.Address]struct{}),
stateObjectsDirty: make(map[common.Address]struct{}),
logs: make(map[common.Hash][]*types.Log),
preimages: make(map[common.Hash][]byte),
journal: newJournal(),
accessList: newAccessList(),
hasher: crypto.NewKeccakState(),
db: db,
trie: tr,
originalRoot: root,
stateObjects: make(map[common.Address]*stateObject),
stateObjectsPending: make(map[common.Address]struct{}),
stateObjectsDirty: make(map[common.Address]struct{}),
logs: make(map[common.Hash][]*types.Log),
preimages: make(map[common.Hash][]byte),
journal: newJournal(),
predicateStorageSlots: make(map[common.Address][]byte),
aaronbuchwald marked this conversation as resolved.
Show resolved Hide resolved
accessList: newAccessList(),
hasher: crypto.NewKeccakState(),
}
if snap != nil {
if snap.Root() != root {
Expand Down Expand Up @@ -674,6 +679,15 @@ func (db *StateDB) ForEachStorage(addr common.Address, cb func(key, value common
return nil
}

// copyPredicateStorageSlots creates a deep copy of the provided predicateStorageSlots map.
func copyPredicateStorageSlots(predicateStorageSlots map[common.Address][]byte) map[common.Address][]byte {
res := make(map[common.Address][]byte, len(predicateStorageSlots))
for address, slots := range predicateStorageSlots {
res[address] = common.CopyBytes(slots)
}
return res
}

// Copy creates a deep, independent copy of the state.
// Snapshots of the copied state cannot be applied to the copy.
func (s *StateDB) Copy() *StateDB {
Expand Down Expand Up @@ -740,6 +754,7 @@ func (s *StateDB) Copy() *StateDB {
// However, it doesn't cost us much to copy an empty list, so we do it anyway
// to not blow up if we ever decide copy it in the middle of a transaction
state.accessList = s.accessList.Copy()
state.predicateStorageSlots = copyPredicateStorageSlots(s.predicateStorageSlots)

// If there's a prefetcher running, make an inactive copy of it that can
// only access data but does not actively preload (since the user will not
Expand Down Expand Up @@ -1060,12 +1075,26 @@ func (s *StateDB) PrepareAccessList(sender common.Address, dst *common.Address,
for _, addr := range precompiles {
s.AddAddressToAccessList(addr)
}

for _, el := range list {
s.AddAddressToAccessList(el.Address)
for _, key := range el.StorageKeys {
s.AddSlotToAccessList(el.Address, key)
}
}
s.preparePredicateStorageSlots(list)
}

// preparePredicateStorageSlots populates the predicateStorageSlots field from the transaction's access list
// Note: if an address is specified multiple times in the access list, only the last storage slots provided
// for it are used in predicates.
// During predicate verification, we require that a precompile address is only specififed in the access list
// once to avoid a situation where we verify multiple predicate and only expose data from the last one.
func (s *StateDB) preparePredicateStorageSlots(list types.AccessList) {
aaronbuchwald marked this conversation as resolved.
Show resolved Hide resolved
s.predicateStorageSlots = make(map[common.Address][]byte)
for _, el := range list {
s.predicateStorageSlots[el.Address] = utils.HashSliceToBytes(el.StorageKeys)
}
}

// AddAddressToAccessList adds the given address to the access list
Expand Down Expand Up @@ -1102,3 +1131,13 @@ func (s *StateDB) AddressInAccessList(addr common.Address) bool {
func (s *StateDB) SlotInAccessList(addr common.Address, slot common.Hash) (addressPresent bool, slotPresent bool) {
return s.accessList.Contains(addr, slot)
}

// GetPredicateStorageSlots returns the storage slots associated with a given address, and whether or not
// that address was included in the optional access list of the transaction.
// The storage slots are returned in the same order as they appeared in the transaction.
// These are the same storage slots that are used to verify any transaction
// predicates for transactions with access list addresses that match a precompile address.
func (s *StateDB) GetPredicateStorageSlots(address common.Address) ([]byte, bool) {
storageSlots, exists := s.predicateStorageSlots[address]
return storageSlots, exists
}
2 changes: 2 additions & 0 deletions core/tx_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -1118,6 +1118,8 @@ func (pool *TxPool) HasLocal(hash common.Hash) bool {
return pool.all.GetLocal(hash) != nil
}

// RemoveTx removes a single transaction from the queue, moving all subsequent
// transactions back to the future queue.
func (pool *TxPool) RemoveTx(hash common.Hash) {
pool.mu.Lock()
defer pool.mu.Unlock()
Expand Down
1 change: 1 addition & 0 deletions core/vm/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ type StateDB interface {
Snapshot() int

AddLog(addr common.Address, topics []common.Hash, data []byte, blockNumber uint64)
GetPredicateStorageSlots(address common.Address) ([]byte, bool)
AddPreimage(common.Hash, []byte)

ForEachStorage(common.Address, func(common.Hash, common.Hash) bool) error
Expand Down
Loading