Skip to content
Snippets Groups Projects
node.go 7.91 KiB
Newer Older
package bfa

import (
	. "../util"
	"fmt"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/consensus/clique"
	"github.com/ethereum/go-ethereum/p2p"
	"github.com/ethereum/go-ethereum/rpc"
)

type Node rpc.Client

type Snapshot struct {
	Number  uint64                          `json:"number"`  // Block number where the snapshot was created
	Hash    common.Hash                     `json:"hash"`    // Block hash where the snapshot was created
	Signers map[common.Address]struct{}     `json:"signers"` // Set of authorized signers at this moment
	Recents map[uint64]common.Address       `json:"recents"` // Set of recent signers for spam protections
	Votes   []*clique.Vote                  `json:"votes"`   // List of votes cast in chronological order
	Tally   map[common.Address]clique.Tally `json:"tally"`   // Current vote tally to avoid recalculating
}

type Tally struct {
	True  int `json:"true"`
	False int `json:"false"`
	Null  int `json:"null"`
}

type Proposals struct {
	BlockNumber int64                       `json:"number"`    // Block number where the snapshot was created
	Proposals   []string                    `json:"proposals"` // List of proposals being voted
	Signers     []string                    `json:"signers"`   // List of authorized signers at this moment
	Tally       map[string]*Tally           `json:"tally"`     // Count of positive, negative and empty votes for a proposal
	Votes       map[string]map[string]*bool `json:"votes"`     // List of votes for each proposal
}

type SealerStatus struct {
	LastBlock  int64    `json:"lastBlockSigned"`
	Time       uint64   `json:"timestamp"`
	Difficulty uint64   `json:"difficulty"`
	Balance    *big.Int `json:"balance"`
	Latest  = "latest"
	Self    = "self"
	Network = 47525974938
	Genesis = "0xe0e26f415af00fe32e1e8d94336355db43a78d9f59eb70910172b61fb670d99e"

func (node *Node) Call(result interface{}, method string, args ...interface{}) {
	Check((*rpc.Client)(node).Call(result, method, args...))
}

func (node *Node) BlockNumber() int64 {
	var bn rpc.BlockNumber
	node.Call(&bn, "eth_blockNumber")
	return bn.Int64()
}

func (node *Node) Accounts() (accounts []string) {
	node.Call(&accounts, "eth_accounts")
	return
}

func (node *Node) IsMining() (mining bool) {
	node.Call(&mining, "eth_mining")
	return
}

func (node *Node) PeerCount() uint64 {
	var peerCount Uint64
	node.Call(&peerCount, "net_peerCount")
	return uint64(peerCount)
func (node *Node) BalanceAtBlock(account common.Address, blockNumber int64) *big.Int {
	var balance BigInt
	node.Call(&balance, "eth_getBalance", account, hexBlockNumber(blockNumber))
	return (*big.Int)(&balance)
func (node *Node) Balance(account common.Address) *big.Int {
	return node.BalanceAtBlock(account, -1)
}

func (node *Node) Coinbase() (coinbase string, err error) {
		if e := recover(); e != nil {
			switch s := e.(type) {
			case string:
				err = fmt.Errorf(s)
			case error:
				err = s
			default:
				err = fmt.Errorf("unknown error while getting coinbase: %v", e)
			}
		}
	}()
	var address common.Address
	node.Call(&address, "eth_coinbase")
	coinbase = BytesToHex(address[:])
	return
}
func hexBlockNumber(number int64) (blockNumber string) {
	if number == -1 {
		blockNumber = Latest
		blockNumber = "0x" + strconv.FormatInt(number, 16)
func (node *Node) BlockNumberInRange(number int64) (blockNumber int64) {
	latest := node.BlockNumber()
	switch {
	case number == -1, number > latest:
		blockNumber = latest
	case number < 0:
		blockNumber = Max(0, latest+1+number)
	default:
		blockNumber = number
func (node *Node) HeaderByNumber(blockNumber int64) (header Header) {
	node.Call(&header, "eth_getBlockByNumber", hexBlockNumber(blockNumber), false)
	return
}

func (node *Node) HeaderByHash(hash common.Hash) (header Header) {
	node.Call(&header, "eth_getBlockByHash", hash, false)
	return
}

func (node *Node) BlockByNumber(blockNumber int64) (block Block) {
	node.Call(&block, "eth_getBlockByNumber", hexBlockNumber(blockNumber), true)
	block.Signer = block.GetSigner()
	block.Hash = block.GetHash()
	return
}

func (node *Node) BlockByHash(hash common.Hash) (block Block) {
	node.Call(&block, "eth_getBlockByHash", hash, true)
	block.Signer = block.GetSigner()
	block.Hash = block.GetHash()
func (node *Node) BlockSigner(blockNumber int64) (signer common.Address) {
	if blockNumber == 0 { // we return an empty signer for genesis block
		return
	}
	header := node.HeaderByNumber(blockNumber)
	return header.GetSigner()
}

func (node *Node) GetSnapshot() (snapshot Snapshot) {
	node.Call(&snapshot, "clique_getSnapshot", nil)
	return
}

func (node *Node) SnapshotAtBlock(blockNumber int64) (snapshot Snapshot) {
	node.Call(&snapshot, "clique_getSnapshot", hexBlockNumber(blockNumber))
func (node *Node) Sealers() (signers []string) {
	node.Call(&signers, "clique_getSigners", nil)
func (node *Node) NodeInfo() (nodeInfo p2p.NodeInfo) {
	node.Call(&nodeInfo, "admin_nodeInfo", nil)
	return
}

func (node *Node) SealersAtBlock(blockNumber int64) (signers []string) {
	if blockNumber == -1 {
	node.Call(&signers, "clique_getSigners", Int64ToHex(blockNumber))
func (node *Node) IsSealer(address string) bool {
	if address == Self {
		var err error
		if address, err = node.Coinbase(); err != nil {
			return false
		}
	}
	return Contains(node.Sealers(), address)
func (node *Node) IsSealerAtBlock(address string, blockNumber int64) bool {
	if address == Self {
		var err error
		if address, err = node.Coinbase(); err != nil {
			return false
		}
	}
	return Contains(node.SealersAtBlock(blockNumber), address)
}

func (node *Node) Propose(address string, vote bool) {
	node.Call(nil, "clique_propose", address, vote)
	return
}

func (node *Node) Votes(blockNumber int64) (votes Proposals) {
	var (
		snapshot Snapshot
	)
	if blockNumber < 0 {
		snapshot = node.GetSnapshot()
	} else {
		snapshot = node.SnapshotAtBlock(blockNumber)
	}
	votes.BlockNumber = int64(snapshot.Number)
	for signer := range snapshot.Signers {
		votes.Signers = append(votes.Signers, BytesToHex(signer[:]))
		sort.Strings(votes.Signers)
	}
	for proposal := range snapshot.Tally {
		votes.Proposals = append(votes.Proposals, BytesToHex(proposal[:]))
	}
	votes.Votes = make(map[string]map[string]*bool)
	votes.Tally = make(map[string]*Tally)
	for _, v := range snapshot.Votes {
		proposal := BytesToHex(v.Address[:])
		signer := BytesToHex(v.Signer[:])
		if votes.Votes[proposal] == nil {
			votes.Votes[proposal] = make(map[string]*bool)
			for _, signer := range votes.Signers {
				votes.Votes[proposal][signer] = nil
			}
			votes.Tally[proposal] = &Tally{0, 0, len(votes.Signers)}
		}
		votes.Votes[proposal][signer] = &v.Authorize
		if v.Authorize {
			votes.Tally[proposal].True += 1
		} else {
			votes.Tally[proposal].False += 1
		}
		votes.Tally[proposal].Null -= 1
	}
	return
}

func (node *Node) SealersStatus(blockNumber int64) (status map[string]*SealerStatus) {
	if blockNumber == 0 { // Genesis block doesn't have signer
		return
	}
	status = make(map[string]*SealerStatus)
	for _, address := range node.SealersAtBlock(blockNumber) {
		status[address] = &SealerStatus{
			Balance: node.BalanceAtBlock(common.HexToAddress(address), blockNumber),
	}
	notSeen := int64(len(status))
	block := node.HeaderByNumber(blockNumber)
	blockNumber = block.Number.Int64()
	until := Max(1, blockNumber-5*notSeen)
		signer := BytesToHex(block.GetSigner().Bytes())
		if status[signer].LastBlock == 0 {
			status[signer].LastBlock = block.Number.Int64()
			status[signer].Time = block.Time.Uint64()
			status[signer].Difficulty = block.Difficulty.Uint64()
			notSeen--
		}
		if blockNumber == until {
			break
		}
		blockNumber--
		block = node.HeaderByNumber(blockNumber)
func Dial(url string) (*Node, error) {
	client, err := rpc.Dial(url)
	return (*Node)(client), err
}

func (node *Node) Close() {
	(*rpc.Client)(node).Close()
}