adjust block/uncle reward tests and add methods to pkg/geth blockchain and ethclient for direct fetching of eth balances

This commit is contained in:
Ian Norden 2019-03-21 22:43:06 -05:00
parent 9030cff2bd
commit 185f4c0e93
14 changed files with 173 additions and 14 deletions

View File

@ -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;

View File

@ -42,7 +42,7 @@ var _ = Describe("Rewards calculations", func() {
blockChain := geth.NewBlockChain(blockChainClient, rpcClient, node, transactionConverter) blockChain := geth.NewBlockChain(blockChainClient, rpcClient, node, transactionConverter)
block, err := blockChain.GetBlockByNumber(1071819) block, err := blockChain.GetBlockByNumber(1071819)
Expect(err).ToNot(HaveOccurred()) 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() { 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) blockChain := geth.NewBlockChain(blockChainClient, rpcClient, node, transactionConverter)
block, err := blockChain.GetBlockByNumber(1071819) block, err := blockChain.GetBlockByNumber(1071819)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
Expect(block.UnclesReward).To(Equal(6.875)) Expect(block.UnclesReward).To(Equal("6875000000000000000"))
}) })
}) })

View File

@ -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 <http://www.gnu.org/licenses/>.
package utilities
func NullToZero(str string) string {
if str == "" {
return "0"
}
return str
}

View File

@ -26,6 +26,7 @@ import (
type BlockChain interface { type BlockChain interface {
ContractDataFetcher ContractDataFetcher
AccountDataFetcher
GetBlockByNumber(blockNumber int64) (Block, error) GetBlockByNumber(blockNumber int64) (Block, error)
GetEthLogsWithCustomQuery(query ethereum.FilterQuery) ([]types.Log, error) GetEthLogsWithCustomQuery(query ethereum.FilterQuery) ([]types.Log, error)
GetHeaderByNumber(blockNumber int64) (Header, error) GetHeaderByNumber(blockNumber int64) (Header, error)
@ -39,3 +40,7 @@ type BlockChain interface {
type ContractDataFetcher interface { type ContractDataFetcher interface {
FetchContractData(abiJSON string, address string, method string, methodArgs []interface{}, result interface{}, blockNumber int64) error 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)
}

View File

@ -32,4 +32,5 @@ type EthClient interface {
HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) 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) 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) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error)
BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error)
} }

View File

@ -19,11 +19,12 @@ package repositories
import ( import (
"database/sql" "database/sql"
"errors" "errors"
log "github.com/sirupsen/logrus"
"math/big" "math/big"
"github.com/jmoiron/sqlx" "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/core"
"github.com/vulcanize/vulcanizedb/pkg/datastore" "github.com/vulcanize/vulcanizedb/pkg/datastore"
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres" "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) (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) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17)
RETURNING id `, 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) Scan(&blockId)
if insertBlockErr != nil { if insertBlockErr != nil {
rollbackErr := tx.Rollback() 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) (block_id, block_hash, uncle_hash, uncle_reward, miner_address)
VALUES ($1, $2, $3, $4, $5) VALUES ($1, $2, $3, $4, $5)
RETURNING id`, RETURNING id`,
blockId, blockHash, miner, uncleHash, amount) blockId, blockHash, miner, uncleHash, utilities.NullToZero(amount))
return err 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") (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) 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, 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 { if err != nil {
return err return err
} }

View File

@ -45,6 +45,10 @@ type MockBlockChain struct {
lastBlock *big.Int lastBlock *big.Int
node core.Node node core.Node
Transactions []core.TransactionModel Transactions []core.TransactionModel
passedAccountAddress common.Address
passedBlockNumer *big.Int
passedAccountBalance *big.Int
getAccountBalanceErr error
} }
func NewMockBlockChain() *MockBlockChain { func NewMockBlockChain() *MockBlockChain {
@ -141,3 +145,17 @@ func (chain *MockBlockChain) AssertFetchContractDataCalledWith(abiJSON string, a
func (blockChain *MockBlockChain) AssertGetEthLogsWithCustomQueryCalledWith(query ethereum.FilterQuery) { func (blockChain *MockBlockChain) AssertGetEthLogsWithCustomQueryCalledWith(query ethereum.FilterQuery) {
Expect(blockChain.logQuery).To(Equal(query)) 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
}

View File

@ -54,6 +54,11 @@ type MockEthClient struct {
passedMethod string passedMethod string
transactionSenderErr error transactionSenderErr error
transactionReceiptErr error transactionReceiptErr error
passedAddress common.Address
passedBlockNumber *big.Int
passedBalance *big.Int
balanceAtErr error
passedbalanceAtContext context.Context
} }
func NewMockEthClient() *MockEthClient { func NewMockEthClient() *MockEthClient {
@ -208,3 +213,24 @@ func (client *MockEthClient) AssertFilterLogsCalledWith(ctx context.Context, q e
func (client *MockEthClient) AssertBatchCalledWith(method string) { func (client *MockEthClient) AssertBatchCalledWith(method string) {
Expect(client.passedMethod).To(Equal(method)) 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))
}

View File

@ -265,3 +265,7 @@ func (blockChain *BlockChain) getPOWHeaders(blockNumbers []int64) (headers []cor
return headers, err return headers, err
} }
func (blockChain *BlockChain) GetAccountBalance(address common.Address, blockNumber *big.Int) (*big.Int, error) {
return blockChain.ethClient.BalanceAt(context.Background(), address, blockNumber)
}

View File

@ -18,6 +18,7 @@ package geth_test
import ( import (
"context" "context"
"errors"
"math/big" "math/big"
"github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum"
@ -244,4 +245,28 @@ var _ = Describe("Geth blockchain", func() {
Expect(result).To(Equal(big.NewInt(blockNumber))) 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))
})
})
}) })

View File

@ -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) { func (client EthClient) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) {
return client.client.TransactionReceipt(ctx, txHash) 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)
}

View File

@ -17,10 +17,13 @@
package common package common
import ( import (
"strings"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/vulcanize/vulcanizedb/libraries/shared/utilities"
"github.com/vulcanize/vulcanizedb/pkg/core" "github.com/vulcanize/vulcanizedb/pkg/core"
"strings"
) )
type BlockConverter struct { type BlockConverter struct {
@ -54,7 +57,7 @@ func (bc BlockConverter) ToCoreBlock(gethBlock *types.Block) (core.Block, error)
} }
coreBlock.Reward = CalcBlockReward(coreBlock, gethBlock.Uncles()).String() coreBlock.Reward = CalcBlockReward(coreBlock, gethBlock.Uncles()).String()
uncleRewards, mappedUncleRewards := CalcUnclesReward(coreBlock, gethBlock.Uncles()) uncleRewards, mappedUncleRewards := CalcUnclesReward(coreBlock, gethBlock.Uncles())
coreBlock.UnclesReward = uncleRewards.String() coreBlock.UnclesReward = utilities.NullToZero(uncleRewards.String())
coreBlock.MappedUncleRewards = mappedUncleRewards coreBlock.MappedUncleRewards = mappedUncleRewards
return coreBlock, nil return coreBlock, nil
} }

View File

@ -114,9 +114,13 @@ var _ = Describe("Conversion of GethBlock to core.Block", func() {
blockConverter := vulcCommon.NewBlockConverter(transactionConverter) blockConverter := vulcCommon.NewBlockConverter(transactionConverter)
coreBlock, err := blockConverter.ToCoreBlock(block) coreBlock, err := blockConverter.ToCoreBlock(block)
Expect(err).ToNot(HaveOccurred()) 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() { 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) blockConverter := vulcCommon.NewBlockConverter(transactionConverter)
coreBlock, err := blockConverter.ToCoreBlock(block) coreBlock, err := blockConverter.ToCoreBlock(block)
Expect(err).ToNot(HaveOccurred()) 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() { 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) blockConverter := vulcCommon.NewBlockConverter(transactionConverter)
coreBlock, err := blockConverter.ToCoreBlock(block) coreBlock, err := blockConverter.ToCoreBlock(block)
Expect(err).ToNot(HaveOccurred()) 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()))
}) })
}) })

View File

@ -37,6 +37,12 @@ func CalcUnclesReward(block core.Block, uncles []*types.Header) (*big.Int, map[s
uncleBlockPlus8MinusMainBlock := uncleBlockPlus8.Sub(uncleBlockPlus8, mainBlock) uncleBlockPlus8MinusMainBlock := uncleBlockPlus8.Sub(uncleBlockPlus8, mainBlock)
thisUncleReward := rewardDiv8.Mul(rewardDiv8, uncleBlockPlus8MinusMainBlock) thisUncleReward := rewardDiv8.Mul(rewardDiv8, uncleBlockPlus8MinusMainBlock)
uncleRewards = uncleRewards.Add(uncleRewards, thisUncleReward) 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) mappedUncleRewards[uncle.Coinbase.Hex()][uncle.Hash().Hex()].Add(mappedUncleRewards[uncle.Coinbase.Hex()][uncle.Hash().Hex()], thisUncleReward)
} }
return uncleRewards, mappedUncleRewards return uncleRewards, mappedUncleRewards