Bugfix/geth console displays incorrect block timestamp (#762)

Fix issue where geth console displays incorrect timestamp under Raft, due to Raft holding block timestamp in nanoseconds, instead of seconds.
This commit is contained in:
Satpal 2019-07-17 22:11:38 +01:00 committed by Samer Falah
parent 9c0195fd57
commit a9f96e0954
5 changed files with 171 additions and 10 deletions

View File

@ -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 })

View File

@ -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 {

View File

@ -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() {

View File

@ -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, &params.IstanbulConfig{1, 1}, false},
{"clique", &params.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) }

View File

@ -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-