From 185f4c0e93db24dc0ffa91e7ad843170724e060a Mon Sep 17 00:00:00 2001 From: Ian Norden Date: Thu, 21 Mar 2019 22:43:06 -0500 Subject: [PATCH] adjust block/uncle reward tests and add methods to pkg/geth blockchain and ethclient for direct fetching of eth balances --- ...46_update_block_and_uncle_reward_types.sql | 9 ++++++ integration_test/block_rewards_test.go | 4 +-- libraries/shared/utilities/utils.go | 24 +++++++++++++++ pkg/core/blockchain.go | 5 ++++ pkg/core/eth_client.go | 1 + .../postgres/repositories/block_repository.go | 25 +++++++++++++--- pkg/fakes/mock_blockchain.go | 18 ++++++++++++ pkg/fakes/mock_eth_client.go | 26 +++++++++++++++++ pkg/geth/blockchain.go | 4 +++ pkg/geth/blockchain_test.go | 25 ++++++++++++++++ pkg/geth/client/eth_client.go | 4 +++ pkg/geth/converters/common/block_converter.go | 7 +++-- .../converters/common/block_converter_test.go | 29 +++++++++++++++---- pkg/geth/converters/common/block_rewards.go | 6 ++++ 14 files changed, 173 insertions(+), 14 deletions(-) create mode 100644 db/migrations/00046_update_block_and_uncle_reward_types.sql create mode 100644 libraries/shared/utilities/utils.go diff --git a/db/migrations/00046_update_block_and_uncle_reward_types.sql b/db/migrations/00046_update_block_and_uncle_reward_types.sql new file mode 100644 index 00000000..8bca0604 --- /dev/null +++ b/db/migrations/00046_update_block_and_uncle_reward_types.sql @@ -0,0 +1,9 @@ +-- +goose Up +ALTER TABLE blocks + ALTER COLUMN uncles_reward TYPE NUMERIC USING uncles_reward::NUMERIC, + ALTER COLUMN reward TYPE NUMERIC USING reward::NUMERIC; + +-- +goose Down +ALTER TABLE blocks + ALTER COLUMN uncles_reward TYPE DOUBLE PRECISION USING uncles_reward::DOUBLE PRECISION, + ALTER COLUMN reward TYPE DOUBLE PRECISION USING reward::DOUBLE PRECISION; diff --git a/integration_test/block_rewards_test.go b/integration_test/block_rewards_test.go index 2cee003d..7e66f374 100644 --- a/integration_test/block_rewards_test.go +++ b/integration_test/block_rewards_test.go @@ -42,7 +42,7 @@ var _ = Describe("Rewards calculations", func() { blockChain := geth.NewBlockChain(blockChainClient, rpcClient, node, transactionConverter) block, err := blockChain.GetBlockByNumber(1071819) Expect(err).ToNot(HaveOccurred()) - Expect(block.Reward).To(Equal(5.31355)) + Expect(block.Reward).To(Equal("5313550000000000000")) }) It("calculates an uncle reward for a real block", func() { @@ -56,7 +56,7 @@ var _ = Describe("Rewards calculations", func() { blockChain := geth.NewBlockChain(blockChainClient, rpcClient, node, transactionConverter) block, err := blockChain.GetBlockByNumber(1071819) Expect(err).ToNot(HaveOccurred()) - Expect(block.UnclesReward).To(Equal(6.875)) + Expect(block.UnclesReward).To(Equal("6875000000000000000")) }) }) diff --git a/libraries/shared/utilities/utils.go b/libraries/shared/utilities/utils.go new file mode 100644 index 00000000..02fc104e --- /dev/null +++ b/libraries/shared/utilities/utils.go @@ -0,0 +1,24 @@ +// VulcanizeDB +// Copyright © 2019 Vulcanize + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. + +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package utilities + +func NullToZero(str string) string { + if str == "" { + return "0" + } + return str +} diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index fb43c655..be2fe9eb 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -26,6 +26,7 @@ import ( type BlockChain interface { ContractDataFetcher + AccountDataFetcher GetBlockByNumber(blockNumber int64) (Block, error) GetEthLogsWithCustomQuery(query ethereum.FilterQuery) ([]types.Log, error) GetHeaderByNumber(blockNumber int64) (Header, error) @@ -39,3 +40,7 @@ type BlockChain interface { type ContractDataFetcher interface { FetchContractData(abiJSON string, address string, method string, methodArgs []interface{}, result interface{}, blockNumber int64) error } + +type AccountDataFetcher interface { + GetAccountBalance(address common.Address, blockNumber *big.Int) (*big.Int, error) +} diff --git a/pkg/core/eth_client.go b/pkg/core/eth_client.go index 183d224b..d2aa2779 100644 --- a/pkg/core/eth_client.go +++ b/pkg/core/eth_client.go @@ -32,4 +32,5 @@ type EthClient interface { HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) TransactionSender(ctx context.Context, tx *types.Transaction, block common.Hash, index uint) (common.Address, error) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) + BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error) } diff --git a/pkg/datastore/postgres/repositories/block_repository.go b/pkg/datastore/postgres/repositories/block_repository.go index 9fe2c0df..3bda7771 100644 --- a/pkg/datastore/postgres/repositories/block_repository.go +++ b/pkg/datastore/postgres/repositories/block_repository.go @@ -19,11 +19,12 @@ package repositories import ( "database/sql" "errors" - log "github.com/sirupsen/logrus" "math/big" "github.com/jmoiron/sqlx" + log "github.com/sirupsen/logrus" + "github.com/vulcanize/vulcanizedb/libraries/shared/utilities" "github.com/vulcanize/vulcanizedb/pkg/core" "github.com/vulcanize/vulcanizedb/pkg/datastore" "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres" @@ -131,7 +132,23 @@ func (blockRepository BlockRepository) insertBlock(block core.Block) (int64, err (eth_node_id, number, gaslimit, gasused, time, difficulty, hash, nonce, parenthash, size, uncle_hash, is_final, miner, extra_data, reward, uncles_reward, eth_node_fingerprint) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17) RETURNING id `, - blockRepository.database.NodeID, block.Number, block.GasLimit, block.GasUsed, block.Time, block.Difficulty, block.Hash, block.Nonce, block.ParentHash, block.Size, block.UncleHash, block.IsFinal, block.Miner, block.ExtraData, block.Reward, block.UnclesReward, blockRepository.database.Node.ID). + blockRepository.database.NodeID, + block.Number, + block.GasLimit, + block.GasUsed, + block.Time, + block.Difficulty, + block.Hash, + block.Nonce, + block.ParentHash, + block.Size, + block.UncleHash, + block.IsFinal, + block.Miner, + block.ExtraData, + utilities.NullToZero(block.Reward), + utilities.NullToZero(block.UnclesReward), + blockRepository.database.Node.ID). Scan(&blockId) if insertBlockErr != nil { rollbackErr := tx.Rollback() @@ -186,7 +203,7 @@ func (blockRepository BlockRepository) createUncleReward(tx *sqlx.Tx, blockId in (block_id, block_hash, uncle_hash, uncle_reward, miner_address) VALUES ($1, $2, $3, $4, $5) RETURNING id`, - blockId, blockHash, miner, uncleHash, amount) + blockId, blockHash, miner, uncleHash, utilities.NullToZero(amount)) return err } @@ -216,7 +233,7 @@ func (blockRepository BlockRepository) createTransaction(tx *sqlx.Tx, blockId in (block_id, gaslimit, gasprice, hash, input_data, nonce, raw, tx_from, tx_index, tx_to, "value") VALUES ($1, $2::NUMERIC, $3::NUMERIC, $4, $5, $6::NUMERIC, $7, $8, $9::NUMERIC, $10, $11::NUMERIC) RETURNING id`, blockId, transaction.GasLimit, transaction.GasPrice, transaction.Hash, transaction.Data, - transaction.Nonce, transaction.Raw, transaction.From, transaction.TxIndex, transaction.To, transaction.Value) + transaction.Nonce, transaction.Raw, transaction.From, transaction.TxIndex, transaction.To, nullStringToZero(transaction.Value)) if err != nil { return err } diff --git a/pkg/fakes/mock_blockchain.go b/pkg/fakes/mock_blockchain.go index 1f337781..7083b1ac 100644 --- a/pkg/fakes/mock_blockchain.go +++ b/pkg/fakes/mock_blockchain.go @@ -45,6 +45,10 @@ type MockBlockChain struct { lastBlock *big.Int node core.Node Transactions []core.TransactionModel + passedAccountAddress common.Address + passedBlockNumer *big.Int + passedAccountBalance *big.Int + getAccountBalanceErr error } func NewMockBlockChain() *MockBlockChain { @@ -141,3 +145,17 @@ func (chain *MockBlockChain) AssertFetchContractDataCalledWith(abiJSON string, a func (blockChain *MockBlockChain) AssertGetEthLogsWithCustomQueryCalledWith(query ethereum.FilterQuery) { Expect(blockChain.logQuery).To(Equal(query)) } + +func (blockChain *MockBlockChain) SetGetAccountBalanceErr(err error) { + blockChain.getAccountBalanceErr = err +} + +func (blockChain *MockBlockChain) SetGetAccountBalance(balance *big.Int) { + blockChain.passedAccountBalance = balance +} + +func (blockChain *MockBlockChain) GetAccountBalance(address common.Address, blockNumber *big.Int) (*big.Int, error) { + blockChain.passedAccountAddress = address + blockChain.passedBlockNumer = blockNumber + return blockChain.passedAccountBalance, blockChain.getAccountBalanceErr +} diff --git a/pkg/fakes/mock_eth_client.go b/pkg/fakes/mock_eth_client.go index 224ae4d5..033232d5 100644 --- a/pkg/fakes/mock_eth_client.go +++ b/pkg/fakes/mock_eth_client.go @@ -54,6 +54,11 @@ type MockEthClient struct { passedMethod string transactionSenderErr error transactionReceiptErr error + passedAddress common.Address + passedBlockNumber *big.Int + passedBalance *big.Int + balanceAtErr error + passedbalanceAtContext context.Context } func NewMockEthClient() *MockEthClient { @@ -208,3 +213,24 @@ func (client *MockEthClient) AssertFilterLogsCalledWith(ctx context.Context, q e func (client *MockEthClient) AssertBatchCalledWith(method string) { Expect(client.passedMethod).To(Equal(method)) } + +func (client *MockEthClient) SetBalanceAtErr(err error) { + client.balanceAtErr = err +} + +func (client *MockEthClient) SetBalanceAt(balance *big.Int) { + client.passedBalance = balance +} + +func (client *MockEthClient) BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error) { + client.passedbalanceAtContext = ctx + client.passedAddress = account + client.passedBlockNumber = blockNumber + return client.passedBalance, client.balanceAtErr +} + +func (client *MockEthClient) AssertBalanceAtCalled(ctx context.Context, account common.Address, blockNumber *big.Int) { + Expect(client.passedbalanceAtContext).To(Equal(ctx)) + Expect(client.passedAddress).To(Equal(account)) + Expect(client.passedBlockNumber).To(Equal(blockNumber)) +} diff --git a/pkg/geth/blockchain.go b/pkg/geth/blockchain.go index 6a16a917..a2b641c7 100644 --- a/pkg/geth/blockchain.go +++ b/pkg/geth/blockchain.go @@ -265,3 +265,7 @@ func (blockChain *BlockChain) getPOWHeaders(blockNumbers []int64) (headers []cor return headers, err } + +func (blockChain *BlockChain) GetAccountBalance(address common.Address, blockNumber *big.Int) (*big.Int, error) { + return blockChain.ethClient.BalanceAt(context.Background(), address, blockNumber) +} diff --git a/pkg/geth/blockchain_test.go b/pkg/geth/blockchain_test.go index 292e2cd5..d70eda92 100644 --- a/pkg/geth/blockchain_test.go +++ b/pkg/geth/blockchain_test.go @@ -18,6 +18,7 @@ package geth_test import ( "context" + "errors" "math/big" "github.com/ethereum/go-ethereum" @@ -244,4 +245,28 @@ var _ = Describe("Geth blockchain", func() { Expect(result).To(Equal(big.NewInt(blockNumber))) }) }) + + Describe("getting an account balance", func() { + It("fetches the balance for a given account address at a given block height", func() { + balance := big.NewInt(100000) + mockClient.SetBalanceAt(balance) + + result, err := blockChain.GetAccountBalance(common.HexToAddress("0x40"), big.NewInt(100)) + Expect(err).NotTo(HaveOccurred()) + + mockClient.AssertBalanceAtCalled(context.Background(), common.HexToAddress("0x40"), big.NewInt(100)) + Expect(result).To(Equal(balance)) + }) + + It("fails if the client returns an error", func() { + balance := big.NewInt(100000) + mockClient.SetBalanceAt(balance) + setErr := errors.New("testError") + mockClient.SetBalanceAtErr(setErr) + + _, err := blockChain.GetAccountBalance(common.HexToAddress("0x40"), big.NewInt(100)) + Expect(err).To(HaveOccurred()) + Expect(err).To(Equal(setErr)) + }) + }) }) diff --git a/pkg/geth/client/eth_client.go b/pkg/geth/client/eth_client.go index 0b335ee6..fca30d39 100644 --- a/pkg/geth/client/eth_client.go +++ b/pkg/geth/client/eth_client.go @@ -56,3 +56,7 @@ func (client EthClient) TransactionSender(ctx context.Context, tx *types.Transac func (client EthClient) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { return client.client.TransactionReceipt(ctx, txHash) } + +func (client EthClient) BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error) { + return client.client.BalanceAt(ctx, account, blockNumber) +} diff --git a/pkg/geth/converters/common/block_converter.go b/pkg/geth/converters/common/block_converter.go index 5b36c271..8a503e57 100644 --- a/pkg/geth/converters/common/block_converter.go +++ b/pkg/geth/converters/common/block_converter.go @@ -17,10 +17,13 @@ package common import ( + "strings" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" + + "github.com/vulcanize/vulcanizedb/libraries/shared/utilities" "github.com/vulcanize/vulcanizedb/pkg/core" - "strings" ) type BlockConverter struct { @@ -54,7 +57,7 @@ func (bc BlockConverter) ToCoreBlock(gethBlock *types.Block) (core.Block, error) } coreBlock.Reward = CalcBlockReward(coreBlock, gethBlock.Uncles()).String() uncleRewards, mappedUncleRewards := CalcUnclesReward(coreBlock, gethBlock.Uncles()) - coreBlock.UnclesReward = uncleRewards.String() + coreBlock.UnclesReward = utilities.NullToZero(uncleRewards.String()) coreBlock.MappedUncleRewards = mappedUncleRewards return coreBlock, nil } diff --git a/pkg/geth/converters/common/block_converter_test.go b/pkg/geth/converters/common/block_converter_test.go index c98d7d3e..7bdf7751 100644 --- a/pkg/geth/converters/common/block_converter_test.go +++ b/pkg/geth/converters/common/block_converter_test.go @@ -114,9 +114,13 @@ var _ = Describe("Conversion of GethBlock to core.Block", func() { blockConverter := vulcCommon.NewBlockConverter(transactionConverter) coreBlock, err := blockConverter.ToCoreBlock(block) - Expect(err).ToNot(HaveOccurred()) - Expect(vulcCommon.CalcBlockReward(coreBlock, block.Uncles())).To(Equal(5.31355)) + + expectedBlockReward := new(big.Int) + expectedBlockReward.SetString("5313550000000000000", 10) + + blockReward := vulcCommon.CalcBlockReward(coreBlock, block.Uncles()) + Expect(blockReward.String()).To(Equal(expectedBlockReward.String())) }) It("calculates the uncles reward for a block", func() { @@ -151,9 +155,19 @@ var _ = Describe("Conversion of GethBlock to core.Block", func() { blockConverter := vulcCommon.NewBlockConverter(transactionConverter) coreBlock, err := blockConverter.ToCoreBlock(block) - Expect(err).ToNot(HaveOccurred()) - Expect(vulcCommon.CalcUnclesReward(coreBlock, block.Uncles())).To(Equal(6.875)) + + expectedTotalReward := new(big.Int) + expectedTotalReward.SetString("6875000000000000000", 10) + totalReward, mappedRewards := vulcCommon.CalcUnclesReward(coreBlock, block.Uncles()) + Expect(totalReward.String()).To(Equal(expectedTotalReward.String())) + + Expect(len(mappedRewards)).To(Equal(1)) + Expect(len(mappedRewards["0x0000000000000000000000000000000000000000"])).To(Equal(2)) + Expect(mappedRewards["0x0000000000000000000000000000000000000000"]["0xb629de4014b6e30cf9555ee833f1806fa0d8b8516fde194405f9c98c2deb8772"].String()). + To(Equal(big.NewInt(3125000000000000000).String())) + Expect(mappedRewards["0x0000000000000000000000000000000000000000"]["0x673f5231e4888a951e0bc8a25b5774b982e6e9e258362c21affaff6e02dd5a2b"].String()). + To(Equal(big.NewInt(3750000000000000000).String())) }) It("decreases the static block reward from 5 to 3 for blocks after block 4,269,999", func() { @@ -200,9 +214,12 @@ var _ = Describe("Conversion of GethBlock to core.Block", func() { blockConverter := vulcCommon.NewBlockConverter(transactionConverter) coreBlock, err := blockConverter.ToCoreBlock(block) - Expect(err).ToNot(HaveOccurred()) - Expect(vulcCommon.CalcBlockReward(coreBlock, block.Uncles())).To(Equal(3.024990672)) + + expectedRewards := new(big.Int) + expectedRewards.SetString("3024990672000000000", 10) + rewards := vulcCommon.CalcBlockReward(coreBlock, block.Uncles()) + Expect(rewards.String()).To(Equal(expectedRewards.String())) }) }) diff --git a/pkg/geth/converters/common/block_rewards.go b/pkg/geth/converters/common/block_rewards.go index 57f8df28..6326323c 100644 --- a/pkg/geth/converters/common/block_rewards.go +++ b/pkg/geth/converters/common/block_rewards.go @@ -37,6 +37,12 @@ func CalcUnclesReward(block core.Block, uncles []*types.Header) (*big.Int, map[s uncleBlockPlus8MinusMainBlock := uncleBlockPlus8.Sub(uncleBlockPlus8, mainBlock) thisUncleReward := rewardDiv8.Mul(rewardDiv8, uncleBlockPlus8MinusMainBlock) uncleRewards = uncleRewards.Add(uncleRewards, thisUncleReward) + if mappedUncleRewards[uncle.Coinbase.Hex()] == nil { + mappedUncleRewards[uncle.Coinbase.Hex()] = make(map[string]*big.Int) + } + if mappedUncleRewards[uncle.Coinbase.Hex()][uncle.Hash().Hex()] == nil { + mappedUncleRewards[uncle.Coinbase.Hex()][uncle.Hash().Hex()] = new(big.Int) + } mappedUncleRewards[uncle.Coinbase.Hex()][uncle.Hash().Hex()].Add(mappedUncleRewards[uncle.Coinbase.Hex()][uncle.Hash().Hex()], thisUncleReward) } return uncleRewards, mappedUncleRewards