diff --git a/clmock/api.go b/clmock/api.go
new file mode 100644
index 0000000000000..e09a5147d4f45
--- /dev/null
+++ b/clmock/api.go
@@ -0,0 +1,22 @@
+package clmock
+
+import (
+ "context"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
+)
+
+type API struct {
+ mock *CLMock
+}
+
+func (api *API) AddWithdrawal(ctx context.Context, withdrawal *types.Withdrawal) error {
+ return api.mock.addWithdrawal(*withdrawal)
+}
+
+func (api *API) SetFeeRecipient(ctx context.Context, feeRecipient *common.Address) {
+ api.mock.mu.Lock()
+ api.mock.feeRecipient = *feeRecipient
+ api.mock.mu.Unlock()
+}
diff --git a/clmock/clmock.go b/clmock/clmock.go
new file mode 100644
index 0000000000000..d20bb6c4eb380
--- /dev/null
+++ b/clmock/clmock.go
@@ -0,0 +1,222 @@
+// Copyright 2023 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package clmock
+
+import (
+ "context"
+ "fmt"
+ "sync"
+ "time"
+
+ "github.com/ethereum/go-ethereum/beacon/engine"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/eth"
+ "github.com/ethereum/go-ethereum/eth/catalyst"
+ "github.com/ethereum/go-ethereum/log"
+ "github.com/ethereum/go-ethereum/node"
+ "github.com/ethereum/go-ethereum/rpc"
+)
+
+type CLMock struct {
+ ctx context.Context
+ cancel context.CancelFunc
+ eth *eth.Ethereum
+ period time.Duration
+ withdrawals []*types.Withdrawal
+ feeRecipient common.Address
+ // mu controls access to the feeRecipient/withdrawals which can be modified by the dev-mode RPC API methods
+ mu sync.Mutex
+ nextWithdrawalIdx uint64
+}
+
+func NewCLMock(eth *eth.Ethereum) *CLMock {
+ chainConfig := eth.APIBackend.ChainConfig()
+ if chainConfig.Dev == nil {
+ log.Crit("incompatible pre-existing chain configuration")
+ }
+
+ return &CLMock{
+ eth: eth,
+ period: time.Duration(chainConfig.Dev.Period) * time.Second,
+ withdrawals: []*types.Withdrawal{},
+ feeRecipient: common.Address{},
+ }
+}
+
+// Start invokes the clmock life-cycle function in a goroutine
+func (c *CLMock) Start() error {
+ c.ctx, c.cancel = context.WithCancel(context.Background())
+ go c.loop()
+ return nil
+}
+
+// Stop halts the clmock service
+func (c *CLMock) Stop() error {
+ c.cancel()
+ return nil
+}
+
+func (c *CLMock) addWithdrawal(w types.Withdrawal) error {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
+ if w.Index < c.nextWithdrawalIdx {
+ return fmt.Errorf("withdrawal has index (%d) less than or equal to latest received withdrawal index (%d)", w.Index, c.nextWithdrawalIdx-1)
+ }
+ c.nextWithdrawalIdx = w.Index + 1
+ c.withdrawals = append(c.withdrawals, &w)
+ return nil
+}
+
+// remove up to 10 withdrawals from the withdrawal queue
+func (c *CLMock) popWithdrawals() []*types.Withdrawal {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
+ var popCount int
+ if len(c.withdrawals) >= 10 {
+ popCount = 10
+ } else {
+ popCount = len(c.withdrawals)
+ }
+
+ popped := make([]*types.Withdrawal, popCount)
+ copy(popped[:], c.withdrawals[0:popCount])
+ c.withdrawals = append([]*types.Withdrawal{}, c.withdrawals[popCount:]...)
+ return popped
+}
+
+// loop manages the lifecycle of clmock.
+// it drives block production, taking the role of a CL client and interacting with Geth via public engine/eth APIs
+func (c *CLMock) loop() {
+ var (
+ ticker = time.NewTicker(time.Millisecond * 100)
+ lastBlockTime = time.Now()
+ engineAPI = catalyst.NewConsensusAPI(c.eth)
+ header = c.eth.BlockChain().CurrentHeader()
+ curForkchoiceState = engine.ForkchoiceStateV1{
+ HeadBlockHash: header.Hash(),
+ SafeBlockHash: header.Hash(),
+ FinalizedBlockHash: header.Hash(),
+ }
+ )
+
+ // if genesis block, send forkchoiceUpdated to trigger transition to PoS
+ if header.Number.BitLen() == 0 {
+ if _, err := engineAPI.ForkchoiceUpdatedV2(curForkchoiceState, nil); err != nil {
+ log.Crit("failed to initiate PoS transition for genesis via Forkchoiceupdated", "err", err)
+ }
+ }
+
+ for {
+ select {
+ case <-c.ctx.Done():
+ break
+ case curTime := <-ticker.C:
+ if curTime.Unix() > lastBlockTime.Add(c.period).Unix() {
+ c.mu.Lock()
+ feeRecipient := c.feeRecipient
+ c.mu.Unlock()
+
+ payloadAttr := &engine.PayloadAttributes{
+ Timestamp: uint64(curTime.Unix()),
+ Random: common.Hash{}, // TODO: make this configurable?
+ SuggestedFeeRecipient: feeRecipient,
+ Withdrawals: c.popWithdrawals(),
+ }
+
+ // trigger block building
+ fcState, err := engineAPI.ForkchoiceUpdatedV2(curForkchoiceState, payloadAttr)
+ if err != nil {
+ log.Crit("failed to trigger block building via forkchoiceupdated", "err", err)
+ }
+
+ var payload *engine.ExecutableData
+
+ var (
+ restartPayloadBuilding bool
+ // building a payload times out after SECONDS_PER_SLOT (12s on mainnet).
+ // trigger building a new payload if this amount of time elapses w/o any transactions or withdrawals
+ // having been received.
+ payloadTimeout = time.NewTimer(12 * time.Second)
+ // interval to poll the pending state to detect if transactions have arrived, and proceed if they have
+ // (or if there are pending withdrawals to include)
+ buildTicker = time.NewTicker(100 * time.Millisecond)
+ )
+ for {
+ select {
+ case <-buildTicker.C:
+ pendingHeader, err := c.eth.APIBackend.HeaderByNumber(context.Background(), rpc.PendingBlockNumber)
+ if err != nil {
+ log.Crit("failed to get pending block header", "err", err)
+ }
+ // don't build a block if we don't have pending txs or withdrawals
+ if pendingHeader.TxHash == types.EmptyTxsHash && len(payloadAttr.Withdrawals) == 0 {
+ continue
+ }
+ payload, err = engineAPI.GetPayloadV1(*fcState.PayloadID)
+ if err != nil {
+ log.Crit("error retrieving payload", "err", err)
+ }
+ // Don't build a block if it doesn't contain transactions or withdrawals.
+ // Somehow, txs can arrive, be detected by this routine, but be missed by the miner payload builder.
+ // So this last clause prevents empty blocks from being built.
+ if len(payload.Transactions) == 0 && len(payloadAttr.Withdrawals) == 0 {
+ restartPayloadBuilding = true
+ }
+ case <-payloadTimeout.C:
+ restartPayloadBuilding = true
+ case <-c.ctx.Done():
+ return
+ }
+ break
+ }
+ if restartPayloadBuilding {
+ continue
+ }
+ // mark the payload as the one we have chosen
+ if _, err = engineAPI.NewPayloadV2(*payload); err != nil {
+ log.Crit("failed to mark payload as canonical", "err", err)
+ }
+
+ newForkchoiceState := &engine.ForkchoiceStateV1{
+ HeadBlockHash: payload.BlockHash,
+ SafeBlockHash: payload.BlockHash,
+ FinalizedBlockHash: payload.BlockHash,
+ }
+ // mark the block containing the payload as canonical
+ _, err = engineAPI.ForkchoiceUpdatedV2(*newForkchoiceState, nil)
+ if err != nil {
+ log.Crit("failed to mark block as canonical", "err", err)
+ }
+ lastBlockTime = time.Unix(int64(payload.Timestamp), 0)
+ curForkchoiceState = *newForkchoiceState
+ }
+ }
+ }
+}
+
+func RegisterAPIs(stack *node.Node, c *CLMock) {
+ stack.RegisterAPIs([]rpc.API{
+ {
+ Namespace: "dev",
+ Service: &API{c},
+ Version: "1.0",
+ },
+ })
+}
diff --git a/clmock/clmock_test.go b/clmock/clmock_test.go
new file mode 100644
index 0000000000000..3107814f8106c
--- /dev/null
+++ b/clmock/clmock_test.go
@@ -0,0 +1,139 @@
+// Copyright 2023 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package clmock
+
+import (
+ "context"
+ "math/big"
+ "testing"
+ "time"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/eth"
+ "github.com/ethereum/go-ethereum/eth/downloader"
+ "github.com/ethereum/go-ethereum/eth/ethconfig"
+ "github.com/ethereum/go-ethereum/node"
+ "github.com/ethereum/go-ethereum/p2p"
+ "github.com/ethereum/go-ethereum/params"
+)
+
+func startEthService(t *testing.T, genesis *core.Genesis) (*node.Node, *eth.Ethereum, *CLMock) {
+ t.Helper()
+
+ n, err := node.New(&node.Config{
+ P2P: p2p.Config{
+ ListenAddr: "127.0.0.1:8545",
+ NoDiscovery: true,
+ MaxPeers: 0,
+ },
+ })
+ if err != nil {
+ t.Fatal("can't create node:", err)
+ }
+
+ ethcfg := ðconfig.Config{Genesis: genesis, SyncMode: downloader.FullSync, TrieTimeout: time.Minute, TrieDirtyCache: 256, TrieCleanCache: 256}
+ ethservice, err := eth.New(n, ethcfg)
+ if err != nil {
+ t.Fatal("can't create eth service:", err)
+ }
+
+ clmock := NewCLMock(ethservice)
+
+ n.RegisterLifecycle(clmock)
+
+ if err := n.Start(); err != nil {
+ t.Fatal("can't start node:", err)
+ }
+
+ ethservice.SetSynced()
+ return n, ethservice, clmock
+}
+
+// send 20 transactions, >10 withdrawals and ensure they are included in order
+// send enough transactions to fill multiple blocks
+func TestCLMockSendWithdrawals(t *testing.T) {
+ var withdrawals []types.Withdrawal
+ txs := make(map[common.Hash]types.Transaction)
+
+ var (
+ // testKey is a private key to use for funding a tester account.
+ testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
+
+ // testAddr is the Ethereum address of the tester account.
+ testAddr = crypto.PubkeyToAddress(testKey.PublicKey)
+ )
+
+ // short period (1 second) for testing purposes
+ period := 1
+ var gasLimit uint64 = 10_000_000
+ genesis := core.DeveloperGenesisBlock(uint64(period), gasLimit, testAddr)
+ node, ethService, mock := startEthService(t, genesis)
+ _ = mock
+ defer node.Close()
+
+ chainHeadCh := make(chan core.ChainHeadEvent, 10)
+ subscription := ethService.BlockChain().SubscribeChainHeadEvent(chainHeadCh)
+ defer subscription.Unsubscribe()
+
+ // generate some withdrawals
+ for i := 0; i < 20; i++ {
+ withdrawals = append(withdrawals, types.Withdrawal{Index: uint64(i)})
+ if err := mock.addWithdrawal(withdrawals[i]); err != nil {
+ t.Fatal("addWithdrawal failed", err)
+ }
+ }
+
+ // generate a bunch of transactions
+ signer := types.NewEIP155Signer(ethService.BlockChain().Config().ChainID)
+ for i := 0; i < 20; i++ {
+ tx, err := types.SignTx(types.NewTransaction(uint64(i), common.Address{}, big.NewInt(1000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), signer, testKey)
+ if err != nil {
+ t.Fatalf("error signing transaction, err=%v", err)
+ }
+ txs[tx.Hash()] = *tx
+
+ if err := ethService.APIBackend.SendTx(context.Background(), tx); err != nil {
+ t.Fatal("SendTx failed", err)
+ }
+ }
+
+ includedTxs := make(map[common.Hash]struct{})
+ var includedWithdrawals []uint64
+
+ timer := time.NewTimer(12 * time.Second)
+ for {
+ select {
+ case evt := <-chainHeadCh:
+ for _, includedTx := range evt.Block.Transactions() {
+ includedTxs[includedTx.Hash()] = struct{}{}
+ }
+ for _, includedWithdrawal := range evt.Block.Withdrawals() {
+ includedWithdrawals = append(includedWithdrawals, includedWithdrawal.Index)
+ }
+
+ // ensure all withdrawals/txs included. this will take two blocks b/c number of withdrawals > 10
+ if len(includedTxs) == len(txs) && len(includedWithdrawals) == len(withdrawals) && evt.Block.Number().Cmp(big.NewInt(2)) == 0 {
+ return
+ }
+ case <-timer.C:
+ t.Fatal("timed out without including all withdrawals/txs")
+ }
+ }
+}
diff --git a/cmd/geth/config.go b/cmd/geth/config.go
index 42ded493224bb..eee3012717584 100644
--- a/cmd/geth/config.go
+++ b/cmd/geth/config.go
@@ -31,6 +31,7 @@ import (
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/accounts/scwallet"
"github.com/ethereum/go-ethereum/accounts/usbwallet"
+ "github.com/ethereum/go-ethereum/clmock"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/eth/ethconfig"
@@ -189,6 +190,14 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) {
if ctx.IsSet(utils.SyncTargetFlag.Name) && cfg.Eth.SyncMode == downloader.FullSync {
utils.RegisterFullSyncTester(stack, eth, ctx.Path(utils.SyncTargetFlag.Name))
}
+
+ // Start the dev mode if requested
+ if ctx.IsSet(utils.DeveloperFlag.Name) {
+ mock := clmock.NewCLMock(eth)
+ clmock.RegisterAPIs(stack, mock)
+ stack.RegisterLifecycle(mock)
+ }
+
return stack, backend
}
diff --git a/cmd/geth/consolecmd.go b/cmd/geth/consolecmd.go
index aeee6f9c9e404..c7ca963371111 100644
--- a/cmd/geth/consolecmd.go
+++ b/cmd/geth/consolecmd.go
@@ -72,6 +72,7 @@ func localConsole(ctx *cli.Context) error {
prepare(ctx)
stack, backend := makeFullNode(ctx)
startNode(ctx, stack, backend, true)
+
defer stack.Close()
// Attach to the newly started node and create the JavaScript console.
diff --git a/cmd/geth/main.go b/cmd/geth/main.go
index 2289a72a197b9..c744c32c64100 100644
--- a/cmd/geth/main.go
+++ b/cmd/geth/main.go
@@ -127,8 +127,8 @@ var (
utils.NodeKeyHexFlag,
utils.DNSDiscoveryFlag,
utils.DeveloperFlag,
- utils.DeveloperPeriodFlag,
utils.DeveloperGasLimitFlag,
+ utils.DeveloperPeriodFlag,
utils.VMEnableDebugFlag,
utils.NetworkIdFlag,
utils.EthStatsURLFlag,
@@ -323,6 +323,7 @@ func geth(ctx *cli.Context) error {
defer stack.Close()
startNode(ctx, stack, backend, false)
+
stack.Wait()
return nil
}
@@ -408,7 +409,7 @@ func startNode(ctx *cli.Context, stack *node.Node, backend ethapi.Backend, isCon
}
// Start auxiliary services if enabled
- if ctx.Bool(utils.MiningEnabledFlag.Name) || ctx.Bool(utils.DeveloperFlag.Name) {
+ if ctx.Bool(utils.MiningEnabledFlag.Name) {
// Mining only makes sense if a full Ethereum node is running
if ctx.String(utils.SyncModeFlag.Name) == "light" {
utils.Fatalf("Light clients do not support mining")
diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go
index 1ebc998a40ed3..0a85e1a2845dc 100644
--- a/cmd/utils/flags.go
+++ b/cmd/utils/flags.go
@@ -162,6 +162,7 @@ var (
DeveloperPeriodFlag = &cli.IntFlag{
Name: "dev.period",
Usage: "Block period to use in developer mode (0 = mine only if transaction pending)",
+ Value: 12,
Category: flags.DevCategory,
}
DeveloperGasLimitFlag = &cli.Uint64Flag{
diff --git a/core/genesis.go b/core/genesis.go
index 1e56845d8ad3b..bdd34b8261b36 100644
--- a/core/genesis.go
+++ b/core/genesis.go
@@ -552,19 +552,17 @@ func DefaultSepoliaGenesisBlock() *Genesis {
// DeveloperGenesisBlock returns the 'geth --dev' genesis block.
func DeveloperGenesisBlock(period uint64, gasLimit uint64, faucet common.Address) *Genesis {
// Override the default period to the user requested one
- config := *params.AllCliqueProtocolChanges
- config.Clique = ¶ms.CliqueConfig{
+ config := *params.AllDevChainProtocolChanges
+ config.Dev = ¶ms.DeveloperModeConfig{
Period: period,
- Epoch: config.Clique.Epoch,
}
// Assemble and return the genesis with the precompiles and faucet pre-funded
return &Genesis{
Config: &config,
- ExtraData: append(append(make([]byte, 32), faucet[:]...), make([]byte, crypto.SignatureLength)...),
GasLimit: gasLimit,
BaseFee: big.NewInt(params.InitialBaseFee),
- Difficulty: big.NewInt(1),
+ Difficulty: big.NewInt(0),
Alloc: map[common.Address]GenesisAccount{
common.BytesToAddress([]byte{1}): {Balance: big.NewInt(1)}, // ECRecover
common.BytesToAddress([]byte{2}): {Balance: big.NewInt(1)}, // SHA256
diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go
index c171709586e6f..f47564dc855e5 100644
--- a/internal/web3ext/web3ext.go
+++ b/internal/web3ext/web3ext.go
@@ -30,6 +30,7 @@ var Modules = map[string]string{
"txpool": TxpoolJs,
"les": LESJs,
"vflux": VfluxJs,
+ "dev": DevJs,
}
const CliqueJs = `
@@ -886,3 +887,22 @@ web3._extend({
]
});
`
+
+const DevJs = `
+web3._extend({
+ property: 'dev',
+ methods:
+ [
+ new web3._extend.Method({
+ name: 'addWithdrawal',
+ call: 'dev_addWithdrawal',
+ params: 1
+ }),
+ new web3._extend.Method({
+ name: 'setFeeRecipient',
+ call: 'dev_setFeeRecipient',
+ params: 1
+ }),
+ ],
+});
+`
diff --git a/miner/miner_test.go b/miner/miner_test.go
index 67d038d684766..585684fd82bdd 100644
--- a/miner/miner_test.go
+++ b/miner/miner_test.go
@@ -31,8 +31,10 @@ import (
"github.com/ethereum/go-ethereum/core/txpool"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
+ "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/event"
+ "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie"
)
@@ -245,6 +247,34 @@ func waitForMiningState(t *testing.T, m *Miner, mining bool) {
t.Fatalf("Mining() == %t, want %t", state, mining)
}
+func minerTestGenesisBlock(period uint64, gasLimit uint64, faucet common.Address) *core.Genesis {
+ config := *params.AllCliqueProtocolChanges
+ config.Clique = ¶ms.CliqueConfig{
+ Period: period,
+ Epoch: config.Clique.Epoch,
+ }
+
+ // Assemble and return the genesis with the precompiles and faucet pre-funded
+ return &core.Genesis{
+ Config: &config,
+ ExtraData: append(append(make([]byte, 32), faucet[:]...), make([]byte, crypto.SignatureLength)...),
+ GasLimit: gasLimit,
+ BaseFee: big.NewInt(params.InitialBaseFee),
+ Difficulty: big.NewInt(1),
+ Alloc: map[common.Address]core.GenesisAccount{
+ common.BytesToAddress([]byte{1}): {Balance: big.NewInt(1)}, // ECRecover
+ common.BytesToAddress([]byte{2}): {Balance: big.NewInt(1)}, // SHA256
+ common.BytesToAddress([]byte{3}): {Balance: big.NewInt(1)}, // RIPEMD
+ common.BytesToAddress([]byte{4}): {Balance: big.NewInt(1)}, // Identity
+ common.BytesToAddress([]byte{5}): {Balance: big.NewInt(1)}, // ModExp
+ common.BytesToAddress([]byte{6}): {Balance: big.NewInt(1)}, // ECAdd
+ common.BytesToAddress([]byte{7}): {Balance: big.NewInt(1)}, // ECScalarMul
+ common.BytesToAddress([]byte{8}): {Balance: big.NewInt(1)}, // ECPairing
+ common.BytesToAddress([]byte{9}): {Balance: big.NewInt(1)}, // BLAKE2b
+ faucet: {Balance: new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(9))},
+ },
+ }
+}
func createMiner(t *testing.T) (*Miner, *event.TypeMux, func(skipMiner bool)) {
// Create Ethash config
config := Config{
@@ -252,7 +282,7 @@ func createMiner(t *testing.T) (*Miner, *event.TypeMux, func(skipMiner bool)) {
}
// Create chainConfig
chainDB := rawdb.NewMemoryDatabase()
- genesis := core.DeveloperGenesisBlock(15, 11_500_000, common.HexToAddress("12345"))
+ genesis := minerTestGenesisBlock(15, 11_500_000, common.HexToAddress("12345"))
chainConfig, _, err := core.SetupGenesisBlock(chainDB, trie.NewDatabase(chainDB), genesis)
if err != nil {
t.Fatalf("can't create new chain config: %v", err)
diff --git a/node/node.go b/node/node.go
index e8494ac3b29e1..7ccebe505a8cb 100644
--- a/node/node.go
+++ b/node/node.go
@@ -484,9 +484,20 @@ func (n *Node) startRPC() error {
}
// Configure authenticated API
if len(openAPIs) != len(allAPIs) {
- jwtSecret, err := n.obtainJWTSecret(n.config.JWTSecret)
- if err != nil {
- return err
+ var (
+ jwtSecret []byte
+ err error
+ )
+ if n.config.DataDir == "" {
+ // TODO: make dev-mode and jwt secret flags mutually exclusive
+
+ // in dev-mode we use a preconfigured jwt secret
+ jwtSecret = make([]byte, 32)
+ } else {
+ jwtSecret, err = n.obtainJWTSecret(n.config.JWTSecret)
+ if err != nil {
+ return err
+ }
}
if err := initAuth(n.config.AuthPort, jwtSecret); err != nil {
return err
diff --git a/params/config.go b/params/config.go
index 455abe206239e..55b81f183945d 100644
--- a/params/config.go
+++ b/params/config.go
@@ -134,6 +134,26 @@ var (
Clique: nil,
}
+ AllDevChainProtocolChanges = &ChainConfig{
+ ChainID: big.NewInt(1337),
+ HomesteadBlock: big.NewInt(0),
+ EIP150Block: big.NewInt(0),
+ EIP155Block: big.NewInt(0),
+ EIP158Block: big.NewInt(0),
+ ByzantiumBlock: big.NewInt(0),
+ ConstantinopleBlock: big.NewInt(0),
+ PetersburgBlock: big.NewInt(0),
+ IstanbulBlock: big.NewInt(0),
+ MuirGlacierBlock: big.NewInt(0),
+ BerlinBlock: big.NewInt(0),
+ LondonBlock: big.NewInt(0),
+ ArrowGlacierBlock: big.NewInt(0),
+ GrayGlacierBlock: big.NewInt(0),
+ ShanghaiTime: newUint64(0),
+ TerminalTotalDifficulty: big.NewInt(0),
+ TerminalTotalDifficultyPassed: true,
+ }
+
// AllCliqueProtocolChanges contains every protocol change (EIPs) introduced
// and accepted by the Ethereum core developers into the Clique consensus.
AllCliqueProtocolChanges = &ChainConfig{
@@ -275,8 +295,9 @@ type ChainConfig struct {
TerminalTotalDifficultyPassed bool `json:"terminalTotalDifficultyPassed,omitempty"`
// Various consensus engines
- Ethash *EthashConfig `json:"ethash,omitempty"`
- Clique *CliqueConfig `json:"clique,omitempty"`
+ Ethash *EthashConfig `json:"ethash,omitempty"`
+ Clique *CliqueConfig `json:"clique,omitempty"`
+ Dev *DeveloperModeConfig `json:"dev,omitempty"`
}
// EthashConfig is the consensus engine configs for proof-of-work based sealing.
@@ -298,6 +319,16 @@ func (c *CliqueConfig) String() string {
return "clique"
}
+// DeveloperModeConfig is the genesis config for dev mode
+type DeveloperModeConfig struct {
+ Period uint64 `json:"period"` // Number of seconds between blocks to enforce
+}
+
+// String implements the stringer interface, returning the consensus engine details.
+func (c *DeveloperModeConfig) String() string {
+ return "dev"
+}
+
// Description returns a human-readable description of ChainConfig.
func (c *ChainConfig) Description() string {
var banner string