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

verify single predicate #960

Merged
merged 23 commits into from
Dec 5, 2023
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
6a0aa58
verify single predicate
ceyonur Oct 18, 2023
08e7adc
Merge branch 'master' into single-predicate-verify
ceyonur Oct 18, 2023
b1ea976
simplify returned error
ceyonur Oct 19, 2023
cc64621
Merge branch 'master' into single-predicate-verify
ceyonur Oct 20, 2023
c433b3c
Merge branch 'master' into single-predicate-verify
ceyonur Oct 26, 2023
fce2296
Merge branch 'master' into single-predicate-verify
ceyonur Nov 15, 2023
33be09b
Merge branch 'master' into single-predicate-verify
maru-ava Nov 21, 2023
63344fc
track indexes for predicates
ceyonur Nov 23, 2023
4156cee
Merge branch 'single-predicate-verify' of github.com:ava-labs/subnet-…
ceyonur Nov 23, 2023
fb9b6f2
Merge branch 'master' into single-predicate-verify
ceyonur Nov 23, 2023
ed1a161
don't short cirtcuit predicate err
ceyonur Nov 24, 2023
3ea2f87
Merge branch 'single-predicate-verify' of github.com:ava-labs/subnet-…
ceyonur Nov 24, 2023
1ec31ab
Update predicate_check.go
ceyonur Nov 28, 2023
f66b39d
Merge branch 'master' into single-predicate-verify
ceyonur Nov 28, 2023
56b9b46
nits for single-predicate-verify (#1012)
darioush Dec 1, 2023
b894adc
Update x/warp/predicate_test.go
ceyonur Dec 4, 2023
0c08519
Update x/warp/predicate_test.go
ceyonur Dec 4, 2023
84fc639
review fixes
ceyonur Dec 4, 2023
b3dcfa8
Merge branch 'single-predicate-verify' of github.com:ava-labs/subnet-…
ceyonur Dec 4, 2023
a46a06f
Merge branch 'master' into single-predicate-verify
ceyonur Dec 4, 2023
6ff9578
use bitset constructor
ceyonur Dec 4, 2023
b1ff9db
change return type to err in verifypredicate
ceyonur Dec 5, 2023
6d7f8e7
Merge branch 'master' into single-predicate-verify
ceyonur Dec 5, 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
57 changes: 35 additions & 22 deletions core/predicate_check.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ import (
"errors"
"fmt"

"github.com/ava-labs/avalanchego/utils/set"
"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/predicate"
"github.com/ava-labs/subnet-evm/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
)
Expand All @@ -31,30 +32,42 @@ func CheckPredicates(rules params.Rules, predicateContext *precompileconfig.Pred

predicateResults := make(map[common.Address][]byte)
// Short circuit early if there are no precompile predicates to verify
if len(rules.Predicaters) == 0 {
if !rules.PredicatersExist() {
return predicateResults, nil
}

// Prepare the predicate storage slots from the transaction's access list
predicateArguments := predicate.PreparePredicateStorageSlots(rules, tx.AccessList())

// If there are no predicates to verify, return early and skip requiring the proposervm block
// context to be populated.
if len(predicateArguments) == 0 {
return predicateResults, nil
}

if predicateContext == nil || predicateContext.ProposerVMBlockCtx == nil {
return nil, ErrMissingPredicateContext
}

for address, predicates := range predicateArguments {
// Since [address] is only added to [predicateArguments] when there's a valid predicate in the ruleset
// there's no need to check if the predicate exists here.
predicaterContract := rules.Predicaters[address]
res := predicaterContract.VerifyPredicate(predicateContext, predicates)
log.Debug("predicate verify", "tx", tx.Hash(), "address", address, "res", res)
marun marked this conversation as resolved.
Show resolved Hide resolved
predicateResults[address] = res
predicateIndexes := make(map[common.Address]int)
for _, al := range tx.AccessList() {
address := al.Address
predicaterContract, exists := rules.Predicaters[address]
if !exists {
continue
}
// Invariant: We should return this error only if there is a predicate in txs.
// If there is no predicate in txs, we should just return an empty result with no error.
if predicateContext == nil || predicateContext.ProposerVMBlockCtx == nil {
darioush marked this conversation as resolved.
Show resolved Hide resolved
return nil, ErrMissingPredicateContext
}
verified := predicaterContract.VerifyPredicate(predicateContext, utils.HashSliceToBytes(al.StorageKeys))
log.Debug("predicate verify", "tx", tx.Hash(), "address", address, "verified", verified)
// Add bitset only if predicate is not verified
if !verified {
resultBitSet := set.NewBits()
currentResult, ok := predicateResults[address]
if ok {
resultBitSet = set.BitsFromBytes(currentResult)
}
// this will default to 0 if the address is not in the map
currentIndex := predicateIndexes[address]
resultBitSet.Add(currentIndex)
predicateResults[address] = resultBitSet.Bytes()
}
// add an empty byte to indicate that the predicate was verified
// for the address
if _, ok := predicateResults[address]; !ok {
predicateResults[address] = []byte{}
}
predicateIndexes[address]++
}

return predicateResults, nil
Expand Down
156 changes: 146 additions & 10 deletions core/predicate_check_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@ func TestCheckPredicate(t *testing.T) {
addr2 := common.HexToAddress("0xbb")
addr3 := common.HexToAddress("0xcc")
addr4 := common.HexToAddress("0xdd")
predicateResultBytes1 := []byte{1, 2, 3}
predicateResultBytes2 := []byte{3, 2, 1}
predicateContext := &precompileconfig.PredicateContext{
ProposerVMBlockCtx: &block.Context{
PChainHeight: 10,
Expand Down Expand Up @@ -142,7 +140,7 @@ func TestCheckPredicate(t *testing.T) {
predicater := precompileconfig.NewMockPredicater(gomock.NewController(t))
arg := common.Hash{1}
predicater.EXPECT().PredicateGas(arg[:]).Return(uint64(0), nil).Times(2)
predicater.EXPECT().VerifyPredicate(gomock.Any(), [][]byte{arg[:]}).Return(predicateResultBytes1)
predicater.EXPECT().VerifyPredicate(gomock.Any(), arg[:]).Return(true)
return map[common.Address]precompileconfig.Predicater{
addr1: predicater,
}
Expand All @@ -156,7 +154,7 @@ func TestCheckPredicate(t *testing.T) {
},
}),
expectedRes: map[common.Address][]byte{
addr1: predicateResultBytes1,
addr1: {}, // valid bytes
},
expectedErr: nil,
},
Expand Down Expand Up @@ -188,7 +186,7 @@ func TestCheckPredicate(t *testing.T) {
predicater := precompileconfig.NewMockPredicater(gomock.NewController(t))
arg := common.Hash{1}
predicater.EXPECT().PredicateGas(arg[:]).Return(uint64(0), nil).Times(2)
predicater.EXPECT().VerifyPredicate(gomock.Any(), [][]byte{arg[:]}).Return(predicateResultBytes1)
predicater.EXPECT().VerifyPredicate(gomock.Any(), arg[:]).Return(true)
return map[common.Address]precompileconfig.Predicater{
addr1: predicater,
addr2: predicater,
Expand All @@ -203,7 +201,7 @@ func TestCheckPredicate(t *testing.T) {
},
}),
expectedRes: map[common.Address][]byte{
addr1: predicateResultBytes1,
addr1: {}, // valid bytes
},
expectedErr: nil,
},
Expand All @@ -215,11 +213,11 @@ func TestCheckPredicate(t *testing.T) {
predicate1 := precompileconfig.NewMockPredicater(ctrl)
arg1 := common.Hash{1}
predicate1.EXPECT().PredicateGas(arg1[:]).Return(uint64(0), nil).Times(2)
predicate1.EXPECT().VerifyPredicate(gomock.Any(), [][]byte{arg1[:]}).Return(predicateResultBytes1)
predicate1.EXPECT().VerifyPredicate(gomock.Any(), arg1[:]).Return(true)
predicate2 := precompileconfig.NewMockPredicater(ctrl)
arg2 := common.Hash{2}
predicate2.EXPECT().PredicateGas(arg2[:]).Return(uint64(0), nil).Times(2)
predicate2.EXPECT().VerifyPredicate(gomock.Any(), [][]byte{arg2[:]}).Return(predicateResultBytes2)
predicate2.EXPECT().VerifyPredicate(gomock.Any(), arg2[:]).Return(false)
return map[common.Address]precompileconfig.Predicater{
addr1: predicate1,
addr2: predicate2,
Expand All @@ -240,8 +238,8 @@ func TestCheckPredicate(t *testing.T) {
},
}),
expectedRes: map[common.Address][]byte{
addr1: predicateResultBytes1,
addr2: predicateResultBytes2,
addr1: {}, // valid bytes
addr2: {1}, // invalid bytes
aaronbuchwald marked this conversation as resolved.
Show resolved Hide resolved
},
expectedErr: nil,
},
Expand Down Expand Up @@ -322,3 +320,141 @@ func TestCheckPredicate(t *testing.T) {
})
}
}

func TestCheckPredicatesOutput(t *testing.T) {
addr1 := common.HexToAddress("0xaa")
addr2 := common.HexToAddress("0xbb")
validHash := common.Hash{1}
invalidHash := common.Hash{2}
predicateContext := &precompileconfig.PredicateContext{
ProposerVMBlockCtx: &block.Context{
PChainHeight: 10,
},
}
type testTuple struct {
address common.Address
isValidPredicate bool
}
type resultTest struct {
name string
expectedRes map[common.Address][]byte
testTuple []testTuple
}
tests := []resultTest{
{name: "no predicates", expectedRes: map[common.Address][]byte{}},
{
name: "one address one predicate",
testTuple: []testTuple{
{address: addr1, isValidPredicate: true},
},
expectedRes: map[common.Address][]byte{addr1: {}},
},
{
name: "one address one invalid predicate",
testTuple: []testTuple{
{address: addr1, isValidPredicate: false},
},
expectedRes: map[common.Address][]byte{addr1: {1}},
aaronbuchwald marked this conversation as resolved.
Show resolved Hide resolved
},
{
name: "one address two invalid predicates",
testTuple: []testTuple{
{address: addr1, isValidPredicate: false},
{address: addr1, isValidPredicate: false},
},
expectedRes: map[common.Address][]byte{addr1: {3}},
},
{
name: "one address two mixed predicates",
testTuple: []testTuple{
{address: addr1, isValidPredicate: true},
{address: addr1, isValidPredicate: false},
},
expectedRes: map[common.Address][]byte{addr1: {2}},
},
{
name: "one address mixed predicates",
testTuple: []testTuple{
{address: addr1, isValidPredicate: true},
{address: addr1, isValidPredicate: false},
{address: addr1, isValidPredicate: false},
{address: addr1, isValidPredicate: true},
},
expectedRes: map[common.Address][]byte{addr1: {6}},
},
{
name: "two addresses mixed predicates",
testTuple: []testTuple{
{address: addr1, isValidPredicate: true},
{address: addr2, isValidPredicate: false},
{address: addr1, isValidPredicate: false},
{address: addr1, isValidPredicate: false},
{address: addr2, isValidPredicate: true},
{address: addr2, isValidPredicate: true},
{address: addr2, isValidPredicate: false},
{address: addr2, isValidPredicate: true},
},
expectedRes: map[common.Address][]byte{addr1: {6}, addr2: {9}},
},
{
name: "two addresses all valid predicates",
testTuple: []testTuple{
{address: addr1, isValidPredicate: true},
{address: addr2, isValidPredicate: true},
{address: addr1, isValidPredicate: true},
{address: addr1, isValidPredicate: true},
},
expectedRes: map[common.Address][]byte{addr1: {}, addr2: {}},
},
{
name: "two addresses all invalid predicates",
testTuple: []testTuple{
{address: addr1, isValidPredicate: false},
{address: addr2, isValidPredicate: false},
{address: addr1, isValidPredicate: false},
{address: addr1, isValidPredicate: false},
},
expectedRes: map[common.Address][]byte{addr1: {7}, addr2: {1}},
},
}
for _, test := range tests {
t.Run(test.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)
predicater := precompileconfig.NewMockPredicater(gomock.NewController(t))
marun marked this conversation as resolved.
Show resolved Hide resolved
predicater.EXPECT().PredicateGas(gomock.Any()).Return(uint64(0), nil).Times(len(test.testTuple))
validPredicateCount := 0

var txAccessList types.AccessList
for _, tuple := range test.testTuple {
predicateHash := invalidHash
if tuple.isValidPredicate {
validPredicateCount++
predicateHash = validHash
}
txAccessList = append(txAccessList, types.AccessTuple{
Address: tuple.address,
StorageKeys: []common.Hash{
predicateHash,
},
})
}

invalidPredicateCount := len(test.testTuple) - validPredicateCount
predicater.EXPECT().VerifyPredicate(gomock.Any(), validHash[:]).Return(true).Times(validPredicateCount)
predicater.EXPECT().VerifyPredicate(gomock.Any(), invalidHash[:]).Return(false).Times(invalidPredicateCount)
rules.Predicaters[addr1] = predicater
rules.Predicaters[addr2] = predicater

// Specify only the access list, since this test should not depend on any other values
tx := types.NewTx(&types.DynamicFeeTx{
AccessList: txAccessList,
Gas: 53000,
})
aaronbuchwald marked this conversation as resolved.
Show resolved Hide resolved
predicateRes, err := CheckPredicates(rules, predicateContext, tx)
require.NoError(err)
require.Equal(test.expectedRes, predicateRes)
})
}
}
4 changes: 2 additions & 2 deletions precompile/precompileconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ type PredicateContext struct {
// will not maintain backwards compatibility of this interface and your code should not
// rely on this. Designed for use only by precompiles that ship with subnet-evm.
type Predicater interface {
PredicateGas(storageSlots []byte) (uint64, error)
VerifyPredicate(predicateContext *PredicateContext, predicates [][]byte) []byte
PredicateGas(predicateBytes []byte) (uint64, error)
VerifyPredicate(predicateContext *PredicateContext, predicateBytes []byte) bool
}

ceyonur marked this conversation as resolved.
Show resolved Hide resolved
// SharedMemoryWriter defines an interface to allow a precompile's Accepter to write operations
Expand Down
4 changes: 2 additions & 2 deletions precompile/precompileconfig/mocks.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

44 changes: 11 additions & 33 deletions precompile/testutils/test_predicate.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"time"

"github.com/ava-labs/subnet-evm/precompile/precompileconfig"
cmath "github.com/ethereum/go-ethereum/common/math"
"github.com/stretchr/testify/require"
)

Expand All @@ -18,48 +17,27 @@ type PredicateTest struct {

PredicateContext *precompileconfig.PredicateContext

StorageSlots [][]byte
Gas uint64
GasErr error
PredicateRes []byte
PredicateBytes []byte
Gas uint64
GasErr error
ExpectedRes bool
aaronbuchwald marked this conversation as resolved.
Show resolved Hide resolved
}

func (test PredicateTest) Run(t testing.TB) {
t.Helper()
require := require.New(t)
predicate := test.Config.(precompileconfig.Predicater)

var (
gas uint64
gasErr error
predicateRes []byte
predicate = test.Config.(precompileconfig.Predicater)
)

for _, predicateBytes := range test.StorageSlots {
predicateGas, predicateGasErr := predicate.PredicateGas(predicateBytes)
if predicateGasErr != nil {
gasErr = predicateGasErr
break
}
updatedGas, overflow := cmath.SafeAdd(gas, predicateGas)
if overflow {
panic("predicate gas should not overflow")
}
gas = updatedGas
}

predicateGas, predicateGasErr := predicate.PredicateGas(test.PredicateBytes)
require.ErrorIs(predicateGasErr, test.GasErr)
if test.GasErr != nil {
// If PredicateGas returns an error, the predicate fails verification and we will
// never call VerifyPredicate.
require.ErrorIs(gasErr, test.GasErr)
return
} else {
require.NoError(gasErr)
}
require.Equal(test.Gas, gas)

predicateRes = predicate.VerifyPredicate(test.PredicateContext, test.StorageSlots)
require.Equal(test.PredicateRes, predicateRes)
require.Equal(test.Gas, predicateGas)

predicateRes := predicate.VerifyPredicate(test.PredicateContext, test.PredicateBytes)
require.Equal(test.ExpectedRes, predicateRes)
}

func RunPredicateTests(t *testing.T, predicateTests map[string]PredicateTest) {
Expand Down
Loading
Loading