package bfa import ( . "../clique" . "../util" "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/rpc" "sort" ) 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 SealerInfo struct { Address string CurrentBlock int64 Since int64 FirstBlock int64 LastBlock int64 } const ( Latest = "latest" Self = "self" ) 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) 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) GetBlockByNumber(blockNumber int64) types.Header { var ( number string resp types.Header ) if blockNumber < 0 { number = Latest } else { number = fmt.Sprintf("0x%x", blockNumber) } node.Call(&resp, "eth_getBlockByNumber", number, false) return resp } func (node *Node) GetBlockSigner(blockNumber int64) (signer string) { header := node.GetBlockByNumber(blockNumber) signer, err := GetSigner(&header) Check(err) return } func (node *Node) GetSnapshot() (snapshot Snapshot) { node.Call(&snapshot, "clique_getSnapshot", nil) return } func (node *Node) GetSnapshotAtHash(hash common.Hash) (snapshot Snapshot) { node.Call(&snapshot, "clique_getSnapshotAtHash", hash) return } func (node *Node) GetSnapshotAtBlock(blockNumber int64) (snapshot Snapshot) { if blockNumber < 0 { return node.GetSnapshot() } node.Call(&snapshot, "clique_getSnapshot", Int64ToHex(blockNumber)) return } func (node *Node) GetSigners() (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) GetSignersAtHash(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) GetSignersAtBlock(blockNumber int64) (signers []string) { var s []common.Address if blockNumber < 0 { return node.GetSigners() } 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.GetSigners(), address) } func (node *Node) Propose(address string, vote bool) { node.Call(nil, "clique_propose", address, vote) return } func (node *Node) GetVotes(blockNumber int64) (votes Proposals) { var ( snapshot Snapshot ) if blockNumber < 0 { snapshot = node.GetSnapshot() } else { snapshot = node.GetSnapshotAtBlock(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]int64) { if blockNumber == 0 { // Genesis block doesn't have signer return } status = make(map[string]int64) for _, address := range node.GetSignersAtBlock(blockNumber) { status[address] = -1 } notSeen := int64(len(status)) block := node.GetBlockByNumber(blockNumber) blockNumber = block.Number.Int64() until := Max(1, blockNumber-5*notSeen) for notSeen > 0 { signer, _ := GetSigner(&block) if status[signer] == -1 { status[signer] = block.Number.Int64() notSeen-- } if blockNumber == until { break } blockNumber-- block = node.GetBlockByNumber(blockNumber) } return status } func (node *Node) GetSealerInception(address string) (since int64) { if signers := node.GetSigners(); !Contains(signers, address) { return -1 } lo := int64(0) hi := node.blockNumber() for lo < hi { mid := lo + (hi-lo)/2 signers := node.GetSignersAtBlock(mid) if Contains(signers, address) { hi = mid } else { lo = mid + 1 } } return hi } func (node *Node) getSignerFirstBlock(signer string, since int64, until int64) (blockNumber int64) { if since < 0 { return -1 } //snapshot := node.GetSnapshotAtBlock(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) getSignerLastBlock(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.GetSnapshotAtBlock(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.GetSnapshotAtBlock(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.GetSealerInception(sealer) if info.Since == -1 { return info, fmt.Errorf("%q is not a sealer", sealer) } info.FirstBlock = node.getSignerFirstBlock(sealer, info.Since+1, info.CurrentBlock) info.LastBlock = node.getSignerLastBlock(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() }