Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Block reward validation #1093

Merged
merged 6 commits into from
May 15, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ UNITE_CORE_H = \
snapshot/state.h \
staking/active_chain.h \
staking/block_index_map.h \
staking/block_reward_validator.h \
staking/block_validation_info.h \
staking/block_validator.h \
staking/coin.h \
Expand Down Expand Up @@ -398,6 +399,7 @@ libunite_server_a_SOURCES = \
snapshot/state.cpp \
staking/active_chain.cpp \
staking/block_index_map.cpp \
staking/block_reward_validator.cpp \
staking/block_validation_info.cpp \
staking/block_validator.cpp \
staking/coin.cpp \
Expand Down
1 change: 1 addition & 0 deletions src/Makefile.test.include
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ UNITE_TESTS =\
test/snapshot/snapshot_index_tests.cpp \
test/snapshot/state_tests.cpp \
test/snapshot/validation_tests.cpp \
test/staking/block_reward_validator_tests.cpp \
test/staking/coin_tests.cpp \
test/staking/proof_of_stake_tests.cpp \
test/streams_tests.cpp \
Expand Down
30 changes: 3 additions & 27 deletions src/consensus/tx_verify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -259,33 +259,9 @@ bool Consensus::CheckTxInputs(const CTransaction& tx, CValidationState& state, c
}

const CAmount value_out = tx.GetValueOut();
if (tx.IsCoinBase()) {
// The coinbase transaction should spend exactly its inputs and the reward.
// The reward output is by definition in the zeroth output. The reward
// consists of newly minted money (the block reward) and the fees accumulated
// from the transactions.
if (tx.vout.empty()) {
return state.DoS(100, false, REJECT_INVALID, "bad-cb-no-reward", false,
strprintf("coinbase without a reward txout"));
}
const CTxOut &reward_out = tx.vout[0];
const CAmount reward = reward_out.nValue;
if (nValueIn + reward < value_out) {
return state.DoS(100, false, REJECT_INVALID, "bad-cb-spends-too-much", false,
strprintf("value in (%s) + reward(%s) != value out (%s) in coinbase",
FormatMoney(nValueIn),
FormatMoney(reward),
FormatMoney(value_out)));
}
if (nValueIn + reward > value_out) {
return state.DoS(100, false, REJECT_INVALID, "bad-cb-spends-too-little", false,
strprintf("value in (%s) + reward(%s) != value out (%s) in coinbase",
FormatMoney(nValueIn),
FormatMoney(reward),
FormatMoney(value_out)));
}
} else if (!tx.IsCoinBase()) {
// All other transactions have to spend no more then their inputs. If they spend
// Coinbase outputs are validated in BlockRewardValidator::CheckBlockRewards
if (!tx.IsCoinBase()) {
// All non-coinbase transactions have to spend no more than their inputs. If they spend
// less, the change is counted towards the fees which are included in the reward
// of the coinbase transaction.
if (nValueIn < value_out) {
Expand Down
4 changes: 4 additions & 0 deletions src/injector.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <settings.h>
#include <staking/active_chain.h>
#include <staking/block_index_map.h>
#include <staking/block_reward_validator.h>
#include <staking/block_validator.h>
#include <staking/legacy_validation_interface.h>
#include <staking/network.h>
Expand Down Expand Up @@ -72,6 +73,9 @@ class UnitEInjector : public Injector<UnitEInjector> {
staking::BlockValidator,
staking::StakeValidator)

COMPONENT(BlockRewardValidator, staking::BlockRewardValidator, staking::BlockRewardValidator::New,
blockchain::Behavior)

COMPONENT(BlockDB, BlockDB, BlockDB::New)

COMPONENT(FinalizationParams, finalization::Params, finalization::Params::New,
Expand Down
76 changes: 76 additions & 0 deletions src/staking/block_reward_validator.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright (c) 2019 The Unit-e developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or https://opensource.org/licenses/MIT.

#include <staking/block_reward_validator.h>

#include <chain.h>
#include <consensus/validation.h>
#include <primitives/transaction.h>

#include <utilmoneystr.h>

namespace staking {

class BlockRewardValidatorImpl : public BlockRewardValidator {
private:
Dependency<blockchain::Behavior> m_behavior;

public:
BlockRewardValidatorImpl(
Dependency<blockchain::Behavior> behavior)
: m_behavior(behavior) {}

bool CheckBlockRewards(const CTransaction &coinbase_tx, CValidationState &state, const CBlockIndex &index,
CAmount input_amount, CAmount fees) const override {
assert(MoneyRange(input_amount));
assert(MoneyRange(fees));

CAmount total_reward = fees + m_behavior->CalculateBlockReward(index.nHeight);

std::size_t num_reward_outputs = 1;
if (coinbase_tx.vout.size() < num_reward_outputs) {
return state.DoS(100,
error("%s: too few coinbase outputs expected at least %d actual %d", __func__,
num_reward_outputs, coinbase_tx.vout.size()),
REJECT_INVALID, "bad-cb-too-few-outputs");
}

CAmount output_amount = coinbase_tx.GetValueOut();
Ruteri marked this conversation as resolved.
Show resolved Hide resolved
if (output_amount - input_amount > total_reward) {
return state.DoS(100,
error("%s: coinbase pays too much (total output=%d total input=%d expected reward=%d )",
__func__, FormatMoney(output_amount), FormatMoney(input_amount),
FormatMoney(total_reward)),
REJECT_INVALID, "bad-cb-amount");
}

// TODO UNIT-E: make the check stricter: if (output_amount - input_amount < total_reward)
if (output_amount - input_amount < total_reward - fees) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check allows proposers to burn fees.

return state.DoS(100,
error("%s: coinbase pays too little (total output=%d total input=%d expected reward=%d )",
__func__, FormatMoney(output_amount), FormatMoney(input_amount),
FormatMoney(total_reward)),
REJECT_INVALID, "bad-cb-spends-too-little");
}

CAmount non_reward_out = 0;
for (std::size_t i = num_reward_outputs; i < coinbase_tx.vout.size(); ++i) {
non_reward_out += coinbase_tx.vout[i].nValue;
}
if (non_reward_out > input_amount) {
return state.DoS(100,
error("%s: coinbase spends too much (non-reward output=%d total input=%d)", __func__,
FormatMoney(non_reward_out), FormatMoney(input_amount)),
REJECT_INVALID, "bad-cb-spends-too-much");
}

return true;
}
};

std::unique_ptr<BlockRewardValidator> BlockRewardValidator::New(
Dependency<blockchain::Behavior> behavior) {
return MakeUnique<BlockRewardValidatorImpl>(behavior);
}
} // namespace staking
37 changes: 37 additions & 0 deletions src/staking/block_reward_validator.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) 2019 The Unit-e developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or https://opensource.org/licenses/MIT.

#ifndef UNIT_E_STAKING_BLOCK_REWARD_VALIDATOR_H
#define UNIT_E_STAKING_BLOCK_REWARD_VALIDATOR_H

#include <amount.h>
#include <blockchain/blockchain_behavior.h>
#include <dependency.h>

#include <memory>

class CBlockIndex;
class CTransaction;
class CValidationState;

namespace staking {

class BlockRewardValidator {
public:
virtual bool CheckBlockRewards(
const CTransaction &coinbase_tx,
CValidationState &state,
const CBlockIndex &index,
CAmount input_amount,
CAmount fees) const = 0;

virtual ~BlockRewardValidator() = default;

static std::unique_ptr<BlockRewardValidator> New(
Dependency<blockchain::Behavior>);
};

} // namespace staking

#endif //UNIT_E_STAKING_BLOCK_REWARD_VALIDATOR_H
149 changes: 149 additions & 0 deletions src/test/staking/block_reward_validator_tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// Copyright (c) 2019 The Unit-e developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or https://opensource.org/licenses/MIT.

#include <blockchain/blockchain_parameters.h>
#include <consensus/validation.h>
#include <staking/block_reward_validator.h>

#include <test/test_unite.h>
#include <boost/test/unit_test.hpp>

namespace {

struct Fixture {

const CAmount total_reward = 10 * UNIT;
const CAmount immediate_reward = 1 * UNIT;

blockchain::Parameters parameters = [this]() {
blockchain::Parameters p = blockchain::Parameters::TestNet();
p.reward = total_reward;
return p;
}();

std::unique_ptr<blockchain::Behavior> b =
blockchain::Behavior::NewFromParameters(parameters);

CBlockIndex prev_block = [this]() {
CBlockIndex b;
b.nHeight = 100;
return b;
}();

const uint256 block_hash;
CBlockIndex block = [this]() {
CBlockIndex b;
b.pprev = &prev_block;
b.nHeight = prev_block.nHeight + 1;
b.phashBlock = &block_hash;
return b;
}();

CTransaction MakeCoinbaseTx(const std::vector<CAmount> &outputs) {
const CTxIn meta_input;
const CTxIn staking_input;

CMutableTransaction tx;
tx.SetType(TxType::COINBASE);
tx.vin = {meta_input, staking_input};
for (const auto out : outputs) {
tx.vout.emplace_back(out, CScript());
}
return tx;
}

std::unique_ptr<staking::BlockRewardValidator> GetBlockRewardValidator() {
return staking::BlockRewardValidator::New(b.get());
}
};

void CheckTransactionIsRejected(
const CTransaction &tx,
const std::string &rejection_reason,
const staking::BlockRewardValidator &validator,
const CBlockIndex &block,
const CAmount input_amount,
const CAmount fees) {
CValidationState validation_state;
const bool result = validator.CheckBlockRewards(tx, validation_state, block, input_amount, fees);
BOOST_CHECK(!result);
BOOST_CHECK(!validation_state.IsValid());
BOOST_CHECK_EQUAL(validation_state.GetRejectCode(), REJECT_INVALID);
BOOST_CHECK_EQUAL(validation_state.GetRejectReason(), rejection_reason);
}

} // namespace

BOOST_AUTO_TEST_SUITE(block_reward_validator_tests)

BOOST_AUTO_TEST_CASE(valid_reward) {
Fixture f;
const auto validator = f.GetBlockRewardValidator();

const CAmount input_amount = 10 * UNIT;
const CAmount fees = UNIT / 2;

auto test_valid_outputs = [&](const std::vector<CAmount> outputs) {
CTransaction tx = f.MakeCoinbaseTx(outputs);
CValidationState validation_state;

const bool result = validator->CheckBlockRewards(tx, validation_state, f.block, input_amount, fees);
BOOST_CHECK(result);
BOOST_CHECK(validation_state.IsValid());
};
test_valid_outputs({f.immediate_reward + fees, input_amount});
test_valid_outputs({f.immediate_reward + fees, input_amount / 2, input_amount / 2});
test_valid_outputs({f.immediate_reward + fees + input_amount});
test_valid_outputs({f.immediate_reward + input_amount});
}

BOOST_AUTO_TEST_CASE(total_output_is_too_large) {
Fixture f;
const auto validator = f.GetBlockRewardValidator();

const CAmount input_amount = 11 * UNIT;
const CAmount fees = UNIT / 2;

CheckTransactionIsRejected(
f.MakeCoinbaseTx({f.immediate_reward + fees + 1, input_amount}),
"bad-cb-amount", *validator, f.block, input_amount, fees);
CheckTransactionIsRejected(
f.MakeCoinbaseTx({f.immediate_reward + fees, input_amount + 1}),
"bad-cb-amount", *validator, f.block, input_amount, fees);
}

BOOST_AUTO_TEST_CASE(no_outputs) {
Fixture f;
const auto validator = f.GetBlockRewardValidator();

const CAmount input_amount = 11 * UNIT;
const CAmount fees = UNIT / 2;

CTransaction tx = f.MakeCoinbaseTx({});
CheckTransactionIsRejected(tx, "bad-cb-too-few-outputs", *validator, f.block, input_amount, fees);
}

BOOST_AUTO_TEST_CASE(total_output_is_too_small) {
Fixture f;
const auto validator = f.GetBlockRewardValidator();

const CAmount input_amount = 11 * UNIT;
const CAmount fees = UNIT / 2;

CTransaction tx = f.MakeCoinbaseTx({0, input_amount});
CheckTransactionIsRejected(tx, "bad-cb-spends-too-little", *validator, f.block, input_amount, fees);
}

BOOST_AUTO_TEST_CASE(non_reward_output_is_too_large) {
Fixture f;
const auto validator = f.GetBlockRewardValidator();

const CAmount input_amount = 15 * UNIT;
const CAmount fees = UNIT / 2;

CTransaction tx = f.MakeCoinbaseTx({f.immediate_reward, input_amount + fees});
CheckTransactionIsRejected(tx, "bad-cb-spends-too-much", *validator, f.block, input_amount, fees);
}

BOOST_AUTO_TEST_SUITE_END()
Loading