diff --git a/internal/cli/server/chains/developer.go b/internal/cli/server/chains/developer.go new file mode 100644 index 0000000000..61a8a186fd --- /dev/null +++ b/internal/cli/server/chains/developer.go @@ -0,0 +1,47 @@ +package chains + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" +) + +// GetDeveloperChain returns the developer mode configs. +func GetDeveloperChain(period uint64, faucet common.Address) *Chain { + // Override the default period to the user requested one + config := *params.AllCliqueProtocolChanges + config.Clique = ¶ms.CliqueConfig{ + Period: period, + Epoch: config.Clique.Epoch, + } + + // Assemble and return the chain having genesis with the + // precompiles and faucet pre-funded + return &Chain{ + Hash: common.Hash{}, + NetworkId: 1337, + Genesis: &core.Genesis{ + Config: &config, + ExtraData: append(append(make([]byte, 32), faucet[:]...), make([]byte, crypto.SignatureLength)...), + GasLimit: 11500000, + 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))}, + }, + }, + Bootnodes: []string{}, + } +} diff --git a/internal/cli/server/config.go b/internal/cli/server/config.go index 457ee53aa9..a42470af45 100644 --- a/internal/cli/server/config.go +++ b/internal/cli/server/config.go @@ -14,6 +14,8 @@ import ( godebug "runtime/debug" + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/fdlimit" "github.com/ethereum/go-ethereum/eth/downloader" @@ -91,6 +93,9 @@ type Config struct { // GRPC has the grpc server related settings GRPC *GRPCConfig + + // Developer has the developer mode related settings + Developer *DeveloperConfig } type P2PConfig struct { @@ -365,6 +370,14 @@ type AccountsConfig struct { UseLightweightKDF bool `hcl:"use-lightweight-kdf,optional"` } +type DeveloperConfig struct { + // Enabled enables the developer mode + Enabled bool `hcl:"dev,optional"` + + // Period is the block period to use in developer mode + Period uint64 `hcl:"period,optional"` +} + func DefaultConfig() *Config { return &Config{ Chain: "mainnet", @@ -486,6 +499,10 @@ func DefaultConfig() *Config { GRPC: &GRPCConfig{ Addr: ":3131", }, + Developer: &DeveloperConfig{ + Enabled: false, + Period: 0, + }, } } @@ -573,6 +590,9 @@ func readConfigFile(path string) (*Config, error) { } func (c *Config) loadChain() error { + if c.Developer.Enabled { + return nil + } chain, ok := chains.GetChain(c.Chain) if !ok { return fmt.Errorf("chain '%s' not found", c.Chain) @@ -585,7 +605,7 @@ func (c *Config) loadChain() error { } // depending on the chain we have different cache values - if c.Chain != "mainnet" { + if c.Chain == "mainnet" { c.Cache.Cache = 4096 } else { c.Cache.Cache = 1024 @@ -593,14 +613,19 @@ func (c *Config) loadChain() error { return nil } -func (c *Config) buildEth() (*ethconfig.Config, error) { +func (c *Config) buildEth(stack *node.Node) (*ethconfig.Config, error) { dbHandles, err := makeDatabaseHandles() if err != nil { return nil, err } n := ethconfig.Defaults - n.NetworkId = c.chain.NetworkId - n.Genesis = c.chain.Genesis + + // only update for non-developer mode as we don't yet + // have the chain object for it. + if !c.Developer.Enabled { + n.NetworkId = c.chain.NetworkId + n.Genesis = c.chain.Genesis + } n.HeimdallURL = c.Heimdall.URL n.WithoutHeimdall = c.Heimdall.Without @@ -640,6 +665,55 @@ func (c *Config) buildEth() (*ethconfig.Config, error) { } } + // update for developer mode + if c.Developer.Enabled { + // Get a keystore + var ks *keystore.KeyStore + if keystores := stack.AccountManager().Backends(keystore.KeyStoreType); len(keystores) > 0 { + ks = keystores[0].(*keystore.KeyStore) + } + + // Create new developer account or reuse existing one + var ( + developer accounts.Account + passphrase string + err error + ) + // etherbase has been set above, configuring the miner address from command line flags. + if n.Miner.Etherbase != (common.Address{}) { + developer = accounts.Account{Address: n.Miner.Etherbase} + } else if accs := ks.Accounts(); len(accs) > 0 { + developer = ks.Accounts()[0] + } else { + developer, err = ks.NewAccount(passphrase) + if err != nil { + return nil, fmt.Errorf("failed to create developer account: %v", err) + } + } + if err := ks.Unlock(developer, passphrase); err != nil { + return nil, fmt.Errorf("failed to unlock developer account: %v", err) + } + log.Info("Using developer account", "address", developer.Address) + + // get developer mode chain config + c.chain = chains.GetDeveloperChain(c.Developer.Period, developer.Address) + + // update the parameters + n.NetworkId = c.chain.NetworkId + n.Genesis = c.chain.Genesis + + // Update cache + c.Cache.Cache = 1024 + + // Update sync mode + c.SyncMode = "full" + + // update miner gas price + if n.Miner.GasPrice == nil { + n.Miner.GasPrice = big.NewInt(1) + } + } + // discovery (this params should be in node.Config) { n.EthDiscoveryURLs = c.P2P.Discovery.DNS @@ -788,6 +862,17 @@ func (c *Config) buildNode() (*node.Config, error) { GraphQLVirtualHosts: c.JsonRPC.VHost, } + // dev mode + if c.Developer.Enabled { + cfg.UseLightweightKDF = true + + // disable p2p networking + c.P2P.NoDiscover = true + cfg.P2P.ListenAddr = "" + cfg.P2P.NoDial = true + cfg.P2P.DiscoveryV5 = false + } + // enable jsonrpc endpoints { if c.JsonRPC.Http.Enabled { @@ -806,23 +891,26 @@ func (c *Config) buildNode() (*node.Config, error) { } cfg.P2P.NAT = natif - // Discovery - // if no bootnodes are defined, use the ones from the chain file. - bootnodes := c.P2P.Discovery.Bootnodes - if len(bootnodes) == 0 { - bootnodes = c.chain.Bootnodes - } - if cfg.P2P.BootstrapNodes, err = parseBootnodes(bootnodes); err != nil { - return nil, err - } - if cfg.P2P.BootstrapNodesV5, err = parseBootnodes(c.P2P.Discovery.BootnodesV5); err != nil { - return nil, err - } - if cfg.P2P.StaticNodes, err = parseBootnodes(c.P2P.Discovery.StaticNodes); err != nil { - return nil, err - } - if cfg.P2P.TrustedNodes, err = parseBootnodes(c.P2P.Discovery.TrustedNodes); err != nil { - return nil, err + // only check for non-developer modes + if !c.Developer.Enabled { + // Discovery + // if no bootnodes are defined, use the ones from the chain file. + bootnodes := c.P2P.Discovery.Bootnodes + if len(bootnodes) == 0 { + bootnodes = c.chain.Bootnodes + } + if cfg.P2P.BootstrapNodes, err = parseBootnodes(bootnodes); err != nil { + return nil, err + } + if cfg.P2P.BootstrapNodesV5, err = parseBootnodes(c.P2P.Discovery.BootnodesV5); err != nil { + return nil, err + } + if cfg.P2P.StaticNodes, err = parseBootnodes(c.P2P.Discovery.StaticNodes); err != nil { + return nil, err + } + if cfg.P2P.TrustedNodes, err = parseBootnodes(c.P2P.Discovery.TrustedNodes); err != nil { + return nil, err + } } if c.P2P.NoDiscover { diff --git a/internal/cli/server/config_test.go b/internal/cli/server/config_test.go index 6383c3e003..62296d82a4 100644 --- a/internal/cli/server/config_test.go +++ b/internal/cli/server/config_test.go @@ -16,7 +16,7 @@ func TestConfigDefault(t *testing.T) { _, err := config.buildNode() assert.NoError(t, err) - _, err = config.buildEth() + _, err = config.buildEth(nil) assert.NoError(t, err) } diff --git a/internal/cli/server/flags.go b/internal/cli/server/flags.go index 09e5a7bf78..7403fc6c88 100644 --- a/internal/cli/server/flags.go +++ b/internal/cli/server/flags.go @@ -461,5 +461,17 @@ func (c *Command) Flags() *flagset.Flagset { Usage: "Address and port to bind the GRPC server", Value: &c.cliConfig.GRPC.Addr, }) + + // developer + f.BoolFlag(&flagset.BoolFlag{ + Name: "dev", + Usage: "Enable developer mode with ephemeral proof-of-authority network and a pre-funded developer account, mining enabled", + Value: &c.cliConfig.Developer.Enabled, + }) + f.Uint64Flag(&flagset.Uint64Flag{ + Name: "dev.period", + Usage: "Block period to use in developer mode (0 = mine only if transaction pending)", + Value: &c.cliConfig.Developer.Period, + }) return f } diff --git a/internal/cli/server/server.go b/internal/cli/server/server.go index 67c335dff4..f20ac43d1c 100644 --- a/internal/cli/server/server.go +++ b/internal/cli/server/server.go @@ -69,11 +69,22 @@ func NewServer(config *Config) (*Server, error) { } srv.node = stack + // setup account manager (only keystore) + { + keydir := stack.KeyStoreDir() + n, p := keystore.StandardScryptN, keystore.StandardScryptP + if config.Accounts.UseLightweightKDF { + n, p = keystore.LightScryptN, keystore.LightScryptP + } + stack.AccountManager().AddBackend(keystore.NewKeyStore(keydir, n, p)) + } + // register the ethereum backend - ethCfg, err := config.buildEth() + ethCfg, err := config.buildEth(stack) if err != nil { return nil, err } + backend, err := eth.New(stack, ethCfg) if err != nil { return nil, err @@ -97,18 +108,8 @@ func NewServer(config *Config) (*Server, error) { } } - // setup account manager (only keystore) - { - keydir := stack.KeyStoreDir() - n, p := keystore.StandardScryptN, keystore.StandardScryptP - if config.Accounts.UseLightweightKDF { - n, p = keystore.LightScryptN, keystore.LightScryptP - } - stack.AccountManager().AddBackend(keystore.NewKeyStore(keydir, n, p)) - } - - // sealing (if enabled) - if config.Sealer.Enabled { + // sealing (if enabled) or in dev mode + if config.Sealer.Enabled || config.Developer.Enabled { if err := backend.StartMining(1); err != nil { return nil, err } diff --git a/internal/cli/server/server_test.go b/internal/cli/server/server_test.go new file mode 100644 index 0000000000..070739c687 --- /dev/null +++ b/internal/cli/server/server_test.go @@ -0,0 +1,41 @@ +package server + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestServer_DeveloperMode(t *testing.T) { + + // get the default config + config := DefaultConfig() + + // enable developer mode + config.Developer.Enabled = true + config.Developer.Period = 2 // block time + + // start the server + server, err1 := NewServer(config) + if err1 != nil { + t.Fatalf("failed to start server: %v", err1) + } + + // record the initial block number + blockNumber := server.backend.BlockChain().CurrentBlock().Header().Number.Int64() + + var i int64 = 0 + for i = 0; i < 10; i++ { + // We expect the node to mine blocks every `config.Developer.Period` time period + time.Sleep(time.Duration(config.Developer.Period) * time.Second) + currBlock := server.backend.BlockChain().CurrentBlock().Header().Number.Int64() + expected := blockNumber + i + 1 + if res := assert.Equal(t, currBlock, expected); res == false { + break + } + } + + // stop the server + server.Stop() +}