Merge pull request #47 from vulcanize/track_uncle_rewards
Track uncle rewards + direct balance polling
This commit is contained in:
commit
04ad7b7696
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
16
db/migrations/00026_create_uncles_table.sql
Normal file
16
db/migrations/00026_create_uncles_table.sql
Normal 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;
|
@ -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"))
|
||||
})
|
||||
|
||||
})
|
||||
|
24
libraries/shared/utilities/utils.go
Normal file
24
libraries/shared/utilities/utils.go
Normal 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
|
||||
}
|
@ -17,20 +17,21 @@
|
||||
package core
|
||||
|
||||
type Block struct {
|
||||
Reward float64 `db:"reward"`
|
||||
Reward string `db:"reward"`
|
||||
Difficulty int64 `db:"difficulty"`
|
||||
ExtraData string `db:"extra_data"`
|
||||
GasLimit uint64 `db:"gaslimit"`
|
||||
GasUsed uint64 `db:"gasused"`
|
||||
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:"parenthash"`
|
||||
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"`
|
||||
UnclesReward string `db:"uncles_reward"`
|
||||
Uncles []Uncle
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
26
pkg/core/uncle.go
Normal 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
|
||||
}
|
@ -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,
|
||||
|
@ -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{
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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))
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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()))
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user