From 0cb91a8d209db6d64578df7bed760994523c18c0 Mon Sep 17 00:00:00 2001 From: Scott Sadler Date: Mon, 16 Apr 2018 22:57:35 -0500 Subject: [PATCH] wip --- migratecoin.md | 61 ++++++ src/Makefile.am | 5 +- src/Makefile.ktest.include | 2 + src/cc/betprotocol.cpp | 146 +++++++++++++++ src/cc/betprotocol.h | 12 +- src/cc/disputepayout.cpp | 84 --------- src/cc/eval.cpp | 37 ++++ src/cc/eval.h | 43 ++++- src/cc/importcoin.cpp | 147 +++++++++++++++ src/cc/importcoin.h | 33 ++++ src/cc/importpayout.cpp | 76 -------- src/chainparams.cpp | 16 +- src/coins.cpp | 11 +- src/coins.h | 5 + src/main.cpp | 91 +++++---- src/miner.cpp | 86 +++++---- src/primitives/transaction.h | 10 + src/rpcblockchain.cpp | 4 +- src/script/interpreter.cpp | 5 +- src/script/interpreter.h | 1 - src/script/script.cpp | 13 ++ src/script/script.h | 1 + src/test-komodo/main.cpp | 7 + src/test-komodo/test_coinimport.cpp | 206 +++++++++++++++++++++ src/test-komodo/test_cryptoconditions.cpp | 10 - src/test-komodo/test_eval_bet.cpp | 6 +- src/test-komodo/test_eval_notarisation.cpp | 24 ++- src/test-komodo/testutils.cpp | 129 +++++++++++++ src/test-komodo/testutils.h | 14 +- src/txmempool.cpp | 6 +- 30 files changed, 1002 insertions(+), 289 deletions(-) create mode 100644 migratecoin.md delete mode 100644 src/cc/disputepayout.cpp create mode 100644 src/cc/importcoin.cpp create mode 100644 src/cc/importcoin.h delete mode 100644 src/cc/importpayout.cpp create mode 100644 src/test-komodo/test_coinimport.cpp create mode 100644 src/test-komodo/testutils.cpp diff --git a/migratecoin.md b/migratecoin.md new file mode 100644 index 000000000..7859bdff2 --- /dev/null +++ b/migratecoin.md @@ -0,0 +1,61 @@ +# MigrateCoin protocol + + + +## ExportCoins tx: + + + +``` + +vin: + + [ any ] + +vout: + + - amount: {burnAmount} + + script: OP_RETURN "send to ledger {id} {voutsHash}" + +``` + + + +* ExportCoin is a standard tx which burns coins in an OP_RETURN + + + +## ImportCoins tx: + + + +``` + +vin: + + - txid: 0000000000000000000000000000000000000000000000000000000000000000 + + idx: 0 + + script: CC_EVAL(EVAL_IMPORTCOINS, {momoProof},{exportCoin}) OP_CHECKCRYPTOCONDITION_UNILATERAL + +vout: + + - [ vouts matching voutsHash in exportCoin ] + +``` + + + +* ImportCoin transaction has no signature + +* ImportCoin is non malleable + +* ImportCoin satisfies tx.IsCoinBase() + +* ImportCoin uses a new opcode which allows a one sided check (no scriptPubKey) + +* ImportCoin must contain CC opcode EVAL_IMPORTCOINS + +* ImportCoin fees are equal to the difference between burnAmount in exportCoins and the sum of outputs. diff --git a/src/Makefile.am b/src/Makefile.am index a1232968d..231386c96 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -256,8 +256,7 @@ libbitcoin_server_a_SOURCES = \ asyncrpcqueue.cpp \ bloom.cpp \ cc/eval.cpp \ - cc/importpayout.cpp \ - cc/disputepayout.cpp \ + cc/importcoin.cpp \ cc/betprotocol.cpp \ chain.cpp \ checkpoints.cpp \ @@ -605,7 +604,7 @@ endif libzcashconsensus_la_LDFLAGS = $(AM_LDFLAGS) -no-undefined $(RELDFLAGS) libzcashconsensus_la_LIBADD = $(LIBSECP256K1) -libzcashconsensus_la_CPPFLAGS = $(AM_CPPFLAGS) -I$(builddir)/obj -I$(srcdir)/secp256k1/include -DBUILD_BITCOIN_INTERNAL +libzcashconsensus_la_CPPFLAGS = $(AM_CPPFLAGS) -I$(builddir)/obj -I$(srcdir)/secp256k1/include -I$(srcdir)/cryptoconditions/include -DBUILD_BITCOIN_INTERNAL libzcashconsensus_la_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) endif diff --git a/src/Makefile.ktest.include b/src/Makefile.ktest.include index 06aab54dc..a6d2d1829 100644 --- a/src/Makefile.ktest.include +++ b/src/Makefile.ktest.include @@ -5,7 +5,9 @@ bin_PROGRAMS += komodo-test # tool for generating our public parameters komodo_test_SOURCES = \ test-komodo/main.cpp \ + test-komodo/testutils.cpp \ test-komodo/test_cryptoconditions.cpp \ + test-komodo/test_coinimport.cpp \ test-komodo/test_eval_bet.cpp \ test-komodo/test_eval_notarisation.cpp diff --git a/src/cc/betprotocol.cpp b/src/cc/betprotocol.cpp index 53b79176c..c3be8be8f 100644 --- a/src/cc/betprotocol.cpp +++ b/src/cc/betprotocol.cpp @@ -1,5 +1,8 @@ #include +#include "hash.h" +#include "main.h" +#include "chain.h" #include "streams.h" #include "script/cc.h" #include "cc/eval.h" @@ -137,3 +140,146 @@ bool GetOpReturnHash(CScript script, uint256 &hash) hash = uint256(vHash); return true; } + + +/* + * Crypto-Condition EVAL method that verifies a payout against a transaction + * notarised on another chain. + * + * IN: params - condition params + * IN: importTx - Payout transaction on value chain (KMD) + * IN: nIn - index of input of stake + * + * importTx: Spends stakeTx with payouts from asset chain + * + * in 0: Spends Stake TX and contains ImportPayout CC + * out 0: OP_RETURN MomProof, disputeTx + * out 1-: arbitrary payouts + * + * disputeTx: Spends sessionTx.0 (opener on asset chain) + * + * in 0: spends sessionTx.0 + * in 1-: anything + * out 0: OP_RETURN hash of payouts + * out 1-: anything + */ +bool Eval::ImportPayout(const std::vector params, const CTransaction &importTx, unsigned int nIn) +{ + if (importTx.vout.size() == 0) return Invalid("no-vouts"); + + // load data from vout[0] + MoMProof proof; + CTransaction disputeTx; + { + std::vector vopret; + GetOpReturnData(importTx.vout[0].scriptPubKey, vopret); + if (!E_UNMARSHAL(vopret, ss >> proof; ss >> disputeTx)) + return Invalid("invalid-payload"); + } + + // Check disputeTx.0 shows correct payouts + { + uint256 givenPayoutsHash; + GetOpReturnHash(disputeTx.vout[0].scriptPubKey, givenPayoutsHash); + std::vector payouts(importTx.vout.begin() + 1, importTx.vout.end()); + if (givenPayoutsHash != SerializeHash(payouts)) + return Invalid("wrong-payouts"); + } + + // Check disputeTx spends sessionTx.0 + // condition ImportPayout params is session ID from other chain + { + uint256 sessionHash; + if (!E_UNMARSHAL(params, ss >> sessionHash)) + return Invalid("malformed-params"); + if (disputeTx.vin[0].prevout != COutPoint(sessionHash, 0)) + return Invalid("wrong-session"); + } + + // Check disputeTx solves momproof from vout[0] + { + NotarisationData data; + if (!GetNotarisationData(proof.notarisationHash, data)) + return Invalid("coudnt-load-mom"); + + if (data.MoM != proof.branch.Exec(disputeTx.GetHash())) + return Invalid("mom-check-fail"); + } + + return Valid(); +} + + +/* + * Crypto-Condition EVAL method that resolves a dispute of a session + * + * IN: vm - AppVM virtual machine to verify states + * IN: params - condition params + * IN: disputeTx - transaction attempting to resolve dispute + * IN: nIn - index of input of dispute tx + * + * disputeTx: attempt to resolve a dispute + * + * in 0: Spends Session TX first output, reveals DisputeHeader + * out 0: OP_RETURN hash of payouts + */ +bool Eval::DisputePayout(AppVM &vm, std::vector params, const CTransaction &disputeTx, unsigned int nIn) +{ + if (disputeTx.vout.size() == 0) return Invalid("no-vouts"); + + // get payouts hash + uint256 payoutHash; + if (!GetOpReturnHash(disputeTx.vout[0].scriptPubKey, payoutHash)) + return Invalid("invalid-payout-hash"); + + // load params + uint16_t waitBlocks; + std::vector vmParams; + if (!E_UNMARSHAL(params, ss >> VARINT(waitBlocks); ss >> vmParams)) + return Invalid("malformed-params"); + + // ensure that enough time has passed + { + CTransaction sessionTx; + CBlockIndex sessionBlock; + + // if unconformed its too soon + if (!GetTxConfirmed(disputeTx.vin[0].prevout.hash, sessionTx, sessionBlock)) + return Error("couldnt-get-parent"); + + if (GetCurrentHeight() < sessionBlock.nHeight + waitBlocks) + return Invalid("dispute-too-soon"); // Not yet + } + + // get spends + std::vector spends; + if (!GetSpendsConfirmed(disputeTx.vin[0].prevout.hash, spends)) + return Error("couldnt-get-spends"); + + // verify result from VM + int maxLength = -1; + uint256 bestPayout; + for (int i=1; i vmState; + if (spends[i].vout.size() == 0) continue; + if (!GetOpReturnData(spends[i].vout[0].scriptPubKey, vmState)) continue; + auto out = vm.evaluate(vmParams, vmState); + uint256 resultHash = SerializeHash(out.second); + if (out.first > maxLength) { + maxLength = out.first; + bestPayout = resultHash; + } + // The below means that if for any reason there is a draw, the first dispute wins + else if (out.first == maxLength) { + if (bestPayout != payoutHash) { + fprintf(stderr, "WARNING: VM has multiple solutions of same length\n"); + bestPayout = resultHash; + } + } + } + + if (maxLength == -1) return Invalid("no-evidence"); + + return bestPayout == payoutHash ? Valid() : Invalid("wrong-payout"); +} diff --git a/src/cc/betprotocol.h b/src/cc/betprotocol.h index b08783b85..fad33f7ed 100644 --- a/src/cc/betprotocol.h +++ b/src/cc/betprotocol.h @@ -3,7 +3,6 @@ #include "cc/eval.h" #include "pubkey.h" -#include "primitives/block.h" #include "primitives/transaction.h" #include "cryptoconditions/include/cryptoconditions.h" @@ -11,25 +10,18 @@ class MoMProof { public: - int nIndex; - std::vector branch; + MerkleBranch branch; uint256 notarisationHash; - - MoMProof() {} - MoMProof(int i, std::vector b, uint256 n) : notarisationHash(n), nIndex(i), branch(b) {} - uint256 Exec(uint256 hash) const { return CBlock::CheckMerkleBranch(hash, branch, nIndex); } - ADD_SERIALIZE_METHODS; - template inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) { - READWRITE(VARINT(nIndex)); READWRITE(branch); READWRITE(notarisationHash); } }; + class BetProtocol { protected: diff --git a/src/cc/disputepayout.cpp b/src/cc/disputepayout.cpp deleted file mode 100644 index 610342274..000000000 --- a/src/cc/disputepayout.cpp +++ /dev/null @@ -1,84 +0,0 @@ -#include - -#include "hash.h" -#include "chain.h" -#include "version.h" -#include "script/cc.h" -#include "cc/eval.h" -#include "cc/betprotocol.h" -#include "primitives/transaction.h" - - -/* - * Crypto-Condition EVAL method that resolves a dispute of a session - * - * IN: vm - AppVM virtual machine to verify states - * IN: params - condition params - * IN: disputeTx - transaction attempting to resolve dispute - * IN: nIn - index of input of dispute tx - * - * disputeTx: attempt to resolve a dispute - * - * in 0: Spends Session TX first output, reveals DisputeHeader - * out 0: OP_RETURN hash of payouts - */ -bool Eval::DisputePayout(AppVM &vm, std::vector params, const CTransaction &disputeTx, unsigned int nIn) -{ - if (disputeTx.vout.size() == 0) return Invalid("no-vouts"); - - // get payouts hash - uint256 payoutHash; - if (!GetOpReturnHash(disputeTx.vout[0].scriptPubKey, payoutHash)) - return Invalid("invalid-payout-hash"); - - // load params - uint16_t waitBlocks; - std::vector vmParams; - if (!E_UNMARSHAL(params, ss >> VARINT(waitBlocks); ss >> vmParams)) - return Invalid("malformed-params"); - - // ensure that enough time has passed - { - CTransaction sessionTx; - CBlockIndex sessionBlock; - - // if unconformed its too soon - if (!GetTxConfirmed(disputeTx.vin[0].prevout.hash, sessionTx, sessionBlock)) - return Error("couldnt-get-parent"); - - if (GetCurrentHeight() < sessionBlock.nHeight + waitBlocks) - return Invalid("dispute-too-soon"); // Not yet - } - - // get spends - std::vector spends; - if (!GetSpendsConfirmed(disputeTx.vin[0].prevout.hash, spends)) - return Error("couldnt-get-spends"); - - // verify result from VM - int maxLength = -1; - uint256 bestPayout; - for (int i=1; i vmState; - if (spends[i].vout.size() == 0) continue; - if (!GetOpReturnData(spends[i].vout[0].scriptPubKey, vmState)) continue; - auto out = vm.evaluate(vmParams, vmState); - uint256 resultHash = SerializeHash(out.second); - if (out.first > maxLength) { - maxLength = out.first; - bestPayout = resultHash; - } - // The below means that if for any reason there is a draw, the first dispute wins - else if (out.first == maxLength) { - if (bestPayout != payoutHash) { - fprintf(stderr, "WARNING: VM has multiple solutions of same length\n"); - bestPayout = resultHash; - } - } - } - - if (maxLength == -1) return Invalid("no-evidence"); - - return bestPayout == payoutHash ? Valid() : Invalid("wrong-payout"); -} diff --git a/src/cc/eval.cpp b/src/cc/eval.cpp index 3c53f9866..e98b0dc6b 100644 --- a/src/cc/eval.cpp +++ b/src/cc/eval.cpp @@ -49,6 +49,10 @@ bool Eval::Dispatch(const CC *cond, const CTransaction &txTo, unsigned int nIn) return ImportPayout(vparams, txTo, nIn); } + if (ecode == EVAL_IMPORTCOIN) { + return ImportCoin(vparams, txTo, nIn); + } + return Invalid("invalid-code"); } @@ -143,6 +147,12 @@ bool Eval::CheckNotaryInputs(const CTransaction &tx, uint32_t height, uint32_t t } +uint32_t Eval::GetCurrentLedgerID() const +{ + return -1; // TODO +} + + /* * Get MoM from a notarisation tx hash */ @@ -157,6 +167,11 @@ bool Eval::GetNotarisationData(const uint256 notaryHash, NotarisationData &data) return true; } +bool Eval::GetNotarisationData(int notarisationHeight, NotarisationData &data, bool verifyCanonical) const +{ + return false; +} + /* * Notarisation data, ie, OP_RETURN payload in notarisation transactions @@ -202,4 +217,26 @@ std::string EvalToStr(EvalCode c) char s[10]; sprintf(s, "0x%x", c); return std::string(s); + +} + + +uint256 SafeCheckMerkleBranch(uint256 hash, const std::vector& vMerkleBranch, int nIndex) +{ + if (nIndex == -1) + return uint256(); + for (auto it(vMerkleBranch.begin()); it != vMerkleBranch.end(); ++it) + { + if (nIndex & 1) { + if (*it == hash) { + // non canonical. hash may be equal to node but never on the right. + return uint256(); + } + hash = Hash(BEGIN(*it), END(*it), BEGIN(hash), END(hash)); + } + else + hash = Hash(BEGIN(hash), END(hash), BEGIN(*it), END(*it)); + nIndex >>= 1; + } + return hash; } diff --git a/src/cc/eval.h b/src/cc/eval.h index f998c9f3d..c65e5f863 100644 --- a/src/cc/eval.h +++ b/src/cc/eval.h @@ -20,8 +20,10 @@ * a possible code is EVAL_BITCOIN_SCRIPT, where the entire binary * after the code is interpreted as a bitcoin script. */ -#define FOREACH_EVAL(EVAL) \ - EVAL(EVAL_IMPORTPAYOUT, 0xe1) +#define FOREACH_EVAL(EVAL) \ + EVAL(EVAL_IMPORTPAYOUT, 0xe1) \ + EVAL(EVAL_IMPORTCOIN, 0xe2) + typedef uint8_t EvalCode; @@ -54,6 +56,11 @@ public: */ bool ImportPayout(std::vector params, const CTransaction &importTx, unsigned int nIn); + /* + * Import coin from another chain with same symbol + */ + bool ImportCoin(std::vector params, const CTransaction &importTx, unsigned int nIn); + /* * IO functions */ @@ -64,7 +71,10 @@ public: virtual bool GetBlock(uint256 hash, CBlockIndex& blockIdx) 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 GetNotarisationData(int notarisationHeight, NotarisationData &data, + bool verifyCanonical) const; virtual bool CheckNotaryInputs(const CTransaction &tx, uint32_t height, uint32_t timestamp) const; + virtual uint32_t GetCurrentLedgerID() const; }; @@ -87,7 +97,6 @@ public: evaluate(std::vector header, std::vector body) = 0; }; - /* * Data from notarisation OP_RETURN */ @@ -99,6 +108,8 @@ public: char symbol[64]; uint256 MoM; uint32_t MoMDepth; + uint256 MoMoM; + uint32_t MoMoMDepth; bool Parse(CScript scriptPubKey); }; @@ -140,4 +151,30 @@ bool DeserializeF(const std::vector vIn, T f) } +/* + * Merkle stuff + */ +uint256 SafeCheckMerkleBranch(uint256 hash, const std::vector& vMerkleBranch, int nIndex); + + +class MerkleBranch +{ +public: + int nIndex; + std::vector branch; + + MerkleBranch() {} + MerkleBranch(int i, std::vector b) : nIndex(i), branch(b) {} + uint256 Exec(uint256 hash) const { return SafeCheckMerkleBranch(hash, branch, nIndex); } + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) { + READWRITE(VARINT(nIndex)); + READWRITE(branch); + } +}; + + #endif /* CC_EVAL_H */ diff --git a/src/cc/importcoin.cpp b/src/cc/importcoin.cpp new file mode 100644 index 000000000..4c9331e73 --- /dev/null +++ b/src/cc/importcoin.cpp @@ -0,0 +1,147 @@ +#include "cc/importcoin.h" +#include "hash.h" +#include "script/cc.h" +#include "primitives/transaction.h" + + +/* + * Generate ImportCoin transaction. + * + * Contains an empty OP_RETURN as first output; this is critical for preventing a double + * import. If it doesn't contain this it's invalid. The empty OP_RETURN will hang around + * in the UTXO set and the transaction will be detected as a duplicate. + */ +CTransaction MakeImportCoinTransaction(const MomoProof proof, const CTransaction burnTx, const std::vector payouts) +{ + std::vector payload = + E_MARSHAL(ss << EVAL_IMPORTCOIN; ss << proof; ss << burnTx); + CMutableTransaction mtx; + mtx.vin.resize(1); + mtx.vin[0].scriptSig << payload; + mtx.vin[0].prevout.n = 10e8; + mtx.vout = payouts; + return CTransaction(mtx); +} + +CTxOut MakeBurnOutput(CAmount value, int targetChain, const std::vector payouts) +{ + std::vector opret = E_MARSHAL(ss << VARINT(targetChain); ss << SerializeHash(payouts)); + return CTxOut(value, CScript() << OP_RETURN << opret); +} + + +/* + * CC Eval method for import coin. + * + * This method has to control *every* parameter of the ImportCoin transaction, so that the legal + * importTx for a valid burnTx is 1:1. There can be no two legal importTx transactions for a burnTx + * on another chain. + */ +bool Eval::ImportCoin(const std::vector params, const CTransaction &importTx, unsigned int nIn) +{ + if (importTx.vout.size() == 0) return Invalid("no-vouts"); + + // params + MomoProof proof; + CTransaction burnTx; + if (!E_UNMARSHAL(params, ss >> proof; ss >> burnTx)) + return Invalid("invalid-params"); + + // Control all aspects of this transaction + // It must not be at all malleable + if (MakeImportCoinTransaction(proof, burnTx, importTx.vout).GetHash() != importTx.GetHash()) + return Invalid("non-canonical"); + + // burn params + uint32_t chain; // todo + uint256 payoutsHash; + std::vector burnOpret; + if (burnTx.vout.size() == 0) return Invalid("invalid-burn-outputs"); + GetOpReturnData(burnTx.vout[0].scriptPubKey, burnOpret); + if (!E_UNMARSHAL(burnOpret, ss >> VARINT(chain); ss >> payoutsHash)) + return Invalid("invalid-burn-params"); + + // check chain + if (chain != GetCurrentLedgerID()) + return Invalid("importcoin-wrong-chain"); + + // check burn amount + { + uint64_t burnAmount = burnTx.vout[0].nValue; + if (burnAmount == 0) + return Invalid("invalid-burn-amount"); + uint64_t totalOut = 0; + for (int i=0; i burnAmount) + return Invalid("payout-too-high"); + } + + // Check burntx shows correct outputs hash + if (payoutsHash != SerializeHash(importTx.vout)) + return Invalid("wrong-payouts"); + + // Check proof confirms existance of burnTx + { + NotarisationData data; + if (!GetNotarisationData(proof.notarisationHeight, data, true)) + return Invalid("coudnt-load-momom"); + + if (data.MoMoM != proof.branch.Exec(burnTx.GetHash())) + return Invalid("momom-check-fail"); + } + + return Valid(); +} + + +/* + * Required by main + */ +CAmount GetCoinImportValue(const CTransaction &tx) +{ + CScript scriptSig = tx.vin[0].scriptSig; + auto pc = scriptSig.begin(); + opcodetype opcode; + std::vector evalScript; + if (!scriptSig.GetOp(pc, opcode, evalScript)) + return false; + if (pc != scriptSig.end()) + return false; + EvalCode code; + MomoProof proof; + CTransaction burnTx; + if (!E_UNMARSHAL(evalScript, ss >> proof; ss >> burnTx)) + return 0; + return burnTx.vout.size() ? burnTx.vout[0].nValue : 0; +} + + +/* + * CoinImport is different enough from normal script execution that it's not worth + * making all the mods neccesary in the interpreter to do the dispatch correctly. + */ +bool VerifyCoinImport(const CScript& scriptSig, TransactionSignatureChecker& checker, CValidationState &state) +{ + auto pc = scriptSig.begin(); + opcodetype opcode; + std::vector evalScript; + + auto f = [&] () { + if (!scriptSig.GetOp(pc, opcode, evalScript)) + return false; + if (pc != scriptSig.end()) + return false; + if (evalScript.size() == 0) + return false; + if (evalScript.begin()[0] != EVAL_IMPORTCOIN) + return false; + // Ok, all looks good so far... + CC *cond = CCNewEval(evalScript); + bool out = checker.CheckEvalCondition(cond); + cc_free(cond); + return out; + }; + + return f() ? true : state.Invalid(false, 0, "invalid-coin-import"); +} diff --git a/src/cc/importcoin.h b/src/cc/importcoin.h new file mode 100644 index 000000000..8773b09a7 --- /dev/null +++ b/src/cc/importcoin.h @@ -0,0 +1,33 @@ +#ifndef CC_IMPORTCOIN_H +#define CC_IMPORTCOIN_H + +#include "cc/eval.h" +#include "primitives/transaction.h" +#include "script/interpreter.h" +#include + + +class MomoProof +{ +public: + MerkleBranch branch; + int notarisationHeight; + ADD_SERIALIZE_METHODS; + template + inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) { + READWRITE(branch); + READWRITE(notarisationHeight); + } +}; + +CAmount GetCoinImportValue(const CTransaction &tx); + +CTransaction MakeImportCoinTransaction(const MomoProof proof, + const CTransaction burnTx, const std::vector payouts); + +CTxOut MakeBurnOutput(CAmount value, int targetChain, const std::vector payouts); + +bool VerifyCoinImport(const CScript& scriptSig, + TransactionSignatureChecker& checker, CValidationState &state); + +#endif /* CC_IMPORTCOIN_H */ diff --git a/src/cc/importpayout.cpp b/src/cc/importpayout.cpp deleted file mode 100644 index 1363eb924..000000000 --- a/src/cc/importpayout.cpp +++ /dev/null @@ -1,76 +0,0 @@ -#include - -#include "main.h" -#include "chain.h" -#include "streams.h" -#include "cc/eval.h" -#include "cc/betprotocol.h" -#include "primitives/transaction.h" - - -/* - * Crypto-Condition EVAL method that verifies a payout against a transaction - * notarised on another chain. - * - * IN: params - condition params - * IN: importTx - Payout transaction on value chain (KMD) - * IN: nIn - index of input of stake - * - * importTx: Spends stakeTx with payouts from asset chain - * - * in 0: Spends Stake TX and contains ImportPayout CC - * out 0: OP_RETURN MomProof, disputeTx - * out 1-: arbitrary payouts - * - * disputeTx: Spends sessionTx.0 (opener on asset chain) - * - * in 0: spends sessionTx.0 - * in 1-: anything - * out 0: OP_RETURN hash of payouts - * out 1-: anything - */ -bool Eval::ImportPayout(const std::vector params, const CTransaction &importTx, unsigned int nIn) -{ - if (importTx.vout.size() == 0) return Invalid("no-vouts"); - - // load data from vout[0] - MoMProof proof; - CTransaction disputeTx; - { - std::vector vopret; - GetOpReturnData(importTx.vout[0].scriptPubKey, vopret); - if (!E_UNMARSHAL(vopret, ss >> proof; ss >> disputeTx)) - return Invalid("invalid-payload"); - } - - // Check disputeTx.0 shows correct payouts - { - uint256 givenPayoutsHash; - GetOpReturnHash(disputeTx.vout[0].scriptPubKey, givenPayoutsHash); - std::vector payouts(importTx.vout.begin() + 1, importTx.vout.end()); - if (givenPayoutsHash != SerializeHash(payouts)) - return Invalid("wrong-payouts"); - } - - // Check disputeTx spends sessionTx.0 - // condition ImportPayout params is session ID from other chain - { - uint256 sessionHash; - if (!E_UNMARSHAL(params, ss >> sessionHash)) - return Invalid("malformed-params"); - if (disputeTx.vin[0].prevout != COutPoint(sessionHash, 0)) - return Invalid("wrong-session"); - } - - // Check disputeTx solves momproof from vout[0] - { - NotarisationData data; - if (!GetNotarisationData(proof.notarisationHash, data)) - return Invalid("coudnt-load-mom"); - - if (data.MoM != proof.Exec(disputeTx.GetHash())) - return Invalid("mom-check-fail"); - } - - return Valid(); -} diff --git a/src/chainparams.cpp b/src/chainparams.cpp index df258d782..cd8c2b451 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -532,11 +532,19 @@ public: BOOST_STATIC_ASSERT(equihash_parameters_acceptable(N, K)); nEquihashN = N; nEquihashK = K; - genesis.nTime = 1296688602; - genesis.nBits = KOMODO_MINDIFF_NBITS; - genesis.nNonce = uint256S("0x0000000000000000000000000000000000000000000000000000000000000021"); - genesis.nSolution = ParseHex("0f2a976db4c4263da10fd5d38eb1790469cf19bdb4bf93450e09a72fdff17a3454326399"); + + + genesis = CreateGenesisBlock( + 1296688602, + uint256S("0x0000000000000000000000000000000000000000000000000000000000000009"), + ParseHex("01936b7db1eb4ac39f151b8704642d0a8bda13ec547d54cd5e43ba142fc6d8877cab07b3"), + 0x200f0f0f, 4, 0); + consensus.hashGenesisBlock = genesis.GetHash(); + + assert(consensus.hashGenesisBlock == uint256S("0x029f11d80ef9765602235e1bc9727e3eb6ba20839319f761fee920d63401e327")); + assert(genesis.hashMerkleRoot == uint256S("0xc4eaa58879081de3c24a7b117ed2b28300e7ec4c4c1dff1d3f1268b7857a4ddb")); + nDefaultPort = 17779; nPruneAfterHeight = 1000; diff --git a/src/coins.cpp b/src/coins.cpp index bb40af9cc..b8f5ffe2c 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -9,6 +9,7 @@ #include "version.h" #include "policy/fees.h" #include "komodo_defs.h" +#include "cc/importcoin.h" #include @@ -392,11 +393,13 @@ extern char ASSETCHAINS_SYMBOL[KOMODO_ASSETCHAIN_MAXLEN]; CAmount CCoinsViewCache::GetValueIn(int32_t nHeight,int64_t *interestp,const CTransaction& tx,uint32_t tiptime) const { + CAmount value,nResult = 0; if ( interestp != 0 ) *interestp = 0; + if ( tx.IsCoinImport() ) + return GetCoinImportValue(tx); if ( tx.IsCoinBase() != 0 ) return 0; - CAmount value,nResult = 0; for (unsigned int i = 0; i < tx.vin.size(); i++) { value = GetOutputFor(tx.vin[i]).nValue; @@ -421,6 +424,7 @@ CAmount CCoinsViewCache::GetValueIn(int32_t nHeight,int64_t *interestp,const CTr return nResult; } + bool CCoinsViewCache::HaveJoinSplitRequirements(const CTransaction& tx) const { boost::unordered_map intermediates; @@ -457,7 +461,7 @@ bool CCoinsViewCache::HaveJoinSplitRequirements(const CTransaction& tx) const bool CCoinsViewCache::HaveInputs(const CTransaction& tx) const { - if (!tx.IsCoinBase()) { + if (!tx.IsMint()) { for (unsigned int i = 0; i < tx.vin.size(); i++) { const COutPoint &prevout = tx.vin[i].prevout; const CCoins* coins = AccessCoins(prevout.hash); @@ -482,6 +486,9 @@ double CCoinsViewCache::GetPriority(const CTransaction &tx, int nHeight) const if (tx.vjoinsplit.size() > 0) { return MAX_PRIORITY; } + if (tx.IsCoinImport()) { + return MAX_PRIORITY; + } double dResult = 0.0; BOOST_FOREACH(const CTxIn& txin, tx.vin) diff --git a/src/coins.h b/src/coins.h index fcc32caae..9cee20712 100644 --- a/src/coins.h +++ b/src/coins.h @@ -97,6 +97,11 @@ public: nVersion = tx.nVersion; //nLockTime = tx.nLockTime; ClearUnspendable(); + + // This must live forever + if (tx.IsCoinImport()) { + vout.insert(vout.begin(), CTxOut(0, CScript() << OP_NOP << OP_RETURN)); + } } //! construct a CCoins from a CTransaction, at a given height diff --git a/src/main.cpp b/src/main.cpp index ab27a998f..104540419 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,6 +10,7 @@ #include "addrman.h" #include "alert.h" #include "arith_uint256.h" +#include "cc/importcoin.h" #include "chainparams.h" #include "checkpoints.h" #include "checkqueue.h" @@ -21,6 +22,7 @@ #include "metrics.h" #include "net.h" #include "pow.h" +#include "script/interpreter.h" #include "txdb.h" #include "txmempool.h" #include "ui_interface.h" @@ -817,7 +819,10 @@ bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs, { if (tx.IsCoinBase()) return true; // Coinbases don't use vin normally - + + if (tx.IsCoinImport()) + return tx.vin[0].scriptSig.IsCoinImport(); + for (unsigned int i = 0; i < tx.vin.size(); i++) { const CTxOut& prev = mapInputs.GetOutputFor(tx.vin[i]); @@ -888,7 +893,7 @@ unsigned int GetLegacySigOpCount(const CTransaction& tx) unsigned int GetP2SHSigOpCount(const CTransaction& tx, const CCoinsViewCache& inputs) { - if (tx.IsCoinBase()) + if (tx.IsCoinBase() || tx.IsCoinImport()) return 0; unsigned int nSigOps = 0; @@ -945,8 +950,8 @@ bool ContextualCheckTransaction(const CTransaction& tx, CValidationState &state, return state.DoS(dosLevel, error("ContextualCheckTransaction(): transaction is expired"), REJECT_INVALID, "tx-overwinter-expired"); } } - - if (!(tx.IsCoinBase() || tx.vjoinsplit.empty())) { + + if (!(tx.IsMint() || tx.vjoinsplit.empty())) { auto consensusBranchId = CurrentEpochBranchId(nHeight, Params().GetConsensus()); // Empty output script. CScript scriptCode; @@ -1001,7 +1006,7 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state, if (!CheckTransactionWithoutProofVerification(tx, state)) { return false; } else { - // Ensure that zk-SNARKs verify + // Ensure that zk-SNARKs v|| y BOOST_FOREACH(const JSDescription &joinsplit, tx.vjoinsplit) { if (!joinsplit.Verify(*pzcashParams, verifier, tx.joinSplitPubKey)) { return state.DoS(100, error("CheckTransaction(): joinsplit does not verify"), @@ -1059,6 +1064,7 @@ bool CheckTransactionWithoutProofVerification(const CTransaction& tx, CValidatio // Transactions can contain empty `vin` and `vout` so long as // `vjoinsplit` is non-empty. + // Migrations may also have empty `vin` if (tx.vin.empty() && tx.vjoinsplit.empty()) return state.DoS(10, error("CheckTransaction(): vin empty"), REJECT_INVALID, "bad-txns-vin-empty"); @@ -1167,7 +1173,7 @@ bool CheckTransactionWithoutProofVerification(const CTransaction& tx, CValidatio } } - if (tx.IsCoinBase()) + if (tx.IsMint()) { // There should be no joinsplits in a coinbase transaction if (tx.vjoinsplit.size() > 0) @@ -1284,7 +1290,7 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa if (pool.exists(hash)) { fprintf(stderr,"already in mempool\n"); - return false; + return state.Invalid(false, REJECT_DUPLICATE, "already in mempool"); } // Check for conflicts with in-memory transactions @@ -1326,31 +1332,36 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa view.SetBackend(viewMemPool); // do we already have it? + // This is what stops coin import transactions from being double spent; they + // grow the UTXO set size slightly if (view.HaveCoins(hash)) { fprintf(stderr,"view.HaveCoins(hash) error\n"); - return false; + return state.Invalid(false, REJECT_DUPLICATE, "already have coins"); } - // do all inputs exist? - // Note that this does not check for the presence of actual outputs (see the next check for that), - // and only helps with filling in pfMissingInputs (to determine missing vs spent). - BOOST_FOREACH(const CTxIn txin, tx.vin) + if (!tx.IsCoinImport()) { - if (!view.HaveCoins(txin.prevout.hash)) + // do all inputs exist? + // Note that this does not check for the presence of actual outputs (see the next check for that), + // and only helps with filling in pfMissingInputs (to determine missing vs spent). + BOOST_FOREACH(const CTxIn txin, tx.vin) { - if (pfMissingInputs) - *pfMissingInputs = true; - //fprintf(stderr,"missing inputs\n"); - return false; + if (!view.HaveCoins(txin.prevout.hash)) + { + if (pfMissingInputs) + *pfMissingInputs = true; + //fprintf(stderr,"missing inputs\n"); + return false; + } + } + + // are the actual inputs available? + if (!view.HaveInputs(tx)) + { + //fprintf(stderr,"accept failure.1\n"); + return state.Invalid(error("AcceptToMemoryPool: inputs already spent"),REJECT_DUPLICATE, "bad-txns-inputs-spent"); } - } - - // are the actual inputs available? - if (!view.HaveInputs(tx)) - { - //fprintf(stderr,"accept failure.1\n"); - return state.Invalid(error("AcceptToMemoryPool: inputs already spent"),REJECT_DUPLICATE, "bad-txns-inputs-spent"); } // are the joinsplit's requirements met? if (!view.HaveJoinSplitRequirements(tx)) @@ -1393,11 +1404,13 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa // Keep track of transactions that spend a coinbase, which we re-scan // during reorgs to ensure COINBASE_MATURITY is still met. bool fSpendsCoinbase = false; - BOOST_FOREACH(const CTxIn &txin, tx.vin) { - const CCoins *coins = view.AccessCoins(txin.prevout.hash); - if (coins->IsCoinBase()) { - fSpendsCoinbase = true; - break; + if (!tx.IsCoinImport()) { + BOOST_FOREACH(const CTxIn &txin, tx.vin) { + const CCoins *coins = view.AccessCoins(txin.prevout.hash); + if (coins->IsCoinBase()) { + fSpendsCoinbase = true; + break; + } } } @@ -1478,6 +1491,7 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa // There is a similar check in CreateNewBlock() to prevent creating // invalid blocks, however allowing such transactions into the mempool // can be exploited as a DoS attack. + // XXX: is this neccesary for CryptoConditions? if (!ContextualCheckInputs(tx, state, view, true, MANDATORY_SCRIPT_VERIFY_FLAGS, true, txdata, Params().GetConsensus(), consensusBranchId)) { fprintf(stderr,"accept failure.10\n"); @@ -1967,7 +1981,7 @@ void static InvalidBlockFound(CBlockIndex *pindex, const CValidationState &state void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, CTxUndo &txundo, int nHeight) { - if (!tx.IsCoinBase()) // mark inputs spent + if (!tx.IsMint()) // mark inputs spent { txundo.vprevout.reserve(tx.vin.size()); BOOST_FOREACH(const CTxIn &txin, tx.vin) { @@ -2003,7 +2017,8 @@ void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, int nHeight) bool CScriptCheck::operator()() { const CScript &scriptSig = ptxTo->vin[nIn].scriptSig; - if (!VerifyScript(scriptSig, scriptPubKey, nFlags, ServerTransactionSignatureChecker(ptxTo, nIn, amount, cacheStore, *txdata), consensusBranchId, &error)) { + ServerTransactionSignatureChecker checker(ptxTo, nIn, amount, cacheStore, *txdata); + if (!VerifyScript(scriptSig, scriptPubKey, nFlags, checker, consensusBranchId, &error)) { return ::error("CScriptCheck(): %s:%d VerifySignature failed: %s", ptxTo->GetHash().ToString(), nIn, ScriptErrorString(error)); } return true; @@ -2113,7 +2128,7 @@ bool ContextualCheckInputs( uint32_t consensusBranchId, std::vector *pvChecks) { - if (!tx.IsCoinBase()) + if (!tx.IsMint()) { if (!Consensus::CheckTxInputs(tx, state, inputs, GetSpendHeight(inputs), consensusParams)) { return false; @@ -2165,7 +2180,13 @@ bool ContextualCheckInputs( } } } - + + if (tx.IsCoinImport()) + { + ServerTransactionSignatureChecker checker(&tx, 0, 0, false, txdata); + return VerifyCoinImport(tx.vin[0].scriptSig, checker, state); + } + return true; } @@ -2400,7 +2421,7 @@ bool DisconnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex } // restore inputs - if (i > 0) { // not coinbases + if (!tx.IsMint()) { const CTxUndo &txundo = blockUndo.vtxundo[i-1]; if (txundo.vprevout.size() != tx.vin.size()) return error("DisconnectBlock(): transaction and undo data inconsistent"); @@ -2681,7 +2702,7 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin return state.DoS(100, error("ConnectBlock(): too many sigops"), REJECT_INVALID, "bad-blk-sigops"); //fprintf(stderr,"ht.%d vout0 t%u\n",pindex->nHeight,tx.nLockTime); - if (!tx.IsCoinBase()) + if (!tx.IsMint()) { if (!view.HaveInputs(tx)) return state.DoS(100, error("ConnectBlock(): inputs missing/spent"), diff --git a/src/miner.cpp b/src/miner.cpp index 59d1b8a11..f57b53d98 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -11,6 +11,7 @@ #include "amount.h" #include "base58.h" #include "chainparams.h" +#include "cc/importcoin.h" #include "consensus/consensus.h" #include "consensus/upgrades.h" #include "consensus/validation.h" @@ -224,48 +225,55 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn) double dPriority = 0; CAmount nTotalIn = 0; bool fMissingInputs = false; - BOOST_FOREACH(const CTxIn& txin, tx.vin) + if (tx.IsCoinImport()) { - // Read prev transaction - if (!view.HaveCoins(txin.prevout.hash)) - { - // This should never happen; all transactions in the memory - // pool should connect to either transactions in the chain - // or other transactions in the memory pool. - if (!mempool.mapTx.count(txin.prevout.hash)) - { - LogPrintf("ERROR: mempool transaction missing input\n"); - if (fDebug) assert("mempool transaction missing input" == 0); - fMissingInputs = true; - if (porphan) - vOrphan.pop_back(); - break; - } - - // Has to wait for dependencies - if (!porphan) - { - // Use list for automatic deletion - vOrphan.push_back(COrphan(&tx)); - porphan = &vOrphan.back(); - } - mapDependers[txin.prevout.hash].push_back(porphan); - porphan->setDependsOn.insert(txin.prevout.hash); - nTotalIn += mempool.mapTx.find(txin.prevout.hash)->GetTx().vout[txin.prevout.n].nValue; - continue; - } - const CCoins* coins = view.AccessCoins(txin.prevout.hash); - assert(coins); - - CAmount nValueIn = coins->vout[txin.prevout.n].nValue; + CAmount nValueIn = GetCoinImportValue(tx); nTotalIn += nValueIn; - - int nConf = nHeight - coins->nHeight; - - dPriority += (double)nValueIn * nConf; + dPriority += (double)nValueIn * 1000; // flat multiplier + } else { + BOOST_FOREACH(const CTxIn& txin, tx.vin) + { + // Read prev transaction + if (!view.HaveCoins(txin.prevout.hash)) + { + // This should never happen; all transactions in the memory + // pool should connect to either transactions in the chain + // or other transactions in the memory pool. + if (!mempool.mapTx.count(txin.prevout.hash)) + { + LogPrintf("ERROR: mempool transaction missing input\n"); + if (fDebug) assert("mempool transaction missing input" == 0); + fMissingInputs = true; + if (porphan) + vOrphan.pop_back(); + break; + } + + // Has to wait for dependencies + if (!porphan) + { + // Use list for automatic deletion + vOrphan.push_back(COrphan(&tx)); + porphan = &vOrphan.back(); + } + mapDependers[txin.prevout.hash].push_back(porphan); + porphan->setDependsOn.insert(txin.prevout.hash); + nTotalIn += mempool.mapTx.find(txin.prevout.hash)->GetTx().vout[txin.prevout.n].nValue; + continue; + } + const CCoins* coins = view.AccessCoins(txin.prevout.hash); + assert(coins); + + CAmount nValueIn = coins->vout[txin.prevout.n].nValue; + nTotalIn += nValueIn; + + int nConf = nHeight - coins->nHeight; + + dPriority += (double)nValueIn * nConf; + } + nTotalIn += tx.GetJoinSplitValueIn(); } - nTotalIn += tx.GetJoinSplitValueIn(); - + if (fMissingInputs) continue; // Priority is sum(valuein * age) / modified_txsize diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index 35c12abc8..cdaba66c2 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -452,11 +452,21 @@ public: // Compute modified tx size for priority calculation (optionally given tx size) unsigned int CalculateModifiedSize(unsigned int nTxSize=0) const; + bool IsMint() const + { + return (vin.size() == 1 && vin[0].prevout.hash.IsNull()); + } + bool IsCoinBase() const { return (vin.size() == 1 && vin[0].prevout.IsNull()); } + bool IsCoinImport() const + { + return IsMint() && vin[0].prevout.n == 10e8; + } + friend bool operator==(const CTransaction& a, const CTransaction& b) { return a.hash == b.hash; diff --git a/src/rpcblockchain.cpp b/src/rpcblockchain.cpp index d9cc072e5..d09479008 100644 --- a/src/rpcblockchain.cpp +++ b/src/rpcblockchain.cpp @@ -9,7 +9,7 @@ #include "checkpoints.h" #include "base58.h" #include "consensus/validation.h" -#include "cc/betprotocol.h" +#include "cc/eval.h" #include "main.h" #include "primitives/transaction.h" #include "rpcserver.h" @@ -1011,7 +1011,7 @@ UniValue txMoMproof(const UniValue& params, bool fHelp) // Encode and return CDataStream ssProof(SER_NETWORK, PROTOCOL_VERSION); - ssProof << MoMProof(nIndex, branch, notarisationHash); + ssProof << std::make_pair(notarisationHash, MerkleBranch(nIndex, branch)); return HexStr(ssProof.begin(), ssProof.end()); } diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 5438102c3..4a2eac2bc 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -3,17 +3,20 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include + #include "interpreter.h" #include "consensus/upgrades.h" #include "primitives/transaction.h" +#include "cc/eval.h" #include "crypto/ripemd160.h" #include "crypto/sha1.h" #include "crypto/sha256.h" #include "pubkey.h" #include "script/script.h" #include "uint256.h" -#include "cryptoconditions/include/cryptoconditions.h" + using namespace std; diff --git a/src/script/interpreter.h b/src/script/interpreter.h index 7aa0d5099..b4c537a2d 100644 --- a/src/script/interpreter.h +++ b/src/script/interpreter.h @@ -186,5 +186,4 @@ bool VerifyScript( const BaseSignatureChecker& checker, uint32_t consensusBranchId, ScriptError* serror = NULL); - #endif // BITCOIN_SCRIPT_INTERPRETER_H diff --git a/src/script/script.cpp b/src/script/script.cpp index a130df4aa..160c57016 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -8,8 +8,10 @@ #include "tinyformat.h" #include "utilstrencodings.h" #include "script/cc.h" +#include "cc/eval.h" #include "cryptoconditions/include/cryptoconditions.h" + namespace { inline std::string ValueString(const std::vector& vch) { @@ -266,6 +268,17 @@ bool CScript::MayAcceptCryptoCondition() const return out; } +bool CScript::IsCoinImport() const +{ + const_iterator pc = this->begin(); + vector data; + opcodetype opcode; + if (this->GetOp(pc, opcode, data)) + if (opcode > OP_0 && opcode <= OP_PUSHDATA4) + return data.begin()[0] == EVAL_IMPORTCOIN; + return false; +} + bool CScript::IsPushOnly() const { const_iterator pc = begin(); diff --git a/src/script/script.h b/src/script/script.h index e5328cf06..864a92f1f 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -570,6 +570,7 @@ public: bool IsPayToScriptHash() const; bool IsPayToCryptoCondition() const; + bool IsCoinImport() const; bool MayAcceptCryptoCondition() const; /** Called by IsStandardTx and P2SH/BIP62 VerifyScript (which makes it consensus-critical). */ diff --git a/src/test-komodo/main.cpp b/src/test-komodo/main.cpp index 527d7666e..0ce8740b3 100644 --- a/src/test-komodo/main.cpp +++ b/src/test-komodo/main.cpp @@ -1,7 +1,9 @@ #include "key.h" +#include "base58.h" #include "chainparams.h" #include "gtest/gtest.h" #include "crypto/common.h" +#include "testutils.h" int main(int argc, char **argv) { @@ -9,6 +11,11 @@ int main(int argc, char **argv) { ECC_Start(); SelectParams(CBaseChainParams::REGTEST); + CBitcoinSecret vchSecret; + // this returns false due to network prefix mismatch but works anyway + vchSecret.SetString(notarySecret); + notaryKey = vchSecret.GetKey(); + testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } diff --git a/src/test-komodo/test_coinimport.cpp b/src/test-komodo/test_coinimport.cpp new file mode 100644 index 000000000..6379aac7f --- /dev/null +++ b/src/test-komodo/test_coinimport.cpp @@ -0,0 +1,206 @@ + +#include +#include + +#include "cc/importcoin.h" +#include "cc/eval.h" +#include "base58.h" +#include "core_io.h" +#include "key.h" +#include "main.h" +#include "script/cc.h" +#include "primitives/transaction.h" +#include "script/interpreter.h" +#include "script/serverchecker.h" +#include "txmempool.h" + +#include "testutils.h" + + +extern Eval* EVAL_TEST; + +namespace TestCoinImport { + + +static uint8_t testNum = 0; + +class TestCoinImport : public ::testing::Test, public Eval { +public: + CMutableTransaction burnTx; + std::vector payouts; + MomoProof proof; + uint256 MoMoM; + CMutableTransaction importTx; + uint32_t chainId = 2; + CAmount amount = 100; + + void SetImportTx() { + CTxOut burnOutput = MakeBurnOutput(amount, chainId, payouts); + burnTx.vout.push_back(burnOutput); + MoMoM = burnTx.GetHash(); // TODO: an actual branch + importTx = CMutableTransaction(MakeImportCoinTransaction(proof, CTransaction(burnTx), payouts)); + } + + uint32_t GetCurrentLedgerID() const { return chainId; } + + bool GetNotarisationData(int notarisationHeight, NotarisationData &data, bool verifyCanonical) const + { + if (MoMoM.IsNull()) return false; + data.MoMoM = MoMoM; + return true; + } + +protected: + static void SetUpTestCase() { setupChain(); } + virtual void SetUp() { + ASSETCHAINS_CC = 1; + EVAL_TEST = this; + + std::vector fakepk; + fakepk.resize(33); + fakepk.begin()[0] = testNum++; + payouts.push_back(CTxOut(amount, CScript() << fakepk << OP_CHECKSIG)); + SetImportTx(); + } + + + void TestRunCCEval(CMutableTransaction mtx) + { + CTransaction importTx(mtx); + PrecomputedTransactionData txdata(importTx); + ServerTransactionSignatureChecker checker(&importTx, 0, 0, false, txdata); + CValidationState verifystate; + if (!VerifyCoinImport(importTx.vin[0].scriptSig, checker, verifystate)) + printf("TestRunCCEval: %s\n", verifystate.GetRejectReason().data()); + } +}; + + +TEST_F(TestCoinImport, testProcessImportThroughPipeline) +{ + CValidationState mainstate; + CTransaction tx(importTx); + + // first should work + acceptTxFail(tx); + + // should fail in mempool + ASSERT_FALSE(acceptTx(tx, mainstate)); + EXPECT_EQ("already in mempool", mainstate.GetRejectReason()); + + // should fail in persisted UTXO set + generateBlock(); + ASSERT_FALSE(acceptTx(tx, mainstate)); + EXPECT_EQ("already have coins", mainstate.GetRejectReason()); + ASSERT_TRUE(pcoinsTip->HaveCoins(tx.GetHash())); + + // Now disconnect the block + CValidationState invalstate; + if (!InvalidateBlock(invalstate, chainActive.Tip())) { + FAIL() << invalstate.GetRejectReason(); + } + ASSERT_FALSE(pcoinsTip->HaveCoins(tx.GetHash())); + + // should be back in mempool + ASSERT_FALSE(acceptTx(tx, mainstate)); + EXPECT_EQ("already in mempool", mainstate.GetRejectReason()); +} + + +TEST_F(TestCoinImport, testNoVouts) +{ + importTx.vout.resize(0); + TestRunCCEval(importTx); + EXPECT_EQ("no-vouts", state.GetRejectReason()); +} + + +TEST_F(TestCoinImport, testInvalidParams) +{ + std::vector payload = E_MARSHAL(ss << EVAL_IMPORTCOIN; ss << 'a'); + importTx.vin[0].scriptSig = CScript() << payload; + TestRunCCEval(importTx); + EXPECT_EQ("invalid-params", state.GetRejectReason()); +} + + +TEST_F(TestCoinImport, testNonCanonical) +{ + importTx.nLockTime = 10; + TestRunCCEval(importTx); + EXPECT_EQ("non-canonical", state.GetRejectReason()); +} + + +TEST_F(TestCoinImport, testInvalidBurnOutputs) +{ + burnTx.vout.resize(0); + MoMoM = burnTx.GetHash(); // TODO: an actual branch + CTransaction tx = MakeImportCoinTransaction(proof, CTransaction(burnTx), payouts); + TestRunCCEval(tx); + EXPECT_EQ("invalid-burn-outputs", state.GetRejectReason()); +} + + +TEST_F(TestCoinImport, testInvalidBurnParams) +{ + burnTx.vout[0].scriptPubKey = CScript() << OP_RETURN << E_MARSHAL(ss << VARINT(chainId)); + MoMoM = burnTx.GetHash(); // TODO: an actual branch + CTransaction tx = MakeImportCoinTransaction(proof, CTransaction(burnTx), payouts); + TestRunCCEval(tx); + EXPECT_EQ("invalid-burn-params", state.GetRejectReason()); +} + + +TEST_F(TestCoinImport, testWrongChainId) +{ + chainId = 0; + TestRunCCEval(importTx); + EXPECT_EQ("importcoin-wrong-chain", state.GetRejectReason()); +} + + +TEST_F(TestCoinImport, testInvalidBurnAmount) +{ + burnTx.vout[0].nValue = 0; + MoMoM = burnTx.GetHash(); // TODO: an actual branch + CTransaction tx = MakeImportCoinTransaction(proof, CTransaction(burnTx), payouts); + TestRunCCEval(tx); + EXPECT_EQ("invalid-burn-amount", state.GetRejectReason()); +} + + +TEST_F(TestCoinImport, testPayoutTooHigh) +{ + importTx.vout[0].nValue = 101; + TestRunCCEval(importTx); + EXPECT_EQ("payout-too-high", state.GetRejectReason()); +} + + +TEST_F(TestCoinImport, testInvalidPayouts) +{ + importTx.vout[0].nValue = 40; + importTx.vout.push_back(importTx.vout[0]); + TestRunCCEval(importTx); + EXPECT_EQ("wrong-payouts", state.GetRejectReason()); +} + + +TEST_F(TestCoinImport, testCouldntLoadMomom) +{ + MoMoM.SetNull(); + TestRunCCEval(importTx); + EXPECT_EQ("coudnt-load-momom", state.GetRejectReason()); +} + + +TEST_F(TestCoinImport, testMomomCheckFail) +{ + MoMoM.SetNull(); + MoMoM.begin()[0] = 1; + TestRunCCEval(importTx); + EXPECT_EQ("momom-check-fail", state.GetRejectReason()); +} + +} /* namespace TestCoinImport */ diff --git a/src/test-komodo/test_cryptoconditions.cpp b/src/test-komodo/test_cryptoconditions.cpp index f1037a2ae..4fddf9c55 100644 --- a/src/test-komodo/test_cryptoconditions.cpp +++ b/src/test-komodo/test_cryptoconditions.cpp @@ -12,11 +12,6 @@ #include "testutils.h" -CKey notaryKey; - -std::string pubkey = "0205a8ad0c1dbc515f149af377981aab58b836af008d4d7ab21bd76faf80550b47"; -std::string secret = "UxFWWxsf1d7w7K5TvAWSkeX4H95XQKwdwGv49DXwWUTzPTTjHBbU"; - class CCTest : public ::testing::Test { public: @@ -32,11 +27,6 @@ protected: virtual void SetUp() { // enable CC ASSETCHAINS_CC = 1; - // Notary key - CBitcoinSecret vchSecret; - // this returns false due to network prefix mismatch but works anyway - vchSecret.SetString(secret); - notaryKey = vchSecret.GetKey(); } }; diff --git a/src/test-komodo/test_eval_bet.cpp b/src/test-komodo/test_eval_bet.cpp index 6f41608b9..4dfcf1cab 100644 --- a/src/test-komodo/test_eval_bet.cpp +++ b/src/test-komodo/test_eval_bet.cpp @@ -208,7 +208,7 @@ public: int nIndex = 5; std::vector vBranch; vBranch.resize(3); - return MoMProof(nIndex, vBranch, EvalMock::NotarisationHash()); + return {MerkleBranch(nIndex, vBranch), EvalMock::NotarisationHash()}; } CMutableTransaction ImportPayoutTx() @@ -237,7 +237,7 @@ public: eval.currentHeight = currentHeight; MoMProof proof = GetMoMProof(); - eval.MoM = proof.Exec(DisputeTx(Player2).GetHash()); + eval.MoM = proof.branch.Exec(DisputeTx(Player2).GetHash()); EVAL_TEST = &eval; return eval; @@ -585,7 +585,7 @@ TEST_F(TestBet, testImportPayoutMomFail) EvalMock eval = ebet.SetEvalMock(12); MoMProof proof = ebet.GetMoMProof(); - proof.nIndex ^= 1; + proof.branch.nIndex ^= 1; CMutableTransaction importTx = ebet.bet.MakeImportPayoutTx( ebet.Payouts(Player2), ebet.DisputeTx(Player2), uint256(), proof); diff --git a/src/test-komodo/test_eval_notarisation.cpp b/src/test-komodo/test_eval_notarisation.cpp index 86d5f58c4..f0ce45358 100644 --- a/src/test-komodo/test_eval_notarisation.cpp +++ b/src/test-komodo/test_eval_notarisation.cpp @@ -15,7 +15,6 @@ #include "testutils.h" -extern Eval* EVAL_TEST; extern int32_t komodo_notaries(uint8_t pubkeys[64][33],int32_t height,uint32_t timestamp); @@ -61,7 +60,7 @@ static auto noop = [&](CMutableTransaction &mtx){}; template -void SetEval(EvalMock &eval, CMutableTransaction ¬ary, Modifier modify) +void SetupEval(EvalMock &eval, CMutableTransaction ¬ary, Modifier modify) { eval.nNotaries = komodo_notaries(eval.notaries, 780060, 1522946781); @@ -80,8 +79,6 @@ void SetEval(EvalMock &eval, CMutableTransaction ¬ary, Modifier modify) eval.txs[notary.GetHash()] = CTransaction(notary); eval.blocks[notary.GetHash()].nHeight = 780060; eval.blocks[notary.GetHash()].nTime = 1522946781; - - EVAL_TEST = &eval; } @@ -94,12 +91,12 @@ 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); + SetupEval(eval, notary, noop); NotarisationData data; ASSERT_TRUE(eval.GetNotarisationData(notary.GetHash(), data)); @@ -111,7 +108,7 @@ TEST(TestEvalNotarisation, testGetNotarisation) MoMProof proof; E_UNMARSHAL(vMomProof, ss >> proof); - EXPECT_EQ(data.MoM, proof.Exec(proofTxHash)); + EXPECT_EQ(data.MoM, proof.branch.Exec(proofTxHash)); } @@ -119,13 +116,14 @@ TEST(TestEvalNotarisation, testInvalidNotaryPubkey) { EvalMock eval; CMutableTransaction notary(notaryTx); - SetEval(eval, notary, noop); + SetupEval(eval, notary, noop); memset(eval.notaries[10], 0, 33); NotarisationData data; ASSERT_FALSE(eval.GetNotarisationData(notary.GetHash(), data)); } +*/ TEST(TestEvalNotarisation, testInvalidNotarisationBadOpReturn) @@ -134,7 +132,7 @@ TEST(TestEvalNotarisation, testInvalidNotarisationBadOpReturn) CMutableTransaction notary(notaryTx); notary.vout[1].scriptPubKey = CScript() << OP_RETURN << 0; - SetEval(eval, notary, noop); + SetupEval(eval, notary, noop); NotarisationData data; ASSERT_FALSE(eval.GetNotarisationData(notary.GetHash(), data)); @@ -146,7 +144,7 @@ TEST(TestEvalNotarisation, testInvalidNotarisationTxNotEnoughSigs) EvalMock eval; CMutableTransaction notary(notaryTx); - SetEval(eval, notary, [](CMutableTransaction &tx) { + SetupEval(eval, notary, [](CMutableTransaction &tx) { tx.vin.resize(10); }); @@ -160,7 +158,7 @@ TEST(TestEvalNotarisation, testInvalidNotarisationTxDoesntExist) EvalMock eval; CMutableTransaction notary(notaryTx); - SetEval(eval, notary, noop); + SetupEval(eval, notary, noop); NotarisationData data; ASSERT_FALSE(eval.GetNotarisationData(uint256(), data)); @@ -172,7 +170,7 @@ TEST(TestEvalNotarisation, testInvalidNotarisationDupeNotary) EvalMock eval; CMutableTransaction notary(notaryTx); - SetEval(eval, notary, [](CMutableTransaction &tx) { + SetupEval(eval, notary, [](CMutableTransaction &tx) { tx.vin[1] = tx.vin[3]; }); @@ -186,7 +184,7 @@ TEST(TestEvalNotarisation, testInvalidNotarisationInputNotCheckSig) EvalMock eval; CMutableTransaction notary(notaryTx); - SetEval(eval, notary, [&](CMutableTransaction &tx) { + SetupEval(eval, notary, [&](CMutableTransaction &tx) { int i = 1; CMutableTransaction txIn; txIn.vout.resize(1); diff --git a/src/test-komodo/testutils.cpp b/src/test-komodo/testutils.cpp new file mode 100644 index 000000000..c3baf4fb6 --- /dev/null +++ b/src/test-komodo/testutils.cpp @@ -0,0 +1,129 @@ +#include +#include +#include + +#include "core_io.h" +#include "key.h" +#include "main.h" +#include "miner.h" +#include "random.h" +#include "rpcserver.h" +#include "rpcprotocol.h" +#include "txdb.h" +#include "util.h" +#include "utilstrencodings.h" +#include "utiltime.h" +#include "consensus/validation.h" +#include "primitives/transaction.h" +#include "script/cc.h" +#include "script/interpreter.h" + +#include "testutils.h" + + +std::string notaryPubkey = "0205a8ad0c1dbc515f149af377981aab58b836af008d4d7ab21bd76faf80550b47"; +std::string notarySecret = "UxFWWxsf1d7w7K5TvAWSkeX4H95XQKwdwGv49DXwWUTzPTTjHBbU"; +CKey notaryKey; + + +/* + * We need to have control of clock, + * otherwise block production can fail. + */ +int64_t nMockTime; + +extern uint32_t USE_EXTERNAL_PUBKEY; + +void setupChain() +{ + SelectParams(CBaseChainParams::REGTEST); + + // Settings to get block reward + //NOTARY_PUBKEY = _NOTARY_PUBKEY; + USE_EXTERNAL_PUBKEY = 1; + mapArgs["-mineraddress"] = "bogus"; + COINBASE_MATURITY = 1; + // Global mock time + nMockTime = GetTime(); + + // Init blockchain + ClearDatadirCache(); + auto pathTemp = GetTempPath() / strprintf("test_komodo_%li_%i", GetTime(), GetRand(100000)); + boost::filesystem::create_directories(pathTemp); + mapArgs["-datadir"] = pathTemp.string(); + pblocktree = new CBlockTreeDB(1 << 20, true); + CCoinsViewDB *pcoinsdbview = new CCoinsViewDB(1 << 23, true); + pcoinsTip = new CCoinsViewCache(pcoinsdbview); + InitBlockIndex(); +} + + +void generateBlock(CBlock *block) +{ + UniValue params; + params.setArray(); + params.push_back(1); + uint256 blockId; + + SetMockTime(nMockTime++); // CreateNewBlock can fail if not enough time passes + + try { + UniValue out = generate(params, false); + blockId.SetHex(out[0].getValStr()); + } catch (const UniValue& e) { + FAIL() << "failed to create block: " << e.write().data(); + } + if (block) ASSERT_TRUE(ReadBlockFromDisk(*block, mapBlockIndex[blockId])); +} + + +void acceptTxFail(const CTransaction tx) +{ + CValidationState state; + if (!acceptTx(tx, state)) FAIL() << state.GetRejectReason(); +} + + +bool acceptTx(const CTransaction tx, CValidationState &state) +{ + LOCK(cs_main); + return AcceptToMemoryPool(mempool, state, tx, false, NULL); +} + + +static CMutableTransaction spendTx(const CTransaction &txIn, int nOut=0) +{ + CMutableTransaction mtx; + mtx.vin.resize(1); + mtx.vin[0].prevout.hash = txIn.GetHash(); + mtx.vin[0].prevout.n = nOut; + mtx.vout.resize(1); + mtx.vout[0].nValue = txIn.vout[nOut].nValue - 1000; + return mtx; +} + + +/* + * In order to do tests there needs to be inputs to spend. + * This method creates a block and returns a transaction that spends the coinbase. + */ +void getInputTx(CScript scriptPubKey, CTransaction &txIn) +{ + // Get coinbase + CBlock block; + generateBlock(&block); + CTransaction coinbase = block.vtx[0]; + + // Create tx + auto mtx = spendTx(coinbase); + mtx.vout[0].scriptPubKey = scriptPubKey; + uint256 hash = SignatureHash(coinbase.vout[0].scriptPubKey, mtx, 0, SIGHASH_ALL, 0, 0); + std::vector vchSig; + notaryKey.Sign(hash, vchSig); + vchSig.push_back((unsigned char)SIGHASH_ALL); + mtx.vin[0].scriptSig << vchSig; + + // Accept + acceptTxFail(mtx); + txIn = CTransaction(mtx); +} diff --git a/src/test-komodo/testutils.h b/src/test-komodo/testutils.h index df8e88cd9..92d28b587 100644 --- a/src/test-komodo/testutils.h +++ b/src/test-komodo/testutils.h @@ -1,7 +1,7 @@ #ifndef TESTUTILS_H #define TESTUTILS_H -#include "script/cc.h" +#include "main.h" #define VCH(a,b) std::vector(a, a + b) @@ -12,4 +12,16 @@ static char ccjsonerr[1000] = "\0"; if (!o) FAIL() << "bad json: " << ccjsonerr; +extern std::string notaryPubkey; +extern std::string notarySecret; +extern CKey notaryKey; + + +void setupChain(); +void generateBlock(CBlock *block=NULL); +bool acceptTx(const CTransaction tx, CValidationState &state); +void acceptTxFail(const CTransaction tx); +void getInputTx(CScript scriptPubKey, CTransaction &txIn); + + #endif /* TESTUTILS_H */ diff --git a/src/txmempool.cpp b/src/txmempool.cpp index dd985f500..9527dd854 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -104,8 +104,10 @@ bool CTxMemPool::addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry, LOCK(cs); mapTx.insert(entry); const CTransaction& tx = mapTx.find(hash)->GetTx(); - for (unsigned int i = 0; i < tx.vin.size(); i++) - mapNextTx[tx.vin[i].prevout] = CInPoint(&tx, i); + if (!tx.IsCoinImport()) { + for (unsigned int i = 0; i < tx.vin.size(); i++) + mapNextTx[tx.vin[i].prevout] = CInPoint(&tx, i); + } BOOST_FOREACH(const JSDescription &joinsplit, tx.vjoinsplit) { BOOST_FOREACH(const uint256 &nf, joinsplit.nullifiers) { mapNullifiers[nf] = &tx;