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

simulators/ethereum/engine: Cross-client Payload Validation #642

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions simulators/ethereum/engine/client/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rpc"
)

type Eth interface {
Expand All @@ -21,6 +22,7 @@ type Eth interface {
StorageAt(ctx context.Context, account common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error)
TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error)
NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error)
CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error)
}

type Engine interface {
Expand All @@ -38,8 +40,10 @@ type Engine interface {
type EngineClient interface {
// General Methods
ID() string
ClientType() string
Close() error
EnodeURL() (string, error)
RPC() *rpc.Client

// Local Test Account Management
GetNextAccountNonce(testCtx context.Context, account common.Address) (uint64, error)
Expand Down
9 changes: 9 additions & 0 deletions simulators/ethereum/engine/client/hive_rpc/hive_rpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ type HiveRPCEngineClient struct {
h *hivesim.Client
c *rpc.Client
cEth *rpc.Client
clientType string
ttd *big.Int
JWTSecretBytes []byte

Expand Down Expand Up @@ -191,6 +192,14 @@ func (ec *HiveRPCEngineClient) ID() string {
return ec.h.Container
}

func (ec *HiveRPCEngineClient) ClientType() string {
return ec.clientType
}

func (ec *HiveRPCEngineClient) RPC() *rpc.Client {
return ec.c
}

func (ec *HiveRPCEngineClient) EnodeURL() (string, error) {
return ec.h.EnodeURL()
}
Expand Down
16 changes: 16 additions & 0 deletions simulators/ethereum/engine/client/node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -806,6 +806,14 @@ func (n *GethNode) NonceAt(ctx context.Context, account common.Address, blockNum
return stateDB.GetNonce(account), nil
}

func (n *GethNode) CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error) {
stateDB, err := n.getStateDB(ctx, blockNumber)
if err != nil {
return nil, err
}
return stateDB.GetCode(account), nil
}

func (n *GethNode) GetBlockTotalDifficulty(ctx context.Context, hash common.Hash) (*big.Int, error) {
block := n.eth.BlockChain().GetBlockByHash(hash)
if block == nil {
Expand Down Expand Up @@ -836,6 +844,14 @@ func (n *GethNode) ID() string {
return n.node.Config().Name
}

func (n *GethNode) ClientType() string {
return n.node.Config().Name
}

func (n *GethNode) RPC() *rpc.Client {
return nil
}

func (n *GethNode) GetNextAccountNonce(testCtx context.Context, account common.Address) (uint64, error) {
// First get the current head of the client where we will send the tx
head, err := n.eth.APIBackend.BlockByNumber(testCtx, LatestBlockNumber)
Expand Down
8 changes: 7 additions & 1 deletion simulators/ethereum/engine/clmock/clmock.go
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,9 @@ func (cl *CLMocker) broadcastNextNewPayload() {
if resp.ExecutePayloadResponse.LatestValidHash != nil && *resp.ExecutePayloadResponse.LatestValidHash != (common.Hash{}) {
cl.Fatalf("CLMocker: NewPayload returned ACCEPTED status with incorrect LatestValidHash==%v", resp.ExecutePayloadResponse.LatestValidHash)
}
} else if resp.ExecutePayloadResponse.Status == api.INVALID {
// At any point during the CLMock workflow there mustn't be any INVALID payload
cl.Fatalf("CLMocker: An invalid payload was produced by one of the clients during the payload building process: Payload builder=%s, invalidating client=%s, hash=%s", cl.NextBlockProducer.ID(), resp.Container, cl.LatestPayloadBuilt.BlockHash)
} else {
cl.Logf("CLMocker: BroadcastNewPayload Response (%v): %v\n", resp.Container, resp.ExecutePayloadResponse)
}
Expand All @@ -356,7 +359,10 @@ func (cl *CLMocker) broadcastLatestForkchoice() {
if resp.ForkchoiceResponse.PayloadID != nil {
cl.Fatalf("CLMocker: Expected empty PayloadID: %v\n", resp.Container, resp.ForkchoiceResponse.PayloadID)
}
} else if resp.ForkchoiceResponse.PayloadStatus.Status != api.VALID {
} else if resp.ForkchoiceResponse.PayloadStatus.Status == api.INVALID {
// At any point during the CLMock workflow there mustn't be any INVALID payload
cl.Fatalf("CLMocker: An invalid payload was produced by one of the clients during the payload building process (ForkchoiceUpdated): Payload builder=%s, invalidating client=%s, hash=%s", cl.NextBlockProducer.ID(), resp.Container, cl.LatestPayloadBuilt.BlockHash)
} else {
cl.Logf("CLMocker: BroadcastForkchoiceUpdated Response (%v): %v\n", resp.Container, resp.ForkchoiceResponse)
}
}
Expand Down
9 changes: 5 additions & 4 deletions simulators/ethereum/engine/globals/globals.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ import (
var (

// Test chain parameters
ChainID = big.NewInt(7)
GasPrice = big.NewInt(30 * params.GWei)
GasTipPrice = big.NewInt(1 * params.GWei)
NetworkID = big.NewInt(7)
ChainID = big.NewInt(7)
GasPrice = big.NewInt(30 * params.GWei)
GasFeeCap = big.NewInt(30 * params.GWei)
GasTipCap = big.NewInt(1 * params.GWei)
NetworkID = big.NewInt(7)

// RPC Timeout for every call
RPCTimeout = 10 * time.Second
Expand Down
42 changes: 39 additions & 3 deletions simulators/ethereum/engine/helper/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package helper

import (
"context"
"strings"
"sync"
"time"

Expand All @@ -17,6 +18,8 @@ import (
"os"

api "github.com/ethereum/go-ethereum/core/beacon"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/hive/simulators/ethereum/engine/client"
"github.com/ethereum/hive/simulators/ethereum/engine/globals"

Expand Down Expand Up @@ -350,13 +353,13 @@ func MakeTransaction(nonce uint64, recipient *common.Address, gasLimit uint64, a
Data: payload,
}
case types.DynamicFeeTxType:
gasFeeCap := new(big.Int).Set(globals.GasPrice)
gasTipCap := new(big.Int).Set(globals.GasTipPrice)
gasFeeCap := new(big.Int).Set(globals.GasFeeCap)
gasTipCap := new(big.Int).Set(globals.GasTipCap)
newTxData = &types.DynamicFeeTx{
Nonce: nonce,
Gas: gasLimit,
GasTipCap: gasTipCap,
GasFeeCap: gasFeeCap,
GasTipCap: gasTipCap,
To: recipient,
Value: amount,
Data: payload,
Expand All @@ -371,6 +374,37 @@ func MakeTransaction(nonce uint64, recipient *common.Address, gasLimit uint64, a
return signedTx, nil
}

func MakeContractTransaction(nonce uint64, gasLimit uint64, amount *big.Int, contractCode []byte, txType TestTransactionType) (common.Address, *types.Transaction, error) {
// Create a contract based on the contract code without any specialized initialization
var initCode []byte
if len(contractCode) > 32 {
return common.Address{}, nil, fmt.Errorf("contract too big")
} else {
// Push the entire contract code onto the stack and init from there
initCode = []byte{ // Push contract code onto stack
byte(vm.PUSH1) + byte(len(contractCode)-1)}
initCode = append(initCode, contractCode...)
initCode = append(initCode, []byte{
byte(vm.PUSH1), 0x0, // memory start on stack
byte(vm.MSTORE),
byte(vm.PUSH1), byte(len(contractCode)), // size
byte(vm.PUSH1), byte(32 - len(contractCode)), // offset
byte(vm.RETURN),
}...)
}

contractAddress := crypto.CreateAddress(globals.VaultAccountAddress, nonce)
tx, err := MakeTransaction(nonce, nil, gasLimit, amount, initCode, txType)
return contractAddress, tx, err
}

// Determines if the error we got from sending the raw tx is because the client
// already knew the tx (might happen if we produced a re-org where the tx was
// unwind back into the txpool)
func SentTxAlreadyKnown(err error) bool {
return strings.Contains(err.Error(), "already known")
}

func SendNextTransaction(testCtx context.Context, node client.EngineClient, recipient common.Address, amount *big.Int, payload []byte, txType TestTransactionType) (*types.Transaction, error) {
nonce, err := node.GetNextAccountNonce(testCtx, globals.VaultAccountAddress)
if err != nil {
Expand All @@ -383,6 +417,8 @@ func SendNextTransaction(testCtx context.Context, node client.EngineClient, reci
err := node.SendTransaction(ctx, tx)
if err == nil {
return tx, nil
} else if SentTxAlreadyKnown(err) {
return tx, nil
}
select {
case <-time.After(time.Second):
Expand Down
2 changes: 1 addition & 1 deletion simulators/ethereum/engine/helper/payload.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ func GenerateInvalidPayload(basePayload *api.ExecutableDataV1, payloadField Inva
case InvalidTransactionGasPrice:
customTxData.GasPriceOrGasFeeCap = common.Big0
case InvalidTransactionGasTipPrice:
invalidGasTip := new(big.Int).Set(globals.GasTipPrice)
invalidGasTip := new(big.Int).Set(globals.GasTipCap)
invalidGasTip.Mul(invalidGasTip, big.NewInt(2))
customTxData.GasTipCap = invalidGasTip
case InvalidTransactionValue:
Expand Down
16 changes: 9 additions & 7 deletions simulators/ethereum/engine/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,23 +81,25 @@ func addTestsToSuite(suite *hivesim.Suite, tests []test.Spec) {
if currentTest.DisableMining {
delete(newParams, "HIVE_MINER")
}
suite.Add(hivesim.ClientTestSpec{
suite.Add(hivesim.TestSpec{
Name: currentTest.Name,
Description: currentTest.About,
Parameters: newParams,
Files: testFiles,
Run: func(t *hivesim.T, c *hivesim.Client) {
t.Logf("Start test (%s): %s", c.Type, currentTest.Name)
Run: func(t *hivesim.T) {
testClientTypes, err := t.Sim.ClientTypes()
if err != nil {
t.Fatalf("No client types")
}
t.Logf("Start test (%s): %s", testClientTypes[0].Name, currentTest.Name)
defer func() {
t.Logf("End test (%s): %s", c.Type, currentTest.Name)
t.Logf("End test (%s): %s", testClientTypes[0].Name, currentTest.Name)
}()
timeout := globals.DefaultTestCaseTimeout
// If a test.Spec specifies a timeout, use that instead
if currentTest.TimeoutSeconds != 0 {
timeout = time.Second * time.Duration(currentTest.TimeoutSeconds)
}
// Run the test case
test.Run(currentTest.Name, big.NewInt(ttd), currentTest.SlotsToSafe, currentTest.SlotsToFinalized, timeout, t, c, currentTest.Run, newParams, testFiles, currentTest.TestTransactionType, currentTest.SafeSlotsToImportOptimistically)
test.Run(currentTest.Name, big.NewInt(ttd), currentTest.SlotsToSafe, currentTest.SlotsToFinalized, timeout, t, currentTest.Run, newParams, testFiles, currentTest.TestTransactionType, currentTest.SafeSlotsToImportOptimistically)
},
})
}
Expand Down
10 changes: 8 additions & 2 deletions simulators/ethereum/engine/suites/auth/tests.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ package suite_auth

import (
"context"
"fmt"
"time"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
api "github.com/ethereum/go-ethereum/core/beacon"
"github.com/ethereum/hive/simulators/ethereum/engine/client/hive_rpc"
"github.com/ethereum/hive/simulators/ethereum/engine/globals"
"github.com/ethereum/hive/simulators/ethereum/engine/test"
)
Expand Down Expand Up @@ -100,12 +102,16 @@ func GenerateAuthTestSpec(authTestSpec AuthTestSpec) test.Spec {
if authTestSpec.TimeDriftSeconds != 0 {
testTime = testTime.Add(time.Second * time.Duration(authTestSpec.TimeDriftSeconds))
}
if err := t.HiveEngine.PrepareAuthCallToken(testSecret, testTime); err != nil {
hiveEngine, ok := t.Engine.(*hive_rpc.HiveRPCEngineClient)
if !ok {
panic(fmt.Errorf("Invalid cast to HiveRPCEngineClient"))
}
if err := hiveEngine.PrepareAuthCallToken(testSecret, testTime); err != nil {
t.Fatalf("FAIL (%s): Unable to prepare the auth token: %v", t.TestName, err)
}
ctx, cancel := context.WithTimeout(t.TestContext, globals.RPCTimeout)
defer cancel()
_, err := t.HiveEngine.ExchangeTransitionConfigurationV1(ctx, &tConf)
_, err := hiveEngine.ExchangeTransitionConfigurationV1(ctx, &tConf)
if (authTestSpec.AuthOk && err == nil) || (!authTestSpec.AuthOk && err != nil) {
// Test passed
return
Expand Down
Loading