diff options
author | Wei-Ning Huang <w@dexon.org> | 2019-03-17 09:12:50 +0800 |
---|---|---|
committer | Wei-Ning Huang <w@byzantine-lab.io> | 2019-06-12 17:27:23 +0800 |
commit | 9493109f2be4507605e6b17e406bf8fd147ab3c8 (patch) | |
tree | 84fee3d1fb2095ff5ba793bdfccba89970bc8f89 | |
parent | 9ee92eff068b09246ab6446efa39abfc0c7bd8a8 (diff) | |
download | go-tangerine-9493109f2be4507605e6b17e406bf8fd147ab3c8.tar.gz go-tangerine-9493109f2be4507605e6b17e406bf8fd147ab3c8.tar.zst go-tangerine-9493109f2be4507605e6b17e406bf8fd147ab3c8.zip |
dex: implement recovery mechanism (#258)
* dex: implement recovery mechanism
The DEXON recovery protocol allows us to use the Ethereum blockchain as a
fallback consensus chain to coordinate recovery.
* fix
32 files changed, 2435 insertions, 435 deletions
diff --git a/cmd/gdex/main.go b/cmd/gdex/main.go index dcd46e796..29b61a4ec 100644 --- a/cmd/gdex/main.go +++ b/cmd/gdex/main.go @@ -140,6 +140,7 @@ var ( utils.IndexerEnableFlag, utils.IndexerPluginFlag, utils.IndexerPluginFlagsFlag, + utils.RecoveryNetworkRPCFlag, configFileFlag, } diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index c6d60d0bb..6b0c682a7 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -664,6 +664,13 @@ var ( Usage: "External indexer plugin's flags if needed", Value: "", } + + // Dexcon settings. + RecoveryNetworkRPCFlag = cli.StringFlag{ + Name: "recovery.network-rpc", + Usage: "RPC URL of the recovery network", + Value: "https://mainnet.infura.io", + } ) // MakeDataDir retrieves the currently requested data directory, terminating @@ -1257,27 +1264,41 @@ func SetDexConfig(ctx *cli.Context, stack *node.Node, cfg *dex.Config) { cfg.RPCGasCap = new(big.Int).SetUint64(ctx.GlobalUint64(RPCGlobalGasCap.Name)) } + cfg.RecoveryNetworkRPC = ctx.GlobalString(RecoveryNetworkRPCFlag.Name) + // Override any default configs for hard coded networks. switch { case ctx.GlobalBool(TestnetFlag.Name): if !ctx.GlobalIsSet(NetworkIdFlag.Name) { cfg.NetworkId = 238 } + if !ctx.GlobalIsSet(RecoveryNetworkRPCFlag.Name) { + cfg.RecoveryNetworkRPC = "http://rinkeby.infura.io" + } cfg.Genesis = core.DefaultTestnetGenesisBlock() case ctx.GlobalBool(TaipeiFlag.Name): if !ctx.GlobalIsSet(NetworkIdFlag.Name) { cfg.NetworkId = 239 } + if !ctx.GlobalIsSet(RecoveryNetworkRPCFlag.Name) { + cfg.RecoveryNetworkRPC = "http://rinkeby.infura.io" + } cfg.Genesis = core.DefaultTaipeiGenesisBlock() case ctx.GlobalBool(YilanFlag.Name): if !ctx.GlobalIsSet(NetworkIdFlag.Name) { cfg.NetworkId = 240 } + if !ctx.GlobalIsSet(RecoveryNetworkRPCFlag.Name) { + cfg.RecoveryNetworkRPC = "http://rinkeby.infura.io" + } cfg.Genesis = core.DefaultYilanGenesisBlock() case ctx.GlobalBool(DeveloperFlag.Name): if !ctx.GlobalIsSet(NetworkIdFlag.Name) { cfg.NetworkId = 1337 } + if !ctx.GlobalIsSet(RecoveryNetworkRPCFlag.Name) { + cfg.RecoveryNetworkRPC = "http://rinkeby.infura.io" + } // Create new developer account or reuse existing one var ( developer accounts.Account diff --git a/core/vm/oracle_contracts.go b/core/vm/oracle_contracts.go index 37e734a5f..d983cdfc8 100644 --- a/core/vm/oracle_contracts.go +++ b/core/vm/oracle_contracts.go @@ -95,7 +95,7 @@ func publicKeyToNodeKeyAddress(pkBytes []byte) (common.Address, error) { return crypto.PubkeyToAddress(*pk), nil } -func idToAddress(id coreTypes.NodeID) common.Address { +func IdToAddress(id coreTypes.NodeID) common.Address { return common.BytesToAddress(id.Hash[12:]) } @@ -517,7 +517,7 @@ func (s *GovernanceState) DeleteNodeOffsets(n *nodeInfo) error { } func (s *GovernanceState) GetNodeByID(id coreTypes.NodeID) (*nodeInfo, error) { - offset := s.NodesOffsetByNodeKeyAddress(idToAddress(id)) + offset := s.NodesOffsetByNodeKeyAddress(IdToAddress(id)) if offset.Cmp(big.NewInt(0)) < 0 { return nil, errors.New("node not found") } @@ -630,7 +630,7 @@ func (s *GovernanceState) PutDKGMPKReady(addr common.Address, ready bool) { } func (s *GovernanceState) ClearDKGMPKReady(dkgSet map[coreTypes.NodeID]struct{}) { for id := range dkgSet { - s.PutDKGMPKReady(idToAddress(id), false) + s.PutDKGMPKReady(IdToAddress(id), false) } } @@ -661,7 +661,7 @@ func (s *GovernanceState) PutDKGFinalized(addr common.Address, finalized bool) { } func (s *GovernanceState) ClearDKGFinalized(dkgSet map[coreTypes.NodeID]struct{}) { for id := range dkgSet { - s.PutDKGFinalized(idToAddress(id), false) + s.PutDKGFinalized(IdToAddress(id), false) } } diff --git a/core/vm/oracle_contracts_test.go b/core/vm/oracle_contracts_test.go index 4539f0864..6e2a7fc7f 100644 --- a/core/vm/oracle_contracts_test.go +++ b/core/vm/oracle_contracts_test.go @@ -933,7 +933,7 @@ func (g *OracleContractsTestSuite) TestResetDKG() { dkgSets[round] = dkgSet for id := range dkgSet { - offset := g.s.NodesOffsetByNodeKeyAddress(idToAddress(id)) + offset := g.s.NodesOffsetByNodeKeyAddress(IdToAddress(id)) if offset.Cmp(big.NewInt(0)) < 0 { panic("DKG node does not exist") } diff --git a/dex/backend.go b/dex/backend.go index 056a6d221..6ee1a5fa1 100644 --- a/dex/backend.go +++ b/dex/backend.go @@ -21,6 +21,7 @@ import ( "fmt" "time" + "github.com/dexon-foundation/dexon-consensus/core/syncer" "github.com/dexon-foundation/dexon/accounts" "github.com/dexon-foundation/dexon/consensus" "github.com/dexon-foundation/dexon/consensus/dexcon" @@ -180,7 +181,12 @@ func New(ctx *node.ServiceContext, config *Config) (*Dexon, error) { dex.protocolManager = pm dex.network = NewDexconNetwork(pm) - dex.bp = NewBlockProposer(dex, dMoment) + recovery := NewRecovery(chainConfig.Recovery, config.RecoveryNetworkRPC, + dex.governance, config.PrivateKey) + watchCat := syncer.NewWatchCat(recovery, dex.governance, 10*time.Second, + time.Duration(chainConfig.Recovery.Timeout)*time.Second, log.Root()) + + dex.bp = NewBlockProposer(dex, watchCat, dMoment) return dex, nil } diff --git a/dex/blockproposer.go b/dex/blockproposer.go index ad8b4c2b0..6c1de818a 100644 --- a/dex/blockproposer.go +++ b/dex/blockproposer.go @@ -24,16 +24,18 @@ type blockProposer struct { syncing int32 proposing int32 dex *Dexon + watchCat *syncer.WatchCat dMoment time.Time wg sync.WaitGroup stopCh chan struct{} } -func NewBlockProposer(dex *Dexon, dMoment time.Time) *blockProposer { +func NewBlockProposer(dex *Dexon, watchCat *syncer.WatchCat, dMoment time.Time) *blockProposer { return &blockProposer{ - dex: dex, - dMoment: dMoment, + dex: dex, + watchCat: watchCat, + dMoment: dMoment, } } @@ -116,9 +118,21 @@ func (b *blockProposer) syncConsensus() (*dexCore.Consensus, error) { consensusSync := syncer.NewConsensus(b.dMoment, b.dex.app, b.dex.governance, db, b.dex.network, privkey, log.Root()) + // Start the watchCat. + log.Info("Starting sync watchCat ...") + b.watchCat.Start() + + // Feed the current block we have in local blockchain. + cb := b.dex.blockchain.CurrentBlock() + var block coreTypes.Block + if err := rlp.DecodeBytes(cb.Header().DexconMeta, &block); err != nil { + panic(err) + } + b.watchCat.Feed(block.Position) + blocksToSync := func(coreHeight, height uint64) []*coreTypes.Block { var blocks []*coreTypes.Block - for len(blocks) < 1024 && coreHeight < height { + for coreHeight < height { var block coreTypes.Block b := b.dex.blockchain.GetBlockByNumber(coreHeight + 1) if err := rlp.DecodeBytes(b.Header().DexconMeta, &block); err != nil { @@ -143,6 +157,7 @@ Loop: if len(blocks) == 0 { break Loop } + b.watchCat.Feed(blocks[len(blocks)-1].Position) log.Debug("Filling compaction chain", "num", len(blocks), "first", blocks[0].Finalization.Height, @@ -172,6 +187,8 @@ ListenLoop: select { case ev := <-ch: blocks := blocksToSync(coreHeight, ev.Block.NumberU64()) + b.watchCat.Feed(blocks[len(blocks)-1].Position) + if len(blocks) > 0 { log.Debug("Filling compaction chain", "num", len(blocks), "first", blocks[0].Finalization.Height, @@ -193,8 +210,13 @@ ListenLoop: case <-b.stopCh: log.Debug("Early stop, before consensus core can run") return nil, errors.New("early stop") + case <-b.watchCat.Meow(): + log.Info("WatchCat signaled to stop syncing") + consensusSync.ForceSync(true) + break ListenLoop } } + b.watchCat.Stop() return consensusSync.GetSyncedConsensus() } diff --git a/dex/config.go b/dex/config.go index 7661907cc..d218b35e2 100644 --- a/dex/config.go +++ b/dex/config.go @@ -126,4 +126,7 @@ type Config struct { // Indexer config Indexer indexer.Config + + // Recovery network RPC + RecoveryNetworkRPC string } diff --git a/dex/governance.go b/dex/governance.go index d9cf8fb65..35c5b4174 100644 --- a/dex/governance.go +++ b/dex/governance.go @@ -263,6 +263,19 @@ func (d *DexconGovernance) NotarySet(round uint64) (map[string]struct{}, error) return r, nil } +func (d *DexconGovernance) NotarySetAddresses(round uint64) (map[common.Address]struct{}, error) { + notarySet, err := d.nodeSetCache.GetNotarySet(round) + if err != nil { + return nil, err + } + + r := make(map[common.Address]struct{}, len(notarySet)) + for id := range notarySet { + r[vm.IdToAddress(id)] = struct{}{} + } + return r, nil +} + func (d *DexconGovernance) DKGSet(round uint64) (map[string]struct{}, error) { dkgSet, err := d.nodeSetCache.GetDKGSet(round) if err != nil { diff --git a/dex/recovery.go b/dex/recovery.go new file mode 100644 index 000000000..cfc8ae203 --- /dev/null +++ b/dex/recovery.go @@ -0,0 +1,484 @@ +// Copyright 2018 The dexon-consensus Authors +// This file is part of the dexon-consensus library. +// +// The dexon-consensus 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 dexon-consensus 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 dexon-consensus library. If not, see +// <http://www.gnu.org/licenses/>. + +package dex + +import ( + "crypto/ecdsa" + "encoding/hex" + "fmt" + "math/big" + "strconv" + "strings" + + "github.com/dexon-foundation/dexon/accounts/abi" + "github.com/dexon-foundation/dexon/common" + "github.com/dexon-foundation/dexon/core/types" + "github.com/dexon-foundation/dexon/crypto" + "github.com/dexon-foundation/dexon/params" + "github.com/dexon-foundation/dexon/rlp" + "github.com/onrik/ethrpc" +) + +const numConfirmation = 1 + +const recoveryABI = ` +[ + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "uint256" + }, + { + "name": "", + "type": "address" + } + ], + "name": "voted", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "owner", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "isOwner", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "depositValue", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "uint256" + }, + { + "name": "", + "type": "uint256" + } + ], + "name": "votes", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "address" + } + ], + "name": "withdrawable", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "height", + "type": "uint256" + }, + { + "indexed": false, + "name": "voter", + "type": "address" + } + ], + "name": "VotedForRecovery", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "name": "amount", + "type": "uint256" + } + ], + "name": "Withdrawn", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "constant": false, + "inputs": [ + { + "name": "DepositValue", + "type": "uint256" + } + ], + "name": "setDeposit", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "height", + "type": "uint256" + }, + { + "name": "value", + "type": "uint256" + } + ], + "name": "refund", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "withdraw", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "height", + "type": "uint256" + } + ], + "name": "voteForSkipBlock", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "height", + "type": "uint256" + } + ], + "name": "numVotes", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } +] +` + +var abiObject abi.ABI + +func init() { + var err error + abiObject, err = abi.JSON(strings.NewReader(recoveryABI)) + if err != nil { + panic(err) + } +} + +type Recovery struct { + gov *DexconGovernance + contract common.Address + confirmation int + privateKey *ecdsa.PrivateKey + nodeAddress common.Address + client *ethrpc.EthRPC +} + +func NewRecovery(config *params.RecoveryConfig, networkRPC string, + gov *DexconGovernance, privKey *ecdsa.PrivateKey) *Recovery { + client := ethrpc.New(networkRPC) + return &Recovery{ + gov: gov, + contract: config.Contract, + confirmation: config.Confirmation, + privateKey: privKey, + nodeAddress: crypto.PubkeyToAddress(privKey.PublicKey), + client: client, + } +} + +func (r *Recovery) genVoteForSkipBlockTx(height uint64) (*types.Transaction, error) { + netVersion, err := r.client.NetVersion() + if err != nil { + return nil, err + } + + networkID, err := strconv.Atoi(netVersion) + if err != nil { + return nil, err + } + + data, err := abiObject.Pack("depositValue") + if err != nil { + return nil, err + } + + res, err := r.client.EthCall(ethrpc.T{ + From: r.nodeAddress.String(), + To: r.contract.String(), + Data: "0x" + hex.EncodeToString(data), + }, "latest") + if err != nil { + return nil, err + } + + resBytes, err := hex.DecodeString(res[2:]) + if err != nil { + return nil, err + } + + var depositValue *big.Int + err = abiObject.Unpack(&depositValue, "depositValue", resBytes) + if err != nil { + return nil, err + } + + data, err = abiObject.Pack("voteForSkipBlock", new(big.Int).SetUint64(height)) + if err != nil { + return nil, err + } + + gasPrice, err := r.client.EthGasPrice() + if err != nil { + return nil, err + } + + nonce, err := r.client.EthGetTransactionCount(r.nodeAddress.String(), "pending") + if err != nil { + return nil, err + } + + // Increase gasPrice to 3 times of suggested gas price to make sure it will + // be included in time. + useGasPrice := new(big.Int).Mul(&gasPrice, big.NewInt(3)) + + tx := types.NewTransaction( + uint64(nonce), + r.contract, + depositValue, + uint64(100000), + useGasPrice, + data) + + signer := types.NewEIP155Signer(big.NewInt(int64(networkID))) + return types.SignTx(tx, signer, r.privateKey) +} + +func (r *Recovery) ProposeSkipBlock(height uint64) error { + tx, err := r.genVoteForSkipBlockTx(height) + if err != nil { + return err + } + + txData, err := rlp.EncodeToBytes(tx) + if err != nil { + return err + } + _, err = r.client.EthSendRawTransaction("0x" + hex.EncodeToString(txData)) + return err +} + +func (r *Recovery) Votes(height uint64) (uint64, error) { + data, err := abiObject.Pack("numVotes", new(big.Int).SetUint64(height)) + if err != nil { + return 0, err + } + + bn, err := r.client.EthBlockNumber() + if err != nil { + return 0, err + } + + snapshotHeight := bn - numConfirmation + + res, err := r.client.EthCall(ethrpc.T{ + From: r.nodeAddress.String(), + To: r.contract.String(), + Data: "0x" + hex.EncodeToString(data), + }, fmt.Sprintf("0x%x", snapshotHeight)) + if err != nil { + return 0, err + } + + resBytes, err := hex.DecodeString(res[2:]) + if err != nil { + return 0, err + } + + votes := new(big.Int) + err = abiObject.Unpack(&votes, "numVotes", resBytes) + if err != nil { + return 0, err + } + + notarySet, err := r.gov.NotarySetAddresses(r.gov.Round()) + if err != nil { + return 0, err + } + + count := uint64(0) + + for i := uint64(0); i < votes.Uint64(); i++ { + data, err = abiObject.Pack( + "votes", new(big.Int).SetUint64(height), new(big.Int).SetUint64(i)) + if err != nil { + return 0, err + } + + res, err = r.client.EthCall(ethrpc.T{ + From: r.nodeAddress.String(), + To: r.contract.String(), + Data: "0x" + hex.EncodeToString(data), + }, fmt.Sprintf("0x%x", snapshotHeight)) + if err != nil { + return 0, err + } + + resBytes, err := hex.DecodeString(res[2:]) + if err != nil { + return 0, err + } + + var addr common.Address + err = abiObject.Unpack(&addr, "votes", resBytes) + if err != nil { + return 0, err + } + + if _, ok := notarySet[addr]; ok { + count += 1 + } + } + return count, nil +} diff --git a/dex/recovery_test.go b/dex/recovery_test.go new file mode 100644 index 000000000..8c039db35 --- /dev/null +++ b/dex/recovery_test.go @@ -0,0 +1,43 @@ +// Copyright 2018 The dexon-consensus Authors +// This file is part of the dexon-consensus library. +// +// The dexon-consensus 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 dexon-consensus 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 dexon-consensus library. If not, see +// <http://www.gnu.org/licenses/>. + +package dex + +import ( + "testing" + + "github.com/dexon-foundation/dexon/common" + "github.com/dexon-foundation/dexon/crypto" + "github.com/dexon-foundation/dexon/params" +) + +func TestRecoveryVoteTxGeneration(t *testing.T) { + key, err := crypto.GenerateKey() + if err != nil { + t.Fatalf("failed to generate keypair: %v", err) + } + + r := NewRecovery(¶ms.RecoveryConfig{ + Contract: common.HexToAddress("f675c0e9bf4b949f50dcec5b224a70f0361d4680"), + Timeout: 30, + Confirmation: 1, + }, "https://rinkeby.infura.io", nil, key) + _, err = r.genVoteForSkipBlockTx(0) + if err != nil { + t.Fatalf("failed to generate voteForSkipBlock tx: %v", err) + } +} diff --git a/params/config.go b/params/config.go index 8cf986f55..4eefc8f08 100644 --- a/params/config.go +++ b/params/config.go @@ -75,6 +75,11 @@ var ( new(big.Int).Mul(big.NewInt(1e18), big.NewInt(1e5)), }, }, + Recovery: &RecoveryConfig{ + Contract: common.HexToAddress("0xcb4bb8ae26b2ebe5a1e2e8d5236020f33ffb2294"), + Timeout: 120, + Confirmation: 5, + }, } // MainnetTrustedCheckpoint contains the light client trusted checkpoint for the main network. @@ -121,6 +126,11 @@ var ( new(big.Int).Mul(big.NewInt(1e18), big.NewInt(1e5)), }, }, + Recovery: &RecoveryConfig{ + Contract: common.HexToAddress("0x4ebe3d13ab18b30d815711b7a33ef1226777b66d"), + Timeout: 120, + Confirmation: 5, + }, } // TaipeiChainConfig contains the chain parameters to run a node on the Taipei test network. @@ -158,6 +168,11 @@ var ( new(big.Int).Mul(big.NewInt(1e18), big.NewInt(1e5)), }, }, + Recovery: &RecoveryConfig{ + Contract: common.HexToAddress("0xac86ab80ab27007801f36f6622fbe0a9432291a2"), + Timeout: 120, + Confirmation: 1, + }, } // TestnetTrustedCheckpoint contains the light client trusted checkpoint for the Ropsten test network. @@ -203,6 +218,11 @@ var ( new(big.Int).Mul(big.NewInt(1e18), big.NewInt(1e5)), }, }, + Recovery: &RecoveryConfig{ + Contract: common.HexToAddress("0x3828134ba7a0629fd52067b80fe696f400eb83dc"), + Timeout: 120, + Confirmation: 1, + }, } // AllEthashProtocolChanges contains every protocol change (EIPs) introduced @@ -210,18 +230,18 @@ var ( // // This configuration is intentionally not using keyed fields to force anyone // adding flags to the config to also have to set these fields. - AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), 0, big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil, nil} + AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), 0, big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil, nil, nil} // AllCliqueProtocolChanges contains every protocol change (EIPs) introduced // and accepted by the Ethereum core developers into the Clique consensus. // // This configuration is intentionally not using keyed fields to force anyone // adding flags to the config to also have to set these fields. - AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), 0, big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}, nil} + AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), 0, big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}, nil, nil} - AllDexconProtocolChanges = &ChainConfig{big.NewInt(1337), 0, big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, new(DexconConfig)} + AllDexconProtocolChanges = &ChainConfig{big.NewInt(1337), 0, big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, new(DexconConfig), new(RecoveryConfig)} - TestChainConfig = &ChainConfig{big.NewInt(1), 0, big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil, nil} + TestChainConfig = &ChainConfig{big.NewInt(1), 0, big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil, nil, nil} TestRules = TestChainConfig.Rules(new(big.Int)) // Ethereum MainnetChainConfig is the chain parameters to run a node on the main network. @@ -299,6 +319,9 @@ type ChainConfig struct { Ethash *EthashConfig `json:"ethash,omitempty"` Clique *CliqueConfig `json:"clique,omitempty"` Dexcon *DexconConfig `json:"dexcon,omitempty"` + + // Dexcon Recovery + Recovery *RecoveryConfig `json:"recovery,omitempty"` } // EthashConfig is the consensus engine configs for proof-of-work based sealing. @@ -372,6 +395,12 @@ func (d *DexconConfig) String() string { ) } +type RecoveryConfig struct { + Contract common.Address `json:"contract"` + Timeout int `json:"timeout"` + Confirmation int `json:"confirmation"` +} + // String implements the fmt.Stringer interface. func (c *ChainConfig) String() string { var engine interface{} diff --git a/params/gen_dexcon_config.go b/params/gen_dexcon_config.go index 1afe11f56..38753916a 100644 --- a/params/gen_dexcon_config.go +++ b/params/gen_dexcon_config.go @@ -22,6 +22,7 @@ func (d DexconConfig) MarshalJSON() ([]byte, error) { MiningVelocity float32 `json:"miningVelocity"` NextHalvingSupply *math.HexOrDecimal256 `json:"nextHalvingSupply"` LastHalvedAmount *math.HexOrDecimal256 `json:"lastHalvedAmount"` + MinGasPrice *math.HexOrDecimal256 `json:"minGasPrice"` BlockGasLimit uint64 `json:"blockGasLimit"` LambdaBA uint64 `json:"lambdaBA"` LambdaDKG uint64 `json:"lambdaDKG"` @@ -30,7 +31,6 @@ func (d DexconConfig) MarshalJSON() ([]byte, error) { RoundLength uint64 `json:"roundLength"` MinBlockInterval uint64 `json:"minBlockInterval"` FineValues []*math.HexOrDecimal256 `json:"fineValues"` - MinGasPrice *math.HexOrDecimal256 `json:"minGasPrice"` } var enc DexconConfig enc.GenesisCRSText = d.GenesisCRSText @@ -40,6 +40,7 @@ func (d DexconConfig) MarshalJSON() ([]byte, error) { enc.MiningVelocity = d.MiningVelocity enc.NextHalvingSupply = (*math.HexOrDecimal256)(d.NextHalvingSupply) enc.LastHalvedAmount = (*math.HexOrDecimal256)(d.LastHalvedAmount) + enc.MinGasPrice = (*math.HexOrDecimal256)(d.MinGasPrice) enc.BlockGasLimit = d.BlockGasLimit enc.LambdaBA = d.LambdaBA enc.LambdaDKG = d.LambdaDKG @@ -53,7 +54,6 @@ func (d DexconConfig) MarshalJSON() ([]byte, error) { enc.FineValues[k] = (*math.HexOrDecimal256)(v) } } - enc.MinGasPrice = (*math.HexOrDecimal256)(d.MinGasPrice) return json.Marshal(&enc) } @@ -67,6 +67,7 @@ func (d *DexconConfig) UnmarshalJSON(input []byte) error { MiningVelocity *float32 `json:"miningVelocity"` NextHalvingSupply *math.HexOrDecimal256 `json:"nextHalvingSupply"` LastHalvedAmount *math.HexOrDecimal256 `json:"lastHalvedAmount"` + MinGasPrice *math.HexOrDecimal256 `json:"minGasPrice"` BlockGasLimit *uint64 `json:"blockGasLimit"` LambdaBA *uint64 `json:"lambdaBA"` LambdaDKG *uint64 `json:"lambdaDKG"` @@ -75,7 +76,6 @@ func (d *DexconConfig) UnmarshalJSON(input []byte) error { RoundLength *uint64 `json:"roundLength"` MinBlockInterval *uint64 `json:"minBlockInterval"` FineValues []*math.HexOrDecimal256 `json:"fineValues"` - MinGasPrice *math.HexOrDecimal256 `json:"minGasPrice"` } var dec DexconConfig if err := json.Unmarshal(input, &dec); err != nil { @@ -102,6 +102,9 @@ func (d *DexconConfig) UnmarshalJSON(input []byte) error { if dec.LastHalvedAmount != nil { d.LastHalvedAmount = (*big.Int)(dec.LastHalvedAmount) } + if dec.MinGasPrice != nil { + d.MinGasPrice = (*big.Int)(dec.MinGasPrice) + } if dec.BlockGasLimit != nil { d.BlockGasLimit = *dec.BlockGasLimit } @@ -129,8 +132,5 @@ func (d *DexconConfig) UnmarshalJSON(input []byte) error { d.FineValues[k] = (*big.Int)(v) } } - if dec.MinGasPrice != nil { - d.MinGasPrice = (*big.Int)(dec.MinGasPrice) - } return nil } diff --git a/test/genesis.json b/test/genesis.json index fc6cd7fe4..8be5a844d 100644 --- a/test/genesis.json +++ b/test/genesis.json @@ -19,6 +19,7 @@ "miningVelocity": 0.1875, "nextHalvingSupply": "0x813f3978f89409844000000", "lastHalvedAmount": "0x4d8c55aefb8c05b5c000000", + "minGasPrice": "0x3b9aca00", "blockGasLimit": 40000000, "lambdaBA": 250, "lambdaDKG": 1500, @@ -30,8 +31,12 @@ "0x21e19e0c9bab2400000", "0x21e19e0c9bab2400000", "0x152d02c7e14af6800000" - ], - "minGasPrice": "0x3b9aca00" + ] + }, + "recovery": { + "contract": "0xf675c0e9bf4b949f50dcec5b224a70f0361d4680", + "timeout": 30, + "confirmation": 1 } }, "nonce": "0x42", diff --git a/test/run_test.sh b/test/run_test.sh index 48934ab7c..b6db1ba09 100755 --- a/test/run_test.sh +++ b/test/run_test.sh @@ -1,6 +1,6 @@ #!/bin/bash -NETWORK="--bootnodes enode://0478aa13c91aa0db8e93b668313b7eb0532fbdb24f64772375373b14dbe326c238ad09ab4469f6442c9a9753f1275aeec2e531912c14a958ed1feb4ae7e227ef@127.0.0.1:30301" +BOOTNODE_FLAGS="--bootnodes enode://0478aa13c91aa0db8e93b668313b7eb0532fbdb24f64772375373b14dbe326c238ad09ab4469f6442c9a9753f1275aeec2e531912c14a958ed1feb4ae7e227ef@127.0.0.1:30301" GENESIS="genesis.json" GDEX="../build/bin/gdex" @@ -42,10 +42,12 @@ __FILE__ # A standalone RPC server for accepting RPC requests. datadir=$PWD/Dexon.rpc -rm -rf $datadir -$GDEX --datadir=$datadir init ${GENESIS} +if [ "$1" != "--continue" ]; then + rm -rf $datadir + $GDEX --datadir=$datadir init ${GENESIS} +fi $GDEX \ - ${NETWORK} \ + ${BOOTNODE_FLAGS} \ --verbosity=3 \ --gcmode=archive \ --datadir=$datadir --nodekey=keystore/rpc.key \ @@ -61,15 +63,19 @@ NUM_NODES=$(cat ${GENESIS} | grep 'DEXON Test Node' | wc -l) # Nodes for i in $(seq 0 $(($NUM_NODES - 1))); do datadir=$PWD/Dexon.$i - rm -rf $datadir - $GDEX --datadir=$datadir init ${GENESIS} + + if [ "$1" != "--continue" ]; then + rm -rf $datadir + $GDEX --datadir=$datadir init ${GENESIS} + fi $GDEX \ - ${NETWORK} \ + ${BOOTNODE_FLAGS} \ --bp \ --verbosity=4 \ --gcmode=archive \ --datadir=$datadir --nodekey=keystore/test$i.key \ --port=$((30305 + $i)) \ + --recovery.network-rpc="https://rinkeby.infura.io" \ --rpc --rpcapi=eth,net,web3,debug \ --rpcaddr=0.0.0.0 --rpcport=$((8547 + $i * 2)) \ --ws --wsapi=eth,net,web3,debug \ diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/agreement-mgr.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/agreement-mgr.go index 7b5effba8..0e39fa52a 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/agreement-mgr.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/agreement-mgr.go @@ -90,8 +90,6 @@ func newAgreementMgrConfig(prev agreementMgrConfig, config *types.Config, type baRoundSetting struct { notarySet map[types.NodeID]struct{} - agr *agreement - recv *consensusBAReceiver ticker Ticker crs common.Hash } @@ -111,6 +109,7 @@ type agreementMgr struct { initRound uint64 configs []agreementMgrConfig baModule *agreement + recv *consensusBAReceiver processedBAResult map[types.Position]struct{} voteFilter *utils.VoteFilter waitGroup sync.WaitGroup @@ -136,15 +135,17 @@ func newAgreementMgr(con *Consensus, initRound uint64, configs: []agreementMgrConfig{initConfig}, voteFilter: utils.NewVoteFilter(), } - recv := &consensusBAReceiver{ - consensus: con, - restartNotary: make(chan types.Position, 1), - roundValue: &atomic.Value{}, + mgr.recv = &consensusBAReceiver{ + consensus: con, + restartNotary: make(chan types.Position, 1), + roundValue: &atomic.Value{}, + changeNotaryHeightValue: &atomic.Value{}, } - recv.roundValue.Store(uint64(0)) + mgr.recv.roundValue.Store(uint64(0)) + mgr.recv.changeNotaryHeightValue.Store(uint64(0)) agr := newAgreement( mgr.ID, - recv, + mgr.recv, newLeaderSelector(genValidLeader(mgr), mgr.logger), mgr.signer, mgr.logger) @@ -156,7 +157,7 @@ func newAgreementMgr(con *Consensus, initRound uint64, agr.notarySet = nodes.GetSubSet( int(initConfig.notarySetSize), types.NewNotarySetTarget(initConfig.crs)) // Hacky way to make agreement module self contained. - recv.agreementModule = agr + mgr.recv.agreementModule = agr mgr.baModule = agr return } @@ -188,15 +189,43 @@ func (mgr *agreementMgr) config(round uint64) *agreementMgrConfig { return &mgr.configs[roundIndex] } -func (mgr *agreementMgr) appendConfig( - round uint64, config *types.Config, crs common.Hash) (err error) { +func (mgr *agreementMgr) notifyRoundEvents(evts []utils.RoundEventParam) error { mgr.lock.Lock() defer mgr.lock.Unlock() - if round != uint64(len(mgr.configs))+mgr.initRound { - return ErrRoundNotIncreasing + apply := func(e utils.RoundEventParam) error { + if len(mgr.configs) > 0 { + lastCfg := mgr.configs[len(mgr.configs)-1] + if e.BeginHeight != lastCfg.RoundEndHeight() { + return ErrInvalidBlockHeight + } + if lastCfg.RoundID() == e.Round { + mgr.configs[len(mgr.configs)-1].ExtendLength() + // It's not an atomic operation to update an atomic value based + // on another. However, it's the best way so far to extend + // length of round without refactoring. + if mgr.recv.round() == e.Round { + mgr.recv.changeNotaryHeightValue.Store( + mgr.configs[len(mgr.configs)-1].RoundEndHeight()) + } + } else if lastCfg.RoundID()+1 == e.Round { + mgr.configs = append(mgr.configs, newAgreementMgrConfig( + lastCfg, e.Config, e.CRS)) + } else { + return ErrInvalidRoundID + } + } else { + c := agreementMgrConfig{} + c.from(e.Round, e.Config, e.CRS) + c.SetRoundBeginHeight(e.BeginHeight) + mgr.configs = append(mgr.configs, c) + } + return nil + } + for _, e := range evts { + if err := apply(e); err != nil { + return err + } } - mgr.configs = append(mgr.configs, newAgreementMgrConfig( - mgr.configs[len(mgr.configs)-1], config, crs)) return nil } @@ -252,7 +281,7 @@ func (mgr *agreementMgr) processAgreementResult( } } else if result.Position.Newer(aID) { mgr.logger.Info("Fast syncing BA", "position", result.Position) - nodes, err := mgr.cache.GetNodeSet(result.Position.Round) + nIDs, err := mgr.cache.GetNotarySet(result.Position.Round) if err != nil { return err } @@ -261,10 +290,6 @@ func (mgr *agreementMgr) processAgreementResult( mgr.network.PullBlocks(common.Hashes{result.BlockHash}) mgr.logger.Debug("Calling Governance.CRS", "round", result.Position.Round) crs := utils.GetCRSWithPanic(mgr.gov, result.Position.Round, mgr.logger) - nIDs := nodes.GetSubSet( - int(utils.GetConfigWithPanic( - mgr.gov, result.Position.Round, mgr.logger).NotarySetSize), - types.NewNotarySetTarget(crs)) for key := range result.Votes { if err := mgr.baModule.processVote(&result.Votes[key]); err != nil { return err @@ -296,10 +321,7 @@ func (mgr *agreementMgr) runBA(initRound uint64) { currentRound uint64 nextRound = initRound curConfig = mgr.config(initRound) - setting = baRoundSetting{ - agr: mgr.baModule, - recv: mgr.baModule.data.recv.(*consensusBAReceiver), - } + setting = baRoundSetting{} tickDuration time.Duration ) @@ -353,12 +375,12 @@ Loop: break Loop default: } - setting.recv.isNotary = checkRound() + mgr.recv.isNotary = checkRound() // Run BA for this round. - setting.recv.roundValue.Store(currentRound) - setting.recv.changeNotaryHeight = curConfig.RoundEndHeight() - setting.recv.restartNotary <- types.Position{ - Round: setting.recv.round(), + mgr.recv.roundValue.Store(currentRound) + mgr.recv.changeNotaryHeightValue.Store(curConfig.RoundEndHeight()) + mgr.recv.restartNotary <- types.Position{ + Round: mgr.recv.round(), Height: math.MaxUint64, } mgr.voteFilter = utils.NewVoteFilter() @@ -373,8 +395,8 @@ Loop: func (mgr *agreementMgr) baRoutineForOneRound( setting *baRoundSetting) (err error) { - agr := setting.agr - recv := setting.recv + agr := mgr.baModule + recv := mgr.recv oldPos := agr.agreementID() restart := func(restartPos types.Position) (breakLoop bool, err error) { if !isStop(restartPos) { diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/blockchain.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/blockchain.go index 19a580b4f..c5a22b628 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/blockchain.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/blockchain.go @@ -41,7 +41,6 @@ var ( ErrNotFollowTipPosition = errors.New("not follow tip position") ErrDuplicatedPendingBlock = errors.New("duplicated pending block") ErrRetrySanityCheckLater = errors.New("retry sanity check later") - ErrRoundNotIncreasing = errors.New("round not increasing") ErrRoundNotSwitch = errors.New("round not switch") ErrIncorrectBlockRandomnessResult = errors.New( "incorrect block randomness result") @@ -142,19 +141,8 @@ type blockChain struct { } func newBlockChain(nID types.NodeID, dMoment time.Time, initBlock *types.Block, - initConfig blockChainConfig, app Application, vGetter tsigVerifierGetter, - signer *utils.Signer, logger common.Logger) *blockChain { - if initBlock != nil { - if initConfig.RoundID() != initBlock.Position.Round { - panic(fmt.Errorf("incompatible config/block %s %d", - initBlock, initConfig.RoundID())) - } - } else { - if initConfig.RoundID() != 0 { - panic(fmt.Errorf("genesis config should from round 0 %d", - initConfig.RoundID())) - } - } + app Application, vGetter tsigVerifierGetter, signer *utils.Signer, + logger common.Logger) *blockChain { return &blockChain{ ID: nID, lastConfirmed: initBlock, @@ -163,23 +151,58 @@ func newBlockChain(nID types.NodeID, dMoment time.Time, initBlock *types.Block, vGetter: vGetter, app: app, logger: logger, - configs: []blockChainConfig{initConfig}, dMoment: dMoment, pendingRandomnesses: make( map[types.Position]*types.BlockRandomnessResult), } } -func (bc *blockChain) appendConfig(round uint64, config *types.Config) error { - expectedRound := uint64(len(bc.configs)) - if bc.lastConfirmed != nil { - expectedRound += bc.lastConfirmed.Position.Round +func (bc *blockChain) notifyRoundEvents(evts []utils.RoundEventParam) error { + bc.lock.Lock() + defer bc.lock.Unlock() + apply := func(e utils.RoundEventParam) error { + if len(bc.configs) > 0 { + lastCfg := bc.configs[len(bc.configs)-1] + if e.BeginHeight != lastCfg.RoundEndHeight() { + return ErrInvalidBlockHeight + } + if lastCfg.RoundID() == e.Round { + bc.configs[len(bc.configs)-1].ExtendLength() + } else if lastCfg.RoundID()+1 == e.Round { + bc.configs = append(bc.configs, newBlockChainConfig( + lastCfg, e.Config)) + } else { + return ErrInvalidRoundID + } + } else { + c := blockChainConfig{} + c.fromConfig(e.Round, e.Config) + c.SetRoundBeginHeight(e.BeginHeight) + if bc.lastConfirmed == nil { + if c.RoundID() != 0 { + panic(fmt.Errorf("genesis config should from round 0 %d", + c.RoundID())) + } + } else { + if c.RoundID() != bc.lastConfirmed.Position.Round { + panic(fmt.Errorf("incompatible config/block %s %d", + bc.lastConfirmed, c.RoundID())) + } + if !c.Contains(bc.lastConfirmed.Position.Height) { + panic(fmt.Errorf( + "unmatched round-event with block %s %d %d %d", + bc.lastConfirmed, e.Round, e.Reset, e.BeginHeight)) + } + } + bc.configs = append(bc.configs, c) + } + return nil } - if round != expectedRound { - return ErrRoundNotIncreasing + for _, e := range evts { + if err := apply(e); err != nil { + return err + } } - bc.configs = append(bc.configs, newBlockChainConfig( - bc.configs[len(bc.configs)-1], config)) return nil } @@ -558,8 +581,11 @@ func (bc *blockChain) prepareBlock(position types.Position, } if tipConfig.IsLastBlock(tip) { if tip.Position.Round+1 != position.Round { - b, err = nil, ErrRoundNotSwitch - return + if !empty { + b, err = nil, ErrRoundNotSwitch + return + } + b.Position.Round = tip.Position.Round + 1 } } else { if tip.Position.Round != position.Round { diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/consensus.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/consensus.go index 4201cbcc2..8529e4031 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/consensus.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/consensus.go @@ -56,18 +56,22 @@ var ( // consensusBAReceiver implements agreementReceiver. type consensusBAReceiver struct { // TODO(mission): consensus would be replaced by blockChain and network. - consensus *Consensus - agreementModule *agreement - changeNotaryHeight uint64 - roundValue *atomic.Value - isNotary bool - restartNotary chan types.Position + consensus *Consensus + agreementModule *agreement + changeNotaryHeightValue *atomic.Value + roundValue *atomic.Value + isNotary bool + restartNotary chan types.Position } func (recv *consensusBAReceiver) round() uint64 { return recv.roundValue.Load().(uint64) } +func (recv *consensusBAReceiver) changeNotaryHeight() uint64 { + return recv.changeNotaryHeightValue.Load().(uint64) +} + func (recv *consensusBAReceiver) ProposeVote(vote *types.Vote) { if !recv.isNotary { return @@ -247,16 +251,17 @@ CleanChannelLoop: } } newPos := block.Position - if block.Position.Height+1 == recv.changeNotaryHeight { + if block.Position.Height+1 == recv.changeNotaryHeight() { newPos.Round++ recv.roundValue.Store(newPos.Round) } currentRound := recv.round() - if block.Position.Height > recv.changeNotaryHeight && + changeNotaryHeight := recv.changeNotaryHeight() + if block.Position.Height > changeNotaryHeight && block.Position.Round <= currentRound { panic(fmt.Errorf( "round not switch when confirmig: %s, %d, should switch at %d", - block, currentRound, recv.changeNotaryHeight)) + block, currentRound, changeNotaryHeight)) } recv.restartNotary <- newPos } @@ -396,11 +401,11 @@ type Consensus struct { bcModule *blockChain dMoment time.Time nodeSetCache *utils.NodeSetCache - roundForNewConfig uint64 lock sync.RWMutex ctx context.Context ctxCancel context.CancelFunc event *common.Event + roundEvent *utils.RoundEvent logger common.Logger resetRandomnessTicker chan struct{} resetDeliveryGuardTicker chan struct{} @@ -453,6 +458,7 @@ func NewConsensusForSimulation( func NewConsensusFromSyncer( initBlock *types.Block, initRoundBeginHeight uint64, + startWithEmpty bool, dMoment time.Time, app Application, gov Governance, @@ -495,6 +501,23 @@ func NewConsensusFromSyncer( continue } } + if startWithEmpty { + pos := initBlock.Position + pos.Height++ + block, err := con.bcModule.addEmptyBlock(pos) + if err != nil { + panic(err) + } + con.processBlockChan <- block + if pos.Round >= DKGDelayRound { + rand := &types.AgreementResult{ + BlockHash: block.Hash, + Position: block.Position, + IsEmptyBlock: true, + } + go con.prepareRandomnessResult(rand) + } + } return con, nil } @@ -522,8 +545,10 @@ func newConsensusForRound( } // Get configuration for bootstrap round. initRound := uint64(0) + initBlockHeight := uint64(0) if initBlock != nil { initRound = initBlock.Position.Round + initBlockHeight = initBlock.Position.Height } initConfig := utils.GetConfigWithPanic(gov, initRound, logger) initCRS := utils.GetCRSWithPanic(gov, initRound, logger) @@ -548,10 +573,7 @@ func newConsensusForRound( if usingNonBlocking { appModule = newNonBlocking(app, debugApp) } - bcConfig := blockChainConfig{} - bcConfig.fromConfig(initRound, initConfig) - bcConfig.SetRoundBeginHeight(initRoundBeginHeight) - bcModule := newBlockChain(ID, dMoment, initBlock, bcConfig, appModule, + bcModule := newBlockChain(ID, dMoment, initBlock, appModule, NewTSigVerifierCache(gov, 7), signer, logger) // Construct Consensus instance. con := &Consensus{ @@ -576,6 +598,10 @@ func newConsensusForRound( processBlockChan: make(chan *types.Block, 1024), } con.ctx, con.ctxCancel = context.WithCancel(context.Background()) + if con.roundEvent, err = utils.NewRoundEvent(con.ctx, gov, logger, initRound, + initRoundBeginHeight, initBlockHeight, ConfigRoundShift); err != nil { + panic(err) + } baConfig := agreementMgrConfig{} baConfig.from(initRound, initConfig, initCRS) baConfig.SetRoundBeginHeight(initRoundBeginHeight) @@ -595,26 +621,139 @@ func newConsensusForRound( // - the last finalized block func (con *Consensus) prepare( initRoundBeginHeight uint64, initBlock *types.Block) (err error) { + // Trigger the round validation method for the next round of the first + // round. // The block past from full node should be delivered already or known by // full node. We don't have to notify it. initRound := uint64(0) if initBlock != nil { initRound = initBlock.Position.Round } - // Setup blockChain module. - con.roundForNewConfig = initRound + 1 - initConfig := utils.GetConfigWithPanic(con.gov, initRound, con.logger) - initPlusOneCfg := utils.GetConfigWithPanic(con.gov, initRound+1, con.logger) - if err = con.bcModule.appendConfig(initRound+1, initPlusOneCfg); err != nil { - return - } if initRound == 0 { if DKGDelayRound == 0 { panic("not implemented yet") } } - // Register events. - con.initialRound(initRoundBeginHeight, initRound, initConfig) + // Register round event handler to update BA and BC modules. + con.roundEvent.Register(func(evts []utils.RoundEventParam) { + // Always updates newer configs to the later modules first in the flow. + if err := con.bcModule.notifyRoundEvents(evts); err != nil { + panic(err) + } + // The init config is provided to baModule when construction. + if evts[len(evts)-1].BeginHeight != initRoundBeginHeight { + if err := con.baMgr.notifyRoundEvents(evts); err != nil { + panic(err) + } + } + }) + // Register round event handler to propose new CRS. + con.roundEvent.Register(func(evts []utils.RoundEventParam) { + // We don't have to propose new CRS during DKG reset, the reset of DKG + // would be done by the DKG set in previous round. + e := evts[len(evts)-1] + if e.Reset != 0 || e.Round < DKGDelayRound { + return + } + if curDkgSet, err := con.nodeSetCache.GetDKGSet(e.Round); err != nil { + con.logger.Error("Error getting DKG set when proposing CRS", + "round", e.Round, + "error", err) + } else { + if _, exist := curDkgSet[con.ID]; !exist { + return + } + con.event.RegisterHeight(e.NextCRSProposingHeight(), func(uint64) { + con.logger.Debug( + "Calling Governance.CRS to check if already proposed", + "round", e.Round+1) + if (con.gov.CRS(e.Round+1) != common.Hash{}) { + con.logger.Debug("CRS already proposed", "round", e.Round+1) + return + } + con.runCRS(e.Round, e.CRS) + }) + } + }) + // Touch nodeSetCache for next round. + con.roundEvent.Register(func(evts []utils.RoundEventParam) { + e := evts[len(evts)-1] + if e.Reset != 0 { + return + } + con.event.RegisterHeight(e.NextTouchNodeSetCacheHeight(), func(uint64) { + if err := con.nodeSetCache.Touch(e.Round + 1); err != nil { + con.logger.Warn("Failed to update nodeSetCache", + "round", e.Round+1, + "error", err) + } + }) + }) + // checkCRS is a generator of checker to check if CRS for that round is + // ready or not. + checkCRS := func(round uint64) func() bool { + return func() bool { + nextCRS := con.gov.CRS(round) + if (nextCRS != common.Hash{}) { + return true + } + con.logger.Debug("CRS is not ready yet. Try again later...", + "nodeID", con.ID, + "round", round) + return false + } + } + // Trigger round validation method for next period. + con.roundEvent.Register(func(evts []utils.RoundEventParam) { + e := evts[len(evts)-1] + // Register a routine to trigger round events. + con.event.RegisterHeight(e.NextRoundValidationHeight(), func( + blockHeight uint64) { + con.roundEvent.ValidateNextRound(blockHeight) + }) + // Register a routine to register next DKG. + con.event.RegisterHeight(e.NextDKGRegisterHeight(), func(uint64) { + nextRound := e.Round + 1 + if nextRound < DKGDelayRound { + con.logger.Info("Skip runDKG for round", "round", nextRound) + return + } + // Normally, gov.CRS would return non-nil. Use this for in case of + // unexpected network fluctuation and ensure the robustness. + if !checkWithCancel( + con.ctx, 500*time.Millisecond, checkCRS(nextRound)) { + con.logger.Debug("unable to prepare CRS for DKG set", + "round", nextRound) + return + } + nextDkgSet, err := con.nodeSetCache.GetDKGSet(nextRound) + if err != nil { + con.logger.Error("Error getting DKG set for next round", + "round", nextRound, + "error", err) + return + } + if _, exist := nextDkgSet[con.ID]; !exist { + con.logger.Info("Not selected as DKG set", "round", nextRound) + return + } + con.logger.Info("Selected as DKG set", "round", nextRound) + nextConfig := utils.GetConfigWithPanic(con.gov, nextRound, + con.logger) + con.cfgModule.registerDKG(nextRound, utils.GetDKGThreshold( + nextConfig)) + con.event.RegisterHeight(e.NextDKGPreparationHeight(), + func(uint64) { + func() { + con.dkgReady.L.Lock() + defer con.dkgReady.L.Unlock() + con.dkgRunning = 0 + }() + con.runDKG(nextRound, nextConfig) + }) + }) + }) + con.roundEvent.TriggerInitEvent() return } @@ -686,27 +825,9 @@ func (con *Consensus) runDKG(round uint64, config *types.Config) { }() } -func (con *Consensus) runCRS(round uint64) { - for { - con.logger.Debug("Calling Governance.CRS to check if already proposed", - "round", round+1) - if (con.gov.CRS(round+1) != common.Hash{}) { - con.logger.Debug("CRS already proposed", "round", round+1) - return - } - con.logger.Debug("Calling Governance.IsDKGFinal to check if ready to run CRS", - "round", round) - if con.cfgModule.isDKGFinal(round) { - break - } - con.logger.Debug("DKG is not ready for running CRS. Retry later...", - "round", round) - time.Sleep(500 * time.Millisecond) - } +func (con *Consensus) runCRS(round uint64, hash common.Hash) { // Start running next round CRS. - con.logger.Debug("Calling Governance.CRS", "round", round) - psig, err := con.cfgModule.preparePartialSignature( - round, utils.GetCRSWithPanic(con.gov, round, con.logger)) + psig, err := con.cfgModule.preparePartialSignature(round, hash) if err != nil { con.logger.Error("Failed to prepare partial signature", "error", err) } else if err = con.signer.SignDKGPartialSignature(psig); err != nil { @@ -733,136 +854,16 @@ func (con *Consensus) runCRS(round uint64) { } } -func (con *Consensus) initialRound( - startHeight uint64, round uint64, config *types.Config) { - select { - case <-con.ctx.Done(): - return - default: - } - if round >= DKGDelayRound { - curDkgSet, err := con.nodeSetCache.GetDKGSet(round) - if err != nil { - con.logger.Error("Error getting DKG set", "round", round, "error", err) - curDkgSet = make(map[types.NodeID]struct{}) - } - // Initiate CRS routine. - if _, exist := curDkgSet[con.ID]; exist { - con.event.RegisterHeight( - startHeight+config.RoundLength/2, - func(uint64) { - go func() { - con.runCRS(round) - }() - }) - } - } - // checkCRS is a generator of checker to check if CRS for that round is - // ready or not. - checkCRS := func(round uint64) func() bool { - return func() bool { - nextCRS := con.gov.CRS(round) - if (nextCRS != common.Hash{}) { - return true - } - con.logger.Debug("CRS is not ready yet. Try again later...", - "nodeID", con.ID, - "round", round) - return false - } - } - // Initiate BA modules. - con.event.RegisterHeight(startHeight+config.RoundLength/2, func(uint64) { - go func(nextRound uint64) { - if !checkWithCancel( - con.ctx, 500*time.Millisecond, checkCRS(nextRound)) { - con.logger.Debug("unable to prepare CRS for baMgr", - "round", nextRound) - return - } - // Notify BA for new round. - nextConfig := utils.GetConfigWithPanic( - con.gov, nextRound, con.logger) - nextCRS := utils.GetCRSWithPanic( - con.gov, nextRound, con.logger) - con.logger.Info("appendConfig for baMgr", "round", nextRound) - if err := con.baMgr.appendConfig( - nextRound, nextConfig, nextCRS); err != nil { - panic(err) - } - }(round + 1) - }) - // Initiate DKG for this round. - con.event.RegisterHeight(startHeight+config.RoundLength/2, func(uint64) { - go func(nextRound uint64) { - if nextRound < DKGDelayRound { - con.logger.Info("Skip runDKG for round", "round", nextRound) - return - } - // Normally, gov.CRS would return non-nil. Use this for in case of - // unexpected network fluctuation and ensure the robustness. - if !checkWithCancel( - con.ctx, 500*time.Millisecond, checkCRS(nextRound)) { - con.logger.Debug("unable to prepare CRS for DKG set", - "round", nextRound) - return - } - nextDkgSet, err := con.nodeSetCache.GetDKGSet(nextRound) - if err != nil { - con.logger.Error("Error getting DKG set", - "round", nextRound, - "error", err) - return - } - if _, exist := nextDkgSet[con.ID]; !exist { - return - } - con.logger.Info("Selected as DKG set", "round", nextRound) - con.cfgModule.registerDKG(nextRound, utils.GetDKGThreshold(config)) - con.event.RegisterHeight(startHeight+config.RoundLength*2/3, - func(uint64) { - func() { - con.dkgReady.L.Lock() - defer con.dkgReady.L.Unlock() - con.dkgRunning = 0 - }() - nextConfig := utils.GetConfigWithPanic( - con.gov, nextRound, con.logger) - con.runDKG(nextRound, nextConfig) - }) - }(round + 1) - }) - // Prepare blockChain module for next round and next "initialRound" routine. - con.event.RegisterHeight(startHeight+config.RoundLength, func(uint64) { - // Change round. - // Get configuration for next round. - nextRound := round + 1 - nextConfig := utils.GetConfigWithPanic(con.gov, nextRound, con.logger) - con.initialRound( - startHeight+config.RoundLength, nextRound, nextConfig) - }) - // Touch nodeSetCache for next round. - con.event.RegisterHeight(startHeight+config.RoundLength*9/10, func(uint64) { - go func() { - // TODO(jimmy): check DKGResetCount and do not touch if nextRound is reset. - if err := con.nodeSetCache.Touch(round + 1); err != nil { - con.logger.Warn("Failed to update nodeSetCache", - "round", round+1, "error", err) - } - if _, _, err := con.bcModule.vGetter.UpdateAndGet(round + 1); err != nil { - con.logger.Warn("Failed to update tsigVerifierCache", - "round", round+1, "error", err) - } - }() - }) -} - // Stop the Consensus core. func (con *Consensus) Stop() { con.ctxCancel() con.baMgr.stop() con.event.Reset() con.waitGroup.Wait() + if nbApp, ok := con.app.(*nonBlocking); ok { + fmt.Println("Stopping nonBlocking App") + nbApp.wait() + } } func (con *Consensus) deliverNetworkMsg() { @@ -1014,62 +1015,64 @@ func (con *Consensus) ProcessAgreementResult( con.logger.Debug("Rebroadcast AgreementResult", "result", rand) con.network.BroadcastAgreementResult(rand) + go con.prepareRandomnessResult(rand) + return nil +} - go func() { - dkgSet, err := con.nodeSetCache.GetDKGSet(rand.Position.Round) - if err != nil { - con.logger.Error("Failed to get dkg set", - "round", rand.Position.Round, "error", err) - return - } - if _, exist := dkgSet[con.ID]; !exist { - return - } - psig, err := con.cfgModule.preparePartialSignature(rand.Position.Round, rand.BlockHash) - if err != nil { - con.logger.Error("Failed to prepare psig", - "round", rand.Position.Round, - "hash", rand.BlockHash.String()[:6], - "error", err) - return - } - if err = con.signer.SignDKGPartialSignature(psig); err != nil { - con.logger.Error("Failed to sign psig", +func (con *Consensus) prepareRandomnessResult(rand *types.AgreementResult) { + dkgSet, err := con.nodeSetCache.GetDKGSet(rand.Position.Round) + if err != nil { + con.logger.Error("Failed to get dkg set", + "round", rand.Position.Round, "error", err) + return + } + if _, exist := dkgSet[con.ID]; !exist { + return + } + con.logger.Debug("PrepareRandomness", "round", rand.Position.Round, "hash", rand.BlockHash) + psig, err := con.cfgModule.preparePartialSignature(rand.Position.Round, rand.BlockHash) + if err != nil { + con.logger.Error("Failed to prepare psig", + "round", rand.Position.Round, + "hash", rand.BlockHash.String()[:6], + "error", err) + return + } + if err = con.signer.SignDKGPartialSignature(psig); err != nil { + con.logger.Error("Failed to sign psig", + "hash", rand.BlockHash.String()[:6], + "error", err) + return + } + if err = con.cfgModule.processPartialSignature(psig); err != nil { + con.logger.Error("Failed process psig", + "hash", rand.BlockHash.String()[:6], + "error", err) + return + } + con.logger.Debug("Calling Network.BroadcastDKGPartialSignature", + "proposer", psig.ProposerID, + "round", psig.Round, + "hash", psig.Hash.String()[:6]) + con.network.BroadcastDKGPartialSignature(psig) + tsig, err := con.cfgModule.runTSig(rand.Position.Round, rand.BlockHash) + if err != nil { + if err != ErrTSigAlreadyRunning { + con.logger.Error("Failed to run TSIG", + "position", rand.Position, "hash", rand.BlockHash.String()[:6], "error", err) - return } - if err = con.cfgModule.processPartialSignature(psig); err != nil { - con.logger.Error("Failed process psig", - "hash", rand.BlockHash.String()[:6], - "error", err) - return - } - con.logger.Debug("Calling Network.BroadcastDKGPartialSignature", - "proposer", psig.ProposerID, - "round", psig.Round, - "hash", psig.Hash.String()[:6]) - con.network.BroadcastDKGPartialSignature(psig) - tsig, err := con.cfgModule.runTSig(rand.Position.Round, rand.BlockHash) - if err != nil { - if err != ErrTSigAlreadyRunning { - con.logger.Error("Failed to run TSIG", - "position", rand.Position, - "hash", rand.BlockHash.String()[:6], - "error", err) - } - return - } - result := &types.BlockRandomnessResult{ - BlockHash: rand.BlockHash, - Position: rand.Position, - Randomness: tsig.Signature, - } - // ProcessBlockRandomnessResult is not thread-safe so we put the result in - // the message channnel to be processed in the main thread. - con.msgChan <- result - }() - return nil + return + } + result := &types.BlockRandomnessResult{ + BlockHash: rand.BlockHash, + Position: rand.Position, + Randomness: tsig.Signature, + } + // ProcessBlockRandomnessResult is not thread-safe so we put the result in + // the message channnel to be processed in the main thread. + con.msgChan <- result } // ProcessBlockRandomnessResult processes the randomness result. @@ -1094,14 +1097,6 @@ func (con *Consensus) ProcessBlockRandomnessResult( // preProcessBlock performs Byzantine Agreement on the block. func (con *Consensus) preProcessBlock(b *types.Block) (err error) { - var exist bool - exist, err = con.nodeSetCache.Exists(b.Position.Round, b.ProposerID) - if err != nil { - return - } - if !exist { - return ErrProposerNotInNodeSet - } err = con.baMgr.processBlock(b) if err == nil && con.debugApp != nil { con.debugApp.BlockReceived(b.Hash) @@ -1137,7 +1132,10 @@ func (con *Consensus) deliveryGuard() { defer con.waitGroup.Done() time.Sleep(con.dMoment.Sub(time.Now())) // Node takes time to start. - time.Sleep(60 * time.Second) + select { + case <-con.ctx.Done(): + case <-time.After(60 * time.Second): + } for { select { case <-con.ctx.Done(): @@ -1176,24 +1174,6 @@ func (con *Consensus) deliverBlock(b *types.Block) { con.cfgModule.untouchTSigHash(b.Hash) con.logger.Debug("Calling Application.BlockDelivered", "block", b) con.app.BlockDelivered(b.Hash, b.Position, b.Finalization.Clone()) - if b.Position.Round == con.roundForNewConfig { - // Get configuration for the round next to next round. Configuration - // for that round should be ready at this moment and is required for - // blockChain module. This logic is related to: - // - roundShift - // - notifyGenesisRound - futureRound := con.roundForNewConfig + 1 - futureConfig := utils.GetConfigWithPanic(con.gov, futureRound, con.logger) - con.logger.Debug("Append Config", "round", futureRound) - if err := con.bcModule.appendConfig( - futureRound, futureConfig); err != nil { - con.logger.Debug("Unable to append config", - "round", futureRound, - "error", err) - panic(err) - } - con.roundForNewConfig++ - } if con.debugApp != nil { con.debugApp.BlockReady(b.Hash) } diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/interfaces.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/interfaces.go index 45a1fc7d5..ddd6c3bb9 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/interfaces.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/interfaces.go @@ -101,8 +101,12 @@ type Governance interface { // Return the genesis configuration if round == 0. Configuration(round uint64) *types.Config - // CRS returns the CRS for a given round. - // Return the genesis CRS if round == 0. + // CRS returns the CRS for a given round. Return the genesis CRS if + // round == 0. + // + // The CRS returned is the proposed or latest reseted one, it would be + // changed later if corresponding DKG set failed to generate group public + // key. CRS(round uint64) common.Hash // Propose a CRS of round. @@ -162,3 +166,12 @@ type Ticker interface { // Retart the ticker and clear all internal data. Restart() } + +// Recovery interface for interacting with recovery information. +type Recovery interface { + // ProposeSkipBlock proposes a skip block. + ProposeSkipBlock(height uint64) error + + // Votes gets the number of votes of given height. + Votes(height uint64) (uint64, error) +} diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/syncer/consensus.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/syncer/consensus.go index 25911ce5f..f2f8f9e66 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/syncer/consensus.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/syncer/consensus.go @@ -69,12 +69,14 @@ type Consensus struct { configs []*types.Config roundBeginHeights []uint64 agreementRoundCut uint64 + heightEvt *common.Event + roundEvt *utils.RoundEvent // lock for accessing all fields. lock sync.RWMutex duringBuffering bool latestCRSRound uint64 - moduleWaitGroup sync.WaitGroup + waitGroup sync.WaitGroup agreementWaitGroup sync.WaitGroup pullChan chan common.Hash receiveChan chan *types.Block @@ -82,6 +84,7 @@ type Consensus struct { ctxCancel context.CancelFunc syncedLastBlock *types.Block syncedConsensus *core.Consensus + syncedSkipNext bool dummyCancel context.CancelFunc dummyFinished <-chan struct{} dummyMsgBuffer []interface{} @@ -115,6 +118,7 @@ func NewConsensus( receiveChan: make(chan *types.Block, 1000), pullChan: make(chan common.Hash, 1000), randomnessResults: make(map[common.Hash]*types.BlockRandomnessResult), + heightEvt: common.NewEvent(), } con.ctx, con.ctxCancel = context.WithCancel(context.Background()) _, con.initChainTipHeight = db.GetCompactionChainTipInfo() @@ -142,9 +146,73 @@ func (con *Consensus) assureBuffering() { return } con.duringBuffering = true + // Get latest block to prepare utils.RoundEvent. + var ( + err error + blockHash, height = con.db.GetCompactionChainTipInfo() + ) + if height == 0 { + con.roundEvt, err = utils.NewRoundEvent(con.ctx, con.gov, con.logger, + uint64(0), uint64(0), uint64(0), core.ConfigRoundShift) + } else { + var b types.Block + if b, err = con.db.GetBlock(blockHash); err == nil { + beginHeight := con.roundBeginHeights[b.Position.Round] + con.roundEvt, err = utils.NewRoundEvent(con.ctx, con.gov, + con.logger, b.Position.Round, beginHeight, beginHeight, + core.ConfigRoundShift) + } + } + if err != nil { + panic(err) + } + // Make sure con.roundEvt stopped before stopping con.agreementModule. + con.waitGroup.Add(1) + // Register a round event handler to notify CRS to agreementModule. + con.roundEvt.Register(func(evts []utils.RoundEventParam) { + con.waitGroup.Add(1) + go func() { + defer con.waitGroup.Done() + for _, e := range evts { + select { + case <-con.ctx.Done(): + return + default: + } + for func() bool { + select { + case <-con.ctx.Done(): + return false + case con.agreementModule.inputChan <- e.Round: + return false + case <-time.After(500 * time.Millisecond): + con.logger.Warn( + "agreement input channel is full when putting CRS", + "round", e.Round, + ) + return true + } + }() { + } + } + }() + }) + // Register a round event handler to validate next round. + con.roundEvt.Register(func(evts []utils.RoundEventParam) { + e := evts[len(evts)-1] + con.heightEvt.RegisterHeight(e.NextRoundValidationHeight(), func( + blockHeight uint64) { + select { + case <-con.ctx.Done(): + return + default: + } + con.roundEvt.ValidateNextRound(blockHeight) + }) + }) + con.roundEvt.TriggerInitEvent() con.startAgreement() con.startNetwork() - con.startCRSMonitor() } func (con *Consensus) checkIfSynced(blocks []*types.Block) (synced bool) { @@ -180,6 +248,29 @@ func (con *Consensus) buildAllEmptyBlocks() { } } +// ForceSync forces syncer to become synced. +func (con *Consensus) ForceSync(skip bool) { + if con.syncedLastBlock != nil { + return + } + hash, _ := con.db.GetCompactionChainTipInfo() + var block types.Block + block, err := con.db.GetBlock(hash) + if err != nil { + panic(err) + } + con.logger.Info("Force Sync", "block", &block) + con.setupConfigsUntilRound(block.Position.Round + core.ConfigRoundShift - 1) + con.syncedLastBlock = &block + con.stopBuffering() + con.dummyCancel, con.dummyFinished = utils.LaunchDummyReceiver( + context.Background(), con.network.ReceiveChan(), + func(msg interface{}) { + con.dummyMsgBuffer = append(con.dummyMsgBuffer, msg) + }) + con.syncedSkipNext = skip +} + // SyncBlocks syncs blocks from compaction chain, latest is true if the caller // regards the blocks are the latest ones. Notice that latest can be true for // many times. @@ -241,6 +332,7 @@ func (con *Consensus) SyncBlocks( b.Hash, b.Finalization.Height); err != nil { return } + go con.heightEvt.NotifyHeight(b.Finalization.Height) } if latest { con.assureBuffering() @@ -279,6 +371,7 @@ func (con *Consensus) GetSyncedConsensus() (*core.Consensus, error) { con.syncedConsensus, err = core.NewConsensusFromSyncer( con.syncedLastBlock, con.roundBeginHeights[con.syncedLastBlock.Position.Round], + con.syncedSkipNext, con.dMoment, con.app, con.gov, @@ -321,7 +414,10 @@ func (con *Consensus) stopBuffering() { return } con.logger.Trace("stop syncer modules") - con.moduleWaitGroup.Wait() + con.roundEvt.Stop() + con.waitGroup.Done() + // Wait for all routines depends on con.agreementModule stopped. + con.waitGroup.Wait() // Since there is no one waiting for the receive channel of fullnode, we // need to launch a dummy receiver right away. con.dummyCancel, con.dummyFinished = utils.LaunchDummyReceiver( @@ -467,9 +563,9 @@ func (con *Consensus) cacheRandomnessResult(r *types.BlockRandomnessResult) { // startNetwork starts network for receiving blocks and agreement results. func (con *Consensus) startNetwork() { - con.moduleWaitGroup.Add(1) + con.waitGroup.Add(1) go func() { - defer con.moduleWaitGroup.Done() + defer con.waitGroup.Done() loop: for { select { @@ -497,62 +593,6 @@ func (con *Consensus) startNetwork() { }() } -// startCRSMonitor is the dummiest way to verify if the CRS for one round -// is ready or not. -func (con *Consensus) startCRSMonitor() { - var lastNotifiedRound uint64 - // Notify all agreements for new CRS. - notifyNewCRS := func(round uint64) { - con.setupConfigsUntilRound(round) - if round == lastNotifiedRound { - return - } - con.logger.Debug("CRS is ready", "round", round) - lastNotifiedRound = round - func() { - con.lock.Lock() - defer con.lock.Unlock() - con.latestCRSRound = round - }() - for func() bool { - select { - case <-con.ctx.Done(): - return false - case con.agreementModule.inputChan <- round: - return false - case <-time.After(500 * time.Millisecond): - con.logger.Debug( - "agreement input channel is full when putting CRS", - "round", round, - ) - return true - } - }() { - } - } - con.moduleWaitGroup.Add(1) - go func() { - defer con.moduleWaitGroup.Done() - for { - select { - case <-con.ctx.Done(): - return - case <-time.After(500 * time.Millisecond): - } - // Notify agreement modules for the latest round that CRS is - // available if the round is not notified yet. - checked := lastNotifiedRound + 1 - for (con.gov.CRS(checked) != common.Hash{}) { - checked++ - } - checked-- - if checked > lastNotifiedRound { - notifyNewCRS(checked) - } - } - }() -} - func (con *Consensus) stopAgreement() { if con.agreementModule.inputChan != nil { close(con.agreementModule.inputChan) diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/syncer/watch-cat.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/syncer/watch-cat.go new file mode 100644 index 000000000..d08bff9e9 --- /dev/null +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/syncer/watch-cat.go @@ -0,0 +1,148 @@ +// Copyright 2019 The dexon-consensus Authors +// This file is part of the dexon-consensus-core library. +// +// The dexon-consensus-core 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 dexon-consensus-core 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 dexon-consensus-core library. If not, see +// <http://www.gnu.org/licenses/>. + +package syncer + +import ( + "context" + "time" + + "github.com/dexon-foundation/dexon-consensus/common" + "github.com/dexon-foundation/dexon-consensus/core" + "github.com/dexon-foundation/dexon-consensus/core/types" + "github.com/dexon-foundation/dexon-consensus/core/utils" +) + +type configReader interface { + Configuration(round uint64) *types.Config +} + +// WatchCat is reponsible for signaling if syncer object should be terminated. +type WatchCat struct { + recovery core.Recovery + timeout time.Duration + configReader configReader + feed chan types.Position + polling time.Duration + ctx context.Context + cancel context.CancelFunc + logger common.Logger +} + +// NewWatchCat creats a new WatchCat 🐱 object. +func NewWatchCat( + recovery core.Recovery, + configReader configReader, + polling time.Duration, + timeout time.Duration, + logger common.Logger) *WatchCat { + wc := &WatchCat{ + recovery: recovery, + timeout: timeout, + configReader: configReader, + feed: make(chan types.Position), + polling: polling, + logger: logger, + } + return wc +} + +// Feed the WatchCat so it won't produce the termination signal. +func (wc *WatchCat) Feed(position types.Position) { + wc.feed <- position +} + +// Start the WatchCat. +func (wc *WatchCat) Start() { + wc.Stop() + wc.ctx, wc.cancel = context.WithCancel(context.Background()) + go func() { + var lastPos types.Position + MonitorLoop: + for { + select { + case <-wc.ctx.Done(): + return + default: + } + select { + case <-wc.ctx.Done(): + return + case pos := <-wc.feed: + if !pos.Newer(lastPos) { + wc.logger.Warn("Feed with older height", + "pos", pos, "lastPos", lastPos) + continue + } + lastPos = pos + case <-time.After(wc.timeout): + break MonitorLoop + } + } + go func() { + for { + select { + case <-wc.ctx.Done(): + return + case <-wc.feed: + } + } + }() + defer wc.cancel() + proposed := false + threshold := uint64( + utils.GetConfigWithPanic(wc.configReader, lastPos.Round, wc.logger). + NotarySetSize / 2) + wc.logger.Info("Threshold for recovery", "votes", threshold) + ResetLoop: + for { + if !proposed { + wc.logger.Info("Calling Recovery.ProposeSkipBlock", + "height", lastPos.Height) + if err := wc.recovery.ProposeSkipBlock(lastPos.Height); err != nil { + wc.logger.Warn("Failed to proposeSkipBlock", "height", lastPos.Height, "error", err) + } else { + proposed = true + } + } + votes, err := wc.recovery.Votes(lastPos.Height) + if err != nil { + wc.logger.Error("Failed to get recovery votes", "height", lastPos.Height, "error", err) + } else if votes > threshold { + wc.logger.Info("Threshold for recovery reached!") + break ResetLoop + } + select { + case <-wc.ctx.Done(): + return + case <-time.After(wc.polling): + } + } + }() +} + +// Stop the WatchCat. +func (wc *WatchCat) Stop() { + if wc.cancel != nil { + wc.cancel() + } +} + +// Meow return a closed channel if syncer should be terminated. +func (wc *WatchCat) Meow() <-chan struct{} { + return wc.ctx.Done() +} diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/utils.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/utils.go index f7dee757f..5742d113a 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/utils.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/utils.go @@ -236,14 +236,14 @@ func checkWithCancel(parentCtx context.Context, interval time.Duration, defer cancel() Loop: for { + if ret = checker(); ret { + return + } select { case <-ctx.Done(): break Loop case <-time.After(interval): } - if ret = checker(); ret { - return - } } return } diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/utils/round-event.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/utils/round-event.go index bab1d32d2..1ce877dda 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/utils/round-event.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/utils/round-event.go @@ -28,15 +28,15 @@ import ( typesDKG "github.com/dexon-foundation/dexon-consensus/core/types/dkg" ) -// ErrUnmatchedBlockHeightWithGov is for invalid parameters for NewRoundEvent. -type ErrUnmatchedBlockHeightWithGov struct { +// ErrUnmatchedBlockHeightWithConfig is for invalid parameters for NewRoundEvent. +type ErrUnmatchedBlockHeightWithConfig struct { round uint64 reset uint64 blockHeight uint64 } -func (e ErrUnmatchedBlockHeightWithGov) Error() string { - return fmt.Sprintf("unsynced block height and gov: round:%d reset:%d h:%d", +func (e ErrUnmatchedBlockHeightWithConfig) Error() string { + return fmt.Sprintf("unsynced block height and cfg: round:%d reset:%d h:%d", e.round, e.reset, e.blockHeight) } @@ -56,11 +56,43 @@ type RoundEventParam struct { CRS common.Hash } -// NextRoundCheckpoint returns the height to check if the next round is ready. -func (e RoundEventParam) NextRoundCheckpoint() uint64 { +// NextRoundValidationHeight returns the height to check if the next round is +// ready. +func (e RoundEventParam) NextRoundValidationHeight() uint64 { + return e.BeginHeight + e.Config.RoundLength*9/10 +} + +// NextCRSProposingHeight returns the height to propose CRS for next round. +func (e RoundEventParam) NextCRSProposingHeight() uint64 { + return e.BeginHeight + e.Config.RoundLength/2 +} + +// NextDKGPreparationHeight returns the height to prepare DKG set for next +// round. +func (e RoundEventParam) NextDKGPreparationHeight() uint64 { + return e.BeginHeight + e.Config.RoundLength*2/3 +} + +// NextRoundHeight returns the height of the beginning of next round. +func (e RoundEventParam) NextRoundHeight() uint64 { + return e.BeginHeight + e.Config.RoundLength +} + +// NextTouchNodeSetCacheHeight returns the height to touch the node set cache. +func (e RoundEventParam) NextTouchNodeSetCacheHeight() uint64 { + return e.BeginHeight + e.Config.RoundLength*9/10 +} + +// NextDKGResetHeight returns the height to reset DKG for next period. +func (e RoundEventParam) NextDKGResetHeight() uint64 { return e.BeginHeight + e.Config.RoundLength*8/10 } +// NextDKGRegisterHeight returns the height to register DKG. +func (e RoundEventParam) NextDKGRegisterHeight() uint64 { + return e.BeginHeight + e.Config.RoundLength/2 +} + // roundEventFn defines the fingerprint of handlers of round events. type roundEventFn func([]RoundEventParam) @@ -131,7 +163,7 @@ func NewRoundEvent(parentCtx context.Context, gov governanceAccessor, e.config.ExtendLength() } if !e.config.Contains(initBlockHeight) { - return nil, ErrUnmatchedBlockHeightWithGov{ + return nil, ErrUnmatchedBlockHeightWithConfig{ round: initRound, reset: resetCount, blockHeight: initBlockHeight, @@ -149,6 +181,22 @@ func (e *RoundEvent) Register(h roundEventFn) { e.handlers = append(e.handlers, h) } +// TriggerInitEvent triggers event from the initial setting. +func (e *RoundEvent) TriggerInitEvent() { + e.lock.Lock() + defer e.lock.Unlock() + events := []RoundEventParam{RoundEventParam{ + Round: e.lastTriggeredRound, + Reset: e.lastTriggeredResetCount, + BeginHeight: e.config.LastPeriodBeginHeight(), + CRS: GetCRSWithPanic(e.gov, e.lastTriggeredRound, e.logger), + Config: GetConfigWithPanic(e.gov, e.lastTriggeredRound, e.logger), + }} + for _, h := range e.handlers { + h(events) + } +} + // ValidateNextRound validate if the DKG set for next round is ready to go or // failed to setup, all registered handlers would be called once some decision // is made on chain. @@ -225,14 +273,6 @@ func (e *RoundEvent) check(blockHeight, startRound uint64, lastDKGCheck bool) ( "crs", param.CRS.String()[:6], ) }() - // Make sure current last config covers the blockHeight. - if !e.config.Contains(blockHeight) { - panic(ErrUnmatchedBlockHeightWithGov{ - round: e.lastTriggeredRound, - reset: e.lastTriggeredResetCount, - blockHeight: blockHeight, - }) - } nextRound := e.lastTriggeredRound + 1 if nextRound >= startRound+e.roundShift { // Avoid access configuration newer than last confirmed one over diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/utils/utils.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/utils/utils.go index b8bd95ec4..14687d6ac 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/utils/utils.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/utils/utils.go @@ -138,3 +138,9 @@ func LaunchDummyReceiver( func GetDKGThreshold(config *types.Config) int { return int(config.DKGSetSize/3) + 1 } + +// GetNextRoundValidationHeight returns the block height to check if the next +// round is ready. +func GetNextRoundValidationHeight(begin, length uint64) uint64 { + return begin + length*9/10 +} diff --git a/vendor/github.com/onrik/ethrpc/LICENSE b/vendor/github.com/onrik/ethrpc/LICENSE new file mode 100644 index 000000000..c8162bd91 --- /dev/null +++ b/vendor/github.com/onrik/ethrpc/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Andrey + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/onrik/ethrpc/README.md b/vendor/github.com/onrik/ethrpc/README.md new file mode 100644 index 000000000..c273e8931 --- /dev/null +++ b/vendor/github.com/onrik/ethrpc/README.md @@ -0,0 +1,103 @@ +# Ethrpc +[![Build Status](https://travis-ci.org/onrik/ethrpc.svg?branch=master)](https://travis-ci.org/onrik/ethrpc) +[![Coverage Status](https://coveralls.io/repos/github/onrik/ethrpc/badge.svg?branch=master)](https://coveralls.io/github/onrik/ethrpc?branch=master) +[![Go Report Card](https://goreportcard.com/badge/github.com/onrik/ethrpc)](https://goreportcard.com/report/github.com/onrik/ethrpc) +[![GoDoc](https://godoc.org/github.com/onrik/ethrpc?status.svg)](https://godoc.org/github.com/onrik/ethrpc) +[![Donate with Ethereum](https://en.cryptobadges.io/badge/micro/0xf4144308d6D67A1F00a61A596c0eB7B08411344a)](https://en.cryptobadges.io/donate/0xf4144308d6D67A1F00a61A596c0eB7B08411344a) + +Golang client for ethereum [JSON RPC API](https://github.com/ethereum/wiki/wiki/JSON-RPC). + +- [x] web3_clientVersion +- [x] web3_sha3 +- [x] net_version +- [x] net_peerCount +- [x] net_listening +- [x] eth_protocolVersion +- [x] eth_syncing +- [x] eth_coinbase +- [x] eth_mining +- [x] eth_hashrate +- [x] eth_gasPrice +- [x] eth_accounts +- [x] eth_blockNumber +- [x] eth_getBalance +- [x] eth_getStorageAt +- [x] eth_getTransactionCount +- [x] eth_getBlockTransactionCountByHash +- [x] eth_getBlockTransactionCountByNumber +- [x] eth_getUncleCountByBlockHash +- [x] eth_getUncleCountByBlockNumber +- [x] eth_getCode +- [x] eth_sign +- [x] eth_sendTransaction +- [x] eth_sendRawTransaction +- [x] eth_call +- [x] eth_estimateGas +- [x] eth_getBlockByHash +- [x] eth_getBlockByNumber +- [x] eth_getTransactionByHash +- [x] eth_getTransactionByBlockHashAndIndex +- [x] eth_getTransactionByBlockNumberAndIndex +- [x] eth_getTransactionReceipt +- [ ] eth_getUncleByBlockHashAndIndex +- [ ] eth_getUncleByBlockNumberAndIndex +- [x] eth_getCompilers +- [ ] eth_compileLLL +- [ ] eth_compileSolidity +- [ ] eth_compileSerpent +- [x] eth_newFilter +- [x] eth_newBlockFilter +- [x] eth_newPendingTransactionFilter +- [x] eth_uninstallFilter +- [x] eth_getFilterChanges +- [x] eth_getFilterLogs +- [x] eth_getLogs +- [ ] eth_getWork +- [ ] eth_submitWork +- [ ] eth_submitHashrate +- [ ] shh_post +- [ ] shh_version +- [ ] shh_newIdentity +- [ ] shh_hasIdentity +- [ ] shh_newGroup +- [ ] shh_addToGroup +- [ ] shh_newFilter +- [ ] shh_uninstallFilter +- [ ] shh_getFilterChanges +- [ ] shh_getMessages + +##### Usage: +```go +package main + +import ( + "fmt" + "log" + + "github.com/onrik/ethrpc" +) + +func main() { + client := ethrpc.New("http://127.0.0.1:8545") + + version, err := client.Web3ClientVersion() + if err != nil { + log.Fatal(err) + } + fmt.Println(version) + + // Send 1 eth + txid, err := client.EthSendTransaction(ethrpc.T{ + From: "0x6247cf0412c6462da2a51d05139e2a3c6c630f0a", + To: "0xcfa202c4268749fbb5136f2b68f7402984ed444b", + Value: ethrpc.Eth1(), + }) + if err != nil { + log.Fatal(err) + } + fmt.Println(txid) +} +``` + +[![Donate with Ethereum](https://en.cryptobadges.io/badge/big/0xf4144308d6D67A1F00a61A596c0eB7B08411344a?showBalance=true)](https://en.cryptobadges.io/donate/0xf4144308d6D67A1F00a61A596c0eB7B08411344a) + diff --git a/vendor/github.com/onrik/ethrpc/ethrpc.go b/vendor/github.com/onrik/ethrpc/ethrpc.go new file mode 100644 index 000000000..5118b425d --- /dev/null +++ b/vendor/github.com/onrik/ethrpc/ethrpc.go @@ -0,0 +1,514 @@ +package ethrpc + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "log" + "math/big" + "net/http" + "os" +) + +// EthError - ethereum error +type EthError struct { + Code int `json:"code"` + Message string `json:"message"` +} + +func (err EthError) Error() string { + return fmt.Sprintf("Error %d (%s)", err.Code, err.Message) +} + +type ethResponse struct { + ID int `json:"id"` + JSONRPC string `json:"jsonrpc"` + Result json.RawMessage `json:"result"` + Error *EthError `json:"error"` +} + +type ethRequest struct { + ID int `json:"id"` + JSONRPC string `json:"jsonrpc"` + Method string `json:"method"` + Params []interface{} `json:"params"` +} + +// EthRPC - Ethereum rpc client +type EthRPC struct { + url string + client httpClient + log logger + Debug bool +} + +// New create new rpc client with given url +func New(url string, options ...func(rpc *EthRPC)) *EthRPC { + rpc := &EthRPC{ + url: url, + client: http.DefaultClient, + log: log.New(os.Stderr, "", log.LstdFlags), + } + for _, option := range options { + option(rpc) + } + + return rpc +} + +// NewEthRPC create new rpc client with given url +func NewEthRPC(url string, options ...func(rpc *EthRPC)) *EthRPC { + return New(url, options...) +} + +func (rpc *EthRPC) call(method string, target interface{}, params ...interface{}) error { + result, err := rpc.Call(method, params...) + if err != nil { + return err + } + + if target == nil { + return nil + } + + return json.Unmarshal(result, target) +} + +// URL returns client url +func (rpc *EthRPC) URL() string { + return rpc.url +} + +// Call returns raw response of method call +func (rpc *EthRPC) Call(method string, params ...interface{}) (json.RawMessage, error) { + request := ethRequest{ + ID: 1, + JSONRPC: "2.0", + Method: method, + Params: params, + } + + body, err := json.Marshal(request) + if err != nil { + return nil, err + } + + response, err := rpc.client.Post(rpc.url, "application/json", bytes.NewBuffer(body)) + if response != nil { + defer response.Body.Close() + } + if err != nil { + return nil, err + } + + data, err := ioutil.ReadAll(response.Body) + if err != nil { + return nil, err + } + + if rpc.Debug { + rpc.log.Println(fmt.Sprintf("%s\nRequest: %s\nResponse: %s\n", method, body, data)) + } + + resp := new(ethResponse) + if err := json.Unmarshal(data, resp); err != nil { + return nil, err + } + + if resp.Error != nil { + return nil, *resp.Error + } + + return resp.Result, nil + +} + +// RawCall returns raw response of method call (Deprecated) +func (rpc *EthRPC) RawCall(method string, params ...interface{}) (json.RawMessage, error) { + return rpc.Call(method, params...) +} + +// Web3ClientVersion returns the current client version. +func (rpc *EthRPC) Web3ClientVersion() (string, error) { + var clientVersion string + + err := rpc.call("web3_clientVersion", &clientVersion) + return clientVersion, err +} + +// Web3Sha3 returns Keccak-256 (not the standardized SHA3-256) of the given data. +func (rpc *EthRPC) Web3Sha3(data []byte) (string, error) { + var hash string + + err := rpc.call("web3_sha3", &hash, fmt.Sprintf("0x%x", data)) + return hash, err +} + +// NetVersion returns the current network protocol version. +func (rpc *EthRPC) NetVersion() (string, error) { + var version string + + err := rpc.call("net_version", &version) + return version, err +} + +// NetListening returns true if client is actively listening for network connections. +func (rpc *EthRPC) NetListening() (bool, error) { + var listening bool + + err := rpc.call("net_listening", &listening) + return listening, err +} + +// NetPeerCount returns number of peers currently connected to the client. +func (rpc *EthRPC) NetPeerCount() (int, error) { + var response string + if err := rpc.call("net_peerCount", &response); err != nil { + return 0, err + } + + return ParseInt(response) +} + +// EthProtocolVersion returns the current ethereum protocol version. +func (rpc *EthRPC) EthProtocolVersion() (string, error) { + var protocolVersion string + + err := rpc.call("eth_protocolVersion", &protocolVersion) + return protocolVersion, err +} + +// EthSyncing returns an object with data about the sync status or false. +func (rpc *EthRPC) EthSyncing() (*Syncing, error) { + result, err := rpc.RawCall("eth_syncing") + if err != nil { + return nil, err + } + syncing := new(Syncing) + if bytes.Equal(result, []byte("false")) { + return syncing, nil + } + err = json.Unmarshal(result, syncing) + return syncing, err +} + +// EthCoinbase returns the client coinbase address +func (rpc *EthRPC) EthCoinbase() (string, error) { + var address string + + err := rpc.call("eth_coinbase", &address) + return address, err +} + +// EthMining returns true if client is actively mining new blocks. +func (rpc *EthRPC) EthMining() (bool, error) { + var mining bool + + err := rpc.call("eth_mining", &mining) + return mining, err +} + +// EthHashrate returns the number of hashes per second that the node is mining with. +func (rpc *EthRPC) EthHashrate() (int, error) { + var response string + + if err := rpc.call("eth_hashrate", &response); err != nil { + return 0, err + } + + return ParseInt(response) +} + +// EthGasPrice returns the current price per gas in wei. +func (rpc *EthRPC) EthGasPrice() (big.Int, error) { + var response string + if err := rpc.call("eth_gasPrice", &response); err != nil { + return big.Int{}, err + } + + return ParseBigInt(response) +} + +// EthAccounts returns a list of addresses owned by client. +func (rpc *EthRPC) EthAccounts() ([]string, error) { + accounts := []string{} + + err := rpc.call("eth_accounts", &accounts) + return accounts, err +} + +// EthBlockNumber returns the number of most recent block. +func (rpc *EthRPC) EthBlockNumber() (int, error) { + var response string + if err := rpc.call("eth_blockNumber", &response); err != nil { + return 0, err + } + + return ParseInt(response) +} + +// EthGetBalance returns the balance of the account of given address in wei. +func (rpc *EthRPC) EthGetBalance(address, block string) (big.Int, error) { + var response string + if err := rpc.call("eth_getBalance", &response, address, block); err != nil { + return big.Int{}, err + } + + return ParseBigInt(response) +} + +// EthGetStorageAt returns the value from a storage position at a given address. +func (rpc *EthRPC) EthGetStorageAt(data string, position int, tag string) (string, error) { + var result string + + err := rpc.call("eth_getStorageAt", &result, data, IntToHex(position), tag) + return result, err +} + +// EthGetTransactionCount returns the number of transactions sent from an address. +func (rpc *EthRPC) EthGetTransactionCount(address, block string) (int, error) { + var response string + + if err := rpc.call("eth_getTransactionCount", &response, address, block); err != nil { + return 0, err + } + + return ParseInt(response) +} + +// EthGetBlockTransactionCountByHash returns the number of transactions in a block from a block matching the given block hash. +func (rpc *EthRPC) EthGetBlockTransactionCountByHash(hash string) (int, error) { + var response string + + if err := rpc.call("eth_getBlockTransactionCountByHash", &response, hash); err != nil { + return 0, err + } + + return ParseInt(response) +} + +// EthGetBlockTransactionCountByNumber returns the number of transactions in a block from a block matching the given block +func (rpc *EthRPC) EthGetBlockTransactionCountByNumber(number int) (int, error) { + var response string + + if err := rpc.call("eth_getBlockTransactionCountByNumber", &response, IntToHex(number)); err != nil { + return 0, err + } + + return ParseInt(response) +} + +// EthGetUncleCountByBlockHash returns the number of uncles in a block from a block matching the given block hash. +func (rpc *EthRPC) EthGetUncleCountByBlockHash(hash string) (int, error) { + var response string + + if err := rpc.call("eth_getUncleCountByBlockHash", &response, hash); err != nil { + return 0, err + } + + return ParseInt(response) +} + +// EthGetUncleCountByBlockNumber returns the number of uncles in a block from a block matching the given block number. +func (rpc *EthRPC) EthGetUncleCountByBlockNumber(number int) (int, error) { + var response string + + if err := rpc.call("eth_getUncleCountByBlockNumber", &response, IntToHex(number)); err != nil { + return 0, err + } + + return ParseInt(response) +} + +// EthGetCode returns code at a given address. +func (rpc *EthRPC) EthGetCode(address, block string) (string, error) { + var code string + + err := rpc.call("eth_getCode", &code, address, block) + return code, err +} + +// EthSign signs data with a given address. +// Calculates an Ethereum specific signature with: sign(keccak256("\x19Ethereum Signed Message:\n" + len(message) + message))) +func (rpc *EthRPC) EthSign(address, data string) (string, error) { + var signature string + + err := rpc.call("eth_sign", &signature, address, data) + return signature, err +} + +// EthSendTransaction creates new message call transaction or a contract creation, if the data field contains code. +func (rpc *EthRPC) EthSendTransaction(transaction T) (string, error) { + var hash string + + err := rpc.call("eth_sendTransaction", &hash, transaction) + return hash, err +} + +// EthSendRawTransaction creates new message call transaction or a contract creation for signed transactions. +func (rpc *EthRPC) EthSendRawTransaction(data string) (string, error) { + var hash string + + err := rpc.call("eth_sendRawTransaction", &hash, data) + return hash, err +} + +// EthCall executes a new message call immediately without creating a transaction on the block chain. +func (rpc *EthRPC) EthCall(transaction T, tag string) (string, error) { + var data string + + err := rpc.call("eth_call", &data, transaction, tag) + return data, err +} + +// EthEstimateGas makes a call or transaction, which won't be added to the blockchain and returns the used gas, which can be used for estimating the used gas. +func (rpc *EthRPC) EthEstimateGas(transaction T) (int, error) { + var response string + + err := rpc.call("eth_estimateGas", &response, transaction) + if err != nil { + return 0, err + } + + return ParseInt(response) +} + +func (rpc *EthRPC) getBlock(method string, withTransactions bool, params ...interface{}) (*Block, error) { + result, err := rpc.RawCall(method, params...) + if err != nil { + return nil, err + } + if bytes.Equal(result, []byte("null")) { + return nil, nil + } + + var response proxyBlock + if withTransactions { + response = new(proxyBlockWithTransactions) + } else { + response = new(proxyBlockWithoutTransactions) + } + + err = json.Unmarshal(result, response) + if err != nil { + return nil, err + } + + block := response.toBlock() + return &block, nil +} + +// EthGetBlockByHash returns information about a block by hash. +func (rpc *EthRPC) EthGetBlockByHash(hash string, withTransactions bool) (*Block, error) { + return rpc.getBlock("eth_getBlockByHash", withTransactions, hash, withTransactions) +} + +// EthGetBlockByNumber returns information about a block by block number. +func (rpc *EthRPC) EthGetBlockByNumber(number int, withTransactions bool) (*Block, error) { + return rpc.getBlock("eth_getBlockByNumber", withTransactions, IntToHex(number), withTransactions) +} + +func (rpc *EthRPC) getTransaction(method string, params ...interface{}) (*Transaction, error) { + transaction := new(Transaction) + + err := rpc.call(method, transaction, params...) + return transaction, err +} + +// EthGetTransactionByHash returns the information about a transaction requested by transaction hash. +func (rpc *EthRPC) EthGetTransactionByHash(hash string) (*Transaction, error) { + return rpc.getTransaction("eth_getTransactionByHash", hash) +} + +// EthGetTransactionByBlockHashAndIndex returns information about a transaction by block hash and transaction index position. +func (rpc *EthRPC) EthGetTransactionByBlockHashAndIndex(blockHash string, transactionIndex int) (*Transaction, error) { + return rpc.getTransaction("eth_getTransactionByBlockHashAndIndex", blockHash, IntToHex(transactionIndex)) +} + +// EthGetTransactionByBlockNumberAndIndex returns information about a transaction by block number and transaction index position. +func (rpc *EthRPC) EthGetTransactionByBlockNumberAndIndex(blockNumber, transactionIndex int) (*Transaction, error) { + return rpc.getTransaction("eth_getTransactionByBlockNumberAndIndex", IntToHex(blockNumber), IntToHex(transactionIndex)) +} + +// EthGetTransactionReceipt returns the receipt of a transaction by transaction hash. +// Note That the receipt is not available for pending transactions. +func (rpc *EthRPC) EthGetTransactionReceipt(hash string) (*TransactionReceipt, error) { + transactionReceipt := new(TransactionReceipt) + + err := rpc.call("eth_getTransactionReceipt", transactionReceipt, hash) + if err != nil { + return nil, err + } + + return transactionReceipt, nil +} + +// EthGetCompilers returns a list of available compilers in the client. +func (rpc *EthRPC) EthGetCompilers() ([]string, error) { + compilers := []string{} + + err := rpc.call("eth_getCompilers", &compilers) + return compilers, err +} + +// EthNewFilter creates a new filter object. +func (rpc *EthRPC) EthNewFilter(params FilterParams) (string, error) { + var filterID string + err := rpc.call("eth_newFilter", &filterID, params) + return filterID, err +} + +// EthNewBlockFilter creates a filter in the node, to notify when a new block arrives. +// To check if the state has changed, call EthGetFilterChanges. +func (rpc *EthRPC) EthNewBlockFilter() (string, error) { + var filterID string + err := rpc.call("eth_newBlockFilter", &filterID) + return filterID, err +} + +// EthNewPendingTransactionFilter creates a filter in the node, to notify when new pending transactions arrive. +// To check if the state has changed, call EthGetFilterChanges. +func (rpc *EthRPC) EthNewPendingTransactionFilter() (string, error) { + var filterID string + err := rpc.call("eth_newPendingTransactionFilter", &filterID) + return filterID, err +} + +// EthUninstallFilter uninstalls a filter with given id. +func (rpc *EthRPC) EthUninstallFilter(filterID string) (bool, error) { + var res bool + err := rpc.call("eth_uninstallFilter", &res, filterID) + return res, err +} + +// EthGetFilterChanges polling method for a filter, which returns an array of logs which occurred since last poll. +func (rpc *EthRPC) EthGetFilterChanges(filterID string) ([]Log, error) { + var logs = []Log{} + err := rpc.call("eth_getFilterChanges", &logs, filterID) + return logs, err +} + +// EthGetFilterLogs returns an array of all logs matching filter with given id. +func (rpc *EthRPC) EthGetFilterLogs(filterID string) ([]Log, error) { + var logs = []Log{} + err := rpc.call("eth_getFilterLogs", &logs, filterID) + return logs, err +} + +// EthGetLogs returns an array of all logs matching a given filter object. +func (rpc *EthRPC) EthGetLogs(params FilterParams) ([]Log, error) { + var logs = []Log{} + err := rpc.call("eth_getLogs", &logs, params) + return logs, err +} + +// Eth1 returns 1 ethereum value (10^18 wei) +func (rpc *EthRPC) Eth1() *big.Int { + return Eth1() +} + +// Eth1 returns 1 ethereum value (10^18 wei) +func Eth1() *big.Int { + return big.NewInt(1000000000000000000) +} diff --git a/vendor/github.com/onrik/ethrpc/go.mod b/vendor/github.com/onrik/ethrpc/go.mod new file mode 100644 index 000000000..8f047b1d3 --- /dev/null +++ b/vendor/github.com/onrik/ethrpc/go.mod @@ -0,0 +1 @@ +module github.com/onrik/ethrpc diff --git a/vendor/github.com/onrik/ethrpc/helpers.go b/vendor/github.com/onrik/ethrpc/helpers.go new file mode 100644 index 000000000..e98030055 --- /dev/null +++ b/vendor/github.com/onrik/ethrpc/helpers.go @@ -0,0 +1,40 @@ +package ethrpc + +import ( + "fmt" + "math/big" + "strconv" + "strings" +) + +// ParseInt parse hex string value to int +func ParseInt(value string) (int, error) { + i, err := strconv.ParseInt(strings.TrimPrefix(value, "0x"), 16, 64) + if err != nil { + return 0, err + } + + return int(i), nil +} + +// ParseBigInt parse hex string value to big.Int +func ParseBigInt(value string) (big.Int, error) { + i := big.Int{} + _, err := fmt.Sscan(value, &i) + + return i, err +} + +// IntToHex convert int to hexadecimal representation +func IntToHex(i int) string { + return fmt.Sprintf("0x%x", i) +} + +// BigToHex covert big.Int to hexadecimal representation +func BigToHex(bigInt big.Int) string { + if bigInt.BitLen() == 0 { + return "0x0" + } + + return "0x" + strings.TrimPrefix(fmt.Sprintf("%x", bigInt.Bytes()), "0") +} diff --git a/vendor/github.com/onrik/ethrpc/interface.go b/vendor/github.com/onrik/ethrpc/interface.go new file mode 100644 index 000000000..2e3021d1b --- /dev/null +++ b/vendor/github.com/onrik/ethrpc/interface.go @@ -0,0 +1,50 @@ +package ethrpc + +import ( + "math/big" +) + +type EthereumAPI interface { + Web3ClientVersion() (string, error) + Web3Sha3(data []byte) (string, error) + NetVersion() (string, error) + NetListening() (bool, error) + NetPeerCount() (int, error) + EthProtocolVersion() (string, error) + EthSyncing() (*Syncing, error) + EthCoinbase() (string, error) + EthMining() (bool, error) + EthHashrate() (int, error) + EthGasPrice() (big.Int, error) + EthAccounts() ([]string, error) + EthBlockNumber() (int, error) + EthGetBalance(address, block string) (big.Int, error) + EthGetStorageAt(data string, position int, tag string) (string, error) + EthGetTransactionCount(address, block string) (int, error) + EthGetBlockTransactionCountByHash(hash string) (int, error) + EthGetBlockTransactionCountByNumber(number int) (int, error) + EthGetUncleCountByBlockHash(hash string) (int, error) + EthGetUncleCountByBlockNumber(number int) (int, error) + EthGetCode(address, block string) (string, error) + EthSign(address, data string) (string, error) + EthSendTransaction(transaction T) (string, error) + EthSendRawTransaction(data string) (string, error) + EthCall(transaction T, tag string) (string, error) + EthEstimateGas(transaction T) (int, error) + EthGetBlockByHash(hash string, withTransactions bool) (*Block, error) + EthGetBlockByNumber(number int, withTransactions bool) (*Block, error) + EthGetTransactionByHash(hash string) (*Transaction, error) + EthGetTransactionByBlockHashAndIndex(blockHash string, transactionIndex int) (*Transaction, error) + EthGetTransactionByBlockNumberAndIndex(blockNumber, transactionIndex int) (*Transaction, error) + EthGetTransactionReceipt(hash string) (*TransactionReceipt, error) + EthGetCompilers() ([]string, error) + EthNewFilter(params FilterParams) (string, error) + EthNewBlockFilter() (string, error) + EthNewPendingTransactionFilter() (string, error) + EthUninstallFilter(filterID string) (bool, error) + EthGetFilterChanges(filterID string) ([]Log, error) + EthGetFilterLogs(filterID string) ([]Log, error) + EthGetLogs(params FilterParams) ([]Log, error) +} + +var _ EthereumAPI = (*EthRPC)(nil) diff --git a/vendor/github.com/onrik/ethrpc/options.go b/vendor/github.com/onrik/ethrpc/options.go new file mode 100644 index 000000000..72ab39879 --- /dev/null +++ b/vendor/github.com/onrik/ethrpc/options.go @@ -0,0 +1,35 @@ +package ethrpc + +import ( + "io" + "net/http" +) + +type httpClient interface { + Post(url string, contentType string, body io.Reader) (*http.Response, error) +} + +type logger interface { + Println(v ...interface{}) +} + +// WithHttpClient set custom http client +func WithHttpClient(client httpClient) func(rpc *EthRPC) { + return func(rpc *EthRPC) { + rpc.client = client + } +} + +// WithLogger set custom logger +func WithLogger(l logger) func(rpc *EthRPC) { + return func(rpc *EthRPC) { + rpc.log = l + } +} + +// WithDebug set debug flag +func WithDebug(enabled bool) func(rpc *EthRPC) { + return func(rpc *EthRPC) { + rpc.Debug = enabled + } +} diff --git a/vendor/github.com/onrik/ethrpc/types.go b/vendor/github.com/onrik/ethrpc/types.go new file mode 100644 index 000000000..b90baeef0 --- /dev/null +++ b/vendor/github.com/onrik/ethrpc/types.go @@ -0,0 +1,322 @@ +package ethrpc + +import ( + "bytes" + "encoding/json" + "math/big" + "unsafe" +) + +// Syncing - object with syncing data info +type Syncing struct { + IsSyncing bool + StartingBlock int + CurrentBlock int + HighestBlock int +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (s *Syncing) UnmarshalJSON(data []byte) error { + proxy := new(proxySyncing) + if err := json.Unmarshal(data, proxy); err != nil { + return err + } + + proxy.IsSyncing = true + *s = *(*Syncing)(unsafe.Pointer(proxy)) + + return nil +} + +// T - input transaction object +type T struct { + From string + To string + Gas int + GasPrice *big.Int + Value *big.Int + Data string + Nonce int +} + +// MarshalJSON implements the json.Unmarshaler interface. +func (t T) MarshalJSON() ([]byte, error) { + params := map[string]interface{}{ + "from": t.From, + } + if t.To != "" { + params["to"] = t.To + } + if t.Gas > 0 { + params["gas"] = IntToHex(t.Gas) + } + if t.GasPrice != nil { + params["gasPrice"] = BigToHex(*t.GasPrice) + } + if t.Value != nil { + params["value"] = BigToHex(*t.Value) + } + if t.Data != "" { + params["data"] = t.Data + } + if t.Nonce > 0 { + params["nonce"] = IntToHex(t.Nonce) + } + + return json.Marshal(params) +} + +// Transaction - transaction object +type Transaction struct { + Hash string + Nonce int + BlockHash string + BlockNumber *int + TransactionIndex *int + From string + To string + Value big.Int + Gas int + GasPrice big.Int + Input string +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (t *Transaction) UnmarshalJSON(data []byte) error { + proxy := new(proxyTransaction) + if err := json.Unmarshal(data, proxy); err != nil { + return err + } + + *t = *(*Transaction)(unsafe.Pointer(proxy)) + + return nil +} + +// Log - log object +type Log struct { + Removed bool + LogIndex int + TransactionIndex int + TransactionHash string + BlockNumber int + BlockHash string + Address string + Data string + Topics []string +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (log *Log) UnmarshalJSON(data []byte) error { + proxy := new(proxyLog) + if err := json.Unmarshal(data, proxy); err != nil { + return err + } + + *log = *(*Log)(unsafe.Pointer(proxy)) + + return nil +} + +// FilterParams - Filter parameters object +type FilterParams struct { + FromBlock string `json:"fromBlock,omitempty"` + ToBlock string `json:"toBlock,omitempty"` + Address []string `json:"address,omitempty"` + Topics [][]string `json:"topics,omitempty"` +} + +// TransactionReceipt - transaction receipt object +type TransactionReceipt struct { + TransactionHash string + TransactionIndex int + BlockHash string + BlockNumber int + CumulativeGasUsed int + GasUsed int + ContractAddress string + Logs []Log + LogsBloom string + Root string + Status string +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (t *TransactionReceipt) UnmarshalJSON(data []byte) error { + proxy := new(proxyTransactionReceipt) + if err := json.Unmarshal(data, proxy); err != nil { + return err + } + + *t = *(*TransactionReceipt)(unsafe.Pointer(proxy)) + + return nil +} + +// Block - block object +type Block struct { + Number int + Hash string + ParentHash string + Nonce string + Sha3Uncles string + LogsBloom string + TransactionsRoot string + StateRoot string + Miner string + Difficulty big.Int + TotalDifficulty big.Int + ExtraData string + Size int + GasLimit int + GasUsed int + Timestamp int + Uncles []string + Transactions []Transaction +} + +type proxySyncing struct { + IsSyncing bool `json:"-"` + StartingBlock hexInt `json:"startingBlock"` + CurrentBlock hexInt `json:"currentBlock"` + HighestBlock hexInt `json:"highestBlock"` +} + +type proxyTransaction struct { + Hash string `json:"hash"` + Nonce hexInt `json:"nonce"` + BlockHash string `json:"blockHash"` + BlockNumber *hexInt `json:"blockNumber"` + TransactionIndex *hexInt `json:"transactionIndex"` + From string `json:"from"` + To string `json:"to"` + Value hexBig `json:"value"` + Gas hexInt `json:"gas"` + GasPrice hexBig `json:"gasPrice"` + Input string `json:"input"` +} + +type proxyLog struct { + Removed bool `json:"removed"` + LogIndex hexInt `json:"logIndex"` + TransactionIndex hexInt `json:"transactionIndex"` + TransactionHash string `json:"transactionHash"` + BlockNumber hexInt `json:"blockNumber"` + BlockHash string `json:"blockHash"` + Address string `json:"address"` + Data string `json:"data"` + Topics []string `json:"topics"` +} + +type proxyTransactionReceipt struct { + TransactionHash string `json:"transactionHash"` + TransactionIndex hexInt `json:"transactionIndex"` + BlockHash string `json:"blockHash"` + BlockNumber hexInt `json:"blockNumber"` + CumulativeGasUsed hexInt `json:"cumulativeGasUsed"` + GasUsed hexInt `json:"gasUsed"` + ContractAddress string `json:"contractAddress,omitempty"` + Logs []Log `json:"logs"` + LogsBloom string `json:"logsBloom"` + Root string `json:"root"` + Status string `json:"status,omitempty"` +} + +type hexInt int + +func (i *hexInt) UnmarshalJSON(data []byte) error { + result, err := ParseInt(string(bytes.Trim(data, `"`))) + *i = hexInt(result) + + return err +} + +type hexBig big.Int + +func (i *hexBig) UnmarshalJSON(data []byte) error { + result, err := ParseBigInt(string(bytes.Trim(data, `"`))) + *i = hexBig(result) + + return err +} + +type proxyBlock interface { + toBlock() Block +} + +type proxyBlockWithTransactions struct { + Number hexInt `json:"number"` + Hash string `json:"hash"` + ParentHash string `json:"parentHash"` + Nonce string `json:"nonce"` + Sha3Uncles string `json:"sha3Uncles"` + LogsBloom string `json:"logsBloom"` + TransactionsRoot string `json:"transactionsRoot"` + StateRoot string `json:"stateRoot"` + Miner string `json:"miner"` + Difficulty hexBig `json:"difficulty"` + TotalDifficulty hexBig `json:"totalDifficulty"` + ExtraData string `json:"extraData"` + Size hexInt `json:"size"` + GasLimit hexInt `json:"gasLimit"` + GasUsed hexInt `json:"gasUsed"` + Timestamp hexInt `json:"timestamp"` + Uncles []string `json:"uncles"` + Transactions []proxyTransaction `json:"transactions"` +} + +func (proxy *proxyBlockWithTransactions) toBlock() Block { + return *(*Block)(unsafe.Pointer(proxy)) +} + +type proxyBlockWithoutTransactions struct { + Number hexInt `json:"number"` + Hash string `json:"hash"` + ParentHash string `json:"parentHash"` + Nonce string `json:"nonce"` + Sha3Uncles string `json:"sha3Uncles"` + LogsBloom string `json:"logsBloom"` + TransactionsRoot string `json:"transactionsRoot"` + StateRoot string `json:"stateRoot"` + Miner string `json:"miner"` + Difficulty hexBig `json:"difficulty"` + TotalDifficulty hexBig `json:"totalDifficulty"` + ExtraData string `json:"extraData"` + Size hexInt `json:"size"` + GasLimit hexInt `json:"gasLimit"` + GasUsed hexInt `json:"gasUsed"` + Timestamp hexInt `json:"timestamp"` + Uncles []string `json:"uncles"` + Transactions []string `json:"transactions"` +} + +func (proxy *proxyBlockWithoutTransactions) toBlock() Block { + block := Block{ + Number: int(proxy.Number), + Hash: proxy.Hash, + ParentHash: proxy.ParentHash, + Nonce: proxy.Nonce, + Sha3Uncles: proxy.Sha3Uncles, + LogsBloom: proxy.LogsBloom, + TransactionsRoot: proxy.TransactionsRoot, + StateRoot: proxy.StateRoot, + Miner: proxy.Miner, + Difficulty: big.Int(proxy.Difficulty), + TotalDifficulty: big.Int(proxy.TotalDifficulty), + ExtraData: proxy.ExtraData, + Size: int(proxy.Size), + GasLimit: int(proxy.GasLimit), + GasUsed: int(proxy.GasUsed), + Timestamp: int(proxy.Timestamp), + Uncles: proxy.Uncles, + } + + block.Transactions = make([]Transaction, len(proxy.Transactions)) + for i := range proxy.Transactions { + block.Transactions[i] = Transaction{ + Hash: proxy.Transactions[i], + } + } + + return block +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 12978a6cd..aa7a3ff65 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -141,16 +141,16 @@ { "checksumSHA1": "8EuKVkP1v/w5fRuuvUaXX5k/F+I=", "path": "github.com/dexon-foundation/dexon-consensus/common", - "revision": "8786160e28cf17c1125e26939b81ac59df5c260a", - "revisionTime": "2019-03-13T07:38:33Z", + "revision": "c0e1c45539fe882c4cd096a41e36f764d5ad2092", + "revisionTime": "2019-03-16T10:51:30Z", "version": "single-chain", "versionExact": "single-chain" }, { - "checksumSHA1": "ukcaBY+jnQSKyanW+y/uwysV+VM=", + "checksumSHA1": "bYtL3br8SNzJRQzD0leNywoSo2M=", "path": "github.com/dexon-foundation/dexon-consensus/core", - "revision": "8786160e28cf17c1125e26939b81ac59df5c260a", - "revisionTime": "2019-03-13T07:38:33Z", + "revision": "c0e1c45539fe882c4cd096a41e36f764d5ad2092", + "revisionTime": "2019-03-16T10:51:30Z", "version": "single-chain", "versionExact": "single-chain" }, @@ -165,64 +165,64 @@ { "checksumSHA1": "tQSbYCu5P00lUhKsx3IbBZCuSLY=", "path": "github.com/dexon-foundation/dexon-consensus/core/crypto", - "revision": "8786160e28cf17c1125e26939b81ac59df5c260a", - "revisionTime": "2019-03-13T07:38:33Z", + "revision": "c0e1c45539fe882c4cd096a41e36f764d5ad2092", + "revisionTime": "2019-03-16T10:51:30Z", "version": "single-chain", "versionExact": "single-chain" }, { "checksumSHA1": "kC/Tu4is9+jABI/EdvEv7VxwvEo=", "path": "github.com/dexon-foundation/dexon-consensus/core/crypto/dkg", - "revision": "8786160e28cf17c1125e26939b81ac59df5c260a", - "revisionTime": "2019-03-13T07:38:33Z", + "revision": "c0e1c45539fe882c4cd096a41e36f764d5ad2092", + "revisionTime": "2019-03-16T10:51:30Z", "version": "single-chain", "versionExact": "single-chain" }, { "checksumSHA1": "BhLKK8RveoLaeXc9UyUKMwQqchU=", "path": "github.com/dexon-foundation/dexon-consensus/core/crypto/ecdsa", - "revision": "8786160e28cf17c1125e26939b81ac59df5c260a", - "revisionTime": "2019-03-13T07:38:33Z", + "revision": "c0e1c45539fe882c4cd096a41e36f764d5ad2092", + "revisionTime": "2019-03-16T10:51:30Z", "version": "single-chain", "versionExact": "single-chain" }, { "checksumSHA1": "dQOZYmiikmjWhwkUJc0QmCJnO9o=", "path": "github.com/dexon-foundation/dexon-consensus/core/db", - "revision": "8786160e28cf17c1125e26939b81ac59df5c260a", - "revisionTime": "2019-03-13T07:38:33Z", + "revision": "c0e1c45539fe882c4cd096a41e36f764d5ad2092", + "revisionTime": "2019-03-16T10:51:30Z", "version": "single-chain", "versionExact": "single-chain" }, { - "checksumSHA1": "wN+K5gI8+j/7l3SB0DYZ8MkTAwo=", + "checksumSHA1": "H0+GIDijBmoic/0HSTZBUwEij5A=", "path": "github.com/dexon-foundation/dexon-consensus/core/syncer", - "revision": "8786160e28cf17c1125e26939b81ac59df5c260a", - "revisionTime": "2019-03-13T07:38:33Z", + "revision": "c0e1c45539fe882c4cd096a41e36f764d5ad2092", + "revisionTime": "2019-03-16T10:51:30Z", "version": "single-chain", "versionExact": "single-chain" }, { "checksumSHA1": "id8imcgp3SqYhIx0k3Chd0VZrUQ=", "path": "github.com/dexon-foundation/dexon-consensus/core/types", - "revision": "8786160e28cf17c1125e26939b81ac59df5c260a", - "revisionTime": "2019-03-13T07:38:33Z", + "revision": "c0e1c45539fe882c4cd096a41e36f764d5ad2092", + "revisionTime": "2019-03-16T10:51:30Z", "version": "single-chain", "versionExact": "single-chain" }, { "checksumSHA1": "QXRBX9UmvX4wszA9qlyJtzYcTOw=", "path": "github.com/dexon-foundation/dexon-consensus/core/types/dkg", - "revision": "8786160e28cf17c1125e26939b81ac59df5c260a", - "revisionTime": "2019-03-13T07:38:33Z", + "revision": "c0e1c45539fe882c4cd096a41e36f764d5ad2092", + "revisionTime": "2019-03-16T10:51:30Z", "version": "single-chain", "versionExact": "single-chain" }, { - "checksumSHA1": "Hg7KG7RnXK3Autq05xDxSIHKxXI=", + "checksumSHA1": "D9I012bShlJM+rsYxG5sH5nvqXA=", "path": "github.com/dexon-foundation/dexon-consensus/core/utils", - "revision": "8786160e28cf17c1125e26939b81ac59df5c260a", - "revisionTime": "2019-03-13T07:38:33Z", + "revision": "c0e1c45539fe882c4cd096a41e36f764d5ad2092", + "revisionTime": "2019-03-16T10:51:30Z", "version": "single-chain", "versionExact": "single-chain" }, @@ -517,6 +517,12 @@ "revisionTime": "2017-01-28T05:05:32Z" }, { + "checksumSHA1": "VxE5yTGoSXiesg2xRPo0lomflF0=", + "path": "github.com/onrik/ethrpc", + "revision": "6b8e9c0e9a8ffd2154cd4470a6ffb4919885e788", + "revisionTime": "2019-03-05T11:28:07Z" + }, + { "checksumSHA1": "wIcN7tZiF441h08RHAm4NV8cYO4=", "path": "github.com/opentracing/opentracing-go", "revision": "bd9c3193394760d98b2fa6ebb2291f0cd1d06a7d", |