ethapi: introduce tests for transaction options

This commit is contained in:
Matus Kysel 2023-03-29 21:55:41 +02:00
parent 26c69a15ba
commit f8c5f55652
No known key found for this signature in database
GPG Key ID: 6A9168A10A6B501C
5 changed files with 220 additions and 26 deletions

@ -680,7 +680,14 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa
// SendTransaction updates the pending block to include the given transaction.
func (b *SimulatedBackend) SendTransactionConditional(ctx context.Context, tx *types.Transaction, opts ethapi.TransactionOpts) error {
return nil
state, err := b.blockchain.State()
if err != nil {
return err
}
if err := opts.Check(b.pendingBlock.NumberU64(), b.pendingBlock.Time(), state); err != nil {
return err
}
return b.SendTransaction(ctx, tx)
}
// FilterLogs executes a log filter operation, blocking during execution and

@ -2144,11 +2144,12 @@ func (s *PublicTransactionPoolAPI) SendRawTransactionConditional(ctx context.Con
if err := tx.UnmarshalBinary(input); err != nil {
return common.Hash{}, err
}
state, _, err := s.b.StateAndHeaderByNumber(ctx, rpc.BlockNumber(s.b.CurrentBlock().Number().Int64()))
header := s.b.CurrentHeader()
state, _, err := s.b.StateAndHeaderByNumber(ctx, rpc.BlockNumber(header.Number.Int64()))
if state == nil || err != nil {
return common.Hash{}, err
}
if err := opts.Check(s.b.CurrentBlock().NumberU64(), s.b.CurrentBlock().Time(), state); err != nil {
if err := opts.Check(header.Number.Uint64(), header.Time, state); err != nil {
return common.Hash{}, err
}
return SubmitTransaction(ctx, s.b, tx)

@ -0,0 +1,58 @@
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
package ethapi
import (
"encoding/json"
"github.com/ethereum/go-ethereum/common/hexutil"
)
// MarshalJSON marshals as JSON.
func (t TransactionOpts) MarshalJSON() ([]byte, error) {
type TransactionOpts struct {
KnownAccounts KnownAccounts `json:"knownAccounts"`
BlockNumberMin *hexutil.Uint64 `json:"blockNumberMin,omitempty"`
BlockNumberMax *hexutil.Uint64 `json:"blockNumberMax,omitempty"`
TimestampMin *hexutil.Uint64 `json:"timestampMin,omitempty"`
TimestampMax *hexutil.Uint64 `json:"timestampMax,omitempty"`
}
var enc TransactionOpts
enc.KnownAccounts = t.KnownAccounts
enc.BlockNumberMin = t.BlockNumberMin
enc.BlockNumberMax = t.BlockNumberMax
enc.TimestampMin = t.TimestampMin
enc.TimestampMax = t.TimestampMax
return json.Marshal(&enc)
}
// UnmarshalJSON unmarshals from JSON.
func (t *TransactionOpts) UnmarshalJSON(input []byte) error {
type TransactionOpts struct {
KnownAccounts *KnownAccounts `json:"knownAccounts"`
BlockNumberMin *hexutil.Uint64 `json:"blockNumberMin,omitempty"`
BlockNumberMax *hexutil.Uint64 `json:"blockNumberMax,omitempty"`
TimestampMin *hexutil.Uint64 `json:"timestampMin,omitempty"`
TimestampMax *hexutil.Uint64 `json:"timestampMax,omitempty"`
}
var dec TransactionOpts
if err := json.Unmarshal(input, &dec); err != nil {
return err
}
if dec.KnownAccounts != nil {
t.KnownAccounts = *dec.KnownAccounts
}
if dec.BlockNumberMin != nil {
t.BlockNumberMin = dec.BlockNumberMin
}
if dec.BlockNumberMax != nil {
t.BlockNumberMax = dec.BlockNumberMax
}
if dec.TimestampMin != nil {
t.TimestampMin = dec.TimestampMin
}
if dec.TimestampMax != nil {
t.TimestampMax = dec.TimestampMax
}
return nil
}

@ -11,32 +11,38 @@ import (
)
type AccountStorage struct {
RootHash *common.Hash
SlotValue map[common.Hash]common.Hash
StorageRoot *common.Hash
StorageSlots map[common.Hash]common.Hash
}
func (r *AccountStorage) UnmarshalJSON(data []byte) error {
func (a *AccountStorage) UnmarshalJSON(data []byte) error {
var hash common.Hash
if err := json.Unmarshal(data, &hash); err == nil {
r.RootHash = &hash
a.StorageRoot = &hash
return nil
}
return json.Unmarshal(data, &r.SlotValue)
return json.Unmarshal(data, &a.StorageSlots)
}
func (r AccountStorage) MarshalJSON() ([]byte, error) {
if r.RootHash != nil {
return json.Marshal(*r.RootHash)
func (a AccountStorage) MarshalJSON() ([]byte, error) {
if a.StorageRoot != nil {
return json.Marshal(*a.StorageRoot)
}
return json.Marshal(r.SlotValue)
return json.Marshal(a.StorageSlots)
}
type KnownAccounts map[common.Address]AccountStorage
// It is known that marshaling is broken
// https://github.com/golang/go/issues/55890
//go:generate go run github.com/fjl/gencodec -type TransactionOpts -out gen_tx_opts_json.go
type TransactionOpts struct {
KnownAccounts map[common.Address]AccountStorage `json:"knownAccounts"`
BlockNumberMin *hexutil.Uint64 `json:"blockNumberMin,omitempty"`
BlockNumberMax *hexutil.Uint64 `json:"blockNumberMax,omitempty"`
TimestampMin *hexutil.Uint64 `json:"timestampMin,omitempty"`
TimestampMax *hexutil.Uint64 `json:"timestampMax,omitempty"`
KnownAccounts KnownAccounts `json:"knownAccounts"`
BlockNumberMin *hexutil.Uint64 `json:"blockNumberMin,omitempty"`
BlockNumberMax *hexutil.Uint64 `json:"blockNumberMax,omitempty"`
TimestampMin *hexutil.Uint64 `json:"timestampMin,omitempty"`
TimestampMax *hexutil.Uint64 `json:"timestampMax,omitempty"`
}
func (o *TransactionOpts) Check(blockNumber uint64, timeStamp uint64, statedb *state.StateDB) error {
@ -54,30 +60,30 @@ func (o *TransactionOpts) Check(blockNumber uint64, timeStamp uint64, statedb *s
}
counter := 0
for _, account := range o.KnownAccounts {
if account.RootHash != nil {
if account.StorageRoot != nil {
counter += 1
} else if account.SlotValue != nil {
counter += len(account.SlotValue)
} else if account.StorageSlots != nil {
counter += len(account.StorageSlots)
}
}
if counter > 1000 {
return errors.New("knownAccounts too large")
}
return o.CheckOnlyStorage(statedb)
return o.CheckStorage(statedb)
}
func (o *TransactionOpts) CheckOnlyStorage(statedb *state.StateDB) error {
func (o *TransactionOpts) CheckStorage(statedb *state.StateDB) error {
for address, accountStorage := range o.KnownAccounts {
if accountStorage.RootHash != nil {
if accountStorage.StorageRoot != nil {
trie := statedb.StorageTrie(address)
if trie == nil {
return errors.New("storage trie not found for address key in knownAccounts option")
}
if trie.Hash() != *accountStorage.RootHash {
if trie.Hash() != *accountStorage.StorageRoot {
return errors.New("storage root hash condition not met")
}
} else if len(accountStorage.SlotValue) > 0 {
for slot, value := range accountStorage.SlotValue {
} else if len(accountStorage.StorageSlots) > 0 {
for slot, value := range accountStorage.StorageSlots {
stored := statedb.GetState(address, slot)
if !bytes.Equal(stored.Bytes(), value.Bytes()) {
return errors.New("storage slot value condition not met")

@ -0,0 +1,122 @@
package ethapi_test
import (
"encoding/json"
"reflect"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/internal/ethapi"
)
func ptr(hash common.Hash) *common.Hash {
return &hash
}
func u64Ptr(v hexutil.Uint64) *hexutil.Uint64 {
return &v
}
func TestTransactionOptsJSONUnMarshalTrip(t *testing.T) {
tests := []struct {
name string
input string
mustFail bool
expected ethapi.TransactionOpts
}{
{
"StateRoot",
`{"knownAccounts":{"0x6b3A8798E5Fb9fC5603F3aB5eA2e8136694e55d0":"0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563"}}`,
false,
ethapi.TransactionOpts{
KnownAccounts: map[common.Address]ethapi.AccountStorage{
common.HexToAddress("0x6b3A8798E5Fb9fC5603F3aB5eA2e8136694e55d0"): ethapi.AccountStorage{
StorageRoot: ptr(common.HexToHash("0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563")),
},
},
},
},
{
"StorageSlots",
`{"knownAccounts":{"0x6b3A8798E5Fb9fC5603F3aB5eA2e8136694e55d0":{"0xc65a7bb8d6351c1cf70c95a316cc6a92839c986682d98bc35f958f4883f9d2a8":"0x0000000000000000000000000000000000000000000000000000000000000000"}}}`,
false,
ethapi.TransactionOpts{
KnownAccounts: map[common.Address]ethapi.AccountStorage{
common.HexToAddress("0x6b3A8798E5Fb9fC5603F3aB5eA2e8136694e55d0"): ethapi.AccountStorage{
StorageRoot: nil,
StorageSlots: map[common.Hash]common.Hash{
common.HexToHash("0xc65a7bb8d6351c1cf70c95a316cc6a92839c986682d98bc35f958f4883f9d2a8"): common.HexToHash("0x"),
},
},
},
},
},
{
"EmptyObject",
`{"knownAccounts":{}}`,
false,
ethapi.TransactionOpts{
KnownAccounts: make(map[common.Address]ethapi.AccountStorage),
},
},
{
"EmptyStrings",
`{"knownAccounts":{"":""}}`,
true,
ethapi.TransactionOpts{
KnownAccounts: nil,
},
},
{
"BlockNumberMin",
`{"blockNumberMin":"0x1"}`,
false,
ethapi.TransactionOpts{
BlockNumberMin: u64Ptr(1),
},
},
{
"BlockNumberMax",
`{"blockNumberMin":"0x1", "blockNumberMax":"0x2"}`,
false,
ethapi.TransactionOpts{
BlockNumberMin: u64Ptr(1),
BlockNumberMax: u64Ptr(2),
},
},
{
"TimestampMin",
`{"timestampMin":"0xffff"}`,
false,
ethapi.TransactionOpts{
TimestampMin: u64Ptr(0xffff),
},
},
{
"TimestampMax",
`{"timestampMax":"0xffffff"}`,
false,
ethapi.TransactionOpts{
TimestampMax: u64Ptr(0xffffff),
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var opts ethapi.TransactionOpts
err := json.Unmarshal([]byte(test.input), &opts)
if test.mustFail && err == nil {
t.Errorf("Test %s should fail", test.name)
}
if !test.mustFail && err != nil {
t.Errorf("Test %s should pass but got err: %v", test.name, err)
}
if !reflect.DeepEqual(opts, test.expected) {
t.Errorf("Test %s got unexpected value, want %#v, got %#v", test.name, test.expected, opts)
}
})
}
}