-
Notifications
You must be signed in to change notification settings - Fork 241
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* 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
1 parent
8a28b23
commit 8f8ae38
Showing
21 changed files
with
790 additions
and
46 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()) | ||
} | ||
}) | ||
} | ||
} |
Oops, something went wrong.