From 63434f6bc91e26167e47c89feb02fa1553193ea1 Mon Sep 17 00:00:00 2001 From: Rob Mulholand Date: Wed, 18 Jul 2018 15:59:40 -0500 Subject: [PATCH] Add tests for pkg/geth/blockchain - inject dependencies instead of initializing them in the constructor --- cmd/coldImport.go | 4 +- cmd/erc20.go | 20 ++- cmd/lightSync.go | 16 ++- cmd/sync.go | 26 +++- .../erc20_watcher/every_block/fetcher_test.go | 40 ++++-- integration_test/block_rewards_test.go | 33 ++++- integration_test/contract_test.go | 50 +++++-- integration_test/geth_blockchain_test.go | 34 +++-- pkg/core/client.go | 16 +++ pkg/fakes/data.go | 5 + .../{blockchain.go => mock_blockchain.go} | 30 ++-- pkg/fakes/mock_client.go | 130 ++++++++++++++++++ pkg/geth/blockchain.go | 60 ++++---- pkg/geth/blockchain_test.go | 125 +++++++++++++++++ pkg/geth/client/client.go | 33 +++++ pkg/history/block_validator_test.go | 4 +- pkg/history/header_validator_test.go | 2 +- pkg/history/populate_blocks_test.go | 10 +- pkg/history/populate_headers_test.go | 4 +- pkg/history/validation_window_test.go | 2 +- 20 files changed, 538 insertions(+), 106 deletions(-) create mode 100644 pkg/core/client.go create mode 100644 pkg/fakes/data.go rename pkg/fakes/{blockchain.go => mock_blockchain.go} (63%) create mode 100644 pkg/fakes/mock_client.go create mode 100644 pkg/geth/blockchain_test.go create mode 100644 pkg/geth/client/client.go diff --git a/cmd/coldImport.go b/cmd/coldImport.go index ee00e04d..d7265cb5 100644 --- a/cmd/coldImport.go +++ b/cmd/coldImport.go @@ -82,8 +82,8 @@ func coldImport() { // init cold importer deps blockRepository := repositories.NewBlockRepository(&pgDB) receiptRepository := repositories.ReceiptRepository{DB: &pgDB} - transactionconverter := cold_db.NewColdDbTransactionConverter() - blockConverter := vulcCommon.NewBlockConverter(transactionconverter) + transactionConverter := cold_db.NewColdDbTransactionConverter() + blockConverter := vulcCommon.NewBlockConverter(transactionConverter) // init and execute cold importer coldImporter := cold_import.NewColdImporter(ethDB, blockRepository, receiptRepository, blockConverter) diff --git a/cmd/erc20.go b/cmd/erc20.go index e1d16531..95f78ae8 100644 --- a/cmd/erc20.go +++ b/cmd/erc20.go @@ -15,11 +15,16 @@ package cmd import ( + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/rpc" "github.com/spf13/cobra" "github.com/vulcanize/vulcanizedb/examples/erc20_watcher/every_block" "github.com/vulcanize/vulcanizedb/libraries/shared" "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres" "github.com/vulcanize/vulcanizedb/pkg/geth" + "github.com/vulcanize/vulcanizedb/pkg/geth/client" + rpc2 "github.com/vulcanize/vulcanizedb/pkg/geth/converters/rpc" + "github.com/vulcanize/vulcanizedb/pkg/geth/node" "log" "time" ) @@ -49,14 +54,23 @@ Expects an ethereum node to be running and requires a .toml config file: func watchERC20s() { ticker := time.NewTicker(5 * time.Second) defer ticker.Stop() - blockchain := geth.NewBlockChain(ipc) - db, err := postgres.NewDB(databaseConfig, blockchain.Node()) + rpcClient, err := rpc.Dial(ipc) + if err != nil { + log.Fatal(err) + } + ethClient := ethclient.NewClient(rpcClient) + client := client.NewClient(ethClient) + clientWrapper := node.ClientWrapper{ContextCaller: rpcClient, IPCPath: ipc} + node := node.MakeNode(clientWrapper) + transactionConverter := rpc2.NewRpcTransactionConverter(ethClient) + blockChain := geth.NewBlockChain(client, node, transactionConverter) + db, err := postgres.NewDB(databaseConfig, blockChain.Node()) if err != nil { log.Fatal("Failed to initialize database.") } watcher := shared.Watcher{ DB: *db, - Blockchain: blockchain, + Blockchain: blockChain, } watcher.AddTransformers(every_block.TransformerInitializers()) diff --git a/cmd/lightSync.go b/cmd/lightSync.go index f59beb5a..224e364f 100644 --- a/cmd/lightSync.go +++ b/cmd/lightSync.go @@ -15,11 +15,16 @@ package cmd import ( + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/rpc" "github.com/spf13/cobra" "github.com/vulcanize/vulcanizedb/pkg/core" "github.com/vulcanize/vulcanizedb/pkg/datastore" "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres/repositories" "github.com/vulcanize/vulcanizedb/pkg/geth" + "github.com/vulcanize/vulcanizedb/pkg/geth/client" + rpc2 "github.com/vulcanize/vulcanizedb/pkg/geth/converters/rpc" + "github.com/vulcanize/vulcanizedb/pkg/geth/node" "github.com/vulcanize/vulcanizedb/pkg/history" "github.com/vulcanize/vulcanizedb/utils" "log" @@ -63,7 +68,16 @@ func backFillAllHeaders(blockchain core.Blockchain, headerRepository datastore.H func lightSync() { ticker := time.NewTicker(pollingInterval) defer ticker.Stop() - blockChain := geth.NewBlockChain(ipc) + rpcClient, err := rpc.Dial(ipc) + if err != nil { + log.Fatal(err) + } + ethClient := ethclient.NewClient(rpcClient) + client := client.NewClient(ethClient) + clientWrapper := node.ClientWrapper{ContextCaller: rpcClient, IPCPath: ipc} + node := node.MakeNode(clientWrapper) + transactionConverter := rpc2.NewRpcTransactionConverter(ethClient) + blockChain := geth.NewBlockChain(client, node, transactionConverter) lastBlock := blockChain.LastBlock().Int64() if lastBlock == 0 { diff --git a/cmd/sync.go b/cmd/sync.go index 115c864f..1e958aa3 100644 --- a/cmd/sync.go +++ b/cmd/sync.go @@ -21,11 +21,16 @@ import ( "log" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/rpc" "github.com/spf13/cobra" "github.com/vulcanize/vulcanizedb/pkg/core" "github.com/vulcanize/vulcanizedb/pkg/datastore" "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres/repositories" "github.com/vulcanize/vulcanizedb/pkg/geth" + "github.com/vulcanize/vulcanizedb/pkg/geth/client" + rpc2 "github.com/vulcanize/vulcanizedb/pkg/geth/converters/rpc" + "github.com/vulcanize/vulcanizedb/pkg/geth/node" "github.com/vulcanize/vulcanizedb/pkg/history" "github.com/vulcanize/vulcanizedb/utils" ) @@ -72,9 +77,18 @@ func backFillAllBlocks(blockchain core.Blockchain, blockRepository datastore.Blo func sync() { ticker := time.NewTicker(pollingInterval) defer ticker.Stop() - blockchain := geth.NewBlockChain(ipc) + rpcClient, err := rpc.Dial(ipc) + if err != nil { + log.Fatal(err) + } + ethClient := ethclient.NewClient(rpcClient) + client := client.NewClient(ethClient) + clientWrapper := node.ClientWrapper{ContextCaller: rpcClient, IPCPath: ipc} + node := node.MakeNode(clientWrapper) + transactionConverter := rpc2.NewRpcTransactionConverter(ethClient) + blockChain := geth.NewBlockChain(client, node, transactionConverter) - lastBlock := blockchain.LastBlock().Int64() + lastBlock := blockChain.LastBlock().Int64() if lastBlock == 0 { log.Fatal("geth initial: state sync not finished") } @@ -82,11 +96,11 @@ func sync() { log.Fatal("starting block number > current block number") } - db := utils.LoadPostgres(databaseConfig, blockchain.Node()) + db := utils.LoadPostgres(databaseConfig, blockChain.Node()) blockRepository := repositories.NewBlockRepository(&db) - validator := history.NewBlockValidator(blockchain, blockRepository, validationWindow) + validator := history.NewBlockValidator(blockChain, blockRepository, validationWindow) missingBlocksPopulated := make(chan int) - go backFillAllBlocks(blockchain, blockRepository, missingBlocksPopulated, startingBlockNumber) + go backFillAllBlocks(blockChain, blockRepository, missingBlocksPopulated, startingBlockNumber) for { select { @@ -94,7 +108,7 @@ func sync() { window := validator.ValidateBlocks() window.Log(os.Stdout) case <-missingBlocksPopulated: - go backFillAllBlocks(blockchain, blockRepository, missingBlocksPopulated, startingBlockNumber) + go backFillAllBlocks(blockChain, blockRepository, missingBlocksPopulated, startingBlockNumber) } } } diff --git a/examples/erc20_watcher/every_block/fetcher_test.go b/examples/erc20_watcher/every_block/fetcher_test.go index 10b2ac65..f74e078c 100644 --- a/examples/erc20_watcher/every_block/fetcher_test.go +++ b/examples/erc20_watcher/every_block/fetcher_test.go @@ -15,29 +15,51 @@ package every_block_test import ( + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/rpc" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/vulcanize/vulcanizedb/examples/constants" "github.com/vulcanize/vulcanizedb/examples/erc20_watcher/every_block" "github.com/vulcanize/vulcanizedb/examples/mocks" "github.com/vulcanize/vulcanizedb/pkg/geth" + "github.com/vulcanize/vulcanizedb/pkg/geth/client" + rpc2 "github.com/vulcanize/vulcanizedb/pkg/geth/converters/rpc" + "github.com/vulcanize/vulcanizedb/pkg/geth/node" "math/big" ) var _ = Describe("ERC20 Fetcher", func() { blockNumber := int64(5502914) - infuraIPC := "https://mainnet.infura.io/J5Vd2fRtGsw0zZ0Ov3BL" - realBlockchain := geth.NewBlockChain(infuraIPC) - realFetcher := every_block.NewFetcher(realBlockchain) + var errorFetcher every_block.Fetcher + var realFetcher every_block.Fetcher + var testFetcher every_block.Fetcher + var fakeBlockchain *mocks.Blockchain + var testAbi string + var testContractAddress string - fakeBlockchain := &mocks.Blockchain{} - testFetcher := every_block.NewFetcher(fakeBlockchain) - testAbi := "testAbi" - testContractAddress := "testContractAddress" + BeforeEach(func() { + rpcClient, err := rpc.Dial(infuraIPC) + Expect(err).NotTo(HaveOccurred()) + ethClient := ethclient.NewClient(rpcClient) + blockChainClient := client.NewClient(ethClient) + clientWrapper := node.ClientWrapper{ + ContextCaller: rpcClient, + IPCPath: infuraIPC, + } + node := node.MakeNode(clientWrapper) + transactionConverter := rpc2.NewRpcTransactionConverter(ethClient) + realBlockChain := geth.NewBlockChain(blockChainClient, node, transactionConverter) + realFetcher = every_block.NewFetcher(realBlockChain) + fakeBlockchain = &mocks.Blockchain{} + testFetcher = every_block.NewFetcher(fakeBlockchain) + testAbi = "testAbi" + testContractAddress = "testContractAddress" - errorBlockchain := &mocks.FailureBlockchain{} - errorFetcher := every_block.NewFetcher(errorBlockchain) + errorBlockchain := &mocks.FailureBlockchain{} + errorFetcher = every_block.NewFetcher(errorBlockchain) + }) Describe("FetchSupplyOf", func() { It("fetches data from the blockchain with the correct arguments", func() { diff --git a/integration_test/block_rewards_test.go b/integration_test/block_rewards_test.go index 55b05bd9..ff4361ea 100644 --- a/integration_test/block_rewards_test.go +++ b/integration_test/block_rewards_test.go @@ -1,24 +1,49 @@ package integration import ( + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/rpc" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/vulcanize/vulcanizedb/pkg/geth" + "github.com/vulcanize/vulcanizedb/pkg/geth/client" + rpc2 "github.com/vulcanize/vulcanizedb/pkg/geth/converters/rpc" + "github.com/vulcanize/vulcanizedb/pkg/geth/node" "github.com/vulcanize/vulcanizedb/test_config" ) var _ = Describe("Rewards calculations", func() { It("calculates a block reward for a real block", func() { - blockchain := geth.NewBlockChain(test_config.InfuraClient.IPCPath) - block, err := blockchain.GetBlockByNumber(1071819) + rpcClient, err := rpc.Dial(test_config.InfuraClient.IPCPath) + Expect(err).NotTo(HaveOccurred()) + ethClient := ethclient.NewClient(rpcClient) + blockChainClient := client.NewClient(ethClient) + clientWrapper := node.ClientWrapper{ + ContextCaller: rpcClient, + IPCPath: test_config.InfuraClient.IPCPath, + } + node := node.MakeNode(clientWrapper) + transactionConverter := rpc2.NewRpcTransactionConverter(ethClient) + blockChain := geth.NewBlockChain(blockChainClient, node, transactionConverter) + block, err := blockChain.GetBlockByNumber(1071819) Expect(err).ToNot(HaveOccurred()) Expect(block.Reward).To(Equal(5.31355)) }) It("calculates an uncle reward for a real block", func() { - blockchain := geth.NewBlockChain(test_config.InfuraClient.IPCPath) - block, err := blockchain.GetBlockByNumber(1071819) + rpcClient, err := rpc.Dial(test_config.InfuraClient.IPCPath) + Expect(err).NotTo(HaveOccurred()) + ethClient := ethclient.NewClient(rpcClient) + blockChainClient := client.NewClient(ethClient) + clientWrapper := node.ClientWrapper{ + ContextCaller: rpcClient, + IPCPath: test_config.InfuraClient.IPCPath, + } + node := node.MakeNode(clientWrapper) + transactionConverter := rpc2.NewRpcTransactionConverter(ethClient) + blockChain := geth.NewBlockChain(blockChainClient, node, transactionConverter) + block, err := blockChain.GetBlockByNumber(1071819) Expect(err).ToNot(HaveOccurred()) Expect(block.UnclesReward).To(Equal(6.875)) }) diff --git a/integration_test/contract_test.go b/integration_test/contract_test.go index 8b55042d..ae07ada4 100644 --- a/integration_test/contract_test.go +++ b/integration_test/contract_test.go @@ -4,10 +4,15 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/rpc" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/vulcanize/vulcanizedb/pkg/core" "github.com/vulcanize/vulcanizedb/pkg/geth" + "github.com/vulcanize/vulcanizedb/pkg/geth/client" + rpc2 "github.com/vulcanize/vulcanizedb/pkg/geth/converters/rpc" + "github.com/vulcanize/vulcanizedb/pkg/geth/node" "github.com/vulcanize/vulcanizedb/pkg/geth/testing" "github.com/vulcanize/vulcanizedb/test_config" ) @@ -27,42 +32,69 @@ var _ = Describe("Reading contracts", func() { }, Index: 19, Data: "0x0000000000000000000000000000000000000000000000000c7d713b49da0000"} - blockchain := geth.NewBlockChain(test_config.InfuraClient.IPCPath) + rpcClient, err := rpc.Dial(test_config.InfuraClient.IPCPath) + Expect(err).NotTo(HaveOccurred()) + ethClient := ethclient.NewClient(rpcClient) + blockChainClient := client.NewClient(ethClient) + clientWrapper := node.ClientWrapper{ + ContextCaller: rpcClient, + IPCPath: test_config.InfuraClient.IPCPath, + } + node := node.MakeNode(clientWrapper) + transactionConverter := rpc2.NewRpcTransactionConverter(ethClient) + blockChain := geth.NewBlockChain(blockChainClient, node, transactionConverter) contract := testing.SampleContract() - logs, err := blockchain.GetLogs(contract, big.NewInt(4703824), nil) + logs, err := blockChain.GetLogs(contract, big.NewInt(4703824), nil) Expect(err).To(BeNil()) Expect(len(logs)).To(Equal(3)) Expect(logs[0]).To(Equal(expectedLogZero)) - }) It("returns and empty log array when no events for a given block / contract combo", func() { - blockchain := geth.NewBlockChain(test_config.InfuraClient.IPCPath) + rpcClient, err := rpc.Dial(test_config.InfuraClient.IPCPath) + Expect(err).NotTo(HaveOccurred()) + ethClient := ethclient.NewClient(rpcClient) + blockChainClient := client.NewClient(ethClient) + clientWrapper := node.ClientWrapper{ + ContextCaller: rpcClient, + IPCPath: test_config.InfuraClient.IPCPath, + } + node := node.MakeNode(clientWrapper) + transactionConverter := rpc2.NewRpcTransactionConverter(ethClient) + blockChain := geth.NewBlockChain(blockChainClient, node, transactionConverter) - logs, err := blockchain.GetLogs(core.Contract{Hash: "x123"}, big.NewInt(4703824), nil) + logs, err := blockChain.GetLogs(core.Contract{Hash: "x123"}, big.NewInt(4703824), nil) Expect(err).To(BeNil()) Expect(len(logs)).To(Equal(0)) }) - }) Describe("Fetching Contract data", func() { It("returns the correct attribute for a real contract", func() { - blockchain := geth.NewBlockChain(test_config.InfuraClient.IPCPath) + rpcClient, err := rpc.Dial(test_config.InfuraClient.IPCPath) + Expect(err).NotTo(HaveOccurred()) + ethClient := ethclient.NewClient(rpcClient) + blockChainClient := client.NewClient(ethClient) + clientWrapper := node.ClientWrapper{ + ContextCaller: rpcClient, + IPCPath: test_config.InfuraClient.IPCPath, + } + node := node.MakeNode(clientWrapper) + transactionConverter := rpc2.NewRpcTransactionConverter(ethClient) + blockChain := geth.NewBlockChain(blockChainClient, node, transactionConverter) contract := testing.SampleContract() var balance = new(big.Int) args := common.HexToHash("0xd26114cd6ee289accf82350c8d8487fedb8a0c07") - err := blockchain.FetchContractData(contract.Abi, "0xd26114cd6ee289accf82350c8d8487fedb8a0c07", "balanceOf", args, &balance, 5167471) + err = blockChain.FetchContractData(contract.Abi, "0xd26114cd6ee289accf82350c8d8487fedb8a0c07", "balanceOf", args, &balance, 5167471) Expect(err).NotTo(HaveOccurred()) expected := new(big.Int) expected.SetString("10897295492887612977137", 10) Expect(balance).To(Equal(expected)) }) }) - }) diff --git a/integration_test/geth_blockchain_test.go b/integration_test/geth_blockchain_test.go index bd8dafd3..91e57fd3 100644 --- a/integration_test/geth_blockchain_test.go +++ b/integration_test/geth_blockchain_test.go @@ -1,40 +1,54 @@ package integration_test import ( + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/rpc" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/vulcanize/vulcanizedb/pkg/core" "github.com/vulcanize/vulcanizedb/pkg/datastore/inmemory" "github.com/vulcanize/vulcanizedb/pkg/geth" + "github.com/vulcanize/vulcanizedb/pkg/geth/client" + rpc2 "github.com/vulcanize/vulcanizedb/pkg/geth/converters/rpc" + "github.com/vulcanize/vulcanizedb/pkg/geth/node" "github.com/vulcanize/vulcanizedb/pkg/history" "github.com/vulcanize/vulcanizedb/test_config" ) var _ = Describe("Reading from the Geth blockchain", func() { - - var blockchain *geth.BlockChain + var blockChain *geth.BlockChain var inMemory *inmemory.InMemory BeforeEach(func() { - blockchain = geth.NewBlockChain(test_config.InfuraClient.IPCPath) + rpcClient, err := rpc.Dial(test_config.InfuraClient.IPCPath) + Expect(err).NotTo(HaveOccurred()) + ethClient := ethclient.NewClient(rpcClient) + blockChainClient := client.NewClient(ethClient) + clientWrapper := node.ClientWrapper{ + ContextCaller: rpcClient, + IPCPath: test_config.InfuraClient.IPCPath, + } + node := node.MakeNode(clientWrapper) + transactionConverter := rpc2.NewRpcTransactionConverter(ethClient) + blockChain = geth.NewBlockChain(blockChainClient, node, transactionConverter) inMemory = inmemory.NewInMemory() }) It("reads two blocks", func(done Done) { blocks := &inmemory.BlockRepository{InMemory: inMemory} - lastBlock := blockchain.LastBlock() + lastBlock := blockChain.LastBlock() queriedBlocks := []int64{lastBlock.Int64() - 5, lastBlock.Int64() - 6} - history.RetrieveAndUpdateBlocks(blockchain, blocks, queriedBlocks) + history.RetrieveAndUpdateBlocks(blockChain, blocks, queriedBlocks) Expect(blocks.BlockCount()).To(Equal(2)) close(done) }, 30) It("retrieves the genesis block and first block", func(done Done) { - genesisBlock, err := blockchain.GetBlockByNumber(int64(0)) + genesisBlock, err := blockChain.GetBlockByNumber(int64(0)) Expect(err).ToNot(HaveOccurred()) - firstBlock, err := blockchain.GetBlockByNumber(int64(1)) + firstBlock, err := blockChain.GetBlockByNumber(int64(1)) Expect(err).ToNot(HaveOccurred()) - lastBlockNumber := blockchain.LastBlock() + lastBlockNumber := blockChain.LastBlock() Expect(genesisBlock.Number).To(Equal(int64(0))) Expect(firstBlock.Number).To(Equal(int64(1))) @@ -43,7 +57,7 @@ var _ = Describe("Reading from the Geth blockchain", func() { }, 15) It("retrieves the node info", func(done Done) { - node := blockchain.Node() + node := blockChain.Node() mainnetID := float64(1) Expect(node.GenesisBlock).ToNot(BeNil()) @@ -60,7 +74,7 @@ var _ = Describe("Reading from the Geth blockchain", func() { var blocks []core.Block n := 10 for i := 5327459; i > 5327459-n; i-- { - block, err := blockchain.GetBlockByNumber(int64(i)) + block, err := blockChain.GetBlockByNumber(int64(i)) Expect(err).ToNot(HaveOccurred()) blocks = append(blocks, block) } diff --git a/pkg/core/client.go b/pkg/core/client.go new file mode 100644 index 00000000..f628e71d --- /dev/null +++ b/pkg/core/client.go @@ -0,0 +1,16 @@ +package core + +import ( + "context" + "math/big" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/core/types" +) + +type Client interface { + BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) + CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) + FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) + HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) +} diff --git a/pkg/fakes/data.go b/pkg/fakes/data.go new file mode 100644 index 00000000..22f1b601 --- /dev/null +++ b/pkg/fakes/data.go @@ -0,0 +1,5 @@ +package fakes + +import "errors" + +var FakeError = errors.New("failed") diff --git a/pkg/fakes/blockchain.go b/pkg/fakes/mock_blockchain.go similarity index 63% rename from pkg/fakes/blockchain.go rename to pkg/fakes/mock_blockchain.go index 8032f3de..5229501c 100644 --- a/pkg/fakes/blockchain.go +++ b/pkg/fakes/mock_blockchain.go @@ -6,7 +6,7 @@ import ( "github.com/vulcanize/vulcanizedb/pkg/core" ) -type BlockChain struct { +type MockBlockChain struct { ContractReturnValue []byte WasToldToStop bool blocks map[int64]core.Block @@ -18,19 +18,19 @@ type BlockChain struct { node core.Node } -func (blockChain *BlockChain) GetHeaderByNumber(blockNumber int64) (core.Header, error) { +func (blockChain *MockBlockChain) GetHeaderByNumber(blockNumber int64) (core.Header, error) { return blockChain.headers[blockNumber], nil } -func (blockChain *BlockChain) FetchContractData(abiJSON string, address string, method string, methodArg interface{}, result interface{}, blockNumber int64) error { +func (blockChain *MockBlockChain) FetchContractData(abiJSON string, address string, method string, methodArg interface{}, result interface{}, blockNumber int64) error { panic("implement me") } -func (blockChain *BlockChain) CallContract(contractHash string, input []byte, blockNumber *big.Int) ([]byte, error) { +func (blockChain *MockBlockChain) CallContract(contractHash string, input []byte, blockNumber *big.Int) ([]byte, error) { return blockChain.ContractReturnValue, nil } -func (blockChain *BlockChain) LastBlock() *big.Int { +func (blockChain *MockBlockChain) LastBlock() *big.Int { var max int64 for blockNumber := range blockChain.blocks { if blockNumber > max { @@ -40,16 +40,16 @@ func (blockChain *BlockChain) LastBlock() *big.Int { return big.NewInt(max) } -func (blockChain *BlockChain) GetLogs(contract core.Contract, startingBlock *big.Int, endingBlock *big.Int) ([]core.Log, error) { +func (blockChain *MockBlockChain) GetLogs(contract core.Contract, startingBlock *big.Int, endingBlock *big.Int) ([]core.Log, error) { return blockChain.logs[contract.Hash], nil } -func (blockChain *BlockChain) Node() core.Node { +func (blockChain *MockBlockChain) Node() core.Node { return blockChain.node } -func NewBlockchain(err error) *BlockChain { - return &BlockChain{ +func NewMockBlockChain(err error) *MockBlockChain { + return &MockBlockChain{ blocks: make(map[int64]core.Block), logs: make(map[string][]core.Log), contractAttributes: make(map[string]map[string]string), @@ -58,17 +58,17 @@ func NewBlockchain(err error) *BlockChain { } } -func NewBlockchainWithBlocks(blocks []core.Block) *BlockChain { +func NewMockBlockChainWithBlocks(blocks []core.Block) *MockBlockChain { blockNumberToBlocks := make(map[int64]core.Block) for _, block := range blocks { blockNumberToBlocks[block.Number] = block } - return &BlockChain{ + return &MockBlockChain{ blocks: blockNumberToBlocks, } } -func NewBlockChainWithHeaders(headers []core.Header) *BlockChain { +func NewMockBlockChainWithHeaders(headers []core.Header) *MockBlockChain { // need to create blocks and headers so that LastBlock() will work in the mock // no reason to implement LastBlock() separately for headers since it checks // the last header in the Node's DB already @@ -78,20 +78,20 @@ func NewBlockChainWithHeaders(headers []core.Header) *BlockChain { memoryBlocks[header.BlockNumber] = core.Block{Number: header.BlockNumber} memoryHeaders[header.BlockNumber] = header } - return &BlockChain{ + return &MockBlockChain{ blocks: memoryBlocks, headers: memoryHeaders, } } -func (blockChain *BlockChain) GetBlockByNumber(blockNumber int64) (core.Block, error) { +func (blockChain *MockBlockChain) GetBlockByNumber(blockNumber int64) (core.Block, error) { if blockChain.err != nil { return core.Block{}, blockChain.err } return blockChain.blocks[blockNumber], nil } -func (blockChain *BlockChain) AddBlock(block core.Block) { +func (blockChain *MockBlockChain) AddBlock(block core.Block) { blockChain.blocks[block.Number] = block blockChain.blocksChannel <- block } diff --git a/pkg/fakes/mock_client.go b/pkg/fakes/mock_client.go new file mode 100644 index 00000000..109bda40 --- /dev/null +++ b/pkg/fakes/mock_client.go @@ -0,0 +1,130 @@ +package fakes + +import ( + "context" + "math/big" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/core/types" + . "github.com/onsi/gomega" +) + +type MockClient struct { + callContractErr error + callContractPassedContext context.Context + callContractPassedMsg ethereum.CallMsg + callContractPassedNumber *big.Int + callContractReturnBytes []byte + blockByNumberErr error + blockByNumberPassedContext context.Context + blockByNumberPassedNumber *big.Int + blockByNumberReturnBlock *types.Block + headerByNumberErr error + headerByNumberPassedContext context.Context + headerByNumberPassedNumber *big.Int + headerByNumberReturnHeader *types.Header + filterLogsErr error + filterLogsPassedContext context.Context + filterLogsPassedQuery ethereum.FilterQuery + filterLogsReturnLogs []types.Log +} + +func NewMockClient() *MockClient { + return &MockClient{ + callContractErr: nil, + callContractPassedContext: nil, + callContractPassedMsg: ethereum.CallMsg{}, + callContractPassedNumber: nil, + callContractReturnBytes: nil, + blockByNumberErr: nil, + blockByNumberPassedContext: nil, + blockByNumberPassedNumber: nil, + blockByNumberReturnBlock: nil, + headerByNumberErr: nil, + headerByNumberPassedContext: nil, + headerByNumberPassedNumber: nil, + headerByNumberReturnHeader: nil, + filterLogsErr: nil, + filterLogsPassedContext: nil, + filterLogsPassedQuery: ethereum.FilterQuery{}, + filterLogsReturnLogs: nil, + } +} + +func (client *MockClient) SetCallContractErr(err error) { + client.callContractErr = err +} + +func (client *MockClient) SetCallContractReturnBytes(returnBytes []byte) { + client.callContractReturnBytes = returnBytes +} + +func (client *MockClient) SetBlockByNumberErr(err error) { + client.blockByNumberErr = err +} + +func (client *MockClient) SetBlockByNumberReturnBlock(block *types.Block) { + client.blockByNumberReturnBlock = block +} + +func (client *MockClient) SetHeaderByNumberErr(err error) { + client.headerByNumberErr = err +} + +func (client *MockClient) SetHeaderByNumberReturnHeader(header *types.Header) { + client.headerByNumberReturnHeader = header +} + +func (client *MockClient) SetFilterLogsErr(err error) { + client.filterLogsErr = err +} + +func (client *MockClient) SetFilterLogsReturnLogs(logs []types.Log) { + client.filterLogsReturnLogs = logs +} + +func (client *MockClient) CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) { + client.callContractPassedContext = ctx + client.callContractPassedMsg = msg + client.callContractPassedNumber = blockNumber + return client.callContractReturnBytes, client.callContractErr +} + +func (client *MockClient) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) { + client.blockByNumberPassedContext = ctx + client.blockByNumberPassedNumber = number + return client.blockByNumberReturnBlock, client.blockByNumberErr +} + +func (client *MockClient) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { + client.headerByNumberPassedContext = ctx + client.headerByNumberPassedNumber = number + return client.headerByNumberReturnHeader, client.headerByNumberErr +} + +func (client *MockClient) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) { + client.filterLogsPassedContext = ctx + client.filterLogsPassedQuery = q + return client.filterLogsReturnLogs, client.filterLogsErr +} + +func (client *MockClient) AssertCallContractCalledWith(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) { + Expect(client.callContractPassedContext).To(Equal(ctx)) + Expect(client.callContractPassedMsg).To(Equal(msg)) + Expect(client.callContractPassedNumber).To(Equal(blockNumber)) +} + +func (client *MockClient) AssertBlockByNumberCalledWith(ctx context.Context, number *big.Int) { + Expect(client.blockByNumberPassedContext).To(Equal(ctx)) + Expect(client.blockByNumberPassedNumber).To(Equal(number)) +} + +func (client *MockClient) AssertHeaderByNumberCalledWith(ctx context.Context, number *big.Int) { + Expect(client.headerByNumberPassedContext).To(Equal(ctx)) + Expect(client.headerByNumberPassedNumber).To(Equal(number)) +} + +func (client *MockClient) AssertFilterLogsCalledWith(ctx context.Context, q ethereum.FilterQuery) { + Expect(client.filterLogsPassedContext).To(Equal(ctx)) + Expect(client.filterLogsPassedQuery).To(Equal(q)) +} diff --git a/pkg/geth/blockchain.go b/pkg/geth/blockchain.go index ecc477e7..2351a777 100644 --- a/pkg/geth/blockchain.go +++ b/pkg/geth/blockchain.go @@ -1,44 +1,48 @@ package geth import ( - "log" "math/big" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/ethclient" - "github.com/ethereum/go-ethereum/rpc" "golang.org/x/net/context" "github.com/vulcanize/vulcanizedb/pkg/core" vulcCommon "github.com/vulcanize/vulcanizedb/pkg/geth/converters/common" - vulcRpc "github.com/vulcanize/vulcanizedb/pkg/geth/converters/rpc" - "github.com/vulcanize/vulcanizedb/pkg/geth/node" ) type BlockChain struct { - client *ethclient.Client + client core.Client blockConverter vulcCommon.BlockConverter headerConverter vulcCommon.HeaderConverter node core.Node } -func NewBlockChain(ipcPath string) *BlockChain { - rpcClient, err := rpc.Dial(ipcPath) - if err != nil { - log.Fatal(err) - } - client := ethclient.NewClient(rpcClient) - clientWrapper := node.ClientWrapper{ContextCaller: rpcClient, IPCPath: ipcPath} - transactionConverter := vulcRpc.NewRpcTransactionConverter(client) +func NewBlockChain(client core.Client, node core.Node, converter vulcCommon.TransactionConverter) *BlockChain { return &BlockChain{ client: client, - blockConverter: vulcCommon.NewBlockConverter(transactionConverter), + blockConverter: vulcCommon.NewBlockConverter(converter), headerConverter: vulcCommon.HeaderConverter{}, - node: node.MakeNode(clientWrapper), + node: node, } } +func (blockChain *BlockChain) GetBlockByNumber(blockNumber int64) (block core.Block, err error) { + gethBlock, err := blockChain.client.BlockByNumber(context.Background(), big.NewInt(blockNumber)) + if err != nil { + return block, err + } + return blockChain.blockConverter.ToCoreBlock(gethBlock) +} + +func (blockChain *BlockChain) GetHeaderByNumber(blockNumber int64) (header core.Header, err error) { + gethHeader, err := blockChain.client.HeaderByNumber(context.Background(), big.NewInt(blockNumber)) + if err != nil { + return header, err + } + return blockChain.headerConverter.Convert(gethHeader) +} + func (blockChain *BlockChain) GetLogs(contract core.Contract, startingBlockNumber, endingBlockNumber *big.Int) ([]core.Log, error) { if endingBlockNumber == nil { endingBlockNumber = startingBlockNumber @@ -57,27 +61,11 @@ func (blockChain *BlockChain) GetLogs(contract core.Contract, startingBlockNumbe return logs, nil } -func (blockChain *BlockChain) Node() core.Node { - return blockChain.node -} - -func (blockChain *BlockChain) GetBlockByNumber(blockNumber int64) (block core.Block, err error) { - gethBlock, err := blockChain.client.BlockByNumber(context.Background(), big.NewInt(blockNumber)) - if err != nil { - return block, err - } - return blockChain.blockConverter.ToCoreBlock(gethBlock) -} - -func (blockChain *BlockChain) GetHeaderByNumber(blockNumber int64) (header core.Header, err error) { - gethHeader, err := blockChain.client.HeaderByNumber(context.Background(), big.NewInt(blockNumber)) - if err != nil { - return header, err - } - return blockChain.headerConverter.Convert(gethHeader) -} - func (blockChain *BlockChain) LastBlock() *big.Int { block, _ := blockChain.client.HeaderByNumber(context.Background(), nil) return block.Number } + +func (blockChain *BlockChain) Node() core.Node { + return blockChain.node +} diff --git a/pkg/geth/blockchain_test.go b/pkg/geth/blockchain_test.go new file mode 100644 index 00000000..2bb207d0 --- /dev/null +++ b/pkg/geth/blockchain_test.go @@ -0,0 +1,125 @@ +package geth_test + +import ( + "context" + "math/big" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + vulcCore "github.com/vulcanize/vulcanizedb/pkg/core" + "github.com/vulcanize/vulcanizedb/pkg/fakes" + "github.com/vulcanize/vulcanizedb/pkg/geth" + "github.com/vulcanize/vulcanizedb/pkg/geth/converters/cold_db" +) + +var _ = Describe("Geth blockchain", func() { + Describe("getting a block", func() { + It("fetches block from client", func() { + mockClient := fakes.NewMockClient() + mockClient.SetBlockByNumberReturnBlock(types.NewBlockWithHeader(&types.Header{})) + node := vulcCore.Node{} + blockChain := geth.NewBlockChain(mockClient, node, cold_db.NewColdDbTransactionConverter()) + blockNumber := int64(100) + + _, err := blockChain.GetBlockByNumber(blockNumber) + + Expect(err).NotTo(HaveOccurred()) + mockClient.AssertBlockByNumberCalledWith(context.Background(), big.NewInt(blockNumber)) + }) + + It("returns err if client returns err", func() { + mockClient := fakes.NewMockClient() + mockClient.SetBlockByNumberErr(fakes.FakeError) + node := vulcCore.Node{} + blockChain := geth.NewBlockChain(mockClient, node, cold_db.NewColdDbTransactionConverter()) + + _, err := blockChain.GetBlockByNumber(100) + + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError(fakes.FakeError)) + }) + }) + + Describe("getting a header", func() { + It("fetches header from client", func() { + mockClient := fakes.NewMockClient() + blockNumber := int64(100) + mockClient.SetHeaderByNumberReturnHeader(&types.Header{Number: big.NewInt(blockNumber)}) + node := vulcCore.Node{} + blockChain := geth.NewBlockChain(mockClient, node, cold_db.NewColdDbTransactionConverter()) + + _, err := blockChain.GetHeaderByNumber(blockNumber) + + Expect(err).NotTo(HaveOccurred()) + mockClient.AssertHeaderByNumberCalledWith(context.Background(), big.NewInt(blockNumber)) + }) + + It("returns err if client returns err", func() { + mockClient := fakes.NewMockClient() + mockClient.SetHeaderByNumberErr(fakes.FakeError) + node := vulcCore.Node{} + blockChain := geth.NewBlockChain(mockClient, node, cold_db.NewColdDbTransactionConverter()) + + _, err := blockChain.GetHeaderByNumber(100) + + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError(fakes.FakeError)) + }) + }) + + Describe("getting logs", func() { + It("fetches logs from client", func() { + mockClient := fakes.NewMockClient() + mockClient.SetFilterLogsReturnLogs([]types.Log{{}}) + node := vulcCore.Node{} + blockChain := geth.NewBlockChain(mockClient, node, cold_db.NewColdDbTransactionConverter()) + contract := vulcCore.Contract{Hash: common.BytesToHash([]byte{1, 2, 3, 4, 5}).Hex()} + startingBlockNumber := big.NewInt(1) + endingBlockNumber := big.NewInt(2) + + _, err := blockChain.GetLogs(contract, startingBlockNumber, endingBlockNumber) + + Expect(err).NotTo(HaveOccurred()) + expectedQuery := ethereum.FilterQuery{ + FromBlock: startingBlockNumber, + ToBlock: endingBlockNumber, + Addresses: []common.Address{common.HexToAddress(contract.Hash)}, + } + mockClient.AssertFilterLogsCalledWith(context.Background(), expectedQuery) + }) + + It("returns err if client returns err", func() { + mockClient := fakes.NewMockClient() + mockClient.SetFilterLogsErr(fakes.FakeError) + node := vulcCore.Node{} + blockChain := geth.NewBlockChain(mockClient, node, cold_db.NewColdDbTransactionConverter()) + contract := vulcCore.Contract{Hash: common.BytesToHash([]byte{1, 2, 3, 4, 5}).Hex()} + startingBlockNumber := big.NewInt(1) + endingBlockNumber := big.NewInt(2) + + _, err := blockChain.GetLogs(contract, startingBlockNumber, endingBlockNumber) + + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError(fakes.FakeError)) + }) + }) + + Describe("getting the most recent block number", func() { + It("fetches latest header from client", func() { + mockClient := fakes.NewMockClient() + blockNumber := int64(100) + mockClient.SetHeaderByNumberReturnHeader(&types.Header{Number: big.NewInt(blockNumber)}) + node := vulcCore.Node{} + blockChain := geth.NewBlockChain(mockClient, node, cold_db.NewColdDbTransactionConverter()) + + result := blockChain.LastBlock() + + mockClient.AssertHeaderByNumberCalledWith(context.Background(), nil) + Expect(result).To(Equal(big.NewInt(blockNumber))) + }) + }) +}) diff --git a/pkg/geth/client/client.go b/pkg/geth/client/client.go new file mode 100644 index 00000000..d98f631f --- /dev/null +++ b/pkg/geth/client/client.go @@ -0,0 +1,33 @@ +package client + +import ( + "context" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" + "math/big" +) + +type Client struct { + client *ethclient.Client +} + +func NewClient(client *ethclient.Client) Client { + return Client{client: client} +} + +func (client Client) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) { + return client.client.BlockByNumber(ctx, number) +} + +func (client Client) CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) { + return client.client.CallContract(ctx, msg, blockNumber) +} + +func (client Client) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) { + return client.client.FilterLogs(ctx, q) +} + +func (client Client) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { + return client.client.HeaderByNumber(ctx, number) +} diff --git a/pkg/history/block_validator_test.go b/pkg/history/block_validator_test.go index adc04aa5..ef271b0f 100644 --- a/pkg/history/block_validator_test.go +++ b/pkg/history/block_validator_test.go @@ -13,7 +13,7 @@ import ( var _ = Describe("Blocks validator", func() { It("calls create or update for all blocks within the window", func() { - blockchain := fakes.NewBlockchainWithBlocks([]core.Block{ + blockchain := fakes.NewMockBlockChainWithBlocks([]core.Block{ {Number: 4}, {Number: 5}, {Number: 6}, @@ -31,7 +31,7 @@ var _ = Describe("Blocks validator", func() { }) It("returns the number of largest block", func() { - blockchain := fakes.NewBlockchainWithBlocks([]core.Block{ + blockchain := fakes.NewMockBlockChainWithBlocks([]core.Block{ {Number: 1}, {Number: 2}, {Number: 3}, diff --git a/pkg/history/header_validator_test.go b/pkg/history/header_validator_test.go index e96ce268..60cae147 100644 --- a/pkg/history/header_validator_test.go +++ b/pkg/history/header_validator_test.go @@ -27,7 +27,7 @@ var _ = Describe("Header validator", func() { Hash: newHash, } headers := []core.Header{newHeader} - blockChain := fakes.NewBlockChainWithHeaders(headers) + blockChain := fakes.NewMockBlockChainWithHeaders(headers) validator := history.NewHeaderValidator(blockChain, headerRepository, 1) validator.ValidateHeaders() diff --git a/pkg/history/populate_blocks_test.go b/pkg/history/populate_blocks_test.go index 0ce0e1ed..ad464188 100644 --- a/pkg/history/populate_blocks_test.go +++ b/pkg/history/populate_blocks_test.go @@ -25,7 +25,7 @@ var _ = Describe("Populating blocks", func() { {Number: 1}, {Number: 2}, } - blockchain := fakes.NewBlockchainWithBlocks(blocks) + blockchain := fakes.NewMockBlockChainWithBlocks(blocks) blockRepository.CreateOrUpdateBlock(core.Block{Number: 2}) @@ -37,7 +37,7 @@ var _ = Describe("Populating blocks", func() { }) It("fills in the three missing blocks (Numbers: 5,8,10)", func() { - blockchain := fakes.NewBlockchainWithBlocks([]core.Block{ + blockchain := fakes.NewMockBlockChainWithBlocks([]core.Block{ {Number: 4}, {Number: 5}, {Number: 6}, @@ -76,7 +76,7 @@ var _ = Describe("Populating blocks", func() { }) It("returns the number of blocks created", func() { - blockchain := fakes.NewBlockchainWithBlocks([]core.Block{ + blockchain := fakes.NewMockBlockChainWithBlocks([]core.Block{ {Number: 4}, {Number: 5}, {Number: 6}, @@ -90,7 +90,7 @@ var _ = Describe("Populating blocks", func() { }) It("updates the repository with a range of blocks w/in the range ", func() { - blockchain := fakes.NewBlockchainWithBlocks([]core.Block{ + blockchain := fakes.NewMockBlockChainWithBlocks([]core.Block{ {Number: 1}, {Number: 2}, {Number: 3}, @@ -104,7 +104,7 @@ var _ = Describe("Populating blocks", func() { }) It("does not call repository create block when there is an error", func() { - blockchain := fakes.NewBlockchain(errors.New("error getting block")) + blockchain := fakes.NewMockBlockChain(errors.New("error getting block")) blocks := history.MakeRange(1, 10) history.RetrieveAndUpdateBlocks(blockchain, blockRepository, blocks) Expect(blockRepository.BlockCount()).To(Equal(0)) diff --git a/pkg/history/populate_headers_test.go b/pkg/history/populate_headers_test.go index d8e46bb7..bb9d41e0 100644 --- a/pkg/history/populate_headers_test.go +++ b/pkg/history/populate_headers_test.go @@ -26,7 +26,7 @@ var _ = Describe("Populating headers", func() { {BlockNumber: 1}, {BlockNumber: 2}, } - blockChain := fakes.NewBlockChainWithHeaders(headers) + blockChain := fakes.NewMockBlockChainWithHeaders(headers) headerRepository.CreateOrUpdateHeader(core.Header{BlockNumber: 2}) headersAdded := history.PopulateMissingHeaders(blockChain, headerRepository, 1) @@ -40,7 +40,7 @@ var _ = Describe("Populating headers", func() { {BlockNumber: 1}, {BlockNumber: 2}, } - blockChain := fakes.NewBlockChainWithHeaders(headers) + blockChain := fakes.NewMockBlockChainWithHeaders(headers) dbHeader, _ := headerRepository.GetHeader(1) Expect(dbHeader.BlockNumber).To(BeZero()) diff --git a/pkg/history/validation_window_test.go b/pkg/history/validation_window_test.go index 07c73dba..5043d352 100644 --- a/pkg/history/validation_window_test.go +++ b/pkg/history/validation_window_test.go @@ -13,7 +13,7 @@ import ( var _ = Describe("", func() { It("creates a ValidationWindow equal to (HEAD-windowSize, HEAD)", func() { - blockchain := fakes.NewBlockchainWithBlocks([]core.Block{ + blockchain := fakes.NewMockBlockChainWithBlocks([]core.Block{ {Number: 1}, {Number: 2}, {Number: 3},