tests for bet protocol done; verify notarisation still to test
This commit is contained in:
@@ -163,7 +163,6 @@ BITCOIN_CORE_H = \
|
|||||||
init.h \
|
init.h \
|
||||||
key.h \
|
key.h \
|
||||||
keystore.h \
|
keystore.h \
|
||||||
komodo_cc.h \
|
|
||||||
leveldbwrapper.h \
|
leveldbwrapper.h \
|
||||||
limitedmap.h \
|
limitedmap.h \
|
||||||
main.h \
|
main.h \
|
||||||
@@ -253,6 +252,7 @@ libbitcoin_server_a_SOURCES = \
|
|||||||
cc/eval.cpp \
|
cc/eval.cpp \
|
||||||
cc/importpayout.cpp \
|
cc/importpayout.cpp \
|
||||||
cc/disputepayout.cpp \
|
cc/disputepayout.cpp \
|
||||||
|
cc/betprotocol.cpp \
|
||||||
chain.cpp \
|
chain.cpp \
|
||||||
checkpoints.cpp \
|
checkpoints.cpp \
|
||||||
deprecation.cpp \
|
deprecation.cpp \
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ bin_PROGRAMS += komodo-test
|
|||||||
# tool for generating our public parameters
|
# tool for generating our public parameters
|
||||||
komodo_test_SOURCES = \
|
komodo_test_SOURCES = \
|
||||||
test-komodo/main.cpp \
|
test-komodo/main.cpp \
|
||||||
test-komodo/test_cryptoconditions.cpp
|
test-komodo/test_cryptoconditions.cpp \
|
||||||
|
test-komodo/test_bet.cpp
|
||||||
|
|
||||||
komodo_test_CPPFLAGS = $(komodod_CPPFLAGS)
|
komodo_test_CPPFLAGS = $(komodod_CPPFLAGS)
|
||||||
|
|
||||||
|
|||||||
@@ -1,117 +1,79 @@
|
|||||||
#include <cryptoconditions.h>
|
#include <cryptoconditions.h>
|
||||||
|
|
||||||
|
#include "streams.h"
|
||||||
|
#include "komodo_cc.h"
|
||||||
|
#include "cc/eval.h"
|
||||||
|
#include "cc/betprotocol.h"
|
||||||
#include "primitives/transaction.h"
|
#include "primitives/transaction.h"
|
||||||
|
|
||||||
|
|
||||||
class DisputeHeader
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
int waitBlocks;
|
|
||||||
std::vector<unsigned char> vmParams;
|
|
||||||
|
|
||||||
ADD_SERIALIZE_METHODS;
|
|
||||||
|
|
||||||
template <typename Stream, typename Operation>
|
|
||||||
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
|
|
||||||
READWRITE(VARINT(waitBlocks));
|
|
||||||
READWRITE(vmParams);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
static CScript CCPubKey(const CC *cond)
|
|
||||||
{
|
|
||||||
unsigned char buf[1000];
|
|
||||||
size_t len = cc_conditionBinary(cond, buf);
|
|
||||||
return CScript() << std::vector<unsigned char>(buf, buf+len) << OP_CHECKCRYPTOCONDITION;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static CScript CCSig(const CC *cond)
|
|
||||||
{
|
|
||||||
unsigned char buf[1000];
|
|
||||||
size_t len = cc_fulfillmentBinary(cond, buf, 1000);
|
|
||||||
auto ffill = std::vector<unsigned char>(buf, buf+len);
|
|
||||||
ffill.push_back(SIGHASH_ALL);
|
|
||||||
return CScript() << ffill;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static unsigned char* CopyPubKey(CPubKey pkIn)
|
static unsigned char* CopyPubKey(CPubKey pkIn)
|
||||||
{
|
{
|
||||||
auto *pk = malloc(33);
|
unsigned char* pk = (unsigned char*) malloc(33);
|
||||||
memcpy(pk, pkIn.begin(), 33); // TODO: compressed?
|
memcpy(pk, pkIn.begin(), 33); // TODO: compressed?
|
||||||
return pk;
|
return pk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PokerProtocol
|
CC* CCNewThreshold(int t, std::vector<CC*> v)
|
||||||
{
|
{
|
||||||
public:
|
|
||||||
CAmount MINFEE = 1;
|
|
||||||
std::vector<CPubKey> players;
|
|
||||||
DisputeHeader disputeHeader;
|
|
||||||
|
|
||||||
// on PANGEA
|
|
||||||
CC* MakeDisputeCond();
|
|
||||||
CMutableTransaction MakeSessionTx(CTxIn dealerInput);
|
|
||||||
CMutableTransaction MakeDisputeTx(uint256 signedSessionTxHash);
|
|
||||||
CMutableTransaction MakePostEvidenceTx(uint256 signedSessionTxHash,
|
|
||||||
CPubKey playerKey, std::vector<unsigned char> state);
|
|
||||||
|
|
||||||
// on KMD
|
|
||||||
CC* MakePayoutCond();
|
|
||||||
CMutableTransaction MakeStakeTx(CAmount totalPayout, std::vector<CTxIn> playerInputs,
|
|
||||||
uint256 signedSessionTx);
|
|
||||||
CMutableTransaction MakeAgreePayoutTx(std::vector<CTxOut> payouts,
|
|
||||||
CC *signedPayoutCond, uint256 signedStakeTxHash);
|
|
||||||
CMutableTransaction MakeImportPayoutTx(std::vector<CTxOut> payouts,
|
|
||||||
CC *signedPayoutCond, CTransaction signedDisputeTx);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
CC* PokerProtocol::MakeDisputeCond()
|
|
||||||
{
|
|
||||||
CC *disputePoker = cond->subconditions[0] = cc_new(CC_Eval);
|
|
||||||
char err[1000];
|
|
||||||
std::vector<unsigned char> headerData;
|
|
||||||
disputeHeader >> CDataStream(headerData, SER_DISK, PROTOCOL_VERSION);
|
|
||||||
disputePoker->paramsBin = malloc(headerData.size());
|
|
||||||
memcpy(disputePoker->paramsBin, headerData.data());
|
|
||||||
disputePoker.paramsBinLength = headerData.size();
|
|
||||||
|
|
||||||
CC *spendSig = cond->subconditions[1] = cc_new(CC_Threshold);
|
|
||||||
spendSig->threshold = 1;
|
|
||||||
spendSig->size = players.size() + 1;
|
|
||||||
spendSig->subconditions = malloc(spendSig->size, sizeof(CC*));
|
|
||||||
|
|
||||||
for (int i=0; i<players.size()+1; i++) {
|
|
||||||
CC *sub = spendSig->subconditions[i] = cc_new(CC_Secp256k1);
|
|
||||||
sub->publicKey = CopyPubKey(players[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
CC *cond = cc_new(CC_Threshold);
|
CC *cond = cc_new(CC_Threshold);
|
||||||
cond->threshold = 2;
|
cond->threshold = t;
|
||||||
cond->size = 2;
|
cond->size = v.size();
|
||||||
cond->subconditions = calloc(2, sizeof(CC*));
|
cond->subconditions = (CC**) calloc(v.size(), sizeof(CC*));
|
||||||
cond->subconditions[0] = disputePoker;
|
memcpy(cond->subconditions, v.data(), v.size() * sizeof(CC*));
|
||||||
cond->subconditions[1] = spendSig;
|
|
||||||
return cond;
|
return cond;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
CMutableTransaction PokerProtocol::MakeSessionTx(CTxIn dealerInput)
|
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;
|
||||||
|
for (int i=0; i<players.size(); i++)
|
||||||
|
subs.push_back(CCNewSecp256k1(players[i]));
|
||||||
|
return subs;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
CC* BetProtocol::MakeDisputeCond()
|
||||||
|
{
|
||||||
|
CC *disputePoker = CCNewEval(disputeFunc, CheckSerialize(disputeHeader));
|
||||||
|
|
||||||
|
CC *anySig = CCNewThreshold(1, PlayerConditions());
|
||||||
|
|
||||||
|
return CCNewThreshold(2, {disputePoker, anySig});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
CMutableTransaction BetProtocol::MakeSessionTx()
|
||||||
{
|
{
|
||||||
CMutableTransaction mtx;
|
CMutableTransaction mtx;
|
||||||
mtx.vin.push_back(dealerInput);
|
|
||||||
|
|
||||||
CC *disputeCond = MakeDisputeCond(players, disputeHeader);
|
CC *disputeCond = MakeDisputeCond();
|
||||||
mtx.vout.push_back(CTxOut(MINFEE, CCPubKey(disputeCond)));
|
mtx.vout.push_back(CTxOut(MINFEE, CCPubKey(disputeCond)));
|
||||||
cc_free(disputeCond);
|
cc_free(disputeCond);
|
||||||
|
|
||||||
for (int i=0; i<players.size(); i++) {
|
for (int i=0; i<players.size(); i++) {
|
||||||
CC *cond = cc_new(CC_Secp256k1);
|
CC *cond = CCNewSecp256k1(players[i]);
|
||||||
cond->publicKey = CopyPubKey(players[i]);
|
|
||||||
mtx.vout.push_back(CTxOut(MINFEE, CCPubKey(cond)));
|
mtx.vout.push_back(CTxOut(MINFEE, CCPubKey(cond)));
|
||||||
cc_free(cond);
|
cc_free(cond);
|
||||||
}
|
}
|
||||||
@@ -119,12 +81,12 @@ CMutableTransaction PokerProtocol::MakeSessionTx(CTxIn dealerInput)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
CMutableTransaction PokerProtocol::MakeDisputeTx(uint256 signedSessionTxHash, CC *signedDisputeCond, uint256 vmResultHash)
|
CMutableTransaction BetProtocol::MakeDisputeTx(uint256 signedSessionTxHash, uint256 vmResultHash)
|
||||||
{
|
{
|
||||||
CMutableTransaction mtx;
|
CMutableTransaction mtx;
|
||||||
|
|
||||||
CC *disputeCond = MakeDisputeCond();
|
CC *disputeCond = MakeDisputeCond();
|
||||||
mtx.vin.push_back(CTxIn(signedSessionTxHash, 0, CCSig(signedDisputeCond)));
|
mtx.vin.push_back(CTxIn(signedSessionTxHash, 0, CScript()));
|
||||||
|
|
||||||
std::vector<unsigned char> result(vmResultHash.begin(), vmResultHash.begin()+32);
|
std::vector<unsigned char> result(vmResultHash.begin(), vmResultHash.begin()+32);
|
||||||
mtx.vout.push_back(CTxOut(0, CScript() << OP_RETURN << result));
|
mtx.vout.push_back(CTxOut(0, CScript() << OP_RETURN << result));
|
||||||
@@ -132,69 +94,29 @@ CMutableTransaction PokerProtocol::MakeDisputeTx(uint256 signedSessionTxHash, CC
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
CMutableTransaction PokerProtocol::MakePostEvidenceTx(uint256 signedSessionTxHash,
|
CMutableTransaction BetProtocol::MakePostEvidenceTx(uint256 signedSessionTxHash,
|
||||||
int playerIdx, std::vector<unsigned char> state)
|
int playerIdx, std::vector<unsigned char> state)
|
||||||
{
|
{
|
||||||
CMutableTransaction mtx;
|
CMutableTransaction mtx;
|
||||||
|
|
||||||
CC *cond = cc_new(CC_Secp256k1);
|
mtx.vin.push_back(CTxIn(signedSessionTxHash, playerIdx+1, CScript()));
|
||||||
cond->publicKey = CopyPubKey(players[i]);
|
|
||||||
mtx.vin.push_back(CTxIn(signedSessionTxHash, playerIdx+1, CCSig(cond)));
|
|
||||||
|
|
||||||
mtx.vout.push_back(CTxOut(0, CScript() << OP_RETURN << state));
|
mtx.vout.push_back(CTxOut(0, CScript() << OP_RETURN << state));
|
||||||
|
|
||||||
return mtx;
|
return mtx;
|
||||||
}
|
}
|
||||||
|
|
||||||
CC* CCNewThreshold(int t, std::vector<CC*> v)
|
|
||||||
|
CC* BetProtocol::MakePayoutCond(uint256 signedSessionTxHash)
|
||||||
{
|
{
|
||||||
CC *cond = cc_new(CC_Threshold);
|
// TODO: 2/3 majority
|
||||||
cond->threshold = t;
|
CC* agree = CCNewThreshold(players.size(), PlayerConditions());
|
||||||
cond->size = v.size();
|
|
||||||
cond->subconditions = calloc(1, sizeof(CC*));
|
|
||||||
memcpy(cond->subconditions, v.data(), v.size() * sizeof(CC*));
|
|
||||||
return cond;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
CCNewSecp256k1(CPubKey &k)
|
|
||||||
{
|
|
||||||
cond = cc_new(CC_Secp256k1);
|
|
||||||
cond->publicKey = CopyPubKey(players[i]);
|
|
||||||
return cond;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
CCNewEval(char *method, unsigned char* paramsBin, size_t len)
|
|
||||||
{
|
|
||||||
CC *cond = cc_new(CC_Eval);
|
|
||||||
strcpy(cond->method, method);
|
|
||||||
cond->paramsBin = malloc(32);
|
|
||||||
memcpy(cond->paramsBin, bin, len);
|
|
||||||
cond->paramsBinLength = len;
|
|
||||||
return cond;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
CC* MakePayoutCond(uint256 signedSessionTx)
|
|
||||||
{
|
|
||||||
CC* agree;
|
|
||||||
{
|
|
||||||
// TODO: 2/3 majority
|
|
||||||
std::vector<CC*> subs;
|
|
||||||
for (int i=0; i<players.size(); i++)
|
|
||||||
subs[i] = CCNewSecp256k1(players[i]);
|
|
||||||
agree = CCNewThreshold(players.size(), subs);
|
|
||||||
}
|
|
||||||
|
|
||||||
CC *import;
|
CC *import;
|
||||||
{
|
{
|
||||||
CC *importEval = CCNewEval("ImportPayout", signedSessionTx.begin(), 32);
|
std::vector<unsigned char> vHash(signedSessionTxHash.begin(), signedSessionTxHash.end());
|
||||||
|
CC *importEval = CCNewEval("ImportPayout", vHash);
|
||||||
|
|
||||||
std::vector<CC*> subs;
|
CC *oneof = CCNewThreshold(1, PlayerConditions());
|
||||||
for (int i=0; i<players.size(); i++)
|
|
||||||
subs[i] = CCNewSecp256k1(players[i]);
|
|
||||||
CC *oneof = CCNewThreshold(1, subs);
|
|
||||||
|
|
||||||
import = CCNewThreshold(2, {oneof, importEval});
|
import = CCNewThreshold(2, {oneof, importEval});
|
||||||
}
|
}
|
||||||
@@ -203,13 +125,11 @@ CC* MakePayoutCond(uint256 signedSessionTx)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
CMutableTransaction PokerProtocol::MakeStakeTx(CAmount totalPayout, std::vector<CTxIn> playerInputs,
|
CMutableTransaction BetProtocol::MakeStakeTx(CAmount totalPayout, uint256 signedSessionTxHash)
|
||||||
uint256 signedSessionTx)
|
|
||||||
{
|
{
|
||||||
CMutableTransaction mtx;
|
CMutableTransaction mtx;
|
||||||
mtx.vin = playerInputs;
|
|
||||||
|
|
||||||
CC *payoutCond = MakePayoutCond(signedSessionTx);push
|
CC *payoutCond = MakePayoutCond(signedSessionTxHash);
|
||||||
mtx.vout.push_back(CTxOut(totalPayout, CCPubKey(payoutCond)));
|
mtx.vout.push_back(CTxOut(totalPayout, CCPubKey(payoutCond)));
|
||||||
cc_free(payoutCond);
|
cc_free(payoutCond);
|
||||||
|
|
||||||
@@ -217,24 +137,23 @@ CMutableTransaction PokerProtocol::MakeStakeTx(CAmount totalPayout, std::vector<
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
CMutableTransaction PokerProtocol::MakeAgreePayoutTx(std::vector<CTxOut> payouts,
|
CMutableTransaction BetProtocol::MakeAgreePayoutTx(std::vector<CTxOut> payouts,
|
||||||
CC *signedPayoutCond, uint256 signedStakeTxHash)
|
uint256 signedStakeTxHash)
|
||||||
{
|
{
|
||||||
CMutableTransaction mtx;
|
CMutableTransaction mtx;
|
||||||
mtx.vin.push_back(CTxIn(signedStakeTxHash, 0, CCSig(signedPayoutCond)));
|
mtx.vin.push_back(CTxIn(signedStakeTxHash, 0, CScript()));
|
||||||
mtx.vouts = payouts;
|
mtx.vout = payouts;
|
||||||
return mtx;
|
return mtx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
CMutableTransaction PokerProtocol::MakeImportPayoutTx(std::vector<CTxOut> payouts,
|
CMutableTransaction BetProtocol::MakeImportPayoutTx(std::vector<CTxOut> payouts,
|
||||||
CC *signedPayoutCond, CTransaction signedDisputeTx, uint256 signedStakeTxHash)
|
CTransaction signedDisputeTx, uint256 signedStakeTxHash, MoMProof momProof)
|
||||||
{
|
{
|
||||||
std::vector<unsigned char> vDisputeTx;
|
|
||||||
signedDisputeTx >> CDataStream(vDisputeTx, SER_DISK, PROTOCOL_VERSION);
|
|
||||||
CMutableTransaction mtx;
|
CMutableTransaction mtx;
|
||||||
mtx.vin.push_back(CTxInput(signedStakeTxHash, 0, CCSig(signedPayoutCond)));
|
mtx.vin.push_back(CTxIn(signedStakeTxHash, 0, CScript()));
|
||||||
mtx.vout = payouts;
|
mtx.vout = payouts;
|
||||||
CMutableTransaction.vout.insert(0, CTxOutput(0, CScript() << OP_RETURN << "PROOF HERE"));
|
mtx.vout.insert(mtx.vout.begin(), CTxOut(0, CScript() << OP_RETURN << CheckSerialize(momProof)));
|
||||||
CMutableTransaction.vout.insert(1, CTxOutput(0, CScript() << OP_RETURN << vDisputeTx));
|
mtx.vout.insert(mtx.vout.begin()+1, CTxOut(0, CScript() << OP_RETURN << CheckSerialize(signedDisputeTx)));
|
||||||
|
return mtx;
|
||||||
}
|
}
|
||||||
|
|||||||
85
src/cc/betprotocol.h
Normal file
85
src/cc/betprotocol.h
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
#ifndef BETPROTOCOL_H
|
||||||
|
#define BETPROTOCOL_H
|
||||||
|
|
||||||
|
#include "pubkey.h"
|
||||||
|
#include "primitives/transaction.h"
|
||||||
|
#include "cryptoconditions/include/cryptoconditions.h"
|
||||||
|
|
||||||
|
|
||||||
|
#define ExecMerkle CBlock::CheckMerkleBranch
|
||||||
|
|
||||||
|
|
||||||
|
class MoMProof
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
int nIndex;
|
||||||
|
std::vector<uint256> branch;
|
||||||
|
uint256 notarisationHash;
|
||||||
|
|
||||||
|
MoMProof() {}
|
||||||
|
MoMProof(int i, std::vector<uint256> b, uint256 n) : notarisationHash(n), nIndex(i), branch(b) {}
|
||||||
|
|
||||||
|
ADD_SERIALIZE_METHODS;
|
||||||
|
|
||||||
|
template <typename Stream, typename Operation>
|
||||||
|
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
|
||||||
|
READWRITE(VARINT(nIndex));
|
||||||
|
READWRITE(branch);
|
||||||
|
READWRITE(notarisationHash);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class DisputeHeader
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
int waitBlocks;
|
||||||
|
std::vector<unsigned char> vmParams;
|
||||||
|
|
||||||
|
DisputeHeader() {}
|
||||||
|
DisputeHeader(int w, std::vector<unsigned char> vmp) : waitBlocks(w), vmParams(vmp) {}
|
||||||
|
|
||||||
|
ADD_SERIALIZE_METHODS;
|
||||||
|
|
||||||
|
template <typename Stream, typename Operation>
|
||||||
|
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
|
||||||
|
READWRITE(VARINT(waitBlocks));
|
||||||
|
READWRITE(vmParams);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class BetProtocol
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
char* disputeFunc = (char*) "DisputeBet";
|
||||||
|
std::vector<CC*> playerConditions();
|
||||||
|
public:
|
||||||
|
CAmount MINFEE = 1;
|
||||||
|
std::vector<CPubKey> players;
|
||||||
|
DisputeHeader disputeHeader;
|
||||||
|
|
||||||
|
// Utility
|
||||||
|
BetProtocol(std::vector<CPubKey> ps, DisputeHeader dh) : players(ps), disputeHeader(dh) {}
|
||||||
|
std::vector<CC*> PlayerConditions();
|
||||||
|
|
||||||
|
// on PANGEA
|
||||||
|
CC* MakeDisputeCond();
|
||||||
|
CMutableTransaction MakeSessionTx();
|
||||||
|
CMutableTransaction MakeDisputeTx(uint256 signedSessionTxHash, uint256 vmResultHash);
|
||||||
|
CMutableTransaction MakePostEvidenceTx(uint256 signedSessionTxHash,
|
||||||
|
int playerIndex, std::vector<unsigned char> state);
|
||||||
|
|
||||||
|
// on KMD
|
||||||
|
CC* MakePayoutCond(uint256 signedSessionTxHash);
|
||||||
|
CMutableTransaction MakeStakeTx(CAmount totalPayout, uint256 signedSessionTx);
|
||||||
|
CMutableTransaction MakeAgreePayoutTx(std::vector<CTxOut> payouts, uint256 signedStakeTxHash);
|
||||||
|
CMutableTransaction MakeImportPayoutTx(std::vector<CTxOut> payouts,
|
||||||
|
CTransaction signedDisputeTx, uint256 signedStakeTxHash, MoMProof momProof);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
CC* CCNewSecp256k1(CPubKey k);
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* BETPROTOCOL_H */
|
||||||
@@ -1,34 +1,27 @@
|
|||||||
#include "primitives/transaction.h"
|
#include <cryptoconditions.h>
|
||||||
#include "streams.h"
|
|
||||||
|
#include "hash.h"
|
||||||
#include "chain.h"
|
#include "chain.h"
|
||||||
#include "main.h"
|
#include "version.h"
|
||||||
|
#include "komodo_cc.h"
|
||||||
#include "cc/eval.h"
|
#include "cc/eval.h"
|
||||||
#include "cryptoconditions/include/cryptoconditions.h"
|
#include "cc/betprotocol.h"
|
||||||
|
#include "primitives/transaction.h"
|
||||||
|
|
||||||
|
|
||||||
bool GetSpends(uint256 hash, std::vector<boost::optional<CTransaction>> &spends)
|
class DisputeHeader;
|
||||||
|
|
||||||
|
|
||||||
|
static bool GetOpReturnHash(CScript script, uint256 &hash)
|
||||||
{
|
{
|
||||||
// NOT IMPLEMENTED
|
std::vector<unsigned char> vHash;
|
||||||
return false;
|
GetOpReturnData(script, vHash);
|
||||||
|
if (vHash.size() != 32) return false;
|
||||||
|
memcpy(hash.begin(), vHash.data(), 32);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class DisputeHeader
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
int waitBlocks;
|
|
||||||
std::vector<unsigned char> vmHeader;
|
|
||||||
|
|
||||||
ADD_SERIALIZE_METHODS;
|
|
||||||
|
|
||||||
template <typename Stream, typename Operation>
|
|
||||||
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
|
|
||||||
READWRITE(VARINT(waitBlocks));
|
|
||||||
READWRITE(vmHeader);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Crypto-Condition EVAL method that resolves a dispute of a session
|
* Crypto-Condition EVAL method that resolves a dispute of a session
|
||||||
*
|
*
|
||||||
@@ -42,48 +35,51 @@ public:
|
|||||||
* in 0: Spends Session TX first output, reveals DisputeHeader
|
* in 0: Spends Session TX first output, reveals DisputeHeader
|
||||||
* out 0: OP_RETURN hash of payouts
|
* out 0: OP_RETURN hash of payouts
|
||||||
*/
|
*/
|
||||||
bool DisputePayout(AppVM &vm, const CC *cond, const CTransaction *disputeTx, int nIn)
|
bool Eval::DisputePayout(AppVM &vm, const CC *cond, const CTransaction &disputeTx, unsigned int nIn)
|
||||||
{
|
{
|
||||||
// TODO: Error messages!
|
if (disputeTx.vout.size() == 0) return Invalid("no-vouts");
|
||||||
if (disputeTx->vout.size() < 2) return 0;
|
|
||||||
|
|
||||||
// get payouts hash
|
// get payouts hash
|
||||||
std::vector<unsigned char> vPayoutHash;
|
|
||||||
uint256 payoutHash;
|
uint256 payoutHash;
|
||||||
if (!GetOpReturnData(disputeTx->vout[0].scriptPubKey, vPayoutHash)) return 0;
|
if (!GetOpReturnHash(disputeTx.vout[0].scriptPubKey, payoutHash))
|
||||||
memcpy(payoutHash.begin(), vPayoutHash.data(), 32);
|
return Invalid("invalid-payout-hash");
|
||||||
|
|
||||||
// load dispute header
|
// load dispute header
|
||||||
DisputeHeader disputeHeader;
|
DisputeHeader disputeHeader;
|
||||||
std::vector<unsigned char> headerData(cond->paramsBin,
|
std::vector<unsigned char> headerData(cond->paramsBin,
|
||||||
cond->paramsBin+cond->paramsBinLength);
|
cond->paramsBin+cond->paramsBinLength);
|
||||||
CDataStream(headerData, SER_DISK, PROTOCOL_VERSION) >> disputeHeader;
|
if (!CheckDeserialize(headerData, disputeHeader))
|
||||||
// TODO: exception? end of stream?
|
return Invalid("invalid-dispute-header");
|
||||||
|
|
||||||
// ensure that enough time has passed
|
// ensure that enough time has passed
|
||||||
CTransaction sessionTx;
|
CTransaction sessionTx;
|
||||||
uint256 sessionBlockHash;
|
uint256 sessionBlockHash;
|
||||||
if (!GetTransaction(disputeTx->vin[0].prevout.hash, sessionTx, sessionBlockHash, false))
|
CBlockIndex sessionBlock;
|
||||||
return false; // wth? TODO: log TODO: MUST be upsteam of disputeTx, how to ensure?
|
|
||||||
// below does this by looking up block in blockindex
|
if (!GetTx(disputeTx.vin[0].prevout.hash, sessionTx, sessionBlockHash, false))
|
||||||
// what if GetTransaction returns from mempool, maybe theres no block?
|
return Error("couldnt-get-parent");
|
||||||
CBlockIndex* sessionBlockIndex = mapBlockIndex[sessionBlockHash];
|
// TODO: This may not be an error, if both txs are to go into the same block...
|
||||||
if (chainActive.Height() < sessionBlockIndex->nHeight + disputeHeader.waitBlocks)
|
// Probably change it to Invalid
|
||||||
return false; // Not yet
|
if (!GetBlock(sessionBlockHash, sessionBlock))
|
||||||
|
return Error("couldnt-get-block");
|
||||||
|
|
||||||
|
if (GetCurrentHeight() < sessionBlock.nHeight + disputeHeader.waitBlocks)
|
||||||
|
return Invalid("dispute-too-soon"); // Not yet
|
||||||
|
|
||||||
// get spends
|
// get spends
|
||||||
std::vector<boost::optional<CTransaction>> spends;
|
std::vector<CTransaction> spends;
|
||||||
if (!GetSpends(disputeTx->vin[0].prevout.hash, spends)) return 0;
|
if (!GetSpends(disputeTx.vin[0].prevout.hash, spends))
|
||||||
|
return Error("couldnt-get-spends");
|
||||||
|
|
||||||
// verify result from VM
|
// verify result from VM
|
||||||
int maxLength = -1;
|
int maxLength = -1;
|
||||||
uint256 bestPayout;
|
uint256 bestPayout;
|
||||||
for (int i=1; i<spends.size(); i++)
|
for (int i=1; i<spends.size(); i++)
|
||||||
{
|
{
|
||||||
if (!spends[i]) continue;
|
std::vector<unsigned char> vmState;
|
||||||
std::vector<unsigned char> vmBody;
|
if (!spends[i].vout.size() > 0) continue;
|
||||||
if (!GetOpReturnData(spends[i]->vout[0].scriptPubKey, vmBody)) continue;
|
if (!GetOpReturnData(spends[i].vout[0].scriptPubKey, vmState)) continue;
|
||||||
auto out = vm.evaluate(disputeHeader.vmHeader, vmBody);
|
auto out = vm.evaluate(disputeHeader.vmParams, vmState);
|
||||||
uint256 resultHash = SerializeHash(out.second);
|
uint256 resultHash = SerializeHash(out.second);
|
||||||
if (out.first > maxLength) {
|
if (out.first > maxLength) {
|
||||||
maxLength = out.first;
|
maxLength = out.first;
|
||||||
@@ -98,5 +94,7 @@ bool DisputePayout(AppVM &vm, const CC *cond, const CTransaction *disputeTx, int
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return bestPayout == payoutHash;
|
if (maxLength == -1) return Invalid("no-evidence");
|
||||||
|
|
||||||
|
return bestPayout == payoutHash ? Valid() : Invalid("wrong-payout");
|
||||||
}
|
}
|
||||||
|
|||||||
137
src/cc/eval.cpp
137
src/cc/eval.cpp
@@ -1,56 +1,151 @@
|
|||||||
|
#include <assert.h>
|
||||||
|
#include <cryptoconditions.h>
|
||||||
|
|
||||||
#include "primitives/transaction.h"
|
#include "primitives/transaction.h"
|
||||||
#include "komodo_cc.h"
|
#include "komodo_cc.h"
|
||||||
#include "cc/eval.h"
|
#include "cc/eval.h"
|
||||||
#include <cryptoconditions.h>
|
#include "main.h"
|
||||||
|
#include "chain.h"
|
||||||
|
|
||||||
|
|
||||||
|
Eval* EVAL_TEST = 0;
|
||||||
|
|
||||||
|
|
||||||
|
bool RunCCEval(const CC *cond, const CTransaction &tx, unsigned int nIn)
|
||||||
|
{
|
||||||
|
Eval eval_;
|
||||||
|
Eval *eval = EVAL_TEST;
|
||||||
|
if (!eval) eval = &eval_;
|
||||||
|
|
||||||
|
bool out = eval->Dispatch(cond, tx, nIn);
|
||||||
|
assert(eval->state.IsValid() == out);
|
||||||
|
|
||||||
|
if (eval->state.IsValid()) return true;
|
||||||
|
|
||||||
|
std::string lvl = eval->state.IsInvalid() ? "Invalid" : "Error!";
|
||||||
|
fprintf(stderr, "CC Eval %s %s: %s in tx %s\n", lvl.data(), cond->method,
|
||||||
|
eval->state.GetRejectReason().data(), tx.GetHash().GetHex().data());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Test the validity of an Eval node
|
* Test the validity of an Eval node
|
||||||
*/
|
*/
|
||||||
bool EvalConditionValidity(const CC *cond, const CTransaction *txTo, int nIn)
|
bool Eval::Dispatch(const CC *cond, const CTransaction &txTo, unsigned int nIn)
|
||||||
{
|
{
|
||||||
if (strcmp(cond->method, "TestEval") == 0) {
|
if (strcmp(cond->method, "TestEval") == 0) {
|
||||||
return cond->paramsBinLength == 8 &&
|
bool valid = cond->paramsBinLength == 8 && memcmp(cond->paramsBin, "TestEval", 8) == 0;
|
||||||
memcmp(cond->paramsBin, "TestEval", 8) == 0;
|
return valid ? Valid() : Invalid("testing");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strcmp(cond->method, "ImportPayout") == 0) {
|
if (strcmp(cond->method, "ImportPayout") == 0) {
|
||||||
return CheckImportPayout(cond, txTo, nIn);
|
return ImportPayout(cond, txTo, nIn);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Example of how you might call DisputePayout
|
/* Example of how you might call DisputePayout
|
||||||
|
|
||||||
if (strcmp(ASSETCHAINS_SYMBOL, "PANGEA") == 0) {
|
if (strcmp(ASSETCHAINS_SYMBOL, "PANGEA") == 0) {
|
||||||
if (strcmp(cond->method, "DisputePoker") == 0) {
|
if (strcmp(cond->method, "DisputePoker") == 0) {
|
||||||
return DisputePayout(PokerVM(), cond, txTo, nIn);
|
return DisputePayout(PokerVM(), cond, txTo, nIn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
fprintf(stderr, "no defined behaviour for method: %s\n", cond->method);
|
return Invalid("no-such-method");
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool Eval::GetSpends(uint256 hash, std::vector<CTransaction> &spends) const
|
||||||
bool GetPushData(const CScript &sig, std::vector<unsigned char> &data)
|
|
||||||
{
|
{
|
||||||
opcodetype opcode;
|
// NOT IMPLEMENTED
|
||||||
auto pc = sig.begin();
|
|
||||||
if (sig.GetOp(pc, opcode, data)) return opcode > OP_0 && opcode <= OP_PUSHDATA4;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool GetOpReturnData(const CScript &sig, std::vector<unsigned char> &data)
|
bool Eval::GetTx(const uint256 &hash, CTransaction &txOut, uint256 &hashBlock, bool fAllowSlow) const
|
||||||
{
|
{
|
||||||
auto pc = sig.begin();
|
return GetTransaction(hash, txOut, hashBlock, fAllowSlow);
|
||||||
opcodetype opcode;
|
}
|
||||||
if (sig.GetOp2(pc, opcode, NULL))
|
|
||||||
if (opcode == OP_RETURN)
|
|
||||||
if (sig.GetOp(pc, opcode, data))
|
unsigned int Eval::GetCurrentHeight() const
|
||||||
return opcode > OP_0 && opcode <= OP_PUSHDATA4;
|
{
|
||||||
|
return chainActive.Height();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool Eval::GetBlock(uint256 hash, CBlockIndex& blockIdx) const
|
||||||
|
{
|
||||||
|
auto r = mapBlockIndex.find(hash);
|
||||||
|
if (r != mapBlockIndex.end()) {
|
||||||
|
blockIdx = *r->second;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extern int32_t komodo_notaries(uint8_t pubkeys[64][33],int32_t height,uint32_t timestamp);
|
||||||
|
|
||||||
|
|
||||||
|
bool Eval::CheckNotaryInputs(const CTransaction &tx, uint32_t height, uint32_t timestamp) const
|
||||||
|
{
|
||||||
|
if (tx.vin.size() < 11) return false;
|
||||||
|
|
||||||
|
uint8_t notaries[64][33];
|
||||||
|
uint8_t seenNotaries[64];
|
||||||
|
int nNotaries = komodo_notaries(notaries, height, timestamp);
|
||||||
|
char pk[33];
|
||||||
|
|
||||||
|
BOOST_FOREACH(const CTxIn &txIn, tx.vin)
|
||||||
|
{
|
||||||
|
// Get notary pubkey
|
||||||
|
CTransaction tx;
|
||||||
|
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;
|
||||||
|
|
||||||
|
// Check it's a notary
|
||||||
|
for (int i=0; i<nNotaries; i++) {
|
||||||
|
if (!seenNotaries[i]) {
|
||||||
|
if (memcmp(pk, notaries[i], 33) == 0) {
|
||||||
|
seenNotaries[i] = 1;
|
||||||
|
goto found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
found:;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get MoM from a notarisation tx hash
|
||||||
|
*/
|
||||||
|
bool Eval::GetMoM(const uint256 notaryHash, uint256 &mom) const
|
||||||
|
{
|
||||||
|
CTransaction notarisationTx;
|
||||||
|
uint256 notarisationBlock;
|
||||||
|
if (!GetTx(notaryHash, notarisationTx, notarisationBlock, true)) return 0;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,18 +1,56 @@
|
|||||||
#ifndef CC_EVAL_H
|
#ifndef CC_EVAL_H
|
||||||
#define CC_EVAL_H
|
#define CC_EVAL_H
|
||||||
|
|
||||||
#include "cryptoconditions/include/cryptoconditions.h"
|
#include <cryptoconditions.h>
|
||||||
|
|
||||||
|
#include "chain.h"
|
||||||
|
#include "streams.h"
|
||||||
|
#include "version.h"
|
||||||
|
#include "consensus/validation.h"
|
||||||
#include "primitives/transaction.h"
|
#include "primitives/transaction.h"
|
||||||
|
|
||||||
/*
|
|
||||||
* Test validity of a CC_Eval node
|
|
||||||
*/
|
|
||||||
bool EvalConditionValidity(const CC *cond, const CTransaction *tx, int nIn);
|
|
||||||
|
|
||||||
/*
|
class AppVM;
|
||||||
* Test an ImportPayout CC Eval condition
|
|
||||||
*/
|
|
||||||
bool CheckImportPayout(const CC *cond, const CTransaction *payoutTx, int nIn);
|
class Eval
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CValidationState state;
|
||||||
|
|
||||||
|
bool Invalid(std::string s) { return state.Invalid(false, 0, s); }
|
||||||
|
bool Error(std::string s) { return state.Error(s); }
|
||||||
|
bool Valid() { return true; }
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Test validity of a CC_Eval node
|
||||||
|
*/
|
||||||
|
virtual bool Dispatch(const CC *cond, const CTransaction &tx, unsigned int nIn);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Dispute a payout using a VM
|
||||||
|
*/
|
||||||
|
bool DisputePayout(AppVM &vm, const CC *cond, const CTransaction &disputeTx, unsigned int nIn);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Test an ImportPayout CC Eval condition
|
||||||
|
*/
|
||||||
|
bool ImportPayout(const CC *cond, const CTransaction &payoutTx, unsigned int nIn);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* IO functions
|
||||||
|
*/
|
||||||
|
virtual bool GetTx(const uint256 &hash, CTransaction &txOut, uint256 &hashBlock, bool fAllowSlow) const;
|
||||||
|
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 bool CheckNotaryInputs(const CTransaction &tx, uint32_t height, uint32_t timestamp) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
bool RunCCEval(const CC *cond, const CTransaction &tx, unsigned int nIn);
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Virtual machine to use in the case of on-chain app evaluation
|
* Virtual machine to use in the case of on-chain app evaluation
|
||||||
@@ -30,20 +68,36 @@ public:
|
|||||||
evaluate(std::vector<unsigned char> header, std::vector<unsigned char> body) = 0;
|
evaluate(std::vector<unsigned char> header, std::vector<unsigned char> body) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
|
||||||
* Test a DisputePayout CC Eval condition, using a provided AppVM
|
|
||||||
*/
|
|
||||||
bool DisputePayout(AppVM &vm, const CC *cond, const CTransaction *disputeTx, int nIn);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Get PUSHDATA from a script
|
* Serialisation boilerplate
|
||||||
*/
|
*/
|
||||||
bool GetPushData(const CScript &sig, std::vector<unsigned char> &data);
|
template <class T>
|
||||||
|
std::vector<unsigned char> CheckSerialize(T &in);
|
||||||
|
template <class T>
|
||||||
|
bool CheckDeserialize(std::vector<unsigned char> vIn, T &out);
|
||||||
|
|
||||||
/*
|
|
||||||
* Get OP_RETURN data from a script
|
|
||||||
*/
|
template <class T>
|
||||||
bool GetOpReturnData(const CScript &sig, std::vector<unsigned char> &data);
|
std::vector<unsigned char> CheckSerialize(T &in)
|
||||||
|
{
|
||||||
|
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
|
||||||
|
ss << in;
|
||||||
|
return std::vector<unsigned char>(ss.begin(), ss.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
bool CheckDeserialize(std::vector<unsigned char> vIn, T &out)
|
||||||
|
{
|
||||||
|
CDataStream ss(vIn, SER_NETWORK, PROTOCOL_VERSION);
|
||||||
|
try {
|
||||||
|
ss >> out;
|
||||||
|
if (ss.eof()) return true;
|
||||||
|
} catch(...) {}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#endif /* CC_EVAL_H */
|
#endif /* CC_EVAL_H */
|
||||||
|
|||||||
@@ -1,78 +1,11 @@
|
|||||||
#include "primitives/transaction.h"
|
#include <cryptoconditions.h>
|
||||||
#include "streams.h"
|
|
||||||
#include "chain.h"
|
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
|
#include "chain.h"
|
||||||
|
#include "streams.h"
|
||||||
#include "cc/eval.h"
|
#include "cc/eval.h"
|
||||||
#include "cc/importpayout.h"
|
#include "cc/betprotocol.h"
|
||||||
#include "cryptoconditions/include/cryptoconditions.h"
|
#include "primitives/transaction.h"
|
||||||
|
|
||||||
|
|
||||||
extern int32_t komodo_notaries(uint8_t pubkeys[64][33],int32_t height,uint32_t timestamp);
|
|
||||||
|
|
||||||
bool DerefNotaryPubkey(const COutPoint &prevout, char *pk33)
|
|
||||||
{
|
|
||||||
CTransaction tx;
|
|
||||||
uint256 hashBlock;
|
|
||||||
if (!GetTransaction(prevout.hash, tx, hashBlock, false)) return false;
|
|
||||||
if (tx.vout.size() < prevout.n) return false;
|
|
||||||
const unsigned char *script = tx.vout[prevout.n].scriptPubKey.data();
|
|
||||||
if (script[0] != 33) return false;
|
|
||||||
memcpy(pk33, script+1, 33);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CheckNotaryInputs(const CTransaction &tx, uint32_t height, uint32_t timestamp)
|
|
||||||
{
|
|
||||||
if (tx.vin.size() < 11) return false;
|
|
||||||
|
|
||||||
uint8_t notaries[64][33];
|
|
||||||
uint8_t seenNotaries[64];
|
|
||||||
int nNotaries = komodo_notaries(notaries, height, timestamp);
|
|
||||||
char *pk;
|
|
||||||
|
|
||||||
BOOST_FOREACH(const CTxIn &txIn, tx.vin)
|
|
||||||
{
|
|
||||||
if (!DerefNotaryPubkey(txIn.prevout, pk)) return false;
|
|
||||||
|
|
||||||
for (int i=0; i<nNotaries; i++) {
|
|
||||||
if (!seenNotaries[i]) {
|
|
||||||
if (memcmp(pk, notaries[i], 33) == 0) {
|
|
||||||
seenNotaries[i] = 1;
|
|
||||||
goto found;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
found:;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Get MoM from a notarisation tx hash
|
|
||||||
*/
|
|
||||||
bool GetMoM(const uint256 notaryHash, uint256 &mom)
|
|
||||||
{
|
|
||||||
CTransaction notarisationTx;
|
|
||||||
uint256 notarisationBlock;
|
|
||||||
if (!GetTransaction(notaryHash, notarisationTx, notarisationBlock, true)) return 0;
|
|
||||||
CBlockIndex* blockindex = mapBlockIndex[notarisationBlock];
|
|
||||||
if (!CheckNotaryInputs(notarisationTx, blockindex->nHeight, blockindex->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;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define ExecMerkle CBlock::CheckMerkleBranch
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -97,54 +30,51 @@ bool GetMoM(const uint256 notaryHash, uint256 &mom)
|
|||||||
* out 0: OP_RETURN hash of payouts
|
* out 0: OP_RETURN hash of payouts
|
||||||
* out 1-: anything
|
* out 1-: anything
|
||||||
*/
|
*/
|
||||||
bool CheckImportPayout(const CC *cond, const CTransaction *payoutTx, int nIn)
|
bool Eval::ImportPayout(const CC *cond, const CTransaction &payoutTx, unsigned int nIn)
|
||||||
{
|
{
|
||||||
// TODO: Error messages!
|
// TODO: Error messages!
|
||||||
if (payoutTx->vin.size() != 1) return 0;
|
if (payoutTx.vout.size() < 2) return Invalid("need-2-vouts");
|
||||||
if (payoutTx->vout.size() < 2) return 0;
|
|
||||||
|
|
||||||
// Get hash of payouts
|
|
||||||
std::vector<CTxOut> payouts(payoutTx->vout.begin() + 2, payoutTx->vout.end());
|
|
||||||
uint256 payoutsHash = SerializeHash(payouts);
|
|
||||||
std::vector<unsigned char> vPayoutsHash(payoutsHash.begin(), payoutsHash.end());
|
|
||||||
|
|
||||||
// load disputeTx from vout[1]
|
// load disputeTx from vout[1]
|
||||||
CTransaction disputeTx;
|
CTransaction disputeTx;
|
||||||
{
|
std::vector<unsigned char> exportData;
|
||||||
std::vector<unsigned char> exportData;
|
GetOpReturnData(payoutTx.vout[1].scriptPubKey, exportData);
|
||||||
if (!GetOpReturnData(payoutTx->vout[1].scriptPubKey, exportData)) return 0;
|
if (!CheckDeserialize(exportData, disputeTx))
|
||||||
CDataStream(exportData, SER_DISK, PROTOCOL_VERSION) >> disputeTx;
|
return Invalid("invalid-dispute-tx");
|
||||||
// TODO: end of stream? exception?
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check disputeTx.0 is vPayoutsHash
|
// Check disputeTx.0 shows correct payouts
|
||||||
std::vector<unsigned char> exportPayoutsHash;
|
{
|
||||||
if (!GetOpReturnData(disputeTx.vout[0].scriptPubKey, exportPayoutsHash)) return 0;
|
std::vector<CTxOut> payouts(payoutTx.vout.begin() + 2, payoutTx.vout.end());
|
||||||
if (exportPayoutsHash != vPayoutsHash) return 0;
|
uint256 payoutsHash = SerializeHash(payouts);
|
||||||
|
std::vector<unsigned char> vPayoutsHash(payoutsHash.begin(), payoutsHash.end());
|
||||||
|
|
||||||
|
if (disputeTx.vout[0].scriptPubKey != CScript() << OP_RETURN << vPayoutsHash)
|
||||||
|
return Invalid("wrong-payouts");
|
||||||
|
}
|
||||||
|
|
||||||
// Check disputeTx spends sessionTx.0
|
// Check disputeTx spends sessionTx.0
|
||||||
// condition ImportPayout params is session ID from other chain
|
// condition ImportPayout params is session ID from other chain
|
||||||
{
|
{
|
||||||
if (cond->paramsBinLength != 32) return 0;
|
if (cond->paramsBinLength != 32) return Invalid("malformed-params");
|
||||||
COutPoint prevout = disputeTx.vin[0].prevout;
|
COutPoint prevout = disputeTx.vin[0].prevout;
|
||||||
if (memcmp(prevout.hash.begin(), cond->paramsBin, 32) != 0 ||
|
if (memcmp(prevout.hash.begin(), cond->paramsBin, 32) != 0 ||
|
||||||
prevout.n != 0) return 0;
|
prevout.n != 0) return Invalid("wrong-session");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check disputeTx solves momproof from vout[0]
|
// Check disputeTx solves momproof from vout[0]
|
||||||
{
|
{
|
||||||
std::vector<unsigned char> vchMomProof;
|
std::vector<unsigned char> vProof;
|
||||||
if (!GetOpReturnData(payoutTx->vout[0].scriptPubKey, vchMomProof)) return 0;
|
GetOpReturnData(payoutTx.vout[0].scriptPubKey, vProof);
|
||||||
|
MoMProof proof;
|
||||||
|
if (!CheckDeserialize(vProof, proof))
|
||||||
|
return Invalid("invalid-mom-proof-payload");
|
||||||
|
|
||||||
MoMProof momProof;
|
uint256 MoM;
|
||||||
CDataStream(vchMomProof, SER_DISK, PROTOCOL_VERSION) >> momProof;
|
if (!GetMoM(proof.notarisationHash, MoM)) return Invalid("coudnt-load-mom");
|
||||||
|
|
||||||
uint256 mom;
|
|
||||||
if (!GetMoM(momProof.notarisationHash, mom)) return 0;
|
|
||||||
|
|
||||||
uint256 proofResult = ExecMerkle(disputeTx.GetHash(), momProof.branch, momProof.nIndex);
|
if (MoM != ExecMerkle(disputeTx.GetHash(), proof.branch, proof.nIndex))
|
||||||
if (proofResult != mom) return 0;
|
return Invalid("mom-check-fail");
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1;
|
return Valid();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
#ifndef KOMODO_TXPROOF_H
|
|
||||||
#define KOMODO_TXPROOF_H
|
|
||||||
|
|
||||||
|
|
||||||
class MoMProof
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
int nIndex;
|
|
||||||
std::vector<uint256> branch;
|
|
||||||
uint256 notarisationHash;
|
|
||||||
|
|
||||||
MoMProof() {}
|
|
||||||
MoMProof(int i, std::vector<uint256> b, uint256 n) : notarisationHash(n), nIndex(i), branch(b) {}
|
|
||||||
|
|
||||||
ADD_SERIALIZE_METHODS;
|
|
||||||
|
|
||||||
template <typename Stream, typename Operation>
|
|
||||||
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
|
|
||||||
READWRITE(VARINT(nIndex));
|
|
||||||
READWRITE(branch);
|
|
||||||
READWRITE(notarisationHash);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
#endif /* KOMODO_TXPROOF_H */
|
|
||||||
@@ -96,6 +96,7 @@ char* cc_typeName(const CC *cond);
|
|||||||
enum CCTypeId cc_typeId(const CC *cond);
|
enum CCTypeId cc_typeId(const CC *cond);
|
||||||
unsigned long cc_getCost(const CC *cond);
|
unsigned long cc_getCost(const CC *cond);
|
||||||
uint32_t cc_typeMask(const CC *cond);
|
uint32_t cc_typeMask(const CC *cond);
|
||||||
|
int cc_isAnon(const CC *cond);
|
||||||
void cc_free(struct CC *cond);
|
void cc_free(struct CC *cond);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|||||||
@@ -284,7 +284,7 @@ char *cc_typeName(const CC *cond) {
|
|||||||
|
|
||||||
CC *cc_new(int typeId) {
|
CC *cc_new(int typeId) {
|
||||||
CC *cond = calloc(1, sizeof(CC));
|
CC *cond = calloc(1, sizeof(CC));
|
||||||
cond->type = CCTypeRegistry[typeId];
|
cond->type = typeId == CC_Anon ? &CC_AnonType : CCTypeRegistry[typeId];
|
||||||
return cond;
|
return cond;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -130,15 +130,19 @@ static CC *thresholdFromFulfillment(const Fulfillment_t *ffill) {
|
|||||||
static Fulfillment_t *thresholdToFulfillment(const CC *cond) {
|
static Fulfillment_t *thresholdToFulfillment(const CC *cond) {
|
||||||
CC *sub;
|
CC *sub;
|
||||||
Fulfillment_t *fulfillment;
|
Fulfillment_t *fulfillment;
|
||||||
|
|
||||||
|
// Make a copy of subconditions so we can leave original order alone
|
||||||
|
CC** subconditions = malloc(cond->size*sizeof(CC*));
|
||||||
|
memcpy(subconditions, cond->subconditions, cond->size*sizeof(CC*));
|
||||||
|
|
||||||
qsort(cond->subconditions, cond->size, sizeof(CC*), cmpConditionCost);
|
qsort(subconditions, cond->size, sizeof(CC*), cmpConditionCost);
|
||||||
|
|
||||||
ThresholdFulfillment_t *tf = calloc(1, sizeof(ThresholdFulfillment_t));
|
ThresholdFulfillment_t *tf = calloc(1, sizeof(ThresholdFulfillment_t));
|
||||||
|
|
||||||
int needed = cond->threshold;
|
int needed = cond->threshold;
|
||||||
|
|
||||||
for (int i=0; i<cond->size; i++) {
|
for (int i=0; i<cond->size; i++) {
|
||||||
sub = cond->subconditions[i];
|
sub = subconditions[i];
|
||||||
if (needed && (fulfillment = asnFulfillmentNew(sub))) {
|
if (needed && (fulfillment = asnFulfillmentNew(sub))) {
|
||||||
asn_set_add(&tf->subfulfillments, fulfillment);
|
asn_set_add(&tf->subfulfillments, fulfillment);
|
||||||
needed--;
|
needed--;
|
||||||
@@ -147,6 +151,8 @@ static Fulfillment_t *thresholdToFulfillment(const CC *cond) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
free(subconditions);
|
||||||
|
|
||||||
if (needed) {
|
if (needed) {
|
||||||
ASN_STRUCT_FREE(asn_DEF_ThresholdFulfillment, tf);
|
ASN_STRUCT_FREE(asn_DEF_ThresholdFulfillment, tf);
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -200,7 +206,7 @@ static void thresholdToJSON(const CC *cond, cJSON *params) {
|
|||||||
|
|
||||||
static int thresholdIsFulfilled(const CC *cond) {
|
static int thresholdIsFulfilled(const CC *cond) {
|
||||||
int nFulfilled = 0;
|
int nFulfilled = 0;
|
||||||
for (int i=0; i<cond->threshold; i++) {
|
for (int i=0; i<cond->size; i++) {
|
||||||
if (cc_isFulfilled(cond->subconditions[i])) {
|
if (cc_isFulfilled(cond->subconditions[i])) {
|
||||||
nFulfilled++;
|
nFulfilled++;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,3 +30,72 @@ bool IsSignedCryptoCondition(const CC *cond)
|
|||||||
if (IsSignedCryptoCondition(cond->subconditions[i])) return true;
|
if (IsSignedCryptoCondition(cond->subconditions[i])) return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
CScript CCPubKey(const CC *cond)
|
||||||
|
{
|
||||||
|
unsigned char buf[1000];
|
||||||
|
size_t len = cc_conditionBinary(cond, buf);
|
||||||
|
return CScript() << std::vector<unsigned char>(buf, buf+len) << OP_CHECKCRYPTOCONDITION;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
CScript CCSig(const CC *cond)
|
||||||
|
{
|
||||||
|
unsigned char buf[1000];
|
||||||
|
size_t len = cc_fulfillmentBinary(cond, buf, 1000);
|
||||||
|
auto ffill = std::vector<unsigned char>(buf, buf+len);
|
||||||
|
ffill.push_back(1); // SIGHASH_ALL
|
||||||
|
return CScript() << ffill;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::string CCShowStructure(CC *cond)
|
||||||
|
{
|
||||||
|
std::string out;
|
||||||
|
if (cc_isAnon(cond)) {
|
||||||
|
out = "A" + std::to_string(cc_typeId(cond));
|
||||||
|
}
|
||||||
|
else if (cc_typeId(cond) == CC_Threshold) {
|
||||||
|
out += "(" + std::to_string(cond->threshold) + " of ";
|
||||||
|
for (int i=0; i<cond->size; i++) {
|
||||||
|
out += CCShowStructure(cond->subconditions[i]);
|
||||||
|
if (i < cond->size - 1) out += ",";
|
||||||
|
}
|
||||||
|
out += ")";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
out = std::to_string(cc_typeId(cond));
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
CC* CCPrune(CC *cond)
|
||||||
|
{
|
||||||
|
std::vector<unsigned char> ffillBin;
|
||||||
|
GetPushData(CCSig(cond), ffillBin);
|
||||||
|
return cc_readFulfillmentBinary(ffillBin.data(), ffillBin.size()-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool GetPushData(const CScript &sig, std::vector<unsigned char> &data)
|
||||||
|
{
|
||||||
|
opcodetype opcode;
|
||||||
|
auto pc = sig.begin();
|
||||||
|
if (sig.GetOp(pc, opcode, data)) return opcode > OP_0 && opcode <= OP_PUSHDATA4;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool GetOpReturnData(const CScript &sig, std::vector<unsigned char> &data)
|
||||||
|
{
|
||||||
|
auto pc = sig.begin();
|
||||||
|
opcodetype opcode;
|
||||||
|
if (sig.GetOp2(pc, opcode, NULL))
|
||||||
|
if (opcode == OP_RETURN)
|
||||||
|
if (sig.GetOp(pc, opcode, data))
|
||||||
|
return opcode > OP_0 && opcode <= OP_PUSHDATA4;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#ifndef KOMODO_CC_H
|
#ifndef KOMODO_CC_H
|
||||||
#define KOMODO_CC_H
|
#define KOMODO_CC_H
|
||||||
|
|
||||||
|
#include "script/script.h"
|
||||||
#include "cryptoconditions/include/cryptoconditions.h"
|
#include "cryptoconditions/include/cryptoconditions.h"
|
||||||
|
|
||||||
|
|
||||||
@@ -31,4 +32,42 @@ bool IsSupportedCryptoCondition(const CC *cond);
|
|||||||
bool IsSignedCryptoCondition(const CC *cond);
|
bool IsSignedCryptoCondition(const CC *cond);
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Turn a condition into a scriptPubKey
|
||||||
|
*/
|
||||||
|
CScript CCPubKey(const CC *cond);
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Turn a condition into a scriptSig
|
||||||
|
*
|
||||||
|
* Note: This will fail in undefined ways if the condition is missing signatures
|
||||||
|
*/
|
||||||
|
CScript CCSig(const CC *cond);
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Produces a string showing the structure of a CC condition
|
||||||
|
*/
|
||||||
|
std::string CCShowStructure(CC *cond);
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Take a signed CC, encode it, and decode it again. This has the effect
|
||||||
|
* of removing branches unneccesary for fulfillment.
|
||||||
|
*/
|
||||||
|
CC* CCPrune(CC *cond);
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get PUSHDATA from a script
|
||||||
|
*/
|
||||||
|
bool GetPushData(const CScript &sig, std::vector<unsigned char> &data);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get OP_RETURN data from a script
|
||||||
|
*/
|
||||||
|
bool GetOpReturnData(const CScript &sig, std::vector<unsigned char> &data);
|
||||||
|
|
||||||
|
|
||||||
#endif /* KOMODO_CC_H */
|
#endif /* KOMODO_CC_H */
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
#include "chainparams.h"
|
#include "chainparams.h"
|
||||||
#include "checkpoints.h"
|
#include "checkpoints.h"
|
||||||
#include "consensus/validation.h"
|
#include "consensus/validation.h"
|
||||||
#include "cc/importpayout.h"
|
#include "cc/betprotocol.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "primitives/transaction.h"
|
#include "primitives/transaction.h"
|
||||||
#include "rpcserver.h"
|
#include "rpcserver.h"
|
||||||
|
|||||||
@@ -1310,18 +1310,22 @@ int TransactionSignatureChecker::CheckCryptoCondition(
|
|||||||
} catch (logic_error ex) {
|
} catch (logic_error ex) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VerifyEval eval = [] (CC *cond, void *checker) {
|
||||||
|
return ((TransactionSignatureChecker*)checker)->CheckEvalCondition(cond);
|
||||||
|
};
|
||||||
|
|
||||||
int out = cc_verify(cond, (const unsigned char*)&sighash, 32, 0,
|
int out = cc_verify(cond, (const unsigned char*)&sighash, 32, 0,
|
||||||
condBin.data(), condBin.size(), GetCCEval(), (void*)this);
|
condBin.data(), condBin.size(), eval, (void*)this);
|
||||||
cc_free(cond);
|
cc_free(cond);
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
VerifyEval TransactionSignatureChecker::GetCCEval() const {
|
int TransactionSignatureChecker::CheckEvalCondition(const CC *cond) const
|
||||||
return [] (CC *cond, void *checker) {
|
{
|
||||||
fprintf(stderr, "Cannot check crypto-condition Eval outside of server\n");
|
fprintf(stderr, "Cannot check crypto-condition Eval outside of server\n");
|
||||||
return 0;
|
return 0;
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -142,13 +142,12 @@ public:
|
|||||||
|
|
||||||
class TransactionSignatureChecker : public BaseSignatureChecker
|
class TransactionSignatureChecker : public BaseSignatureChecker
|
||||||
{
|
{
|
||||||
private:
|
protected:
|
||||||
const CTransaction* txTo;
|
const CTransaction* txTo;
|
||||||
unsigned int nIn;
|
unsigned int nIn;
|
||||||
const CAmount amount;
|
const CAmount amount;
|
||||||
const PrecomputedTransactionData* txdata;
|
const PrecomputedTransactionData* txdata;
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual bool VerifySignature(const std::vector<unsigned char>& vchSig, const CPubKey& vchPubKey, const uint256& sighash) const;
|
virtual bool VerifySignature(const std::vector<unsigned char>& vchSig, const CPubKey& vchPubKey, const uint256& sighash) const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -161,7 +160,7 @@ public:
|
|||||||
const std::vector<unsigned char>& ffillBin,
|
const std::vector<unsigned char>& ffillBin,
|
||||||
const CScript& scriptCode,
|
const CScript& scriptCode,
|
||||||
uint32_t consensusBranchId) const;
|
uint32_t consensusBranchId) const;
|
||||||
virtual VerifyEval GetCCEval() const;
|
virtual int CheckEvalCondition(const CC *cond) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
class MutableTransactionSignatureChecker : public TransactionSignatureChecker
|
class MutableTransactionSignatureChecker : public TransactionSignatureChecker
|
||||||
|
|||||||
@@ -100,14 +100,7 @@ bool ServerTransactionSignatureChecker::VerifySignature(const std::vector<unsign
|
|||||||
* code without pulling the whole bitcoin server code into bitcoin common was
|
* code without pulling the whole bitcoin server code into bitcoin common was
|
||||||
* using this class. Thus it has been renamed to ServerTransactionSignatureChecker.
|
* using this class. Thus it has been renamed to ServerTransactionSignatureChecker.
|
||||||
*/
|
*/
|
||||||
VerifyEval ServerTransactionSignatureChecker::GetCCEval() const
|
int ServerTransactionSignatureChecker::CheckEvalCondition(const CC *cond) const
|
||||||
{
|
{
|
||||||
return [] (CC *cond, void *checker) {
|
return RunCCEval(cond, *txTo, nIn);
|
||||||
return ((ServerTransactionSignatureChecker*)checker)->CheckEvalCondition(cond);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
int ServerTransactionSignatureChecker::CheckEvalCondition(CC *cond) const
|
|
||||||
{
|
|
||||||
return EvalConditionValidity(cond, txTo, nIn);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,15 +16,12 @@ class ServerTransactionSignatureChecker : public TransactionSignatureChecker
|
|||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
bool store;
|
bool store;
|
||||||
const CTransaction* txTo;
|
|
||||||
unsigned int nIn;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ServerTransactionSignatureChecker(const CTransaction* txToIn, unsigned int nInIn, const CAmount& amount, bool storeIn, PrecomputedTransactionData& txdataIn) : TransactionSignatureChecker(txToIn, nInIn, amount, txdataIn), store(storeIn) {}
|
ServerTransactionSignatureChecker(const CTransaction* txToIn, unsigned int nIn, const CAmount& amount, bool storeIn, PrecomputedTransactionData& txdataIn) : TransactionSignatureChecker(txToIn, nIn, amount, txdataIn), store(storeIn) {}
|
||||||
|
|
||||||
bool VerifySignature(const std::vector<unsigned char>& vchSig, const CPubKey& vchPubKey, const uint256& sighash) const;
|
bool VerifySignature(const std::vector<unsigned char>& vchSig, const CPubKey& vchPubKey, const uint256& sighash) const;
|
||||||
int CheckEvalCondition(CC *cond) const;
|
int CheckEvalCondition(const CC *cond) const;
|
||||||
VerifyEval GetCCEval() const;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // BITCOIN_SCRIPT_SERVERCHECKER_H
|
#endif // BITCOIN_SCRIPT_SERVERCHECKER_H
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
#include "key.h"
|
#include "key.h"
|
||||||
|
#include "chainparams.h"
|
||||||
#include "gtest/gtest.h"
|
#include "gtest/gtest.h"
|
||||||
#include "crypto/common.h"
|
#include "crypto/common.h"
|
||||||
|
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
assert(init_and_check_sodium() != -1);
|
assert(init_and_check_sodium() != -1);
|
||||||
ECC_Start();
|
ECC_Start();
|
||||||
|
SelectParams(CBaseChainParams::REGTEST);
|
||||||
|
|
||||||
testing::InitGoogleTest(&argc, argv);
|
testing::InitGoogleTest(&argc, argv);
|
||||||
return RUN_ALL_TESTS();
|
return RUN_ALL_TESTS();
|
||||||
}
|
}
|
||||||
|
|||||||
596
src/test-komodo/test_bet.cpp
Normal file
596
src/test-komodo/test_bet.cpp
Normal file
@@ -0,0 +1,596 @@
|
|||||||
|
#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());
|
||||||
|
}
|
||||||
@@ -8,60 +8,35 @@
|
|||||||
#include "script/interpreter.h"
|
#include "script/interpreter.h"
|
||||||
#include "script/serverchecker.h"
|
#include "script/serverchecker.h"
|
||||||
|
|
||||||
|
#include "testutils.h"
|
||||||
|
|
||||||
#define VCH(a,b) std::vector<unsigned char>(a, a + b)
|
|
||||||
|
CKey notaryKey;
|
||||||
|
|
||||||
std::string pubkey = "0205a8ad0c1dbc515f149af377981aab58b836af008d4d7ab21bd76faf80550b47";
|
std::string pubkey = "0205a8ad0c1dbc515f149af377981aab58b836af008d4d7ab21bd76faf80550b47";
|
||||||
std::string secret = "UxFWWxsf1d7w7K5TvAWSkeX4H95XQKwdwGv49DXwWUTzPTTjHBbU";
|
std::string secret = "UxFWWxsf1d7w7K5TvAWSkeX4H95XQKwdwGv49DXwWUTzPTTjHBbU";
|
||||||
CKey notaryKey;
|
|
||||||
|
|
||||||
|
|
||||||
char ccjsonerr[1000] = "\0";
|
|
||||||
#define CCFromJson(o,s) \
|
|
||||||
o = cc_conditionFromJSONString(s, ccjsonerr); \
|
|
||||||
if (!o) FAIL() << "bad json: " << ccjsonerr;
|
|
||||||
|
|
||||||
|
|
||||||
CScript CCPubKey(const CC *cond) {
|
|
||||||
unsigned char buf[1000];
|
|
||||||
size_t len = cc_conditionBinary(cond, buf);
|
|
||||||
return CScript() << VCH(buf, len) << OP_CHECKCRYPTOCONDITION;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
CScript CCSig(const CC *cond) {
|
|
||||||
unsigned char buf[1000];
|
|
||||||
size_t len = cc_fulfillmentBinary(cond, buf, 1000);
|
|
||||||
auto ffill = VCH(buf, len);
|
|
||||||
ffill.push_back(SIGHASH_ALL);
|
|
||||||
return CScript() << ffill;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void CCSign(CMutableTransaction &tx, CC *cond) {
|
|
||||||
tx.vin.resize(1);
|
|
||||||
PrecomputedTransactionData txdata(tx);
|
|
||||||
uint256 sighash = SignatureHash(CCPubKey(cond), tx, 0, SIGHASH_ALL, 0, 0, &txdata);
|
|
||||||
|
|
||||||
int out = cc_signTreeSecp256k1Msg32(cond, notaryKey.begin(), sighash.begin());
|
|
||||||
tx.vin[0].scriptSig = CCSig(cond);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class CCTest : public ::testing::Test {
|
class CCTest : public ::testing::Test {
|
||||||
|
public:
|
||||||
|
void CCSign(CMutableTransaction &tx, CC *cond) {
|
||||||
|
tx.vin.resize(1);
|
||||||
|
PrecomputedTransactionData txdata(tx);
|
||||||
|
uint256 sighash = SignatureHash(CCPubKey(cond), tx, 0, SIGHASH_ALL, 0, 0, &txdata);
|
||||||
|
|
||||||
|
int out = cc_signTreeSecp256k1Msg32(cond, notaryKey.begin(), sighash.begin());
|
||||||
|
tx.vin[0].scriptSig = CCSig(cond);
|
||||||
|
}
|
||||||
protected:
|
protected:
|
||||||
static void SetUpTestCase() {
|
virtual void SetUp() {
|
||||||
SelectParams(CBaseChainParams::REGTEST);
|
// enable CC
|
||||||
|
ASSETCHAINS_CC = 1;
|
||||||
// Notary key
|
// Notary key
|
||||||
CBitcoinSecret vchSecret;
|
CBitcoinSecret vchSecret;
|
||||||
// this returns false due to network prefix mismatch but works anyway
|
// this returns false due to network prefix mismatch but works anyway
|
||||||
vchSecret.SetString(secret);
|
vchSecret.SetString(secret);
|
||||||
notaryKey = vchSecret.GetKey();
|
notaryKey = vchSecret.GetKey();
|
||||||
}
|
}
|
||||||
virtual void SetUp() {
|
|
||||||
// enable CC
|
|
||||||
ASSETCHAINS_CC = 1;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
15
src/test-komodo/testutils.h
Normal file
15
src/test-komodo/testutils.h
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#ifndef TESTUTILS_H
|
||||||
|
#define TESTUTILS_H
|
||||||
|
|
||||||
|
#include "komodo_cc.h"
|
||||||
|
|
||||||
|
|
||||||
|
#define VCH(a,b) std::vector<unsigned char>(a, a + b)
|
||||||
|
|
||||||
|
static char ccjsonerr[1000] = "\0";
|
||||||
|
#define CCFromJson(o,s) \
|
||||||
|
o = cc_conditionFromJSONString(s, ccjsonerr); \
|
||||||
|
if (!o) FAIL() << "bad json: " << ccjsonerr;
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* TESTUTILS_H */
|
||||||
Reference in New Issue
Block a user