Skip to content

Commit

Permalink
Add support for predicate gas into IntrinsicGas (#713)
Browse files Browse the repository at this point in the history
* Add support for predicate gas

* Address review

* Fix test and add test for PredicateGas

* Address comments
  • Loading branch information
aaronbuchwald authored Jul 7, 2023
1 parent 75c3486 commit 2082f8a
Show file tree
Hide file tree
Showing 12 changed files with 317 additions and 44 deletions.
2 changes: 1 addition & 1 deletion core/bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) {
return func(i int, gen *BlockGen) {
toaddr := common.Address{}
data := make([]byte, nbytes)
gas, _ := IntrinsicGas(data, nil, false, false, false)
gas, _ := IntrinsicGas(data, nil, false, params.Rules{}) // Disable Istanbul and EIP-2028 for this test
tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(benchRootAddr), toaddr, big.NewInt(1), gas, big.NewInt(225000000000), data), types.HomesteadSigner{}, benchRootKey)
gen.AddTx(tx)
}
Expand Down
9 changes: 9 additions & 0 deletions core/predicate_check.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ 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 {
// 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)
if err != nil {
return err
}
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
}
Expand Down
139 changes: 111 additions & 28 deletions core/predicate_check_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,36 +22,55 @@ var (
)

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

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

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

type mockProposerPredicater struct {
predicateFunc func(*precompileconfig.ProposerPredicateContext, []byte) error
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
accessList types.AccessList
gas uint64
emptyProposerBlockCtx bool
expectedErr error
}

func TestCheckPredicate(t *testing.T) {
for name, test := range map[string]predicateCheckTest{
"no predicates, no access list passes": {
gas: 53000,
expectedErr: nil,
},
"no predicates, with access list passes": {
gas: 57300,
accessList: types.AccessList([]types.AccessTuple{
{
Address: common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"),
Expand All @@ -64,23 +83,51 @@ func TestCheckPredicate(t *testing.T) {
},
"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 }},
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()) {
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{
{1},
},
},
}),
expectedErr: nil,
},
"predicate with valid access list and non-empty PredicateGas passes": {
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
} else {
return fmt.Errorf("unexpected bytes: 0x%x", b)
}
}},
},
predicateGasFunc: func(b []byte) (uint64, error) {
return 100_000, nil
},
},
accessList: types.AccessList([]types.AccessTuple{
{
Address: common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"),
Expand All @@ -93,13 +140,39 @@ func TestCheckPredicate(t *testing.T) {
},
"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()) {
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
} 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 and non-empty PredicateGas passes": {
address: common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"),
gas: 153000,
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
},
predicateGasFunc: func(b []byte) (uint64, error) {
return 100_000, nil
},
},
accessList: types.AccessList([]types.AccessTuple{
{
Address: common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"),
Expand All @@ -112,13 +185,15 @@ func TestCheckPredicate(t *testing.T) {
},
"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()) {
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
} else {
return fmt.Errorf("unexpected bytes: 0x%x", b)
}
}},
},
},
accessList: types.AccessList([]types.AccessTuple{
{
Address: common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"),
Expand All @@ -131,13 +206,15 @@ func TestCheckPredicate(t *testing.T) {
},
"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()) {
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
} else {
return fmt.Errorf("unexpected bytes: 0x%x", b)
}
}},
},
},
accessList: types.AccessList([]types.AccessTuple{
{
Address: common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"),
Expand All @@ -150,12 +227,14 @@ func TestCheckPredicate(t *testing.T) {
},
"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, common.Big0)
if test.proposerPredicater != nil {
Expand All @@ -168,17 +247,21 @@ func TestCheckPredicate(t *testing.T) {
// 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{}
if !test.emptyProposerBlockCtx {
predicateContext.ProposerVMBlockCtx = &block.Context{}
}
err := CheckPredicates(rules, predicateContext, tx)
if test.expectedErr == nil {
require.NoError(t, err)
require.NoError(err)
} else {
require.ErrorContains(t, err, test.expectedErr.Error())
require.ErrorContains(err, test.expectedErr.Error())
}
intrinsicGas, err := IntrinsicGas(tx.Data(), tx.AccessList(), true, rules)
require.NoError(err)
require.Equal(tx.Gas(), intrinsicGas) // Require test specifies exact amount of gas consumed
})
}
}
17 changes: 17 additions & 0 deletions core/state/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,18 @@ func (s *StateDB) Logs() []*types.Log {
return logs
}

// GetLogData returns the underlying data from each log included in the StateDB
// Test helper function.
func (s *StateDB) GetLogData() [][]byte {
var logData [][]byte
for _, lgs := range s.logs {
for _, log := range lgs {
logData = append(logData, common.CopyBytes(log.Data))
}
}
return logData
}

// AddPreimage records a SHA3 preimage seen by the VM.
func (s *StateDB) AddPreimage(hash common.Hash, preimage []byte) {
if _, ok := s.preimages[hash]; !ok {
Expand Down Expand Up @@ -1145,3 +1157,8 @@ func (s *StateDB) GetPredicateStorageSlots(address common.Address) ([]byte, bool
storageSlots, exists := s.predicateStorageSlots[address]
return storageSlots, exists
}

// SetPredicateStorageSlots sets the predicate storage slots for the given address
func (s *StateDB) SetPredicateStorageSlots(address common.Address, predicate []byte) {
s.predicateStorageSlots[address] = predicate
}
Loading

0 comments on commit 2082f8a

Please sign in to comment.