]> Git Repo - VerusCoin.git/commitdiff
tests for getting MoM in Eval and fixes
authorScott Sadler <[email protected]>
Fri, 6 Apr 2018 06:52:30 +0000 (03:52 -0300)
committerScott Sadler <[email protected]>
Fri, 6 Apr 2018 06:52:30 +0000 (03:52 -0300)
src/Makefile.ktest.include
src/cc/betprotocol.cpp
src/cc/betprotocol.h
src/cc/eval.cpp
src/cc/eval.h
src/cc/importpayout.cpp
src/rpcblockchain.cpp
src/test-komodo/test_bet.cpp [deleted file]
src/test-komodo/test_eval_bet.cpp [new file with mode: 0644]
src/test-komodo/test_eval_notarisation.cpp [new file with mode: 0644]

index cef80b0d12d11a8e2bd42a129e671f16b97aa251..06aab54dc8726514ae66df58b588002e2c77ac97 100644 (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)
 
index 92ec961ee07fe1d7c66d56a31a37abb8b608a0b2..12637ebbdca7ca1dafb2878d6620a0e8928a14f6 100644 (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;
index dcbb6ec4ec2e0847c5ecc7cf3f30ea70a00e2339..67a1979c3d33e20371d865eda07bcc533cf97524 100644 (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 */
index 589a362c0ad4fda328e4585245648de74f32fd7f..3c605e1afad9cd469c556ba9ca86ddd4444b4140 100644 (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;
 }
index 2060d76caf4e6229435ff52a8efb81732d129506..55d45153c47e223cb408944de3d6980906a82060 100644 (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;
 };
 
@@ -70,15 +72,24 @@ public:
 
 
 /*
- * 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)
 {
@@ -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)
 {
index 5251f1dfda487b36f5360a83daae4fd07ebdadc4..3be0cdaf7664b25de3a4d4d5ae48a4491a3ebcf7 100644 (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");
     }
 
index f353d549d98415e08d82b5074abc3d023c308b75..343b651f076d86907d0a7311fe21b98ae7b85460 100644 (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);
diff --git a/src/test-komodo/test_bet.cpp b/src/test-komodo/test_bet.cpp
deleted file mode 100644 (file)
index d0072ad..0000000
+++ /dev/null
@@ -1,596 +0,0 @@
-#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());
-}
diff --git a/src/test-komodo/test_eval_bet.cpp b/src/test-komodo/test_eval_bet.cpp
new file mode 100644 (file)
index 0000000..25fea5d
--- /dev/null
@@ -0,0 +1,601 @@
+#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 */
diff --git a/src/test-komodo/test_eval_notarisation.cpp b/src/test-komodo/test_eval_notarisation.cpp
new file mode 100644 (file)
index 0000000..736e289
--- /dev/null
@@ -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 */
This page took 0.070459 seconds and 4 git commands to generate.