Skip to content

Commit

Permalink
Add pegin validation unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
stevenroose committed Jan 2, 2019
1 parent 5cbb014 commit 0e4e834
Show file tree
Hide file tree
Showing 3 changed files with 286 additions and 1 deletion.
5 changes: 4 additions & 1 deletion src/Makefile.test.include
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,10 @@ BITCOIN_TESTS =\
test/uint256_tests.cpp \
test/util_tests.cpp \
test/validation_block_tests.cpp \
test/versionbits_tests.cpp
test/versionbits_tests.cpp \
test/pegin_spent_tests.cpp \
test/pegin_witness_tests.cpp
# ELEMENTS IN THE END

if ENABLE_WALLET
BITCOIN_TESTS += \
Expand Down
146 changes: 146 additions & 0 deletions src/test/pegin_spent_tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// Copyright (c) 2011-2015 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <chainparams.h>
#include <coins.h>
#include <consensus/consensus.h>
#include <consensus/merkle.h>
#include <consensus/validation.h>
#include <validation.h>
#include <miner.h>
#include <pubkey.h>
#include <script/standard.h>
#include <txmempool.h>
#include <uint256.h>
#include <util.h>
#include <utilstrencodings.h>

#include <txdb.h>

#include <test/test_bitcoin.h>

#include <boost/test/unit_test.hpp>

BOOST_FIXTURE_TEST_SUITE(pegin_spent_tests, TestingSetup)

class CCoinsViewTester : public CCoinsView {
public:
bool IsPeginSpentCalled;
bool IsPeginSpent(const std::pair<uint256, COutPoint> &outpoint) const {
const_cast<bool&>(IsPeginSpentCalled) = true;
return CCoinsView::IsPeginSpent(outpoint);
}

CCoinsMap mapCoinsWritten;
bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) {
mapCoinsWritten.clear();
for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end(); it = mapCoins.erase(it)) {
mapCoinsWritten[it->first] = it->second;
}
//mapCoinsWritten = mapCoins;
return CCoinsView::BatchWrite(mapCoins, hashBlock);
}

CCoinsViewTester() : IsPeginSpentCalled(false) {}
};

BOOST_AUTO_TEST_CASE(PeginSpent_validity)
{
CCoinsViewTester coins;
CCoinsViewCache coinsCache(&coins);
Coin ret;

//Basic insert of blank outpoint pair, blank COutPoint allows for checking coinsCache

std::pair<uint256, COutPoint> outpoint = std::make_pair(GetRandHash(), COutPoint(GetRandHash(), 42));
BOOST_CHECK(!coinsCache.GetCoin(outpoint.second, ret));

//Checking for pegin spentness should not create an entry
BOOST_CHECK(!coinsCache.IsPeginSpent(outpoint));
BOOST_CHECK(coins.IsPeginSpentCalled);
coins.IsPeginSpentCalled = false;
BOOST_CHECK(!coinsCache.IsPeginSpent(outpoint));
BOOST_CHECK(!coins.IsPeginSpentCalled);

coinsCache.SetPeginSpent(outpoint, true);
BOOST_CHECK(coinsCache.IsPeginSpent(outpoint));
coinsCache.SetPeginSpent(outpoint, false);
BOOST_CHECK(!coinsCache.IsPeginSpent(outpoint));
coinsCache.SetPeginSpent(outpoint, true);
BOOST_CHECK(coinsCache.IsPeginSpent(outpoint));

//Check for slightly similar non-existent entries
std::pair<uint256, COutPoint> outpoint2(outpoint);
outpoint2.second.n = 0;
BOOST_CHECK(!coinsCache.IsPeginSpent(outpoint2));

CCoinsMap mapCoins;
CCoinsCacheEntry entry;
std::pair<uint256, COutPoint> outpoint3(std::make_pair(GetRandHash(), COutPoint(GetRandHash(), 42)));

//Attempt batch write of non-dirty pegin, no effect
entry.flags = CCoinsCacheEntry::PEGIN;
entry.peginSpent = true;
mapCoins.insert(std::make_pair(outpoint3, entry));
coinsCache.BatchWrite(mapCoins, uint256());
//Check for effect
coins.IsPeginSpentCalled = false;
BOOST_CHECK(!coinsCache.IsPeginSpent(outpoint3));
BOOST_CHECK(coins.IsPeginSpentCalled);
BOOST_CHECK(mapCoins.size() == 0);

//Write again with pegin, dirty && fresh flags, but unspent. No effect.
entry.peginSpent = false;
entry.flags |= CCoinsCacheEntry::DIRTY | CCoinsCacheEntry::FRESH;
mapCoins.insert(std::make_pair(outpoint3, entry));
coinsCache.BatchWrite(mapCoins, uint256());
//Check for effect
coins.IsPeginSpentCalled = false;
BOOST_CHECK(!coinsCache.IsPeginSpent(outpoint3));
BOOST_CHECK(coins.IsPeginSpentCalled);
BOOST_CHECK(mapCoins.size() == 0);

//Re-mark as spent. It's super effective.
entry.peginSpent = true;
mapCoins.insert(std::make_pair(outpoint3, entry));
coinsCache.BatchWrite(mapCoins, uint256());
//Check for effect
coins.IsPeginSpentCalled = false;
BOOST_CHECK(coinsCache.IsPeginSpent(outpoint3));
BOOST_CHECK(!coins.IsPeginSpentCalled);
BOOST_CHECK(mapCoins.size() == 0);

//Add an entry we never IsPeginSpent'd first (ie added to cache via SetPeginSpent)
std::pair<uint256, COutPoint> outpoint4(std::make_pair(GetRandHash(), COutPoint(GetRandHash(), 42)));
coinsCache.SetPeginSpent(outpoint4, true);

// Check the final state of coinsCache.mapCoins is sane.
BOOST_CHECK_EQUAL(coins.mapCoinsWritten.size(), 0);
coinsCache.Flush();
BOOST_CHECK_EQUAL(coins.mapCoinsWritten.size(), 4);
BOOST_CHECK_EQUAL(coins.mapCoinsWritten[outpoint].flags, CCoinsCacheEntry::DIRTY | CCoinsCacheEntry::FRESH | CCoinsCacheEntry::PEGIN);
BOOST_CHECK_EQUAL(coins.mapCoinsWritten[outpoint].peginSpent, true);
BOOST_CHECK_EQUAL(coins.mapCoinsWritten[outpoint2].flags, CCoinsCacheEntry::FRESH | CCoinsCacheEntry::PEGIN);
BOOST_CHECK_EQUAL(coins.mapCoinsWritten[outpoint2].peginSpent, false);
BOOST_CHECK_EQUAL(coins.mapCoinsWritten[outpoint3].flags, CCoinsCacheEntry::DIRTY | CCoinsCacheEntry::FRESH | CCoinsCacheEntry::PEGIN);
BOOST_CHECK_EQUAL(coins.mapCoinsWritten[outpoint3].peginSpent, true);
BOOST_CHECK_EQUAL(coins.mapCoinsWritten[outpoint4].flags, CCoinsCacheEntry::DIRTY | CCoinsCacheEntry::FRESH | CCoinsCacheEntry::PEGIN);
BOOST_CHECK_EQUAL(coins.mapCoinsWritten[outpoint3].peginSpent, true);

// CCoinsViewCache should lose outpoint2 in BatchWrite logic
CCoinsViewTester coins2;
CCoinsViewCache coinsCache2(&coins2);
BOOST_CHECK(coinsCache2.BatchWrite(coins.mapCoinsWritten, uint256()));
coinsCache2.Flush();
BOOST_CHECK_EQUAL(coins2.mapCoinsWritten.size(), 3);
BOOST_CHECK_EQUAL(coins2.mapCoinsWritten[outpoint].flags, CCoinsCacheEntry::DIRTY | CCoinsCacheEntry::FRESH | CCoinsCacheEntry::PEGIN);
BOOST_CHECK_EQUAL(coins2.mapCoinsWritten[outpoint].peginSpent, true);
BOOST_CHECK_EQUAL(coins2.mapCoinsWritten[outpoint3].flags, CCoinsCacheEntry::DIRTY | CCoinsCacheEntry::FRESH | CCoinsCacheEntry::PEGIN);
BOOST_CHECK_EQUAL(coins2.mapCoinsWritten[outpoint3].peginSpent, true);
BOOST_CHECK_EQUAL(coins2.mapCoinsWritten[outpoint4].flags, CCoinsCacheEntry::DIRTY | CCoinsCacheEntry::FRESH | CCoinsCacheEntry::PEGIN);
BOOST_CHECK_EQUAL(coins2.mapCoinsWritten[outpoint4].peginSpent, true);
}

BOOST_AUTO_TEST_SUITE_END()

136 changes: 136 additions & 0 deletions src/test/pegin_witness_tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// Copyright (c) 2017-2017 Blockstream
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <clientversion.h>
#include <chainparams.h>
#include <checkqueue.h>
#include <consensus/tx_verify.h>
#include <consensus/validation.h>
#include <core_io.h>
#include <validation.h> // For CheckTransaction
#include <pegins.h>
#include <policy/policy.h>
#include <script/script.h>
#include <script/script_error.h>
#include <utilstrencodings.h>
#include <validation.h>
#include <streams.h>
#include <test/test_bitcoin.h>
#include <util.h>

#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/test/unit_test.hpp>

std::vector<std::vector<unsigned char> > witness_stack = {
ParseHex("00ca9a3b00000000"),
ParseHex("e48a1a02a8f799892fda58347c2d794144311d4307dbfd10f77ffe28088c60be"),
ParseHex("06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f"),
ParseHex("00141eef6361cd1507a303834285d1521d6baf1b19ae"),
ParseHex("0200000001b399292c8100b8a1b66eb23896f799c1712390d560af0f70e81acd2d17a3b06e0000000049483045022100c3c749623486ea57ea93dfaf78d85590d78c7590a25768fe80f0ea4d6047419002202a0a00a90392b86c53c0fdda908c4591ba28040c16c25734c23b7df3c8b70acd01feffffff0228196bee000000001976a914470dd41542ee1a1bd75f1a838878648c8d65622488ac00ca9a3b0000000017a914cb60b1d7f76ba12b45a116c482c165a74c5d7e388765000000"),
ParseHex("000000205e3913a320cd2e3a2efa141e47419f54cb9e82320cf8dbc812fc19b9a1b2413a57f5e9fb4fa22de191454a241387f5d10cc794ee0fbf72ae2841baf3129a4eab8133025affff7f20000000000200000002f9d0be670007d38fceece999cb6144658a99c307ccc37f6d8f69129ed0f4545ff321df9790633bc33c67239c4174df8142ee616ee6a2e2788fe4820fe70e9bce0105")
};

//std::vector<unsigned char> pegin_transaction = ParseHex("020000000101f321df9790633bc33c67239c4174df8142ee616ee6a2e2788fe4820fe70e9bce0100004000ffffffff0201e48a1a02a8f799892fda58347c2d794144311d4307dbfd10f77ffe28088c60be01000000003b9ab2e0001976a914809326f7628dc976fbe63806479a1b8dfcc8c4b988ac01e48a1a02a8f799892fda58347c2d794144311d4307dbfd10f77ffe28088c60be010000000000001720000000000000000002483045022100ae17064745d80650a6a5cbcbe15c8c45ba498d1c6f45a7c0f5f32d871b463fc60220799f2836471702c21f7cfe124651727b530ad41f7af4dc213c65f5030a2f6fc4012103a9d3c6c7c161a565a76113632fe13330cf2c0207ba79a76d1154cdc3cb94d940060800ca9a3b0000000020e48a1a02a8f799892fda58347c2d794144311d4307dbfd10f77ffe28088c60be2006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f1600141eef6361cd1507a303834285d1521d6baf1b19aebe0200000001b399292c8100b8a1b66eb23896f799c1712390d560af0f70e81acd2d17a3b06e0000000049483045022100c3c749623486ea57ea93dfaf78d85590d78c7590a25768fe80f0ea4d6047419002202a0a00a90392b86c53c0fdda908c4591ba28040c16c25734c23b7df3c8b70acd01feffffff0228196bee000000001976a914470dd41542ee1a1bd75f1a838878648c8d65622488ac00ca9a3b0000000017a914cb60b1d7f76ba12b45a116c482c165a74c5d7e38876500000097000000205e3913a320cd2e3a2efa141e47419f54cb9e82320cf8dbc812fc19b9a1b2413a57f5e9fb4fa22de191454a241387f5d10cc794ee0fbf72ae2841baf3129a4eab8133025affff7f20000000000200000002f9d0be670007d38fceece999cb6144658a99c307ccc37f6d8f69129ed0f4545ff321df9790633bc33c67239c4174df8142ee616ee6a2e2788fe4820fe70e9bce010500000000");
std::vector<unsigned char> pegin_transaction = ParseHex("02000000000101f321df9790633bc33c67239c4174df8142ee616ee6a2e2788fe4820fe70e9bce0100004000ffffffff02e0b29a3b000000001976a914809326f7628dc976fbe63806479a1b8dfcc8c4b988ac20170000000000000002483045022100ae17064745d80650a6a5cbcbe15c8c45ba498d1c6f45a7c0f5f32d871b463fc60220799f2836471702c21f7cfe124651727b530ad41f7af4dc213c65f5030a2f6fc4012103a9d3c6c7c161a565a76113632fe13330cf2c0207ba79a76d1154cdc3cb94d940060800ca9a3b0000000020e48a1a02a8f799892fda58347c2d794144311d4307dbfd10f77ffe28088c60be2006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f1600141eef6361cd1507a303834285d1521d6baf1b19aebe0200000001b399292c8100b8a1b66eb23896f799c1712390d560af0f70e81acd2d17a3b06e0000000049483045022100c3c749623486ea57ea93dfaf78d85590d78c7590a25768fe80f0ea4d6047419002202a0a00a90392b86c53c0fdda908c4591ba28040c16c25734c23b7df3c8b70acd01feffffff0228196bee000000001976a914470dd41542ee1a1bd75f1a838878648c8d65622488ac00ca9a3b0000000017a914cb60b1d7f76ba12b45a116c482c165a74c5d7e38876500000097000000205e3913a320cd2e3a2efa141e47419f54cb9e82320cf8dbc812fc19b9a1b2413a57f5e9fb4fa22de191454a241387f5d10cc794ee0fbf72ae2841baf3129a4eab8133025affff7f20000000000200000002f9d0be670007d38fceece999cb6144658a99c307ccc37f6d8f69129ed0f4545ff321df9790633bc33c67239c4174df8142ee616ee6a2e2788fe4820fe70e9bce010500000000");

COutPoint prevout(uint256S("ce9b0ee70f82e48f78e2a2e66e61ee4281df74419c23673cc33b639097df21f3"), 1);

// Needed for easier parent PoW check, and setting fedpegscript
struct RegtestingSetup : public TestingSetup {
RegtestingSetup() : TestingSetup("custom", "512103dff4923d778550cc13ce0d887d737553b4b58f4e8e886507fc39f5e447b2186451ae") {}
};

BOOST_FIXTURE_TEST_SUITE(pegin_witness_tests, RegtestingSetup)

BOOST_AUTO_TEST_CASE(witness_valid)
{
CScriptWitness witness;
witness.stack = witness_stack;

BOOST_CHECK(IsValidPeginWitness(witness, prevout));

// Missing byte on each field to make claim ill-formatted
// This will break deserialization and other data-matching checks
for (unsigned int i = 0; i < witness.stack.size(); i++) {
//TODO(rebase) CA remove this exception
if (i == 1) {
continue;
}
witness.stack[i].pop_back();
BOOST_CHECK(!IsValidPeginWitness(witness, prevout));
witness.stack = witness_stack;
BOOST_CHECK(IsValidPeginWitness(witness, prevout));
}

// Test mismatched but valid nOut to proof
COutPoint fake_prevout = prevout;
fake_prevout.n = 0;
BOOST_CHECK(!IsValidPeginWitness(witness, fake_prevout));

// Test mistmatched but valid txid
fake_prevout = prevout;
fake_prevout.hash = uint256S("2f103ee04a5649eecb932b4da4ca9977f53a12bbe04d9d1eb5ccc0f4a06334");
BOOST_CHECK(!IsValidPeginWitness(witness, fake_prevout));

// Ensure that all witness stack sizes are handled
BOOST_CHECK(IsValidPeginWitness(witness, prevout));
for (unsigned int i = 0; i < witness.stack.size(); i++) {
witness.stack.pop_back();
BOOST_CHECK(!IsValidPeginWitness(witness, prevout));
}
witness.stack = witness_stack;

// Extra element causes failure
witness.stack.push_back(witness.stack.back());
BOOST_CHECK(!IsValidPeginWitness(witness, prevout));
witness.stack = witness_stack;

// Check validation of peg-in transaction's inputs and balance
CDataStream ssTx(pegin_transaction, SER_NETWORK, PROTOCOL_VERSION);
CTransactionRef txRef;
ssTx >> txRef;
CTransaction tx(*txRef);

// Only one(valid) input witness should exist, and should match
BOOST_CHECK(tx.vin[0].m_pegin_witness.stack == witness_stack);
BOOST_CHECK(tx.vin[0].m_is_pegin);
// Check that serialization doesn't cause issuance to become non-null
//TODO(rebase) CA
//BOOST_CHECK(tx.vin[0].assetIssuance.IsNull());
BOOST_CHECK(IsValidPeginWitness(tx.vin[0].m_pegin_witness, prevout));

std::set<std::pair<uint256, COutPoint> > setPeginsSpent;
CValidationState state;
CCoinsView coinsDummy;
CCoinsViewCache coins(&coinsDummy);
CAmount txfee;
BOOST_CHECK(Consensus::CheckTxInputs(tx, state, coins, 0, txfee, setPeginsSpent));
BOOST_CHECK(setPeginsSpent.size() == 1);
setPeginsSpent.clear();

// Strip pegin_witness
CMutableTransaction mtxn(tx);
mtxn.vin[0].m_pegin_witness.SetNull();
CTransaction tx2(mtxn);
BOOST_CHECK(!Consensus::CheckTxInputs(tx2, state, coins, 0, txfee, setPeginsSpent));
BOOST_CHECK(setPeginsSpent.empty());

// Invalidate peg-in (and spending) authorization by pegin marker.
// This only checks for peg-in authorization, with the only input marked
// as m_is_pegin
CMutableTransaction mtxn2(tx);
mtxn2.vin[0].m_is_pegin = false;
CTransaction tx3(mtxn2);
BOOST_CHECK(!Consensus::CheckTxInputs(tx3, state, coins, 0, txfee, setPeginsSpent));
BOOST_CHECK(setPeginsSpent.empty());


// TODO Test mixed pegin/non-pegin input case
// TODO Test spending authorization in conjunction with valid witness program in pegin auth

}

BOOST_AUTO_TEST_SUITE_END()

0 comments on commit 0e4e834

Please sign in to comment.