Skip to content

Commit

Permalink
Align on a single predicate type (#813)
Browse files Browse the repository at this point in the history
* Align on single predicate type

* Revert BlockContext comment change

* Update PredicateContext comment

* Address comments
  • Loading branch information
aaronbuchwald authored Aug 25, 2023
1 parent aedcf84 commit 8b4e084
Show file tree
Hide file tree
Showing 12 changed files with 77 additions and 274 deletions.
48 changes: 7 additions & 41 deletions core/predicate_check.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ import (
)

// 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 {
func CheckPredicates(rules params.Rules, predicateContext *precompileconfig.PredicateContext, tx *types.Transaction) error {
// Short circuit early if there are no precompile predicates to verify
if len(rules.Predicates) == 0 {
return nil
}

// Check that the transaction can cover its IntrinsicGas (including the gas required by the predicate) before
// verifying the predicate.
intrinsicGas, err := IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, rules)
Expand All @@ -24,51 +29,12 @@ func CheckPredicates(rules params.Rules, predicateContext *precompileconfig.Prop
if tx.Gas() < intrinsicGas {
return fmt.Errorf("insufficient gas for predicate verification (%d) < intrinsic gas (%d)", tx.Gas(), intrinsicGas)
}
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{}{}
predicateBytes := predicateutils.HashSliceToBytes(accessTuple.StorageKeys)
if err := predicater.VerifyPredicate(predicateContext, predicateBytes); 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]
predicater, ok := rules.Predicates[address]
if !ok {
continue
}
Expand Down
121 changes: 10 additions & 111 deletions core/predicate_check_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,14 @@ import (
"github.com/stretchr/testify/require"
)

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

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

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

Expand All @@ -37,26 +34,9 @@ func (m *mockPredicater) PredicateGas(b []byte) (uint64, error) {
return m.predicateGasFunc(b)
}

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

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

func (m *mockProposerPredicater) PredicateGas(b []byte) (uint64, error) {
if m.predicateGasFunc == nil {
return 0, nil
}
return m.predicateGasFunc(b)
}

type predicateCheckTest struct {
address common.Address
predicater precompileconfig.PrecompilePredicater
proposerPredicater precompileconfig.ProposerPredicater
predicater precompileconfig.Predicater
accessList types.AccessList
gas uint64
emptyProposerBlockCtx bool
Expand All @@ -81,23 +61,17 @@ func TestCheckPredicate(t *testing.T) {
}),
expectedErr: nil,
},
"proposer predicate, no access list passes": {
address: common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"),
gas: 53000,
proposerPredicater: &mockProposerPredicater{predicateFunc: func(*precompileconfig.ProposerPredicateContext, []byte) error { return nil }},
expectedErr: nil,
},
"predicate, no access list passes": {
address: common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"),
gas: 53000,
predicater: &mockPredicater{predicateFunc: func(*precompileconfig.PrecompilePredicateContext, []byte) error { return nil }},
predicater: &mockPredicater{predicateFunc: func(*precompileconfig.PredicateContext, []byte) error { return nil }},
expectedErr: nil,
},
"predicate with valid access list passes": {
address: common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"),
gas: 53000,
predicater: &mockPredicater{
predicateFunc: func(_ *precompileconfig.PrecompilePredicateContext, b []byte) error {
predicateFunc: func(_ *precompileconfig.PredicateContext, b []byte) error {
if !bytes.Equal(b, common.Hash{1}.Bytes()) {
return fmt.Errorf("unexpected bytes: 0x%x", b)
}
Expand All @@ -118,52 +92,7 @@ func TestCheckPredicate(t *testing.T) {
address: common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"),
gas: 153000,
predicater: &mockPredicater{
predicateFunc: func(_ *precompileconfig.PrecompilePredicateContext, b []byte) error {
if !bytes.Equal(b, common.Hash{1}.Bytes()) {
return fmt.Errorf("unexpected bytes: 0x%x", b)
}
return nil
},
predicateGasFunc: func(b []byte) (uint64, error) {
return 100_000, nil
},
},
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"),
gas: 53000,
proposerPredicater: &mockProposerPredicater{
predicateFunc: func(_ *precompileconfig.ProposerPredicateContext, b []byte) error {
if !bytes.Equal(b, common.Hash{1}.Bytes()) {
return fmt.Errorf("unexpected bytes: 0x%x", b)
}
return nil
},
},
accessList: types.AccessList([]types.AccessTuple{
{
Address: common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"),
StorageKeys: []common.Hash{
{1},
},
},
}),
expectedErr: nil,
},
"proposer predicate with valid access list and non-empty PredicateGas passes": {
address: common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"),
gas: 153000,
proposerPredicater: &mockProposerPredicater{
predicateFunc: func(_ *precompileconfig.ProposerPredicateContext, b []byte) error {
predicateFunc: func(_ *precompileconfig.PredicateContext, b []byte) error {
if !bytes.Equal(b, common.Hash{1}.Bytes()) {
return fmt.Errorf("unexpected bytes: 0x%x", b)
}
Expand All @@ -187,28 +116,7 @@ func TestCheckPredicate(t *testing.T) {
address: common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"),
gas: 53000,
predicater: &mockPredicater{
predicateFunc: func(_ *precompileconfig.PrecompilePredicateContext, b []byte) error {
if !bytes.Equal(b, common.Hash{1}.Bytes()) {
return fmt.Errorf("unexpected bytes: 0x%x", b)
}
return nil
},
},
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"),
gas: 53000,
proposerPredicater: &mockProposerPredicater{
predicateFunc: func(_ *precompileconfig.ProposerPredicateContext, b []byte) error {
predicateFunc: func(_ *precompileconfig.PredicateContext, b []byte) error {
if !bytes.Equal(b, common.Hash{1}.Bytes()) {
return fmt.Errorf("unexpected bytes: 0x%x", b)
}
Expand All @@ -225,31 +133,22 @@ func TestCheckPredicate(t *testing.T) {
}),
expectedErr: fmt.Errorf("unexpected bytes: 0x%x", common.Hash{2}.Bytes()),
},
"proposer predicate with empty proposer block ctx passes": {
address: common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"),
gas: 53000,
proposerPredicater: &mockProposerPredicater{predicateFunc: func(_ *precompileconfig.ProposerPredicateContext, b []byte) error { return nil }},
emptyProposerBlockCtx: true,
},
} {
test := test
t.Run(name, func(t *testing.T) {
require := require.New(t)
// Create the rules from TestChainConfig and update the predicates based on the test params
rules := params.TestChainConfig.AvalancheRules(common.Big0, 0)
if test.proposerPredicater != nil {
rules.ProposerPredicates[test.address] = test.proposerPredicater
}
if test.predicater != nil {
rules.PredicatePrecompiles[test.address] = test.predicater
rules.Predicates[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,
Gas: test.gas,
})
predicateContext := &precompileconfig.ProposerPredicateContext{}
predicateContext := &precompileconfig.PredicateContext{}
if !test.emptyProposerBlockCtx {
predicateContext.ProposerVMBlockCtx = &block.Context{}
}
Expand Down
17 changes: 3 additions & 14 deletions core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,8 @@ func accessListGas(rules params.Rules, accessList types.AccessList) (uint64, err

for _, accessTuple := range accessList {
address := accessTuple.Address
if !rules.PredicateExists(address) {
predicate, ok := rules.Predicates[address]
if !ok {
// Previous access list gas calculation does not use safemath because an overflow would not be possible with
// the size of access lists that could be included in a block and standard access list gas costs.
// Therefore, we only check for overflow when adding to [totalGas], which could include the sum of values
Expand All @@ -156,7 +157,7 @@ func accessListGas(rules params.Rules, accessList types.AccessList) (uint64, err
}
gas = totalGas
} else {
predicateGas, err := applyPredicateGas(rules, accessTuple)
predicateGas, err := predicate.PredicateGas(predicateutils.HashSliceToBytes(accessTuple.StorageKeys))
if err != nil {
return 0, err
}
Expand All @@ -180,18 +181,6 @@ func toWordSize(size uint64) uint64 {
return (size + 31) / 32
}

func applyPredicateGas(rules params.Rules, accessTuple types.AccessTuple) (uint64, error) {
predicate, ok := rules.PredicatePrecompiles[accessTuple.Address]
if ok {
return predicate.PredicateGas(predicateutils.HashSliceToBytes(accessTuple.StorageKeys))
}
proposerPredicate, ok := rules.ProposerPredicates[accessTuple.Address]
if !ok {
return 0, nil
}
return proposerPredicate.PredicateGas(predicateutils.HashSliceToBytes(accessTuple.StorageKeys))
}

// A Message contains the data derived from a single transaction that is relevant to state
// processing.
type Message struct {
Expand Down
2 changes: 1 addition & 1 deletion miner/miner.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func (miner *Miner) SetEtherbase(addr common.Address) {
miner.worker.setEtherbase(addr)
}

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

Expand Down
6 changes: 3 additions & 3 deletions miner/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ type environment struct {
size uint64

rules params.Rules
predicateContext *precompileconfig.ProposerPredicateContext
predicateContext *precompileconfig.PredicateContext

start time.Time // Time that block building began
}
Expand Down Expand Up @@ -117,7 +117,7 @@ func (w *worker) setEtherbase(addr common.Address) {
}

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

Expand Down Expand Up @@ -219,7 +219,7 @@ func (w *worker) commitNewWork(predicateContext *precompileconfig.ProposerPredic
return w.commit(env)
}

func (w *worker) createCurrentEnvironment(predicateContext *precompileconfig.ProposerPredicateContext, parent *types.Header, header *types.Header, tstart time.Time) (*environment, error) {
func (w *worker) createCurrentEnvironment(predicateContext *precompileconfig.PredicateContext, parent *types.Header, header *types.Header, tstart time.Time) (*environment, error) {
state, err := w.chain.StateAt(parent.Root)
if err != nil {
return nil, err
Expand Down
27 changes: 8 additions & 19 deletions params/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -386,16 +386,12 @@ func (c *ChainConfig) IsDUpgrade(time uint64) bool {
}

func (r *Rules) PredicatesExist() bool {
return len(r.PredicatePrecompiles) > 0 || len(r.ProposerPredicates) > 0
return len(r.Predicates) > 0
}

func (r *Rules) PredicateExists(addr common.Address) bool {
_, predicateExists := r.PredicatePrecompiles[addr]
if predicateExists {
return true
}
_, proposerPredicateExists := r.ProposerPredicates[addr]
return proposerPredicateExists
_, predicateExists := r.Predicates[addr]
return predicateExists
}

// IsPrecompileEnabled returns whether precompile with [address] is enabled at [timestamp].
Expand Down Expand Up @@ -723,12 +719,9 @@ type Rules struct {
// Note: none of these addresses should conflict with the address space used by
// any existing precompiles.
ActivePrecompiles map[common.Address]precompileconfig.Config
// PrecompilePredicates maps addresses to stateful precompile predicate functions
// Predicates maps addresses to stateful precompile predicate functions
// that are enabled for this rule set.
PredicatePrecompiles map[common.Address]precompileconfig.PrecompilePredicater
// ProposerPredicates maps addresses to stateful precompile predicate functions
// that are enabled for this rule set and require access to the ProposerVM wrapper.
ProposerPredicates map[common.Address]precompileconfig.ProposerPredicater
Predicates map[common.Address]precompileconfig.Predicater
// AccepterPrecompiles map addresses to stateful precompile accepter functions
// that are enabled for this rule set.
AccepterPrecompiles map[common.Address]precompileconfig.Accepter
Expand Down Expand Up @@ -769,17 +762,13 @@ func (c *ChainConfig) AvalancheRules(blockNum *big.Int, timestamp uint64) Rules

// Initialize the stateful precompiles that should be enabled at [blockTimestamp].
rules.ActivePrecompiles = make(map[common.Address]precompileconfig.Config)
rules.PredicatePrecompiles = make(map[common.Address]precompileconfig.PrecompilePredicater)
rules.ProposerPredicates = make(map[common.Address]precompileconfig.ProposerPredicater)
rules.Predicates = make(map[common.Address]precompileconfig.Predicater)
rules.AccepterPrecompiles = make(map[common.Address]precompileconfig.Accepter)
for _, module := range modules.RegisteredModules() {
if config := c.getActivePrecompileConfig(module.Address, timestamp); config != nil && !config.IsDisabled() {
rules.ActivePrecompiles[module.Address] = config
if precompilePredicate, ok := config.(precompileconfig.PrecompilePredicater); ok {
rules.PredicatePrecompiles[module.Address] = precompilePredicate
}
if proposerPredicate, ok := config.(precompileconfig.ProposerPredicater); ok {
rules.ProposerPredicates[module.Address] = proposerPredicate
if predicate, ok := config.(precompileconfig.Predicater); ok {
rules.Predicates[module.Address] = predicate
}
if precompileAccepter, ok := config.(precompileconfig.Accepter); ok {
rules.AccepterPrecompiles[module.Address] = precompileAccepter
Expand Down
Loading

0 comments on commit 8b4e084

Please sign in to comment.