Skip to content
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
1 change: 1 addition & 0 deletions divi/src/Makefile.test.include
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ BITCOIN_TESTS =\
test/FakeBlockIndexChain.cpp \
test/getarg_tests.cpp \
test/hash_tests.cpp \
test/kernel_tests.cpp \
test/key_tests.cpp \
test/main_tests.cpp \
test/mempool_tests.cpp \
Expand Down
46 changes: 46 additions & 0 deletions divi/src/kernel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,16 @@
#include <primitives/block.h>
#include "blockmap.h"
#include "BlockDiskAccessor.h"
#include "BlockRewards.h"
#include "chain.h"
#include "chainparams.h"
#include "coins.h"
#include "script/interpreter.h"
#include "script/SignatureCheckers.h"
#include "script/standard.h"
#include "script/StakingVaultScript.h"
#include <streams.h>
#include "utilmoneystr.h"
#include "utilstrencodings.h"

#include <boost/assign/list_of.hpp>
Expand Down Expand Up @@ -329,3 +333,45 @@ bool CheckProofOfStake(const CBlock& block, int blockHeight, uint256& hashProofO

return true;
}

bool CheckCoinstakeForVaults(const CTransaction& tx, const CBlockRewards& expectedRewards,
const CCoinsViewCache& view)
{
if (!tx.IsCoinStake())
return error("%s: transaction is not a coinstake", __func__);

CAmount nValueIn = 0;
bool foundVault = false;
CScript vaultScript;
for (const auto& in : tx.vin) {
const auto& prevOut = view.GetOutputFor(in);
nValueIn += prevOut.nValue;
if (!IsStakingVaultScript(prevOut.scriptPubKey))
continue;

if (foundVault) {
/* CheckProofOfStake already verifies that all inputs used are
from a single script. */
assert(vaultScript == prevOut.scriptPubKey);
} else {
foundVault = true;
vaultScript = prevOut.scriptPubKey;
}
}

if (!foundVault)
return true;

assert(tx.vout.size() >= 2);
const CTxOut& rewardOut = tx.vout[1];

if (rewardOut.scriptPubKey != vaultScript)
return error("%s: output is not sent back to the vault input script", __func__);

const CAmount expectedOutput = nValueIn + expectedRewards.nStakeReward;
if (rewardOut.nValue < expectedOutput)
return error("%s: expected output to be at least %s, got only %s",
__func__, FormatMoney(expectedOutput), FormatMoney(rewardOut.nValue));

return true;
}
12 changes: 12 additions & 0 deletions divi/src/kernel.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
#include <amount.h>
#include <map>
class CBlockIndex;
class CBlockRewards;
class CBlock;
class CCoinsViewCache;
class CTransaction;
class COutPoint;
class BlockMap;
Expand All @@ -24,6 +26,16 @@ bool CheckProofOfStake(
int blockHeight,
uint256& hashProofOfStake);

/** Checks if the transaction is a valid coinstake after the staking vault
* fork (which adds extra rules, like paying back at least the expected
* staking reward to the same script that the staking input came from).
* Note that the extra conditions only apply to actual stake inputs that
* are vault scripts; if the tx is a coinstake but the input is not a vault,
* then the fucntion just returns true without further checks. */
bool CheckCoinstakeForVaults(const CTransaction& tx,
const CBlockRewards& expectedRewards,
const CCoinsViewCache& view);

// Check stake modifier hard checkpoints
bool CheckStakeModifierCheckpoints(
int nHeight,
Expand Down
185 changes: 185 additions & 0 deletions divi/src/test/kernel_tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
// Copyright (c) 2020 The Divi developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "BlockRewards.h"
#include "coins.h"
#include "hash.h"
#include "kernel.h"
#include "script/StakingVaultScript.h"
#include "utilstrencodings.h"

#include <boost/test/unit_test.hpp>
#include "test_only.h"

namespace
{

/**
* Fixture for tests of CheckCoinstakeForVaults. It mostly sets up
* a coins view with some example coins in it and an example
* vault script.
*/
class CheckCoinstakeForVaultsTestFixture
{

private:

CCoinsView coinsDummy;
CCoinsViewCache coins;

const CBlockRewards rewards;

protected:

CScript scriptVault;
CScript scriptOtherVault;
CScript scriptNonVault;

std::vector<COutPoint> vaultCoins;
std::vector<COutPoint> nonVaultCoins;

CheckCoinstakeForVaultsTestFixture()
: coins(&coinsDummy), rewards(CENT, 0, 0, 0, 0, 0)
{
const std::vector<unsigned char> key1(20, 'x');
const std::vector<unsigned char> key2(20, 'y');
const std::vector<unsigned char> key3(20, 'z');

scriptVault = CreateStakingVaultScript(key1, key2);
scriptOtherVault = CreateStakingVaultScript(key3, key2);
scriptNonVault = CScript() << OP_TRUE;

BOOST_CHECK(IsStakingVaultScript(scriptVault));
BOOST_CHECK(IsStakingVaultScript(scriptOtherVault));
BOOST_CHECK(!IsStakingVaultScript(scriptNonVault));

CMutableTransaction dummyTxVault;
CMutableTransaction dummyTxNonVault;
dummyTxVault.vout.resize(2);
dummyTxNonVault.vout.resize(dummyTxVault.vout.size());
for (unsigned i = 0; i < dummyTxVault.vout.size(); ++i) {
dummyTxVault.vout[i].nValue = COIN;
dummyTxVault.vout[i].scriptPubKey = scriptVault;
dummyTxNonVault.vout[i].nValue = COIN;
dummyTxNonVault.vout[i].scriptPubKey = scriptNonVault;
}

coins.ModifyCoins(dummyTxVault.GetHash())->FromTx(dummyTxVault, 0);
coins.ModifyCoins(dummyTxNonVault.GetHash())->FromTx(dummyTxNonVault, 0);

for (unsigned i = 0; i < dummyTxVault.vout.size(); ++i) {
vaultCoins.emplace_back(dummyTxVault.GetHash(), i);
nonVaultCoins.emplace_back(dummyTxNonVault.GetHash(), i);
}
}

bool RunCheck(const CMutableTransaction& mtx)
{
return CheckCoinstakeForVaults(CTransaction(mtx), rewards, coins);
}

};

BOOST_FIXTURE_TEST_SUITE(CheckCoinstakeForVaults_tests, CheckCoinstakeForVaultsTestFixture)

BOOST_AUTO_TEST_CASE(willFailNonCoinstakeTransactions)
{
CMutableTransaction mtx;
mtx.vin.push_back(CTxIn(nonVaultCoins[0]));
mtx.vout.push_back(CTxOut(1, scriptNonVault));
mtx.vout.push_back(CTxOut(COIN + CENT, scriptNonVault));

BOOST_CHECK(!CTransaction(mtx).IsCoinStake());
BOOST_CHECK(!RunCheck(mtx));

mtx.vout[0].SetEmpty();
BOOST_CHECK(CTransaction(mtx).IsCoinStake());
BOOST_CHECK(RunCheck(mtx));
}

BOOST_AUTO_TEST_CASE(willAllowSplittingOfInputPlusRewardIntoTwoOutputs)
{
CMutableTransaction mtx;
mtx.vin.push_back(CTxIn(nonVaultCoins[0]));
mtx.vin.push_back(CTxIn(nonVaultCoins[1]));

mtx.vout.push_back(CTxOut());
mtx.vout[0].SetEmpty();
mtx.vout.push_back(CTxOut(COIN / 2, scriptNonVault));
mtx.vout.push_back(CTxOut(COIN / 2, scriptNonVault));

BOOST_CHECK(CTransaction(mtx).IsCoinStake());
BOOST_CHECK(RunCheck(mtx));
}

BOOST_AUTO_TEST_CASE(willAllowCorrectVaultPayment)
{
CMutableTransaction mtx;
mtx.vin.push_back(CTxIn(vaultCoins[0]));
mtx.vin.push_back(CTxIn(vaultCoins[1]));

mtx.vout.push_back(CTxOut());
mtx.vout[0].SetEmpty();
mtx.vout.push_back(CTxOut(2 * COIN + CENT, scriptVault));
mtx.vout.push_back(CTxOut(CENT, scriptNonVault));
BOOST_CHECK(CTransaction(mtx).IsCoinStake());
BOOST_CHECK(RunCheck(mtx));

mtx.vout[1].nValue += 1;
BOOST_CHECK(RunCheck(mtx));
}

BOOST_AUTO_TEST_CASE(willNotAllowPaymentToNonVault)
{
CMutableTransaction mtx;
mtx.vin.push_back(CTxIn(vaultCoins[0]));

mtx.vout.push_back(CTxOut());
mtx.vout[0].SetEmpty();
mtx.vout.push_back(CTxOut(COIN + CENT, scriptNonVault));

BOOST_CHECK(CTransaction(mtx).IsCoinStake());
BOOST_CHECK(!RunCheck(mtx));

mtx.vout.push_back(CTxOut());
mtx.vout[0].SetEmpty();
mtx.vout.push_back(CTxOut(COIN + CENT, scriptOtherVault));

BOOST_CHECK(CTransaction(mtx).IsCoinStake());
BOOST_CHECK(!RunCheck(mtx));
}

BOOST_AUTO_TEST_CASE(willDisallowVaultToUnderpay)
{
CMutableTransaction mtx;
mtx.vin.push_back(CTxIn(vaultCoins[0]));

mtx.vout.push_back(CTxOut());
mtx.vout[0].SetEmpty();
mtx.vout.push_back(CTxOut(COIN + CENT - 1, scriptVault));

BOOST_CHECK(CTransaction(mtx).IsCoinStake());
BOOST_CHECK(!RunCheck(mtx));

mtx.vout[1].nValue += 1;
BOOST_CHECK(RunCheck(mtx));
}

BOOST_AUTO_TEST_CASE(willNotAllowStakingOutputToBeSplit)
{
CMutableTransaction mtx;
mtx.vin.push_back(CTxIn(vaultCoins[0]));

mtx.vout.push_back(CTxOut());
mtx.vout[0].SetEmpty();
mtx.vout.push_back(CTxOut(COIN, scriptVault));
mtx.vout.push_back(CTxOut(CENT, scriptVault));

BOOST_CHECK(CTransaction(mtx).IsCoinStake());
BOOST_CHECK(!RunCheck(mtx));
}

BOOST_AUTO_TEST_SUITE_END()

} // anonymous namespace