aboutsummaryrefslogblamecommitdiffstats
path: root/dex/recovery.go
blob: 1393d37df7370190db3467edaab771a80031f417 (plain) (tree)





















                                                                               
                








                                                        
                                               










































































































                                                  


































                                    



























                                    










































                                    

                                                              













                                                                 
                           











                                                                     
                                                                                          





                                                                        
















                                                                     










                                                                                     
                                                                                      



                               
                                                  



                               
















                                                                          









































                                                                                        







                                                        
                                                  


                                   
























                                                                                  
                                                                             









                                                            
                                                                     












                                                                                           
                                                                                     















                                                                
// 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"
    "errors"
    "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/log"
    "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": 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
}