tests for getting MoM in Eval and fixes

This commit is contained in:
Scott Sadler
2018-04-06 03:52:30 -03:00
parent e625be68a9
commit 9bf132a5a8
9 changed files with 307 additions and 91 deletions

View File

@@ -6,7 +6,8 @@ bin_PROGRAMS += komodo-test
komodo_test_SOURCES = \
test-komodo/main.cpp \
test-komodo/test_cryptoconditions.cpp \
test-komodo/test_bet.cpp
test-komodo/test_eval_bet.cpp \
test-komodo/test_eval_notarisation.cpp
komodo_test_CPPFLAGS = $(komodod_CPPFLAGS)

View File

@@ -7,44 +7,6 @@
#include "primitives/transaction.h"
static unsigned char* CopyPubKey(CPubKey pkIn)
{
unsigned char* pk = (unsigned char*) malloc(33);
memcpy(pk, pkIn.begin(), 33); // TODO: compressed?
return pk;
}
CC* CCNewThreshold(int t, std::vector<CC*> v)
{
CC *cond = cc_new(CC_Threshold);
cond->threshold = t;
cond->size = v.size();
cond->subconditions = (CC**) calloc(v.size(), sizeof(CC*));
memcpy(cond->subconditions, v.data(), v.size() * sizeof(CC*));
return cond;
}
CC* CCNewSecp256k1(CPubKey k)
{
CC *cond = cc_new(CC_Secp256k1);
cond->publicKey = CopyPubKey(k);
return cond;
}
CC* CCNewEval(std::string method, std::vector<unsigned char> paramsBin)
{
CC *cond = cc_new(CC_Eval);
strcpy(cond->method, method.data());
cond->paramsBin = (unsigned char*) malloc(paramsBin.size());
memcpy(cond->paramsBin, paramsBin.data(), paramsBin.size());
cond->paramsBinLength = paramsBin.size();
return cond;
}
std::vector<CC*> BetProtocol::PlayerConditions()
{
std::vector<CC*> subs;

View File

@@ -2,13 +2,11 @@
#define BETPROTOCOL_H
#include "pubkey.h"
#include "primitives/block.h"
#include "primitives/transaction.h"
#include "cryptoconditions/include/cryptoconditions.h"
#define ExecMerkle CBlock::CheckMerkleBranch
class MoMProof
{
public:
@@ -18,6 +16,7 @@ public:
MoMProof() {}
MoMProof(int i, std::vector<uint256> b, uint256 n) : notarisationHash(n), nIndex(i), branch(b) {}
uint256 Exec(uint256 hash) const { return CBlock::CheckMerkleBranch(hash, branch, nIndex); }
ADD_SERIALIZE_METHODS;
@@ -79,7 +78,4 @@ public:
};
CC* CCNewSecp256k1(CPubKey k);
#endif /* BETPROTOCOL_H */

View File

@@ -88,14 +88,19 @@ bool Eval::GetBlock(uint256 hash, CBlockIndex& blockIdx) const
extern int32_t komodo_notaries(uint8_t pubkeys[64][33],int32_t height,uint32_t timestamp);
int32_t Eval::GetNotaries(uint8_t pubkeys[64][33], int32_t height, uint32_t timestamp) const
{
return komodo_notaries(pubkeys, height, timestamp);
}
bool Eval::CheckNotaryInputs(const CTransaction &tx, uint32_t height, uint32_t timestamp) const
{
if (tx.vin.size() < 11) return false;
uint8_t seenNotaries[64] = {0};
uint8_t notaries[64][33];
uint8_t seenNotaries[64];
int nNotaries = komodo_notaries(notaries, height, timestamp);
char pk[33];
int nNotaries = GetNotaries(notaries, height, timestamp);
BOOST_FOREACH(const CTxIn &txIn, tx.vin)
{
@@ -104,10 +109,11 @@ bool Eval::CheckNotaryInputs(const CTransaction &tx, uint32_t height, uint32_t t
uint256 hashBlock;
if (!GetTx(txIn.prevout.hash, tx, hashBlock, false)) return false;
if (tx.vout.size() < txIn.prevout.n) return false;
const unsigned char *script = tx.vout[txIn.prevout.n].scriptPubKey.data();
if (script[0] != 33) return false;
memcpy(pk, script+1, 33);
return true;
CScript spk = tx.vout[txIn.prevout.n].scriptPubKey;
if (spk.size() != 35) return false;
const unsigned char *pk = spk.data();
if (pk++[0] != 33) return false;
if (pk[33] != OP_CHECKSIG) return false;
// Check it's a notary
for (int i=0; i<nNotaries; i++) {
@@ -121,31 +127,51 @@ bool Eval::CheckNotaryInputs(const CTransaction &tx, uint32_t height, uint32_t t
return false;
found:;
}
return true;
}
bool NotarisationData::Parse(const CScript scriptPK)
{
*this = NotarisationData();
std::vector<unsigned char> vdata;
if (!GetOpReturnData(scriptPK, vdata)) return false;
CDataStream ss(vdata, SER_NETWORK, PROTOCOL_VERSION);
try {
ss >> blockHash;
ss >> height;
char *nullPos = (char*) memchr(&ss[0], 0, ss.size());
if (!nullPos) return false;
ss.read(symbol, nullPos-&ss[0]+1);
if (ss.size() != 36) return false;
ss >> MoM;
ss >> MoMDepth;
} catch (...) {
return false;
}
return true;
}
/*
* Get MoM from a notarisation tx hash
*/
bool Eval::GetMoM(const uint256 notaryHash, uint256 &mom) const
bool Eval::GetNotarisationData(const uint256 notaryHash, NotarisationData &data) const
{
CTransaction notarisationTx;
uint256 notarisationBlock;
if (!GetTx(notaryHash, notarisationTx, notarisationBlock, true)) return 0;
if (!GetTx(notaryHash, notarisationTx, notarisationBlock, true)) return false;
CBlockIndex block;
if (!GetBlock(notarisationBlock, block)) return 0;
if (!CheckNotaryInputs(notarisationTx, block.nHeight, block.nTime)) {
return false;
}
if (!notarisationTx.vout.size() < 1) return 0;
std::vector<unsigned char> opret;
if (!GetOpReturnData(notarisationTx.vout[0].scriptPubKey, opret)) return 0;
if (opret.size() < 36) return 0; // In reality it is more than 36, but at the moment I
// only know where it is relative to the end, and this
// is enough to prevent a memory fault. In the case that
// the assumption about the presence of a MoM at this
// offset fails, we will return random other data that is
// not more likely to generate a false positive.
memcpy(mom.begin(), opret.data()+opret.size()-36, 32);
return 1;
if (!GetBlock(notarisationBlock, block)) return false;
if (!CheckNotaryInputs(notarisationTx, block.nHeight, block.nTime)) return false;
if (notarisationTx.vout.size() < 2) return false;
if (!data.Parse(notarisationTx.vout[1].scriptPubKey)) return false;
return true;
}

View File

@@ -11,6 +11,7 @@
class AppVM;
class NotarisationData;
class Eval
@@ -44,7 +45,8 @@ public:
virtual unsigned int GetCurrentHeight() const;
virtual bool GetSpends(uint256 hash, std::vector<CTransaction> &spends) const;
virtual bool GetBlock(uint256 hash, CBlockIndex& blockIdx) const;
virtual bool GetMoM(uint256 notarisationHash, uint256& MoM) const;
virtual int32_t GetNotaries(uint8_t pubkeys[64][33], int32_t height, uint32_t timestamp) const;
virtual bool GetNotarisationData(uint256 notarisationHash, NotarisationData &data) const;
virtual bool CheckNotaryInputs(const CTransaction &tx, uint32_t height, uint32_t timestamp) const;
};
@@ -69,16 +71,25 @@ public:
};
/*
* Data from notarisation OP_RETURN
*/
class NotarisationData {
public:
uint256 blockHash;
uint32_t height;
uint256 txHash;
char symbol[64];
uint256 MoM;
uint32_t MoMDepth;
bool Parse(CScript scriptPubKey);
};
/*
* Serialisation boilerplate
*/
template <class T>
std::vector<unsigned char> CheckSerialize(T &in);
template <class T>
bool CheckDeserialize(std::vector<unsigned char> vIn, T &out);
template <class T>
std::vector<unsigned char> CheckSerialize(T &in)
{
@@ -87,7 +98,6 @@ std::vector<unsigned char> CheckSerialize(T &in)
return std::vector<unsigned char>(ss.begin(), ss.end());
}
template <class T>
bool CheckDeserialize(std::vector<unsigned char> vIn, T &out)
{

View File

@@ -69,10 +69,10 @@ bool Eval::ImportPayout(const CC *cond, const CTransaction &payoutTx, unsigned i
if (!CheckDeserialize(vProof, proof))
return Invalid("invalid-mom-proof-payload");
uint256 MoM;
if (!GetMoM(proof.notarisationHash, MoM)) return Invalid("coudnt-load-mom");
NotarisationData data;
if (!GetNotarisationData(proof.notarisationHash, data)) return Invalid("coudnt-load-mom");
if (MoM != ExecMerkle(disputeTx.GetHash(), proof.branch, proof.nIndex))
if (data.MoM != proof.Exec(disputeTx.GetHash()))
return Invalid("mom-check-fail");
}

View File

@@ -635,7 +635,7 @@ UniValue txMoMproof(const UniValue& params, bool fHelp)
if ( fHelp || params.size() != 1)
throw runtime_error("txmomproof needs a txid");
uint256 hash(uint256S(params[0].get_str()));
hash = uint256S(params[0].get_str());
uint256 blockHash;
CTransaction tx;
@@ -666,6 +666,10 @@ UniValue txMoMproof(const UniValue& params, bool fHelp)
fakeBlock.vtx.push_back(fakeTx);
}
branch = fakeBlock.GetMerkleBranch(nIndex);
// Check branch
if (MoM != CBlock::CheckMerkleBranch(blockIndex->hashMerkleRoot, branch, nIndex))
throw JSONRPCError(RPC_INTERNAL_ERROR, "Failed merkle block->MoM");
}
// Now get the tx merkle branch
@@ -687,12 +691,21 @@ UniValue txMoMproof(const UniValue& params, bool fHelp)
if (nTxIndex == (int)block.vtx.size())
throw JSONRPCError(RPC_INTERNAL_ERROR, "Error locating tx in block");
// concatenate branches
std::vector<uint256> txBranch = block.GetMerkleBranch(nTxIndex);
nIndex = nIndex << txBranch.size() + nTxIndex;
// Check branch
if (block.hashMerkleRoot != CBlock::CheckMerkleBranch(hash, txBranch, nTxIndex))
throw JSONRPCError(RPC_INTERNAL_ERROR, "Failed merkle tx->block");
// concatenate branches
nIndex = (nIndex << txBranch.size()) + nTxIndex;
branch.insert(branch.begin(), txBranch.begin(), txBranch.end());
}
// Check the proof
if (MoM != CBlock::CheckMerkleBranch(hash, branch, nIndex))
throw JSONRPCError(RPC_INTERNAL_ERROR, "Failed validating MoM");
// Encode and return
CDataStream ssProof(SER_NETWORK, PROTOCOL_VERSION);
ssProof << MoMProof(nIndex, branch, notarisationHash);

View File

@@ -14,6 +14,11 @@
#include "testutils.h"
extern Eval* EVAL_TEST;
namespace TestBet {
static std::vector<CKey> playerSecrets;
static std::vector<CPubKey> players;
@@ -72,7 +77,7 @@ public:
std::map<uint256, CBlockIndex> blocks;
std::map<uint256, std::vector<CTransaction>> spends;
bool Dispatch(const CC *cond, const CTransaction &txTo, unsigned int nIn)
bool Dispatch(const CC *cond, const CTransaction &txTo, unsigned int nIn)
{
if (strcmp(cond->method, "DisputeBet") == 0) {
MockVM vm;
@@ -115,10 +120,10 @@ public:
return true;
}
bool GetMoM(uint256 notarisationHash, uint256& _MoM) const
bool GetNotarisationData(uint256 notarisationHash, NotarisationData &data) const
{
if (notarisationHash == NotarisationHash()) {
_MoM = MoM;
data.MoM = MoM;
return true;
}
return false;
@@ -133,9 +138,6 @@ public:
};
extern Eval* EVAL_TEST;
/*
* Generates example data that we will test with and shows how to call BetProtocol.
*/
@@ -230,7 +232,7 @@ public:
eval.currentHeight = currentHeight;
MoMProof proof = GetMoMProof();
eval.MoM = ExecMerkle(DisputeTx(Player2).GetHash(), proof.branch, proof.nIndex);
eval.MoM = proof.Exec(DisputeTx(Player2).GetHash());
EVAL_TEST = &eval;
return eval;
@@ -594,3 +596,6 @@ TEST_F(TestBet, testImportPayoutMomFail)
EXPECT_FALSE(TestCC(importTx, 0, payoutCond));
EXPECT_EQ("mom-check-fail", eval.state.GetRejectReason());
}
} /* namespace TestBet */

View File

@@ -0,0 +1,203 @@
#include <cryptoconditions.h>
#include <gtest/gtest.h>
#include "cc/betprotocol.h"
#include "cc/eval.h"
#include "base58.h"
#include "core_io.h"
#include "key.h"
#include "main.h"
#include "komodo_cc.h"
#include "primitives/transaction.h"
#include "script/interpreter.h"
#include "script/serverchecker.h"
#include "testutils.h"
extern Eval* EVAL_TEST;
extern int32_t komodo_notaries(uint8_t pubkeys[64][33],int32_t height,uint32_t timestamp);
namespace TestEvalNotarisation {
class EvalMock : public Eval
{
public:
uint32_t nNotaries;
uint8_t notaries[64][33];
std::map<uint256, CTransaction> txs;
std::map<uint256, CBlockIndex> blocks;
int32_t GetNotaries(uint8_t pubkeys[64][33], int32_t height, uint32_t timestamp) const
{
memcpy(pubkeys, notaries, sizeof(notaries));
return nNotaries;
}
bool GetTx(const uint256 &hash, CTransaction &txOut, uint256 &hashBlock, bool fAllowSlow) const
{
auto r = txs.find(hash);
if (r != txs.end()) {
txOut = r->second;
hashBlock = hash;
return true;
}
return false;
}
bool GetBlock(uint256 hash, CBlockIndex& blockIdx) const
{
auto r = blocks.find(hash);
if (r == blocks.end()) return false;
blockIdx = r->second;
return true;
}
};
static auto noop = [&](CMutableTransaction &mtx){};
template<typename Modifier>
void SetEval(EvalMock &eval, CMutableTransaction &notary, Modifier modify)
{
eval.nNotaries = komodo_notaries(eval.notaries, 780060, 1522946781);
// make fake notary inputs
notary.vin.resize(11);
for (int i=0; i<notary.vin.size(); i++) {
CMutableTransaction txIn;
txIn.vout.resize(1);
txIn.vout[0].scriptPubKey << VCH(eval.notaries[i*2], 33) << OP_CHECKSIG;
notary.vin[i].prevout = COutPoint(txIn.GetHash(), 0);
eval.txs[txIn.GetHash()] = CTransaction(txIn);
}
modify(notary);
eval.txs[notary.GetHash()] = CTransaction(notary);
eval.blocks[notary.GetHash()].nHeight = 780060;
eval.blocks[notary.GetHash()].nTime = 1522946781;
EVAL_TEST = &eval;
}
// https://kmd.explorer.supernet.org/tx/5b8055d37cff745a404d1ae45e21ffdba62da7b28ed6533c67468d7379b20bae
// inputs have been dropped
static auto rawNotaryTx = "01000000000290460100000000002321020e46e79a2a8d12b9b5d12c7a91adb4e454edfae43c0a0cb805427d2ac7613fd9ac0000000000000000506a4c4dae8e0f3e6e5de498a072f5967f3c418c4faba5d56ac8ce17f472d029ef3000008f2e0100424f545300050ba773f0bc31da5839fc7cb9bd7b87f3b765ca608e5cf66785a466659b28880500000000000000";
CTransaction notaryTx;
static bool init = DecodeHexTx(notaryTx, rawNotaryTx);
static uint256 proofTxHash = uint256S("37f76551a16093fbb0a92ee635bbd45b3460da8fd00cf7d5a6b20d93e727fe4c");
static auto vMomProof = ParseHex("0303faecbdd4b3da128c2cd2701bb143820a967069375b2ec5b612f39bbfe78a8611978871c193457ab1e21b9520f4139f113b8d75892eb93ee247c18bccfd067efed7eacbfcdc8946cf22de45ad536ec0719034fb9bc825048fe6ab61fee5bd6e9aae0bb279738d46673c53d68eb2a72da6dbff215ee41a4d405a74ff7cd355805b"); // $ fiat/bots txMoMproof $proofTxHash
TEST(TestEvalNotarisation, testGetNotarisation)
{
EvalMock eval;
CMutableTransaction notary(notaryTx);
SetEval(eval, notary, noop);
NotarisationData data;
ASSERT_TRUE(eval.GetNotarisationData(notary.GetHash(), data));
EXPECT_EQ(data.height, 77455);
EXPECT_EQ(data.blockHash.GetHex(), "000030ef29d072f417cec86ad5a5ab4f8c413c7f96f572a098e45d6e3e0f8eae");
EXPECT_STREQ(data.symbol, "BOTS");
EXPECT_EQ(data.MoMDepth, 5);
EXPECT_EQ(data.MoM.GetHex(), "88289b6566a48567f65c8e60ca65b7f3877bbdb97cfc3958da31bcf073a70b05");
MoMProof proof;
CheckDeserialize(vMomProof, proof);
EXPECT_EQ(data.MoM, proof.Exec(proofTxHash));
}
TEST(TestEvalNotarisation, testInvalidNotaryPubkey)
{
EvalMock eval;
CMutableTransaction notary(notaryTx);
SetEval(eval, notary, noop);
memset(eval.notaries[10], 0, 33);
NotarisationData data;
ASSERT_FALSE(eval.GetNotarisationData(notary.GetHash(), data));
}
TEST(TestEvalNotarisation, testInvalidNotarisationBadOpReturn)
{
EvalMock eval;
CMutableTransaction notary(notaryTx);
notary.vout[1].scriptPubKey = CScript() << OP_RETURN << 0;
SetEval(eval, notary, noop);
NotarisationData data;
ASSERT_FALSE(eval.GetNotarisationData(notary.GetHash(), data));
}
TEST(TestEvalNotarisation, testInvalidNotarisationTxNotEnoughSigs)
{
EvalMock eval;
CMutableTransaction notary(notaryTx);
SetEval(eval, notary, [](CMutableTransaction &tx) {
tx.vin.resize(10);
});
NotarisationData data;
ASSERT_FALSE(eval.GetNotarisationData(notary.GetHash(), data));
}
TEST(TestEvalNotarisation, testInvalidNotarisationTxDoesntExist)
{
EvalMock eval;
CMutableTransaction notary(notaryTx);
SetEval(eval, notary, noop);
NotarisationData data;
ASSERT_FALSE(eval.GetNotarisationData(uint256(), data));
}
TEST(TestEvalNotarisation, testInvalidNotarisationDupeNotary)
{
EvalMock eval;
CMutableTransaction notary(notaryTx);
SetEval(eval, notary, [](CMutableTransaction &tx) {
tx.vin[1] = tx.vin[3];
});
NotarisationData data;
ASSERT_FALSE(eval.GetNotarisationData(notary.GetHash(), data));
}
TEST(TestEvalNotarisation, testInvalidNotarisationInputNotCheckSig)
{
EvalMock eval;
CMutableTransaction notary(notaryTx);
SetEval(eval, notary, [&](CMutableTransaction &tx) {
int i = 1;
CMutableTransaction txIn;
txIn.vout.resize(1);
txIn.vout[0].scriptPubKey << VCH(eval.notaries[i*2], 33) << OP_RETURN;
notary.vin[i].prevout = COutPoint(txIn.GetHash(), 0);
eval.txs[txIn.GetHash()] = CTransaction(txIn);
});
NotarisationData data;
ASSERT_FALSE(eval.GetNotarisationData(notary.GetHash(), data));
}
} /* namespace TestEvalNotarisation */