Newer
Older
"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"
)
type Node rpc.Client
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
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"`
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
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) HeaderByNumber(blockNumber int64) types.Header {
var (
number string
resp types.Header
)
if blockNumber < 0 || blockNumber > node.BlockNumber() {
} else {
number = fmt.Sprintf("0x%x", blockNumber)
}
node.Call(&resp, "eth_getBlockByNumber", number, true)
func (node *Node) BlockByNumber(blockNumber int64) (block Block) {
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)
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
}
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))
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.HeaderByNumber(blockNumber)
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)
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
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) {
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
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()
}