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 2 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
49 changes: 49 additions & 0 deletions core/predicate_check.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// (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/contract"
"github.com/ava-labs/subnet-evm/precompile/modules"
"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 *contract.PredicateContext, tx *types.Transaction) error {
precompileConfigs := rules.ActivePrecompiles
// Track addresses that we've performed a predicate check for
precompileAddressChecks := make(map[common.Address]struct{})
for _, accessTuple := range tx.AccessList() {
aaronbuchwald marked this conversation as resolved.
Show resolved Hide resolved
address := accessTuple.Address
_, isPrecompile := precompileConfigs[address]
if !isPrecompile {
continue
}

module, ok := modules.GetPrecompileModuleByAddress(address)
if !ok {
return fmt.Errorf("predicate accessed precompile config under address %s with no registered module for tx %s", address, tx.Hash())
}
predicater, ok := module.Contract.(contract.Predicater)
aaronbuchwald marked this conversation as resolved.
Show resolved Hide resolved
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
}
55 changes: 44 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)
aaronbuchwald marked this conversation as resolved.
Show resolved Hide resolved
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,11 +1075,19 @@ func (s *StateDB) PrepareAccessList(sender common.Address, dst *common.Address,
for _, addr := range precompiles {
s.AddAddressToAccessList(addr)
}

// Note: If an address is specified multiple times in the access list, only the
// last storage slots provided for it are used in predicates.
aaronbuchwald marked this conversation as resolved.
Show resolved Hide resolved
// During predicate verification, we enforce that a precompile address can only
// have one access tuple per address to avoid a situation where we verify multiple
// predicates and only expose the data from the last one.
s.predicateStorageSlots = make(map[common.Address][]byte)
aaronbuchwald marked this conversation as resolved.
Show resolved Hide resolved
for _, el := range list {
s.AddAddressToAccessList(el.Address)
for _, key := range el.StorageKeys {
s.AddSlotToAccessList(el.Address, key)
}
s.predicateStorageSlots[el.Address] = utils.HashSliceToBytes(el.StorageKeys)
}
}

Expand Down Expand Up @@ -1102,3 +1125,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
5 changes: 3 additions & 2 deletions miner/miner.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
"github.com/ava-labs/subnet-evm/core"
"github.com/ava-labs/subnet-evm/core/types"
"github.com/ava-labs/subnet-evm/params"
"github.com/ava-labs/subnet-evm/precompile/contract"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/event"
)
Expand Down Expand Up @@ -62,8 +63,8 @@ func (miner *Miner) SetEtherbase(addr common.Address) {
miner.worker.setEtherbase(addr)
}

func (miner *Miner) GenerateBlock() (*types.Block, error) {
return miner.worker.commitNewWork()
func (miner *Miner) GenerateBlock(predicateContext *contract.PredicateContext) (*types.Block, error) {
return miner.worker.commitNewWork(predicateContext)
}

// SubscribePendingLogs starts delivering logs from pending transactions
Expand Down
36 changes: 34 additions & 2 deletions miner/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import (
"github.com/ava-labs/subnet-evm/core/state"
"github.com/ava-labs/subnet-evm/core/types"
"github.com/ava-labs/subnet-evm/params"
"github.com/ava-labs/subnet-evm/precompile/contract"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
Expand Down Expand Up @@ -112,7 +113,7 @@ func (w *worker) setEtherbase(addr common.Address) {
}

// commitNewWork generates several new sealing tasks based on the parent block.
func (w *worker) commitNewWork() (*types.Block, error) {
func (w *worker) commitNewWork(predicateContext *contract.PredicateContext) (*types.Block, error) {
w.mu.RLock()
defer w.mu.RUnlock()

Expand Down Expand Up @@ -192,8 +193,11 @@ func (w *worker) commitNewWork() (*types.Block, error) {
return nil, err
}

// Fill the block with all available pending transactions.
// Get the pending txs from TxPool
pending := w.eth.TxPool().Pending(true)
// Filter out transactions that don't satisfy predicateContext and remove them from TxPool
rules := w.chainConfig.AvalancheRules(header.Number, new(big.Int).SetUint64(header.Time))
pending = w.enforcePredicates(rules, predicateContext, pending)
aaronbuchwald marked this conversation as resolved.
Show resolved Hide resolved

// Split the pending transactions into locals and remotes
localTxs := make(map[common.Address]types.Transactions)
Expand Down Expand Up @@ -391,3 +395,31 @@ func totalFees(block *types.Block, receipts []*types.Receipt) *big.Float {
}
return new(big.Float).Quo(new(big.Float).SetInt(feesWei), new(big.Float).SetInt(big.NewInt(params.Ether)))
}

// enforcePredicates takes a set of pending transactions (grouped by sender, and ordered by nonce)
// and returns the subset of those transactions (following the same grouping) that satisfy predicateContext.
// Any transaction that fails predicate verification will be removed from the tx pool and excluded
// from the return value.
// Transactions with a nonce that follows a removed transaction will be added back to the future
// queue of the tx pool.
func (w *worker) enforcePredicates(
rules params.Rules,
predicateContext *contract.PredicateContext,
pending map[common.Address]types.Transactions,
) map[common.Address]types.Transactions {
result := make(map[common.Address]types.Transactions, len(pending))
for addr, txs := range pending {
for i, tx := range txs {
if err := core.CheckPredicates(rules, predicateContext, tx); err != nil {
aaronbuchwald marked this conversation as resolved.
Show resolved Hide resolved
log.Debug("Transaction predicate failed verification in miner", "sender", addr, "err", err)
aaronbuchwald marked this conversation as resolved.
Show resolved Hide resolved
aaronbuchwald marked this conversation as resolved.
Show resolved Hide resolved
w.eth.TxPool().RemoveTx(tx.Hash()) // RemoveTx will move all subsequent transactions back to the future queue
aaronbuchwald marked this conversation as resolved.
Show resolved Hide resolved
txs = txs[:i] // Cut off any transactions past the failed predicate
break
}
}
if len(txs) > 0 {
result[addr] = txs
}
}
return result
}
Loading