package bfa import ( . "../clique" . "../util" "errors" "fmt" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/clique" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/rpc" "math/big" "sort" "strconv" ) type Node rpc.Client type BigInt big.Int func (bigInt *BigInt) MarshalJSON() ([]byte, error) { i := (*big.Int)(bigInt) return []byte(i.String()), nil } func (bigInt *BigInt) UnmarshalJSON(b []byte) error { if i, ok := new(big.Int).SetString(string(b[1:len(b)-1]), 0); ok { *bigInt = BigInt(*i) return nil } return errors.New("can't unmarshal BigInt") } func (bigInt *BigInt) String() string { return (*big.Int)(bigInt).String() } type Uint64 uint64 func (u *Uint64) MarshalJSON() ([]byte, error) { return []byte(strconv.FormatUint(*(*uint64)(u), 10)), nil } func (u *Uint64) UnmarshalJSON(b []byte) (err error) { i, err := strconv.ParseUint(string(b[1:len(b)-1]), 0, 64) *u = Uint64(i) return } // RPCTransaction represents a transaction type RPCTransaction struct { BlockHash common.Hash `json:"blockHash"` BlockNumber Uint64 `json:"blockNumber"` From common.Address `json:"from"` Gas Uint64 `json:"gas"` GasPrice *BigInt `json:"gasPrice"` Hash common.Hash `json:"hash"` Input string `json:"input"` Nonce Uint64 `json:"nonce"` To common.Address `json:"to"` TransactionIndex Uint64 `json:"transactionIndex"` Value *BigInt `json:"value"` V *BigInt `json:"v"` R *BigInt `json:"r"` S *BigInt `json:"s"` } type Block struct { ParentHash common.Hash `json:"parentHash"` Coinbase common.Address `json:"miner"` Root common.Hash `json:"stateRoot"` TxHash common.Hash `json:"transactionsRoot"` ReceiptHash common.Hash `json:"receiptsRoot"` Bloom types.Bloom `json:"logsBloom"` Difficulty Uint64 `json:"difficulty"` Number Uint64 `json:"number"` GasLimit Uint64 `json:"gasLimit"` GasUsed Uint64 `json:"gasUsed"` Time Uint64 `json:"timestamp"` Extra []byte `json:"extraData"` MixDigest common.Hash `json:"mixHash"` Nonce Uint64 `json:"nonce"` Transactions []RPCTransaction `json:"transactions"` TotalDifficulty Uint64 `json:"totalDifficulty"` Signer common.Address `json:"signer"` } 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 SealerInfo struct { Address string CurrentBlock int64 Since int64 FirstBlock int64 LastBlock int64 } 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() int64 { var peerCount string node.Call(&peerCount, "net_peerCount") p, _ := strconv.ParseInt(peerCount, 0, 64) return p } func (node *Node) BalanceAtBlock(account string, blockNumber int64) *big.Int { var ( balance string block = Latest ) if blockNumber >= 0 { block = Int64ToHex(blockNumber) } node.Call(&balance, "eth_getBalance", account, block) n, _ := new(big.Int).SetString(balance, 0) return n } func (node *Node) Balance(account string) *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 (node *Node) HeaderByNumber(blockNumber int64) types.Header { var ( number string resp types.Header ) if blockNumber < 0 || blockNumber > node.BlockNumber() { number = Latest } else { number = fmt.Sprintf("0x%x", blockNumber) } node.Call(&resp, "eth_getBlockByNumber", number, true) return resp } func (node *Node) BlockByNumber(blockNumber int64) (block Block) { var ( number string ) if blockNumber < 0 || blockNumber > node.BlockNumber() { number = Latest } else { number = fmt.Sprintf("0x%x", blockNumber) } node.Call(&block, "eth_getBlockByNumber", number, true) block.Signer = node.BlockSigner(blockNumber) 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) signer = GetSigner(&header) return } func (node *Node) GetSnapshot() (snapshot Snapshot) { node.Call(&snapshot, "clique_getSnapshot", nil) return } func (node *Node) SnapshotAtHash(hash common.Hash) (snapshot Snapshot) { node.Call(&snapshot, "clique_getSnapshotAtHash", hash) return } func (node *Node) SnapshotAtBlock(blockNumber int64) (snapshot Snapshot) { if blockNumber < 0 || blockNumber >= node.BlockNumber() { return node.GetSnapshot() } node.Call(&snapshot, "clique_getSnapshot", Int64ToHex(blockNumber)) return } func (node *Node) Sealers() (signers []string) { var s []common.Address node.Call(&s, "clique_getSigners", nil) for _, signer := range s { signers = append(signers, BytesToHex(signer.Bytes())) } return } func (node *Node) NodeInfo() (nodeInfo p2p.NodeInfo) { node.Call(&nodeInfo, "admin_nodeInfo", nil) return } func (node *Node) SealersAtHash(hash common.Hash) (signers []string) { var s []common.Address node.Call(&signers, "clique_getSignersAtHash", hash) for _, signer := range s { signers = append(signers, BytesToHex(signer.Bytes())) } return } func (node *Node) SealersAtBlock(blockNumber int64) (signers []string) { var s []common.Address if blockNumber < 0 { return node.Sealers() } node.Call(&s, "clique_getSigners", Int64ToHex(blockNumber)) for _, signer := range s { signers = append(signers, BytesToHex(signer.Bytes())) } 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) 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(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(GetSigner(&block).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 (node *Node) SealerInception(address string) (since int64) { if signers := node.Sealers(); !Contains(signers, address) { return -1 } lo := int64(0) hi := node.BlockNumber() for lo < hi { mid := lo + (hi-lo)/2 signers := node.SealersAtBlock(mid) if Contains(signers, address) { hi = mid } else { lo = mid + 1 } } return hi } func (node *Node) signerFirstBlock(signer string, since int64, until int64) (blockNumber int64) { if since < 0 { return -1 } //snapshot := node.SnapshotAtBlock(since) var ( count int64 = 20 found int64 = -1 ) // first, we look close to the inception if blockNumber, _ = node.searchSnapshotForward(since, signer, count); blockNumber > 0 { return } // next, we do a coarse search since += count for i := since + 10000; i < until && found == -1; i += 10000 { if found, _ = node.searchSnapshotBackward(i, signer, 0); found == -1 { // still not found since = i } } if found > 0 { until = found } n := Min(10, (until-since)/count+1) // number of goroutines ch := make(chan int64) for i := int64(0); i < n; i++ { go func() { for blockNumber := <-ch; blockNumber > 0; blockNumber = <-ch { cnt := Min(count, until-blockNumber+1) if found, _ := node.searchSnapshotForward(blockNumber, signer, cnt); found > 0 { ch <- found } } ch <- -1 }() } for { select { case ch <- since: switch { case since < 0: continue case since+count < until: since += count default: since = -1 //we have exhausted the search space } case found := <-ch: switch { case found < 0: n-- // a goroutine has ended if n == 0 { // all goroutines have ended return } case blockNumber < 0: blockNumber = found // first time we see this signer since = -1 // Notify everyone case found < blockNumber: blockNumber = found // found an earlier block } } } } func (node *Node) signerLastBlock(signer string, since int64, until int64) (blockNumber int64) { if since < 0 { return -1 } var ( count int64 = 20 visited int64 = 0 found int64 = -1 ) // first, we look close to the last block if blockNumber, visited = node.searchSnapshotBackward(until, signer, 0); blockNumber > 0 { return } // next, we do a coarse search until -= visited for i := until - 10000; i > since && found == -1; i -= 10000 { if found, _ = node.searchSnapshotBackward(i, signer, 0); found == -1 { // still not found until = i } } if found > 0 { since = found } n := Min(10, (until-since)/count+1) // number of goroutines ch := make(chan int64) for i := int64(0); i < n; i++ { go func() { for blockNumber := <-ch; blockNumber > 0; blockNumber = <-ch { cnt := Min(count, blockNumber-since+1) if found, _ := node.searchSnapshotBackward(blockNumber, signer, cnt); found > 0 { ch <- found } } ch <- -1 }() } for { select { case ch <- until: switch { case until < 0: continue case until-count > since: until -= count default: until = -1 //we have exhausted the search space } case found := <-ch: switch { case found < 0: n-- // a goroutine has ended if n == 0 { // all goroutines have ended return } case blockNumber < 0: blockNumber = found // first time we see this signer until = -1 // Notify everyone case found > blockNumber: blockNumber = found // found a later block } } } } func (node *Node) searchSnapshotForward(blockNumber int64, signer string, count int64) (found int64, visited int64) { found = -1 if blockNumber+count < 1 { return } for count > 0 { snapshot := node.SnapshotAtBlock(blockNumber + count - 1) recents := int64(len(snapshot.Recents)) visited += recents count -= recents for b, s := range snapshot.Recents { if BytesToHex(s[:]) == signer { found = int64(b) } } } return } func (node *Node) searchSnapshotBackward(blockNumber int64, signer string, count int64) (found int64, visited int64) { found = -1 if blockNumber < 1 { // Genesis block has no signers return } count = Min(count, blockNumber) // Never search below block 1 for { snapshot := node.SnapshotAtBlock(blockNumber - visited) visited += int64(len(snapshot.Recents)) if count == 0 { count = Min(blockNumber, int64(2*len(snapshot.Signers))) } for b, s := range snapshot.Recents { if BytesToHex(s[:]) == signer { found = int64(b) return } } if visited >= count { return } } } func (node *Node) SealerInfo(sealer string) (info SealerInfo, err error) { info.Address = sealer info.CurrentBlock = node.BlockNumber() info.Since = node.SealerInception(sealer) if info.Since == -1 { return info, fmt.Errorf("%q is not a sealer", sealer) } info.FirstBlock = node.signerFirstBlock(sealer, info.Since+1, info.CurrentBlock) info.LastBlock = node.signerLastBlock(sealer, info.FirstBlock, info.CurrentBlock) return } func Dial(url string) (*Node, error) { client, err := rpc.Dial(url) return (*Node)(client), err } func (node *Node) Close() { (*rpc.Client)(node).Close() }