Skip to content
Snippets Groups Projects
node.go 12.6 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"`
type Header 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"`
}

func (header *Header) ParentHsh() common.Hash {
	return header.ParentHash
}

func (header *Header) UncleHsh() common.Hash {
	return header.UncleHash
}

func (header *Header) RootHsh() common.Hash {
	return header.Root
}

func (header *Header) TxHsh() common.Hash {
	return header.TxHash
}

func (header *Header) ReceiptHsh() common.Hash {
	return header.ReceiptHash
}

func (header *Header) MixDigestHsh() common.Hash {
	return header.MixDigest
}

func (header *Header) LogsBloom() types.Bloom {
	return header.Bloom
}

func (header *Header) BlockNonce() types.BlockNonce {
	return header.Nonce
}

func (header *Header) BlockDifficulty() *big.Int {
	return (*big.Int)(header.Difficulty)
}

func (header *Header) BlockNumber() *big.Int {
	return (*big.Int)(header.Number)
}

func (header *Header) Timestamp() *big.Int {
	return (*big.Int)(header.Time)
}

func (header *Header) MaxGas() uint64 {
	return uint64(header.GasLimit)
}

func (header *Header) Gas() uint64 {
	return uint64(header.GasUsed)
}

func (header *Header) ExtraWithoutSignature() []byte {
	return header.Extra[:len(header.Extra)-65]
}

func (header *Header) Signature() []byte {
	return header.Extra[len(header.Extra)-65:]
}

func (header *Header) Miner() common.Address {
	return header.Coinbase
}

	Transactions    []RPCTransaction `json:"transactions"`
	TotalDifficulty Uint64           `json:"totalDifficulty"`
	Signer          common.Address   `json:"signer"`
func (block *Block) GetHeader() (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() {
	block.Signer = GetSigner(block)
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 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
func (node *Node) HeaderByNumber(blockNumber int64) (header 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) 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) 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.BlockNumber().Int64()
	until := Max(1, blockNumber-5*notSeen)
		signer := BytesToHex(GetSigner(&block).Bytes())
		if status[signer].LastBlock == 0 {
			status[signer].LastBlock = block.BlockNumber().Int64()
			status[signer].Time = block.Timestamp().Uint64()
			status[signer].Difficulty = block.BlockDifficulty().Uint64()
			notSeen--
		}
		if blockNumber == until {
			break
		}
		blockNumber--
		block = node.HeaderByNumber(blockNumber)
func Dial(url string) (*Node, error) {
	client, err := rpc.Dial(url)
	return (*Node)(client), err
}

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