From 4add699cbb0fc06d1b13a054523652dd872abf62 Mon Sep 17 00:00:00 2001 From: dimxy Date: Thu, 18 Apr 2019 16:54:32 +0500 Subject: [PATCH] token migration moved to FSM --- src/Makefile.am | 2 +- src/cc/CCinclude.h | 6 +- src/cc/CCtokens.cpp | 225 ++++-- src/cc/CCtokens.h | 1 + .../{CCtokensOpRet.cpp => CCtokenutils.cpp} | 133 +++- src/cc/CCutils.cpp | 124 ++-- src/cc/import.cpp | 333 ++++++--- src/crosschain.cpp | 123 +++- src/crosschain.h | 2 +- src/importcoin.cpp | 205 +++++- src/importcoin.h | 79 +- src/rpc/crosschain.cpp | 690 +++++++++++++++--- src/rpc/server.cpp | 3 + src/rpc/server.h | 3 + 14 files changed, 1525 insertions(+), 404 deletions(-) rename src/cc/{CCtokensOpRet.cpp => CCtokenutils.cpp} (69%) diff --git a/src/Makefile.am b/src/Makefile.am index 248f9aa27..7e1647a6c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -491,7 +491,7 @@ libbitcoin_common_a_SOURCES = \ script/sign.cpp \ script/standard.cpp \ transaction_builder.cpp \ - cc/CCtokensOpRet.cpp \ + cc/CCtokenutils.cpp \ cc/CCutilbits.cpp \ $(BITCOIN_CORE_H) \ $(LIBZCASH_H) diff --git a/src/cc/CCinclude.h b/src/cc/CCinclude.h index 6ca6fa3bc..5a515634d 100644 --- a/src/cc/CCinclude.h +++ b/src/cc/CCinclude.h @@ -206,13 +206,13 @@ int32_t komodo_blockload(CBlock& block,CBlockIndex *pindex); CScript EncodeTokenCreateOpRet(uint8_t funcid, std::vector origpubkey, std::string name, std::string description, vscript_t vopretNonfungible); CScript EncodeTokenCreateOpRet(uint8_t funcid, std::vector origpubkey, std::string name, std::string description, std::vector> oprets); -CScript EncodeTokenImportOpRet(std::vector origpubkey, std::string name, std::string description, uint256 srctokenid, std::vector> oprets); + CScript EncodeTokenOpRet(uint256 tokenid, std::vector voutPubkeys, std::pair opretWithId); CScript EncodeTokenOpRet(uint256 tokenid, std::vector voutPubkeys, std::vector> oprets); int64_t AddCClibtxfee(struct CCcontract_info *cp,CMutableTransaction &mtx,CPubKey pk); uint8_t DecodeTokenCreateOpRet(const CScript &scriptPubKey, std::vector &origpubkey, std::string &name, std::string &description); uint8_t DecodeTokenCreateOpRet(const CScript &scriptPubKey, std::vector &origpubkey, std::string &name, std::string &description, std::vector> &oprets); -uint8_t DecodeTokenImportOpRet(const CScript &scriptPubKey, std::vector &origpubkey, std::string &name, std::string &description, uint256 &srctokenid, std::vector> &oprets); + uint8_t DecodeTokenOpRet(const CScript scriptPubKey, uint8_t &evalCodeTokens, uint256 &tokenid, std::vector &voutPubkeys, std::vector> &oprets); void GetNonfungibleData(uint256 tokenid, vscript_t &vopretNonfungible); bool ExtractTokensCCVinPubkeys(const CTransaction &tx, std::vector &vinPubkeys); @@ -293,6 +293,8 @@ void vcalc_sha256(char deprecated[(256 >> 3) * 2 + 1],uint8_t hash[256 >> 3],uin bits256 bits256_doublesha256(char *deprecated,uint8_t *data,int32_t datalen); UniValue ValueFromAmount(const CAmount& amount); +int64_t TotalPubkeyNormalInputs(const CTransaction &tx, const CPubKey &pubkey); +int64_t TotalPubkeyCCInputs(const CTransaction &tx, const CPubKey &pubkey); // bitcoin LogPrintStr with category "-debug" cmdarg support for C++ ostringstream: #define CCLOG_INFO 0 diff --git a/src/cc/CCtokens.cpp b/src/cc/CCtokens.cpp index a6ecf7123..be3cbd922 100644 --- a/src/cc/CCtokens.cpp +++ b/src/cc/CCtokens.cpp @@ -1,5 +1,5 @@ /****************************************************************************** - * Copyright © 2014-2018 The SuperNET Developers. * + * Copyright © 2014-2019 The SuperNET Developers. * * * * See the AUTHORS, DEVELOPER-AGREEMENT and LICENSE files at * * the top-level directory of this distribution for the individual copyright * @@ -14,6 +14,7 @@ ******************************************************************************/ #include "CCtokens.h" +#include "importcoin.h" /* TODO: correct this: ----------------------------- @@ -102,22 +103,21 @@ bool TokensValidate(struct CCcontract_info *cp, Eval* eval, const CTransaction & switch (funcid) { - case 'c': // create wont be called to be verified as it has no CC inputs + case 'c': // token create should not be validated as it has no CC inputs, so return 'invalid' + // token tx structure for 'c': //vin.0: normal input //vout.0: issuance tokenoshis to CC //vout.1: normal output for change (if any) //vout.n-1: opreturn EVAL_TOKENS 'c' - //if (evalCodeInOpret != EVAL_TOKENS) - // return eval->Invalid("unexpected TokenValidate for createtoken"); - //else - return true; + return eval->Invalid("incorrect token funcid"); case 't': // transfer + // token tx structure for 't' //vin.0: normal input //vin.1 .. vin.n-1: valid CC outputs //vout.0 to n-2: tokenoshis output to CC //vout.n-2: normal output for change (if any) - //vout.n-1: opreturn 't' tokenid + //vout.n-1: opreturn EVAL_TOKENS 't' tokenid if (inputs == 0) return eval->Invalid("no token inputs for transfer"); @@ -129,20 +129,7 @@ bool TokensValidate(struct CCcontract_info *cp, Eval* eval, const CTransaction & return eval->Invalid("unexpected token funcid"); } - // forward validation if evalcode in opret is not EVAL_TOKENS - // init for forwarding validation call - //if (evalCodeInOpret != EVAL_TOKENS) { // TODO: should we check also only allowed for tokens evalcodes, like EVAL_ASSETS, EVAL_GATEWAYS? - // struct CCcontract_info *cpOther = NULL, C; - - // cpOther = CCinit(&C, evalCodeInOpret); - // if (cpOther) - // return cpOther->validate(cpOther, eval, tx, nIn); - // else - // return eval->Invalid("unsupported evalcode in opret"); - //} return true; - // what does this do? - // return(PreventCC(eval,tx,preventCCvins,numvins,preventCCvouts,numvouts)); } // helper funcs: @@ -333,7 +320,8 @@ int64_t IsTokensvout(bool goDeeper, bool checkPubkeys /*<--not used, always true vscript_t vopretExtra, vopretNonfungible; std::vector> oprets; - uint8_t evalCode = EVAL_TOKENS; // if both payloads are empty maybe it is a transfer to non-payload-one-eval-token vout like GatewaysClaim + uint8_t evalCodeNonfungible = 0; + uint8_t evalCode1 = EVAL_TOKENS; // if both payloads are empty maybe it is a transfer to non-payload-one-eval-token vout like GatewaysClaim uint8_t evalCode2 = 0; // will be checked if zero or not // test vouts for possible token use-cases: @@ -354,12 +342,12 @@ int64_t IsTokensvout(bool goDeeper, bool checkPubkeys /*<--not used, always true // non-fungible-eval -> EVAL_TOKENS -> assets-eval if (vopretNonfungible.size() > 0) - evalCode = vopretNonfungible.begin()[0]; + evalCodeNonfungible = evalCode1 = vopretNonfungible.begin()[0]; if (vopretExtra.size() > 0) evalCode2 = vopretExtra.begin()[0]; - if (evalCode == EVAL_TOKENS && evalCode2 != 0) { - evalCode = evalCode2; // for using MakeTokensCC1vout(evalcode,...) instead of MakeCC1vout(EVAL_TOKENS, evalcode...) + if (evalCode1 == EVAL_TOKENS && evalCode2 != 0) { + evalCode1 = evalCode2; // for using MakeTokensCC1vout(evalcode,...) instead of MakeCC1vout(EVAL_TOKENS, evalcode...) evalCode2 = 0; } @@ -369,39 +357,41 @@ int64_t IsTokensvout(bool goDeeper, bool checkPubkeys /*<--not used, always true // maybe this is dual-eval 1 pubkey or 1of2 pubkey vout? if (voutPubkeys.size() >= 1 && voutPubkeys.size() <= 2) { // check dual/three-eval 1 pubkey vout with the first pubkey - testVouts.push_back( std::make_pair(MakeTokensCC1vout(evalCode, evalCode2, tx.vout[v].nValue, voutPubkeys[0]), std::string("three-eval cc1 pk[0]")) ); + testVouts.push_back( std::make_pair(MakeTokensCC1vout(evalCode1, evalCode2, tx.vout[v].nValue, voutPubkeys[0]), std::string("three-eval cc1 pk[0]")) ); if (evalCode2 != 0) // also check in backward evalcode order - testVouts.push_back( std::make_pair(MakeTokensCC1vout(evalCode2, evalCode, tx.vout[v].nValue, voutPubkeys[0]), std::string("three-eval cc1 pk[0] backward-eval")) ); + testVouts.push_back( std::make_pair(MakeTokensCC1vout(evalCode2, evalCode1, tx.vout[v].nValue, voutPubkeys[0]), std::string("three-eval cc1 pk[0] backward-eval")) ); if(voutPubkeys.size() == 2) { // check dual/three eval 1of2 pubkeys vout - testVouts.push_back( std::make_pair(MakeTokensCC1of2vout(evalCode, evalCode2, tx.vout[v].nValue, voutPubkeys[0], voutPubkeys[1]), std::string("three-eval cc1of2")) ); + testVouts.push_back( std::make_pair(MakeTokensCC1of2vout(evalCode1, evalCode2, tx.vout[v].nValue, voutPubkeys[0], voutPubkeys[1]), std::string("three-eval cc1of2")) ); // check dual/three eval 1 pubkey vout with the second pubkey - testVouts.push_back(std::make_pair(MakeTokensCC1vout(evalCode, evalCode2, tx.vout[v].nValue, voutPubkeys[1]), std::string("three-eval cc1 pk[1]"))); + testVouts.push_back(std::make_pair(MakeTokensCC1vout(evalCode1, evalCode2, tx.vout[v].nValue, voutPubkeys[1]), std::string("three-eval cc1 pk[1]"))); if (evalCode2 != 0) { // also check in backward evalcode order: // check dual/three eval 1of2 pubkeys vout - testVouts.push_back(std::make_pair(MakeTokensCC1of2vout(evalCode2, evalCode, tx.vout[v].nValue, voutPubkeys[0], voutPubkeys[1]), std::string("three-eval cc1of2 backward-eval"))); + testVouts.push_back(std::make_pair(MakeTokensCC1of2vout(evalCode2, evalCode1, tx.vout[v].nValue, voutPubkeys[0], voutPubkeys[1]), std::string("three-eval cc1of2 backward-eval"))); // check dual/three eval 1 pubkey vout with the second pubkey - testVouts.push_back(std::make_pair(MakeTokensCC1vout(evalCode2, evalCode, tx.vout[v].nValue, voutPubkeys[1]), std::string("three-eval cc1 pk[1] backward-eval"))); + testVouts.push_back(std::make_pair(MakeTokensCC1vout(evalCode2, evalCode1, tx.vout[v].nValue, voutPubkeys[1]), std::string("three-eval cc1 pk[1] backward-eval"))); } } + // maybe this is like gatewayclaim to single-eval token? + if( evalCodeNonfungible == 0 ) // do not allow to convert non-fungible to fungible token + testVouts.push_back(std::make_pair(MakeCC1vout(EVAL_TOKENS, tx.vout[v].nValue, voutPubkeys[0]), std::string("single-eval cc1 pk[0]"))); - // maybe this is like gatewayclaim to single-eval token? - testVouts.push_back(std::make_pair(MakeCC1vout(EVAL_TOKENS, tx.vout[v].nValue, voutPubkeys[0]), std::string("single-eval cc1 pk[0]"))); // maybe this is like FillSell for non-fungible token? - if( evalCode != 0 ) - testVouts.push_back(std::make_pair(MakeTokensCC1vout(evalCode, tx.vout[v].nValue, voutPubkeys[0]), std::string("dual-eval-token cc1 pk[0]"))); + if( evalCode1 != 0 ) + testVouts.push_back(std::make_pair(MakeTokensCC1vout(evalCode1, tx.vout[v].nValue, voutPubkeys[0]), std::string("dual-eval-token cc1 pk[0]"))); if( evalCode2 != 0 ) testVouts.push_back(std::make_pair(MakeTokensCC1vout(evalCode2, tx.vout[v].nValue, voutPubkeys[0]), std::string("dual-eval2-token cc1 pk[0]"))); + // the same for pk[1]: if (voutPubkeys.size() == 2) { - // the same for pk[1]: - testVouts.push_back(std::make_pair(MakeCC1vout(EVAL_TOKENS, tx.vout[v].nValue, voutPubkeys[1]), std::string("single-eval cc1 pk[1]"))); - if (evalCode != 0) - testVouts.push_back(std::make_pair(MakeTokensCC1vout(evalCode, tx.vout[v].nValue, voutPubkeys[1]), std::string("dual-eval-token cc1 pk[1]"))); + if (evalCodeNonfungible == 0) // do not allow to convert non-fungible to fungible token + testVouts.push_back(std::make_pair(MakeCC1vout(EVAL_TOKENS, tx.vout[v].nValue, voutPubkeys[1]), std::string("single-eval cc1 pk[1]"))); + if (evalCode1 != 0) + testVouts.push_back(std::make_pair(MakeTokensCC1vout(evalCode1, tx.vout[v].nValue, voutPubkeys[1]), std::string("dual-eval-token cc1 pk[1]"))); if (evalCode2 != 0) testVouts.push_back(std::make_pair(MakeTokensCC1vout(evalCode2, tx.vout[v].nValue, voutPubkeys[1]), std::string("dual-eval2-token cc1 pk[1]"))); } @@ -413,52 +403,95 @@ int64_t IsTokensvout(bool goDeeper, bool checkPubkeys /*<--not used, always true FilterOutTokensUnspendablePk(vinPubkeysUnfiltered, vinPubkeys); // cannot send tokens to token unspendable cc addr (only marker is allowed there) for(std::vector::iterator it = vinPubkeys.begin(); it != vinPubkeys.end(); it++) { - testVouts.push_back(std::make_pair(MakeCC1vout(EVAL_TOKENS, tx.vout[v].nValue, *it), std::string("single-eval cc1 self vin pk"))); - testVouts.push_back(std::make_pair(MakeTokensCC1vout(evalCode, evalCode2, tx.vout[v].nValue, *it), std::string("three-eval cc1 self vin pk"))); + if (evalCodeNonfungible == 0) // do not allow to convert non-fungible to fungible token + testVouts.push_back(std::make_pair(MakeCC1vout(EVAL_TOKENS, tx.vout[v].nValue, *it), std::string("single-eval cc1 self vin pk"))); + testVouts.push_back(std::make_pair(MakeTokensCC1vout(evalCode1, evalCode2, tx.vout[v].nValue, *it), std::string("three-eval cc1 self vin pk"))); if (evalCode2 != 0) // also check in backward evalcode order: - testVouts.push_back(std::make_pair(MakeTokensCC1vout(evalCode2, evalCode, tx.vout[v].nValue, *it), std::string("three-eval cc1 self vin pk backward-eval"))); + testVouts.push_back(std::make_pair(MakeTokensCC1vout(evalCode2, evalCode1, tx.vout[v].nValue, *it), std::string("three-eval cc1 self vin pk backward-eval"))); } - } - else { - CPubKey origPubkey; - vscript_t vorigPubkey; - std::string dummyName, dummyDescription; - std::vector> oprets; - - if (DecodeTokenCreateOpRet(tx.vout.back().scriptPubKey, vorigPubkey, dummyName, dummyDescription, oprets) == 0) { - LOGSTREAM((char *)"cctokens", CCLOG_INFO, stream << indentStr << "IsTokensvout() could not decode create opret" << " for txid=" << tx.GetHash().GetHex() << " for tokenid=" << reftokenid.GetHex() << std::endl); - return 0; + // try all test vouts: + for (auto t : testVouts) { + if (t.first == tx.vout[v]) { // test vout matches + LOGSTREAM((char *)"cctokens", CCLOG_DEBUG1, stream << indentStr << "IsTokensvout() valid amount=" << tx.vout[v].nValue << " msg=" << t.second << " evalCode=" << (int)evalCode1 << " evalCode2=" << (int)evalCode2 << " txid=" << tx.GetHash().GetHex() << " tokenid=" << reftokenid.GetHex() << std::endl); + return tx.vout[v].nValue; + } } - origPubkey = pubkey2pk(vorigPubkey); - - // for 'c' recognize the tokens only to token originator pubkey (but not to unspendable <-- closed sec violation) - // maybe this is like gatewayclaim to single-eval token? - testVouts.push_back(std::make_pair(MakeCC1vout(EVAL_TOKENS, tx.vout[v].nValue, origPubkey), std::string("single-eval cc1 orig-pk"))); - // maybe this is like FillSell for non-fungible token? - if (evalCode != 0) - testVouts.push_back(std::make_pair(MakeTokensCC1vout(evalCode, tx.vout[v].nValue, origPubkey), std::string("dual-eval-token cc1 orig-pk"))); } + else { // funcid == 'c' + + if (!tx.IsCoinImport()) { - // try all test vouts: - for (auto t : testVouts) { - if (t.first == tx.vout[v]) { - LOGSTREAM((char *)"cctokens", CCLOG_DEBUG1, stream << indentStr << "IsTokensvout() valid amount=" << tx.vout[v].nValue << " msg=" << t.second << " evalCode=" << (int)evalCode << " evalCode2=" << (int)evalCode2 << " txid=" << tx.GetHash().GetHex() << " tokenid=" << reftokenid.GetHex() << std::endl); - return tx.vout[v].nValue; + vscript_t vorigPubkey; + std::string dummyName, dummyDescription; + std::vector> oprets; + + if (DecodeTokenCreateOpRet(tx.vout.back().scriptPubKey, vorigPubkey, dummyName, dummyDescription, oprets) == 0) { + LOGSTREAM((char *)"cctokens", CCLOG_INFO, stream << indentStr << "IsTokensvout() could not decode create opret" << " for txid=" << tx.GetHash().GetHex() << " for tokenid=" << reftokenid.GetHex() << std::endl); + return 0; + } + + CPubKey origPubkey = pubkey2pk(vorigPubkey); + + + // TODO: add voutPubkeys for 'c' tx + + /* this would not work for imported tokens: + // for 'c' recognize the tokens only to token originator pubkey (but not to unspendable <-- closed sec violation) + // maybe this is like gatewayclaim to single-eval token? + if (evalCodeNonfungible == 0) // do not allow to convert non-fungible to fungible token + testVouts.push_back(std::make_pair(MakeCC1vout(EVAL_TOKENS, tx.vout[v].nValue, origPubkey), std::string("single-eval cc1 orig-pk"))); + // maybe this is like FillSell for non-fungible token? + if (evalCode1 != 0) + testVouts.push_back(std::make_pair(MakeTokensCC1vout(evalCode1, tx.vout[v].nValue, origPubkey), std::string("dual-eval-token cc1 orig-pk"))); */ + + // note: this would not work if there are several pubkeys in the tokencreator's wallet (AddNormalinputs does not use pubkey param): + // for tokenbase tx check that normal inputs sent from origpubkey > cc outputs + int64_t ccOutputs = 0; + for (auto vout : tx.vout) + if (vout.scriptPubKey.IsPayToCryptoCondition() //TODO: add voutPubkey validation + && !IsTokenMarkerVout(vout)) // should not be marker here + ccOutputs += vout.nValue; + + int64_t normalInputs = TotalPubkeyNormalInputs(tx, origPubkey); // check if normal inputs are really signed by originator pubkey (someone not cheating with originator pubkey) + LOGSTREAM("cctokens", CCLOG_DEBUG2, stream << indentStr << "IsTokensvout() normalInputs=" << normalInputs << " ccOutputs=" << ccOutputs << " for tokenbase=" << reftokenid.GetHex() << std::endl); + + if (normalInputs >= ccOutputs) { + LOGSTREAM("cctokens", CCLOG_DEBUG2, stream << indentStr << "IsTokensvout() assured normalInputs >= ccOutputs" << " for tokenbase=" << reftokenid.GetHex() << std::endl); + if (!IsTokenMarkerVout(tx.vout[v])) // exclude marker + return tx.vout[v].nValue; + else + return 0; // vout is good, but do not take marker into account + } + else { + LOGSTREAM("cctokens", CCLOG_INFO, stream << indentStr << "IsTokensvout() skipping vout not fulfilled normalInputs >= ccOutput" << " for tokenbase=" << reftokenid.GetHex() << " normalInputs=" << normalInputs << " ccOutputs=" << ccOutputs << std::endl); + } } - } - LOGSTREAM((char *)"cctokens", CCLOG_DEBUG1, stream << indentStr << "IsTokensvout() no valid vouts evalCode=" << (int)evalCode << " evalCode2=" << (int)evalCode2 << " for txid=" << tx.GetHash().GetHex() << " for tokenid=" << reftokenid.GetHex() << std::endl); + else { + // imported tokens are checked in the eval::ImportCoin() validation code + if (!IsTokenMarkerVout(tx.vout[v])) // exclude marker + return tx.vout[v].nValue; + else + return 0; // vout is good, but do not take marker into account + } + } + LOGSTREAM("cctokens", CCLOG_DEBUG1, stream << indentStr << "IsTokensvout() no valid vouts evalCode=" << (int)evalCode1 << " evalCode2=" << (int)evalCode2 << " for txid=" << tx.GetHash().GetHex() << " for tokenid=" << reftokenid.GetHex() << std::endl); } - //std::cerr << indentStr; fprintf(stderr,"IsTokensvout() CC vout v.%d of n=%d amount=%.8f txid=%s\n",v,n,(double)0/COIN, tx.GetHash().GetHex().c_str()); } //std::cerr << indentStr; fprintf(stderr,"IsTokensvout() normal output v.%d %.8f\n",v,(double)tx.vout[v].nValue/COIN); return(0); } +bool IsTokenMarkerVout(CTxOut vout) { + struct CCcontract_info *cpTokens, CCtokens_info; + cpTokens = CCinit(&CCtokens_info, EVAL_TOKENS); + return vout == MakeCC1vout(EVAL_TOKENS, vout.nValue, GetUnspendable(cpTokens, NULL)); +} + // compares cc inputs vs cc outputs (to prevent feeding vouts from normal inputs) bool TokensExactAmounts(bool goDeeper, struct CCcontract_info *cp, int64_t &inputs, int64_t &outputs, Eval* eval, const CTransaction &tx, uint256 reftokenid) { @@ -582,6 +615,7 @@ int64_t AddTokenCCInputs(struct CCcontract_info *cp, CMutableTransaction &mtx, C GetTokensCCaddress(cp, tokenaddr, pk); SetCCunspents(unspentOutputs, tokenaddr,true); + if (unspentOutputs.empty()) { LOGSTREAM((char *)"cctokens", CCLOG_INFO, stream << "AddTokenCCInputs() no utxos for token dual/three eval addr=" << tokenaddr << " evalcode=" << (int)cp->evalcode << " additionalTokensEvalcode2=" << (int)cp->additionalTokensEvalcode2 << std::endl); } @@ -747,7 +781,7 @@ CPubKey GetTokenOriginatorPubKey(CScript scriptPubKey) { return CPubKey(); //return invalid pubkey } - +// returns token creation signed raw tx std::string CreateToken(int64_t txfee, int64_t tokensupply, std::string name, std::string description, vscript_t nonfungibleData) { CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), komodo_nextheight()); @@ -767,7 +801,7 @@ std::string CreateToken(int64_t txfee, int64_t tokensupply, std::string name, st cp = CCinit(&C, EVAL_TOKENS); if (name.size() > 32 || description.size() > 4096) // this is also checked on rpc level { - LOGSTREAM((char *)"cctokens", CCLOG_INFO, stream << "name len=" << name.size() << " or description len=" << description.size() << " is too big" << std::endl); + LOGSTREAM((char *)"cctokens", CCLOG_DEBUG1, stream << "name len=" << name.size() << " or description len=" << description.size() << " is too big" << std::endl); CCerror = "name should be <= 32, description should be <= 4096"; return(""); } @@ -777,6 +811,13 @@ std::string CreateToken(int64_t txfee, int64_t tokensupply, std::string name, st if (AddNormalinputs(mtx, mypk, tokensupply + 2 * txfee, 64) > 0) { + + int64_t mypkInputs = TotalPubkeyNormalInputs(mtx, mypk); + if (mypkInputs < tokensupply) { // check that tokens amount are really issued with mypk (because in the wallet there maybe other privkeys) + CCerror = "some inputs signed not with -pubkey=pk"; + return std::string(""); + } + uint8_t destEvalCode = EVAL_TOKENS; if( nonfungibleData.size() > 0 ) destEvalCode = nonfungibleData.begin()[0]; @@ -843,7 +884,7 @@ std::string TokenTransfer(int64_t txfee, uint256 tokenid, vscript_t destpubkey, } else { CCerror = strprintf("no token inputs"); - LOGSTREAM((char *)"cctokens", CCLOG_INFO, stream << "TokenTransfer() " << CCerror << total << std::endl); + LOGSTREAM((char *)"cctokens", CCLOG_INFO, stream << "TokenTransfer() " << CCerror << " for amount=" << total << std::endl); } //} else fprintf(stderr,"numoutputs.%d != numamounts.%d\n",n,(int32_t)amounts.size()); } @@ -880,7 +921,7 @@ UniValue TokenInfo(uint256 tokenid) { UniValue result(UniValue::VOBJ); uint256 hashBlock; - CTransaction vintx; + CTransaction tokenbaseTx; std::vector origpubkey; std::vector> oprets; vscript_t vopretNonfungible; @@ -889,14 +930,14 @@ UniValue TokenInfo(uint256 tokenid) cpTokens = CCinit(&tokensCCinfo, EVAL_TOKENS); - if( !GetTransaction(tokenid, vintx, hashBlock, false) ) + if( !GetTransaction(tokenid, tokenbaseTx, hashBlock, false) ) { fprintf(stderr, "TokenInfo() cant find tokenid\n"); result.push_back(Pair("result", "error")); result.push_back(Pair("error", "cant find tokenid")); return(result); } - if (vintx.vout.size() > 0 && DecodeTokenCreateOpRet(vintx.vout[vintx.vout.size() - 1].scriptPubKey, origpubkey, name, description, oprets) != 'c') + if (tokenbaseTx.vout.size() > 0 && DecodeTokenCreateOpRet(tokenbaseTx.vout[tokenbaseTx.vout.size() - 1].scriptPubKey, origpubkey, name, description, oprets) != 'c') { LOGSTREAM((char *)"cctokens", CCLOG_INFO, stream << "TokenInfo() passed tokenid isnt token creation txid" << std::endl); result.push_back(Pair("result", "error")); @@ -909,8 +950,8 @@ UniValue TokenInfo(uint256 tokenid) result.push_back(Pair("name", name)); int64_t supply = 0, output; - for (int v = 0; v < vintx.vout.size() - 1; v++) - if ((output = IsTokensvout(false, true, cpTokens, NULL, vintx, v, tokenid)) > 0) + for (int v = 0; v < tokenbaseTx.vout.size() - 1; v++) + if ((output = IsTokensvout(false, true, cpTokens, NULL, tokenbaseTx, v, tokenid)) > 0) supply += output; result.push_back(Pair("supply", supply)); result.push_back(Pair("description", description)); @@ -919,6 +960,40 @@ UniValue TokenInfo(uint256 tokenid) if( !vopretNonfungible.empty() ) result.push_back(Pair("data", HexStr(vopretNonfungible))); + if (tokenbaseTx.IsCoinImport()) { // if imported token + ImportProof proof; + CTransaction burnTx; + std::vector payouts; + CTxDestination importaddress; + + std::string sourceSymbol = "can't decode"; + std::string sourceTokenId = "can't decode"; + + if (UnmarshalImportTx(tokenbaseTx, proof, burnTx, payouts)) + { + // extract op_return to get burn source chain. + std::vector burnOpret; + std::string targetSymbol; + uint32_t targetCCid; + uint256 payoutsHash; + std::vector rawproof; + if (UnmarshalBurnTx(burnTx, targetSymbol, &targetCCid, payoutsHash, rawproof)) { + if (rawproof.size() > 0) { + CTransaction tokenbasetx; + E_UNMARSHAL(rawproof, ss >> sourceSymbol; + if (!ss.eof()) + ss >> tokenbasetx); + + if (!tokenbasetx.IsNull()) + sourceTokenId = tokenbasetx.GetHash().GetHex(); + } + } + } + result.push_back(Pair("IsImported", "yes")); + result.push_back(Pair("sourceChain", sourceSymbol)); + result.push_back(Pair("sourceTokenId", sourceTokenId)); + } + return result; } diff --git a/src/cc/CCtokens.h b/src/cc/CCtokens.h index f63c563a9..3705a8f6d 100644 --- a/src/cc/CCtokens.h +++ b/src/cc/CCtokens.h @@ -32,6 +32,7 @@ std::string CreateToken(int64_t txfee, int64_t assetsupply, std::string name, st std::string TokenTransfer(int64_t txfee, uint256 assetid, std::vector destpubkey, int64_t total); int64_t HasBurnedTokensvouts(struct CCcontract_info *cp, Eval* eval, const CTransaction& tx, uint256 reftokenid); CPubKey GetTokenOriginatorPubKey(CScript scriptPubKey); +bool IsTokenMarkerVout(CTxOut vout); int64_t GetTokenBalance(CPubKey pk, uint256 tokenid); UniValue TokenInfo(uint256 tokenid); diff --git a/src/cc/CCtokensOpRet.cpp b/src/cc/CCtokenutils.cpp similarity index 69% rename from src/cc/CCtokensOpRet.cpp rename to src/cc/CCtokenutils.cpp index 4c6dc4b6d..73209bcb5 100644 --- a/src/cc/CCtokensOpRet.cpp +++ b/src/cc/CCtokenutils.cpp @@ -1,5 +1,21 @@ +/****************************************************************************** +* Copyright © 2014-2019 The SuperNET Developers. * +* * +* See the AUTHORS, DEVELOPER-AGREEMENT and LICENSE files at * +* the top-level directory of this distribution for the individual copyright * +* holder information and the developer policies on copyright and licensing. * +* * +* Unless otherwise agreed in a custom licensing agreement, no part of the * +* SuperNET software, including this file may be copied, modified, propagated * +* or distributed except according to the terms contained in the LICENSE file * +* * +* Removal or modification of this copyright notice is prohibited. * +* * +******************************************************************************/ + // encode decode tokens opret -// (moved to a separate file to enable linking lib common.so with importcoin.cpp) +// make token cryptoconditions and vouts +// This code was moved to a separate source file to enable linking libcommon.so (with importcoin.cpp which depends on some token functions) #include "CCtokens.h" @@ -44,6 +60,7 @@ CScript EncodeTokenCreateOpRet(uint8_t funcid, std::vector origpubkey, return(opret); } +/* // opret 'i' for imported tokens CScript EncodeTokenImportOpRet(std::vector origpubkey, std::string name, std::string description, uint256 srctokenid, std::vector> oprets) { @@ -62,7 +79,7 @@ CScript EncodeTokenImportOpRet(std::vector origpubkey, std::string name }); return(opret); } - +*/ CScript EncodeTokenOpRet(uint256 tokenid, std::vector voutPubkeys, std::pair opretWithId) @@ -158,37 +175,9 @@ uint8_t DecodeTokenCreateOpRet(const CScript &scriptPubKey, std::vector return (uint8_t)0; } -// for imported tokens -uint8_t DecodeTokenImportOpRet(const CScript &scriptPubKey, std::vector &origpubkey, std::string &name, std::string &description, uint256 &srctokenid, std::vector> &oprets) -{ - vscript_t vopret, vblob; - uint8_t dummyEvalcode, funcid, opretId = 0; - - GetOpReturnData(scriptPubKey, vopret); - oprets.clear(); - - if (vopret.size() > 2 && vopret.begin()[0] == EVAL_TOKENS && vopret.begin()[1] == 'i') - { - if (E_UNMARSHAL(vopret, ss >> dummyEvalcode; ss >> funcid; ss >> origpubkey; ss >> name; ss >> description; ss >> srctokenid; - while (!ss.eof()) { - ss >> opretId; - if (!ss.eof()) { - ss >> vblob; - oprets.push_back(std::make_pair(opretId, vblob)); - } - })) - { - srctokenid = revuint256(srctokenid); // do not forget this - return(funcid); - } - } - LOGSTREAM((char *)"cctokens", CCLOG_INFO, stream << "DecodeTokenImportOpRet() incorrect token import opret" << std::endl); - return (uint8_t)0; -} - -// decodes token opret: +// decode token opret: // for 't' returns all data from opret, vopretExtra contains other contract's data (currently only assets'). -// for 'c' and 'i' returns only funcid. NOTE: nonfungible data is not returned +// for 'c' returns only funcid. NOTE: nonfungible data is not returned uint8_t DecodeTokenOpRet(const CScript scriptPubKey, uint8_t &evalCodeTokens, uint256 &tokenid, std::vector &voutPubkeys, std::vector> &oprets) { vscript_t vopret, vblob, dummyPubkey, vnonfungibleDummy; @@ -207,9 +196,6 @@ uint8_t DecodeTokenOpRet(const CScript scriptPubKey, uint8_t &evalCodeTokens, ui if (script != NULL && vopret.size() > 2) { - // NOTE: if parse error occures, parse might not be able to set error. It is safer to treat that it was eof if it is not set! - // bool isEof = true; - evalCodeTokens = script[0]; if (evalCodeTokens != EVAL_TOKENS) { LOGSTREAM((char *)"cctokens", CCLOG_INFO, stream << "DecodeTokenOpRet() incorrect evalcode in tokens opret" << std::endl); @@ -217,15 +203,13 @@ uint8_t DecodeTokenOpRet(const CScript scriptPubKey, uint8_t &evalCodeTokens, ui } funcId = script[1]; - LOGSTREAM((char *)"cctokens", CCLOG_DEBUG2, stream << "DecodeTokenOpRet decoded funcId=" << (char)(funcId ? funcId : ' ') << std::endl); + LOGSTREAM((char *)"cctokens", CCLOG_DEBUG2, stream << "DecodeTokenOpRet() decoded funcId=" << (char)(funcId ? funcId : ' ') << std::endl); switch (funcId) { case 'c': return DecodeTokenCreateOpRet(scriptPubKey, dummyPubkey, dummyName, dummyDescription, oprets); - case 'i': - return DecodeTokenImportOpRet(scriptPubKey, dummyPubkey, dummyName, dummyDescription, dummySrcTokenId, oprets); - //break; + case 't': // compatibility with old-style rogue or assets data (with no opretid): @@ -293,4 +277,75 @@ uint8_t DecodeTokenOpRet(const CScript scriptPubKey, uint8_t &evalCodeTokens, ui } +// make three-eval (token+evalcode+evalcode2) 1of2 cryptocondition: +CC *MakeTokensCCcond1of2(uint8_t evalcode, uint8_t evalcode2, CPubKey pk1, CPubKey pk2) +{ + // make 1of2 sigs cond + std::vector pks; + pks.push_back(CCNewSecp256k1(pk1)); + pks.push_back(CCNewSecp256k1(pk2)); + + std::vector thresholds; + thresholds.push_back(CCNewEval(E_MARSHAL(ss << evalcode))); + if (evalcode != EVAL_TOKENS) // if evalCode == EVAL_TOKENS, it is actually MakeCCcond1of2()! + thresholds.push_back(CCNewEval(E_MARSHAL(ss << (uint8_t)EVAL_TOKENS))); // this is eval token cc + if (evalcode2 != 0) + thresholds.push_back(CCNewEval(E_MARSHAL(ss << evalcode2))); // add optional additional evalcode + thresholds.push_back(CCNewThreshold(1, pks)); // this is 1 of 2 sigs cc + + return CCNewThreshold(thresholds.size(), thresholds); +} +// overload to make two-eval (token+evalcode) 1of2 cryptocondition: +CC *MakeTokensCCcond1of2(uint8_t evalcode, CPubKey pk1, CPubKey pk2) { + return MakeTokensCCcond1of2(evalcode, 0, pk1, pk2); +} + +// make three-eval (token+evalcode+evalcode2) cryptocondition: +CC *MakeTokensCCcond1(uint8_t evalcode, uint8_t evalcode2, CPubKey pk) +{ + std::vector pks; + pks.push_back(CCNewSecp256k1(pk)); + + std::vector thresholds; + thresholds.push_back(CCNewEval(E_MARSHAL(ss << evalcode))); + if (evalcode != EVAL_TOKENS) // if evalCode == EVAL_TOKENS, it is actually MakeCCcond1()! + thresholds.push_back(CCNewEval(E_MARSHAL(ss << (uint8_t)EVAL_TOKENS))); // this is eval token cc + if (evalcode2 != 0) + thresholds.push_back(CCNewEval(E_MARSHAL(ss << evalcode2))); // add optional additional evalcode + thresholds.push_back(CCNewThreshold(1, pks)); // signature + + return CCNewThreshold(thresholds.size(), thresholds); +} +// overload to make two-eval (token+evalcode) cryptocondition: +CC *MakeTokensCCcond1(uint8_t evalcode, CPubKey pk) { + return MakeTokensCCcond1(evalcode, 0, pk); +} + +// make three-eval (token+evalcode+evalcode2) 1of2 cc vout: +CTxOut MakeTokensCC1of2vout(uint8_t evalcode, uint8_t evalcode2, CAmount nValue, CPubKey pk1, CPubKey pk2) +{ + CTxOut vout; + CC *payoutCond = MakeTokensCCcond1of2(evalcode, evalcode2, pk1, pk2); + vout = CTxOut(nValue, CCPubKey(payoutCond)); + cc_free(payoutCond); + return(vout); +} +// overload to make two-eval (token+evalcode) 1of2 cc vout: +CTxOut MakeTokensCC1of2vout(uint8_t evalcode, CAmount nValue, CPubKey pk1, CPubKey pk2) { + return MakeTokensCC1of2vout(evalcode, 0, nValue, pk1, pk2); +} + +// make three-eval (token+evalcode+evalcode2) cc vout: +CTxOut MakeTokensCC1vout(uint8_t evalcode, uint8_t evalcode2, CAmount nValue, CPubKey pk) +{ + CTxOut vout; + CC *payoutCond = MakeTokensCCcond1(evalcode, evalcode2, pk); + vout = CTxOut(nValue, CCPubKey(payoutCond)); + cc_free(payoutCond); + return(vout); +} +// overload to make two-eval (token+evalcode) cc vout: +CTxOut MakeTokensCC1vout(uint8_t evalcode, CAmount nValue, CPubKey pk) { + return MakeTokensCC1vout(evalcode, 0, nValue, pk); +} diff --git a/src/cc/CCutils.cpp b/src/cc/CCutils.cpp index a34bf1a81..e9acfbe20 100644 --- a/src/cc/CCutils.cpp +++ b/src/cc/CCutils.cpp @@ -93,79 +93,6 @@ CTxOut MakeCC1of2vout(uint8_t evalcode,CAmount nValue,CPubKey pk1,CPubKey pk2, c return(vout); } -// make three-eval (token+evalcode+evalcode2) 1of2 cryptocondition: -CC *MakeTokensCCcond1of2(uint8_t evalcode, uint8_t evalcode2, CPubKey pk1, CPubKey pk2) -{ - // make 1of2 sigs cond - std::vector pks; - pks.push_back(CCNewSecp256k1(pk1)); - pks.push_back(CCNewSecp256k1(pk2)); - - std::vector thresholds; - thresholds.push_back( CCNewEval(E_MARSHAL(ss << evalcode)) ); - if( evalcode != EVAL_TOKENS ) // if evalCode == EVAL_TOKENS, it is actually MakeCCcond1of2()! - thresholds.push_back(CCNewEval(E_MARSHAL(ss << (uint8_t)EVAL_TOKENS))); // this is eval token cc - if( evalcode2 != 0 ) - thresholds.push_back(CCNewEval(E_MARSHAL(ss << evalcode2))); // add optional additional evalcode - thresholds.push_back(CCNewThreshold(1, pks)); // this is 1 of 2 sigs cc - - return CCNewThreshold(thresholds.size(), thresholds); -} -// overload to make two-eval (token+evalcode) 1of2 cryptocondition: -CC *MakeTokensCCcond1of2(uint8_t evalcode, CPubKey pk1, CPubKey pk2) { - return MakeTokensCCcond1of2(evalcode, 0, pk1, pk2); -} - -// make three-eval (token+evalcode+evalcode2) cryptocondition: -CC *MakeTokensCCcond1(uint8_t evalcode, uint8_t evalcode2, CPubKey pk) -{ - std::vector pks; - pks.push_back(CCNewSecp256k1(pk)); - - std::vector thresholds; - thresholds.push_back(CCNewEval(E_MARSHAL(ss << evalcode))); - if (evalcode != EVAL_TOKENS) // if evalCode == EVAL_TOKENS, it is actually MakeCCcond1()! - thresholds.push_back(CCNewEval(E_MARSHAL(ss << (uint8_t)EVAL_TOKENS))); // this is eval token cc - if (evalcode2 != 0) - thresholds.push_back(CCNewEval(E_MARSHAL(ss << evalcode2))); // add optional additional evalcode - thresholds.push_back(CCNewThreshold(1, pks)); // signature - - return CCNewThreshold(thresholds.size(), thresholds); -} -// overload to make two-eval (token+evalcode) cryptocondition: -CC *MakeTokensCCcond1(uint8_t evalcode, CPubKey pk) { - return MakeTokensCCcond1(evalcode, 0, pk); -} - -// make three-eval (token+evalcode+evalcode2) 1of2 cc vout: -CTxOut MakeTokensCC1of2vout(uint8_t evalcode, uint8_t evalcode2, CAmount nValue, CPubKey pk1, CPubKey pk2) -{ - CTxOut vout; - CC *payoutCond = MakeTokensCCcond1of2(evalcode, evalcode2, pk1, pk2); - vout = CTxOut(nValue, CCPubKey(payoutCond)); - cc_free(payoutCond); - return(vout); -} -// overload to make two-eval (token+evalcode) 1of2 cc vout: -CTxOut MakeTokensCC1of2vout(uint8_t evalcode, CAmount nValue, CPubKey pk1, CPubKey pk2) { - return MakeTokensCC1of2vout(evalcode, 0, nValue, pk1, pk2); -} - -// make three-eval (token+evalcode+evalcode2) cc vout: -CTxOut MakeTokensCC1vout(uint8_t evalcode, uint8_t evalcode2, CAmount nValue, CPubKey pk) -{ - CTxOut vout; - CC *payoutCond = MakeTokensCCcond1(evalcode, evalcode2, pk); - vout = CTxOut(nValue, CCPubKey(payoutCond)); - cc_free(payoutCond); - return(vout); -} -// overload to make two-eval (token+evalcode) cc vout: -CTxOut MakeTokensCC1vout(uint8_t evalcode, CAmount nValue, CPubKey pk) { - return MakeTokensCC1vout(evalcode, 0, nValue, pk); -} - - CC* GetCryptoCondition(CScript const& scriptSig) { auto pc = scriptSig.begin(); @@ -709,6 +636,57 @@ CPubKey check_signing_pubkey(CScript scriptSig) return CPubKey(); } + +// returns total of normal inputs signed with this pubkey +int64_t TotalPubkeyNormalInputs(const CTransaction &tx, const CPubKey &pubkey) +{ + int64_t total = 0; + for (auto vin : tx.vin) { + CTransaction vintx; + uint256 hashBlock; + if (!IsCCInput(vin.scriptSig) && myGetTransaction(vin.prevout.hash, vintx, hashBlock)) { + typedef std::vector valtype; + std::vector vSolutions; + txnouttype whichType; + + if (Solver(vintx.vout[vin.prevout.n].scriptPubKey, whichType, vSolutions)) { + switch (whichType) { + case TX_PUBKEY: + if (pubkey == CPubKey(vSolutions[0])) // is my input? + total += vintx.vout[vin.prevout.n].nValue; + break; + case TX_PUBKEYHASH: + if (pubkey.GetID() == CKeyID(uint160(vSolutions[0]))) // is my input? + total += vintx.vout[vin.prevout.n].nValue; + break; + } + } + } + } + return total; +} + +// returns total of CC inputs signed with this pubkey +int64_t TotalPubkeyCCInputs(const CTransaction &tx, const CPubKey &pubkey) +{ + int64_t total = 0; + for (auto vin : tx.vin) { + if (IsCCInput(vin.scriptSig)) { + CPubKey vinPubkey = check_signing_pubkey(vin.scriptSig); + if (vinPubkey.IsValid()) { + if (vinPubkey == pubkey) { + CTransaction vintx; + uint256 hashBlock; + if (myGetTransaction(vin.prevout.hash, vintx, hashBlock)) { + total += vintx.vout[vin.prevout.n].nValue; + } + } + } + } + } + return total; +} + bool ProcessCC(struct CCcontract_info *cp,Eval* eval, std::vector paramsNull,const CTransaction &ctx, unsigned int nIn) { CTransaction createTx; uint256 assetid,assetid2,hashBlock; uint8_t funcid; int32_t height,i,n,from_mempool = 0; int64_t amount; std::vector origpubkey; diff --git a/src/cc/import.cpp b/src/cc/import.cpp index ea22f5e42..45dd16732 100644 --- a/src/cc/import.cpp +++ b/src/cc/import.cpp @@ -20,9 +20,18 @@ #include "primitives/transaction.h" #include "cc/CCinclude.h" #include +#include "cc/CCtokens.h" #include "key_io.h" #define CODA_BURN_ADDRESS "KPrrRoPfHOnNpZZQ6laHXdQDkSQDkVHaN0V+LizLlHxz7NaA59sBAAAA" +/* + * 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. + + ##### 0xffffffff is a special CCid for single chain/dual daemon imports + */ extern std::string ASSETCHAINS_SELFIMPORT; extern uint16_t ASSETCHAINS_CODAPORT,ASSETCHAINS_BEAMPORT; @@ -60,64 +69,65 @@ cJSON* CodaRPC(char **retstr,char const *arg0,char const *arg1,char const *arg2, } // makes source tx for self import tx -std::string MakeSelfImportSourceTx(CTxDestination &dest, int64_t amount, CMutableTransaction &mtx) +CMutableTransaction MakeSelfImportSourceTx(CTxDestination &dest, int64_t amount) { const int64_t txfee = 10000; int64_t inputs, change; CPubKey myPubKey = Mypubkey(); struct CCcontract_info *cpDummy, C; - cpDummy = CCinit(&C, EVAL_TOKENS); + cpDummy = CCinit(&C, EVAL_TOKENS); // this is just for FinalizeCCTx to work - mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), komodo_nextheight()); + CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), komodo_nextheight()); - if( (inputs = AddNormalinputs(mtx, myPubKey, txfee, 4)) == 0 ) { - LOGSTREAM("importcoin", CCLOG_INFO, stream << "MakeSelfImportSourceTx: cannot find normal imputs for txfee" << std::endl); - return std::string(""); + if (AddNormalinputs(mtx, myPubKey, 2 * txfee, 4) == 0) { + LOGSTREAM("importcoin", CCLOG_INFO, stream << "MakeSelfImportSourceTx() warning: cannot find normal inputs for txfee" << std::endl); } - + CScript scriptPubKey = GetScriptForDestination(dest); mtx.vout.push_back(CTxOut(txfee, scriptPubKey)); - change = inputs - txfee; - if( change != 0 ) - mtx.vout.push_back(CTxOut(change, CScript() << ParseHex(HexStr(myPubKey)) << OP_CHECKSIG)); - //make opret with amount: - return FinalizeCCTx(0, cpDummy, mtx, myPubKey, txfee, CScript() << OP_RETURN << E_MARSHAL(ss << (uint8_t)EVAL_IMPORTCOIN << (uint8_t)'A' << amount)); + //make opret with 'burned' amount: + FinalizeCCTx(0, cpDummy, mtx, myPubKey, txfee, CScript() << OP_RETURN << E_MARSHAL(ss << (uint8_t)EVAL_IMPORTCOIN << (uint8_t)'A' << amount)); + return mtx; } -// make sure vin0 is signed by ASSETCHAINS_OVERRIDE_PUBKEY33 -int32_t CheckVin0PubKey(const CTransaction &sourcetx) +// make sure vin is signed by pubkey33 +bool CheckVinPubKey(const CTransaction &sourcetx, int32_t i, uint8_t pubkey33[33]) { CTransaction vintx; uint256 blockHash; char destaddr[64], pkaddr[64]; - if( !myGetTransaction(sourcetx.vin[0].prevout.hash, vintx, blockHash) ) { - LOGSTREAM("importcoin", CCLOG_INFO, stream << "CheckVin0PubKey() could not load vintx" << sourcetx.vin[0].prevout.hash.GetHex() << std::endl); - return(-1); + if (i < 0 || i >= sourcetx.vin.size()) + return false; + + if( !myGetTransaction(sourcetx.vin[i].prevout.hash, vintx, blockHash) ) { + LOGSTREAM("importcoin", CCLOG_INFO, stream << "CheckVinPubKey() could not load vintx" << sourcetx.vin[i].prevout.hash.GetHex() << std::endl); + return false; } - if( sourcetx.vin[0].prevout.n < vintx.vout.size() && Getscriptaddress(destaddr, vintx.vout[sourcetx.vin[0].prevout.n].scriptPubKey) != 0 ) + if( sourcetx.vin[i].prevout.n < vintx.vout.size() && Getscriptaddress(destaddr, vintx.vout[sourcetx.vin[i].prevout.n].scriptPubKey) != 0 ) { - pubkey2addr(pkaddr, ASSETCHAINS_OVERRIDE_PUBKEY33); + pubkey2addr(pkaddr, pubkey33); if (strcmp(pkaddr, destaddr) == 0) { - return(0); + return true; } - LOGSTREAM("importcoin", CCLOG_INFO, stream << "CheckVin0PubKey() mismatched vin0[prevout.n=" << sourcetx.vin[0].prevout.n << "] -> destaddr=" << destaddr << " vs pkaddr=" << pkaddr << std::endl); + LOGSTREAM("importcoin", CCLOG_INFO, stream << "CheckVinPubKey() mismatched vin[" << i << "].prevout.n=" << sourcetx.vin[i].prevout.n << " -> destaddr=" << destaddr << " vs pkaddr=" << pkaddr << std::endl); } - return -1; + return false; } // ac_import=PUBKEY support: // prepare a tx for creating import tx and quasi-burn tx -int32_t GetSelfimportProof(std::string source, CMutableTransaction &mtx, CScript &scriptPubKey, TxProof &proof, std::string rawsourcetx, int32_t &ivout, uint256 sourcetxid, uint64_t burnAmount) // find burnTx with hash from "other" daemon +int32_t GetSelfimportProof(const CMutableTransaction &sourceMtx, CMutableTransaction &templateMtx, ImportProof &proofNull) // find burnTx with hash from "other" daemon { MerkleBranch newBranch; CMutableTransaction tmpmtx; - CTransaction sourcetx; + //CTransaction sourcetx; tmpmtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), komodo_nextheight()); + /* if (!E_UNMARSHAL(ParseHex(rawsourcetx), ss >> sourcetx)) { LOGSTREAM("importcoin", CCLOG_INFO, stream << "GetSelfimportProof: could not unmarshal source tx" << std::endl); return(-1); @@ -126,9 +136,9 @@ int32_t GetSelfimportProof(std::string source, CMutableTransaction &mtx, CScript if (sourcetx.vout.size() == 0) { LOGSTREAM("importcoin", CCLOG_INFO, stream << "GetSelfimportProof: vout size is 0" << std::endl); return -1; - } + } */ - if (ivout < 0) { // "ivout < 0" means "find" + /*if (ivout < 0) { // "ivout < 0" means "find" // try to find vout CPubKey myPubkey = Mypubkey(); ivout = 0; @@ -140,38 +150,49 @@ int32_t GetSelfimportProof(std::string source, CMutableTransaction &mtx, CScript if (ivout >= sourcetx.vout.size()) { LOGSTREAM("importcoin", CCLOG_INFO, stream << "GetSelfimportProof: needed vout not found" << std::endl); return -1; - } + } */ - LOGSTREAM("importcoin", CCLOG_DEBUG1, stream << "GetSelfimportProof: using vout[" << ivout << "] of the passed rawtx" << std::endl); + int32_t ivout = 0; - scriptPubKey = sourcetx.vout[ivout].scriptPubKey; + // LOGSTREAM("importcoin", CCLOG_DEBUG1, stream << "GetSelfimportProof: using vout[" << ivout << "] of the passed rawtx" << std::endl); + + CScript scriptPubKey = sourceMtx.vout[ivout].scriptPubKey; //mtx is template for import tx - mtx = sourcetx; - mtx.fOverwintered = tmpmtx.fOverwintered; + templateMtx = sourceMtx; + templateMtx.fOverwintered = tmpmtx.fOverwintered; //malleability fix for burn tx: //mtx.nExpiryHeight = tmpmtx.nExpiryHeight; - mtx.nExpiryHeight = sourcetx.nExpiryHeight; + templateMtx.nExpiryHeight = sourceMtx.nExpiryHeight; - mtx.nVersionGroupId = tmpmtx.nVersionGroupId; - mtx.nVersion = tmpmtx.nVersion; - mtx.vout.clear(); - mtx.vout.resize(1); - mtx.vout[0].nValue = burnAmount; - mtx.vout[0].scriptPubKey = scriptPubKey; + templateMtx.nVersionGroupId = tmpmtx.nVersionGroupId; + templateMtx.nVersion = tmpmtx.nVersion; + templateMtx.vout.clear(); + templateMtx.vout.resize(1); - // not sure we need this now as we create sourcetx ourselves: - if (sourcetx.GetHash() != sourcetxid) { - LOGSTREAM("importcoin", CCLOG_INFO, stream << "GetSelfimportProof: passed source txid incorrect" << std::endl); - return(-1); - } - - // check ac_pubkey: - if (CheckVin0PubKey(sourcetx) < 0) { + uint8_t evalCode, funcId; + int64_t burnAmount; + vscript_t vopret; + if( !GetOpReturnData(sourceMtx.vout.back().scriptPubKey, vopret) || + !E_UNMARSHAL(vopret, ss >> evalCode; ss >> funcId; ss >> burnAmount)) { + LOGSTREAM("importcoin", CCLOG_INFO, stream << "GetSelfimportProof() could not unmarshal source tx opret" << std::endl); return -1; } - proof = std::make_pair(sourcetxid, newBranch); + templateMtx.vout[0].nValue = burnAmount; + templateMtx.vout[0].scriptPubKey = scriptPubKey; + + // not sure we need this now as we create sourcetx ourselves: + /*if (sourcetx.GetHash() != sourcetxid) { + LOGSTREAM("importcoin", CCLOG_INFO, stream << "GetSelfimportProof: passed source txid incorrect" << std::endl); + return(-1); + }*/ + + // check ac_pubkey: + if (!CheckVinPubKey(sourceMtx, 0, ASSETCHAINS_OVERRIDE_PUBKEY33)) { + return -1; + } + proofNull = ImportProof(std::make_pair(sourceMtx.GetHash(), newBranch)); return 0; } @@ -470,7 +491,7 @@ int32_t CheckPUBKEYimport(TxProof proof,std::vector rawproof,CTransacti } //ac_pubkey check: - if (CheckVin0PubKey(sourcetx) < 0) { + if (!CheckVinPubKey(sourcetx, 0, ASSETCHAINS_OVERRIDE_PUBKEY33)) { return -1; } @@ -479,9 +500,11 @@ int32_t CheckPUBKEYimport(TxProof proof,std::vector rawproof,CTransacti uint8_t evalCode, funcId; int64_t amount; - GetOpReturnData(sourcetx.vout.back().scriptPubKey, vopret); - if (vopret.size() == 0 || !E_UNMARSHAL(vopret, ss >> evalCode; ss >> funcId; ss >> amount) || evalCode != EVAL_IMPORTCOIN || funcId != 'A') { - LOGSTREAM("importcoin", CCLOG_INFO, stream << "no or incorrect opret to validate in source txid=" << sourcetxid.GetHex() << std::endl); + if (!GetOpReturnData(sourcetx.vout.back().scriptPubKey, vopret) || + vopret.size() == 0 || + !E_UNMARSHAL(vopret, ss >> evalCode; ss >> funcId; ss >> amount) || + evalCode != EVAL_IMPORTCOIN || funcId != 'A') { + LOGSTREAM("importcoin", CCLOG_INFO, stream << "none or incorrect opret to validate in source txid=" << sourcetxid.GetHex() << std::endl); return -1; } @@ -496,21 +519,149 @@ int32_t CheckPUBKEYimport(TxProof proof,std::vector rawproof,CTransacti return(0); } -/* - * 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. - - ##### 0xffffffff is a special CCid for single chain/dual daemon imports - */ -bool Eval::ImportCoin(const std::vector params,const CTransaction &importTx,unsigned int nIn) +bool CheckFungible(Eval *eval, const CTransaction &importTx, const CTransaction &burnTx, std::vector & payouts, const ImportProof &proof, const std::vector &rawproof) { - TxProof proof; CTransaction burnTx; std::vector payouts; int64_t txfee = 10000, amount; int32_t height,burnvout; std::vector publishers; - uint32_t targetCcid; std::string targetSymbol,srcaddr,destaddr,receipt,rawburntx; uint256 payoutsHash,bindtxid,burntxid; std::vector rawproof; - std::vector txids; CPubKey destpub; + if (strcmp(ASSETCHAINS_SYMBOL, "CFEKDIMXY6") == 0 && chainActive.Height() <= 10699) + return true; - if ( importTx.vout.size() < 2 ) + vscript_t vimportOpret; + if (!GetOpReturnData(importTx.vout.back().scriptPubKey, vimportOpret) || + vimportOpret.empty()) + return eval->Invalid("invalid-import-tx-no-opret"); + + + uint256 tokenid = zeroid; + if (vimportOpret.begin()[0] == EVAL_TOKENS) { // for tokens (new opret with tokens) + struct CCcontract_info *cpTokens, CCtokens_info; + std::vector> oprets; + uint8_t evalCodeInOpret; + std::vector voutTokenPubkeys; + vscript_t vnonfungibleOpret; + + cpTokens = CCinit(&CCtokens_info, EVAL_TOKENS); + + if (DecodeTokenOpRet(importTx.vout.back().scriptPubKey, evalCodeInOpret, tokenid, voutTokenPubkeys, oprets) == 0) + return eval->Invalid("cannot-decode-import-tx-token-opret"); + + uint8_t nonfungibleEvalCode = EVAL_TOKENS; // init to no non-fungibles + GetOpretBlob(oprets, OPRETID_NONFUNGIBLEDATA, vnonfungibleOpret); + if (!vnonfungibleOpret.empty()) + nonfungibleEvalCode = vnonfungibleOpret.begin()[0]; + + // check if burn tx at least has cc evaltoken vins (we cannot get cc input) + bool hasTokenVin = false; + for (auto vin : burnTx.vin) + if (cpTokens->ismyvin(vin.scriptSig)) + hasTokenVin = true; + if (!hasTokenVin) + return eval->Invalid("burn-tx-has-no-token-vins"); + + // calc outputs for burn tx + CAmount ccBurnOutputs = 0; + for (auto v : burnTx.vout) + if (v.scriptPubKey.IsPayToCryptoCondition() && + CTxOut(v.nValue, v.scriptPubKey) == MakeTokensCC1vout(nonfungibleEvalCode, v.nValue, pubkey2pk(ParseHex(CC_BURNPUBKEY)))) // burned to dead pubkey + ccBurnOutputs += v.nValue; + + // calc outputs for import tx + CAmount ccImportOutputs = 0; + for (auto v : importTx.vout) + if (v.scriptPubKey.IsPayToCryptoCondition() && + !IsTokenMarkerVout(v)) // should not be marker here + ccImportOutputs += v.nValue; + + if (ccBurnOutputs != ccImportOutputs) + return eval->Invalid("token-cc-burned-output-not-equal-cc-imported-output"); + + } + else if (vimportOpret.begin()[0] != EVAL_IMPORTCOIN) { + return eval->Invalid("import-tx-incorrect-opret-eval"); + } + + // for tokens check burn, import, tokenbase tx + if (!tokenid.IsNull()) { + + std::string sourceSymbol; + CTransaction tokenbaseTx; + if (!E_UNMARSHAL(rawproof, ss >> sourceSymbol; ss >> tokenbaseTx)) + return eval->Invalid("cannot-unmarshal-rawproof-for-tokens"); + + uint256 sourceTokenId; + std::vector> oprets; + uint8_t evalCodeInOpret; + std::vector voutTokenPubkeys; + if (burnTx.vout.size() > 0 && DecodeTokenOpRet(burnTx.vout.back().scriptPubKey, evalCodeInOpret, sourceTokenId, voutTokenPubkeys, oprets) == 0) + return eval->Invalid("cannot-decode-burn-tx-token-opret"); + + if (sourceTokenId != tokenbaseTx.GetHash()) // check tokenid in burn tx opret maches the passed tokenbase tx (to prevent cheating by importing user) + return eval->Invalid("incorrect-token-creation-tx-passed"); + + std::vector> opretsSrc; + vscript_t vorigpubkeySrc; + std::string nameSrc, descSrc; + if (DecodeTokenCreateOpRet(tokenbaseTx.vout.back().scriptPubKey, vorigpubkeySrc, nameSrc, descSrc, opretsSrc) == 0) + return eval->Invalid("cannot-decode-token-creation-tx"); + + std::vector> opretsImport; + vscript_t vorigpubkeyImport; + std::string nameImport, descImport; + if (importTx.vout.size() == 0 || DecodeTokenCreateOpRet(importTx.vout.back().scriptPubKey, vorigpubkeySrc, nameSrc, descSrc, opretsImport) == 0) + return eval->Invalid("cannot-decode-token-import-tx"); + + // check that name,pubkey,description in import tx correspond ones in token creation tx in the source chain: + if (vorigpubkeySrc != vorigpubkeyImport || + nameSrc != nameImport || + descSrc != descImport) + return eval->Invalid("import-tx-token-params-incorrect"); + } + + + // Check burntx shows correct outputs hash +// if (payoutsHash != SerializeHash(payouts)) // done in ImportCoin +// return eval->Invalid("wrong-payouts"); + + + TxProof merkleBranchProof; + std::vector notaryTxids; + + // Check proof confirms existance of burnTx + if (proof.IsMerkleBranch(merkleBranchProof)) { + uint256 target = merkleBranchProof.second.Exec(burnTx.GetHash()); + LOGSTREAM("importcoin", CCLOG_DEBUG2, stream << "Eval::ImportCoin() momom target=" << target.GetHex() << " merkleBranchProof.first=" << merkleBranchProof.first.GetHex() << std::endl); + if (!CheckMoMoM(merkleBranchProof.first, target)) { + LOGSTREAM("importcoin", CCLOG_INFO, stream << "MoMoM check failed for importtx=" << importTx.GetHash().GetHex() << std::endl); + return eval->Invalid("momom-check-fail"); + } + } + else if (proof.IsNotaryTxids(notaryTxids)) { + if (!CheckNotariesApproval(burnTx.GetHash(), notaryTxids)) { + return eval->Invalid("notaries-approval-check-fail"); + } + } + else { + return eval->Invalid("invalid-import-proof"); + } + return true; +} + +bool Eval::ImportCoin(const std::vector params, const CTransaction &importTx, unsigned int nIn) +{ + ImportProof proof; + CTransaction burnTx; + std::vector payouts; + CAmount txfee = 10000; + int32_t height, burnvout; + std::vector publishers; + uint32_t targetCcid; + std::string targetSymbol, srcaddr, destaddr, receipt, rawburntx; + uint256 payoutsHash, bindtxid; + std::vector rawproof; + std::vector txids; + CPubKey destpub; + + LOGSTREAM("importcoin", CCLOG_DEBUG1, stream << "Validating import tx..., txid=" << importTx.GetHash().GetHex() << std::endl); + + if (importTx.vout.size() < 2) return Invalid("too-few-vouts"); // params if (!UnmarshalImportTx(importTx, proof, burnTx, payouts)) @@ -522,38 +673,47 @@ bool Eval::ImportCoin(const std::vector params,const CTransaction &impo // burn params if (!UnmarshalBurnTx(burnTx, targetSymbol, &targetCcid, payoutsHash, rawproof)) return Invalid("invalid-burn-tx"); - // check burn amount - { - uint64_t burnAmount = burnTx.vout.back().nValue; - if (burnAmount == 0) - return Invalid("invalid-burn-amount"); - uint64_t totalOut = 0; - for (int i=0; i burnAmount || totalOut < burnAmount-txfee ) - return Invalid("payout-too-high-or-too-low"); - } + + if (burnTx.vout.size() == 0) + return Invalid("invalid-burn-tx-no-vouts"); + + // check burned normal amount >= import amount && burned amount <= import amount + txfee (extra txfee is for miners and relaying, see GetImportCoinValue() func) + CAmount burnAmount = burnTx.vout.back().nValue; + if (burnAmount == 0) + return Invalid("invalid-burn-amount"); + CAmount totalOut = 0; + for (auto v : importTx.vout) + if (!v.scriptPubKey.IsPayToCryptoCondition()) + totalOut += v.nValue; + if (totalOut > burnAmount || totalOut < burnAmount - txfee) + return Invalid("payout-too-high-or-too-low"); + // Check burntx shows correct outputs hash if (payoutsHash != SerializeHash(payouts)) return Invalid("wrong-payouts"); if (targetCcid < KOMODO_FIRSTFUNGIBLEID) return Invalid("chain-not-fungible"); - // Check proof confirms existance of burnTx + if ( targetCcid != 0xffffffff ) { - if ( targetCcid != GetAssetchainsCC() || targetSymbol != GetAssetchainsSymbol() ) + + if (targetCcid != GetAssetchainsCC() || targetSymbol != GetAssetchainsSymbol()) return Invalid("importcoin-wrong-chain"); - uint256 target = proof.second.Exec(burnTx.GetHash()); - if (!CheckMoMoM(proof.first, target)) - return Invalid("momom-check-fail"); + + if (!CheckFungible(this, importTx, burnTx, payouts, proof, rawproof)) + return false; } else { + TxProof merkleBranchProof; + if (!proof.IsMerkleBranch(merkleBranchProof)) + return Invalid("invalid-import-proof-for-0xFFFFFFFF"); + if ( targetSymbol == "BEAM" ) { if ( ASSETCHAINS_BEAMPORT == 0 ) return Invalid("BEAM-import-without-port"); - else if ( CheckBEAMimport(proof,rawproof,burnTx,payouts) < 0 ) + else if ( CheckBEAMimport(merkleBranchProof,rawproof,burnTx,payouts) < 0 ) return Invalid("BEAM-import-failure"); } else if ( targetSymbol == "CODA" ) @@ -567,7 +727,7 @@ bool Eval::ImportCoin(const std::vector params,const CTransaction &impo { if ( ASSETCHAINS_SELFIMPORT != "PUBKEY" ) return Invalid("PUBKEY-import-when-notPUBKEY"); - else if ( CheckPUBKEYimport(proof,rawproof,burnTx,payouts) < 0 ) + else if ( CheckPUBKEYimport(merkleBranchProof,rawproof,burnTx,payouts) < 0 ) return Invalid("PUBKEY-import-failure"); } else @@ -578,5 +738,14 @@ bool Eval::ImportCoin(const std::vector params,const CTransaction &impo return Invalid("GATEWAY-import-failure"); } } + + // return Invalid("test-invalid"); + LOGSTREAM("importcoin", CCLOG_DEBUG2, stream << "Valid import tx! txid=" << importTx.GetHash().GetHex() << std::endl); + + /*if (vimportOpret.begin()[0] == EVAL_TOKENS) + return Invalid("test-invalid-tokens-are-good!!"); + else + return Invalid("test-invalid-coins-are-good!!");*/ + return Valid(); } diff --git a/src/crosschain.cpp b/src/crosschain.cpp index 82a46eaab..0a081c086 100644 --- a/src/crosschain.cpp +++ b/src/crosschain.cpp @@ -18,6 +18,9 @@ #include "importcoin.h" #include "main.h" #include "notarisationdb.h" +#include "merkleblock.h" + +#include "cc/CCinclude.h" /* * The crosschain workflow. @@ -229,22 +232,25 @@ cont: */ void CompleteImportTransaction(CTransaction &importTx, int32_t offset) { - TxProof proof; CTransaction burnTx; std::vector payouts; std::vector rawproof; + ImportProof proof; CTransaction burnTx; std::vector payouts; std::vector rawproof; if (!UnmarshalImportTx(importTx, proof, burnTx, payouts)) - throw std::runtime_error("Couldn't parse importTx"); + throw std::runtime_error("Couldn't unmarshal importTx"); std::string targetSymbol; uint32_t targetCCid; uint256 payoutsHash; if (!UnmarshalBurnTx(burnTx, targetSymbol, &targetCCid, payoutsHash, rawproof)) - throw std::runtime_error("Couldn't parse burnTx"); + throw std::runtime_error("Couldn't unmarshal burnTx"); - proof = GetCrossChainProof(burnTx.GetHash(), targetSymbol.data(), targetCCid, proof, offset); + TxProof merkleBranch; + if( !proof.IsMerkleBranch(merkleBranch) ) + throw std::runtime_error("Incorrect import tx proof"); + TxProof newMerkleBranch = GetCrossChainProof(burnTx.GetHash(), targetSymbol.data(), targetCCid, merkleBranch, offset); + ImportProof newProof(newMerkleBranch); - importTx = MakeImportCoinTransaction(proof, burnTx, payouts); + importTx = MakeImportCoinTransaction(newProof, burnTx, payouts); } - bool IsSameAssetChain(const Notarisation ¬a) { return strcmp(nota.second.symbol, ASSETCHAINS_SYMBOL) == 0; }; @@ -306,6 +312,111 @@ bool CheckMoMoM(uint256 kmdNotarisationHash, uint256 momom) } +/* +* Check notaries approvals for the txoutproofs of burn tx +* (alternate check if MoMoM check has failed) +* Params: +* burntxid - txid of burn tx on the source chain +* rawproof - array of txids of notaries' proofs +*/ +bool CheckNotariesApproval(uint256 burntxid, const std::vector & notaryTxids) +{ + int count = 0; + + // get notaries: + uint8_t notaries_pubkeys[64][33]; + std::vector< std::vector > alreadySigned; + + //unmarshal notaries approval txids + for(auto notarytxid : notaryTxids ) { + EvalRef eval; + CBlockIndex block; + CTransaction notarytx; // tx with notary approval of txproof existence + + // get notary approval tx + if (eval->GetTxConfirmed(notarytxid, notarytx, block)) { + + std::vector vopret; + if (!notarytx.vout.empty() && GetOpReturnData(notarytx.vout.back().scriptPubKey, vopret)) { + std::vector txoutproof; + + if (E_UNMARSHAL(vopret, ss >> txoutproof)) { + CMerkleBlock merkleBlock; + std::vector prooftxids; + // extract block's merkle tree + if (E_UNMARSHAL(txoutproof, ss >> merkleBlock)) { + + // extract proven txids: + merkleBlock.txn.ExtractMatches(prooftxids); + if (merkleBlock.txn.ExtractMatches(prooftxids) != merkleBlock.header.hashMerkleRoot || // check block merkle root is correct + std::find(prooftxids.begin(), prooftxids.end(), burntxid) != prooftxids.end()) { // check burn txid is in proven txids list + + if (komodo_notaries(notaries_pubkeys, block.GetHeight(), block.GetBlockTime()) >= 0) { + // check it is a notary who signed approved tx: + int i; + for (i = 0; i < sizeof(notaries_pubkeys) / sizeof(notaries_pubkeys[0]); i++) { + std::vector vnotarypubkey(notaries_pubkeys[i], notaries_pubkeys[i] + 33); +#ifdef TESTMODE + char test_notary_pubkey_hex[] = "029fa302968bbae81f41983d2ec20445557b889d31227caec5d910d19b7510ef86"; + uint8_t test_notary_pubkey33[33]; + decode_hex(test_notary_pubkey33, 33, test_notary_pubkey_hex); +#endif + if (CheckVinPubKey(notarytx, 0, notaries_pubkeys[i]) // is signed by a notary? + && std::find(alreadySigned.begin(), alreadySigned.end(), vnotarypubkey) == alreadySigned.end() // check if notary not re-used +#ifdef TESTMODE + || CheckVinPubKey(notarytx, 0, test_notary_pubkey33) // test +#endif + ) + { + alreadySigned.push_back(vnotarypubkey); + count++; + LOGSTREAM("importcoin", CCLOG_DEBUG1, stream << "CheckNotariesApproval() notary approval checked, count=" << count << std::endl); + break; + } + } + if (i == sizeof(notaries_pubkeys) / sizeof(notaries_pubkeys[0])) + LOGSTREAM("importcoin", CCLOG_DEBUG1, stream << "CheckNotariesApproval() txproof not signed by a notary or reused" << std::endl); + } + else { + LOGSTREAM("importcoin", CCLOG_INFO, stream << "CheckNotariesApproval() cannot get current notaries pubkeys" << std::endl); + } + } + else { + LOGSTREAM("importcoin", CCLOG_INFO, stream << "CheckNotariesApproval() burntxid not found in txoutproof or incorrect txoutproof" << std::endl); + } + } + else { + LOGSTREAM("importcoin", CCLOG_INFO, stream << "CheckNotariesApproval() could not unmarshal merkleBlock" << std::endl); + } + } + else { + LOGSTREAM("importcoin", CCLOG_INFO, stream << "CheckNotariesApproval() could not unmarshal txoutproof" << std::endl); + } + } + else { + LOGSTREAM("importcoin", CCLOG_INFO, stream << "CheckNotariesApproval() no opret in the notary tx" << std::endl); + } + } + else { + LOGSTREAM("importcoin", CCLOG_INFO, stream << "CheckNotariesApproval() could not load notary tx" << std::endl); + } + } + + bool retcode; +#ifdef TESTMODE + if (count < 1) { // 1 for test +#else + if (count < 5) { +#endif + LOGSTREAM("importcoin", CCLOG_INFO, stream << "CheckNotariesApproval() not enough signed notary transactions=" << count << std::endl); + retcode = false; + } + else + retcode = true; + + return retcode; +} + /* * On assetchain diff --git a/src/crosschain.h b/src/crosschain.h index 9ff41eff1..25763c01b 100644 --- a/src/crosschain.h +++ b/src/crosschain.h @@ -43,6 +43,6 @@ void CompleteImportTransaction(CTransaction &importTx,int32_t offset); /* On assetchain */ bool CheckMoMoM(uint256 kmdNotarisationHash, uint256 momom); - +bool CheckNotariesApproval(uint256 burntxid, const std::vector & notaryTxids); #endif /* CROSSCHAIN_H */ diff --git a/src/importcoin.cpp b/src/importcoin.cpp index 6702a78dd..d1b317425 100644 --- a/src/importcoin.cpp +++ b/src/importcoin.cpp @@ -24,28 +24,63 @@ #include "script/sign.h" #include "wallet/wallet.h" +#include "cc/CCinclude.h" + int32_t komodo_nextheight(); -CTransaction MakeImportCoinTransaction(const TxProof proof, const CTransaction burnTx, const std::vector payouts, uint32_t nExpiryHeightOverride) +// makes import tx for either coins or tokens +CTransaction MakeImportCoinTransaction(const ImportProof &proof, const CTransaction &burnTx, const std::vector &payouts, uint32_t nExpiryHeightOverride) { - std::vector payload = E_MARSHAL(ss << EVAL_IMPORTCOIN); + //std::vector payload = E_MARSHAL(ss << EVAL_IMPORTCOIN); + CScript scriptSig; + CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), komodo_nextheight()); if (mtx.fOverwintered) mtx.nExpiryHeight = 0; - mtx.vin.push_back(CTxIn(COutPoint(burnTx.GetHash(), 10e8), CScript() << payload)); mtx.vout = payouts; - auto importData = E_MARSHAL(ss << proof; ss << burnTx); - mtx.vout.push_back(CTxOut(0, CScript() << OP_RETURN << importData)); - if (nExpiryHeightOverride != 0) - mtx.nExpiryHeight = nExpiryHeightOverride; //this is for construction of the tx used for validating importtx + if (mtx.vout.size() == 0) + return CTransaction(mtx); + + // add special import tx vin: + scriptSig << E_MARSHAL(ss << EVAL_IMPORTCOIN); // simple payload for coins + mtx.vin.push_back(CTxIn(COutPoint(burnTx.GetHash(), 10e8), scriptSig)); + + if (nExpiryHeightOverride != 0) + mtx.nExpiryHeight = nExpiryHeightOverride; //this is for validation code, to make a tx used for validating the import tx + + auto importData = E_MARSHAL(ss << EVAL_IMPORTCOIN; ss << proof; ss << burnTx); // added evalcode to differentiate importdata from token opret + // if it is tokens: + vscript_t vopret; + GetOpReturnData(mtx.vout.back().scriptPubKey, vopret); + + if (!vopret.empty()) { + std::vector vorigpubkey; + uint8_t funcId; + std::vector > oprets; + std::string name, desc; + + if (DecodeTokenCreateOpRet(mtx.vout.back().scriptPubKey, vorigpubkey, name, desc, oprets) == 'c') { // parse token 'c' opret + mtx.vout.pop_back(); //remove old token opret + oprets.push_back(std::make_pair(OPRETID_IMPORTDATA, importData)); + mtx.vout.push_back(CTxOut(0, EncodeTokenCreateOpRet('c', vorigpubkey, name, desc, oprets))); // make new token 'c' opret with importData + } + else { + LOGSTREAM("importcoin", CCLOG_INFO, stream << "MakeImportCoinTransaction() incorrect token import opret" << std::endl); + } + } + else { //no opret in coin payouts + mtx.vout.push_back(CTxOut(0, CScript() << OP_RETURN << importData)); // import tx's opret now is in the vout's tail + } + return CTransaction(mtx); } -CTxOut MakeBurnOutput(CAmount value, uint32_t targetCCid, std::string targetSymbol, const std::vector payouts,std::vector rawproof) +CTxOut MakeBurnOutput(CAmount value, uint32_t targetCCid, const std::string &targetSymbol, const std::vector &payouts, const std::vector &rawproof) { std::vector opret; - opret = E_MARSHAL(ss << VARINT(targetCCid); + opret = E_MARSHAL(ss << (uint8_t)EVAL_IMPORTCOIN; // should mark burn opret to differentiate it from token opret + ss << VARINT(targetCCid); ss << targetSymbol; ss << SerializeHash(payouts); ss << rawproof); @@ -87,32 +122,103 @@ CTxOut MakeBurnOutput(CAmount value, uint32_t targetCCid, std::string targetSymb } -bool UnmarshalImportTx(const CTransaction importTx, TxProof &proof, CTransaction &burnTx, - std::vector &payouts) +bool UnmarshalImportTx(const CTransaction &importTx, ImportProof &proof, CTransaction &burnTx, std::vector &payouts) { - std::vector vData; - GetOpReturnData(importTx.vout[importTx.vout.size()-1].scriptPubKey, vData); - if (importTx.vout.size() < 1) return false; - payouts = std::vector(importTx.vout.begin(), importTx.vout.end()-1); - return importTx.vin.size() == 1 && - importTx.vin[0].scriptSig == (CScript() << E_MARSHAL(ss << EVAL_IMPORTCOIN)) && - E_UNMARSHAL(vData, ss >> proof; ss >> burnTx); + if (importTx.vout.size() < 1) + return false; + + if (importTx.vin.size() != 1 || importTx.vin[0].scriptSig != (CScript() << E_MARSHAL(ss << EVAL_IMPORTCOIN))) { + LOGSTREAM("importcoin", CCLOG_INFO, stream << "UnmarshalImportTx() incorrect import tx vin" << std::endl); + return false; + } + + std::vector vImportData; + GetOpReturnData(importTx.vout.back().scriptPubKey, vImportData); + if (vImportData.empty()) { + LOGSTREAM("importcoin", CCLOG_INFO, stream << "UnmarshalImportTx() no opret" << std::endl); + return false; + } + + if (vImportData.begin()[0] == EVAL_TOKENS) { // if it is tokens + // get import data after token opret: + std::vector> oprets; + std::vector vorigpubkey; + std::string name, desc; + + if (DecodeTokenCreateOpRet(importTx.vout.back().scriptPubKey, vorigpubkey, name, desc, oprets) == 0) { + LOGSTREAM("importcoin", CCLOG_INFO, stream << "UnmarshalImportTx() could not decode token opret" << std::endl); + return false; + } + + GetOpretBlob(oprets, OPRETID_IMPORTDATA, vImportData); // fetch import data after token opret + for (std::vector>::const_iterator i = oprets.begin(); i != oprets.end(); i++) + if ((*i).first == OPRETID_IMPORTDATA) { + oprets.erase(i); // remove import data from token opret to restore original payouts: + break; + } + + payouts = std::vector(importTx.vout.begin(), importTx.vout.end()-1); //exclude opret with import data + payouts.push_back(CTxOut(0, EncodeTokenCreateOpRet('c', vorigpubkey, name, desc, oprets))); // make original payouts token opret (without import data) + } + else { + //payouts = std::vector(importTx.vout.begin()+1, importTx.vout.end()); // see next + payouts = std::vector(importTx.vout.begin(), importTx.vout.end() - 1); // skip opret; and it is now in the back + } + + uint8_t evalCode; + bool retcode = E_UNMARSHAL(vImportData, ss >> evalCode; ss >> proof; ss >> burnTx); + if (!retcode) + LOGSTREAM("importcoin", CCLOG_INFO, stream << "UnmarshalImportTx() could not unmarshal import data" << std::endl); + return retcode; } -bool UnmarshalBurnTx(const CTransaction burnTx, std::string &targetSymbol, uint32_t *targetCCid, uint256 &payoutsHash,std::vector&rawproof) +bool UnmarshalBurnTx(const CTransaction &burnTx, std::string &targetSymbol, uint32_t *targetCCid, uint256 &payoutsHash,std::vector&rawproof) { - std::vector burnOpret; uint32_t ccid = 0; bool isEof=true; + std::vector vburnOpret; uint32_t ccid = 0; + uint8_t evalCode; - if (burnTx.vout.size() == 0) return false; - GetOpReturnData(burnTx.vout.back().scriptPubKey, burnOpret); - return E_UNMARSHAL(burnOpret, ss >> VARINT(*targetCCid); - ss >> targetSymbol; - ss >> payoutsHash; - ss >> rawproof; isEof=ss.eof();) || !isEof; + if (burnTx.vout.size() == 0) + return false; + + GetOpReturnData(burnTx.vout.back().scriptPubKey, vburnOpret); + if (vburnOpret.empty()) { + LOGSTREAM("importcoin", CCLOG_INFO, stream << "UnmarshalBurnTx() cannot unmarshal burn tx: empty burn opret" << std::endl); + return false; + } + + if (vburnOpret.begin()[0] == EVAL_TOKENS) { //if it is tokens + std::vector> oprets; + uint256 tokenid; + uint8_t evalCodeInOpret; + std::vector voutTokenPubkeys; + + if (DecodeTokenOpRet(burnTx.vout.back().scriptPubKey, evalCodeInOpret, tokenid, voutTokenPubkeys, oprets) != 't') + return false; + + //skip token opret: + GetOpretBlob(oprets, OPRETID_BURNDATA, vburnOpret); // fetch burnOpret after token opret + if (vburnOpret.empty()) { + LOGSTREAM("importcoin", CCLOG_INFO, stream << "UnmarshalBurnTx() cannot unmarshal token burn tx: empty burn opret for tokenid=" << tokenid.GetHex() << std::endl); + return false; + } + } + + if (vburnOpret.begin()[0] == EVAL_IMPORTCOIN) { + uint8_t evalCode; + return E_UNMARSHAL(vburnOpret, ss >> evalCode; + ss >> VARINT(*targetCCid); + ss >> targetSymbol; + ss >> payoutsHash; + ss >> rawproof); + } + else { + LOGSTREAM("importcoin", CCLOG_INFO, stream << "UnmarshalBurnTx() invalid eval code in opret" << std::endl); + return false; + } } -bool UnmarshalBurnTx(const CTransaction burnTx, std::string &srcaddr, std::string &receipt) +bool UnmarshalBurnTx(const CTransaction &burnTx, std::string &srcaddr, std::string &receipt) { std::vector burnOpret,rawproof; bool isEof=true; std::string targetSymbol; uint32_t targetCCid; uint256 payoutsHash; @@ -155,16 +261,55 @@ bool UnmarshalBurnTx(const CTransaction burnTx,uint256 &bindtxid,std::vector payouts; - if (UnmarshalImportTx(tx, proof, burnTx, payouts)) { - return burnTx.vout.size() ? burnTx.vout.back().nValue : 0; + + bool isNewImportTx = false; + if ((isNewImportTx = UnmarshalImportTx(tx, proof, burnTx, payouts))) { + if (burnTx.vout.size() > 0) { + vscript_t vburnOpret; + + GetOpReturnData(burnTx.vout.back().scriptPubKey, vburnOpret); + if (vburnOpret.empty()) { + LOGSTREAM("importcoin", CCLOG_INFO, stream << "GetCoinImportValue() empty burn opret" << std::endl); + return 0; + } + + if (isNewImportTx && vburnOpret.begin()[0] == EVAL_TOKENS) { //if it is tokens + + uint8_t evalCodeInOpret; + uint256 tokenid; + std::vector voutTokenPubkeys; + std::vector> oprets; + + if (DecodeTokenOpRet(tx.vout.back().scriptPubKey, evalCodeInOpret, tokenid, voutTokenPubkeys, oprets) == 0) + return 0; + + uint8_t nonfungibleEvalCode = EVAL_TOKENS; // init as if no non-fungibles + vscript_t vnonfungibleOpret; + GetOpretBlob(oprets, OPRETID_NONFUNGIBLEDATA, vnonfungibleOpret); + if (!vnonfungibleOpret.empty()) + nonfungibleEvalCode = vnonfungibleOpret.begin()[0]; + + // calc outputs for burn tx + int64_t ccBurnOutputs = 0; + for (auto v : burnTx.vout) + if (v.scriptPubKey.IsPayToCryptoCondition() && + CTxOut(v.nValue, v.scriptPubKey) == MakeTokensCC1vout(nonfungibleEvalCode, v.nValue, pubkey2pk(ParseHex(CC_BURNPUBKEY)))) // burned to dead pubkey + ccBurnOutputs += v.nValue; + + return ccBurnOutputs + burnTx.vout.back().nValue; // total token burned value + } + else + return burnTx.vout.back().nValue; // coin burned value + } } 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. diff --git a/src/importcoin.h b/src/importcoin.h index 0fcc350d0..c510fe405 100644 --- a/src/importcoin.h +++ b/src/importcoin.h @@ -22,13 +22,79 @@ #include "script/interpreter.h" #include +enum ProofKind : uint8_t { + PROOF_NONE = 0x00, + PROOF_MERKLEBRANCH = 0x11, + PROOF_NOTARYTXIDS = 0x12, + PROOF_MERKLEBLOCK = 0x13 +}; + +class ImportProof { + +private: + uint8_t proofKind; + TxProof proofBranch; + std::vector notaryTxids; + std::vector proofBlock; + +public: + ImportProof() { proofKind = PROOF_NONE; } + ImportProof(const TxProof &_proofBranch) { + proofKind = PROOF_MERKLEBRANCH; proofBranch = _proofBranch; + } + ImportProof(const std::vector &_notaryTxids) { + proofKind = PROOF_NOTARYTXIDS; notaryTxids = _notaryTxids; + } + ImportProof(const std::vector &_proofBlock) { + proofKind = PROOF_MERKLEBLOCK; proofBlock = _proofBlock; + } + + ADD_SERIALIZE_METHODS + + template + inline void SerializationOp(Stream& s, Operation ser_action) { + READWRITE(proofKind); + if (proofKind == PROOF_MERKLEBRANCH) + READWRITE(proofBranch); + else if (proofKind == PROOF_NOTARYTXIDS) + READWRITE(notaryTxids); + else if (proofKind == PROOF_MERKLEBLOCK) + READWRITE(proofBlock); + } + + bool IsMerkleBranch(TxProof &_proofBranch) const { + if (proofKind == PROOF_MERKLEBRANCH) { + _proofBranch = proofBranch; + return true; + } + else + return false; + } + bool IsNotaryTxids(std::vector &_notaryTxids) const { + if (proofKind == PROOF_NOTARYTXIDS) { + _notaryTxids = notaryTxids; + return true; + } + else + return false; + } + bool IsMerkleBlock(std::vector &_proofBlock) const { + if (proofKind == PROOF_MERKLEBLOCK) { + _proofBlock = proofBlock; + return true; + } + else + return false; + } +}; + + CAmount GetCoinImportValue(const CTransaction &tx); -CTransaction MakeImportCoinTransaction(const TxProof proof, - const CTransaction burnTx, const std::vector payouts, uint32_t nExpiryHeightOverride = 0); +CTransaction MakeImportCoinTransaction(const ImportProof &proof, const CTransaction &burnTx, const std::vector &payouts, uint32_t nExpiryHeightOverride = 0); -CTxOut MakeBurnOutput(CAmount value, uint32_t targetCCid, std::string targetSymbol, const std::vector payouts,std::vector rawproof); +CTxOut MakeBurnOutput(CAmount value, uint32_t targetCCid, const std::string &targetSymbol, const std::vector &payouts, const std::vector &rawproof); CTxOut MakeBurnOutput(CAmount value, uint32_t targetCCid, std::string targetSymbol, const std::vector payouts,std::vector rawproof, uint256 bindtxid,std::vector publishers,std::vectortxids,uint256 burntxid,int32_t height,int32_t burnvout,std::string rawburntx,CPubKey destpub, int64_t amount); CTxOut MakeBurnOutput(CAmount value, uint32_t targetCCid, std::string targetSymbol, const std::vector payouts,std::vector rawproof,std::string srcaddr, @@ -37,7 +103,7 @@ CTxOut MakeBurnOutput(CAmount value, uint32_t targetCCid, std::string targetSymb bool UnmarshalBurnTx(const CTransaction burnTx, std::string &targetSymbol, uint32_t *targetCCid, uint256 &payoutsHash,std::vector &rawproof); bool UnmarshalBurnTx(const CTransaction burnTx, std::string &srcaddr, std::string &receipt); bool UnmarshalBurnTx(const CTransaction burnTx,uint256 &bindtxid,std::vector &publishers,std::vector &txids,uint256& burntxid,int32_t &height,int32_t &burnvout,std::string &rawburntx,CPubKey &destpub, int64_t &amount); -bool UnmarshalImportTx(const CTransaction importTx, TxProof &proof, CTransaction &burnTx,std::vector &payouts); +bool UnmarshalImportTx(const CTransaction &importTx, ImportProof &proof, CTransaction &burnTx,std::vector &payouts); bool VerifyCoinImport(const CScript& scriptSig, TransactionSignatureChecker& checker, CValidationState &state); @@ -45,4 +111,9 @@ void AddImportTombstone(const CTransaction &importTx, CCoinsViewCache &inputs, i void RemoveImportTombstone(const CTransaction &importTx, CCoinsViewCache &inputs); int ExistsImportTombstone(const CTransaction &importTx, const CCoinsViewCache &inputs); +bool CheckVinPubKey(const CTransaction &sourcetx, int32_t i, uint8_t pubkey33[33]); + +CMutableTransaction MakeSelfImportSourceTx(CTxDestination &dest, int64_t amount); +int32_t GetSelfimportProof(const CMutableTransaction &sourceMtx, CMutableTransaction &templateMtx, ImportProof &proofNull); + #endif /* IMPORTCOIN_H */ diff --git a/src/rpc/crosschain.cpp b/src/rpc/crosschain.cpp index cacf4357e..7f50c32ee 100644 --- a/src/rpc/crosschain.cpp +++ b/src/rpc/crosschain.cpp @@ -38,6 +38,7 @@ #include "key_io.h" #include "cc/CCImportGateway.h" +#include "cc/CCtokens.h" #include #include @@ -52,6 +53,8 @@ extern std::string CCerror; extern std::string ASSETCHAINS_SELFIMPORT; extern uint16_t ASSETCHAINS_CODAPORT, ASSETCHAINS_BEAMPORT; int32_t ensure_CCrequirements(uint8_t evalcode); +bool EnsureWalletIsAvailable(bool avoidException); + 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); @@ -60,8 +63,8 @@ uint256 komodo_calcMoM(int32_t height,int32_t MoMdepth); int32_t komodo_notaries(uint8_t pubkeys[64][33],int32_t height,uint32_t timestamp); extern std::string ASSETCHAINS_SELFIMPORT; -std::string MakeSelfImportSourceTx(CTxDestination &dest, int64_t amount, CMutableTransaction &mtx); -int32_t GetSelfimportProof(std::string source, CMutableTransaction &mtx, CScript &scriptPubKey, TxProof &proof, std::string rawsourcetx, int32_t &ivout, uint256 sourcetxid, uint64_t burnAmount); +//std::string MakeSelfImportSourceTx(CTxDestination &dest, int64_t amount, CMutableTransaction &mtx); +//int32_t GetSelfimportProof(std::string source, CMutableTransaction &mtx, CScript &scriptPubKey, TxProof &proof, std::string rawsourcetx, int32_t &ivout, uint256 sourcetxid, uint64_t burnAmount); std::string MakeCodaImportTx(uint64_t txfee, std::string receipt, std::string srcaddr, std::vector vouts); UniValue assetchainproof(const UniValue& params, bool fHelp) @@ -185,7 +188,7 @@ UniValue migrate_converttoexport(const UniValue& params, bool fHelp) "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 " + "the \"migrate_createimporttransaction\" method to get the corresponding " "import transaction.\n" ); @@ -206,19 +209,30 @@ UniValue migrate_converttoexport(const UniValue& params, bool fHelp) if (strcmp(ASSETCHAINS_SYMBOL,targetSymbol.c_str()) == 0) throw runtime_error("cant send a coin to the same chain"); + + /// Tested 44 vins p2pkh inputs as working. Set this at 25, but its a tx size limit. + // likely with a single RPC you can limit it by the size of tx. + if (tx.vout.size() > 25) + throw JSONRPCError(RPC_TYPE_ERROR, "Cannot have more than 50 vins, transaction too large."); CAmount burnAmount = 0; for (int i=0; i 1000000LL*COIN) throw JSONRPCError(RPC_TYPE_ERROR, "Cannot export more than 1 million coins per export."); + /* note: we marshal to rawproof in a different way (to be able to add other objects) rawproof.resize(strlen(ASSETCHAINS_SYMBOL)); ptr = rawproof.data(); for (i=0; i 32) + throw runtime_error("targetSymbol length must be >0 and <=32"); + + if (strcmp(ASSETCHAINS_SYMBOL, targetSymbol.c_str()) == 0) + throw runtime_error("cant send a coin to the same chain"); + + std::string dest_addr_or_pubkey = params[1].get_str(); + + CAmount burnAmount; + if(params.size() == 3) + burnAmount = (CAmount)( atof(params[2].get_str().c_str()) * COIN + 0.00000000499999 ); + else + burnAmount = atoll(params[2].get_str().c_str()); + + if (burnAmount <= 0) + throw JSONRPCError(RPC_TYPE_ERROR, "Cannot export a negative or zero value."); + if (burnAmount > 1000000LL * COIN) + throw JSONRPCError(RPC_TYPE_ERROR, "Cannot export more than 1 million coins per export."); + + uint256 tokenid = zeroid; + if( params.size() == 4 ) + tokenid = Parseuint256(params[3].get_str().c_str()); + + CPubKey myPubKey = Mypubkey(); + struct CCcontract_info *cpTokens, C; + cpTokens = CCinit(&C, EVAL_TOKENS); + + CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), komodo_nextheight()); + + const std::string chainSymbol(ASSETCHAINS_SYMBOL); + std::vector rawproof; //(chainSymbol.begin(), chainSymbol.end()); + + if (tokenid.IsNull()) { // coins + int64_t inputs; + if ((inputs = AddNormalinputs(mtx, myPubKey, burnAmount + txfee, 60)) == 0) { + throw runtime_error("Cannot find normal inputs\n"); + } + + CTxDestination txdest = DecodeDestination(dest_addr_or_pubkey.c_str()); + CScript scriptPubKey = GetScriptForDestination(txdest); + if (!scriptPubKey.IsPayToPublicKeyHash()) { + throw JSONRPCError(RPC_TYPE_ERROR, "Incorrect destination addr."); + } + mtx.vout.push_back(CTxOut(burnAmount, scriptPubKey)); // 'model' vout + ret.push_back(Pair("payouts", HexStr(E_MARSHAL(ss << mtx.vout)))); // save 'model' vout + + rawproof = E_MARSHAL(ss << chainSymbol); // add src chain name + + CTxOut burnOut = MakeBurnOutput(burnAmount+txfee, ccid, targetSymbol, mtx.vout, rawproof); //make opret with burned amount + + mtx.vout.clear(); // remove 'model' vout + + int64_t change = inputs - (burnAmount+txfee); + if (change != 0) + mtx.vout.push_back(CTxOut(change, CScript() << ParseHex(HexStr(myPubKey)) << OP_CHECKSIG)); // make change here to prevent it from making in FinalizeCCtx + + mtx.vout.push_back(burnOut); // mtx now has only burned vout (that is, amount sent to OP_RETURN making it unspendable) + //std::string exportTxHex = FinalizeCCTx(0, cpTokens, mtx, myPubKey, txfee, CScript()); // no change no opret + + } + else { // tokens + CTransaction tokenbasetx; + uint256 hashBlock; + vscript_t vopretNonfungible; + vscript_t vopretBurnData; + std::vector vorigpubkey, vdestpubkey; + std::string name, description; + std::vector> oprets; + + if (!myGetTransaction(tokenid, tokenbasetx, hashBlock)) + throw runtime_error("Could not load token creation tx\n"); + + // check if it is non-fungible tx and get its second evalcode from non-fungible payload + if (tokenbasetx.vout.size() == 0) + throw runtime_error("No vouts in token tx\n"); + + if (DecodeTokenCreateOpRet(tokenbasetx.vout.back().scriptPubKey, vorigpubkey, name, description, oprets) != 'c') + throw runtime_error("Incorrect token creation tx\n"); + GetOpretBlob(oprets, OPRETID_NONFUNGIBLEDATA, vopretNonfungible); + /* allow fungible tokens: + if (vopretNonfungible.empty()) + throw runtime_error("No non-fungible token data\n"); */ + + uint8_t destEvalCode = EVAL_TOKENS; + if (!vopretNonfungible.empty()) + destEvalCode = vopretNonfungible.begin()[0]; + + // check non-fungible tokens amount + if (!vopretNonfungible.empty() && burnAmount != 1) + throw JSONRPCError(RPC_TYPE_ERROR, "For non-fungible tokens amount should be equal to 1."); + + vdestpubkey = ParseHex(dest_addr_or_pubkey); + CPubKey destPubKey = pubkey2pk(vdestpubkey); + if (!destPubKey.IsValid()) + throw runtime_error("Invalid destination pubkey\n"); + + int64_t inputs; + if ((inputs = AddNormalinputs(mtx, myPubKey, txfee, 1)) == 0) // for miners in dest chain + throw runtime_error("No normal input found for two txfee\n"); + + int64_t ccInputs; + if ((ccInputs = AddTokenCCInputs(cpTokens, mtx, myPubKey, tokenid, burnAmount, 4)) < burnAmount) + throw runtime_error("No token inputs found (please try to consolidate tokens)\n"); + + // make payouts (which will be in the import tx with token): + mtx.vout.push_back(MakeCC1vout(EVAL_TOKENS, txfee, GetUnspendable(cpTokens, NULL))); // new marker to token cc addr, burnable and validated, vout position now changed to 0 (from 1) + mtx.vout.push_back(MakeTokensCC1vout(destEvalCode, burnAmount, destPubKey)); + + std::vector> voprets; + if (!vopretNonfungible.empty()) + voprets.push_back(std::make_pair(OPRETID_NONFUNGIBLEDATA, vopretNonfungible)); // add additional opret with non-fungible data + + mtx.vout.push_back(CTxOut((CAmount)0, EncodeTokenCreateOpRet('c', vorigpubkey, name, description, voprets))); // make token import opret + ret.push_back(Pair("payouts", HexStr(E_MARSHAL(ss << mtx.vout)))); // save payouts for import tx + + rawproof = E_MARSHAL(ss << chainSymbol << tokenbasetx); // add src chain name and token creation tx + + CTxOut burnOut = MakeBurnOutput(0, ccid, targetSymbol, mtx.vout, rawproof); //make opret with amount=0 because tokens are burned, not coins (see next vout) + mtx.vout.clear(); // remove payouts + + // now make burn transaction: + mtx.vout.push_back(MakeTokensCC1vout(destEvalCode, burnAmount, pubkey2pk(ParseHex(CC_BURNPUBKEY)))); // burn tokens + + int64_t change = inputs - txfee; + if (change != 0) + mtx.vout.push_back(CTxOut(change, CScript() << ParseHex(HexStr(myPubKey)) << OP_CHECKSIG)); // make change here to prevent it from making in FinalizeCCtx + + std::vector voutTokenPubkeys; + voutTokenPubkeys.push_back(pubkey2pk(ParseHex(CC_BURNPUBKEY))); // maybe we do not need this because ccTokens has the const for burn pubkey + + int64_t ccChange = ccInputs - burnAmount; + if (ccChange != 0) + mtx.vout.push_back(MakeTokensCC1vout(destEvalCode, ccChange, myPubKey)); + + GetOpReturnData(burnOut.scriptPubKey, vopretBurnData); + mtx.vout.push_back(CTxOut(txfee, EncodeTokenOpRet(tokenid, voutTokenPubkeys, std::make_pair(OPRETID_BURNDATA, vopretBurnData)))); //burn txfee for miners in dest chain + } + + std::string burnTxHex = FinalizeCCTx(0, cpTokens, mtx, myPubKey, txfee, CScript()); //no change, no opret + ret.push_back(Pair("BurnTxHex", burnTxHex)); + return ret; +} + +// util func to check burn tx and source chain params +void CheckBurnTxSource(uint256 burntxid, UniValue &info) { + + CTransaction burnTx; + uint256 blockHash; + + if (!GetTransaction(burntxid, burnTx, blockHash, true)) + throw std::runtime_error("Cannot find burn transaction"); + + if (blockHash.IsNull()) + throw std::runtime_error("Burn tx still in mempool"); + + uint256 payoutsHash; + std::string targetSymbol; + uint32_t targetCCid; + std::vector rawproof; + + if (!UnmarshalBurnTx(burnTx, targetSymbol, &targetCCid, payoutsHash, rawproof)) + throw std::runtime_error("Cannot unmarshal burn tx data"); + + vscript_t vopret; + std::string sourceSymbol; + CTransaction tokenbasetxStored; + uint256 tokenid = zeroid; + + if (burnTx.vout.size() > 0 && GetOpReturnData(burnTx.vout.back().scriptPubKey, vopret) && !vopret.empty()) { + if (vopret.begin()[0] == EVAL_TOKENS) { + if (!E_UNMARSHAL(rawproof, ss >> sourceSymbol; ss >> tokenbasetxStored)) + throw std::runtime_error("Cannot unmarshal rawproof for tokens"); + + uint8_t evalCode; + std::vector voutPubkeys; + std::vector> oprets; + if( DecodeTokenOpRet(burnTx.vout.back().scriptPubKey, evalCode, tokenid, voutPubkeys, oprets) == 0 ) + throw std::runtime_error("Cannot decode token opret in burn tx"); + + if( tokenid != tokenbasetxStored.GetHash() ) + throw std::runtime_error("Incorrect tokenbase in burn tx"); + + CTransaction tokenbasetx; + uint256 hashBlock; + if (!myGetTransaction(tokenid, tokenbasetx, hashBlock)) { + throw std::runtime_error("Could not load tokenbase tx"); + } + + // check if nonfungible data present + if (tokenbasetx.vout.size() > 0) { + std::vector origpubkey; + std::string name, description; + std::vector> oprets; + + vscript_t vopretNonfungible; + if (DecodeTokenCreateOpRet(tokenbasetx.vout.back().scriptPubKey, origpubkey, name, description, oprets) == 'c') { + GetOpretBlob(oprets, OPRETID_NONFUNGIBLEDATA, vopretNonfungible); + if (vopretNonfungible.empty()) + throw std::runtime_error("Could not migrate fungible tokens"); + } + else + throw std::runtime_error("Could not decode opreturn in tokenbase tx"); + } + else + throw std::runtime_error("Incorrect tokenbase tx: not opreturn"); + + + struct CCcontract_info *cpTokens, CCtokens_info; + cpTokens = CCinit(&CCtokens_info, EVAL_TOKENS); + int64_t ccInputs = 0, ccOutputs = 0; + if( !TokensExactAmounts(true, cpTokens, ccInputs, ccOutputs, NULL, burnTx, tokenid) ) + throw std::runtime_error("Incorrect token burn tx: cc inputs <> cc outputs"); + } + else if (vopret.begin()[0] == EVAL_IMPORTCOIN) { + if (!E_UNMARSHAL(rawproof, ss >> sourceSymbol)) + throw std::runtime_error("Cannot unmarshal rawproof for coins"); + } + else + throw std::runtime_error("Incorrect eval code in opreturn"); + } + else + throw std::runtime_error("No opreturn in burn tx"); + + + if (sourceSymbol != ASSETCHAINS_SYMBOL) + throw std::runtime_error("Incorrect source chain in rawproof"); + + if (targetCCid != ASSETCHAINS_CC) + throw std::runtime_error("Incorrect CCid in burn tx"); + + if (targetSymbol == ASSETCHAINS_SYMBOL) + throw std::runtime_error("Must not be called on the destination chain"); + + // fill info to return for the notary operator (if manual notarization) or user + info.push_back(Pair("SourceSymbol", sourceSymbol)); + info.push_back(Pair("TargetSymbol", targetSymbol)); + info.push_back(Pair("TargetCCid", std::to_string(targetCCid))); + if (!tokenid.IsNull()) + info.push_back(Pair("tokenid", tokenid.GetHex())); + +} /* - * The process to migrate funds + * The process to migrate funds from a chain to chain * - * Create a transaction on assetchain: + * 1.Create a transaction on assetchain (deprecated): + * 1.1 generaterawtransaction + * 1.2 migrate_converttoexport + * 1.3 fundrawtransaction + * 1.4 signrawtransaction * - * generaterawtransaction - * migrate_converttoexport - * fundrawtransaction - * signrawtransaction + * alternatively, burn (export) transaction may be created with this new rpc call: + * 1. migrate_createburntransaction * - * migrate_createimportransaction - * migrate_completeimporttransaction + * next steps: + * 2. migrate_createimporttransaction + * 3. migrate_completeimporttransaction */ UniValue migrate_createimporttransaction(const UniValue& params, bool fHelp) { - if (fHelp || params.size() != 2) - throw runtime_error("migrate_createimporttransaction burnTx payouts\n\n" - "Create an importTx given a burnTx and the corresponding payouts, hex encoded"); + if (fHelp || params.size() < 2) + throw runtime_error("migrate_createimporttransaction burnTx payouts [notarytxid-1]..[notarytxid-N]\n\n" + "Create an importTx given a burnTx and the corresponding payouts, hex encoded\n" + "optional notarytxids are txids of notary operator proofs of burn tx existense (from destination chain).\n" + "Do not make subsequent call to migrate_completeimporttransaction if notary txids are set"); if (ASSETCHAINS_CC < KOMODO_FIRSTFUNGIBLEID) throw runtime_error("-ac_cc < KOMODO_FIRSTFUNGIBLEID"); @@ -261,27 +557,52 @@ UniValue migrate_createimporttransaction(const UniValue& params, bool fHelp) if (!E_UNMARSHAL(txData, ss >> burnTx)) throw runtime_error("Couldn't parse burnTx"); + if( burnTx.vin.size() == 0 ) + throw runtime_error("No vins in the burnTx"); + + if (burnTx.vout.size() == 0) + throw runtime_error("No vouts in the 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(),burnTx); + ImportProof importProof; + if (params.size() == 2) { // standard MoMoM based notarization + // get MoM import proof + importProof = ImportProof(GetAssetchainProof(burnTx.GetHash(), burnTx)); + } + else { // notarization by manual operators notary tx + UniValue info(UniValue::VOBJ); + CheckBurnTxSource(burnTx.GetHash(), info); - CTransaction importTx = MakeImportCoinTransaction(proof, burnTx, payouts); + // get notary import proof + std::vector notaryTxids; + for (int i = 2; i < params.size(); i++) { + uint256 txid = Parseuint256(params[i].get_str().c_str()); + if (txid.IsNull()) + throw runtime_error("Incorrect notary approval txid"); + notaryTxids.push_back(txid); + } + importProof = ImportProof(notaryTxids); + } - return HexStr(E_MARSHAL(ss << importTx)); + CTransaction importTx = MakeImportCoinTransaction(importProof, burnTx, payouts); + + std::string importTxHex = HexStr(E_MARSHAL(ss << importTx)); + UniValue ret(UniValue::VOBJ); + ret.push_back(Pair("ImportTxHex", importTxHex)); + return ret; } - UniValue migrate_completeimporttransaction(const UniValue& params, bool fHelp) { if (fHelp || params.size() < 1 || params.size() > 2) - throw runtime_error("migrate_completeimporttransaction importTx (offset)\n\n" + throw runtime_error("migrate_completeimporttransaction importTx [offset]\n\n" "Takes a cross chain import tx with proof generated on assetchain " "and extends proof to target chain proof root\n" - "offset is optional, use it to increase the used KMD height, use when import fails on target."); + "offset is optional, use it to increase the used KMD height, use when import fails."); if (ASSETCHAINS_SYMBOL[0] != 0) throw runtime_error("Must be called on KMD"); @@ -289,67 +610,130 @@ UniValue migrate_completeimporttransaction(const UniValue& params, bool fHelp) CTransaction importTx; if (!E_UNMARSHAL(ParseHexV(params[0], "argument 1"), ss >> importTx)) throw runtime_error("Couldn't parse importTx"); - + int32_t offset = 0; if ( params.size() == 2 ) offset = params[1].get_int(); - + CompleteImportTransaction(importTx, offset); - return HexStr(E_MARSHAL(ss << importTx)); + std::string importTxHex = HexStr(E_MARSHAL(ss << importTx)); + UniValue ret(UniValue::VOBJ); + ret.push_back(Pair("ImportTxHex", importTxHex)); + return ret; } +/* +* Alternate coin migration solution if MoMoM migration has failed +* +* The workflow: +* On the source chain user calls migrate_createburntransaction, sends the burn tx to the chain and sends its txid and the source chain name to the notary operators (off-chain) +* the notary operators call migrate_checkburntransactionsource on the source chain +* on the destination chain the notary operators call migrate_createnotaryapprovaltransaction and pass the burn txid and txoutproof received from the previous call, +* the notary operators send the approval transactions to the chain and send their txids to the user (off-chain) +* on the source chain the user calls migrate_createimporttransaction and passes to it notary txids as additional parameters +* then the user sends the import transaction to the destination chain (where the notary approvals will be validated) +*/ + +// checks if burn tx exists and params stored in the burn tx match to the source chain +// returns txproof +// run it on the source chain +UniValue migrate_checkburntransactionsource(const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() != 1) + throw runtime_error("migrate_checkburntransactionsource burntxid\n\n" + "checks if params stored in the burn tx match to its tx chain"); + + if (ASSETCHAINS_SYMBOL[0] == 0) + throw runtime_error("Must be called on asset chain"); + + uint256 burntxid = Parseuint256(params[0].get_str().c_str()); + UniValue result(UniValue::VOBJ); + CheckBurnTxSource(burntxid, result); // check and get burn tx data + + // get tx proof for burn tx + UniValue nextparams(UniValue::VARR); + UniValue txids(UniValue::VARR); + txids.push_back(burntxid.GetHex()); + nextparams.push_back(txids); + result.push_back(Pair("TxOutProof", gettxoutproof(nextparams, false))); // get txoutproof + result.push_back(Pair("result", "success")); // get txoutproof + + return result; +} + +// creates a tx for the dest chain with txproof +// used as a momom-backup manual import solution +// run it on the dest chain +UniValue migrate_createnotaryapprovaltransaction(const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() != 2) + throw runtime_error("migrate_createnotaryapprovaltransaction burntxid txoutproof\n\n" + "Creates a tx for destination chain with burn tx proof\n" + "txoutproof should be retrieved by komodo-cli migrate_checkburntransactionsource call on the source chain\n" ); + + if (ASSETCHAINS_SYMBOL[0] == 0) + throw runtime_error("Must be called on asset chain"); + + uint256 burntxid = Parseuint256(params[0].get_str().c_str()); + if (burntxid.IsNull()) + throw runtime_error("Couldn't parse burntxid or it is null"); + + std::vector proofData = ParseHex(params[1].get_str()); + CMerkleBlock merkleBlock; + std::vector prooftxids; + if (!E_UNMARSHAL(proofData, ss >> merkleBlock)) + throw runtime_error("Couldn't parse txoutproof"); + + merkleBlock.txn.ExtractMatches(prooftxids); + if (std::find(prooftxids.begin(), prooftxids.end(), burntxid) == prooftxids.end()) + throw runtime_error("No burntxid in txoutproof"); + + const int64_t txfee = 10000; + struct CCcontract_info *cpDummy, C; + cpDummy = CCinit(&C, EVAL_TOKENS); // just for FinalizeCCtx to work + + // creating a tx with proof: + CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), komodo_nextheight()); + if (AddNormalinputs(mtx, Mypubkey(), txfee*2, 4) == 0) + throw runtime_error("Cannot find normal inputs\n"); + + mtx.vout.push_back(CTxOut(txfee, CScript() << ParseHex(HexStr(Mypubkey())) << OP_CHECKSIG)); + std::string notaryTxHex = FinalizeCCTx(0, cpDummy, mtx, Mypubkey(), txfee, CScript() << OP_RETURN << E_MARSHAL(ss << proofData;)); + + UniValue result(UniValue::VOBJ); + result.push_back(Pair("NotaryTxHex", notaryTxHex)); + return result; +} + +// creates a source 'quasi-burn' tx for AC_PUBKEY +// run it on the same asset chain UniValue selfimport(const UniValue& params, bool fHelp) { UniValue result(UniValue::VOBJ); - CMutableTransaction sourceMtx, templateMtx; std::string destaddr; std::string source; - std::string rawsourcetx; + std::string sourceTxHex; + std::string importTxHex; CTransaction burnTx; CTxOut burnOut; uint64_t burnAmount; uint256 sourcetxid, blockHash; std::vector vouts; - std::vector rawproof, rawproofEmpty; - int32_t ivout = 0; - CScript scriptPubKey; - TxProof proof; + std::vector rawproof; if ( ASSETCHAINS_SELFIMPORT.size() == 0 ) throw runtime_error("selfimport only works on -ac_import chains"); if (fHelp || params.size() != 2) throw runtime_error("selfimport destaddr amount\n" - //old: "selfimport rawsourcetx sourcetxid {nvout|\"find\"} amount \n" //TODO: "or selfimport rawburntx burntxid {nvout|\"find\"} rawproof source bindtxid height} \n" "\ncreates self import coin transaction"); -/* OLD selfimport schema: - rawsourcetx = params[0].get_str(); - sourcetxid = Parseuint256((char *)params[1].get_str().c_str()); // allow for txid != hash(rawtx) - - int32_t ivout = -1; - if( params[2].get_str() != "find" ) { - if( !std::all_of(params[2].get_str().begin(), params[2].get_str().end(), ::isdigit) ) // check if not all chars are digit - throw std::runtime_error("incorrect nvout param"); - - ivout = atoi(params[2].get_str().c_str()); - } - - burnAmount = atof(params[3].get_str().c_str()) * COIN + 0.00000000499999; */ - destaddr = params[0].get_str(); burnAmount = atof(params[1].get_str().c_str()) * COIN + 0.00000000499999; source = ASSETCHAINS_SELFIMPORT; //defaults to -ac_import=... param - /* TODO for gateways: - if ( params.size() >= 5 ) - { - rawproof = ParseHex(params[4].get_str().c_str()); - if ( params.size() == 6 ) - source = params[5].get_str(); - } */ if (source == "BEAM") { @@ -369,51 +753,34 @@ UniValue selfimport(const UniValue& params, bool fHelp) } else if (source == "PUBKEY") { - + ImportProof proofNull; CTxDestination dest = DecodeDestination(destaddr.c_str()); - rawsourcetx = MakeSelfImportSourceTx(dest, burnAmount, sourceMtx); - sourcetxid = sourceMtx.GetHash(); - + CMutableTransaction sourceMtx = MakeSelfImportSourceTx(dest, burnAmount); // make self-import source tx + vscript_t rawProofEmpty; + + CMutableTransaction templateMtx; // prepare self-import 'quasi-burn' tx and also create vout for import tx (in mtx.vout): - if (GetSelfimportProof(source, templateMtx, scriptPubKey, proof, rawsourcetx, ivout, sourcetxid, burnAmount) < 0) - throw std::runtime_error("Failed validating selfimport"); + if (GetSelfimportProof(sourceMtx, templateMtx, proofNull) < 0) + throw std::runtime_error("Failed creating selfimport template tx"); vouts = templateMtx.vout; - burnOut = MakeBurnOutput(burnAmount, 0xffffffff, ASSETCHAINS_SELFIMPORT, vouts, rawproofEmpty); + burnOut = MakeBurnOutput(burnAmount, 0xffffffff, ASSETCHAINS_SELFIMPORT, vouts, rawProofEmpty); templateMtx.vout.clear(); templateMtx.vout.push_back(burnOut); // burn tx has only opret with vouts and optional proof burnTx = templateMtx; // complete the creation of 'quasi-burn' tx - std::string hextx = HexStr(E_MARSHAL(ss << MakeImportCoinTransaction(proof, burnTx, vouts))); - - CTxDestination address; - bool fValidAddress = ExtractDestination(scriptPubKey, address); - - result.push_back(Pair("sourceTxHex", rawsourcetx)); - result.push_back(Pair("importTxHex", hextx)); - result.push_back(Pair("UsedRawtxVout", ivout)); // notify user about the used vout of rawtx - result.push_back(Pair("DestinationAddress", EncodeDestination(address))); // notify user about the address where the funds will be sent - + sourceTxHex = HexStr(E_MARSHAL(ss << sourceMtx)); + importTxHex = HexStr(E_MARSHAL(ss << MakeImportCoinTransaction(proofNull, burnTx, vouts))); + + result.push_back(Pair("SourceTxHex", sourceTxHex)); + result.push_back(Pair("ImportTxHex", importTxHex)); + return result; } else if (source == ASSETCHAINS_SELFIMPORT) { - throw std::runtime_error("not implemented yet\n"); - - if (params.size() != 8) - throw runtime_error("use \'selfimport rawburntx burntxid nvout rawproof source bindtxid height\' to import from a coin chain\n"); - - uint256 bindtxid = Parseuint256((char *)params[6].get_str().c_str()); - int32_t height = atoi((char *)params[7].get_str().c_str()); - - - // source is external coin is the assetchains symbol in the burnTx OP_RETURN - // burnAmount, rawtx and rawproof should be enough for gatewaysdeposit equivalent - //std::string hextx = MakeGatewaysImportTx(0, bindtxid, height, source, rawproof, rawsourcetx, ivout, ""); - - // result.push_back(Pair("hex", hextx)); - // result.push_back(Pair("UsedRawtxVout", ivout)); // notify user about the used vout of rawtx + return -1; } return result; } @@ -939,14 +1306,15 @@ UniValue getimports(const UniValue& params, bool fHelp) { UniValue objTx(UniValue::VOBJ); objTx.push_back(Pair("txid",tx.GetHash().ToString())); - TxProof proof; CTransaction burnTx; std::vector payouts; CTxDestination importaddress; - TotalImported += tx.vout[0].nValue; - objTx.push_back(Pair("amount", ValueFromAmount(tx.vout[0].nValue))); - if (ExtractDestination(tx.vout[0].scriptPubKey, importaddress)) + ImportProof proof; CTransaction burnTx; std::vector payouts; CTxDestination importaddress; + TotalImported += tx.vout[1].nValue; + objTx.push_back(Pair("amount", ValueFromAmount(tx.vout[1].nValue))); + if (ExtractDestination(tx.vout[1].scriptPubKey, importaddress)) { objTx.push_back(Pair("address", CBitcoinAddress(importaddress).ToString())); } - UniValue objBurnTx(UniValue::VOBJ); + UniValue objBurnTx(UniValue::VOBJ); + CPubKey vinPubkey; if (UnmarshalImportTx(tx, proof, burnTx, payouts)) { if (burnTx.vout.size() == 0) @@ -959,8 +1327,14 @@ UniValue getimports(const UniValue& params, bool fHelp) { if (rawproof.size() > 0) { - std::string sourceSymbol(rawproof.begin(), rawproof.end()); + std::string sourceSymbol; + CTransaction tokenbasetx; + E_UNMARSHAL(rawproof, ss >> sourceSymbol; + if (!ss.eof()) + ss >> tokenbasetx ); objBurnTx.push_back(Pair("source", sourceSymbol)); + if( !tokenbasetx.IsNull() ) + objBurnTx.push_back(Pair("tokenid", tokenbasetx.GetHash().GetHex())); } } } @@ -973,3 +1347,137 @@ UniValue getimports(const UniValue& params, bool fHelp) result.push_back(Pair("time", block.GetBlockTime())); return result; } + + +// outputs burn transactions in the wallet +UniValue getwalletburntransactions(const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() > 1) + throw runtime_error( + "getwalletburntransactions \"count\"\n\n" + "Lists most recent wallet burn transactions up to \'count\' parameter\n" + "parameter \'count\' is optional. If omitted, defaults to 10 burn transactions" + "\n\n" + "\nResult:\n" + "[\n" + " {\n" + " \"txid\": (string)\n" + " \"burnedAmount\" : (numeric)\n" + " \"targetSymbol\" : (string)\n" + " \"targetCCid\" : (numeric)\n" + " }\n" + "]\n" + "\nExamples:\n" + + HelpExampleCli("getwalletburntransactions", "100") + + HelpExampleRpc("getwalletburntransactions", "100") + + HelpExampleCli("getwalletburntransactions", "") + + HelpExampleRpc("getwalletburntransactions", "") + ); + + if (!EnsureWalletIsAvailable(fHelp)) + return NullUniValue; + + LOCK2(cs_main, pwalletMain->cs_wallet); + + string strAccount = "*"; + isminefilter filter = ISMINE_SPENDABLE; + int nCount = 10; + + if (params.size() == 1) + nCount = atoi(params[0].get_str()); + if (nCount < 0) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative count"); + + UniValue ret(UniValue::VARR); + + std::list acentries; + CWallet::TxItems txOrdered = pwalletMain->OrderedTxItems(acentries, strAccount); + + // iterate backwards until we have nCount items to return: + for (CWallet::TxItems::reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it) + { + CWalletTx *const pwtx = (*it).second.first; + if (pwtx != 0) + { + LOGSTREAM("importcoin", CCLOG_DEBUG2, stream << "pwtx iterpos=" << (int32_t)pwtx->nOrderPos << " txid=" << pwtx->GetHash().GetHex() << std::endl); + vscript_t vopret; + std::string targetSymbol; + uint32_t targetCCid; uint256 payoutsHash; + std::vector rawproof; + + if (pwtx->vout.size() > 0 && GetOpReturnData(pwtx->vout.back().scriptPubKey, vopret) && !vopret.empty() && + UnmarshalBurnTx(*pwtx, targetSymbol, &targetCCid, payoutsHash, rawproof)) { + UniValue entry(UniValue::VOBJ); + entry.push_back(Pair("txid", pwtx->GetHash().GetHex())); + if (vopret.begin()[0] == EVAL_TOKENS) { + // get burned token value + std::vector> oprets; + uint256 tokenid; + uint8_t evalCodeInOpret; + std::vector voutTokenPubkeys; + + //skip token opret: + if (DecodeTokenOpRet(pwtx->vout.back().scriptPubKey, evalCodeInOpret, tokenid, voutTokenPubkeys, oprets) != 0) { + CTransaction tokenbasetx; + uint256 hashBlock; + + if (myGetTransaction(tokenid, tokenbasetx, hashBlock)) { + std::vector vorigpubkey; + std::string name, description; + std::vector> oprets; + + if (tokenbasetx.vout.size() > 0 && + DecodeTokenCreateOpRet(tokenbasetx.vout.back().scriptPubKey, vorigpubkey, name, description, oprets) == 'c') + { + uint8_t destEvalCode = EVAL_TOKENS; // init set to fungible token: + vscript_t vopretNonfungible; + GetOpretBlob(oprets, OPRETID_NONFUNGIBLEDATA, vopretNonfungible); + if (!vopretNonfungible.empty()) + destEvalCode = vopretNonfungible.begin()[0]; + + int64_t burnAmount = 0; + for (auto v : pwtx->vout) + if (v.scriptPubKey.IsPayToCryptoCondition() && + CTxOut(v.nValue, v.scriptPubKey) == MakeTokensCC1vout(destEvalCode ? destEvalCode : EVAL_TOKENS, v.nValue, pubkey2pk(ParseHex(CC_BURNPUBKEY)))) // burned to dead pubkey + burnAmount += v.nValue; + + entry.push_back(Pair("burnedAmount", ValueFromAmount(burnAmount))); + entry.push_back(Pair("tokenid", tokenid.GetHex())); + } + } + } + } + else + entry.push_back(Pair("burnedAmount", ValueFromAmount(pwtx->vout.back().nValue))); // coins + entry.push_back(Pair("targetSymbol", targetSymbol)); + entry.push_back(Pair("targetCCid", std::to_string(targetCCid))); + if (mytxid_inmempool(pwtx->GetHash())) + entry.push_back(Pair("inMempool", "yes")); + ret.push_back(entry); + } + } //else fprintf(stderr,"null pwtx\n + if ((int)ret.size() >= (nCount)) + break; + } + // ret is newest to oldest + + if (nCount > (int)ret.size()) + nCount = ret.size(); + + vector arrTmp = ret.getValues(); + + vector::iterator first = arrTmp.begin(); + vector::iterator last = arrTmp.begin(); + std::advance(last, nCount); + + if (last != arrTmp.end()) arrTmp.erase(last, arrTmp.end()); + if (first != arrTmp.begin()) arrTmp.erase(arrTmp.begin(), first); + + std::reverse(arrTmp.begin(), arrTmp.end()); // Return oldest to newest + + ret.clear(); + ret.setArray(); + ret.push_backV(arrTmp); + + return ret; +} \ No newline at end of file diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index fc34a2a3f..1b6442865 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -350,9 +350,12 @@ static const CRPCCommand vRPCCommands[] = { "crosschain", "getNotarisationsForBlock", &getNotarisationsForBlock, true }, { "crosschain", "scanNotarisationsDB", &scanNotarisationsDB, true }, { "crosschain", "getimports", &getimports, true }, + { "crosschain", "getwalletburntransactions", &getwalletburntransactions, true }, { "crosschain", "migrate_converttoexport", &migrate_converttoexport, true }, { "crosschain", "migrate_createimporttransaction", &migrate_createimporttransaction, true }, { "crosschain", "migrate_completeimporttransaction", &migrate_completeimporttransaction, true }, + { "crosschain", "migrate_checkburntransactionsource", &migrate_checkburntransactionsource, true }, + { "crosschain", "migrate_createnotaryapprovaltransaction", &migrate_createnotaryapprovaltransaction, true }, { "crosschain", "selfimport", &selfimport, true }, { "crosschain", "importdual", &importdual, true }, //ImportGateway diff --git a/src/rpc/server.h b/src/rpc/server.h index d8fd0e736..db8906f1f 100644 --- a/src/rpc/server.h +++ b/src/rpc/server.h @@ -478,9 +478,12 @@ extern UniValue crosschainproof(const UniValue& params, bool fHelp); extern UniValue getNotarisationsForBlock(const UniValue& params, bool fHelp); extern UniValue scanNotarisationsDB(const UniValue& params, bool fHelp); extern UniValue getimports(const UniValue& params, bool fHelp); +extern UniValue getwalletburntransactions(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 migrate_checkburntransactionsource(const UniValue& params, bool fHelp); +extern UniValue migrate_createnotaryapprovaltransaction(const UniValue& params, bool fHelp); extern UniValue notaries(const UniValue& params, bool fHelp); extern UniValue minerids(const UniValue& params, bool fHelp);