Skip to content
Snippets Groups Projects
Commit b8f583d3 authored by Miguel Montes's avatar Miguel Montes
Browse files

Versión modificada de bfa_client

parents
No related branches found
No related tags found
No related merge requests found
bin/
This diff is collapsed.
go.mod 0 → 100644
go.sum 0 → 100644
This diff is collapsed.
package bfa
import (
"github.com/ethereum/go-ethereum/common"
"sort"
)
type Address struct{ common.Address }
func (address Address) MarshalText() ([]byte, error) {
return []byte(address.Hex()), nil
}
func (a Address) LessThan(b Address) bool {
for i, x := range a.Address {
if x >= b.Address[i] {
return false
}
}
return true
}
func (address Address) In(slice []Address) bool {
for _, x := range slice {
if x == address {
return true
}
}
return false
}
func SortAddresses(slice []Address) {
sort.Slice(slice, func(i int, j int) bool { return slice[i].LessThan(slice[j]) })
}
func HexToAddress(s string) (address Address) {
address = Address{common.HexToAddress(s)}
return
}
package bfa
import (
"bytes"
"encoding/hex"
"errors"
"math/big"
"strconv"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"
"gitlab.bfa.ar/miguel/bfa/internal/util"
)
type BigInt struct {
*big.Int
}
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) IsZero() bool {
return bigInt.BitLen() == 0
}
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) {
util.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 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 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 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 Uint64 `json:"timestamp"`
Extra Bytes `json:"extraData"`
MixDigest common.Hash `json:"mixHash"`
Nonce types.BlockNonce `json:"nonce"`
}
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) ethHeader() *types.Header {
var h types.Header
h.ParentHash = header.ParentHash
h.UncleHash = header.UncleHash
h.Coinbase = header.Coinbase.Address
h.Root = header.Root
h.TxHash = header.TxHash
h.ReceiptHash = header.ReceiptHash
h.Bloom = header.Bloom
h.Difficulty = header.Difficulty.Int
h.Number = header.Number.Int
h.GasLimit = uint64(header.GasLimit)
h.GasUsed = uint64(header.GasUsed)
h.Time = uint64(header.Time)
h.Extra = header.Extra
h.MixDigest = header.MixDigest
h.Nonce = header.Nonce
return &h
}
func (header *Header) GetSigner() Address {
return getSigner(header.ethHeader())
}
func (header *Header) GetHash() (h common.Hash) {
headerBytes, _ := rlp.EncodeToBytes(header.ethHeader())
return crypto.Keccak256Hash(headerBytes)
}
type Block struct {
Header
Transactions []RPCTransaction `json:"transactions"`
TotalDifficulty Uint64 `json:"totalDifficulty"`
Signer Address `json:"signer"`
Hash common.Hash `json:"hash"`
}
package bfa
import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"
)
func sigHash(header *types.Header) (hash common.Hash) {
headerBytes, _ := rlp.EncodeToBytes([]interface{}{
header.ParentHash,
header.UncleHash,
header.Coinbase,
header.Root,
header.TxHash,
header.ReceiptHash,
header.Bloom,
header.Difficulty,
header.Number,
header.GasLimit,
header.GasUsed,
header.Time,
header.Extra[:len(header.Extra)-65], // Yes, this will panic if extra is too short
header.MixDigest,
header.Nonce,
})
return crypto.Keccak256Hash(headerBytes)
}
func getSigner(header *types.Header) (address Address) {
if header.Number.Int64() == 0 { // Genesis block has no signers, we return an empty address
return
}
hash := sigHash(header).Bytes()
pubkey, err := crypto.Ecrecover(hash, header.Extra[len(header.Extra)-65:])
if err != nil {
panic(err)
}
copy(address.Address[:], crypto.Keccak256(pubkey[1:])[12:])
return
}
package bfa
import (
"strconv"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/consensus/clique"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/rpc"
. "gitlab.bfa.ar/miguel/bfa/internal/util"
)
type Node rpc.Client
type Network int
type Genesis string
type Vote struct {
Signer Address `json:"signer"` // Authorized signer that cast this vote
Block uint64 `json:"block"` // Block number the vote was cast in (expire old votes)
Address Address `json:"address"` // Account being voted on to change its authorization
Authorize bool `json:"authorize"` // Whether to authorize or deauthorize the voted account
}
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[Address]struct{} `json:"signers"` // Set of authorized signers at this moment
Recents map[uint64]Address `json:"recents"` // Set of recent signers for spam protections
Votes []Vote `json:"votes"` // List of votes cast in chronological order
Tally map[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 []Address `json:"proposals"` // List of proposals being voted
Signers []Address `json:"signers"` // List of authorized signers at this moment
Tally map[Address]*Tally `json:"tally"` // Count of positive, negative and empty votes for a proposal
Votes map[Address]map[Address]*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 *BigInt `json:"balance"`
}
const (
Latest = "latest"
MainNetwork Network = 47525974938
MainGenesis Genesis = "0xe0e26f415af00fe32e1e8d94336355db43a78d9f59eb70910172b61fb670d99e"
TestNetwork Network = 55555000000
TestGenesis Genesis = "0x67e3e0fe207bd536875c14293132029b2cbf72aeb47afa865c2262293bca7d58"
SealerRounds = 2
)
func (network Network) String() string {
switch network {
case MainNetwork:
return "main"
case TestNetwork:
return "test"
default:
return "unknown"
}
}
func (genesis Genesis) String() string {
switch genesis {
case MainGenesis:
return "main"
case TestGenesis:
return "test"
default:
return "unknown"
}
}
var Self = Address{} // Empty address to represent the address of the caller
func (node *Node) Call(result interface{}, method string, args ...interface{}) {
Check((*rpc.Client)(node).Call(result, method, args...))
}
func (node *Node) CallWithError(result interface{}, method string, args ...interface{}) error {
return (*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) GasPrice() (gp int64) {
node.Call(&gp, "eth_gasPrice")
return gp
}
func (node *Node) Accounts() (accounts []Address) {
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 Address, blockNumber int64) *BigInt {
var balance BigInt
node.Call(&balance, "eth_getBalance", account, hexBlockNumber(blockNumber))
return &balance
}
func (node *Node) Balance(account Address) *BigInt {
return node.BalanceAtBlock(account, -1)
}
func (node *Node) Coinbase() (address Address, err error) {
err = node.CallWithError(&address, "eth_coinbase")
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), false)
return
}
func (node *Node) HeaderByHash(hash common.Hash) (header Header) {
node.Call(&header, "eth_getBlockByHash", hash, false)
return
}
func (node *Node) BlockByNumber(blockNumber int64) (block Block) {
node.Call(&block, "eth_getBlockByNumber", hexBlockNumber(blockNumber), true)
block.Signer = block.GetSigner()
block.Hash = block.GetHash()
return
}
func (node *Node) BlockByHash(hash common.Hash) (block Block) {
node.Call(&block, "eth_getBlockByHash", hash, true)
block.Signer = block.GetSigner()
block.Hash = block.GetHash()
return
}
func (node *Node) BlockTransactionCount(blockNumber int64) (count int64) {
var num hexutil.Uint64
node.Call(&num, "eth_getBlockTransactionCountByNumber", hexBlockNumber(blockNumber))
return int64(num)
}
func (node *Node) BlockSigner(blockNumber int64) (signer Address) {
if blockNumber == 0 { // we return an empty signer for genesis block
return
}
header := node.HeaderByNumber(blockNumber)
return header.GetSigner()
}
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 []Address) {
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 []Address) {
if blockNumber == -1 {
return node.Sealers()
}
node.Call(&signers, "clique_getSigners", Int64ToHex(blockNumber))
return
}
func (node *Node) IsSealer(address Address) bool {
if address == Self {
var (
err error
)
if address, err = node.Coinbase(); err != nil {
return false
}
}
return address.In(node.Sealers())
}
func (node *Node) IsSealerAtBlock(address Address, blockNumber int64) bool {
if address == Self {
var (
err error
)
if address, err = node.Coinbase(); err != nil {
return false
}
}
return address.In(node.SealersAtBlock(blockNumber))
}
func (node *Node) Propose(address Address, vote bool) {
node.Call(nil, "clique_propose", address.String(), 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, signer)
}
SortAddresses(votes.Signers)
for proposal := range snapshot.Tally {
votes.Proposals = append(votes.Proposals, proposal)
}
SortAddresses(votes.Proposals)
votes.Votes = make(map[Address]map[Address]*bool)
votes.Tally = make(map[Address]*Tally)
for _, v := range snapshot.Votes {
proposal := v.Address
signer := v.Signer
if votes.Votes[proposal] == nil {
votes.Votes[proposal] = make(map[Address]*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) LastBlockSignedBy(address Address, upperLimit int64) (blockNumber int64) {
if upperLimit == 0 || !node.IsSealer(address) {
return
}
for i := 0; i < 5; i++ {
snapshot := node.SnapshotAtBlock(upperLimit)
for number, sealer := range snapshot.Recents {
if sealer == address {
return int64(number)
}
}
upperLimit = int64(snapshot.Number) - int64((len(snapshot.Recents)))
if upperLimit <= 0 {
return
}
}
return
}
func (node *Node) SealersStatus(blockNumber int64) (status map[Address]*SealerStatus) {
if blockNumber == 0 { // Genesis block doesn't have signer
return
}
status = make(map[Address]*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-SealerRounds*notSeen)
for notSeen > 0 {
signer := block.GetSigner()
if status[signer].LastBlock == 0 {
status[signer].LastBlock = block.Number.Int64()
status[signer].Time = uint64(block.Time)
status[signer].Difficulty = block.Difficulty.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()
}
func hexBlockNumber(number int64) (blockNumber string) {
if number == -1 {
blockNumber = Latest
} else {
blockNumber = "0x" + strconv.FormatInt(number, 16)
}
return
}
package bfa
import (
"flag"
"github.com/ethereum/go-ethereum/common"
"log"
"os"
"testing"
)
const (
NumBlocks int64 = 1000
)
var (
node *Node
numbers map[string]int64
hashes map[string][]common.Hash
)
func BenchmarkBlockByNumber(b *testing.B) {
for r, base := range numbers {
b.Run(r, func(b *testing.B) {
for i := int64(0); i < int64(b.N); i++ {
_ = node.BlockByNumber(base + i%NumBlocks)
}
})
}
}
func BenchmarkHeaderByNumber(b *testing.B) {
for r, base := range numbers {
b.Run(r, func(b *testing.B) {
for i := int64(0); i < int64(b.N); i++ {
_ = node.HeaderByNumber(base + i%NumBlocks)
}
})
}
}
func BenchmarkBlockByHash(b *testing.B) {
for r := range numbers {
b.Run(r, func(b *testing.B) {
for i := int64(0); i < int64(b.N); i++ {
_ = node.BlockByHash(hashes[r][i%NumBlocks])
}
})
}
}
func BenchmarkHeaderByHash(b *testing.B) {
for r := range numbers {
b.Run(r, func(b *testing.B) {
for i := int64(0); i < int64(b.N); i++ {
_ = node.HeaderByHash(hashes[r][i%NumBlocks])
}
})
}
}
func (node *Node) getBlocksByNumber(last int64, n int64) int64 {
block := node.BlockByNumber(last)
for i := int64(0); i < n; i++ {
block = node.BlockByNumber(block.Number.Int64() - 1)
}
return block.Number.Int64()
}
func (node *Node) getBlocksByHash(last int64, n int64) int64 {
block := node.BlockByNumber(last)
for i := int64(0); i < n; i++ {
block = node.BlockByHash(block.ParentHash)
}
return block.Number.Int64()
}
func (node *Node) getHeadersByNumber(last int64, n int64) int64 {
header := node.HeaderByNumber(last)
for i := int64(0); i < n; i++ {
header = node.HeaderByNumber(header.Number.Int64() - 1)
}
return header.Number.Int64()
}
func (node *Node) getHeadersByHash(last int64, n int64) int64 {
header := node.HeaderByNumber(last)
for i := int64(0); i < n; i++ {
header = node.HeaderByHash(header.ParentHash)
}
return header.Number.Int64()
}
func TestBlockGetters(t *testing.T) {
latest := node.BlockNumber()
if latest < NumBlocks {
t.Skip("No hay suficientes bloques")
}
t.Run("BlockByNumber", func(t *testing.T) {
if node.getBlocksByNumber(latest, NumBlocks) != latest-NumBlocks {
t.Fail()
}
})
t.Run("BlockByHash", func(t *testing.T) {
if node.getBlocksByHash(latest, NumBlocks) != latest-NumBlocks {
t.Fail()
}
})
}
func TestHeaderGetters(t *testing.T) {
latest := node.BlockNumber()
if latest < NumBlocks {
t.Skip("No hay suficientes bloques")
}
t.Run("HeaderByNumber", func(t *testing.T) {
if node.getHeadersByNumber(latest, NumBlocks) != latest-NumBlocks {
t.Fail()
}
})
t.Run("HeaderByHash", func(t *testing.T) {
if node.getHeadersByHash(latest, NumBlocks) != latest-NumBlocks {
t.Fail()
}
})
}
func BenchmarkBlockGetters(b *testing.B) {
latest := node.BlockNumber()
if latest < NumBlocks {
b.Skip("No hay suficientes bloques")
}
b.Run("BlockByNumber", func(b *testing.B) {
for i := int64(0); i < int64(b.N); i++ {
_ = node.getBlocksByNumber(latest, NumBlocks)
}
})
b.Run("BlockByHash", func(b *testing.B) {
for i := int64(0); i < int64(b.N); i++ {
_ = node.getBlocksByHash(latest, NumBlocks)
}
})
}
func BenchmarkHeaderGetters(b *testing.B) {
latest := node.BlockNumber()
if latest < NumBlocks {
b.Skip("No hay suficientes bloques")
}
b.Run("HeaderByNumber", func(b *testing.B) {
for i := int64(0); i < int64(b.N); i++ {
_ = node.getHeadersByNumber(latest, NumBlocks)
}
})
b.Run("HeaderByHash", func(b *testing.B) {
for i := int64(0); i < int64(b.N); i++ {
_ = node.getHeadersByHash(latest, NumBlocks)
}
})
}
func TestMain(m *testing.M) {
flag.Parse()
var err error
if node, err = Dial("http://localhost:8545"); err != nil {
log.Fatal(err)
}
latest := node.BlockNumber()
if latest < 3*NumBlocks {
log.Fatal("No hay suficientes bloques como para correr benchmarks")
}
numbers = map[string]int64{"lo": 1, "mid": latest / 2, "hi": latest - NumBlocks}
hashes = make(map[string][]common.Hash)
for r, base := range numbers {
for i := int64(0); i < NumBlocks; i++ {
header := node.HeaderByNumber(base + 1)
hashes[r] = append(hashes[r], header.GetHash())
}
}
os.Exit(m.Run())
}
package util
import (
"encoding/json"
"fmt"
"github.com/ethereum/go-ethereum/common"
"log"
"os"
"runtime"
"strconv"
"strings"
)
func Contains(slice []string, s string) bool {
for _, x := range slice {
if x == s {
return true
}
}
return false
}
func Error(format string, args ...interface{}) {
_, _ = fmt.Fprintf(os.Stderr, format, args...)
os.Exit(1)
}
func Ensure(condition bool, format string, args ...interface{}) {
if !condition {
Error(format, args...)
}
}
func Check(err error) {
Ensure(err == nil, "%v\n", err)
}
func Int64ToHex(n int64) string {
return "0x" + strconv.FormatInt(n, 16)
}
func Require(condition bool, msg string) {
if !condition {
ptr, _, _, _ := runtime.Caller(1)
log.Panicf("%v in %v", msg, runtime.FuncForPC(ptr).Name())
}
}
func Min(a, b int64) int64 {
if a < b {
return a
}
return b
}
func Max(a, b int64) int64 {
if a > b {
return a
}
return b
}
func PrintJson(s interface{}) {
v, err := json.MarshalIndent(s, "", " ")
Check(err)
fmt.Println(string(v))
return
}
func isMixedCase(address string) bool {
return strings.ContainsAny(address, "abcdef") && strings.ContainsAny(address, "ABCDEF")
}
func IsValidAddress(address string) bool {
if !common.IsHexAddress(address) {
return false
}
if isMixedCase(address) {
return common.HexToAddress(address).Hex() == address
} else {
return true
}
}
package util
import (
"strings"
"testing"
)
var (
addresses = map[string]string{
"ValidReal": "0x46991ada2a2544468eb3673524641bf293f23ccc",
"ValidSeq": "0x000102030405060708090a0b0c0d0e0f10111213",
"ValidZero": "0x0000000000000000000000000000000000000000",
"InvalidNoPrefix": "0046991ada2a2544468eb3673524641bf293f23ccc",
"InvalidEmpty": "",
"InvalidLengthVeryShort": "0x",
"InvalidLengthShort": "0x46991ada2a2544468eb3673524641bf293f23cc",
"InvalidLengthLong": "0x46991ada2a2544468eb3673524641bf293f23ccc00",
"InvalidNoHexAtBeginning": "0xx6991ada2a2544468eb3673524641bf293f23ccc",
"InvalidNoHexAtEnd": "0x46991ada2a2544468eb3673524641bf293f23ccx",
}
)
func TestValidAddress(t *testing.T) {
for name, address := range addresses {
t.Run(name, func(t *testing.T) {
if IsValidAddress(address) != strings.HasPrefix(name, "Valid") {
t.Fail()
}
})
}
}
func BenchmarkIsAddressTrue(b *testing.B) {
for name, address := range addresses {
b.Run(name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = IsValidAddress(address)
}
})
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment