ethapi: introduce tests for transaction options
This commit is contained in:
parent
26c69a15ba
commit
f8c5f55652
@ -680,7 +680,14 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa
|
|||||||
|
|
||||||
// SendTransaction updates the pending block to include the given transaction.
|
// SendTransaction updates the pending block to include the given transaction.
|
||||||
func (b *SimulatedBackend) SendTransactionConditional(ctx context.Context, tx *types.Transaction, opts ethapi.TransactionOpts) error {
|
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
|
// 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 {
|
if err := tx.UnmarshalBinary(input); err != nil {
|
||||||
return common.Hash{}, err
|
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 {
|
if state == nil || err != nil {
|
||||||
return common.Hash{}, err
|
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 common.Hash{}, err
|
||||||
}
|
}
|
||||||
return SubmitTransaction(ctx, s.b, tx)
|
return SubmitTransaction(ctx, s.b, tx)
|
||||||
|
58
internal/ethapi/gen_tx_opts_json.go
Normal file
58
internal/ethapi/gen_tx_opts_json.go
Normal file
@ -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 {
|
type AccountStorage struct {
|
||||||
RootHash *common.Hash
|
StorageRoot *common.Hash
|
||||||
SlotValue map[common.Hash]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
|
var hash common.Hash
|
||||||
if err := json.Unmarshal(data, &hash); err == nil {
|
if err := json.Unmarshal(data, &hash); err == nil {
|
||||||
r.RootHash = &hash
|
a.StorageRoot = &hash
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return json.Unmarshal(data, &r.SlotValue)
|
return json.Unmarshal(data, &a.StorageSlots)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r AccountStorage) MarshalJSON() ([]byte, error) {
|
func (a AccountStorage) MarshalJSON() ([]byte, error) {
|
||||||
if r.RootHash != nil {
|
if a.StorageRoot != nil {
|
||||||
return json.Marshal(*r.RootHash)
|
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 {
|
type TransactionOpts struct {
|
||||||
KnownAccounts map[common.Address]AccountStorage `json:"knownAccounts"`
|
KnownAccounts KnownAccounts `json:"knownAccounts"`
|
||||||
BlockNumberMin *hexutil.Uint64 `json:"blockNumberMin,omitempty"`
|
BlockNumberMin *hexutil.Uint64 `json:"blockNumberMin,omitempty"`
|
||||||
BlockNumberMax *hexutil.Uint64 `json:"blockNumberMax,omitempty"`
|
BlockNumberMax *hexutil.Uint64 `json:"blockNumberMax,omitempty"`
|
||||||
TimestampMin *hexutil.Uint64 `json:"timestampMin,omitempty"`
|
TimestampMin *hexutil.Uint64 `json:"timestampMin,omitempty"`
|
||||||
TimestampMax *hexutil.Uint64 `json:"timestampMax,omitempty"`
|
TimestampMax *hexutil.Uint64 `json:"timestampMax,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *TransactionOpts) Check(blockNumber uint64, timeStamp uint64, statedb *state.StateDB) error {
|
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
|
counter := 0
|
||||||
for _, account := range o.KnownAccounts {
|
for _, account := range o.KnownAccounts {
|
||||||
if account.RootHash != nil {
|
if account.StorageRoot != nil {
|
||||||
counter += 1
|
counter += 1
|
||||||
} else if account.SlotValue != nil {
|
} else if account.StorageSlots != nil {
|
||||||
counter += len(account.SlotValue)
|
counter += len(account.StorageSlots)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if counter > 1000 {
|
if counter > 1000 {
|
||||||
return errors.New("knownAccounts too large")
|
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 {
|
for address, accountStorage := range o.KnownAccounts {
|
||||||
if accountStorage.RootHash != nil {
|
if accountStorage.StorageRoot != nil {
|
||||||
trie := statedb.StorageTrie(address)
|
trie := statedb.StorageTrie(address)
|
||||||
if trie == nil {
|
if trie == nil {
|
||||||
return errors.New("storage trie not found for address key in knownAccounts option")
|
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")
|
return errors.New("storage root hash condition not met")
|
||||||
}
|
}
|
||||||
} else if len(accountStorage.SlotValue) > 0 {
|
} else if len(accountStorage.StorageSlots) > 0 {
|
||||||
for slot, value := range accountStorage.SlotValue {
|
for slot, value := range accountStorage.StorageSlots {
|
||||||
stored := statedb.GetState(address, slot)
|
stored := statedb.GetState(address, slot)
|
||||||
if !bytes.Equal(stored.Bytes(), value.Bytes()) {
|
if !bytes.Equal(stored.Bytes(), value.Bytes()) {
|
||||||
return errors.New("storage slot value condition not met")
|
return errors.New("storage slot value condition not met")
|
||||||
|
122
internal/ethapi/transaction_options_test.go
Normal file
122
internal/ethapi/transaction_options_test.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user