diff --git a/bfa_client/Makefile b/bfa_client/Makefile index 77f6e402f22cf55142936801e2a3c10114eb9838..ba58e52f67380c78fba0fd17c5430803bba81b42 100644 --- a/bfa_client/Makefile +++ b/bfa_client/Makefile @@ -1,4 +1,4 @@ -SRC:=src/client/bfa_client.go src/bfa/node.go src/util/util.go src/clique/clique.go +SRC:=src/client/bfa_client.go src/bfa/node.go src/util/util.go src/bfa/clique.go src/bfa/block.go all: bin/bfa_client diff --git a/bfa_client/src/bfa/block.go b/bfa_client/src/bfa/block.go new file mode 100644 index 0000000000000000000000000000000000000000..248ebf939c9c821104614d5b38242861ce073546 --- /dev/null +++ b/bfa_client/src/bfa/block.go @@ -0,0 +1,148 @@ +package bfa + +import ( + "../util" + "bytes" + "encoding/hex" + "errors" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto/sha3" + "github.com/ethereum/go-ethereum/rlp" + "math/big" + "strconv" +) + +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) Int64() int64 { + return (*big.Int)(bigInt).Int64() +} + +func (bigInt *BigInt) Uint64() uint64 { + return (*big.Int)(bigInt).Uint64() +} + +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 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) 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 + h.Root = header.Root + h.TxHash = header.TxHash + h.ReceiptHash = header.ReceiptHash + h.Bloom = header.Bloom + h.Difficulty = (*big.Int)(header.Difficulty) + h.Number = (*big.Int)(header.Number) + h.GasLimit = uint64(header.GasLimit) + h.GasUsed = uint64(header.GasUsed) + h.Time = (*big.Int)(header.Time) + h.Extra = header.Extra + h.MixDigest = header.MixDigest + h.Nonce = header.Nonce + return &h +} + +func (header *Header) GetSigner() common.Address { + return getSigner(header.ethHeader()) +} + +func (header *Header) GetHash() (h common.Hash) { + hw := sha3.NewKeccak256() + _ = rlp.Encode(hw, header.ethHeader()) + hw.Sum(h[:0]) + return h +} + +type Block struct { + Header + Transactions []RPCTransaction `json:"transactions"` + TotalDifficulty Uint64 `json:"totalDifficulty"` + Signer common.Address `json:"signer"` + Hash common.Hash `json:"hash"` +} diff --git a/bfa_client/src/bfa/clique.go b/bfa_client/src/bfa/clique.go new file mode 100644 index 0000000000000000000000000000000000000000..af6dc84a154039b615245a5e4feca40e14ca9655 --- /dev/null +++ b/bfa_client/src/bfa/clique.go @@ -0,0 +1,46 @@ +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/crypto/sha3" + "github.com/ethereum/go-ethereum/rlp" +) + +func sigHash(header *types.Header) (hash common.Hash) { + hasher := sha3.NewKeccak256() + + _ = rlp.Encode(hasher, []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, + }) + hasher.Sum(hash[:0]) + return hash +} + +func getSigner(header *types.Header) (address common.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[:], crypto.Keccak256(pubkey[1:])[12:]) + return +} diff --git a/bfa_client/src/bfa/node.go b/bfa_client/src/bfa/node.go index c0b66fe304321761d796392aeaf22f71e0125bce..23f6c084640154cd58c3c390b3d02c488e63519a 100644 --- a/bfa_client/src/bfa/node.go +++ b/bfa_client/src/bfa/node.go @@ -1,15 +1,10 @@ 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" @@ -19,183 +14,6 @@ import ( 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 @@ -311,13 +129,26 @@ func (node *Node) BlockNumberInRange(number int64) (blockNumber int64) { } func (node *Node) HeaderByNumber(blockNumber int64) (header Header) { - node.Call(&header, "eth_getBlockByNumber", hexBlockNumber(blockNumber), true) + 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.setSigner() + 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 } @@ -326,8 +157,7 @@ func (node *Node) BlockSigner(blockNumber int64) (signer common.Address) { return } header := node.HeaderByNumber(blockNumber) - signer = GetSigner(&header) - return + return header.GetSigner() } func (node *Node) GetSnapshot() (snapshot Snapshot) { @@ -435,14 +265,14 @@ func (node *Node) SealersStatus(blockNumber int64) (status map[string]*SealerSta } notSeen := int64(len(status)) block := node.HeaderByNumber(blockNumber) - blockNumber = block.BlockNumber().Int64() + blockNumber = block.Number.Int64() until := Max(1, blockNumber-5*notSeen) for notSeen > 0 { - signer := BytesToHex(GetSigner(&block).Bytes()) + signer := BytesToHex(block.GetSigner().Bytes()) if status[signer].LastBlock == 0 { - status[signer].LastBlock = block.BlockNumber().Int64() - status[signer].Time = block.Timestamp().Uint64() - status[signer].Difficulty = block.BlockDifficulty().Uint64() + status[signer].LastBlock = block.Number.Int64() + status[signer].Time = block.Time.Uint64() + status[signer].Difficulty = block.Difficulty.Uint64() notSeen-- } if blockNumber == until { diff --git a/bfa_client/src/bfa/node_test.go b/bfa_client/src/bfa/node_test.go new file mode 100644 index 0000000000000000000000000000000000000000..0133db642fee59af20c38ceb50e2eb4a7f04e32b --- /dev/null +++ b/bfa_client/src/bfa/node_test.go @@ -0,0 +1,181 @@ +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()) + +} diff --git a/bfa_client/src/client/bfa_client.go b/bfa_client/src/client/bfa_client.go index 7be848fbce2bda9d1e0f13052f07c381cae87f74..eac8434165334385cd9b04c6ebdc34e54bcea9de 100644 --- a/bfa_client/src/client/bfa_client.go +++ b/bfa_client/src/client/bfa_client.go @@ -339,9 +339,9 @@ func propose() { isSealer := util.Contains(votes.Signers, address) switch { // address is not in a proposal, we only allow removing signers or adding non signers case isSealer && authorize: - util.Error("'%v' ya es un sellador", address) + util.Error("'%v' ya es un sellador\n", address) case !isSealer && !authorize: - util.Error("'%v' no es un sellador", address) + util.Error("'%v' no es un sellador\n", address) } } node.Propose(address, authorize) diff --git a/bfa_client/src/clique/clique.go b/bfa_client/src/clique/clique.go deleted file mode 100644 index fbc159e531ec564ee614f910500b482de35c2754..0000000000000000000000000000000000000000 --- a/bfa_client/src/clique/clique.go +++ /dev/null @@ -1,65 +0,0 @@ -package clique - -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/crypto/sha3" - "github.com/ethereum/go-ethereum/rlp" - "math/big" -) - -type BlockHeader interface { - ParentHsh() common.Hash - UncleHsh() common.Hash - Miner() common.Address - RootHsh() common.Hash - TxHsh() common.Hash - ReceiptHsh() common.Hash - LogsBloom() types.Bloom - BlockDifficulty() *big.Int - BlockNumber() *big.Int - MaxGas() uint64 - Gas() uint64 - Timestamp() *big.Int - ExtraWithoutSignature() []byte - MixDigestHsh() common.Hash - BlockNonce() types.BlockNonce - Signature() []byte -} - -func sigHash(header BlockHeader) (hash common.Hash) { - hasher := sha3.NewKeccak256() - _ = rlp.Encode(hasher, []interface{}{ - header.ParentHsh(), - header.UncleHsh(), - header.Miner(), - header.RootHsh(), - header.TxHsh(), - header.ReceiptHsh(), - header.LogsBloom(), - header.BlockDifficulty(), - header.BlockNumber(), - header.MaxGas(), - header.Gas(), - header.Timestamp(), - header.ExtraWithoutSignature(), - header.MixDigestHsh(), - header.BlockNonce(), - }) - hasher.Sum(hash[:0]) - return hash -} - -func GetSigner(header BlockHeader) (address common.Address) { - if header.BlockNumber().Int64() == 0 { // Genesis block has no signers, we return an empty address - return - } - hash := sigHash(header).Bytes() - pubkey, err := crypto.Ecrecover(hash, header.Signature()) - if err != nil { - panic(err) - } - copy(address[:], crypto.Keccak256(pubkey[1:])[12:]) - return -} diff --git a/bfa_client/src/util/util_test.go b/bfa_client/src/util/util_test.go new file mode 100644 index 0000000000000000000000000000000000000000..5de70e7831f7dc5f2d635b0be60ed57d0b666f53 --- /dev/null +++ b/bfa_client/src/util/util_test.go @@ -0,0 +1,41 @@ +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 IsAddress(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++ { + _ = IsAddress(address) + } + }) + } +}