Skip to content
Snippets Groups Projects
node.go 16.8 KiB
Newer Older
	. "../clique"
	"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 BigInt big.Int

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
type RPCTransaction struct {
	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"`
	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)
}

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) {
		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 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)
	signer = GetSigner(&header)
	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) {
	var s []common.Address
	if blockNumber < 0 {
	}
	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)
	blockNumber = block.Number.Int64()
	until := Max(1, blockNumber-5*notSeen)
		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)
func (node *Node) SealerInception(address string) (since int64) {
	if signers := node.Sealers(); !Contains(signers, address) {
	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) {
	//snapshot := node.SnapshotAtBlock(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) signerLastBlock(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.SnapshotAtBlock(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.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 {
			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.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)
func Dial(url string) (*Node, error) {
	client, err := rpc.Dial(url)
	return (*Node)(client), err
}

func (node *Node) Close() {
	(*rpc.Client)(node).Close()
}