diff --git a/.gitignore b/.gitignore index bebcef932..3916d4e87 100644 --- a/.gitignore +++ b/.gitignore @@ -120,3 +120,4 @@ src/komodo-cli src/komodod src/komodo-tx src/komodo-test +src/wallet-utility 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..f9b19978b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -256,11 +256,11 @@ libbitcoin_server_a_SOURCES = \ asyncrpcqueue.cpp \ bloom.cpp \ cc/eval.cpp \ - cc/importpayout.cpp \ - cc/disputepayout.cpp \ + cc/import.cpp \ cc/betprotocol.cpp \ chain.cpp \ checkpoints.cpp \ + crosschain.cpp \ deprecation.cpp \ httprpc.cpp \ httpserver.cpp \ @@ -272,12 +272,14 @@ libbitcoin_server_a_SOURCES = \ miner.cpp \ net.cpp \ noui.cpp \ + notarisationdb.cpp \ paymentdisclosure.cpp \ paymentdisclosuredb.cpp \ policy/fees.cpp \ pow.cpp \ rest.cpp \ rpcblockchain.cpp \ + rpccrosschain.cpp \ rpcmining.cpp \ rpcmisc.cpp \ rpcnet.cpp \ @@ -385,6 +387,7 @@ libbitcoin_common_a_SOURCES = \ core_read.cpp \ core_write.cpp \ hash.cpp \ + importcoin.cpp \ key.cpp \ keystore.cpp \ netbase.cpp \ @@ -605,7 +608,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..07c64caa0 100644 --- a/src/Makefile.ktest.include +++ b/src/Makefile.ktest.include @@ -5,9 +5,13 @@ 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 + test-komodo/test_eval_notarisation.cpp \ + test-komodo/test_crosschain.cpp \ + test-komodo/test_parse_notarisation.cpp komodo_test_CPPFLAGS = $(komodod_CPPFLAGS) diff --git a/src/assetchains.old b/src/assetchains.old index 3f42fef13..228767f25 100755 --- a/src/assetchains.old +++ b/src/assetchains.old @@ -35,6 +35,7 @@ echo $pubkey ./komodod -pubkey=$pubkey -ac_name=GLXT -ac_supply=100000000 -addnode=13.230.224.15 & ./komodod -pubkey=$pubkey -ac_name=EQL -ac_supply=500000000 -addnode=46.101.124.153 & ./komodod -pubkey=$pubkey -ac_name=ZILLA -ac_supply=11000000 -addnode=54.39.23.248 & -~/VerusCoin/src/komodod -ac_name=VRSC -ac_algo=verushash -ac_cc=1 -ac_veruspos=50 -ac_supply=0 -ac_eras=3 -ac_reward=0,38400000000,2400000000 -ac_halving=1,43200,1051920 -ac_decay=100000000,0,0 -ac_end=10080,226080,0 -ac_timelockgte=19200000000 -ac_timeunlockfrom=129600 -ac_timeunlockto=1180800 -addnode=185.25.48.236 -addnode=185.64.105.111 & +./komodod -pubkey=$pubkey -ac_name=RFOX -ac_supply=1000000000 -ac_reward=100000000 -addnode=78.47.196.146 & +~/veruscoin/src/komodod -ac_name=VRSC -ac_algo=verushash -ac_cc=1 -ac_veruspos=50 -ac_supply=0 -ac_eras=3 -ac_reward=0,38400000000,2400000000 -ac_halving=1,43200,1051920 -ac_decay=100000000,0,0 -ac_end=10080,226080,0 -ac_timelockgte=19200000000 -ac_timeunlockfrom=129600 -ac_timeunlockto=1180800 -addnode=185.25.48.236 -addnode=185.64.105.111 & diff --git a/src/cc/betprotocol.cpp b/src/cc/betprotocol.cpp index 53b79176c..9fe9a567d 100644 --- a/src/cc/betprotocol.cpp +++ b/src/cc/betprotocol.cpp @@ -1,9 +1,13 @@ #include +#include "hash.h" +#include "main.h" +#include "chain.h" #include "streams.h" #include "script/cc.h" -#include "cc/eval.h" #include "cc/betprotocol.h" +#include "cc/eval.h" +#include "cc/utils.h" #include "primitives/transaction.h" @@ -137,3 +141,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(0); + 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..25550d1e2 100644 --- a/src/cc/eval.cpp +++ b/src/cc/eval.cpp @@ -1,12 +1,15 @@ #include #include +#include "primitives/block.h" #include "primitives/transaction.h" #include "script/cc.h" #include "cc/eval.h" +#include "cc/utils.h" #include "main.h" #include "chain.h" #include "core_io.h" +#include "crosschain.h" Eval* EVAL_TEST = 0; @@ -14,9 +17,7 @@ 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_; + EvalRef eval; bool out = eval->Dispatch(cond, tx, nIn); assert(eval->state.IsValid() == out); @@ -49,6 +50,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"); } @@ -144,7 +149,7 @@ bool Eval::CheckNotaryInputs(const CTransaction &tx, uint32_t height, uint32_t t /* - * Get MoM from a notarisation tx hash + * Get MoM from a notarisation tx hash (on KMD) */ bool Eval::GetNotarisationData(const uint256 notaryHash, NotarisationData &data) const { @@ -152,54 +157,84 @@ bool Eval::GetNotarisationData(const uint256 notaryHash, NotarisationData &data) CBlockIndex block; if (!GetTxConfirmed(notaryHash, notarisationTx, block)) return false; if (!CheckNotaryInputs(notarisationTx, block.nHeight, block.nTime)) return false; - if (notarisationTx.vout.size() < 2) return false; - if (!data.Parse(notarisationTx.vout[1].scriptPubKey)) return false; + if (!ParseNotarisationOpReturn(notarisationTx, data)) return false; return true; } +/* + * Get MoMoM corresponding to a notarisation tx hash (on assetchain) + */ +bool Eval::GetProofRoot(uint256 kmdNotarisationHash, uint256 &momom) const +{ + std::pair out; + if (!GetNextBacknotarisation(kmdNotarisationHash, out)) return false; + momom = out.second.MoMoM; + return true; +} + + +uint32_t Eval::GetAssetchainsCC() const +{ + return ASSETCHAINS_CC; +} + + +std::string Eval::GetAssetchainsSymbol() const +{ + return std::string(ASSETCHAINS_SYMBOL); +} + /* * Notarisation data, ie, OP_RETURN payload in notarisation transactions */ -extern char ASSETCHAINS_SYMBOL[16]; - -bool NotarisationData::Parse(const CScript scriptPK) +bool ParseNotarisationOpReturn(const CTransaction &tx, NotarisationData &data) { - *this = NotarisationData(); - + if (tx.vout.size() < 2) return false; std::vector vdata; - if (!GetOpReturnData(scriptPK, vdata)) return false; - - CDataStream ss(vdata, SER_NETWORK, PROTOCOL_VERSION); - - try { - ss >> blockHash; - ss >> height; - if (ASSETCHAINS_SYMBOL[0]) - ss >> txHash; - - char *nullPos = (char*) memchr(&ss[0], 0, ss.size()); - if (!nullPos) return false; - ss.read(symbol, nullPos-&ss[0]+1); - - if (ss.size() < 36) return false; - ss >> MoM; - ss >> MoMDepth; - } catch (...) { - return false; - } - return true; + if (!GetOpReturnData(tx.vout[1].scriptPubKey, vdata)) return false; + bool out = E_UNMARSHAL(vdata, ss >> data); + return out; } /* * Misc */ - std::string EvalToStr(EvalCode c) { FOREACH_EVAL(EVAL_GENERATE_STRING); 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; +} + + +uint256 GetMerkleRoot(const std::vector& vLeaves) +{ + bool fMutated; + std::vector vMerkleTree; + return BuildMerkleTree(&fMutated, vLeaves, vMerkleTree); } diff --git a/src/cc/eval.h b/src/cc/eval.h index f998c9f3d..4bcdd5617 100644 --- a/src/cc/eval.h +++ b/src/cc/eval.h @@ -3,6 +3,7 @@ #include +#include "cc/utils.h" #include "chain.h" #include "streams.h" #include "version.h" @@ -20,8 +21,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 +57,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,10 +72,30 @@ 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 GetProofRoot(uint256 kmdNotarisationHash, uint256 &momom) const; virtual bool CheckNotaryInputs(const CTransaction &tx, uint32_t height, uint32_t timestamp) const; + virtual uint32_t GetAssetchainsCC() const; + virtual std::string GetAssetchainsSymbol() const; }; +extern Eval* EVAL_TEST; + + +/* + * Get a pointer to an Eval to use + */ +typedef std::unique_ptr EvalRef_; +class EvalRef : public EvalRef_ +{ +public: + EvalRef() : EvalRef_( + EVAL_TEST ? EVAL_TEST : new Eval(), + [](Eval* e){if (e!=EVAL_TEST) delete e;}) { } +}; + + + bool RunCCEval(const CC *cond, const CTransaction &tx, unsigned int nIn); @@ -88,22 +116,89 @@ public: }; -/* - * Data from notarisation OP_RETURN - */ -class NotarisationData { -public: - uint256 blockHash; - uint32_t height; - uint256 txHash; // Only get this guy in asset chains not in KMD - char symbol[64]; - uint256 MoM; - uint32_t MoMDepth; +extern char ASSETCHAINS_SYMBOL[65]; - bool Parse(CScript scriptPubKey); + +/* + * Data from notarisation OP_RETURN from chain being notarised + */ +class NotarisationData +{ +public: + int IsBackNotarisation = 0; + uint256 blockHash = uint256(); + uint32_t height = 0; + uint256 txHash = uint256(); + char symbol[64] = "\0"; + uint256 MoM = uint256(); + uint16_t MoMDepth = 0; + uint16_t ccId = 0; + uint256 MoMoM = uint256(); + uint32_t MoMoMDepth = 0; + + NotarisationData(int IsBack=2) : IsBackNotarisation(IsBack) {} + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) { + + bool IsBack = IsBackNotarisation; + if (2 == IsBackNotarisation) IsBack = DetectBackNotarisation(s, ser_action); + + READWRITE(blockHash); + READWRITE(height); + if (IsBack) + READWRITE(txHash); + SerSymbol(s, ser_action); + if (s.size() == 0) return; + READWRITE(MoM); + READWRITE(MoMDepth); + READWRITE(ccId); + if (s.size() == 0) return; + if (IsBack) { + READWRITE(MoMoM); + READWRITE(MoMoMDepth); + } + } + + template + void SerSymbol(Stream& s, CSerActionSerialize act) + { + s.write(symbol, strlen(symbol)+1); + } + + template + void SerSymbol(Stream& s, CSerActionUnserialize act) + { + char *nullPos = (char*) memchr(&s[0], 0, s.size()); + if (!nullPos) + throw std::ios_base::failure("couldn't parse symbol"); + s.read(symbol, nullPos-&s[0]+1); + } + + template + bool DetectBackNotarisation(Stream& s, CSerActionUnserialize act) + { + if (ASSETCHAINS_SYMBOL[0]) return 1; + if (s.size() >= 72) { + if (strcmp("BTC", &s[68]) == 0) return 1; + if (strcmp("KMD", &s[68]) == 0) return 1; + } + return 0; + } + + template + bool DetectBackNotarisation(Stream& s, CSerActionSerialize act) + { + return !txHash.IsNull(); + } }; +bool ParseNotarisationOpReturn(const CTransaction &tx, NotarisationData &data); + + /* * Eval code utilities. */ @@ -116,28 +211,42 @@ std::string EvalToStr(EvalCode c); /* - * Serialisation boilerplate + * Merkle stuff */ -#define E_MARSHAL(body) SerializeF([&] (CDataStream &ss) {body;}) -template -std::vector SerializeF(const T f) -{ - CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); - f(ss); - return std::vector(ss.begin(), ss.end()); -} +uint256 SafeCheckMerkleBranch(uint256 hash, const std::vector& vMerkleBranch, int nIndex); -#define E_UNMARSHAL(params, body) DeserializeF(params, [&] (CDataStream &ss) {body;}) -template -bool DeserializeF(const std::vector vIn, T f) + +class MerkleBranch { - CDataStream ss(vIn, SER_NETWORK, PROTOCOL_VERSION); - try { - f(ss); - if (ss.eof()) return true; - } catch(...) {} - return false; -} +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); } + + MerkleBranch& operator<<(MerkleBranch append) + { + nIndex += append.nIndex << branch.size(); + branch.insert(branch.end(), append.branch.begin(), append.branch.end()); + return *this; + } + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) { + READWRITE(VARINT(nIndex)); + READWRITE(branch); + } +}; + + +typedef std::pair TxProof; + + +uint256 GetMerkleRoot(const std::vector& vLeaves); #endif /* CC_EVAL_H */ diff --git a/src/cc/import.cpp b/src/cc/import.cpp new file mode 100644 index 000000000..5f0b804e8 --- /dev/null +++ b/src/cc/import.cpp @@ -0,0 +1,72 @@ +#include "cc/eval.h" +#include "cc/utils.h" +#include "importcoin.h" +#include "primitives/transaction.h" + + +/* + * CC Eval method for import coin. + * + * This method should control every parameter of the ImportCoin transaction, since it has no signature + * to protect it from malleability. + */ +bool Eval::ImportCoin(const std::vector params, const CTransaction &importTx, unsigned int nIn) +{ + if (importTx.vout.size() < 2) + return Invalid("too-few-vouts"); + + // params + TxProof proof; + CTransaction burnTx; + std::vector payouts; + + if (!UnmarshalImportTx(importTx, proof, burnTx, payouts)) + return Invalid("invalid-params"); + + // Control all aspects of this transaction + // It should not be at all malleable + if (MakeImportCoinTransaction(proof, burnTx, payouts).GetHash() != importTx.GetHash()) + return Invalid("non-canonical"); + + // burn params + uint32_t targetCcid; + std::string targetSymbol; + uint256 payoutsHash; + + if (!UnmarshalBurnTx(burnTx, targetSymbol, &targetCcid, payoutsHash)) + return Invalid("invalid-burn-tx"); + + if (targetCcid != GetAssetchainsCC() || targetSymbol != GetAssetchainsSymbol()) + 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(payouts)) + return Invalid("wrong-payouts"); + + // Check proof confirms existance of burnTx + { + uint256 momom, target; + if (!GetProofRoot(proof.first, momom)) + return Invalid("coudnt-load-momom"); + + target = proof.second.Exec(burnTx.GetHash()); + if (momom != proof.second.Exec(burnTx.GetHash())) + return Invalid("momom-check-fail"); + } + + return Valid(); +} + + 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/cc/utils.cpp b/src/cc/utils.cpp new file mode 100644 index 000000000..e69de29bb diff --git a/src/cc/utils.h b/src/cc/utils.h new file mode 100644 index 000000000..5704016e4 --- /dev/null +++ b/src/cc/utils.h @@ -0,0 +1,34 @@ +#ifndef CC_UTILS_H +#define CC_UTILS_H + +#include "streams.h" +#include "version.h" + + +/* + * Serialisation boilerplate + */ + +template +std::vector SerializeF(const T f) +{ + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + f(ss); + return std::vector(ss.begin(), ss.end()); +} + +template +bool DeserializeF(const std::vector vIn, T f) +{ + CDataStream ss(vIn, SER_NETWORK, PROTOCOL_VERSION); + try { + f(ss); + if (ss.eof()) return true; + } catch(...) {} + return false; +} + +#define E_MARSHAL(body) SerializeF([&] (CDataStream &ss) {body;}) +#define E_UNMARSHAL(params, body) DeserializeF(params, [&] (CDataStream &ss) {body;}) + +#endif /* CC_UTILS_H */ diff --git a/src/chainparams.cpp b/src/chainparams.cpp index e90fc23f0..d48d32c93 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -536,11 +536,13 @@ public: BOOST_STATIC_ASSERT(equihash_parameters_acceptable(N, K)); nEquihashN = N; nEquihashK = K; - + genesis = CreateGenesisBlock( 1296688602, uint256S("0x0000000000000000000000000000000000000000000000000000000000000009"), ParseHex("01936b7db1eb4ac39f151b8704642d0a8bda13ec547d54cd5e43ba142fc6d8877cab07b3"), + + KOMODO_MINDIFF_NBITS, 4, 0); consensus.hashGenesisBlock = genesis.GetHash(); assert(consensus.hashGenesisBlock == uint256S("0x029f11d80ef9765602235e1bc9727e3eb6ba20839319f761fee920d63401e327")); diff --git a/src/coins.cpp b/src/coins.cpp index 42939ccac..b2d838506 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 "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); @@ -483,6 +487,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/crosschain.cpp b/src/crosschain.cpp new file mode 100644 index 000000000..ca7223abb --- /dev/null +++ b/src/crosschain.cpp @@ -0,0 +1,336 @@ +#include "cc/eval.h" +#include "crosschain.h" +#include "importcoin.h" +#include "main.h" +#include "notarisationdb.h" + +/* + * The crosschain workflow. + * + * 3 chains, A, B, and KMD. We would like to prove TX on B. + * There is a notarisation, nA0, which will include TX via an MoM. + * The notarisation nA0 must fall between 2 notarisations of B, + * ie, nB0 and nB1. An MoMoM including this range is propagated to + * B in notarisation receipt (backnotarisation) bnB2. + * + * A: TX bnA0 + * \ / + * KMD: nB0 nA0 nB1 nB2 + * \ \ \ + * B: bnB0 bnB1 bnB2 + */ + +// XXX: There are potential crashes wherever we access chainActive without a lock, +// because it might be disconnecting blocks at the same time. + + +int NOTARISATION_SCAN_LIMIT_BLOCKS = 1440; + + +/* On KMD */ +uint256 CalculateProofRoot(const char* symbol, uint32_t targetCCid, int kmdHeight, + std::vector &moms, uint256 &destNotarisationTxid) +{ + /* + * Notaries don't wait for confirmation on KMD before performing a backnotarisation, + * but we need a determinable range that will encompass all merkle roots. Include MoMs + * including the block height of the last notarisation until the height before the + * previous notarisation. + * + * kmdHeight notarisations-0 notarisations-1 + * *********************| + * > scan backwards > + */ + + if (targetCCid <= 1) + return uint256(); + + if (kmdHeight < 0 || kmdHeight > chainActive.Height()) + return uint256(); + + int seenOwnNotarisations = 0; + + for (int i=0; i kmdHeight) break; + NotarisationsInBlock notarisations; + uint256 blockHash = *chainActive[kmdHeight-i]->phashBlock; + if (!GetBlockNotarisations(blockHash, notarisations)) + continue; + + // See if we have an own notarisation in this block + BOOST_FOREACH(Notarisation& nota, notarisations) { + if (strcmp(nota.second.symbol, symbol) == 0) + { + seenOwnNotarisations++; + if (seenOwnNotarisations == 1) + destNotarisationTxid = nota.first; + else if (seenOwnNotarisations == 2) + goto end; + break; + } + } + + if (seenOwnNotarisations == 1) { + BOOST_FOREACH(Notarisation& nota, notarisations) { + if (nota.second.ccId == targetCCid) + moms.push_back(nota.second.MoM); + } + } + } + +end: + return GetMerkleRoot(moms); +} + + +/* + * Get a notarisation from a given height + * + * Will scan notarisations leveldb up to a limit + */ +template +int ScanNotarisationsFromHeight(int nHeight, const IsTarget f, Notarisation &found) +{ + int limit = std::min(nHeight + NOTARISATION_SCAN_LIMIT_BLOCKS, chainActive.Height()); + + for (int h=nHeight; hphashBlock, notarisations)) + continue; + + BOOST_FOREACH(found, notarisations) { + if (f(found)) { + return h; + } + } + } + return 0; +} + + +/* On KMD */ +TxProof GetCrossChainProof(const uint256 txid, const char* targetSymbol, uint32_t targetCCid, + const TxProof assetChainProof) +{ + /* + * Here we are given a proof generated by an assetchain A which goes from given txid to + * an assetchain MoM. We need to go from the notarisationTxid for A to the MoMoM range of the + * backnotarisation for B (given by kmdheight of notarisation), find the MoM within the MoMs for + * that range, and finally extend the proof to lead to the MoMoM (proof root). + */ + EvalRef eval; + uint256 MoM = assetChainProof.second.Exec(txid); + + // Get a kmd height for given notarisation Txid + int kmdHeight; + { + CTransaction sourceNotarisation; + uint256 hashBlock; + CBlockIndex blockIdx; + if (!eval->GetTxConfirmed(assetChainProof.first, sourceNotarisation, blockIdx)) + throw std::runtime_error("Notarisation not found"); + kmdHeight = blockIdx.nHeight; + } + + // We now have a kmdHeight of the notarisation from chain A. So we know that a MoM exists + // at that height. + // If we call CalculateProofRoot with that height, it'll scan backwards, until it finds + // a notarisation from B, and it might not include our notarisation from A + // at all. So, the thing we need to do is scan forwards to find the notarisation for B, + // that is inclusive of A. + Notarisation nota; + auto isTarget = [&](Notarisation ¬a) { + return strcmp(nota.second.symbol, targetSymbol) == 0; + }; + kmdHeight = ScanNotarisationsFromHeight(kmdHeight, isTarget, nota); + if (!kmdHeight) + throw std::runtime_error("Cannot find notarisation for target inclusive of source"); + + // Get MoMs for kmd height and symbol + std::vector moms; + uint256 targetChainNotarisationTxid; + uint256 MoMoM = CalculateProofRoot(targetSymbol, targetCCid, kmdHeight, moms, targetChainNotarisationTxid); + if (MoMoM.IsNull()) + throw std::runtime_error("No MoMs found"); + + // Find index of source MoM in MoMoM + int nIndex; + for (nIndex=0; nIndex vBranch; + { + CBlock fakeBlock; + for (int i=0; i payouts; + if (!UnmarshalImportTx(importTx, proof, burnTx, payouts)) + throw std::runtime_error("Couldn't parse importTx"); + + std::string targetSymbol; + uint32_t targetCCid; + uint256 payoutsHash; + if (!UnmarshalBurnTx(burnTx, targetSymbol, &targetCCid, payoutsHash)) + throw std::runtime_error("Couldn't parse burnTx"); + + proof = GetCrossChainProof(burnTx.GetHash(), targetSymbol.data(), targetCCid, proof); + + importTx = MakeImportCoinTransaction(proof, burnTx, payouts); +} + + +bool IsSameAssetChain(const Notarisation ¬a) { + return strcmp(nota.second.symbol, ASSETCHAINS_SYMBOL) == 0; +}; + + +/* On assetchain */ +bool GetNextBacknotarisation(uint256 kmdNotarisationTxid, Notarisation &out) +{ + /* + * Here we are given a txid, and a proof. + * We go from the KMD notarisation txid to the backnotarisation, + * then jump to the next backnotarisation, which contains the corresponding MoMoM. + */ + Notarisation bn; + if (!GetBackNotarisation(kmdNotarisationTxid, bn)) + return false; + + // Need to get block height of that backnotarisation + EvalRef eval; + CBlockIndex block; + CTransaction tx; + if (!eval->GetTxConfirmed(bn.first, tx, block)){ + fprintf(stderr, "Can't get height of backnotarisation, this should not happen\n"); + return false; + } + + return (bool) ScanNotarisationsFromHeight(block.nHeight+1, &IsSameAssetChain, out); +} + + +/* + * On assetchain + * in: txid + * out: pair + */ +TxProof GetAssetchainProof(uint256 hash) +{ + int nIndex; + CBlockIndex* blockIndex; + Notarisation nota; + std::vector branch; + + { + uint256 blockHash; + CTransaction tx; + if (!GetTransaction(hash, tx, blockHash, true)) + throw std::runtime_error("cannot find transaction"); + + if (blockHash.IsNull()) + throw std::runtime_error("tx still in mempool"); + + blockIndex = mapBlockIndex[blockHash]; + int h = blockIndex->nHeight; + // The assumption here is that the first notarisation for a height GTE than + // the transaction block height will contain the corresponding MoM. If there + // are sequence issues with the notarisations this may fail. + auto isTarget = [&](Notarisation ¬a) { + if (!IsSameAssetChain(nota)) return false; + return nota.second.height >= blockIndex->nHeight; + }; + if (!ScanNotarisationsFromHeight(blockIndex->nHeight, isTarget, nota)) + throw std::runtime_error("backnotarisation not yet confirmed"); + + // index of block in MoM leaves + nIndex = nota.second.height - blockIndex->nHeight; + } + + // build merkle chain from blocks to MoM + { + std::vector leaves, tree; + for (int i=0; ihashMerkleRoot; + leaves.push_back(mRoot); + } + bool fMutated; + BuildMerkleTree(&fMutated, leaves, tree); + branch = GetMerkleBranch(nIndex, leaves.size(), tree); + + // Check branch + uint256 ourResult = SafeCheckMerkleBranch(blockIndex->hashMerkleRoot, branch, nIndex); + if (nota.second.MoM != ourResult) + throw std::runtime_error("Failed merkle block->MoM"); + } + + // Now get the tx merkle branch + { + CBlock block; + + if (fHavePruned && !(blockIndex->nStatus & BLOCK_HAVE_DATA) && blockIndex->nTx > 0) + throw std::runtime_error("Block not available (pruned data)"); + + if(!ReadBlockFromDisk(block, blockIndex,1)) + throw std::runtime_error("Can't read block from disk"); + + // Locate the transaction in the block + int nTxIndex; + for (nTxIndex = 0; nTxIndex < (int)block.vtx.size(); nTxIndex++) + if (block.vtx[nTxIndex].GetHash() == hash) + break; + + if (nTxIndex == (int)block.vtx.size()) + throw std::runtime_error("Error locating tx in block"); + + std::vector txBranch = block.GetMerkleBranch(nTxIndex); + + // Check branch + if (block.hashMerkleRoot != CBlock::CheckMerkleBranch(hash, txBranch, nTxIndex)) + throw std::runtime_error("Failed merkle tx->block"); + + // concatenate branches + nIndex = (nIndex << txBranch.size()) + nTxIndex; + branch.insert(branch.begin(), txBranch.begin(), txBranch.end()); + } + + // Check the proof + if (nota.second.MoM != CBlock::CheckMerkleBranch(hash, branch, nIndex)) + throw std::runtime_error("Failed validating MoM"); + + // All done! + CDataStream ssProof(SER_NETWORK, PROTOCOL_VERSION); + return std::make_pair(nota.second.txHash, MerkleBranch(nIndex, branch)); +} diff --git a/src/crosschain.h b/src/crosschain.h new file mode 100644 index 000000000..15452ac63 --- /dev/null +++ b/src/crosschain.h @@ -0,0 +1,21 @@ +#ifndef CROSSCHAIN_H +#define CROSSCHAIN_H + +#include "cc/eval.h" + + +/* On assetchain */ +TxProof GetAssetchainProof(uint256 hash); + +/* On KMD */ +uint256 CalculateProofRoot(const char* symbol, uint32_t targetCCid, int kmdHeight, + std::vector &moms, uint256 &destNotarisationTxid); +TxProof GetCrossChainProof(const uint256 txid, const char* targetSymbol, uint32_t targetCCid, + const TxProof assetChainProof); +void CompleteImportTransaction(CTransaction &importTx); + +/* On assetchain */ +bool GetNextBacknotarisation(uint256 txid, std::pair &bn); + + +#endif /* CROSSCHAIN_H */ diff --git a/src/dpowassets b/src/dpowassets index b15bdbbd3..f9d86dd57 100755 --- a/src/dpowassets +++ b/src/dpowassets @@ -36,3 +36,5 @@ curl --url "http://127.0.0.1:7776" --data "{\"agent\":\"iguana\",\"method\":\"dp curl --url "http://127.0.0.1:7776" --data "{\"agent\":\"iguana\",\"method\":\"dpow\",\"symbol\":\"ZILLA\",\"pubkey\":\"$pubkey\"}" curl --url "http://127.0.0.1:7776" --data "{\"agent\":\"iguana\",\"method\":\"dpow\",\"symbol\":\"CHIPS\",\"pubkey\":\"$pubkey\"}" curl --url "http://127.0.0.1:7776" --data "{\"agent\":\"iguana\",\"method\":\"dpow\",\"symbol\":\"GAME\",\"freq\":5,\"pubkey\":\"$pubkey\"}" +curl --url "http://127.0.0.1:7776" --data "{\"agent\":\"iguana\",\"method\":\"dpow\",\"symbol\":\"RFOX\",\"freq\":10,\"pubkey\":\"$pubkey\"}" +curl --url "http://127.0.0.1:7776" --data "{\"agent\":\"iguana\",\"method\":\"dpow\",\"symbol\":\"VRSC\",\"freq\":10,\"pubkey\":\"$pubkey\"}" diff --git a/src/fiat-cli b/src/fiat-cli index 4e1c61675..2838575f9 100755 --- a/src/fiat-cli +++ b/src/fiat-cli @@ -31,3 +31,5 @@ echo dsec; fiat/dsec $1 $2 $3 $4 echo glxt; fiat/glxt $1 $2 $3 $4 echo eql; fiat/eql $1 $2 $3 $4 echo zilla; fiat/zilla $1 $2 $3 $4 +echo vrsc; fiat/vrsc $1 $2 $3 $4 +echo rfox; fiat/rfox $1 $2 $3 $4 diff --git a/src/fiat/rfox b/src/fiat/rfox new file mode 100755 index 000000000..e082505d0 --- /dev/null +++ b/src/fiat/rfox @@ -0,0 +1,2 @@ +#!/bin/bash +./komodo-cli -ac_name=RFOX $1 $2 $3 $4 $5 $6 diff --git a/src/fiat/vrsc b/src/fiat/vrsc new file mode 100755 index 000000000..6cd4dd1fb --- /dev/null +++ b/src/fiat/vrsc @@ -0,0 +1,2 @@ +#!/bin/bash +./komodo-cli -ac_name=VRSC $1 $2 $3 $4 $5 $6 diff --git a/src/importcoin.cpp b/src/importcoin.cpp new file mode 100644 index 000000000..8b87cb535 --- /dev/null +++ b/src/importcoin.cpp @@ -0,0 +1,121 @@ +#include "crosschain.h" +#include "importcoin.h" +#include "cc/utils.h" +#include "coins.h" +#include "hash.h" +#include "script/cc.h" +#include "primitives/transaction.h" + + +CTransaction MakeImportCoinTransaction(const TxProof proof, const CTransaction burnTx, const std::vector payouts) +{ + std::vector payload = E_MARSHAL(ss << EVAL_IMPORTCOIN); + CMutableTransaction mtx; + mtx.vin.push_back(CTxIn(COutPoint(burnTx.GetHash(), 10e8), CScript() << payload)); + mtx.vout = payouts; + auto importData = E_MARSHAL(ss << proof; ss << burnTx); + mtx.vout.insert(mtx.vout.begin(), CTxOut(0, CScript() << OP_RETURN << importData)); + return CTransaction(mtx); +} + + +CTxOut MakeBurnOutput(CAmount value, uint32_t targetCCid, std::string targetSymbol, const std::vector payouts) +{ + std::vector opret = E_MARSHAL(ss << VARINT(targetCCid); + ss << targetSymbol; + ss << SerializeHash(payouts)); + return CTxOut(value, CScript() << OP_RETURN << opret); +} + + +bool UnmarshalImportTx(const CTransaction &importTx, TxProof &proof, CTransaction &burnTx, + std::vector &payouts) +{ + std::vector vData; + GetOpReturnData(importTx.vout[0].scriptPubKey, vData); + if (importTx.vout.size() < 1) return false; + payouts = std::vector(importTx.vout.begin()+1, importTx.vout.end()); + return importTx.vin.size() == 1 && + importTx.vin[0].scriptSig == (CScript() << E_MARSHAL(ss << EVAL_IMPORTCOIN)) && + E_UNMARSHAL(vData, ss >> proof; ss >> burnTx); +} + + +bool UnmarshalBurnTx(const CTransaction &burnTx, std::string &targetSymbol, uint32_t *targetCCid, uint256 &payoutsHash) +{ + std::vector burnOpret; + if (burnTx.vout.size() == 0) return false; + GetOpReturnData(burnTx.vout[0].scriptPubKey, burnOpret); + return E_UNMARSHAL(burnOpret, ss >> VARINT(*targetCCid); + ss >> targetSymbol; + ss >> payoutsHash); +} + + +/* + * Required by main + */ +CAmount GetCoinImportValue(const CTransaction &tx) +{ + TxProof proof; + CTransaction burnTx; + std::vector payouts; + if (UnmarshalImportTx(tx, proof, burnTx, payouts)) { + return burnTx.vout.size() ? burnTx.vout[0].nValue : 0; + } + return 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"); +} + + +void AddImportTombstone(const CTransaction &importTx, CCoinsViewCache &inputs, int nHeight) +{ + uint256 burnHash = importTx.vin[0].prevout.hash; + CCoinsModifier modifier = inputs.ModifyCoins(burnHash); + modifier->nHeight = nHeight; + modifier->nVersion = 1; + modifier->vout.push_back(CTxOut(0, CScript() << OP_0)); +} + + +void RemoveImportTombstone(const CTransaction &importTx, CCoinsViewCache &inputs) +{ + uint256 burnHash = importTx.vin[0].prevout.hash; + inputs.ModifyCoins(burnHash)->Clear(); +} + + +int ExistsImportTombstone(const CTransaction &importTx, const CCoinsViewCache &inputs) +{ + uint256 burnHash = importTx.vin[0].prevout.hash; + return inputs.HaveCoins(burnHash); +} diff --git a/src/importcoin.h b/src/importcoin.h new file mode 100644 index 000000000..f62bb45a1 --- /dev/null +++ b/src/importcoin.h @@ -0,0 +1,28 @@ +#ifndef IMPORTCOIN_H +#define IMPORTCOIN_H + +#include "cc/eval.h" +#include "coins.h" +#include "primitives/transaction.h" +#include "script/interpreter.h" +#include + + +CAmount GetCoinImportValue(const CTransaction &tx); + +CTransaction MakeImportCoinTransaction(const TxProof proof, + const CTransaction burnTx, const std::vector payouts); + +CTxOut MakeBurnOutput(CAmount value, uint32_t targetCCid, std::string targetSymbol, const std::vector payouts); + +bool UnmarshalBurnTx(const CTransaction &burnTx, std::string &targetSymbol, uint32_t *targetCCid, uint256 &payoutsHash); +bool UnmarshalImportTx(const CTransaction &importTx, TxProof &proof, CTransaction &burnTx, + std::vector &payouts); + +bool VerifyCoinImport(const CScript& scriptSig, TransactionSignatureChecker& checker, CValidationState &state); + +void AddImportTombstone(const CTransaction &importTx, CCoinsViewCache &inputs, int nHeight); +void RemoveImportTombstone(const CTransaction &importTx, CCoinsViewCache &inputs); +int ExistsImportTombstone(const CTransaction &importTx, const CCoinsViewCache &inputs); + +#endif /* IMPORTCOIN_H */ diff --git a/src/init.cpp b/src/init.cpp index aa55d1782..52c919421 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -21,6 +21,7 @@ #include "httpserver.h" #include "httprpc.h" #include "key.h" +#include "notarisationdb.h" #include "main.h" #include "metrics.h" #include "miner.h" @@ -1413,11 +1414,14 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) delete pcoinsdbview; delete pcoinscatcher; delete pblocktree; + delete pnotarisations; pblocktree = new CBlockTreeDB(nBlockTreeDBCache, false, fReindex, dbCompression, dbMaxOpenFiles); pcoinsdbview = new CCoinsViewDB(nCoinDBCache, false, fReindex); pcoinscatcher = new CCoinsViewErrorCatcher(pcoinsdbview); pcoinsTip = new CCoinsViewCache(pcoinscatcher); + pnotarisations = new NotarisationDB(100*1024*1024, false, fReindex); + if (fReindex) { pblocktree->WriteReindexing(true); diff --git a/src/komodo_bitcoind.h b/src/komodo_bitcoind.h index be0f07c5a..5a41c2bfc 100644 --- a/src/komodo_bitcoind.h +++ b/src/komodo_bitcoind.h @@ -1097,10 +1097,10 @@ uint32_t komodo_stake(int32_t validateflag,arith_uint256 bnTarget,int32_t nHeigh fprintf(stderr,"komodo_stake null %.8f %u %u %u\n",dstr(value),txtime,blocktime,prevtime); return(0); } - if ( (minage= nHeight*3) > 6000 ) + if ( (minage= nHeight*3) > 6000 ) // about 100 blocks minage = 6000; pindex = 0; - if ( (pindex= komodo_chainactive(nHeight>200?nHeight-200:1)) != 0 ) + if ( (pindex= komodo_chainactive(nHeight>50?nHeight-50:1)) != 0 ) { vcalc_sha256(0,(uint8_t *)&addrhash,(uint8_t *)address,(int32_t)strlen(address)); segid = ((nHeight + addrhash.uints[0]) & 0x3f); @@ -1296,6 +1296,8 @@ int64_t komodo_checkcommission(CBlock *pblock,int32_t height) return(checktoshis); } +bool KOMODO_TEST_ASSETCHAIN_SKIP_POW = 0; + int32_t komodo_checkPOW(int32_t slowflag,CBlock *pblock,int32_t height) { uint256 hash; arith_uint256 bnTarget,bhash; bool fNegative,fOverflow; uint8_t *script,pubkey33[33],pubkeys[64][33]; int32_t i,possible,PoSperc,is_PoSblock=0,n,failed = 0,notaryid = -1; int64_t checktoshis,value; CBlockIndex *pprev; @@ -1335,6 +1337,7 @@ int32_t komodo_checkPOW(int32_t slowflag,CBlock *pblock,int32_t height) } else if ( possible == 0 || ASSETCHAINS_SYMBOL[0] != 0 ) { + if (KOMODO_TEST_ASSETCHAIN_SKIP_POW) return(0); fprintf(stderr,"pow violation and no chance it is notary ht.%d %s\n",height,hash.ToString().c_str()); return(-1); } diff --git a/src/komodo_ccdata.h b/src/komodo_ccdata.h index 6e7d0fabe..012183fc7 100644 --- a/src/komodo_ccdata.h +++ b/src/komodo_ccdata.h @@ -19,54 +19,29 @@ struct komodo_ccdata *CC_data; int32_t CC_firstheight; -bits256 iguana_merkle(bits256 *tree,int32_t txn_count) -{ - int32_t i,n=0,prev; uint8_t serialized[sizeof(bits256) * 2]; - if ( txn_count == 1 ) - return(tree[0]); - prev = 0; - while ( txn_count > 1 ) - { - if ( (txn_count & 1) != 0 ) - tree[prev + txn_count] = tree[prev + txn_count-1], txn_count++; - n += txn_count; - for (i=0; i> 1)] = bits256_doublesha256(0,serialized,sizeof(serialized)); - } - prev = n; - txn_count >>= 1; - } - return(tree[n]); -} +uint256 BuildMerkleTree(bool* fMutated, const std::vector leaves, std::vector &vMerkleTree); uint256 komodo_calcMoM(int32_t height,int32_t MoMdepth) { - static uint256 zero; bits256 MoM,*tree; CBlockIndex *pindex; int32_t i; - MoMdepth &= 0xffff; + static uint256 zero; CBlockIndex *pindex; int32_t i; std::vector tree, leaves; + bool fMutated; + MoMdepth &= 0xffff; // In case it includes the ccid if ( MoMdepth >= height ) return(zero); - tree = (bits256 *)calloc(MoMdepth * 3,sizeof(*tree)); for (i=0; ihashMerkleRoot,sizeof(bits256)); + leaves.push_back(pindex->hashMerkleRoot); else - { - free(tree); return(zero); - } } - MoM = iguana_merkle(tree,MoMdepth); - free(tree); - return(*(uint256 *)&MoM); + return BuildMerkleTree(&fMutated, leaves, tree); } struct komodo_ccdata_entry *komodo_allMoMs(int32_t *nump,uint256 *MoMoMp,int32_t kmdstarti,int32_t kmdendi) { - struct komodo_ccdata_entry *allMoMs=0; bits256 *tree,tmp; struct komodo_ccdata *ccdata,*tmpptr; int32_t i,num,max; + struct komodo_ccdata_entry *allMoMs=0; struct komodo_ccdata *ccdata,*tmpptr; int32_t i,num,max; + bool fMutated; std::vector tree, leaves; num = max = 0; portable_mutex_lock(&KOMODO_CC_mutex); DL_FOREACH_SAFE(CC_data,ccdata,tmpptr) @@ -91,11 +66,9 @@ struct komodo_ccdata_entry *komodo_allMoMs(int32_t *nump,uint256 *MoMoMp,int32_t portable_mutex_unlock(&KOMODO_CC_mutex); if ( (*nump= num) > 0 ) { - tree = (bits256 *)calloc(sizeof(bits256),num*3); for (i=0; iNUM_NPOINTS-1; i>=0; i--) { + *idx = i; np = &sp->NPOINTS[i]; if ( np->MoMdepth != 0 && height > np->notarized_height-(np->MoMdepth&0xffff) && height <= np->notarized_height ) return(np); } } + *idx = -1; + return(0); +} + +struct notarized_checkpoint *komodo_npptr(int32_t height) +{ + int idx; + return komodo_npptr_for_height(height, &idx); +} + +struct notarized_checkpoint *komodo_npptr_at(int idx) +{ + char symbol[KOMODO_ASSETCHAIN_MAXLEN],dest[KOMODO_ASSETCHAIN_MAXLEN]; struct komodo_state *sp; + if ( (sp= komodo_stateptr(symbol,dest)) != 0 ) + if (idx < sp->NUM_NPOINTS) + return &sp->NPOINTS[idx]; return(0); } diff --git a/src/main.cpp b/src/main.cpp index c9a798e4d..98fdf9452 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,6 +10,7 @@ #include "addrman.h" #include "alert.h" #include "arith_uint256.h" +#include "importcoin.h" #include "chainparams.h" #include "checkpoints.h" #include "checkqueue.h" @@ -19,8 +20,10 @@ #include "init.h" #include "merkleblock.h" #include "metrics.h" +#include "notarisationdb.h" #include "net.h" #include "pow.h" +#include "script/interpreter.h" #include "txdb.h" #include "txmempool.h" #include "ui_interface.h" @@ -826,7 +829,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]); @@ -897,7 +903,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; @@ -954,8 +960,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; @@ -1010,7 +1016,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"), @@ -1068,6 +1074,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"); @@ -1176,7 +1183,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) @@ -1293,7 +1300,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 @@ -1338,28 +1345,37 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa 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)) + // Inverse of normal case; if input exists, it's been spent + if (ExistsImportTombstone(tx, view)) + return state.Invalid(false, REJECT_DUPLICATE, "import tombstone exists"); + } + else + { + // 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)) @@ -1402,11 +1418,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; + } } } @@ -1487,6 +1505,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"); @@ -1976,7 +1995,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) { @@ -2002,6 +2021,13 @@ void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, CTxUndo &txund } } inputs.ModifyCoins(tx.GetHash())->FromTx(tx, nHeight); // add outputs + + // Unorthodox state + if (tx.IsCoinImport()) { + // add a tombstone for the burnTx + AddImportTombstone(tx, inputs, nHeight); + } + } void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, int nHeight) @@ -2012,7 +2038,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; @@ -2122,7 +2149,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; @@ -2174,7 +2201,13 @@ bool ContextualCheckInputs( } } } - + + if (tx.IsCoinImport()) + { + ServerTransactionSignatureChecker checker(&tx, 0, 0, false, txdata); + return VerifyCoinImport(tx.vin[0].scriptSig, checker, state); + } + return true; } @@ -2325,6 +2358,37 @@ static bool ApplyTxInUndo(const CTxInUndo& undo, CCoinsViewCache& view, const CO return fClean; } + +void ConnectNotarisations(const CBlock &block, int height) +{ + // Record Notarisations + NotarisationsInBlock notarisations = ScanBlockNotarisations(block, height); + if (notarisations.size() > 0) { + CLevelDBBatch batch; + batch.Write(block.GetHash(), notarisations); + WriteBackNotarisations(notarisations, batch); + pnotarisations->WriteBatch(batch, true); + LogPrintf("ConnectBlock: wrote %i block notarisations in block: %s\n", + notarisations.size(), block.GetHash().GetHex().data()); + } +} + + +void DisconnectNotarisations(const CBlock &block) +{ + // Delete from notarisations cache + NotarisationsInBlock nibs; + if (GetBlockNotarisations(block.GetHash(), nibs)) { + CLevelDBBatch batch; + batch.Erase(block.GetHash()); + EraseBackNotarisations(nibs, batch); + pnotarisations->WriteBatch(batch, true); + LogPrintf("DisconnectTip: deleted %i block notarisations in block: %s\n", + nibs.size(), block.GetHash().GetHex().data()); + } +} + + bool DisconnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex, CCoinsViewCache& view, bool* pfClean) { assert(pindex->GetBlockHash() == view.GetBestBlock()); @@ -2365,7 +2429,8 @@ bool DisconnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex // undo unspent index addressUnspentIndex.push_back(make_pair(CAddressUnspentKey(2, uint160(hashBytes), hash, k), CAddressUnspentValue())); - } else if (out.scriptPubKey.IsPayToPublicKeyHash()) { + } + else if (out.scriptPubKey.IsPayToPublicKeyHash()) { vector hashBytes(out.scriptPubKey.begin()+3, out.scriptPubKey.begin()+23); // undo receiving activity @@ -2374,7 +2439,18 @@ bool DisconnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex // undo unspent index addressUnspentIndex.push_back(make_pair(CAddressUnspentKey(1, uint160(hashBytes), hash, k), CAddressUnspentValue())); - } else { + } + else if (out.scriptPubKey.IsPayToPublicKey()) { + vector hashBytes(out.scriptPubKey.begin()+1, out.scriptPubKey.begin()+34); + + // undo receiving activity + addressIndex.push_back(make_pair(CAddressIndexKey(1, Hash160(hashBytes), pindex->nHeight, i, hash, k, false), out.nValue)); + + // undo unspent index + addressUnspentIndex.push_back(make_pair(CAddressUnspentKey(1, Hash160(hashBytes), hash, k), CAddressUnspentValue())); + + } + else { continue; } @@ -2409,7 +2485,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"); @@ -2438,7 +2514,8 @@ bool DisconnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex addressUnspentIndex.push_back(make_pair(CAddressUnspentKey(2, uint160(hashBytes), input.prevout.hash, input.prevout.n), CAddressUnspentValue(prevout.nValue, prevout.scriptPubKey, undo.nHeight))); - } else if (prevout.scriptPubKey.IsPayToPublicKeyHash()) { + } + else if (prevout.scriptPubKey.IsPayToPublicKeyHash()) { vector hashBytes(prevout.scriptPubKey.begin()+3, prevout.scriptPubKey.begin()+23); // undo spending activity @@ -2447,14 +2524,29 @@ bool DisconnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex // restore unspent index addressUnspentIndex.push_back(make_pair(CAddressUnspentKey(1, uint160(hashBytes), input.prevout.hash, input.prevout.n), CAddressUnspentValue(prevout.nValue, prevout.scriptPubKey, undo.nHeight))); - } else { + } + else if (prevout.scriptPubKey.IsPayToPublicKey()) { + vector hashBytes(prevout.scriptPubKey.begin()+1, prevout.scriptPubKey.begin()+34); + + // undo spending activity + addressIndex.push_back(make_pair(CAddressIndexKey(1, Hash160(hashBytes), pindex->nHeight, i, hash, j, true), prevout.nValue * -1)); + + // restore unspent index + addressUnspentIndex.push_back(make_pair(CAddressUnspentKey(1, Hash160(hashBytes), input.prevout.hash, input.prevout.n), CAddressUnspentValue(prevout.nValue, prevout.scriptPubKey, undo.nHeight))); + + } + else { continue; } } } } + else if (tx.IsCoinImport()) + { + RemoveImportTombstone(tx, view); + } } - + // set the old best anchor back view.PopAnchor(blockUndo.old_tree_root); @@ -2474,6 +2566,7 @@ bool DisconnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex return AbortNode(state, "Failed to write address unspent index"); } } + return fClean; } @@ -2571,6 +2664,7 @@ void PartitionCheck(bool (*initialDownloadCheck)(), CCriticalSection& cs, const } } + static int64_t nTimeVerify = 0; static int64_t nTimeConnect = 0; static int64_t nTimeIndex = 0; @@ -2690,7 +2784,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)) { @@ -2713,10 +2807,16 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin if (prevout.scriptPubKey.IsPayToScriptHash()) { hashBytes = uint160(vector (prevout.scriptPubKey.begin()+2, prevout.scriptPubKey.begin()+22)); addressType = 2; - } else if (prevout.scriptPubKey.IsPayToPublicKeyHash()) { + } + else if (prevout.scriptPubKey.IsPayToPublicKeyHash()) { hashBytes = uint160(vector (prevout.scriptPubKey.begin()+3, prevout.scriptPubKey.begin()+23)); addressType = 1; - } else { + } + else if (prevout.scriptPubKey.IsPayToPublicKey()) { + hashBytes = Hash160(vector (prevout.scriptPubKey.begin()+1, prevout.scriptPubKey.begin()+34)); + addressType = 1; + } + else { hashBytes.SetNull(); addressType = 0; } @@ -2762,7 +2862,7 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin if (fAddressIndex) { for (unsigned int k = 0; k < tx.vout.size(); k++) { const CTxOut &out = tx.vout[k]; - +//fprintf(stderr,"add %d vouts\n",(int32_t)tx.vout.size()); if (out.scriptPubKey.IsPayToScriptHash()) { vector hashBytes(out.scriptPubKey.begin()+2, out.scriptPubKey.begin()+22); @@ -2772,7 +2872,8 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin // record unspent output addressUnspentIndex.push_back(make_pair(CAddressUnspentKey(2, uint160(hashBytes), txhash, k), CAddressUnspentValue(out.nValue, out.scriptPubKey, pindex->nHeight))); - } else if (out.scriptPubKey.IsPayToPublicKeyHash()) { + } + else if (out.scriptPubKey.IsPayToPublicKeyHash()) { vector hashBytes(out.scriptPubKey.begin()+3, out.scriptPubKey.begin()+23); // record receiving activity @@ -2781,7 +2882,18 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin // record unspent output addressUnspentIndex.push_back(make_pair(CAddressUnspentKey(1, uint160(hashBytes), txhash, k), CAddressUnspentValue(out.nValue, out.scriptPubKey, pindex->nHeight))); - } else { + } + else if (out.scriptPubKey.IsPayToPublicKey()) { + vector hashBytes(out.scriptPubKey.begin()+1, out.scriptPubKey.begin()+34); + + // record receiving activity + addressIndex.push_back(make_pair(CAddressIndexKey(1, Hash160(hashBytes), pindex->nHeight, i, txhash, k, false), out.nValue)); + + // record unspent output + addressUnspentIndex.push_back(make_pair(CAddressUnspentKey(1, Hash160(hashBytes), txhash, k), CAddressUnspentValue(out.nValue, out.scriptPubKey, pindex->nHeight))); + + } + else { continue; } @@ -2807,7 +2919,7 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin vPos.push_back(std::make_pair(tx.GetHash(), pos)); pos.nTxOffset += ::GetSerializeSize(tx, SER_DISK, CLIENT_VERSION); } - + view.PushAnchor(tree); if (!fJustCheck) { pindex->hashAnchorEnd = tree.root(); @@ -2876,6 +2988,8 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin pindex->RaiseValidity(BLOCK_VALID_SCRIPTS); setDirtyBlockIndex.insert(pindex); } + + ConnectNotarisations(block, pindex->nHeight); if (fTxIndex) if (!pblocktree->WriteTxIndex(vPos)) @@ -3113,6 +3227,7 @@ bool static DisconnectTip(CValidationState &state, bool fBare = false) { if (!DisconnectBlock(block, state, pindexDelete, view)) return error("DisconnectTip(): DisconnectBlock %s failed", pindexDelete->GetBlockHash().ToString()); assert(view.Flush()); + DisconnectNotarisations(block); } LogPrint("bench", "- Disconnect block: %.2fms\n", (GetTimeMicros() - nStart) * 0.001); uint256 anchorAfterDisconnect = pcoinsTip->GetBestAnchor(); diff --git a/src/miner.cpp b/src/miner.cpp index 5f753f267..ec410384f 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -11,6 +11,7 @@ #include "amount.h" #include "base58.h" #include "chainparams.h" +#include "importcoin.h" #include "consensus/consensus.h" #include "consensus/upgrades.h" #include "consensus/validation.h" @@ -230,48 +231,55 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn,int32_t gpucount) 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/notarisationdb.cpp b/src/notarisationdb.cpp new file mode 100644 index 000000000..6210d88dd --- /dev/null +++ b/src/notarisationdb.cpp @@ -0,0 +1,84 @@ +#include "leveldbwrapper.h" +#include "notarisationdb.h" +#include "uint256.h" +#include "cc/eval.h" + +#include + + +NotarisationDB *pnotarisations; + + +NotarisationDB::NotarisationDB(size_t nCacheSize, bool fMemory, bool fWipe) : CLevelDBWrapper(GetDataDir() / "notarisations", nCacheSize, fMemory, fWipe, false, 64) { } + + +NotarisationsInBlock ScanBlockNotarisations(const CBlock &block, int nHeight) +{ + EvalRef eval; + NotarisationsInBlock vNotarisations; + + for (unsigned int i = 0; i < block.vtx.size(); i++) { + CTransaction tx = block.vtx[i]; + + // Special case for TXSCL. Should prob be removed at some point. + bool isTxscl = 0; + { + NotarisationData data; + if (ParseNotarisationOpReturn(tx, data)) + if (strlen(data.symbol) >= 5 && strncmp(data.symbol, "TXSCL", 5) == 0) + isTxscl = 1; + } + + if (isTxscl || eval->CheckNotaryInputs(tx, nHeight, block.nTime)) { + NotarisationData data; + if (ParseNotarisationOpReturn(tx, data)) { + vNotarisations.push_back(std::make_pair(tx.GetHash(), data)); + //printf("Parsed a notarisation for: %s, txid:%s, ccid:%i, momdepth:%i\n", + // data.symbol, tx.GetHash().GetHex().data(), data.ccId, data.MoMDepth); + //if (!data.MoMoM.IsNull()) printf("MoMoM:%s\n", data.MoMoM.GetHex().data()); + } + else + LogPrintf("WARNING: Couldn't parse notarisation for tx: %s at height %i\n", + tx.GetHash().GetHex().data(), nHeight); + } + } + return vNotarisations; +} + + +bool GetBlockNotarisations(uint256 blockHash, NotarisationsInBlock &nibs) +{ + return pnotarisations->Read(blockHash, nibs); +} + + +bool GetBackNotarisation(uint256 notarisationHash, Notarisation &n) +{ + return pnotarisations->Read(notarisationHash, n); +} + + +/* + * Write an index of KMD notarisation id -> backnotarisation + */ +void WriteBackNotarisations(const NotarisationsInBlock notarisations, CLevelDBBatch &batch) +{ + int wrote = 0; + BOOST_FOREACH(const Notarisation &n, notarisations) + { + if (!n.second.txHash.IsNull()) { + batch.Write(n.second.txHash, n); + wrote++; + } + } +} + + +void EraseBackNotarisations(const NotarisationsInBlock notarisations, CLevelDBBatch &batch) +{ + BOOST_FOREACH(const Notarisation &n, notarisations) + { + if (!n.second.txHash.IsNull()) + batch.Erase(n.second.txHash); + } +} diff --git a/src/notarisationdb.h b/src/notarisationdb.h new file mode 100644 index 000000000..ce5360e7d --- /dev/null +++ b/src/notarisationdb.h @@ -0,0 +1,27 @@ +#ifndef NOTARISATIONDB_H +#define NOTARISATIONDB_H + +#include "uint256.h" +#include "leveldbwrapper.h" +#include "cc/eval.h" + + +class NotarisationDB : public CLevelDBWrapper +{ +public: + NotarisationDB(size_t nCacheSize, bool fMemory = false, bool fWipe = false); +}; + + +extern NotarisationDB *pnotarisations; + +typedef std::pair Notarisation; +typedef std::vector NotarisationsInBlock; + +NotarisationsInBlock ScanBlockNotarisations(const CBlock &block, int nHeight); +bool GetBlockNotarisations(uint256 blockHash, NotarisationsInBlock &nibs); +bool GetBackNotarisation(uint256 notarisationHash, Notarisation &n); +void WriteBackNotarisations(const NotarisationsInBlock notarisations, CLevelDBBatch &batch); +void EraseBackNotarisations(const NotarisationsInBlock notarisations, CLevelDBBatch &batch); + +#endif /* NOTARISATIONDB_H */ diff --git a/src/primitives/block.cpp b/src/primitives/block.cpp index c2300c82a..8543a601e 100644 --- a/src/primitives/block.cpp +++ b/src/primitives/block.cpp @@ -15,7 +15,9 @@ uint256 CBlockHeader::GetHash() const return SerializeHash(*this); } -uint256 CBlock::BuildMerkleTree(bool* fMutated) const + +uint256 BuildMerkleTree(bool* fMutated, const std::vector leaves, + std::vector &vMerkleTree) { /* WARNING! If you're reading this because you're learning about crypto and/or designing a new system that will use merkle trees, keep in mind @@ -28,10 +30,10 @@ uint256 CBlock::BuildMerkleTree(bool* fMutated) const transactions leading to the same merkle root. For example, these two trees: - A A - / \ / \ - B C B C - / \ | / \ / \ + A A + / \ / \ + B C B C + / \ \ / \ / \ D E F D E F F / \ / \ / \ / \ / \ / \ / \ 1 2 3 4 5 6 1 2 3 4 5 6 5 6 @@ -52,13 +54,14 @@ uint256 CBlock::BuildMerkleTree(bool* fMutated) const known ways of changing the transactions without affecting the merkle root. */ + vMerkleTree.clear(); - vMerkleTree.reserve(vtx.size() * 2 + 16); // Safe upper bound for the number of total nodes. - for (std::vector::const_iterator it(vtx.begin()); it != vtx.end(); ++it) - vMerkleTree.push_back(it->GetHash()); + vMerkleTree.reserve(leaves.size() * 2 + 16); // Safe upper bound for the number of total nodes. + for (std::vector::const_iterator it(leaves.begin()); it != leaves.end(); ++it) + vMerkleTree.push_back(*it); int j = 0; bool mutated = false; - for (int nSize = vtx.size(); nSize > 1; nSize = (nSize + 1) / 2) + for (int nSize = leaves.size(); nSize > 1; nSize = (nSize + 1) / 2) { for (int i = 0; i < nSize; i += 2) { @@ -78,13 +81,20 @@ uint256 CBlock::BuildMerkleTree(bool* fMutated) const return (vMerkleTree.empty() ? uint256() : vMerkleTree.back()); } -std::vector CBlock::GetMerkleBranch(int nIndex) const + +uint256 CBlock::BuildMerkleTree(bool* fMutated) const +{ + std::vector leaves; + for (int i=0; i GetMerkleBranch(int nIndex, int nLeaves, const std::vector &vMerkleTree) { - if (vMerkleTree.empty()) - BuildMerkleTree(); std::vector vMerkleBranch; int j = 0; - for (int nSize = vtx.size(); nSize > 1; nSize = (nSize + 1) / 2) + for (int nSize = nLeaves; nSize > 1; nSize = (nSize + 1) / 2) { int i = std::min(nIndex^1, nSize-1); vMerkleBranch.push_back(vMerkleTree[j+i]); @@ -94,6 +104,15 @@ std::vector CBlock::GetMerkleBranch(int nIndex) const return vMerkleBranch; } + +std::vector CBlock::GetMerkleBranch(int nIndex) const +{ + if (vMerkleTree.empty()) + BuildMerkleTree(); + return ::GetMerkleBranch(nIndex, vtx.size(), vMerkleTree); +} + + uint256 CBlock::CheckMerkleBranch(uint256 hash, const std::vector& vMerkleBranch, int nIndex) { if (nIndex == -1) diff --git a/src/primitives/block.h b/src/primitives/block.h index 6b3f13a86..6bc06f426 100644 --- a/src/primitives/block.h +++ b/src/primitives/block.h @@ -139,6 +139,12 @@ public: }; +uint256 BuildMerkleTree(bool* fMutated, const std::vector leaves, + std::vector &vMerkleTree); + +std::vector GetMerkleBranch(int nIndex, int nLeaves, const std::vector &vMerkleTree); + + /** * Custom serializer for CBlockHeader that omits the nonce and solution, for use * as input to Equihash. diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index 35c12abc8..88ee9a312 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 IsCoinImport() || IsCoinBase(); + } + bool IsCoinBase() const { return (vin.size() == 1 && vin[0].prevout.IsNull()); } + bool IsCoinImport() const + { + return (vin.size() == 1 && 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 cfb89815b..42cf7f2b0 100644 --- a/src/rpcblockchain.cpp +++ b/src/rpcblockchain.cpp @@ -7,9 +7,10 @@ #include "chain.h" #include "chainparams.h" #include "checkpoints.h" +#include "crosschain.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" @@ -171,9 +172,11 @@ UniValue blockToDeltasJSON(const CBlock& block, const CBlockIndex* blockindex) if (GetSpentIndex(spentKey, spentInfo)) { if (spentInfo.addressType == 1) { delta.push_back(Pair("address", CBitcoinAddress(CKeyID(spentInfo.addressHash)).ToString())); - } else if (spentInfo.addressType == 2) { + } + else if (spentInfo.addressType == 2) { delta.push_back(Pair("address", CBitcoinAddress(CScriptID(spentInfo.addressHash)).ToString())); - } else { + } + else { continue; } delta.push_back(Pair("satoshis", -1 * spentInfo.satoshis)); @@ -202,10 +205,21 @@ UniValue blockToDeltasJSON(const CBlock& block, const CBlockIndex* blockindex) vector hashBytes(out.scriptPubKey.begin()+2, out.scriptPubKey.begin()+22); delta.push_back(Pair("address", CBitcoinAddress(CScriptID(uint160(hashBytes))).ToString())); - } else if (out.scriptPubKey.IsPayToPublicKeyHash()) { + } + else if (out.scriptPubKey.IsPayToPublicKeyHash()) { vector hashBytes(out.scriptPubKey.begin()+3, out.scriptPubKey.begin()+23); delta.push_back(Pair("address", CBitcoinAddress(CKeyID(uint160(hashBytes))).ToString())); - } else { + } + else if (out.scriptPubKey.IsPayToPublicKey()) { + CTxDestination address; + if (ExtractDestination(out.scriptPubKey, address)) + { + //vector hashBytes(out.scriptPubKey.begin()+1, out.scriptPubKey.begin()+34); + //xxx delta.push_back(Pair("address", CBitcoinAddress(CKeyID(uint160(hashBytes))).ToString())); + delta.push_back(Pair("address", CBitcoinAddress(address).ToString())); + } + } + else { continue; } @@ -758,10 +772,6 @@ int32_t komodo_notaries(uint8_t pubkeys[64][33],int32_t height,uint32_t timestam char *bitcoin_address(char *coinaddr,uint8_t addrtype,uint8_t *pubkey_or_rmd160,int32_t len); int32_t komodo_minerids(uint8_t *minerids,int32_t height,int32_t width); int32_t komodo_kvsearch(uint256 *refpubkeyp,int32_t current_height,uint32_t *flagsp,int32_t *heightp,uint8_t value[IGUANA_MAXSCRIPTSIZE],uint8_t *key,int32_t keylen); -int32_t komodo_MoM(int32_t *notarized_htp,uint256 *MoMp,uint256 *kmdtxidp,int32_t nHeight,uint256 *MoMoMp,int32_t *MoMoMoffsetp,int32_t *MoMoMdepthp,int32_t *kmdstartip,int32_t *kmdendip); -int32_t komodo_MoMoMdata(char *hexstr,int32_t hexsize,struct komodo_ccdataMoMoM *mdata,char *symbol,int32_t kmdheight,int32_t notarized_height); -struct komodo_ccdata_entry *komodo_allMoMs(int32_t *nump,uint256 *MoMoMp,int32_t kmdstarti,int32_t kmdendi); -uint256 komodo_calcMoM(int32_t height,int32_t MoMdepth); UniValue kvsearch(const UniValue& params, bool fHelp) { @@ -819,221 +829,6 @@ UniValue kvsearch(const UniValue& params, bool fHelp) return ret; } -UniValue allMoMs(const UniValue& params, bool fHelp) -{ - struct komodo_ccdata_entry *allMoMs; uint256 MoMoM; int32_t num,i,kmdstarti,kmdendi; UniValue ret(UniValue::VOBJ); UniValue a(UniValue::VARR); - if ( fHelp || params.size() != 2 ) - throw runtime_error("allMoMs kmdstarti kmdendi\n"); - LOCK(cs_main); - kmdstarti = atoi(params[0].get_str().c_str()); - kmdendi = atoi(params[1].get_str().c_str()); - ret.push_back(Pair("kmdstarti",kmdstarti)); - ret.push_back(Pair("kmdendi",kmdendi)); - if ( (allMoMs= komodo_allMoMs(&num,&MoMoM,kmdstarti,kmdendi)) != 0 ) - { - for (i=0; i= height ) - throw runtime_error("calc_MoM illegal height or MoMdepth\n"); - //fprintf(stderr,"height_MoM height.%d\n",height); - MoM = komodo_calcMoM(height,MoMdepth); - ret.push_back(Pair("coin",(char *)(ASSETCHAINS_SYMBOL[0] == 0 ? "KMD" : ASSETCHAINS_SYMBOL))); - ret.push_back(Pair("height",height)); - ret.push_back(Pair("MoMdepth",MoMdepth)); - ret.push_back(Pair("MoM",MoM.GetHex())); - return ret; -} - -UniValue height_MoM(const UniValue& params, bool fHelp) -{ - int32_t height,depth,notarized_height,MoMoMdepth,MoMoMoffset,kmdstarti,kmdendi; uint256 MoM,MoMoM,kmdtxid; uint32_t timestamp = 0; UniValue ret(UniValue::VOBJ); UniValue a(UniValue::VARR); - if ( fHelp || params.size() != 1 ) - throw runtime_error("height_MoM height\n"); - LOCK(cs_main); - height = atoi(params[0].get_str().c_str()); - if ( height <= 0 ) - { - if ( chainActive.Tip() == 0 ) - { - ret.push_back(Pair("error",(char *)"no active chain yet")); - return(ret); - } - height = chainActive.Tip()->nHeight; - } - //fprintf(stderr,"height_MoM height.%d\n",height); - depth = komodo_MoM(¬arized_height,&MoM,&kmdtxid,height,&MoMoM,&MoMoMoffset,&MoMoMdepth,&kmdstarti,&kmdendi); - ret.push_back(Pair("coin",(char *)(ASSETCHAINS_SYMBOL[0] == 0 ? "KMD" : ASSETCHAINS_SYMBOL))); - ret.push_back(Pair("height",height)); - ret.push_back(Pair("timestamp",(uint64_t)timestamp)); - if ( depth > 0 ) - { - ret.push_back(Pair("depth",depth)); - ret.push_back(Pair("notarized_height",notarized_height)); - ret.push_back(Pair("MoM",MoM.GetHex())); - ret.push_back(Pair("kmdtxid",kmdtxid.GetHex())); - if ( ASSETCHAINS_SYMBOL[0] != 0 ) - { - ret.push_back(Pair("MoMoM",MoMoM.GetHex())); - ret.push_back(Pair("MoMoMoffset",MoMoMoffset)); - ret.push_back(Pair("MoMoMdepth",MoMoMdepth)); - ret.push_back(Pair("kmdstarti",kmdstarti)); - ret.push_back(Pair("kmdendi",kmdendi)); - } - } else ret.push_back(Pair("error",(char *)"no MoM for height")); - - return ret; -} - -UniValue txMoMproof(const UniValue& params, bool fHelp) -{ - uint256 hash, notarisationHash, MoM,MoMoM; int32_t notarisedHeight, depth; CBlockIndex* blockIndex; - std::vector branch; - int nIndex,MoMoMdepth,MoMoMoffset,kmdstarti,kmdendi; - - // parse params and get notarisation data for tx - { - if ( fHelp || params.size() != 1) - throw runtime_error("txMoMproof needs a txid"); - - hash = uint256S(params[0].get_str()); - - uint256 blockHash; - CTransaction tx; - if (!GetTransaction(hash, tx, blockHash, true)) - throw runtime_error("cannot find transaction"); - - blockIndex = mapBlockIndex[blockHash]; - - depth = komodo_MoM(¬arisedHeight, &MoM, ¬arisationHash, blockIndex->nHeight,&MoMoM,&MoMoMoffset,&MoMoMdepth,&kmdstarti,&kmdendi); - - if (!depth) - throw runtime_error("notarisation not found"); - - // index of block in MoM leaves - nIndex = notarisedHeight - blockIndex->nHeight; - } - - // build merkle chain from blocks to MoM - { - // since the merkle branch code is tied up in a block class - // and we want to make a merkle branch for something that isnt transactions - CBlock fakeBlock; - for (int i=0; ihashMerkleRoot; - CTransaction fakeTx; - // first value in CTransaction memory is it's hash - memcpy((void*)&fakeTx, mRoot.begin(), 32); - fakeBlock.vtx.push_back(fakeTx); - } - branch = fakeBlock.GetMerkleBranch(nIndex); - - // Check branch - if (MoM != CBlock::CheckMerkleBranch(blockIndex->hashMerkleRoot, branch, nIndex)) - throw JSONRPCError(RPC_INTERNAL_ERROR, "Failed merkle block->MoM"); - } - - // Now get the tx merkle branch - { - CBlock block; - - if (fHavePruned && !(blockIndex->nStatus & BLOCK_HAVE_DATA) && blockIndex->nTx > 0) - throw JSONRPCError(RPC_INTERNAL_ERROR, "Block not available (pruned data)"); - - if(!ReadBlockFromDisk(block, blockIndex,1)) - throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk"); - - // Locate the transaction in the block - int nTxIndex; - for (nTxIndex = 0; nTxIndex < (int)block.vtx.size(); nTxIndex++) - if (block.vtx[nTxIndex].GetHash() == hash) - break; - - if (nTxIndex == (int)block.vtx.size()) - throw JSONRPCError(RPC_INTERNAL_ERROR, "Error locating tx in block"); - - std::vector txBranch = block.GetMerkleBranch(nTxIndex); - - // Check branch - if (block.hashMerkleRoot != CBlock::CheckMerkleBranch(hash, txBranch, nTxIndex)) - throw JSONRPCError(RPC_INTERNAL_ERROR, "Failed merkle tx->block"); - - // concatenate branches - nIndex = (nIndex << txBranch.size()) + nTxIndex; - branch.insert(branch.begin(), txBranch.begin(), txBranch.end()); - } - - // Check the proof - if (MoM != CBlock::CheckMerkleBranch(hash, branch, nIndex)) - throw JSONRPCError(RPC_INTERNAL_ERROR, "Failed validating MoM"); - - // Encode and return - CDataStream ssProof(SER_NETWORK, PROTOCOL_VERSION); - ssProof << MoMProof(nIndex, branch, notarisationHash); - return HexStr(ssProof.begin(), ssProof.end()); -} - UniValue minerids(const UniValue& params, bool fHelp) { uint32_t timestamp = 0; UniValue ret(UniValue::VOBJ); UniValue a(UniValue::VARR); uint8_t minerids[2000],pubkeys[65][33]; int32_t i,j,n,numnotaries,tally[129]; diff --git a/src/rpcclient.cpp b/src/rpcclient.cpp index 5b3f0c9eb..9d1d0b336 100644 --- a/src/rpcclient.cpp +++ b/src/rpcclient.cpp @@ -134,17 +134,20 @@ static const CRPCConvertParam vRPCConvertParams[] = { "paxprices", 3 }, { "paxpending", 0 }, { "notaries", 2 }, - { "height_MoM", 1 }, - { "MoMoMdata", 3 }, - { "allMoMs", 2 }, - { "txMoMproof", 1 }, { "minerids", 1 }, { "kvsearch", 1 }, { "kvupdate", 4 }, { "z_importkey", 2 }, { "z_importviewingkey", 2 }, { "z_getpaymentdisclosure", 1}, - { "z_getpaymentdisclosure", 2} + { "z_getpaymentdisclosure", 2}, + + // crosschain + { "assetchainproof", 1}, + { "crosschainproof", 1}, + { "getproofroot", 2}, + { "height_MoM", 1}, + { "calc_MoM", 2}, }; class CRPCConvertTable diff --git a/src/rpccrosschain.cpp b/src/rpccrosschain.cpp new file mode 100644 index 000000000..35542613a --- /dev/null +++ b/src/rpccrosschain.cpp @@ -0,0 +1,253 @@ +#include "amount.h" +#include "chain.h" +#include "chainparams.h" +#include "checkpoints.h" +#include "crosschain.h" +#include "importcoin.h" +#include "base58.h" +#include "consensus/validation.h" +#include "cc/eval.h" +#include "cc/utils.h" +#include "main.h" +#include "primitives/transaction.h" +#include "rpcserver.h" +#include "sync.h" +#include "util.h" +#include "script/script.h" +#include "script/script_error.h" +#include "script/sign.h" +#include "script/standard.h" + +#include +#include +#include + + +using namespace std; + +int32_t komodo_MoM(int32_t *notarized_htp,uint256 *MoMp,uint256 *kmdtxidp,int32_t nHeight,uint256 *MoMoMp,int32_t *MoMoMoffsetp,int32_t *MoMoMdepthp,int32_t *kmdstartip,int32_t *kmdendip); +int32_t komodo_MoMoMdata(char *hexstr,int32_t hexsize,struct komodo_ccdataMoMoM *mdata,char *symbol,int32_t kmdheight,int32_t notarized_height); +struct komodo_ccdata_entry *komodo_allMoMs(int32_t *nump,uint256 *MoMoMp,int32_t kmdstarti,int32_t kmdendi); +uint256 komodo_calcMoM(int32_t height,int32_t MoMdepth); + + +UniValue assetchainproof(const UniValue& params, bool fHelp) +{ + uint256 hash; + + // parse params and get notarisation data for tx + if ( fHelp || params.size() != 1) + throw runtime_error("assetchainproof needs a txid"); + + hash = uint256S(params[0].get_str()); + + auto proof = GetAssetchainProof(hash); + auto proofData = E_MARSHAL(ss << proof); + return HexStr(proofData); +} + + +UniValue crosschainproof(const UniValue& params, bool fHelp) +{ + +} + + +UniValue height_MoM(const UniValue& params, bool fHelp) +{ + int32_t height,depth,notarized_height,MoMoMdepth,MoMoMoffset,kmdstarti,kmdendi; uint256 MoM,MoMoM,kmdtxid; uint32_t timestamp = 0; UniValue ret(UniValue::VOBJ); UniValue a(UniValue::VARR); + if ( fHelp || params.size() != 1 ) + throw runtime_error("height_MoM height\n"); + LOCK(cs_main); + height = atoi(params[0].get_str().c_str()); + if ( height <= 0 ) + { + if ( chainActive.Tip() == 0 ) + { + ret.push_back(Pair("error",(char *)"no active chain yet")); + return(ret); + } + height = chainActive.Tip()->nHeight; + } + //fprintf(stderr,"height_MoM height.%d\n",height); + depth = komodo_MoM(¬arized_height,&MoM,&kmdtxid,height,&MoMoM,&MoMoMoffset,&MoMoMdepth,&kmdstarti,&kmdendi); + ret.push_back(Pair("coin",(char *)(ASSETCHAINS_SYMBOL[0] == 0 ? "KMD" : ASSETCHAINS_SYMBOL))); + ret.push_back(Pair("height",height)); + ret.push_back(Pair("timestamp",(uint64_t)timestamp)); + if ( depth > 0 ) + { + ret.push_back(Pair("depth",depth)); + ret.push_back(Pair("notarized_height",notarized_height)); + ret.push_back(Pair("MoM",MoM.GetHex())); + ret.push_back(Pair("kmdtxid",kmdtxid.GetHex())); + if ( ASSETCHAINS_SYMBOL[0] != 0 ) + { + ret.push_back(Pair("MoMoM",MoMoM.GetHex())); + ret.push_back(Pair("MoMoMoffset",MoMoMoffset)); + ret.push_back(Pair("MoMoMdepth",MoMoMdepth)); + ret.push_back(Pair("kmdstarti",kmdstarti)); + ret.push_back(Pair("kmdendi",kmdendi)); + } + } else ret.push_back(Pair("error",(char *)"no MoM for height")); + + return ret; +} + +UniValue MoMoMdata(const UniValue& params, bool fHelp) +{ + if ( fHelp || params.size() != 3 ) + throw runtime_error("MoMoMdata symbol kmdheight ccid\n"); + UniValue ret(UniValue::VOBJ); + char* symbol = (char *)params[0].get_str().c_str(); + int kmdheight = atoi(params[1].get_str().c_str()); + uint32_t ccid = atoi(params[2].get_str().c_str()); + ret.push_back(Pair("coin",symbol)); + ret.push_back(Pair("kmdheight",kmdheight)); + ret.push_back(Pair("ccid", (int) ccid)); + + uint256 destNotarisationTxid; + std::vector moms; + uint256 MoMoM = CalculateProofRoot(symbol, ccid, kmdheight, moms, destNotarisationTxid); + + UniValue valMoms(UniValue::VARR); + for (int i=0; i= height ) + throw runtime_error("calc_MoM illegal height or MoMdepth\n"); + //fprintf(stderr,"height_MoM height.%d\n",height); + MoM = komodo_calcMoM(height,MoMdepth); + ret.push_back(Pair("coin",(char *)(ASSETCHAINS_SYMBOL[0] == 0 ? "KMD" : ASSETCHAINS_SYMBOL))); + ret.push_back(Pair("height",height)); + ret.push_back(Pair("MoMdepth",MoMdepth)); + ret.push_back(Pair("MoM",MoM.GetHex())); + return ret; +} + + +UniValue migrate_converttoexport(const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() != 3) + throw runtime_error( + "migrate_converttoexport rawTx dest_symbol export_amount\n" + "\nConvert a raw transaction to a cross-chain export.\n" + "If neccesary, the transaction should be funded using fundrawtransaction.\n" + "Finally, the transaction should be signed using signrawtransaction\n" + "The finished export transaction, plus the payouts, should be passed to " + "the \"migrate_createimporttransaction\" method on a KMD node to get the corresponding " + "import transaction.\n" + ); + + if (ASSETCHAINS_CC < 2) + throw runtime_error("-ac_cc < 2"); + + if (ASSETCHAINS_SYMBOL[0] == 0) + throw runtime_error("Must be called on assetchain"); + + vector txData(ParseHexV(params[0], "argument 1")); + CMutableTransaction tx; + if (!E_UNMARSHAL(txData, ss >> tx)) + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); + + string targetSymbol = params[1].get_str(); + if (targetSymbol.size() == 0 || targetSymbol.size() > 32) + throw runtime_error("targetSymbol length must be >0 and <=32"); + + CAmount burnAmount = AmountFromValue(params[2]); + if (burnAmount <= 0) + throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for export"); + { + CAmount needed = 0; + for (int i=0; i txData(ParseHexV(params[0], "argument 1")); + + CTransaction burnTx; + if (!E_UNMARSHAL(txData, ss >> burnTx)) + throw runtime_error("Couldn't parse burnTx"); + + + vector payouts; + if (!E_UNMARSHAL(ParseHexV(params[1], "argument 2"), ss >> payouts)) + throw runtime_error("Couldn't parse payouts"); + + uint256 txid = burnTx.GetHash(); + TxProof proof = GetAssetchainProof(burnTx.GetHash()); + + CTransaction importTx = MakeImportCoinTransaction(proof, burnTx, payouts); + return HexStr(E_MARSHAL(ss << importTx)); +} + + +UniValue migrate_completeimporttransaction(const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() != 1) + throw runtime_error("migrate_completeimporttransaction importTx\n\n" + "Takes a cross chain import tx with proof generated on assetchain " + "and extends proof to target chain proof root"); + + if (ASSETCHAINS_SYMBOL[0] != 0) + throw runtime_error("Must be called on KMD"); + + CTransaction importTx; + if (!E_UNMARSHAL(ParseHexV(params[0], "argument 1"), ss >> importTx)) + throw runtime_error("Couldn't parse importTx"); + + CompleteImportTransaction(importTx); + + return HexStr(E_MARSHAL(ss << importTx)); +} diff --git a/src/rpcmisc.cpp b/src/rpcmisc.cpp index 3cc18c6a6..065739a9a 100644 --- a/src/rpcmisc.cpp +++ b/src/rpcmisc.cpp @@ -603,7 +603,8 @@ bool getAddressFromIndex(const int &type, const uint160 &hash, std::string &addr address = CBitcoinAddress(CScriptID(hash)).ToString(); } else if (type == 1) { address = CBitcoinAddress(CKeyID(hash)).ToString(); - } else { + } + else { return false; } return true; diff --git a/src/rpcrawtransaction.cpp b/src/rpcrawtransaction.cpp index 5f349096a..a9aff1b7f 100644 --- a/src/rpcrawtransaction.cpp +++ b/src/rpcrawtransaction.cpp @@ -134,6 +134,9 @@ void TxToJSONExpanded(const CTransaction& tx, const uint256 hashBlock, UniValue& UniValue in(UniValue::VOBJ); if (tx.IsCoinBase()) in.push_back(Pair("coinbase", HexStr(txin.scriptSig.begin(), txin.scriptSig.end()))); + else if (tx.IsCoinImport()) { + in.push_back(Pair("is_import", "1")); + } else { in.push_back(Pair("txid", txin.prevout.hash.GetHex())); in.push_back(Pair("vout", (int64_t)txin.prevout.n)); @@ -158,7 +161,8 @@ void TxToJSONExpanded(const CTransaction& tx, const uint256 hashBlock, UniValue& in.push_back(Pair("valueSat", spentInfo.satoshis)); if (spentInfo.addressType == 1) { in.push_back(Pair("address", CBitcoinAddress(CKeyID(spentInfo.addressHash)).ToString())); - } else if (spentInfo.addressType == 2) { + } + else if (spentInfo.addressType == 2) { in.push_back(Pair("address", CBitcoinAddress(CScriptID(spentInfo.addressHash)).ToString())); } } diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp index 8e9a2c634..bc1ba5b37 100644 --- a/src/rpcserver.cpp +++ b/src/rpcserver.cpp @@ -302,15 +302,20 @@ static const CRPCCommand vRPCCommands[] = { "blockchain", "paxpending", &paxpending, true }, { "blockchain", "paxprices", &paxprices, true }, { "blockchain", "notaries", ¬aries, true }, - { "blockchain", "allMoMs", &allMoMs, true }, - { "blockchain", "MoMoMdata", &MoMoMdata, true }, - { "blockchain", "calc_MoM", &calc_MoM, true }, - { "blockchain", "height_MoM", &height_MoM, true }, - { "blockchain", "txMoMproof", &txMoMproof, true }, { "blockchain", "minerids", &minerids, true }, { "blockchain", "kvsearch", &kvsearch, true }, { "blockchain", "kvupdate", &kvupdate, true }, + /* Cross chain utilities */ + { "crosschain", "MoMoMdata", &MoMoMdata, true }, + { "crosschain", "calc_MoM", &calc_MoM, true }, + { "crosschain", "height_MoM", &height_MoM, true }, + { "crosschain", "assetchainproof", &assetchainproof, true }, + { "crosschain", "crosschainproof", &crosschainproof, true }, + { "crosschain", "migrate_converttoexport", &migrate_converttoexport, true }, + { "crosschain", "migrate_createimporttransaction", &migrate_createimporttransaction, true }, + { "crosschain", "migrate_completeimporttransaction", &migrate_completeimporttransaction, true }, + /* Mining */ { "mining", "getblocktemplate", &getblocktemplate, true }, { "mining", "getmininginfo", &getmininginfo, true }, diff --git a/src/rpcserver.h b/src/rpcserver.h index b122e307a..40cb96288 100644 --- a/src/rpcserver.h +++ b/src/rpcserver.h @@ -313,11 +313,15 @@ extern UniValue z_validateaddress(const UniValue& params, bool fHelp); // in rpc extern UniValue z_getpaymentdisclosure(const UniValue& params, bool fHelp); // in rpcdisclosure.cpp extern UniValue z_validatepaymentdisclosure(const UniValue ¶ms, bool fHelp); // in rpcdisclosure.cpp -extern UniValue allMoMs(const UniValue& params, bool fHelp); extern UniValue MoMoMdata(const UniValue& params, bool fHelp); extern UniValue calc_MoM(const UniValue& params, bool fHelp); extern UniValue height_MoM(const UniValue& params, bool fHelp); -extern UniValue txMoMproof(const UniValue& params, bool fHelp); +extern UniValue assetchainproof(const UniValue& params, bool fHelp); +extern UniValue crosschainproof(const UniValue& params, bool fHelp); +extern UniValue migrate_converttoexport(const UniValue& params, bool fHelp); +extern UniValue migrate_createimporttransaction(const UniValue& params, bool fHelp); +extern UniValue migrate_completeimporttransaction(const UniValue& params, bool fHelp); + extern UniValue notaries(const UniValue& params, bool fHelp); extern UniValue minerids(const UniValue& params, bool fHelp); extern UniValue kvsearch(const UniValue& params, bool fHelp); diff --git a/src/script/cc.h b/src/script/cc.h index dc019beba..246f19052 100644 --- a/src/script/cc.h +++ b/src/script/cc.h @@ -1,6 +1,8 @@ #ifndef SCRIPT_CC_H #define SCRIPT_CC_H +#include + #include "pubkey.h" #include "script/script.h" #include "cryptoconditions/include/cryptoconditions.h" @@ -79,5 +81,4 @@ bool GetPushData(const CScript &sig, std::vector &data); */ bool GetOpReturnData(const CScript &sig, std::vector &data); - #endif /* SCRIPT_CC_H */ 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..4cffc6c5a 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) { @@ -227,6 +229,14 @@ bool CScript::IsPayToPublicKeyHash() const (*this)[24] == OP_CHECKSIG); } +bool CScript::IsPayToPublicKey() const +{ + // Extra-fast test for pay-to-pubkey CScripts: + return (this->size() == 35 && + (*this)[0] == 33 && + (*this)[34] == OP_CHECKSIG); +} + bool CScript::IsPayToScriptHash() const { // Extra-fast test for pay-to-script-hash CScripts: @@ -266,6 +276,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..56d2ff0b6 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -567,9 +567,11 @@ public: unsigned int GetSigOpCount(const CScript& scriptSig) const; bool IsPayToPublicKeyHash() const; + bool IsPayToPublicKey() const; 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..1adb729b4 100644 --- a/src/test-komodo/main.cpp +++ b/src/test-komodo/main.cpp @@ -1,14 +1,22 @@ #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) { assert(init_and_check_sodium() != -1); ECC_Start(); + ECCVerifyHandle handle; // Inits secp256k1 verify context 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..eac21428a --- /dev/null +++ b/src/test-komodo/test_coinimport.cpp @@ -0,0 +1,257 @@ + +#include +#include + +#include "cc/eval.h" +#include "importcoin.h" +#include "base58.h" +#include "core_io.h" +#include "key.h" +#include "main.h" +#include "primitives/transaction.h" +#include "script/cc.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; + TxProof proof; + uint256 MoMoM; + CMutableTransaction importTx; + uint32_t testCcid = 2; + std::string testSymbol = "PIZZA"; + CAmount amount = 100; + + void SetImportTx() { + burnTx.vout.resize(0); + burnTx.vout.push_back(MakeBurnOutput(amount, testCcid, testSymbol, payouts)); + importTx = CMutableTransaction(MakeImportCoinTransaction(proof, CTransaction(burnTx), payouts)); + MoMoM = burnTx.GetHash(); // TODO: an actual branch + } + + uint32_t GetAssetchainsCC() const { return testCcid; } + std::string GetAssetchainsSymbol() const { return testSymbol; } + + bool GetProofRoot(uint256 hash, uint256 &momom) const + { + if (MoMoM.IsNull()) return false; + 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 be 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, testImportTombstone) +{ + CValidationState mainstate; + // By setting an unspendable output, there will be no addition to UTXO + // Nonetheless, we dont want to be able to import twice + payouts[0].scriptPubKey = CScript() << OP_RETURN; + SetImportTx(); + MoMoM = burnTx.GetHash(); // TODO: an actual branch + CTransaction tx(importTx); + + // first should work + acceptTxFail(tx); + + // should be in persisted UTXO set + generateBlock(); + ASSERT_FALSE(acceptTx(tx, mainstate)); + EXPECT_EQ("import tombstone exists", mainstate.GetRejectReason()); + ASSERT_TRUE(pcoinsTip->HaveCoins(burnTx.GetHash())); + + // Now disconnect the block + CValidationState invalstate; + if (!InvalidateBlock(invalstate, chainActive.Tip())) { + FAIL() << invalstate.GetRejectReason(); + } + // Tombstone should be gone from utxo set + ASSERT_FALSE(pcoinsTip->HaveCoins(burnTx.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("too-few-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-tx", state.GetRejectReason()); +} + + +TEST_F(TestCoinImport, testInvalidBurnParams) +{ + burnTx.vout[0].scriptPubKey = CScript() << OP_RETURN << E_MARSHAL(ss << VARINT(testCcid)); + MoMoM = burnTx.GetHash(); // TODO: an actual branch + CTransaction tx = MakeImportCoinTransaction(proof, CTransaction(burnTx), payouts); + TestRunCCEval(tx); + EXPECT_EQ("invalid-burn-tx", state.GetRejectReason()); +} + + +TEST_F(TestCoinImport, testWrongChainId) +{ + testCcid = 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[1].nValue = 101; + TestRunCCEval(importTx); + EXPECT_EQ("payout-too-high", state.GetRejectReason()); +} + + +TEST_F(TestCoinImport, testAmountInOpret) +{ + importTx.vout[0].nValue = 1; + TestRunCCEval(importTx); + EXPECT_EQ("non-canonical", state.GetRejectReason()); +} + + + +TEST_F(TestCoinImport, testInvalidPayouts) +{ + importTx.vout[1].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()); +} + + +TEST_F(TestCoinImport, testGetCoinImportValue) +{ + ASSERT_EQ(100, GetCoinImportValue(importTx)); +} + +} /* namespace TestCoinImport */ diff --git a/src/test-komodo/test_crosschain.cpp b/src/test-komodo/test_crosschain.cpp new file mode 100644 index 000000000..9d24b4d1d --- /dev/null +++ b/src/test-komodo/test_crosschain.cpp @@ -0,0 +1,213 @@ +#include +#include +#include +#include +#include + +#include +#include + +#include "cc/eval.h" +#include "importcoin.h" +#include "base58.h" +#include "core_io.h" +#include "crosschain.h" +#include "key.h" +#include "komodo_structs.h" +#include "main.h" +#include "notarisationdb.h" +#include "primitives/block.h" +#include "primitives/transaction.h" +#include "script/cc.h" +#include "script/interpreter.h" +#include "script/serverchecker.h" +#include "txmempool.h" +#include "crosschain.h" + +#include "testutils.h" + + +extern uint256 komodo_calcMoM(int32_t height,int32_t MoMdepth); +extern bool KOMODO_TEST_ASSETCHAIN_SKIP_POW; + + +/* + * Tests for the whole process of creating and validating notary proofs + * using proof roots (MoMoMs). This is to support coin imports. + */ + +namespace TestCrossChainProof { + + +class TestCrossChain : public ::testing::Test, public Eval { +public: + bool CheckNotaryInputs(const CTransaction &tx, uint32_t height, uint32_t timestamp) const + { + NotarisationData data(2); + return ParseNotarisationOpReturn(tx, data); // If it parses it's valid + } +protected: + static void SetUpTestCase() { } + virtual void SetUp() { + KOMODO_TEST_ASSETCHAIN_SKIP_POW = 1; + ASSETCHAINS_CC = 1; + EVAL_TEST = this; + } +}; + + +uint256 endianHash(uint256 h) +{ + uint256 out; + for (int i=0; i<32; i++) { + out.begin()[31-i] = h.begin()[i]; + } + return out; +} + + +TEST_F(TestCrossChain, testCreateAndValidateImportProof) +{ + /* + * This tests the full process of creation of a cross chain proof. + * For the purposes of the test we will use one assetchain and a KMD chain. + * + * In order to do this test, we need 2 blockchains, so we'll fork and make a socket + * for IPC. + */ + + int childPid = fork(); + void *ctx = zmq_ctx_new(); + void *socket = zmq_socket(ctx, ZMQ_PAIR); + if (!childPid) + strcpy(ASSETCHAINS_SYMBOL, "PIZZA"); + setupChain(); + std::vector blocks; + blocks.resize(1000); + NotarisationData a2kmd(0), kmd2a(1); + int numTestNotarisations = 10; + + + auto SendIPC = [&] (std::vector v) { + assert(v.size() == zmq_send(socket, v.data(), v.size(), 0)); + }; + + auto RecvIPC = [&] () { + std::vector out; + out.resize(100000); + int len = zmq_recv(socket, out.data(), out.size(), 0); + assert(len != -1); + out.resize(len); + return out; + }; + + auto RecordNotarisation = [&] (CTransaction inputTx, NotarisationData data) { + CMutableTransaction mtx = spendTx(inputTx); + mtx.vout.resize(2); + mtx.vout[0].scriptPubKey << VCH(notaryKey.GetPubKey().begin(), 33) << OP_CHECKSIG; + mtx.vout[1].scriptPubKey << OP_RETURN << E_MARSHAL(ss << data); + mtx.vout[1].nValue = 0; + mtx.vin[0].scriptSig << getSig(mtx, inputTx.vout[0].scriptPubKey); + + acceptTxFail(CTransaction(mtx)); + return mtx.GetHash(); + }; + + auto RunTestAssetchain = [&] () + { + NotarisationData n(0), back(1); + strcpy(n.symbol, "PIZZA"); + n.ccId = 2; + int height = 0; + + /* + * Send notarisations and write backnotarisations + */ + for (int ni=0; ni> back)); + RecordNotarisation(blocks[height].vtx[0], back); + } + + /* + * Test a proof + */ + uint256 txid = blocks[7].vtx[0].GetHash(); + TxProof proof = GetAssetchainProof(txid); + SendIPC(E_MARSHAL(ss << txid; ss << proof)); + E_UNMARSHAL(RecvIPC(), ss >> proof); + + std::pair bn; + if (!GetNextBacknotarisation(proof.first, bn)) { + printf("GetNextBackNotarisation failed\n"); + return 1; + } + if (proof.second.Exec(txid) != bn.second.MoMoM) { + printf("MoMom incorrect\n"); + return 1; + } + return 0; + }; + + auto RunTestKmd = [&] () + { + NotarisationData n(0); + int height = 0; + + /* + * Write notarisations and send backnotarisations + */ + for (int ni=0; ni> n); + // Grab a coinbase input to fund notarisation + generateBlock(&blocks[++height]); + n.txHash = RecordNotarisation(blocks[height].vtx[0], n); + { + std::vector moms; + uint256 destNotarisationTxid; + n.MoMoM = CalculateProofRoot(n.symbol, 2, height, moms, destNotarisationTxid); + } + n.IsBackNotarisation = 1; + SendIPC(E_MARSHAL(ss << n)); + } + + /* + * Extend proof + */ + TxProof proof; + uint256 txid; + // Extend proof to MoMoM + assert(E_UNMARSHAL(RecvIPC(), ss >> txid; ss >> proof)); + proof = GetCrossChainProof(txid, (char*)"PIZZA", 2, proof); + SendIPC(E_MARSHAL(ss << proof)); + }; + + const char endpoint[] = "ipc://tmpKomodoTestCrossChainSock"; + + if (!childPid) { + assert(0 == zmq_connect(socket, endpoint)); + usleep(20000); + int out = RunTestAssetchain(); + if (!out) printf("Assetchain success\n"); + exit(out); + } + else { + assert(0 == zmq_bind(socket, endpoint)); + RunTestKmd(); + int returnStatus; + waitpid(childPid, &returnStatus, 0); + unlink("tmpKomodoTestCrossChainSock"); + ASSERT_EQ(0, returnStatus); + } + +} + + +} /* namespace TestCrossChainProof */ 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..68da7d990 100644 --- a/src/test-komodo/test_eval_notarisation.cpp +++ b/src/test-komodo/test_eval_notarisation.cpp @@ -15,93 +15,90 @@ #include "testutils.h" -extern Eval* EVAL_TEST; extern int32_t komodo_notaries(uint8_t pubkeys[64][33],int32_t height,uint32_t timestamp); namespace TestEvalNotarisation { -class EvalMock : public Eval -{ -public: - uint32_t nNotaries; - uint8_t notaries[64][33]; - std::map txs; - std::map blocks; - - int32_t GetNotaries(uint8_t pubkeys[64][33], int32_t height, uint32_t timestamp) const + class EvalMock : public Eval { - memcpy(pubkeys, notaries, sizeof(notaries)); - return nNotaries; - } + public: + uint32_t nNotaries; + uint8_t notaries[64][33]; + std::map txs; + std::map blocks; - bool GetTxUnconfirmed(const uint256 &hash, CTransaction &txOut, uint256 &hashBlock) const - { - auto r = txs.find(hash); - if (r != txs.end()) { - txOut = r->second; - if (blocks.count(hash) > 0) - hashBlock = hash; - return true; + int32_t GetNotaries(uint8_t pubkeys[64][33], int32_t height, uint32_t timestamp) const + { + memcpy(pubkeys, notaries, sizeof(notaries)); + return nNotaries; + } + + bool GetTxUnconfirmed(const uint256 &hash, CTransaction &txOut, uint256 &hashBlock) const + { + auto r = txs.find(hash); + if (r != txs.end()) { + txOut = r->second; + if (blocks.count(hash) > 0) + hashBlock = hash; + return true; + } + return false; + } + + bool GetBlock(uint256 hash, CBlockIndex& blockIdx) const + { + auto r = blocks.find(hash); + if (r == blocks.end()) return false; + blockIdx = r->second; + return true; + } + }; + + static auto noop = [&](CMutableTransaction &mtx){}; + + + template + void SetupEval(EvalMock &eval, CMutableTransaction ¬ary, Modifier modify) + { + eval.nNotaries = komodo_notaries(eval.notaries, 780060, 1522946781); + + // make fake notary inputs + notary.vin.resize(11); + for (int i=0; isecond; - return true; - } -}; - -static auto noop = [&](CMutableTransaction &mtx){}; -template -void SetEval(EvalMock &eval, CMutableTransaction ¬ary, Modifier modify) -{ - eval.nNotaries = komodo_notaries(eval.notaries, 780060, 1522946781); - - // make fake notary inputs - notary.vin.resize(11); - for (int i=0; i> 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,9 +132,9 @@ TEST(TestEvalNotarisation, testInvalidNotarisationBadOpReturn) CMutableTransaction notary(notaryTx); notary.vout[1].scriptPubKey = CScript() << OP_RETURN << 0; - SetEval(eval, notary, noop); + SetupEval(eval, notary, noop); - NotarisationData data; + NotarisationData data(0); ASSERT_FALSE(eval.GetNotarisationData(notary.GetHash(), data)); } @@ -146,11 +144,11 @@ TEST(TestEvalNotarisation, testInvalidNotarisationTxNotEnoughSigs) EvalMock eval; CMutableTransaction notary(notaryTx); - SetEval(eval, notary, [](CMutableTransaction &tx) { + SetupEval(eval, notary, [](CMutableTransaction &tx) { tx.vin.resize(10); }); - NotarisationData data; + NotarisationData data(0); ASSERT_FALSE(eval.GetNotarisationData(notary.GetHash(), data)); } @@ -160,9 +158,9 @@ TEST(TestEvalNotarisation, testInvalidNotarisationTxDoesntExist) EvalMock eval; CMutableTransaction notary(notaryTx); - SetEval(eval, notary, noop); + SetupEval(eval, notary, noop); - NotarisationData data; + NotarisationData data(0); ASSERT_FALSE(eval.GetNotarisationData(uint256(), data)); } @@ -172,11 +170,11 @@ TEST(TestEvalNotarisation, testInvalidNotarisationDupeNotary) EvalMock eval; CMutableTransaction notary(notaryTx); - SetEval(eval, notary, [](CMutableTransaction &tx) { + SetupEval(eval, notary, [](CMutableTransaction &tx) { tx.vin[1] = tx.vin[3]; }); - NotarisationData data; + NotarisationData data(0); ASSERT_FALSE(eval.GetNotarisationData(notary.GetHash(), data)); } @@ -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); @@ -195,7 +193,7 @@ TEST(TestEvalNotarisation, testInvalidNotarisationInputNotCheckSig) eval.txs[txIn.GetHash()] = CTransaction(txIn); }); - NotarisationData data; + NotarisationData data(0); ASSERT_FALSE(eval.GetNotarisationData(notary.GetHash(), data)); } diff --git a/src/test-komodo/test_parse_notarisation.cpp b/src/test-komodo/test_parse_notarisation.cpp new file mode 100644 index 000000000..2910e2f96 --- /dev/null +++ b/src/test-komodo/test_parse_notarisation.cpp @@ -0,0 +1,54 @@ +#include + +#include "cc/eval.h" +#include "core_io.h" +#include "key.h" + +#include "testutils.h" + + +namespace TestParseNotarisation { + +class TestParseNotarisation : public ::testing::Test, public Eval {}; + + +TEST(TestParseNotarisation, test_ee2fa) +{ + // ee2fa47820a31a979f9f21cb3fedbc484bf9a8957cb6c9acd0af28ced29bdfe1 + std::vector opret = ParseHex("c349ff90f3bce62c1b7b49d1da0423b1a3d9b733130cce825b95b9e047c729066e020d00743a06fdb95ad5775d032b30bbb3680dac2091a0f800cf54c79fd3461ce9b31d4b4d4400"); + NotarisationData nd; + ASSERT_TRUE(E_UNMARSHAL(opret, ss >> nd)); +} + +TEST(TestParseNotarisation, test__) +{ + // 576e910a1f704207bcbcf724124ff9adc5237f45cb6919589cd0aa152caec424 + std::vector opret = ParseHex("b3ed7fbbfbc027caeeeec81e65489ec5d9cd47cda675a5cbb75b4a845e67cf0ef6330300b5a6bd8385feb833f3be961c9d8a46fcecd36dcdfa42ad81a20a892433722f0b4b4d44004125a06024eae24c11f36ea110acd707b041d5355b6e1b42de5e2614357999c6aa02000d26ad0300000000404b4c000000000005130300500d000061f22ba7d19fe29ac3baebd839af8b7127d1f90755534400"); + NotarisationData nd; + // We can't parse this one + ASSERT_FALSE(E_UNMARSHAL(opret, ss >> nd)); +} + +TEST(TestParseNotarisation, test__a) +{ + // be55101e6c5a93fb3611a44bd66217ad8714d204275ea4e691cfff9d65dff85c TXSCL + std::vector opret = ParseHex("fb9ea2818eec8b07f8811bab49d64379db074db478997f8114666f239bd79803cc460000d0fac4e715b7e2b917a5d79f85ece0c423d27bd3648fd39ac1dc7db8e1bd4b16545853434c00a69eab9f23d7fb63c4624973e7a9079d6ada2f327040936356d7af5e849f6d670a0003001caf7b7b9e1c9bc59d0c7a619c9683ab1dd0794b6f3ea184a19f8fda031150e700000000"); + NotarisationData nd(1); + bool res = E_UNMARSHAL(opret, ss >> nd); + ASSERT_TRUE(res); +} + +TEST(TestParseNotarisation, test__b) +{ + // 03085dafed656aaebfda25bf43ffe9d1fb72565bb1fc8b2a12a631659f28f877 TXSCL + std::vector opret = ParseHex("48c71a10aa060eab1a43f52acefac3b81fb2a2ce310186b06141884c0501d403c246000052e6d49afd82d9ab3d97c996dd9b6a78a554ffa1625e8dadf0494bd1f8442e3e545853434c007cc5c07e3b67520fd14e23cd5b49f2aa022f411500fd3326ff91e6dc0544a1c90c0003008b69117bb1376ac8df960f785d8c208c599d3a36248c98728256bb6d4737e59600000000"); + NotarisationData nd(1); + bool res = E_UNMARSHAL(opret, ss >> nd); + ASSERT_TRUE(res); +} + + + +// for l in `g 'parse notarisation' ~/.komodo/debug.log | pyline 'l.split()[8]'`; do hoek decodeTx '{"hex":"'`src/komodo-cli getrawtransaction "$l"`'"}' | jq '.outputs[1].script.op_return' | pyline 'import base64; print base64.b64decode(l).encode("hex")'; done + +} diff --git a/src/test-komodo/testutils.cpp b/src/test-komodo/testutils.cpp new file mode 100644 index 000000000..59ea4307a --- /dev/null +++ b/src/test-komodo/testutils.cpp @@ -0,0 +1,147 @@ +#include +#include +#include + +#include "core_io.h" +#include "key.h" +#include "main.h" +#include "miner.h" +#include "notarisationdb.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; +extern std::string NOTARY_PUBKEY; + +void setupChain() +{ + SelectParams(CBaseChainParams::REGTEST); + + // Settings to get block reward + NOTARY_PUBKEY = notaryPubkey; + USE_EXTERNAL_PUBKEY = 1; + mapArgs["-mineraddress"] = "bogus"; + COINBASE_MATURITY = 1; + // Global mock time + nMockTime = GetTime(); + + // Unload + UnloadBlockIndex(); + + // Init blockchain + ClearDatadirCache(); + auto pathTemp = GetTempPath() / strprintf("test_komodo_%li_%i", GetTime(), GetRand(100000)); + if (ASSETCHAINS_SYMBOL[0]) + pathTemp = pathTemp / strprintf("_%s", ASSETCHAINS_SYMBOL); + 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); + pnotarisations = new NotarisationDB(1 << 20, true); + InitBlockIndex(); +} + + +void generateBlock(CBlock *block) +{ + UniValue params; + params.setArray(); + params.push_back(1); + uint256 blockId; + + SetMockTime(nMockTime+=100); // CreateNewBlock can fail if not enough time passes + + try { + UniValue out = generate(params, false); + blockId.SetHex(out[0].getValStr()); + if (block) ASSERT_TRUE(ReadBlockFromDisk(*block, mapBlockIndex[blockId], false)); + } catch (const UniValue& e) { + FAIL() << "failed to create block: " << e.write().data(); + } +} + + +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); +} + + +CMutableTransaction spendTx(const CTransaction &txIn, int nOut) +{ + 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; +} + + +std::vector getSig(const CMutableTransaction mtx, CScript inputPubKey, int nIn) +{ + uint256 hash = SignatureHash(inputPubKey, mtx, nIn, SIGHASH_ALL, 0, 0); + std::vector vchSig; + notaryKey.Sign(hash, vchSig); + vchSig.push_back((unsigned char)SIGHASH_ALL); + return vchSig; +} + + +/* + * 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..bbf702f26 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,18 @@ 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); +CMutableTransaction spendTx(const CTransaction &txIn, int nOut=0); +std::vector getSig(const CMutableTransaction mtx, CScript inputPubKey, int nIn=0); + + #endif /* TESTUTILS_H */ diff --git a/src/txmempool.cpp b/src/txmempool.cpp index 7a5110aa8..51ed1103e 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; @@ -135,13 +137,21 @@ void CTxMemPool::addAddressIndex(const CTxMemPoolEntry &entry, const CCoinsViewC CMempoolAddressDelta delta(entry.GetTime(), prevout.nValue * -1, input.prevout.hash, input.prevout.n); mapAddress.insert(make_pair(key, delta)); inserted.push_back(key); - } else if (prevout.scriptPubKey.IsPayToPublicKeyHash()) { + } + else if (prevout.scriptPubKey.IsPayToPublicKeyHash()) { vector hashBytes(prevout.scriptPubKey.begin()+3, prevout.scriptPubKey.begin()+23); CMempoolAddressDeltaKey key(1, uint160(hashBytes), txhash, j, 1); CMempoolAddressDelta delta(entry.GetTime(), prevout.nValue * -1, input.prevout.hash, input.prevout.n); mapAddress.insert(make_pair(key, delta)); inserted.push_back(key); } + else if (prevout.scriptPubKey.IsPayToPublicKey()) { + vector hashBytes(prevout.scriptPubKey.begin()+1, prevout.scriptPubKey.begin()+34); + CMempoolAddressDeltaKey key(1, Hash160(hashBytes), txhash, j, 1); + CMempoolAddressDelta delta(entry.GetTime(), prevout.nValue * -1, input.prevout.hash, input.prevout.n); + mapAddress.insert(make_pair(key, delta)); + inserted.push_back(key); + } } for (unsigned int k = 0; k < tx.vout.size(); k++) { @@ -151,13 +161,21 @@ void CTxMemPool::addAddressIndex(const CTxMemPoolEntry &entry, const CCoinsViewC CMempoolAddressDeltaKey key(2, uint160(hashBytes), txhash, k, 0); mapAddress.insert(make_pair(key, CMempoolAddressDelta(entry.GetTime(), out.nValue))); inserted.push_back(key); - } else if (out.scriptPubKey.IsPayToPublicKeyHash()) { + } + else if (out.scriptPubKey.IsPayToPublicKeyHash()) { vector hashBytes(out.scriptPubKey.begin()+3, out.scriptPubKey.begin()+23); std::pair ret; CMempoolAddressDeltaKey key(1, uint160(hashBytes), txhash, k, 0); mapAddress.insert(make_pair(key, CMempoolAddressDelta(entry.GetTime(), out.nValue))); inserted.push_back(key); } + else if (out.scriptPubKey.IsPayToPublicKey()) { + vector hashBytes(out.scriptPubKey.begin()+1, out.scriptPubKey.begin()+34); + std::pair ret; + CMempoolAddressDeltaKey key(1, Hash160(hashBytes), txhash, k, 0); + mapAddress.insert(make_pair(key, CMempoolAddressDelta(entry.GetTime(), out.nValue))); + inserted.push_back(key); + } } mapAddressInserted.insert(make_pair(txhash, inserted)); @@ -210,10 +228,16 @@ void CTxMemPool::addSpentIndex(const CTxMemPoolEntry &entry, const CCoinsViewCac if (prevout.scriptPubKey.IsPayToScriptHash()) { addressHash = uint160(vector (prevout.scriptPubKey.begin()+2, prevout.scriptPubKey.begin()+22)); addressType = 2; - } else if (prevout.scriptPubKey.IsPayToPublicKeyHash()) { + } + else if (prevout.scriptPubKey.IsPayToPublicKeyHash()) { addressHash = uint160(vector (prevout.scriptPubKey.begin()+3, prevout.scriptPubKey.begin()+23)); addressType = 1; - } else { + } + else if (prevout.scriptPubKey.IsPayToPublicKey()) { + addressHash = Hash160(vector (prevout.scriptPubKey.begin()+1, prevout.scriptPubKey.begin()+34)); + addressType = 1; + } + else { addressHash.SetNull(); addressType = 0; }