Skip to content
Snippets Groups Projects
node.go 17.1 KiB
Newer Older
	. "../clique"
	"encoding/json"
	"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 (b BigInt) MarshalJSON() ([]byte, error) {
	i := (big.Int)(b)
	return []byte(i.String()), nil
}

func (b *BigInt) String() string {
	return (*big.Int)(b).String()
}

// RPCTransaction represents a transaction that will serialize to the RPC representation of a transaction
type RPCTransaction struct {
	BlockHash        string  `json:"blockHash"`
	BlockNumber      int64   `json:"blockNumber"`
	From             string  `json:"from"`
	Gas              uint64  `json:"gas"`
	GasPrice         uint64  `json:"gasPrice"`
	Hash             string  `json:"hash"`
	Input            string  `json:"input"`
	Nonce            uint64  `json:"nonce"`
	To               string  `json:"to"`
	TransactionIndex uint    `json:"transactionIndex"`
	Value            string  `json:"value"`
	V                *BigInt `json:"v"`
	R                *BigInt `json:"r"`
	S                *BigInt `json:"s"`
}

	ParentHash      string           `json:"parentHash"`
	Coinbase        string           `json:"miner"`
	Root            string           `json:"stateRoot"`
	TxHash          string           `json:"transactionsRoot"`
	ReceiptHash     string           `json:"receiptsRoot"`
	Bloom           string           `json:"logsBloom"`
	Difficulty      uint64           `json:"difficulty"`
	Number          uint64           `json:"number"`
	GasLimit        uint64           `json:"gasLimit"`
	GasUsed         uint64           `json:"gasUsed"`
	Time            uint64           `json:"timestamp"`
	Extra           string           `json:"extraData"`
	MixDigest       string           `json:"mixHash"`
	Nonce           uint64           `json:"nonce"`
	Transactions    []RPCTransaction `json:"transactions"`
	TotalDifficulty uint64           `json:"totalDifficulty"`
	Signer          string           `json:"signer"`
}

func mapToStruct(m map[string]interface{}, s interface{}) {
	v := reflect.ValueOf(s).Elem()
	numFields := v.NumField()
	for i := 0; i < numFields; i++ {
		field := v.Field(i)
		jsonName := v.Type().Field(i).Tag.Get("json")
		if value, ok := m[jsonName]; ok {
			switch x := value.(type) {
			case string:
				switch field.Kind() {
				case reflect.String:
					field.SetString(x)
				case reflect.Uint64, reflect.Uint:
					if uintValue, err := strconv.ParseUint(x, 0, 64); err == nil {
						field.SetUint(uintValue)
					}
				case reflect.Int64, reflect.Int:
					if intValue, err := strconv.ParseInt(x, 0, 64); err == nil {
						field.SetInt(intValue)
					}
				}
			}
		}
	}
}

func (block *Block) UnmarshalJSON(b []byte) error {
	var m map[string]interface{}
	if err := json.Unmarshal(b, &m); err != nil {
		return err
	}
	mapToStruct(m, block)
	//v := reflect.ValueOf(block).Elem()
	//numFields := v.NumField()
	//for i := 0; i < numFields; i++ {
	//	field := v.Field(i)
	//	jsonName := v.Type().Field(i).Tag.Get("json")
	//	if value, ok := m[jsonName]; ok {
	//		switch x := value.(type) {
	//		case string:
	//			switch field.Kind() {
	//			case reflect.String:
	//				field.SetString(x)
	//			case reflect.Uint64:
	//				if uintValue, err := strconv.ParseUint(x, 0, 64); err == nil {
	//					field.SetUint(uintValue)
	//				}
	//			}
	//		}
	//	}
	//}
	for _, x := range m["transactions"].([]interface{}) {
		t := new(RPCTransaction)
		m := x.(map[string]interface{})
		mapToStruct(m, t)
		v, _ := new(big.Int).SetString(m["v"].(string), 0)
		r, _ := new(big.Int).SetString(m["r"].(string), 0)
		s, _ := new(big.Int).SetString(m["s"].(string), 0)
		t.V = (*BigInt)(v)
		t.R = (*BigInt)(r)
		t.S = (*BigInt)(s)
		fmt.Println(t.R, t.S, t.V)
		block.Transactions = append(block.Transactions, *t)
	}
	return nil
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 (node *Node) HeaderByNumber(blockNumber int64) types.Header {
	var (
		number string
		resp   types.Header
	)
	if blockNumber < 0 {
		number = Latest
	} else {
		number = fmt.Sprintf("0x%x", blockNumber)
	}
	node.Call(&resp, "eth_getBlockByNumber", number, true)
func (node *Node) BlockByNumber(blockNumber int64) (block Block) {
	var (
		number string
	)
	if blockNumber < 0 {
		number = Latest
	} else {
		number = fmt.Sprintf("0x%x", blockNumber)
	node.Call(&block, "eth_getBlockByNumber", number, true)
	return
//func (node *Node) BlockxByNumber(blockNumber int64) (block Block) {
//	ethClient := ethclient.NewClient((*rpc.Client)(node))
//	b, err := ethClient.BlockByNumber(context.Background(), big.NewInt(blockNumber))
//	h := b.Header()
//	Check(err)
//	block.ParentHash = h.ParentHash
//	block.Coinbase = h.Coinbase
//	block.Root = h.Root
//	block.TxHash = h.TxHash
//	block.ReceiptHash = h.ReceiptHash
//	block.Bloom = h.Bloom
//	block.Difficulty = h.Difficulty
//	block.Number = h.Number
//	block.GasLimit = h.GasLimit
//	block.GasUsed = h.GasUsed
//	block.Time = h.Time
//	block.Extra = h.Extra
//	block.MixDigest = h.MixDigest
//	block.Nonce = h.Nonce
//	block.Transactions = b.Transactions()
//	block.Signer, err = GetSigner(h)
//	block.TotalDifficulty = b.DeprecatedTd()
//	return block
//}

func (node *Node) BlockSigner(blockNumber int64) (signer string) {
	header := node.HeaderByNumber(blockNumber)
	signer, err := GetSigner(&header)
	Check(err)
	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 {
		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) {
	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, _ := GetSigner(&block)
		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()
}