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)
#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;
#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:
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;
};
-CC* CCNewSecp256k1(CPubKey k);
-
-
#endif /* BETPROTOCOL_H */
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)
{
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++) {
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;
}
class AppVM;
+class NotarisationData;
class Eval
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;
};
/*
- * Serialisation boilerplate
+ * Data from notarisation OP_RETURN
*/
-template <class T>
-std::vector<unsigned char> CheckSerialize(T &in);
-template <class T>
-bool CheckDeserialize(std::vector<unsigned char> vIn, T &out);
-
+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)
{
return std::vector<unsigned char>(ss.begin(), ss.end());
}
-
template <class T>
bool CheckDeserialize(std::vector<unsigned char> vIn, T &out)
{
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");
}
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;
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
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);
+++ /dev/null
-#include <cryptoconditions.h>
-#include <gtest/gtest.h>
-
-#include "cc/betprotocol.h"
-#include "cc/eval.h"
-#include "base58.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"
-
-
-
-static std::vector<CKey> playerSecrets;
-static std::vector<CPubKey> players;
-
-static int Dealer = 0, Player1 = 1, Player2 = 2;
-
-
-int CCSign(CMutableTransaction &tx, unsigned int nIn, CC *cond, std::vector<int> keyIds) {
- PrecomputedTransactionData txdata(tx);
- uint256 sighash = SignatureHash(CCPubKey(cond), tx, nIn, SIGHASH_ALL, 0, 0, &txdata);
- int nSigned = 0;
- for (int i=0; i<keyIds.size(); i++)
- nSigned += cc_signTreeSecp256k1Msg32(cond, playerSecrets[keyIds[i]].begin(), sighash.begin());
- tx.vin[nIn].scriptSig = CCSig(cond);
- return nSigned;
-}
-
-
-int TestCC(CMutableTransaction &mtxTo, unsigned int nIn, CC *cond)
-{
- CAmount amount;
- ScriptError error;
- CTransaction txTo(mtxTo);
- PrecomputedTransactionData txdata(txTo);
- auto checker = ServerTransactionSignatureChecker(&txTo, nIn, amount, false, txdata);
- return VerifyScript(txTo.vin[nIn].scriptSig, CCPubKey(cond), 0, checker, 0, &error);
-}
-
-
-#define ASSERT_CC(tx, nIn, cond) if (!TestCC(tx, nIn, cond)) FAIL();
-
-
-class MockVM : public AppVM
-{
-public:
- std::pair<int,std::vector<CTxOut>> evaluate(
- std::vector<unsigned char> header, std::vector<unsigned char> body)
- {
- std::vector<CTxOut> outs;
- if (memcmp(header.data(), "BetHeader", 9)) {
- printf("Wrong VM header\n");
- return std::make_pair(0, outs);
- }
- outs.push_back(CTxOut(2, CScript() << OP_RETURN << body.size()));
- return std::make_pair(body.size(), outs);
- }
-};
-
-
-class EvalMock : public Eval
-{
-public:
- uint256 MoM;
- int currentHeight;
- std::map<uint256, CTransaction> txs;
- std::map<uint256, CBlockIndex> blocks;
- std::map<uint256, std::vector<CTransaction>> spends;
-
- bool Dispatch(const CC *cond, const CTransaction &txTo, unsigned int nIn)
- {
- if (strcmp(cond->method, "DisputeBet") == 0) {
- MockVM vm;
- return DisputePayout(vm, cond, txTo, nIn);
- }
- if (strcmp(cond->method, "ImportPayout") == 0) {
- return ImportPayout(cond, txTo, nIn);
- }
- return Invalid("invalid-method");
- }
-
- bool GetSpends(uint256 hash, std::vector<CTransaction> &spendsOut) const
- {
- auto r = spends.find(hash);
- if (r != spends.end()) {
- spendsOut = r->second;
- return true;
- }
- return false;
- }
-
- 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;
- }
-
- unsigned int GetCurrentHeight() const { return currentHeight; }
-
- bool GetBlock(uint256 hash, CBlockIndex& blockIdx) const
- {
- auto r = blocks.find(hash);
- if (r == blocks.end()) return false;
- blockIdx = r->second;
- return true;
- }
-
- bool GetMoM(uint256 notarisationHash, uint256& _MoM) const
- {
- if (notarisationHash == NotarisationHash()) {
- _MoM = MoM;
- return true;
- }
- return false;
- }
-
- static uint256 NotarisationHash()
- {
- uint256 h;
- h.begin()[0] = 123;
- return h;
- }
-};
-
-
-extern Eval* EVAL_TEST;
-
-
-/*
- * Generates example data that we will test with and shows how to call BetProtocol.
- */
-class ExampleBet
-{
-public:
- BetProtocol bet;
- CAmount totalPayout;
-
- ExampleBet() : bet(BetProtocol(players, DisputeHeader(2, VCH("BetHeader", 9)))), totalPayout(100) {}
- ~ExampleBet() {};
-
- CTransaction SessionTx()
- {
- return CTransaction(bet.MakeSessionTx());
- }
-
- CC* DisputeCond()
- {
- return bet.MakeDisputeCond();
- }
-
- CC* PayoutCond()
- {
- return bet.MakePayoutCond(SessionTx().GetHash());
- }
-
- CTransaction StakeTx()
- {
- return CTransaction(bet.MakeStakeTx(totalPayout, SessionTx().GetHash()));
- }
-
- std::vector<unsigned char> PlayerState(int playerIdx)
- {
- std::vector<unsigned char> state;
- for (int i=0; i<playerIdx+1; i++) state.push_back(1);
- return state;
- }
-
- std::vector<CTxOut> Payouts(int playerIdx)
- {
- return MockVM().evaluate(bet.disputeHeader.vmParams, PlayerState(playerIdx)).second;
- }
-
- CMutableTransaction DisputeTx(int playerIdx)
- {
- return bet.MakeDisputeTx(SessionTx().GetHash(), SerializeHash(Payouts(playerIdx)));
- }
-
- CMutableTransaction PostEvidenceTx(int playerIdx)
- {
- return bet.MakePostEvidenceTx(SessionTx().GetHash(), playerIdx, PlayerState(playerIdx));
- }
-
- CMutableTransaction AgreePayoutTx()
- {
- std::vector<CTxOut> v;
- return bet.MakeAgreePayoutTx(v, uint256());
- }
-
- MoMProof GetMoMProof()
- {
- int nIndex = 5;
- std::vector<uint256> vBranch;
- vBranch.resize(3);
- return MoMProof(nIndex, vBranch, EvalMock::NotarisationHash());
- }
-
- CMutableTransaction ImportPayoutTx()
- {
- CMutableTransaction disputeTx = DisputeTx(Player2);
- return bet.MakeImportPayoutTx(Payouts(Player2), disputeTx, uint256(), GetMoMProof());
- }
-
- EvalMock SetEvalMock(int currentHeight)
- {
- EvalMock eval;
- CTransaction sessionTx = SessionTx();
-
- eval.txs[sessionTx.GetHash()] = sessionTx;
-
- CBlockIndex sessionBlock;
- sessionBlock.nHeight = 10;
- eval.blocks[sessionTx.GetHash()] = sessionBlock;
-
- std::vector<CTransaction> sessionSpends;
- sessionSpends.push_back(CTransaction(PostEvidenceTx(Dealer)));
- sessionSpends.push_back(CTransaction()); // Invalid, should be ignored
- sessionSpends.push_back(CTransaction(PostEvidenceTx(Player2)));
- eval.spends[sessionTx.GetHash()] = sessionSpends;
-
- eval.currentHeight = currentHeight;
-
- MoMProof proof = GetMoMProof();
- eval.MoM = ExecMerkle(DisputeTx(Player2).GetHash(), proof.branch, proof.nIndex);
-
- EVAL_TEST = &eval;
- return eval;
- }
-};
-
-
-ExampleBet ebet;
-
-
-class TestBet : public ::testing::Test {
-protected:
- static void SetUpTestCase() {
- // Make playerSecrets
- CBitcoinSecret vchSecret;
- auto addKey = [&] (std::string k) { vchSecret.SetString(k); playerSecrets.push_back(vchSecret.GetKey()); };
- addKey("UwFBKf4d6wC3yqdnk3LoGrFjy7gwxrWerBT8jTFamrBbem8wSw9L");
- addKey("Up6GpWwrmx2VpqF8rD3snJXToKT56Dzc8YSoL24osXnfNdCucaMR");
- addKey("UxEHwki3A95PSHHVRzE2N67eHTeoUcqLkovxp6yDPVViv54skF8c");
- // Make playerpubkeys
- for (int i=0; i<playerSecrets.size(); i++) players.push_back(playerSecrets[i].GetPubKey());
- // enable CC
- ASSETCHAINS_CC = 1;
- }
- virtual void SetUp() {
- EVAL_TEST = 0;
- ebet = ExampleBet();
- }
-};
-
-
-TEST_F(TestBet, testMakeSessionTx)
-{
- CTransaction sessionTx = ebet.SessionTx();
- EXPECT_EQ(0, sessionTx.vin.size());
- EXPECT_EQ(4, sessionTx.vout.size());
- EXPECT_EQ(CCPubKey(ebet.DisputeCond()), sessionTx.vout[0].scriptPubKey);
- for (int i=0; i<players.size(); i++)
- EXPECT_EQ(CCPubKey(CCNewSecp256k1(players[i])), sessionTx.vout[i+1].scriptPubKey);
-}
-
-
-TEST_F(TestBet, testMakeDisputeCond)
-{
- CC *disputeCond = ebet.DisputeCond();
- EXPECT_EQ("(2 of 15,(1 of 5,5,5))", CCShowStructure(disputeCond));
- EXPECT_EQ(0, memcmp("\x2\tBetHeader", (char*) disputeCond->subconditions[0]->paramsBin, 11));
- for (int i=0; i<players.size(); i++)
- EXPECT_EQ(CCPubKey(CCNewSecp256k1(players[i])),
- CCPubKey(disputeCond->subconditions[1]->subconditions[i]));
-}
-
-
-TEST_F(TestBet, testSignDisputeCond)
-{
- // Only one key needed to dispute
- CMutableTransaction disputeTx = ebet.DisputeTx(Player1);
- CC *disputeCond = ebet.DisputeCond();
- EXPECT_EQ(1, CCSign(disputeTx, 0, disputeCond, {Player1}));
-
- EXPECT_EQ(1, cc_isFulfilled(disputeCond->subconditions[0]));
- EXPECT_EQ(1, cc_isFulfilled(disputeCond->subconditions[1]));
- EXPECT_EQ(0, cc_isFulfilled(disputeCond->subconditions[1]->subconditions[0]));
- EXPECT_EQ(1, cc_isFulfilled(disputeCond->subconditions[1]->subconditions[1]));
- EXPECT_EQ(0, cc_isFulfilled(disputeCond->subconditions[1]->subconditions[2]));
- EXPECT_EQ(1, cc_isFulfilled(disputeCond));
-}
-
-
-TEST_F(TestBet, testDispute)
-{
- EvalMock eval = ebet.SetEvalMock(12);
-
- // Only one key needed to dispute
- CMutableTransaction disputeTx = ebet.DisputeTx(Player2);
- CC *disputeCond = ebet.DisputeCond();
- EXPECT_EQ(1, CCSign(disputeTx, 0, disputeCond, {Player2}));
-
- // Success
- EXPECT_TRUE(TestCC(disputeTx, 0, disputeCond));
-
- // Set result hash to some rubbish and check false
- uint256 rubbishHash;
- std::vector<unsigned char> rubbish(rubbishHash.begin(), rubbishHash.end());
- disputeTx.vout[0].scriptPubKey = CScript() << OP_RETURN << rubbish;
- EXPECT_EQ(1, CCSign(disputeTx, 0, disputeCond, {Player2}));
- EXPECT_FALSE(TestCC(disputeTx, 0, disputeCond));
- EXPECT_EQ("wrong-payout", eval.state.GetRejectReason());
-}
-
-
-TEST_F(TestBet, testDisputeInvalidOutput)
-{
- EvalMock eval = ebet.SetEvalMock(11);
-
- // Only one key needed to dispute
- CMutableTransaction disputeTx = ebet.DisputeTx(Dealer);
- CC *disputeCond = ebet.DisputeCond();
-
- // invalid payout hash
- std::vector<unsigned char> invalidHash = {0,1,2};
- disputeTx.vout[0].scriptPubKey = CScript() << OP_RETURN << invalidHash;
- ASSERT_EQ(1, CCSign(disputeTx, 0, disputeCond, {Player1}));
- EXPECT_FALSE(TestCC(disputeTx, 0, disputeCond));
- EXPECT_EQ("invalid-payout-hash", eval.state.GetRejectReason());
-
- // no vout at all
- disputeTx.vout.resize(0);
- ASSERT_EQ(1, CCSign(disputeTx, 0, disputeCond, {Player1}));
- EXPECT_FALSE(TestCC(disputeTx, 0, disputeCond));
- EXPECT_EQ("no-vouts", eval.state.GetRejectReason());
-}
-
-
-TEST_F(TestBet, testDisputeEarly)
-{
- EvalMock eval = ebet.SetEvalMock(11);
-
- // Only one key needed to dispute
- CMutableTransaction disputeTx = ebet.DisputeTx(Dealer);
- CC *disputeCond = ebet.DisputeCond();
- EXPECT_EQ(1, CCSign(disputeTx, 0, disputeCond, {Player1}));
-
- EXPECT_FALSE(TestCC(disputeTx, 0, disputeCond));
- EXPECT_EQ("dispute-too-soon", eval.state.GetRejectReason());
-}
-
-
-TEST_F(TestBet, testDisputeInvalidParams)
-{
- EvalMock eval = ebet.SetEvalMock(12);
-
- CMutableTransaction disputeTx = ebet.DisputeTx(Player2);
- CC *disputeCond = ebet.DisputeCond();
- CC *evalCond = disputeCond->subconditions[0];
-
- // too long
- evalCond->paramsBin = (unsigned char*) realloc(evalCond->paramsBin, ++evalCond->paramsBinLength);
- ASSERT_EQ(1, CCSign(disputeTx, 0, disputeCond, {Player2}));
- EXPECT_FALSE(TestCC(disputeTx, 0, disputeCond));
- EXPECT_EQ("invalid-dispute-header", eval.state.GetRejectReason());
-
- // too short
- eval.state = CValidationState();
- evalCond->paramsBinLength = 1;
- ASSERT_EQ(1, CCSign(disputeTx, 0, disputeCond, {Player2}));
- EXPECT_FALSE(TestCC(disputeTx, 0, disputeCond));
- EXPECT_EQ("invalid-dispute-header", eval.state.GetRejectReason());
-
- // is fine
- eval.state = CValidationState();
- evalCond->paramsBinLength = 11;
- ASSERT_EQ(1, CCSign(disputeTx, 0, disputeCond, {Player2}));
- EXPECT_TRUE(TestCC(disputeTx, 0, disputeCond));
-}
-
-
-TEST_F(TestBet, testDisputeInvalidEvidence)
-{
- EvalMock eval = ebet.SetEvalMock(12);
-
- CMutableTransaction disputeTx = ebet.DisputeTx(Player2);
- CC *disputeCond = ebet.DisputeCond();
- CCSign(disputeTx, 0, disputeCond, {Player2});
-
- CMutableTransaction mtx;
-
- mtx.vout.resize(1);
- mtx.vout[0].scriptPubKey = CScript();
- eval.spends[ebet.SessionTx().GetHash()][1] = CTransaction(mtx);
- ASSERT_TRUE(TestCC(disputeTx, 0, disputeCond));
-
- mtx.vout[0].scriptPubKey << OP_RETURN;
- eval.spends[ebet.SessionTx().GetHash()][1] = CTransaction(mtx);
- ASSERT_TRUE(TestCC(disputeTx, 0, disputeCond));
-
- mtx.vout[0].scriptPubKey = CScript() << 0;
- eval.spends[ebet.SessionTx().GetHash()][1] = CTransaction(mtx);
- ASSERT_TRUE(TestCC(disputeTx, 0, disputeCond));
-
- eval.spends[ebet.SessionTx().GetHash()].resize(1);
- eval.spends[ebet.SessionTx().GetHash()][0] = CTransaction();
- ASSERT_FALSE(TestCC(disputeTx, 0, disputeCond));
- EXPECT_EQ("no-evidence", eval.state.GetRejectReason());
-}
-
-
-TEST_F(TestBet, testMakeStakeTx)
-{
- CTransaction stakeTx = ebet.StakeTx();
- EXPECT_EQ(0, stakeTx.vin.size());
- EXPECT_EQ(1, stakeTx.vout.size());
- EXPECT_EQ(ebet.totalPayout, stakeTx.vout[0].nValue);
- EXPECT_EQ(CCPubKey(ebet.PayoutCond()), stakeTx.vout[0].scriptPubKey);
-}
-
-
-TEST_F(TestBet, testMakePayoutCond)
-{
- CC *payoutCond = ebet.PayoutCond();
- EXPECT_EQ("(1 of (3 of 5,5,5),(2 of (1 of 5,5,5),15))", CCShowStructure(payoutCond));
- EXPECT_EQ(0, memcmp(payoutCond->subconditions[1]->subconditions[1]->paramsBin,
- ebet.SessionTx().GetHash().begin(), 32));
-}
-
-
-TEST_F(TestBet, testSignPayout)
-{
-
- CMutableTransaction payoutTx = ebet.AgreePayoutTx();
- CC *payoutCond = ebet.PayoutCond();
-
- EXPECT_EQ(0, cc_isFulfilled(payoutCond->subconditions[0]));
- EXPECT_EQ(0, cc_isFulfilled(payoutCond->subconditions[1]));
- EXPECT_EQ(0, cc_isFulfilled(payoutCond));
-
- EXPECT_EQ(2, CCSign(payoutTx, 0, payoutCond, {Player1}));
- EXPECT_EQ(0, cc_isFulfilled(payoutCond->subconditions[0]));
- EXPECT_EQ(1, cc_isFulfilled(payoutCond->subconditions[1]));
- EXPECT_EQ(1, cc_isFulfilled(payoutCond));
-
- EXPECT_EQ(2, CCSign(payoutTx, 0, payoutCond, {Player2}));
- EXPECT_EQ(0, cc_isFulfilled(payoutCond->subconditions[0]));
-
- EXPECT_EQ(2, CCSign(payoutTx, 0, payoutCond, {Dealer}));
- EXPECT_EQ(1, cc_isFulfilled(payoutCond->subconditions[0]));
-}
-
-
-TEST_F(TestBet, testAgreePayout)
-{
- EvalMock eval = ebet.SetEvalMock(12);
-
- CMutableTransaction payoutTx = ebet.AgreePayoutTx();
- CC *payoutCond = ebet.PayoutCond();
-
- EXPECT_EQ(2, CCSign(payoutTx, 0, payoutCond, {Dealer}));
- EXPECT_FALSE(TestCC(payoutTx, 0, payoutCond));
- EXPECT_EQ("(1 of (2 of (1 of 5,A5,A5),15),A2)",
- CCShowStructure(CCPrune(payoutCond)));
-
- EXPECT_EQ(2, CCSign(payoutTx, 0, payoutCond, {Player1}));
- EXPECT_FALSE(TestCC(payoutTx, 0, payoutCond));
- EXPECT_EQ("(1 of (2 of (1 of 5,A5,A5),15),A2)",
- CCShowStructure(CCPrune(payoutCond)));
-
- EXPECT_EQ(2, CCSign(payoutTx, 0, payoutCond, {Player2}));
- EXPECT_TRUE( TestCC(payoutTx, 0, payoutCond));
- EXPECT_EQ("(1 of (3 of 5,5,5),A2)",
- CCShowStructure(CCPrune(payoutCond)));
-}
-
-
-TEST_F(TestBet, testImportPayout)
-{
- EvalMock eval = ebet.SetEvalMock(12);
-
- CMutableTransaction importTx = ebet.ImportPayoutTx();
- CC *payoutCond = ebet.PayoutCond();
- EXPECT_EQ(2, CCSign(importTx, 0, payoutCond, {Player2}));
- EXPECT_TRUE(TestCC(importTx, 0, payoutCond));
-}
-
-
-TEST_F(TestBet, testImportPayoutFewVouts)
-{
- EvalMock eval = ebet.SetEvalMock(12);
-
- CMutableTransaction importTx = ebet.ImportPayoutTx();
- importTx.vout.resize(1);
- CC *payoutCond = ebet.PayoutCond();
- EXPECT_EQ(2, CCSign(importTx, 0, payoutCond, {Player2}));
- EXPECT_FALSE(TestCC(importTx, 0, payoutCond));
- EXPECT_EQ("need-2-vouts", eval.state.GetRejectReason());
-}
-
-
-TEST_F(TestBet, testImportPayoutInvalidDisputeTx)
-{
- EvalMock eval = ebet.SetEvalMock(12);
-
- CMutableTransaction importTx = ebet.ImportPayoutTx();
- importTx.vout[1].scriptPubKey.pop_back();
- CC *payoutCond = ebet.PayoutCond();
- EXPECT_EQ(2, CCSign(importTx, 0, payoutCond, {Player2}));
- EXPECT_FALSE(TestCC(importTx, 0, payoutCond));
- EXPECT_EQ("invalid-dispute-tx", eval.state.GetRejectReason());
-}
-
-
-TEST_F(TestBet, testImportPayoutWrongPayouts)
-{
- EvalMock eval = ebet.SetEvalMock(12);
-
- CMutableTransaction importTx = ebet.ImportPayoutTx();
- importTx.vout[2].nValue = 7;
- CC *payoutCond = ebet.PayoutCond();
- EXPECT_EQ(2, CCSign(importTx, 0, payoutCond, {Player2}));
- ASSERT_FALSE(TestCC(importTx, 0, payoutCond));
- EXPECT_EQ("wrong-payouts", eval.state.GetRejectReason());
-}
-
-
-TEST_F(TestBet, testImportPayoutMangleSessionId)
-{
- EvalMock eval = ebet.SetEvalMock(12);
-
- CMutableTransaction importTx = ebet.ImportPayoutTx();
- CC *payoutCond = ebet.PayoutCond();
- payoutCond->subconditions[1]->subconditions[1]->paramsBinLength = 31;
- EXPECT_EQ(2, CCSign(importTx, 0, payoutCond, {Player2}));
- ASSERT_FALSE(TestCC(importTx, 0, payoutCond));
- EXPECT_EQ("malformed-params", eval.state.GetRejectReason());
-
- payoutCond = ebet.PayoutCond();
- memset(payoutCond->subconditions[1]->subconditions[1]->paramsBin, 1, 32);
- EXPECT_EQ(2, CCSign(importTx, 0, payoutCond, {Player2}));
- ASSERT_FALSE(TestCC(importTx, 0, payoutCond));
- EXPECT_EQ("wrong-session", eval.state.GetRejectReason());
-}
-
-
-TEST_F(TestBet, testImportPayoutInvalidProofPayload)
-{
- EvalMock eval = ebet.SetEvalMock(12);
-
- CMutableTransaction importTx = ebet.ImportPayoutTx();
- importTx.vout[0].scriptPubKey.pop_back();
- CC *payoutCond = ebet.PayoutCond();
- EXPECT_EQ(2, CCSign(importTx, 0, payoutCond, {Player2}));
- EXPECT_FALSE(TestCC(importTx, 0, payoutCond));
- EXPECT_EQ("invalid-mom-proof-payload", eval.state.GetRejectReason());
-}
-
-
-TEST_F(TestBet, testImportPayoutInvalidNotarisationHash)
-{
- EvalMock eval = ebet.SetEvalMock(12);
-
- CMutableTransaction importTx = ebet.ImportPayoutTx();
- MoMProof proof = ebet.GetMoMProof();
- proof.notarisationHash = uint256();
- importTx.vout[0].scriptPubKey = CScript() << OP_RETURN << CheckSerialize(proof);
- CC *payoutCond = ebet.PayoutCond();
- EXPECT_EQ(2, CCSign(importTx, 0, payoutCond, {Player2}));
- EXPECT_FALSE(TestCC(importTx, 0, payoutCond));
- EXPECT_EQ("coudnt-load-mom", eval.state.GetRejectReason());
-}
-
-
-TEST_F(TestBet, testImportPayoutMomFail)
-{
- EvalMock eval = ebet.SetEvalMock(12);
-
- CMutableTransaction importTx = ebet.ImportPayoutTx();
- MoMProof proof = ebet.GetMoMProof();
- proof.nIndex ^= 1;
- importTx.vout[0].scriptPubKey = CScript() << OP_RETURN << CheckSerialize(proof);
- CC *payoutCond = ebet.PayoutCond();
- EXPECT_EQ(2, CCSign(importTx, 0, payoutCond, {Player2}));
- EXPECT_FALSE(TestCC(importTx, 0, payoutCond));
- EXPECT_EQ("mom-check-fail", eval.state.GetRejectReason());
-}
--- /dev/null
+#include <cryptoconditions.h>
+#include <gtest/gtest.h>
+
+#include "cc/betprotocol.h"
+#include "cc/eval.h"
+#include "base58.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;
+
+
+namespace TestBet {
+
+
+static std::vector<CKey> playerSecrets;
+static std::vector<CPubKey> players;
+
+static int Dealer = 0, Player1 = 1, Player2 = 2;
+
+
+int CCSign(CMutableTransaction &tx, unsigned int nIn, CC *cond, std::vector<int> keyIds) {
+ PrecomputedTransactionData txdata(tx);
+ uint256 sighash = SignatureHash(CCPubKey(cond), tx, nIn, SIGHASH_ALL, 0, 0, &txdata);
+ int nSigned = 0;
+ for (int i=0; i<keyIds.size(); i++)
+ nSigned += cc_signTreeSecp256k1Msg32(cond, playerSecrets[keyIds[i]].begin(), sighash.begin());
+ tx.vin[nIn].scriptSig = CCSig(cond);
+ return nSigned;
+}
+
+
+int TestCC(CMutableTransaction &mtxTo, unsigned int nIn, CC *cond)
+{
+ CAmount amount;
+ ScriptError error;
+ CTransaction txTo(mtxTo);
+ PrecomputedTransactionData txdata(txTo);
+ auto checker = ServerTransactionSignatureChecker(&txTo, nIn, amount, false, txdata);
+ return VerifyScript(txTo.vin[nIn].scriptSig, CCPubKey(cond), 0, checker, 0, &error);
+}
+
+
+#define ASSERT_CC(tx, nIn, cond) if (!TestCC(tx, nIn, cond)) FAIL();
+
+
+class MockVM : public AppVM
+{
+public:
+ std::pair<int,std::vector<CTxOut>> evaluate(
+ std::vector<unsigned char> header, std::vector<unsigned char> body)
+ {
+ std::vector<CTxOut> outs;
+ if (memcmp(header.data(), "BetHeader", 9)) {
+ printf("Wrong VM header\n");
+ return std::make_pair(0, outs);
+ }
+ outs.push_back(CTxOut(2, CScript() << OP_RETURN << body.size()));
+ return std::make_pair(body.size(), outs);
+ }
+};
+
+
+class EvalMock : public Eval
+{
+public:
+ uint256 MoM;
+ int currentHeight;
+ std::map<uint256, CTransaction> txs;
+ std::map<uint256, CBlockIndex> blocks;
+ std::map<uint256, std::vector<CTransaction>> spends;
+
+ bool Dispatch(const CC *cond, const CTransaction &txTo, unsigned int nIn)
+ {
+ if (strcmp(cond->method, "DisputeBet") == 0) {
+ MockVM vm;
+ return DisputePayout(vm, cond, txTo, nIn);
+ }
+ if (strcmp(cond->method, "ImportPayout") == 0) {
+ return ImportPayout(cond, txTo, nIn);
+ }
+ return Invalid("invalid-method");
+ }
+
+ bool GetSpends(uint256 hash, std::vector<CTransaction> &spendsOut) const
+ {
+ auto r = spends.find(hash);
+ if (r != spends.end()) {
+ spendsOut = r->second;
+ return true;
+ }
+ return false;
+ }
+
+ 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;
+ }
+
+ unsigned int GetCurrentHeight() const { return currentHeight; }
+
+ bool GetBlock(uint256 hash, CBlockIndex& blockIdx) const
+ {
+ auto r = blocks.find(hash);
+ if (r == blocks.end()) return false;
+ blockIdx = r->second;
+ return true;
+ }
+
+ bool GetNotarisationData(uint256 notarisationHash, NotarisationData &data) const
+ {
+ if (notarisationHash == NotarisationHash()) {
+ data.MoM = MoM;
+ return true;
+ }
+ return false;
+ }
+
+ static uint256 NotarisationHash()
+ {
+ uint256 h;
+ h.begin()[0] = 123;
+ return h;
+ }
+};
+
+
+/*
+ * Generates example data that we will test with and shows how to call BetProtocol.
+ */
+class ExampleBet
+{
+public:
+ BetProtocol bet;
+ CAmount totalPayout;
+
+ ExampleBet() : bet(BetProtocol(players, DisputeHeader(2, VCH("BetHeader", 9)))), totalPayout(100) {}
+ ~ExampleBet() {};
+
+ CTransaction SessionTx()
+ {
+ return CTransaction(bet.MakeSessionTx());
+ }
+
+ CC* DisputeCond()
+ {
+ return bet.MakeDisputeCond();
+ }
+
+ CC* PayoutCond()
+ {
+ return bet.MakePayoutCond(SessionTx().GetHash());
+ }
+
+ CTransaction StakeTx()
+ {
+ return CTransaction(bet.MakeStakeTx(totalPayout, SessionTx().GetHash()));
+ }
+
+ std::vector<unsigned char> PlayerState(int playerIdx)
+ {
+ std::vector<unsigned char> state;
+ for (int i=0; i<playerIdx+1; i++) state.push_back(1);
+ return state;
+ }
+
+ std::vector<CTxOut> Payouts(int playerIdx)
+ {
+ return MockVM().evaluate(bet.disputeHeader.vmParams, PlayerState(playerIdx)).second;
+ }
+
+ CMutableTransaction DisputeTx(int playerIdx)
+ {
+ return bet.MakeDisputeTx(SessionTx().GetHash(), SerializeHash(Payouts(playerIdx)));
+ }
+
+ CMutableTransaction PostEvidenceTx(int playerIdx)
+ {
+ return bet.MakePostEvidenceTx(SessionTx().GetHash(), playerIdx, PlayerState(playerIdx));
+ }
+
+ CMutableTransaction AgreePayoutTx()
+ {
+ std::vector<CTxOut> v;
+ return bet.MakeAgreePayoutTx(v, uint256());
+ }
+
+ MoMProof GetMoMProof()
+ {
+ int nIndex = 5;
+ std::vector<uint256> vBranch;
+ vBranch.resize(3);
+ return MoMProof(nIndex, vBranch, EvalMock::NotarisationHash());
+ }
+
+ CMutableTransaction ImportPayoutTx()
+ {
+ CMutableTransaction disputeTx = DisputeTx(Player2);
+ return bet.MakeImportPayoutTx(Payouts(Player2), disputeTx, uint256(), GetMoMProof());
+ }
+
+ EvalMock SetEvalMock(int currentHeight)
+ {
+ EvalMock eval;
+ CTransaction sessionTx = SessionTx();
+
+ eval.txs[sessionTx.GetHash()] = sessionTx;
+
+ CBlockIndex sessionBlock;
+ sessionBlock.nHeight = 10;
+ eval.blocks[sessionTx.GetHash()] = sessionBlock;
+
+ std::vector<CTransaction> sessionSpends;
+ sessionSpends.push_back(CTransaction(PostEvidenceTx(Dealer)));
+ sessionSpends.push_back(CTransaction()); // Invalid, should be ignored
+ sessionSpends.push_back(CTransaction(PostEvidenceTx(Player2)));
+ eval.spends[sessionTx.GetHash()] = sessionSpends;
+
+ eval.currentHeight = currentHeight;
+
+ MoMProof proof = GetMoMProof();
+ eval.MoM = proof.Exec(DisputeTx(Player2).GetHash());
+
+ EVAL_TEST = &eval;
+ return eval;
+ }
+};
+
+
+ExampleBet ebet;
+
+
+class TestBet : public ::testing::Test {
+protected:
+ static void SetUpTestCase() {
+ // Make playerSecrets
+ CBitcoinSecret vchSecret;
+ auto addKey = [&] (std::string k) { vchSecret.SetString(k); playerSecrets.push_back(vchSecret.GetKey()); };
+ addKey("UwFBKf4d6wC3yqdnk3LoGrFjy7gwxrWerBT8jTFamrBbem8wSw9L");
+ addKey("Up6GpWwrmx2VpqF8rD3snJXToKT56Dzc8YSoL24osXnfNdCucaMR");
+ addKey("UxEHwki3A95PSHHVRzE2N67eHTeoUcqLkovxp6yDPVViv54skF8c");
+ // Make playerpubkeys
+ for (int i=0; i<playerSecrets.size(); i++) players.push_back(playerSecrets[i].GetPubKey());
+ // enable CC
+ ASSETCHAINS_CC = 1;
+ }
+ virtual void SetUp() {
+ EVAL_TEST = 0;
+ ebet = ExampleBet();
+ }
+};
+
+
+TEST_F(TestBet, testMakeSessionTx)
+{
+ CTransaction sessionTx = ebet.SessionTx();
+ EXPECT_EQ(0, sessionTx.vin.size());
+ EXPECT_EQ(4, sessionTx.vout.size());
+ EXPECT_EQ(CCPubKey(ebet.DisputeCond()), sessionTx.vout[0].scriptPubKey);
+ for (int i=0; i<players.size(); i++)
+ EXPECT_EQ(CCPubKey(CCNewSecp256k1(players[i])), sessionTx.vout[i+1].scriptPubKey);
+}
+
+
+TEST_F(TestBet, testMakeDisputeCond)
+{
+ CC *disputeCond = ebet.DisputeCond();
+ EXPECT_EQ("(2 of 15,(1 of 5,5,5))", CCShowStructure(disputeCond));
+ EXPECT_EQ(0, memcmp("\x2\tBetHeader", (char*) disputeCond->subconditions[0]->paramsBin, 11));
+ for (int i=0; i<players.size(); i++)
+ EXPECT_EQ(CCPubKey(CCNewSecp256k1(players[i])),
+ CCPubKey(disputeCond->subconditions[1]->subconditions[i]));
+}
+
+
+TEST_F(TestBet, testSignDisputeCond)
+{
+ // Only one key needed to dispute
+ CMutableTransaction disputeTx = ebet.DisputeTx(Player1);
+ CC *disputeCond = ebet.DisputeCond();
+ EXPECT_EQ(1, CCSign(disputeTx, 0, disputeCond, {Player1}));
+
+ EXPECT_EQ(1, cc_isFulfilled(disputeCond->subconditions[0]));
+ EXPECT_EQ(1, cc_isFulfilled(disputeCond->subconditions[1]));
+ EXPECT_EQ(0, cc_isFulfilled(disputeCond->subconditions[1]->subconditions[0]));
+ EXPECT_EQ(1, cc_isFulfilled(disputeCond->subconditions[1]->subconditions[1]));
+ EXPECT_EQ(0, cc_isFulfilled(disputeCond->subconditions[1]->subconditions[2]));
+ EXPECT_EQ(1, cc_isFulfilled(disputeCond));
+}
+
+
+TEST_F(TestBet, testDispute)
+{
+ EvalMock eval = ebet.SetEvalMock(12);
+
+ // Only one key needed to dispute
+ CMutableTransaction disputeTx = ebet.DisputeTx(Player2);
+ CC *disputeCond = ebet.DisputeCond();
+ EXPECT_EQ(1, CCSign(disputeTx, 0, disputeCond, {Player2}));
+
+ // Success
+ EXPECT_TRUE(TestCC(disputeTx, 0, disputeCond));
+
+ // Set result hash to some rubbish and check false
+ uint256 rubbishHash;
+ std::vector<unsigned char> rubbish(rubbishHash.begin(), rubbishHash.end());
+ disputeTx.vout[0].scriptPubKey = CScript() << OP_RETURN << rubbish;
+ EXPECT_EQ(1, CCSign(disputeTx, 0, disputeCond, {Player2}));
+ EXPECT_FALSE(TestCC(disputeTx, 0, disputeCond));
+ EXPECT_EQ("wrong-payout", eval.state.GetRejectReason());
+}
+
+
+TEST_F(TestBet, testDisputeInvalidOutput)
+{
+ EvalMock eval = ebet.SetEvalMock(11);
+
+ // Only one key needed to dispute
+ CMutableTransaction disputeTx = ebet.DisputeTx(Dealer);
+ CC *disputeCond = ebet.DisputeCond();
+
+ // invalid payout hash
+ std::vector<unsigned char> invalidHash = {0,1,2};
+ disputeTx.vout[0].scriptPubKey = CScript() << OP_RETURN << invalidHash;
+ ASSERT_EQ(1, CCSign(disputeTx, 0, disputeCond, {Player1}));
+ EXPECT_FALSE(TestCC(disputeTx, 0, disputeCond));
+ EXPECT_EQ("invalid-payout-hash", eval.state.GetRejectReason());
+
+ // no vout at all
+ disputeTx.vout.resize(0);
+ ASSERT_EQ(1, CCSign(disputeTx, 0, disputeCond, {Player1}));
+ EXPECT_FALSE(TestCC(disputeTx, 0, disputeCond));
+ EXPECT_EQ("no-vouts", eval.state.GetRejectReason());
+}
+
+
+TEST_F(TestBet, testDisputeEarly)
+{
+ EvalMock eval = ebet.SetEvalMock(11);
+
+ // Only one key needed to dispute
+ CMutableTransaction disputeTx = ebet.DisputeTx(Dealer);
+ CC *disputeCond = ebet.DisputeCond();
+ EXPECT_EQ(1, CCSign(disputeTx, 0, disputeCond, {Player1}));
+
+ EXPECT_FALSE(TestCC(disputeTx, 0, disputeCond));
+ EXPECT_EQ("dispute-too-soon", eval.state.GetRejectReason());
+}
+
+
+TEST_F(TestBet, testDisputeInvalidParams)
+{
+ EvalMock eval = ebet.SetEvalMock(12);
+
+ CMutableTransaction disputeTx = ebet.DisputeTx(Player2);
+ CC *disputeCond = ebet.DisputeCond();
+ CC *evalCond = disputeCond->subconditions[0];
+
+ // too long
+ evalCond->paramsBin = (unsigned char*) realloc(evalCond->paramsBin, ++evalCond->paramsBinLength);
+ ASSERT_EQ(1, CCSign(disputeTx, 0, disputeCond, {Player2}));
+ EXPECT_FALSE(TestCC(disputeTx, 0, disputeCond));
+ EXPECT_EQ("invalid-dispute-header", eval.state.GetRejectReason());
+
+ // too short
+ eval.state = CValidationState();
+ evalCond->paramsBinLength = 1;
+ ASSERT_EQ(1, CCSign(disputeTx, 0, disputeCond, {Player2}));
+ EXPECT_FALSE(TestCC(disputeTx, 0, disputeCond));
+ EXPECT_EQ("invalid-dispute-header", eval.state.GetRejectReason());
+
+ // is fine
+ eval.state = CValidationState();
+ evalCond->paramsBinLength = 11;
+ ASSERT_EQ(1, CCSign(disputeTx, 0, disputeCond, {Player2}));
+ EXPECT_TRUE(TestCC(disputeTx, 0, disputeCond));
+}
+
+
+TEST_F(TestBet, testDisputeInvalidEvidence)
+{
+ EvalMock eval = ebet.SetEvalMock(12);
+
+ CMutableTransaction disputeTx = ebet.DisputeTx(Player2);
+ CC *disputeCond = ebet.DisputeCond();
+ CCSign(disputeTx, 0, disputeCond, {Player2});
+
+ CMutableTransaction mtx;
+
+ mtx.vout.resize(1);
+ mtx.vout[0].scriptPubKey = CScript();
+ eval.spends[ebet.SessionTx().GetHash()][1] = CTransaction(mtx);
+ ASSERT_TRUE(TestCC(disputeTx, 0, disputeCond));
+
+ mtx.vout[0].scriptPubKey << OP_RETURN;
+ eval.spends[ebet.SessionTx().GetHash()][1] = CTransaction(mtx);
+ ASSERT_TRUE(TestCC(disputeTx, 0, disputeCond));
+
+ mtx.vout[0].scriptPubKey = CScript() << 0;
+ eval.spends[ebet.SessionTx().GetHash()][1] = CTransaction(mtx);
+ ASSERT_TRUE(TestCC(disputeTx, 0, disputeCond));
+
+ eval.spends[ebet.SessionTx().GetHash()].resize(1);
+ eval.spends[ebet.SessionTx().GetHash()][0] = CTransaction();
+ ASSERT_FALSE(TestCC(disputeTx, 0, disputeCond));
+ EXPECT_EQ("no-evidence", eval.state.GetRejectReason());
+}
+
+
+TEST_F(TestBet, testMakeStakeTx)
+{
+ CTransaction stakeTx = ebet.StakeTx();
+ EXPECT_EQ(0, stakeTx.vin.size());
+ EXPECT_EQ(1, stakeTx.vout.size());
+ EXPECT_EQ(ebet.totalPayout, stakeTx.vout[0].nValue);
+ EXPECT_EQ(CCPubKey(ebet.PayoutCond()), stakeTx.vout[0].scriptPubKey);
+}
+
+
+TEST_F(TestBet, testMakePayoutCond)
+{
+ CC *payoutCond = ebet.PayoutCond();
+ EXPECT_EQ("(1 of (3 of 5,5,5),(2 of (1 of 5,5,5),15))", CCShowStructure(payoutCond));
+ EXPECT_EQ(0, memcmp(payoutCond->subconditions[1]->subconditions[1]->paramsBin,
+ ebet.SessionTx().GetHash().begin(), 32));
+}
+
+
+TEST_F(TestBet, testSignPayout)
+{
+
+ CMutableTransaction payoutTx = ebet.AgreePayoutTx();
+ CC *payoutCond = ebet.PayoutCond();
+
+ EXPECT_EQ(0, cc_isFulfilled(payoutCond->subconditions[0]));
+ EXPECT_EQ(0, cc_isFulfilled(payoutCond->subconditions[1]));
+ EXPECT_EQ(0, cc_isFulfilled(payoutCond));
+
+ EXPECT_EQ(2, CCSign(payoutTx, 0, payoutCond, {Player1}));
+ EXPECT_EQ(0, cc_isFulfilled(payoutCond->subconditions[0]));
+ EXPECT_EQ(1, cc_isFulfilled(payoutCond->subconditions[1]));
+ EXPECT_EQ(1, cc_isFulfilled(payoutCond));
+
+ EXPECT_EQ(2, CCSign(payoutTx, 0, payoutCond, {Player2}));
+ EXPECT_EQ(0, cc_isFulfilled(payoutCond->subconditions[0]));
+
+ EXPECT_EQ(2, CCSign(payoutTx, 0, payoutCond, {Dealer}));
+ EXPECT_EQ(1, cc_isFulfilled(payoutCond->subconditions[0]));
+}
+
+
+TEST_F(TestBet, testAgreePayout)
+{
+ EvalMock eval = ebet.SetEvalMock(12);
+
+ CMutableTransaction payoutTx = ebet.AgreePayoutTx();
+ CC *payoutCond = ebet.PayoutCond();
+
+ EXPECT_EQ(2, CCSign(payoutTx, 0, payoutCond, {Dealer}));
+ EXPECT_FALSE(TestCC(payoutTx, 0, payoutCond));
+ EXPECT_EQ("(1 of (2 of (1 of 5,A5,A5),15),A2)",
+ CCShowStructure(CCPrune(payoutCond)));
+
+ EXPECT_EQ(2, CCSign(payoutTx, 0, payoutCond, {Player1}));
+ EXPECT_FALSE(TestCC(payoutTx, 0, payoutCond));
+ EXPECT_EQ("(1 of (2 of (1 of 5,A5,A5),15),A2)",
+ CCShowStructure(CCPrune(payoutCond)));
+
+ EXPECT_EQ(2, CCSign(payoutTx, 0, payoutCond, {Player2}));
+ EXPECT_TRUE( TestCC(payoutTx, 0, payoutCond));
+ EXPECT_EQ("(1 of (3 of 5,5,5),A2)",
+ CCShowStructure(CCPrune(payoutCond)));
+}
+
+
+TEST_F(TestBet, testImportPayout)
+{
+ EvalMock eval = ebet.SetEvalMock(12);
+
+ CMutableTransaction importTx = ebet.ImportPayoutTx();
+ CC *payoutCond = ebet.PayoutCond();
+ EXPECT_EQ(2, CCSign(importTx, 0, payoutCond, {Player2}));
+ EXPECT_TRUE(TestCC(importTx, 0, payoutCond));
+}
+
+
+TEST_F(TestBet, testImportPayoutFewVouts)
+{
+ EvalMock eval = ebet.SetEvalMock(12);
+
+ CMutableTransaction importTx = ebet.ImportPayoutTx();
+ importTx.vout.resize(1);
+ CC *payoutCond = ebet.PayoutCond();
+ EXPECT_EQ(2, CCSign(importTx, 0, payoutCond, {Player2}));
+ EXPECT_FALSE(TestCC(importTx, 0, payoutCond));
+ EXPECT_EQ("need-2-vouts", eval.state.GetRejectReason());
+}
+
+
+TEST_F(TestBet, testImportPayoutInvalidDisputeTx)
+{
+ EvalMock eval = ebet.SetEvalMock(12);
+
+ CMutableTransaction importTx = ebet.ImportPayoutTx();
+ importTx.vout[1].scriptPubKey.pop_back();
+ CC *payoutCond = ebet.PayoutCond();
+ EXPECT_EQ(2, CCSign(importTx, 0, payoutCond, {Player2}));
+ EXPECT_FALSE(TestCC(importTx, 0, payoutCond));
+ EXPECT_EQ("invalid-dispute-tx", eval.state.GetRejectReason());
+}
+
+
+TEST_F(TestBet, testImportPayoutWrongPayouts)
+{
+ EvalMock eval = ebet.SetEvalMock(12);
+
+ CMutableTransaction importTx = ebet.ImportPayoutTx();
+ importTx.vout[2].nValue = 7;
+ CC *payoutCond = ebet.PayoutCond();
+ EXPECT_EQ(2, CCSign(importTx, 0, payoutCond, {Player2}));
+ ASSERT_FALSE(TestCC(importTx, 0, payoutCond));
+ EXPECT_EQ("wrong-payouts", eval.state.GetRejectReason());
+}
+
+
+TEST_F(TestBet, testImportPayoutMangleSessionId)
+{
+ EvalMock eval = ebet.SetEvalMock(12);
+
+ CMutableTransaction importTx = ebet.ImportPayoutTx();
+ CC *payoutCond = ebet.PayoutCond();
+ payoutCond->subconditions[1]->subconditions[1]->paramsBinLength = 31;
+ EXPECT_EQ(2, CCSign(importTx, 0, payoutCond, {Player2}));
+ ASSERT_FALSE(TestCC(importTx, 0, payoutCond));
+ EXPECT_EQ("malformed-params", eval.state.GetRejectReason());
+
+ payoutCond = ebet.PayoutCond();
+ memset(payoutCond->subconditions[1]->subconditions[1]->paramsBin, 1, 32);
+ EXPECT_EQ(2, CCSign(importTx, 0, payoutCond, {Player2}));
+ ASSERT_FALSE(TestCC(importTx, 0, payoutCond));
+ EXPECT_EQ("wrong-session", eval.state.GetRejectReason());
+}
+
+
+TEST_F(TestBet, testImportPayoutInvalidProofPayload)
+{
+ EvalMock eval = ebet.SetEvalMock(12);
+
+ CMutableTransaction importTx = ebet.ImportPayoutTx();
+ importTx.vout[0].scriptPubKey.pop_back();
+ CC *payoutCond = ebet.PayoutCond();
+ EXPECT_EQ(2, CCSign(importTx, 0, payoutCond, {Player2}));
+ EXPECT_FALSE(TestCC(importTx, 0, payoutCond));
+ EXPECT_EQ("invalid-mom-proof-payload", eval.state.GetRejectReason());
+}
+
+
+TEST_F(TestBet, testImportPayoutInvalidNotarisationHash)
+{
+ EvalMock eval = ebet.SetEvalMock(12);
+
+ CMutableTransaction importTx = ebet.ImportPayoutTx();
+ MoMProof proof = ebet.GetMoMProof();
+ proof.notarisationHash = uint256();
+ importTx.vout[0].scriptPubKey = CScript() << OP_RETURN << CheckSerialize(proof);
+ CC *payoutCond = ebet.PayoutCond();
+ EXPECT_EQ(2, CCSign(importTx, 0, payoutCond, {Player2}));
+ EXPECT_FALSE(TestCC(importTx, 0, payoutCond));
+ EXPECT_EQ("coudnt-load-mom", eval.state.GetRejectReason());
+}
+
+
+TEST_F(TestBet, testImportPayoutMomFail)
+{
+ EvalMock eval = ebet.SetEvalMock(12);
+
+ CMutableTransaction importTx = ebet.ImportPayoutTx();
+ MoMProof proof = ebet.GetMoMProof();
+ proof.nIndex ^= 1;
+ importTx.vout[0].scriptPubKey = CScript() << OP_RETURN << CheckSerialize(proof);
+ CC *payoutCond = ebet.PayoutCond();
+ EXPECT_EQ(2, CCSign(importTx, 0, payoutCond, {Player2}));
+ EXPECT_FALSE(TestCC(importTx, 0, payoutCond));
+ EXPECT_EQ("mom-check-fail", eval.state.GetRejectReason());
+}
+
+
+} /* namespace TestBet */
--- /dev/null
+#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 ¬ary, 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 */