Newer
Older
. "../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/p2p"
"github.com/ethereum/go-ethereum/rpc"
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
)
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
}
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() 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) {
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) BlockByNumber(blockNumber int64) types.Header {
var (
number string
resp types.Header
)
if blockNumber < 0 {
} else {
number = fmt.Sprintf("0x%x", blockNumber)
}
node.Call(&resp, "eth_getBlockByNumber", number, false)
return resp
}
func (node *Node) BlockSigner(blockNumber int64) (signer string) {
header := node.BlockByNumber(blockNumber)
Check(err)
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 {
return node.GetSnapshot()
}
node.Call(&snapshot, "clique_getSnapshot", Int64ToHex(blockNumber))
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()))
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()))
func (node *Node) SealersAtBlock(blockNumber int64) (signers []string) {
return node.Sealers()
}
node.Call(&s, "clique_getSigners", Int64ToHex(blockNumber))
for _, signer := range s {
signers = append(signers, BytesToHex(signer.Bytes()))
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.BlockByNumber(blockNumber)
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.BlockByNumber(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)
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
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
}
}
}()
}
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) {
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
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
}
}
}()
}
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 {
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 {
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)
client, err := rpc.Dial(url)
}
func (node *Node) Close() {
(*rpc.Client)(node).Close()
}