package bfa import ( . "../clique" . "../util" "bytes" "encoding/hex" "errors" "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" "math/big" "sort" "strconv" ) type Node rpc.Client 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 } type Block struct { Header 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"` } const ( 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() uint64 { var peerCount Uint64 node.Call(&peerCount, "net_peerCount") return uint64(peerCount) } func (node *Node) BalanceAtBlock(account common.Address, blockNumber int64) *big.Int { var balance BigInt node.Call(&balance, "eth_getBalance", account, hexBlockNumber(blockNumber)) return (*big.Int)(&balance) } func (node *Node) Balance(account common.Address) *big.Int { return node.BalanceAtBlock(account, -1) } func (node *Node) Coinbase() (coinbase string, err error) { defer func() { 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 } else { blockNumber = "0x" + strconv.FormatInt(number, 16) } return } 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 Header) { node.Call(&header, "eth_getBlockByNumber", hexBlockNumber(blockNumber), true) return } func (node *Node) BlockByNumber(blockNumber int64) (block Block) { node.Call(&block, "eth_getBlockByNumber", hexBlockNumber(blockNumber), true) block.setSigner() return } 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) { node.Call(&snapshot, "clique_getSnapshot", hexBlockNumber(blockNumber)) return } func (node *Node) Sealers() (signers []string) { node.Call(&signers, "clique_getSigners", nil) return } func (node *Node) NodeInfo() (nodeInfo p2p.NodeInfo) { node.Call(&nodeInfo, "admin_nodeInfo", nil) return } func (node *Node) SealersAtBlock(blockNumber int64) (signers []string) { if blockNumber == -1 { return node.Sealers() } node.Call(&signers, "clique_getSigners", Int64ToHex(blockNumber)) return } 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) IsSealerAtBlock(address string, blockNumber int64) bool { if address == Self { var err error if address, err = node.Coinbase(); err != nil { return false } } return Contains(node.SealersAtBlock(blockNumber), 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(common.HexToAddress(address), blockNumber), } } notSeen := int64(len(status)) block := node.HeaderByNumber(blockNumber) blockNumber = block.BlockNumber().Int64() until := Max(1, blockNumber-5*notSeen) for notSeen > 0 { 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) } return status } func Dial(url string) (*Node, error) { client, err := rpc.Dial(url) return (*Node)(client), err } func (node *Node) Close() { (*rpc.Client)(node).Close() }