Merge pull request #47 from vulcanize/track_uncle_rewards

Track uncle rewards + direct balance polling
This commit is contained in:
Ian Norden 2019-04-04 15:55:40 -05:00 committed by GitHub
commit 04ad7b7696
26 changed files with 408 additions and 89 deletions

View File

@ -3,15 +3,15 @@ CREATE TABLE public.blocks (
id SERIAL PRIMARY KEY,
difficulty BIGINT,
extra_data VARCHAR,
gaslimit BIGINT,
gasused BIGINT,
gas_limit BIGINT,
gas_used BIGINT,
hash VARCHAR(66),
miner VARCHAR(42),
nonce VARCHAR(20),
"number" BIGINT,
parenthash VARCHAR(66),
reward DOUBLE PRECISION,
uncles_reward DOUBLE PRECISION,
parent_hash VARCHAR(66),
reward NUMERIC,
uncles_reward NUMERIC,
"size" VARCHAR,
"time" BIGINT,
is_final BOOLEAN,

View File

@ -2,8 +2,8 @@
CREATE TABLE full_sync_transactions (
id SERIAL PRIMARY KEY,
block_id INTEGER NOT NULL REFERENCES blocks(id) ON DELETE CASCADE,
gaslimit NUMERIC,
gasprice NUMERIC,
gas_limit NUMERIC,
gas_price NUMERIC,
hash VARCHAR(66),
input_data BYTEA,
nonce NUMERIC,

View File

@ -5,11 +5,8 @@ CREATE TABLE public.headers (
block_number BIGINT,
raw JSONB,
block_timestamp NUMERIC,
eth_node_id INTEGER,
eth_node_fingerprint VARCHAR(128),
CONSTRAINT eth_nodes_fk FOREIGN KEY (eth_node_id)
REFERENCES eth_nodes (id)
ON DELETE CASCADE
eth_node_id INTEGER NOT NULL REFERENCES eth_nodes (id) ON DELETE CASCADE,
eth_node_fingerprint VARCHAR(128)
);
-- Index is removed when table is

View File

@ -3,8 +3,8 @@ CREATE TABLE light_sync_transactions (
id SERIAL PRIMARY KEY,
header_id INTEGER NOT NULL REFERENCES headers(id) ON DELETE CASCADE,
hash TEXT,
gaslimit NUMERIC,
gasprice NUMERIC,
gas_limit NUMERIC,
gas_price NUMERIC,
input_data BYTEA,
nonce NUMERIC,
raw BYTEA,

View File

@ -0,0 +1,16 @@
-- +goose Up
CREATE TABLE public.uncles (
id SERIAL PRIMARY KEY,
hash VARCHAR(66) NOT NULL,
block_id INTEGER NOT NULL REFERENCES blocks (id) ON DELETE CASCADE,
reward NUMERIC NOT NULL,
miner VARCHAR(42) NOT NULL,
raw JSONB,
block_timestamp NUMERIC,
eth_node_id INTEGER NOT NULL REFERENCES eth_nodes (id) ON DELETE CASCADE,
eth_node_fingerprint VARCHAR(128),
UNIQUE (block_id, hash)
);
-- +goose Down
DROP TABLE public.uncles;

View File

@ -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"))
})
})

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

@ -17,20 +17,21 @@
package core
type Block struct {
Reward float64 `db:"reward"`
Difficulty int64 `db:"difficulty"`
ExtraData string `db:"extra_data"`
GasLimit uint64 `db:"gaslimit"`
GasUsed uint64 `db:"gasused"`
Hash string `db:"hash"`
IsFinal bool `db:"is_final"`
Miner string `db:"miner"`
Nonce string `db:"nonce"`
Number int64 `db:"number"`
ParentHash string `db:"parenthash"`
Size string `db:"size"`
Time int64 `db:"time"`
Reward string `db:"reward"`
Difficulty int64 `db:"difficulty"`
ExtraData string `db:"extra_data"`
GasLimit uint64 `db:"gas_limit"`
GasUsed uint64 `db:"gas_used"`
Hash string `db:"hash"`
IsFinal bool `db:"is_final"`
Miner string `db:"miner"`
Nonce string `db:"nonce"`
Number int64 `db:"number"`
ParentHash string `db:"parent_hash"`
Size string `db:"size"`
Time int64 `db:"time"`
Transactions []TransactionModel
UncleHash string `db:"uncle_hash"`
UnclesReward float64 `db:"uncles_reward"`
UncleHash string `db:"uncle_hash"`
UnclesReward string `db:"uncles_reward"`
Uncles []Uncle
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -19,8 +19,8 @@ package core
type TransactionModel struct {
Data []byte `db:"input_data"`
From string `db:"tx_from"`
GasLimit uint64
GasPrice int64
GasLimit uint64 `db:"gas_limit"`
GasPrice int64 `db:"gas_price"`
Hash string
Nonce uint64
Raw []byte

26
pkg/core/uncle.go Normal file
View File

@ -0,0 +1,26 @@
// 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 core
type Uncle struct {
Id int64
Miner string
Reward string
Hash string
Timestamp string `db:"block_timestamp"`
Raw []byte
}

View File

@ -19,9 +19,9 @@ package repositories
import (
"database/sql"
"errors"
log "github.com/sirupsen/logrus"
"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"
@ -90,13 +90,13 @@ func (blockRepository BlockRepository) GetBlock(blockNumber int64) (core.Block,
blockRows := blockRepository.database.QueryRowx(
`SELECT id,
number,
gaslimit,
gasused,
gas_limit,
gas_used,
time,
difficulty,
hash,
nonce,
parenthash,
parent_hash,
size,
uncle_hash,
is_final,
@ -127,10 +127,26 @@ func (blockRepository BlockRepository) insertBlock(block core.Block) (int64, err
}
insertBlockErr := tx.QueryRow(
`INSERT INTO blocks
(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, gas_limit, gas_used, time, difficulty, hash, nonce, parent_hash, 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()
@ -139,6 +155,13 @@ func (blockRepository BlockRepository) insertBlock(block core.Block) (int64, err
}
return 0, postgres.ErrDBInsertFailed(insertBlockErr)
}
if len(block.Uncles) > 0 {
insertUncleErr := blockRepository.createUncles(tx, blockId, block.Hash, block.Uncles)
if insertUncleErr != nil {
tx.Rollback()
return 0, postgres.ErrDBInsertFailed(insertUncleErr)
}
}
if len(block.Transactions) > 0 {
insertTxErr := blockRepository.createTransactions(tx, blockId, block.Transactions)
if insertTxErr != nil {
@ -160,6 +183,26 @@ func (blockRepository BlockRepository) insertBlock(block core.Block) (int64, err
return blockId, nil
}
func (blockRepository BlockRepository) createUncles(tx *sqlx.Tx, blockId int64, blockHash string, uncles []core.Uncle) error {
for _, uncle := range uncles {
err := blockRepository.createUncle(tx, blockId, uncle)
if err != nil {
return err
}
}
return nil
}
func (blockRepository BlockRepository) createUncle(tx *sqlx.Tx, blockId int64, uncle core.Uncle) error {
_, err := tx.Exec(
`INSERT INTO uncles
(hash, block_id, reward, miner, raw, block_timestamp, eth_node_id, eth_node_fingerprint)
VALUES ($1, $2, $3, $4, $5, $6, $7::NUMERIC, $8)
RETURNING id`,
uncle.Hash, blockId, utilities.NullToZero(uncle.Reward), uncle.Miner, uncle.Raw, uncle.Timestamp, blockRepository.database.NodeID, blockRepository.database.Node.ID)
return err
}
func (blockRepository BlockRepository) createTransactions(tx *sqlx.Tx, blockId int64, transactions []core.TransactionModel) error {
for _, transaction := range transactions {
err := blockRepository.createTransaction(tx, blockId, transaction)
@ -183,10 +226,10 @@ func nullStringToZero(s string) string {
func (blockRepository BlockRepository) createTransaction(tx *sqlx.Tx, blockId int64, transaction core.TransactionModel) error {
_, err := tx.Exec(
`INSERT INTO full_sync_transactions
(block_id, gaslimit, gasprice, hash, input_data, nonce, raw, tx_from, tx_index, tx_to, "value")
(block_id, gas_limit, gas_price, 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
}
@ -282,8 +325,8 @@ func (blockRepository BlockRepository) loadBlock(blockRows *sqlx.Row) (core.Bloc
}
transactionRows, err := blockRepository.database.Queryx(`
SELECT hash,
gaslimit,
gasprice,
gas_limit,
gas_price,
input_data,
nonce,
raw,

View File

@ -84,8 +84,8 @@ var _ = Describe("Saving blocks", func() {
uncleHash := "x789"
blockSize := string("1000")
difficulty := int64(10)
blockReward := float64(5.132)
unclesReward := float64(3.580)
blockReward := "5132000000000000000"
unclesReward := "3580000000000000000"
block := core.Block{
Reward: blockReward,
Difficulty: difficulty,
@ -158,6 +158,72 @@ var _ = Describe("Saving blocks", func() {
Expect(len(savedBlock.Transactions)).To(Equal(2))
})
It("saves one uncle associated to the block", func() {
block := core.Block{
Hash: fakes.FakeHash.String(),
Number: 123,
Transactions: []core.TransactionModel{fakes.FakeTransaction},
Uncles: []core.Uncle{fakes.GetFakeUncle(common.BytesToHash([]byte{1, 2, 3}).String(), "100000")},
UnclesReward: "156250000000000000",
}
id, insertErr := blockRepository.CreateOrUpdateBlock(block)
Expect(insertErr).NotTo(HaveOccurred())
savedBlock, getErr := blockRepository.GetBlock(123)
Expect(getErr).NotTo(HaveOccurred())
Expect(len(savedBlock.Transactions)).To(Equal(1))
Expect(savedBlock.UnclesReward).To(Equal(big.NewInt(0).Div(big.NewInt(5000000000000000000), big.NewInt(32)).String()))
var uncleModel core.Uncle
err := db.Get(&uncleModel, `SELECT hash, reward, miner, raw, block_timestamp FROM uncles
WHERE block_id = $1 AND hash = $2`, id, common.BytesToHash([]byte{1, 2, 3}).Hex())
Expect(err).ToNot(HaveOccurred())
Expect(uncleModel.Hash).To(Equal(common.BytesToHash([]byte{1, 2, 3}).Hex()))
Expect(uncleModel.Reward).To(Equal("100000"))
Expect(uncleModel.Miner).To(Equal(fakes.FakeAddress.Hex()))
Expect(uncleModel.Timestamp).To(Equal("111111111"))
})
It("saves two uncles associated to the block", func() {
block := core.Block{
Hash: fakes.FakeHash.String(),
Number: 123,
Transactions: []core.TransactionModel{fakes.FakeTransaction},
Uncles: []core.Uncle{
fakes.GetFakeUncle(common.BytesToHash([]byte{1, 2, 3}).String(), "100000"),
fakes.GetFakeUncle(common.BytesToHash([]byte{3, 2, 1}).String(), "90000")},
UnclesReward: "312500000000000000",
}
id, insertErr := blockRepository.CreateOrUpdateBlock(block)
Expect(insertErr).NotTo(HaveOccurred())
savedBlock, getErr := blockRepository.GetBlock(123)
Expect(getErr).NotTo(HaveOccurred())
Expect(len(savedBlock.Transactions)).To(Equal(1))
b := new(big.Int)
b.SetString("10000000000000000000", 10)
Expect(savedBlock.UnclesReward).To(Equal(big.NewInt(0).Div(b, big.NewInt(32)).String()))
var uncleModel core.Uncle
err := db.Get(&uncleModel, `SELECT hash, reward, miner, raw, block_timestamp FROM uncles
WHERE block_id = $1 AND hash = $2`, id, common.BytesToHash([]byte{1, 2, 3}).Hex())
Expect(err).ToNot(HaveOccurred())
Expect(uncleModel.Hash).To(Equal(common.BytesToHash([]byte{1, 2, 3}).Hex()))
Expect(uncleModel.Reward).To(Equal("100000"))
Expect(uncleModel.Miner).To(Equal(fakes.FakeAddress.Hex()))
Expect(uncleModel.Timestamp).To(Equal("111111111"))
err = db.Get(&uncleModel, `SELECT hash, reward, miner, raw, block_timestamp FROM uncles
WHERE block_id = $1 AND hash = $2`, id, common.BytesToHash([]byte{3, 2, 1}).Hex())
Expect(err).ToNot(HaveOccurred())
Expect(uncleModel.Hash).To(Equal(common.BytesToHash([]byte{3, 2, 1}).Hex()))
Expect(uncleModel.Reward).To(Equal("90000"))
Expect(uncleModel.Miner).To(Equal(fakes.FakeAddress.Hex()))
Expect(uncleModel.Timestamp).To(Equal("111111111"))
})
It(`replaces blocks and transactions associated to the block
when a more new block is in conflict (same block number + nodeid)`, func() {
blockOne := core.Block{

View File

@ -82,8 +82,8 @@ func (contractRepository ContractRepository) addTransactions(contract core.Contr
nonce,
tx_to,
tx_from,
gaslimit,
gasprice,
gas_limit,
gas_price,
value,
input_data
FROM full_sync_transactions

View File

@ -52,7 +52,7 @@ func (repository HeaderRepository) CreateOrUpdateHeader(header core.Header) (int
func (repository HeaderRepository) CreateTransactions(headerID int64, transactions []core.TransactionModel) error {
for _, transaction := range transactions {
_, err := repository.database.Exec(`INSERT INTO public.light_sync_transactions
(header_id, hash, gaslimit, gasprice, input_data, nonce, raw, tx_from, tx_index, tx_to, "value")
(header_id, hash, gas_limit, gas_price, input_data, nonce, raw, tx_from, tx_index, tx_to, "value")
VALUES ($1, $2, $3::NUMERIC, $4::NUMERIC, $5, $6::NUMERIC, $7, $8, $9::NUMERIC, $10, $11::NUMERIC)
ON CONFLICT DO NOTHING`, headerID, transaction.Hash, transaction.GasLimit, transaction.GasPrice,
transaction.Data, transaction.Nonce, transaction.Raw, transaction.From, transaction.TxIndex, transaction.To,

View File

@ -226,7 +226,7 @@ var _ = Describe("Block header repository", func() {
It("adds transactions", func() {
var dbTransactions []core.TransactionModel
err = db.Select(&dbTransactions,
`SELECT hash, gaslimit, gasprice, input_data, nonce, raw, tx_from, tx_index, tx_to, "value"
`SELECT hash, gas_limit, gas_price, input_data, nonce, raw, tx_from, tx_index, tx_to, "value"
FROM public.light_sync_transactions WHERE header_id = $1`, headerID)
Expect(err).NotTo(HaveOccurred())
Expect(dbTransactions).To(ConsistOf(transactions))
@ -238,7 +238,7 @@ var _ = Describe("Block header repository", func() {
var dbTransactions []core.TransactionModel
err = db.Select(&dbTransactions,
`SELECT hash, gaslimit, gasprice, input_data, nonce, raw, tx_from, tx_index, tx_to, "value"
`SELECT hash, gas_limit, gas_price, input_data, nonce, raw, tx_from, tx_index, tx_to, "value"
FROM public.light_sync_transactions WHERE header_id = $1`, headerID)
Expect(err).NotTo(HaveOccurred())
Expect(len(dbTransactions)).To(Equal(2))

View File

@ -73,7 +73,7 @@ func GetFakeTransaction(hash string, receipt core.Receipt) core.TransactionModel
var raw bytes.Buffer
err := gethTransaction.EncodeRLP(&raw)
if err != nil {
panic("failed to marshal transaction creating test fake")
panic("failed to marshal transaction while creating test fake")
}
return core.TransactionModel{
Data: []byte{},
@ -89,3 +89,13 @@ func GetFakeTransaction(hash string, receipt core.Receipt) core.TransactionModel
Value: "0",
}
}
func GetFakeUncle(hash, reward string) core.Uncle {
return core.Uncle{
Miner: FakeAddress.String(),
Hash: hash,
Reward: reward,
Raw: rawFakeHeader,
Timestamp: strconv.FormatInt(fakeTimestamp, 10),
}
}

View File

@ -45,6 +45,8 @@ type MockBlockChain struct {
lastBlock *big.Int
node core.Node
Transactions []core.TransactionModel
accountBalanceReturnValue *big.Int
getAccountBalanceErr error
}
func NewMockBlockChain() *MockBlockChain {
@ -141,3 +143,15 @@ 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.accountBalanceReturnValue = balance
}
func (blockChain *MockBlockChain) GetAccountBalance(address common.Address, blockNumber *big.Int) (*big.Int, error) {
return blockChain.accountBalanceReturnValue, blockChain.getAccountBalanceErr
}

View File

@ -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))
}

View File

@ -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)
}

View File

@ -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))
})
})
})

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) {
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,14 @@
package common
import (
"encoding/json"
"math/big"
"strings"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/vulcanize/vulcanizedb/pkg/core"
"strings"
)
type BlockConverter struct {
@ -52,7 +56,35 @@ func (bc BlockConverter) ToCoreBlock(gethBlock *types.Block) (core.Block, error)
Transactions: transactions,
UncleHash: gethBlock.UncleHash().Hex(),
}
coreBlock.Reward = CalcBlockReward(coreBlock, gethBlock.Uncles())
coreBlock.UnclesReward = CalcUnclesReward(coreBlock, gethBlock.Uncles())
coreBlock.Reward = CalcBlockReward(coreBlock, gethBlock.Uncles()).String()
totalUncleReward, uncles := bc.ToCoreUncle(coreBlock, gethBlock.Uncles())
coreBlock.UnclesReward = totalUncleReward.String()
coreBlock.Uncles = uncles
return coreBlock, nil
}
// Rewards for the miners of uncles is calculated as (U_n + 8 - B_n) * R / 8
// Where U_n is the uncle block number, B_n is the parent block number and R is the static block reward at B_n
// https://github.com/ethereum/go-ethereum/issues/1591
// https://ethereum.stackexchange.com/questions/27172/different-uncles-reward
// https://github.com/ethereum/homestead-guide/issues/399
// Returns the total uncle reward and the individual processed uncles
func (bc BlockConverter) ToCoreUncle(block core.Block, uncles []*types.Header) (*big.Int, []core.Uncle) {
totalUncleRewards := new(big.Int)
coreUncles := make([]core.Uncle, 0, len(uncles))
for _, uncle := range uncles {
thisUncleReward := calcUncleMinerReward(block.Number, uncle.Number.Int64())
raw, _ := json.Marshal(uncle)
coreUncle := core.Uncle{
Miner: uncle.Coinbase.Hex(),
Hash: uncle.Hash().Hex(),
Raw: raw,
Reward: thisUncleReward.String(),
Timestamp: uncle.Time.String(),
}
coreUncles = append(coreUncles, coreUncle)
totalUncleRewards.Add(totalUncleRewards, thisUncleReward)
}
return totalUncleRewards, coreUncles
}

View File

@ -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,20 @@ 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, coreUncles := blockConverter.ToCoreUncle(coreBlock, block.Uncles())
Expect(totalReward.String()).To(Equal(expectedTotalReward.String()))
Expect(len(coreUncles)).To(Equal(2))
Expect(coreUncles[0].Reward).To(Equal("3125000000000000000"))
Expect(coreUncles[0].Miner).To(Equal("0x0000000000000000000000000000000000000000"))
Expect(coreUncles[0].Hash).To(Equal("0xb629de4014b6e30cf9555ee833f1806fa0d8b8516fde194405f9c98c2deb8772"))
Expect(coreUncles[1].Reward).To(Equal("3750000000000000000"))
Expect(coreUncles[1].Miner).To(Equal("0x0000000000000000000000000000000000000000"))
Expect(coreUncles[1].Hash).To(Equal("0x673f5231e4888a951e0bc8a25b5774b982e6e9e258362c21affaff6e02dd5a2b"))
})
It("decreases the static block reward from 5 to 3 for blocks after block 4,269,999", func() {
@ -200,9 +215,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()))
})
})

View File

@ -17,54 +17,61 @@
package common
import (
"math/big"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params"
"github.com/vulcanize/vulcanizedb/pkg/core"
)
func CalcUnclesReward(block core.Block, uncles []*types.Header) float64 {
var unclesReward float64
for _, uncle := range uncles {
blockNumber := block.Number
staticBlockReward := float64(staticRewardByBlockNumber(blockNumber))
unclesReward += (1.0 + float64(uncle.Number.Int64()-block.Number)/8.0) * staticBlockReward
}
return unclesReward
}
func CalcBlockReward(block core.Block, uncles []*types.Header) float64 {
blockNumber := block.Number
staticBlockReward := staticRewardByBlockNumber(blockNumber)
func CalcBlockReward(block core.Block, uncles []*types.Header) *big.Int {
staticBlockReward := staticRewardByBlockNumber(block.Number)
transactionFees := calcTransactionFees(block)
uncleInclusionRewards := calcUncleInclusionRewards(block, uncles)
return transactionFees + uncleInclusionRewards + staticBlockReward
tmp := transactionFees.Add(transactionFees, uncleInclusionRewards)
return tmp.Add(tmp, staticBlockReward)
}
func calcTransactionFees(block core.Block) float64 {
var transactionFees float64
func calcUncleMinerReward(blockNumber, uncleBlockNumber int64) *big.Int {
staticBlockReward := staticRewardByBlockNumber(blockNumber)
rewardDiv8 := staticBlockReward.Div(staticBlockReward, big.NewInt(8))
mainBlock := big.NewInt(blockNumber)
uncleBlock := big.NewInt(uncleBlockNumber)
uncleBlockPlus8 := uncleBlock.Add(uncleBlock, big.NewInt(8))
uncleBlockPlus8MinusMainBlock := uncleBlockPlus8.Sub(uncleBlockPlus8, mainBlock)
return rewardDiv8.Mul(rewardDiv8, uncleBlockPlus8MinusMainBlock)
}
func calcTransactionFees(block core.Block) *big.Int {
transactionFees := new(big.Int)
for _, transaction := range block.Transactions {
receipt := transaction.Receipt
transactionFees += float64(uint64(transaction.GasPrice) * receipt.GasUsed)
gasPrice := big.NewInt(transaction.GasPrice)
gasUsed := big.NewInt(int64(receipt.GasUsed))
transactionFee := gasPrice.Mul(gasPrice, gasUsed)
transactionFees = transactionFees.Add(transactionFees, transactionFee)
}
return transactionFees / params.Ether
return transactionFees
}
func calcUncleInclusionRewards(block core.Block, uncles []*types.Header) float64 {
var uncleInclusionRewards float64
staticBlockReward := staticRewardByBlockNumber(block.Number)
func calcUncleInclusionRewards(block core.Block, uncles []*types.Header) *big.Int {
uncleInclusionRewards := new(big.Int)
for range uncles {
uncleInclusionRewards += staticBlockReward * 1 / 32
staticBlockReward := staticRewardByBlockNumber(block.Number)
staticBlockReward.Div(staticBlockReward, big.NewInt(32))
uncleInclusionRewards.Add(uncleInclusionRewards, staticBlockReward)
}
return uncleInclusionRewards
}
func staticRewardByBlockNumber(blockNumber int64) float64 {
var staticBlockReward float64
func staticRewardByBlockNumber(blockNumber int64) *big.Int {
staticBlockReward := new(big.Int)
//https://blog.ethereum.org/2017/10/12/byzantium-hf-announcement/
if blockNumber >= 4370000 {
staticBlockReward = 3
if blockNumber >= 7280000 {
staticBlockReward.SetString("2000000000000000000", 10)
} else if blockNumber >= 4370000 {
staticBlockReward.SetString("3000000000000000000", 10)
} else {
staticBlockReward = 5
staticBlockReward.SetString("5000000000000000000", 10)
}
return staticBlockReward
}