Newer
Older
5
6
7
8
9
10
11
12
13
14
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
. "../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
}
type SealerStatus struct {
LastBlock int64 `json:"lastBlockSigned"`
Time uint64 `json:"timestamp"`
}
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) {
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 {
} 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)
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()))
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]*SealerStatus) {
if blockNumber == 0 { // Genesis block doesn't have signer
return
}
status = make(map[string]*SealerStatus)
for _, address := range node.GetSignersAtBlock(blockNumber) {
status[address] = &SealerStatus{}
}
notSeen := int64(len(status))
block := node.GetBlockByNumber(blockNumber)
blockNumber = block.Number.Int64()
if status[signer].LastBlock == 0 {
status[signer].LastBlock = block.Number.Int64()
status[signer].Time = block.Time.Uint64()
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
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)
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
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
308
309
310
311
312
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
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
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
}
}
}()
}
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 {
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 {
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
}
client, err := rpc.Dial(url)
}
func (node *Node) Close() {
(*rpc.Client)(node).Close()
}