Newer
Older
"bytes"
"encoding/hex"
"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) {
return (*big.Int)(bigInt).MarshalJSON()
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) UnmarshalJSON(b []byte) (err error) {
i, err := strconv.ParseUint(string(b[1:len(b)-1]), 0, 64)
*u = Uint64(i)
return
type Bytes []byte
func (b Bytes) MarshalJSON() ([]byte, error) {
dest := make([]byte, 2+hex.EncodedLen(len(b)))
copy(dest, []byte("\"0x"))
hex.Encode(dest[2:], b)
dest[len(dest)-1] = '"'
return dest, nil
}
func (b *Bytes) UnmarshalJSON(src []byte) (err error) {
Require(len(src) >= 4 && bytes.Equal(src[1:3], []byte("0x")), "invalid json string")
dest := make([]byte, hex.DecodedLen(len(src)-4))
_, err = hex.Decode(dest, src[3:len(src)-1])
if err == nil {
*b = dest
}
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"`
UncleHash common.Hash `json:"sha3Uncles"`
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 *BigInt `json:"difficulty"`
Number *BigInt `json:"number"`
GasLimit Uint64 `json:"gasLimit"`
GasUsed Uint64 `json:"gasUsed"`
Time *BigInt `json:"timestamp"`
Extra Bytes `json:"extraData"`
MixDigest common.Hash `json:"mixHash"`
Nonce types.BlockNonce `json:"nonce"`
Transactions []RPCTransaction `json:"transactions"`
TotalDifficulty Uint64 `json:"totalDifficulty"`
Signer common.Address `json:"signer"`
func (block *Block) Header() (header types.Header) {
header.ParentHash = block.ParentHash
header.UncleHash = block.UncleHash
header.Coinbase = block.Coinbase
header.Root = block.Root
header.TxHash = block.TxHash
header.ReceiptHash = block.TxHash
header.Bloom = block.Bloom
header.Difficulty = (*big.Int)(block.Difficulty)
header.Number = (*big.Int)(block.Number)
header.GasLimit = uint64(block.GasLimit)
header.GasUsed = uint64(block.GasUsed)
header.Time = (*big.Int)(block.Time)
header.Extra = block.Extra
header.MixDigest = block.MixDigest
header.Nonce = block.Nonce
return
}
func (block *Block) setSigner() {
header := block.Header()
block.Signer = GetSigner(&header)
}
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
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 hexBlockNumber(number int64) (blockNumber string) {
if number == -1 {
blockNumber = Latest
blockNumber = "0x" + strconv.FormatInt(number, 16)
func (node *Node) BlockNumberInRange(number int64) (blockNumber int64) {
latest := node.BlockNumber()
switch {
case number == -1, number > latest:
blockNumber = latest
case number < 0:
blockNumber = Max(0, latest+1+number)
default:
blockNumber = number
return
}
func (node *Node) HeaderByNumber(blockNumber int64) (header types.Header) {
Require(blockNumber == -1 || blockNumber == node.BlockNumberInRange(blockNumber), "block number out of range")
node.Call(&header, "eth_getBlockByNumber", hexBlockNumber(blockNumber), true)
return
}
func (node *Node) BlockByNumber(blockNumber int64) (block Block) {
Require(blockNumber == -1 || blockNumber == node.BlockNumberInRange(blockNumber), "block number out of range")
node.Call(&block, "eth_getBlockByNumber", hexBlockNumber(blockNumber), true)
block.setSigner()
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) {
Require(blockNumber == -1 || blockNumber == node.BlockNumberInRange(blockNumber), "block number out of range")
node.Call(&snapshot, "clique_getSnapshot", hexBlockNumber(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)
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
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) {
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
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()
}