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" "math/big" "sort" "strconv" ) 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"` } const ( 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) { defer func() { 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 } else { blockNumber = "0x" + strconv.FormatInt(number, 16) } return } 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 } return } 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() return } 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)) return } func (node *Node) Sealers() (signers []string) { node.Call(&signers, "clique_getSigners", nil) return } 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 { return node.Sealers() } node.Call(&signers, "clique_getSigners", Int64ToHex(blockNumber)) return } 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) for notSeen > 0 { 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) } return status } func Dial(url string) (*Node, error) { client, err := rpc.Dial(url) return (*Node)(client), err } func (node *Node) Close() { (*rpc.Client)(node).Close() }