aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWei-Ning Huang <w@dexon.org>2019-03-17 09:12:50 +0800
committerWei-Ning Huang <w@byzantine-lab.io>2019-06-12 17:27:23 +0800
commit9493109f2be4507605e6b17e406bf8fd147ab3c8 (patch)
tree84fee3d1fb2095ff5ba793bdfccba89970bc8f89
parent9ee92eff068b09246ab6446efa39abfc0c7bd8a8 (diff)
downloadgo-tangerine-9493109f2be4507605e6b17e406bf8fd147ab3c8.tar
go-tangerine-9493109f2be4507605e6b17e406bf8fd147ab3c8.tar.gz
go-tangerine-9493109f2be4507605e6b17e406bf8fd147ab3c8.tar.bz2
go-tangerine-9493109f2be4507605e6b17e406bf8fd147ab3c8.tar.lz
go-tangerine-9493109f2be4507605e6b17e406bf8fd147ab3c8.tar.xz
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
-rw-r--r--cmd/gdex/main.go1
-rw-r--r--cmd/utils/flags.go21
-rw-r--r--core/vm/oracle_contracts.go8
-rw-r--r--core/vm/oracle_contracts_test.go2
-rw-r--r--dex/backend.go8
-rw-r--r--dex/blockproposer.go30
-rw-r--r--dex/config.go3
-rw-r--r--dex/governance.go13
-rw-r--r--dex/recovery.go484
-rw-r--r--dex/recovery_test.go43
-rw-r--r--params/config.go37
-rw-r--r--params/gen_dexcon_config.go12
-rw-r--r--test/genesis.json9
-rwxr-xr-xtest/run_test.sh20
-rw-r--r--vendor/github.com/dexon-foundation/dexon-consensus/core/agreement-mgr.go84
-rw-r--r--vendor/github.com/dexon-foundation/dexon-consensus/core/blockchain.go76
-rw-r--r--vendor/github.com/dexon-foundation/dexon-consensus/core/consensus.go472
-rw-r--r--vendor/github.com/dexon-foundation/dexon-consensus/core/interfaces.go17
-rw-r--r--vendor/github.com/dexon-foundation/dexon-consensus/core/syncer/consensus.go162
-rw-r--r--vendor/github.com/dexon-foundation/dexon-consensus/core/syncer/watch-cat.go148
-rw-r--r--vendor/github.com/dexon-foundation/dexon-consensus/core/utils.go6
-rw-r--r--vendor/github.com/dexon-foundation/dexon-consensus/core/utils/round-event.go70
-rw-r--r--vendor/github.com/dexon-foundation/dexon-consensus/core/utils/utils.go6
-rw-r--r--vendor/github.com/onrik/ethrpc/LICENSE21
-rw-r--r--vendor/github.com/onrik/ethrpc/README.md103
-rw-r--r--vendor/github.com/onrik/ethrpc/ethrpc.go514
-rw-r--r--vendor/github.com/onrik/ethrpc/go.mod1
-rw-r--r--vendor/github.com/onrik/ethrpc/helpers.go40
-rw-r--r--vendor/github.com/onrik/ethrpc/interface.go50
-rw-r--r--vendor/github.com/onrik/ethrpc/options.go35
-rw-r--r--vendor/github.com/onrik/ethrpc/types.go322
-rw-r--r--vendor/vendor.json52
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(&params.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",