// 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 // . package dex import ( "crypto/ecdsa" "encoding/hex" "errors" "fmt" "math/big" "strconv" "strings" "github.com/onrik/ethrpc" "github.com/tangerine-network/go-tangerine/accounts/abi" "github.com/tangerine-network/go-tangerine/common" "github.com/tangerine-network/go-tangerine/core/types" "github.com/tangerine-network/go-tangerine/crypto" "github.com/tangerine-network/go-tangerine/log" "github.com/tangerine-network/go-tangerine/params" "github.com/tangerine-network/go-tangerine/rlp" ) 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": 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": "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": "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 errAlreadyVoted = errors.New("already voted for recovery") 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 publicKey string 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, publicKey: hex.EncodeToString(crypto.FromECDSAPub(&privKey.PublicKey)), privateKey: privKey, nodeAddress: crypto.PubkeyToAddress(privKey.PublicKey), client: client, } } func (r *Recovery) callRPC(data []byte, tag string) ([]byte, error) { res, err := r.client.EthCall(ethrpc.T{ From: r.nodeAddress.String(), To: r.contract.String(), Data: "0x" + hex.EncodeToString(data), }, tag) if err != nil { return nil, err } resBytes, err := hex.DecodeString(res[2:]) if err != nil { return nil, err } return resBytes, nil } 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("voted", big.NewInt(int64(height)), r.nodeAddress) if err != nil { return nil, err } resBytes, err := r.callRPC(data, "latest") if err != nil { return nil, err } var voted bool err = abiObject.Unpack(&voted, "voted", resBytes) if err != nil { return nil, err } if voted { log.Info("Already voted for skip block", "height", height) return nil, errAlreadyVoted } data, err = abiObject.Pack("depositValue") if err != nil { return nil, err } resBytes, err = r.callRPC(data, "latest") 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 { notarySet, err := r.gov.NotarySet(r.gov.Round()) if err != nil { return err } if _, ok := notarySet[r.publicKey]; !ok { return errors.New("not in notary set") } tx, err := r.genVoteForSkipBlockTx(height) if err == errAlreadyVoted { return nil } 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 resBytes, err := r.callRPC(data, fmt.Sprintf("0x%x", snapshotHeight)) 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.DKGSetNodeKeyAddresses(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 } resBytes, err := r.callRPC(data, fmt.Sprintf("0x%x", snapshotHeight)) 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 }