diff --git a/cmd/geth/consolecmd_test.go b/cmd/geth/consolecmd_test.go index 849f3a356..5cb423a50 100644 --- a/cmd/geth/consolecmd_test.go +++ b/cmd/geth/consolecmd_test.go @@ -33,7 +33,7 @@ import ( const ( ipcAPIs = "admin:1.0 debug:1.0 eth:1.0 istanbul:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 shh:1.0 txpool:1.0 web3:1.0" - httpAPIs = "eth:1.0 net:1.0 rpc:1.0 web3:1.0" + httpAPIs = "admin:1.0 eth:1.0 net:1.0 rpc:1.0 web3:1.0" nodeKey = "b68c0338aa4b266bf38ebe84c6199ae9fac8b29f32998b3ed2fbeafebe8d65c9" ) @@ -142,7 +142,7 @@ func TestHTTPAttachWelcome(t *testing.T) { geth := runGeth(t, "--datadir", datadir, "--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none", - "--etherbase", coinbase, "--rpc", "--rpcport", port) + "--etherbase", coinbase, "--rpc", "--rpcport", port, "--rpcapi", "admin,eth,net,web3") time.Sleep(2 * time.Second) // Simple way to wait for the RPC endpoint to open testAttachWelcome(t, geth, "http://localhost:"+port, httpAPIs) @@ -160,7 +160,7 @@ func TestWSAttachWelcome(t *testing.T) { geth := runGeth(t, "--datadir", datadir, "--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none", - "--etherbase", coinbase, "--ws", "--wsport", port) + "--etherbase", coinbase, "--ws", "--wsport", port, "--wsapi", "admin,eth,net,web3") time.Sleep(2 * time.Second) // Simple way to wait for the RPC endpoint to open testAttachWelcome(t, geth, "ws://localhost:"+port, httpAPIs) @@ -183,7 +183,7 @@ func testAttachWelcome(t *testing.T, geth *testgeth, endpoint, apis string) { attach.SetTemplateFunc("quorumver", func() string { return params.QuorumVersion }) attach.SetTemplateFunc("etherbase", func() string { return geth.Etherbase }) attach.SetTemplateFunc("niltime", func() string { return time.Unix(0, 0).Format(time.RFC1123) }) - attach.SetTemplateFunc("ipc", func() bool { return strings.HasPrefix(endpoint, "ipc") }) + attach.SetTemplateFunc("ipc", func() bool { return strings.HasPrefix(endpoint, "ipc") || strings.Contains(apis, "admin") }) attach.SetTemplateFunc("datadir", func() string { return geth.Datadir }) attach.SetTemplateFunc("apis", func() string { return apis }) diff --git a/console/console.go b/console/console.go index 3c397f800..dd6ccf66d 100644 --- a/console/console.go +++ b/console/console.go @@ -17,6 +17,7 @@ package console import ( + "context" "fmt" "io" "io/ioutil" @@ -271,17 +272,33 @@ func (c *Console) AutoCompleteInput(line string, pos int) (string, []string, str return line[:start], c.jsre.CompleteKeywords(line[start:pos]), line[pos:] } -// Welcome show summary of current Geth instance and some metadata about the +// Welcome shows a summary of the current Geth instance and some metadata about the // console's available modules. func (c *Console) Welcome() { + consensus := c.getConsensus() + // Print some generic Geth metadata fmt.Fprintf(c.printer, "Welcome to the Geth JavaScript console!\n\n") c.jsre.Run(` - console.log("instance: " + web3.version.node); - console.log("coinbase: " + eth.coinbase); - console.log("at block: " + eth.blockNumber + " (" + new Date(1000 * eth.getBlock(eth.blockNumber).timestamp) + ")"); - console.log(" datadir: " + admin.datadir); - `) + console.log("instance: " + web3.version.node); + console.log("coinbase: " + eth.coinbase); + `) + + // Quorum: Block timestamp for Raft is in nanoseconds, so convert accordingly + if consensus == "raft" { + c.jsre.Run(` + console.log("at block: " + eth.blockNumber + " (" + new Date(eth.getBlock(eth.blockNumber).timestamp / 1000000) + ")"); + `) + } else { + c.jsre.Run(` + console.log("at block: " + eth.blockNumber + " (" + new Date(1000 * eth.getBlock(eth.blockNumber).timestamp) + ")"); + `) + } + + c.jsre.Run(` + console.log(" datadir: " + admin.datadir); + `) + // List all the supported modules for the user to call if apis, err := c.client.SupportedModules(); err == nil { modules := make([]string, 0, len(apis)) @@ -294,6 +311,30 @@ func (c *Console) Welcome() { fmt.Fprintln(c.printer) } +// Get the consensus mechanism that is in use +func (c *Console) getConsensus() string { + + var nodeInfo struct { + Protocols struct { + Eth struct { // only partial of eth/handler.go#NodeInfo + Consensus string + } + Istanbul struct { // a bit different from others + Consensus string + } + } + } + + if err := c.client.CallContext(context.Background(), &nodeInfo, "admin_nodeInfo"); err != nil { + _, _ = fmt.Fprintf(c.printer, "WARNING: call to admin.getNodeInfo() failed, unable to determine consensus mechanism\n") + return "unknown" + } + if nodeInfo.Protocols.Istanbul.Consensus != "" { + return nodeInfo.Protocols.Istanbul.Consensus + } + return nodeInfo.Protocols.Eth.Consensus +} + // Evaluate executes code and pretty prints the result to the specified output // stream. func (c *Console) Evaluate(statement string) error { diff --git a/eth/handler.go b/eth/handler.go index 5800d7ec0..528e877ee 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -28,6 +28,8 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/clique" + "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/consensus/misc" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" @@ -831,20 +833,42 @@ type NodeInfo struct { Genesis common.Hash `json:"genesis"` // SHA3 hash of the host's genesis block Config *params.ChainConfig `json:"config"` // Chain configuration for the fork rules Head common.Hash `json:"head"` // SHA3 hash of the host's best owned block + Consensus string `json:"consensus"` // Consensus mechanism in use } // NodeInfo retrieves some protocol metadata about the running host node. func (pm *ProtocolManager) NodeInfo() *NodeInfo { currentBlock := pm.blockchain.CurrentBlock() + return &NodeInfo{ Network: pm.networkID, Difficulty: pm.blockchain.GetTd(currentBlock.Hash(), currentBlock.NumberU64()), Genesis: pm.blockchain.Genesis().Hash(), Config: pm.blockchain.Config(), Head: currentBlock.Hash(), + Consensus: pm.getConsensusAlgorithm(), } } +func (pm *ProtocolManager) getConsensusAlgorithm() string { + var consensusAlgo string + if pm.raftMode { // raft does not use consensus interface + consensusAlgo = "raft" + } else { + switch pm.engine.(type) { + case consensus.Istanbul: + consensusAlgo = "istanbul" + case *clique.Clique: + consensusAlgo = "clique" + case *ethash.Ethash: + consensusAlgo = "ethash" + default: + consensusAlgo = "unknown" + } + } + return consensusAlgo +} + func (self *ProtocolManager) FindPeers(targets map[common.Address]bool) map[common.Address]consensus.Peer { m := make(map[common.Address]consensus.Peer) for _, p := range self.peers.Peers() { diff --git a/eth/handler_test.go b/eth/handler_test.go index 5fe16bedc..9583b3cde 100644 --- a/eth/handler_test.go +++ b/eth/handler_test.go @@ -70,6 +70,45 @@ func TestProtocolCompatibility(t *testing.T) { } } +// Tests that correct consensus mechanism details are returned in NodeInfo. +func TestNodeInfo(t *testing.T) { + + // Define the tests to be run + tests := []struct { + consensus string + cliqueConfig *params.CliqueConfig + istanbulConfig *params.IstanbulConfig + raftMode bool + }{ + {"ethash", nil, nil, false}, + {"raft", nil, nil, true}, + {"istanbul", nil, ¶ms.IstanbulConfig{1, 1}, false}, + {"clique", ¶ms.CliqueConfig{1, 1}, nil, false}, + } + + // Make sure anything we screw up is restored + backup := consensus.EthProtocol.Versions + defer func() { consensus.EthProtocol.Versions = backup }() + + // Try all available consensus mechanisms and check for errors + for i, tt := range tests { + + pm, _, err := newTestProtocolManagerConsensus(tt.consensus, tt.cliqueConfig, tt.istanbulConfig, tt.raftMode) + + if pm != nil { + defer pm.Stop() + } + if err == nil { + pmConsensus := pm.getConsensusAlgorithm() + if tt.consensus != pmConsensus { + t.Errorf("test %d: consensus type error, wanted %v but got %v", i, tt.consensus, pmConsensus) + } + } else { + t.Errorf("test %d: consensus type error %v", i, err) + } + } +} + // Tests that block headers can be retrieved from a remote chain based on user queries. func TestGetBlockHeaders62(t *testing.T) { testGetBlockHeaders(t, 62) } func TestGetBlockHeaders63(t *testing.T) { testGetBlockHeaders(t, 63) } diff --git a/eth/helper_test.go b/eth/helper_test.go index 456797cde..2da1d1fb0 100644 --- a/eth/helper_test.go +++ b/eth/helper_test.go @@ -27,6 +27,11 @@ import ( "sync" "testing" + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/clique" + "github.com/ethereum/go-ethereum/consensus/istanbul" + istanbulBackend "github.com/ethereum/go-ethereum/consensus/istanbul/backend" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" @@ -74,6 +79,58 @@ func newTestProtocolManager(mode downloader.SyncMode, blocks int, generator func return pm, db, nil } +// newTestProtocolManagerConsensus creates a new protocol manager for testing purposes, +// that uses the specified consensus mechanism. +func newTestProtocolManagerConsensus(consensusAlgo string, cliqueConfig *params.CliqueConfig, istanbulConfig *params.IstanbulConfig, raftMode bool) (*ProtocolManager, *ethdb.MemDatabase, error) { + + config := params.QuorumTestChainConfig + config.Clique = cliqueConfig + config.Istanbul = istanbulConfig + + var ( + blocks = 0 + evmux = new(event.TypeMux) + engine consensus.Engine = ethash.NewFaker() + db = ethdb.NewMemDatabase() + gspec = &core.Genesis{ + Config: params.TestChainConfig, + Alloc: core.GenesisAlloc{testBank: {Balance: big.NewInt(1000000)}}, + } + genesis = gspec.MustCommit(db) + blockchain, _ = core.NewBlockChain(db, nil, gspec.Config, engine, vm.Config{}, nil) + ) + chain, _ := core.GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, blocks, nil) + if _, err := blockchain.InsertChain(chain); err != nil { + panic(err) + } + + switch consensusAlgo { + case "raft": + engine = ethash.NewFaker() //raft doesn't use engine, but just mirroring what runtime code does + + case "istanbul": + var istanbul istanbul.Config + config.Istanbul.Epoch = istanbulConfig.Epoch + config.Istanbul.ProposerPolicy = istanbulConfig.ProposerPolicy + + nodeKey, _ := crypto.GenerateKey() + engine = istanbulBackend.New(&istanbul, nodeKey, db) + + case "clique": + engine = clique.New(config.Clique, db) + + default: + engine = ethash.NewFaker() + } + + pm, err := NewProtocolManager(config, 61, DefaultConfig.NetworkId, evmux, &testTxPool{added: nil}, engine, blockchain, db, raftMode) + if err != nil { + return nil, nil, err + } + pm.Start(1000) + return pm, db, nil +} + // newTestProtocolManagerMust creates a new protocol manager for testing purposes, // with the given number of blocks already known, and potential notification // channels for different events. In case of an error, the constructor force-