diff options
author | Wei-Ning Huang <w@dexon.org> | 2019-03-17 09:12:50 +0800 |
---|---|---|
committer | Jimmy Hu <jimmy.hu@dexon.org> | 2019-03-17 09:12:50 +0800 |
commit | 6091c2de57fee155e5d7f512b326bde53c84c04e (patch) | |
tree | 7625cec0e792068b5945d3dda9bb4998457df7b4 /dex/recovery.go | |
parent | 8ff31f980a7e169dda85cd77f88f56d03788a135 (diff) | |
download | dexon-6091c2de57fee155e5d7f512b326bde53c84c04e.tar dexon-6091c2de57fee155e5d7f512b326bde53c84c04e.tar.gz dexon-6091c2de57fee155e5d7f512b326bde53c84c04e.tar.bz2 dexon-6091c2de57fee155e5d7f512b326bde53c84c04e.tar.lz dexon-6091c2de57fee155e5d7f512b326bde53c84c04e.tar.xz dexon-6091c2de57fee155e5d7f512b326bde53c84c04e.tar.zst dexon-6091c2de57fee155e5d7f512b326bde53c84c04e.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
Diffstat (limited to 'dex/recovery.go')
-rw-r--r-- | dex/recovery.go | 484 |
1 files changed, 484 insertions, 0 deletions
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 +} |