diff --git a/integration_test/block_rewards_test.go b/integration_test/block_rewards_test.go new file mode 100644 index 00000000..d183ff92 --- /dev/null +++ b/integration_test/block_rewards_test.go @@ -0,0 +1,36 @@ +package integration + +import ( + "log" + + cfg "github.com/8thlight/vulcanizedb/pkg/config" + "github.com/8thlight/vulcanizedb/pkg/geth" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Reading contracts", func() { + + Describe("Block and Uncle rewards", func() { + It("calculates a block reward for a real block", func() { + config, err := cfg.NewConfig("infura") + if err != nil { + log.Fatalln(err) + } + blockchain := geth.NewGethBlockchain(config.Client.IPCPath) + block := blockchain.GetBlockByNumber(1071819) + Expect(block.BlockReward).To(Equal(5.31355)) + }) + + It("calculates an uncle reward for a real block", func() { + config, err := cfg.NewConfig("infura") + if err != nil { + log.Fatalln(err) + } + blockchain := geth.NewGethBlockchain(config.Client.IPCPath) + block := blockchain.GetBlockByNumber(1071819) + Expect(block.UncleReward).To(Equal(6.875)) + }) + }) + +}) diff --git a/pkg/core/block.go b/pkg/core/block.go index a8300a50..5d78fe1d 100644 --- a/pkg/core/block.go +++ b/pkg/core/block.go @@ -1,6 +1,7 @@ package core type Block struct { + BlockReward float64 Difficulty int64 ExtraData string GasLimit int64 @@ -15,4 +16,5 @@ type Block struct { Time int64 Transactions []Transaction UncleHash string + UncleReward float64 } diff --git a/pkg/geth/geth_block_rewards.go b/pkg/geth/geth_block_rewards.go new file mode 100644 index 00000000..8e4ad79e --- /dev/null +++ b/pkg/geth/geth_block_rewards.go @@ -0,0 +1,54 @@ +package geth + +import ( + "context" + + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" +) + +func UncleReward(gethBlock *types.Block, client GethClient) float64 { + var uncleReward float64 + for _, uncle := range gethBlock.Uncles() { + staticBlockReward := float64(blockNumberStaticReward(gethBlock)) / float64(8) + uncleReward += float64(uncle.Number.Int64()-gethBlock.Number().Int64()+int64(8)) * staticBlockReward + } + return uncleReward +} + +func BlockReward(gethBlock *types.Block, client GethClient) float64 { + staticBlockReward := blockNumberStaticReward(gethBlock) + transactionFees := calcTransactionFees(gethBlock, client) + uncleInclusionRewards := uncleInclusionRewards(gethBlock, staticBlockReward) + return transactionFees + uncleInclusionRewards + staticBlockReward +} + +func uncleInclusionRewards(gethBlock *types.Block, staticBlockReward float64) float64 { + var uncleInclusionRewards float64 + for range gethBlock.Uncles() { + uncleInclusionRewards += staticBlockReward * 1 / 32 + } + return uncleInclusionRewards +} + +func calcTransactionFees(gethBlock *types.Block, client GethClient) float64 { + var transactionFees float64 + for _, transaction := range gethBlock.Transactions() { + receipt, err := client.TransactionReceipt(context.Background(), transaction.Hash()) + if err != nil { + continue + } + transactionFees += float64(transaction.GasPrice().Int64() * receipt.GasUsed.Int64()) + } + return transactionFees / params.Ether +} + +func blockNumberStaticReward(gethBlock *types.Block) float64 { + var staticBlockReward float64 + if gethBlock.Number().Int64() > 4269999 { + staticBlockReward = 3 + } else { + staticBlockReward = 5 + } + return staticBlockReward +} diff --git a/pkg/geth/geth_block_to_core_block.go b/pkg/geth/geth_block_to_core_block.go index e9895cbd..af307b7a 100644 --- a/pkg/geth/geth_block_to_core_block.go +++ b/pkg/geth/geth_block_to_core_block.go @@ -12,6 +12,7 @@ import ( type GethClient interface { 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) } func GethBlockToCoreBlock(gethBlock *types.Block, client GethClient) core.Block { @@ -21,6 +22,8 @@ func GethBlockToCoreBlock(gethBlock *types.Block, client GethClient) core.Block transaction := gethTransToCoreTrans(gethTransaction, &from) transactions = append(transactions, transaction) } + blockReward := BlockReward(gethBlock, client) + uncleReward := UncleReward(gethBlock, client) return core.Block{ Difficulty: gethBlock.Difficulty().Int64(), GasLimit: gethBlock.GasLimit().Int64(), @@ -34,7 +37,9 @@ func GethBlockToCoreBlock(gethBlock *types.Block, client GethClient) core.Block Size: gethBlock.Size().Int64(), Time: gethBlock.Time().Int64(), Transactions: transactions, + BlockReward: blockReward, UncleHash: gethBlock.UncleHash().Hex(), + UncleReward: uncleReward, } } diff --git a/pkg/geth/geth_block_to_core_block_test.go b/pkg/geth/geth_block_to_core_block_test.go index 065e6d55..ccf7a85e 100644 --- a/pkg/geth/geth_block_to_core_block_test.go +++ b/pkg/geth/geth_block_to_core_block_test.go @@ -13,7 +13,28 @@ import ( . "github.com/onsi/gomega" ) -type FakeGethClient struct{} +type FakeGethClient struct { + receipts map[string]*types.Receipt +} + +func NewFakeClient() *FakeGethClient { + return &FakeGethClient{ + receipts: make(map[string]*types.Receipt), + } +} + +func (client *FakeGethClient) AddReceipts(receipts []*types.Receipt) { + for _, receipt := range receipts { + client.receipts[receipt.TxHash.Hex()] = receipt + } +} + +func (client *FakeGethClient) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { + if gasUsed, ok := client.receipts[txHash.Hex()]; ok { + return gasUsed, nil + } + return &types.Receipt{GasUsed: big.NewInt(0)}, nil +} func (client *FakeGethClient) TransactionSender(ctx context.Context, tx *types.Transaction, block common.Hash, index uint) (common.Address, error) { return common.HexToAddress("0x123"), nil @@ -62,6 +83,103 @@ var _ = Describe("Conversion of GethBlock to core.Block", func() { Expect(gethBlock.IsFinal).To(BeFalse()) }) + Describe("the block and uncle rewards calculations", func() { + It("Calculates block rewards for a block", func() { + number := int64(1071819) + uncles := []*types.Header{{Number: big.NewInt(1071817)}, {Number: big.NewInt(1071818)}} + + nonce := uint64(226823) + to := common.HexToAddress("0x108fedb097c1dcfed441480170144d8e19bb217f") + amount := big.NewInt(1080900090000000000) + gasLimit := big.NewInt(90000) + gasPrice := big.NewInt(50000000000) + var payload []byte + transaction := types.NewTransaction(nonce, to, amount, gasLimit, gasPrice, payload) + transactions := []*types.Transaction{transaction} + + txHash := transaction.Hash() + receipt := types.Receipt{TxHash: txHash, GasUsed: big.NewInt(21000)} + receipts := []*types.Receipt{&receipt} + + header := types.Header{ + Number: big.NewInt(number), + } + block := types.NewBlock(&header, transactions, uncles, []*types.Receipt{}) + + client := NewFakeClient() + client.AddReceipts(receipts) + + Expect(geth.BlockReward(block, client)).To(Equal(5.31355)) + }) + + It("decreases the static block reward from 5 to 3 for blocks after block 4,269,999", func() { + number := int64(4370055) + var uncles []*types.Header + + nonce := uint64(8072) + to := common.HexToAddress("0xebd17720aeb7ac5186c5dfa7bafeb0bb14c02551 ") + amount := big.NewInt(0) + gasLimit := big.NewInt(500000) + gasPrice := big.NewInt(42000000000) + var payload []byte + transactionOne := types.NewTransaction(nonce, to, amount, gasLimit, gasPrice, payload) + + nonce = uint64(8071) + to = common.HexToAddress("0x3cdab63d764c8c5048ed5e8f0a4e95534ba7e1ea") + amount = big.NewInt(0) + gasLimit = big.NewInt(500000) + gasPrice = big.NewInt(42000000000) + transactionTwo := types.NewTransaction(nonce, to, amount, gasLimit, gasPrice, payload) + + transactions := []*types.Transaction{transactionOne, transactionTwo} + + txHashOne := transactionOne.Hash() + receiptOne := types.Receipt{TxHash: txHashOne, GasUsed: big.NewInt(297508)} + txHashTwo := transactionTwo.Hash() + receiptTwo := types.Receipt{TxHash: txHashTwo, GasUsed: big.NewInt(297508)} + receipts := []*types.Receipt{&receiptOne, &receiptTwo} + + header := types.Header{ + Number: big.NewInt(number), + } + block := types.NewBlock(&header, transactions, uncles, receipts) + + client := NewFakeClient() + client.AddReceipts(receipts) + + Expect(geth.BlockReward(block, client)).To(Equal(3.024990672)) + }) + + It("Calculates uncle rewards for a block", func() { + number := int64(1071819) + uncles := []*types.Header{{Number: big.NewInt(1071816)}, {Number: big.NewInt(1071817)}} + + nonce := uint64(226823) + to := common.HexToAddress("0x108fedb097c1dcfed441480170144d8e19bb217f") + amount := big.NewInt(1080900090000000000) + gasLimit := big.NewInt(90000) + gasPrice := big.NewInt(50000000000) + var payload []byte + transaction := types.NewTransaction(nonce, to, amount, gasLimit, gasPrice, payload) + transactions := []*types.Transaction{transaction} + + txHash := transaction.Hash() + receipt := types.Receipt{TxHash: txHash, GasUsed: big.NewInt(21000)} + receipts := []*types.Receipt{&receipt} + + header := types.Header{ + Number: big.NewInt(number), + } + block := types.NewBlock(&header, transactions, uncles, []*types.Receipt{}) + + client := NewFakeClient() + client.AddReceipts(receipts) + + Expect(geth.UncleReward(block, client)).To(Equal(6.875)) + }) + + }) + Describe("the converted transations", func() { It("is empty", func() { header := types.Header{} @@ -72,7 +190,7 @@ var _ = Describe("Conversion of GethBlock to core.Block", func() { Expect(len(coreBlock.Transactions)).To(Equal(0)) }) - It("converts a single transations", func() { + It("converts a single transaction", func() { nonce := uint64(10000) header := types.Header{} to := common.Address{1} @@ -82,7 +200,9 @@ var _ = Describe("Conversion of GethBlock to core.Block", func() { payload := []byte("1234") gethTransaction := types.NewTransaction(nonce, to, amount, gasLimit, gasPrice, payload) - client := &FakeGethClient{} + + client := NewFakeClient() + gethBlock := types.NewBlock(&header, []*types.Transaction{gethTransaction}, []*types.Header{}, []*types.Receipt{}) coreBlock := geth.GethBlockToCoreBlock(gethBlock, client) @@ -100,8 +220,8 @@ var _ = Describe("Conversion of GethBlock to core.Block", func() { It("has an empty to field when transaction creates a new contract", func() { gethTransaction := types.NewContractCreation(uint64(10000), big.NewInt(10), big.NewInt(5000), big.NewInt(3), []byte("1234")) gethBlock := types.NewBlock(&types.Header{}, []*types.Transaction{gethTransaction}, []*types.Header{}, []*types.Receipt{}) - client := &FakeGethClient{} + client := NewFakeClient() coreBlock := geth.GethBlockToCoreBlock(gethBlock, client) coreTransaction := coreBlock.Transactions[0]