Merge pull request #1442 from dimxy/token-migration
Token crosschain migration support in fungible chain cluster
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -206,13 +206,13 @@ int32_t komodo_blockload(CBlock& block,CBlockIndex *pindex);
|
||||
|
||||
CScript EncodeTokenCreateOpRet(uint8_t funcid, std::vector<uint8_t> origpubkey, std::string name, std::string description, vscript_t vopretNonfungible);
|
||||
CScript EncodeTokenCreateOpRet(uint8_t funcid, std::vector<uint8_t> origpubkey, std::string name, std::string description, std::vector<std::pair<uint8_t, vscript_t>> oprets);
|
||||
CScript EncodeTokenImportOpRet(std::vector<uint8_t> origpubkey, std::string name, std::string description, uint256 srctokenid, std::vector<std::pair<uint8_t, vscript_t>> oprets);
|
||||
|
||||
CScript EncodeTokenOpRet(uint256 tokenid, std::vector<CPubKey> voutPubkeys, std::pair<uint8_t, vscript_t> opretWithId);
|
||||
CScript EncodeTokenOpRet(uint256 tokenid, std::vector<CPubKey> voutPubkeys, std::vector<std::pair<uint8_t, vscript_t>> oprets);
|
||||
int64_t AddCClibtxfee(struct CCcontract_info *cp,CMutableTransaction &mtx,CPubKey pk);
|
||||
uint8_t DecodeTokenCreateOpRet(const CScript &scriptPubKey, std::vector<uint8_t> &origpubkey, std::string &name, std::string &description);
|
||||
uint8_t DecodeTokenCreateOpRet(const CScript &scriptPubKey, std::vector<uint8_t> &origpubkey, std::string &name, std::string &description, std::vector<std::pair<uint8_t, vscript_t>> &oprets);
|
||||
uint8_t DecodeTokenImportOpRet(const CScript &scriptPubKey, std::vector<uint8_t> &origpubkey, std::string &name, std::string &description, uint256 &srctokenid, std::vector<std::pair<uint8_t, vscript_t>> &oprets);
|
||||
|
||||
uint8_t DecodeTokenOpRet(const CScript scriptPubKey, uint8_t &evalCodeTokens, uint256 &tokenid, std::vector<CPubKey> &voutPubkeys, std::vector<std::pair<uint8_t, vscript_t>> &oprets);
|
||||
void GetNonfungibleData(uint256 tokenid, vscript_t &vopretNonfungible);
|
||||
bool ExtractTokensCCVinPubkeys(const CTransaction &tx, std::vector<CPubKey> &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
|
||||
|
||||
@@ -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' <tokenname> <description>
|
||||
//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 <other evalcode> 't' tokenid <other contract payload>
|
||||
//vout.n-1: opreturn EVAL_TOKENS 't' tokenid <other contract payload>
|
||||
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<std::pair<uint8_t, vscript_t>> 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<CPubKey>::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<std::pair<uint8_t, vscript_t>> 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<std::pair<uint8_t, vscript_t>> 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<uint8_t> origpubkey;
|
||||
std::vector<std::pair<uint8_t, vscript_t>> 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<CTxOut> 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<uint8_t> burnOpret;
|
||||
std::string targetSymbol;
|
||||
uint32_t targetCCid;
|
||||
uint256 payoutsHash;
|
||||
std::vector<uint8_t> 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<uint8_t> 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);
|
||||
|
||||
@@ -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<uint8_t> origpubkey,
|
||||
return(opret);
|
||||
}
|
||||
|
||||
/*
|
||||
// opret 'i' for imported tokens
|
||||
CScript EncodeTokenImportOpRet(std::vector<uint8_t> origpubkey, std::string name, std::string description, uint256 srctokenid, std::vector<std::pair<uint8_t, vscript_t>> oprets)
|
||||
{
|
||||
@@ -62,7 +79,7 @@ CScript EncodeTokenImportOpRet(std::vector<uint8_t> origpubkey, std::string name
|
||||
});
|
||||
return(opret);
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
|
||||
CScript EncodeTokenOpRet(uint256 tokenid, std::vector<CPubKey> voutPubkeys, std::pair<uint8_t, vscript_t> opretWithId)
|
||||
@@ -158,37 +175,9 @@ uint8_t DecodeTokenCreateOpRet(const CScript &scriptPubKey, std::vector<uint8_t>
|
||||
return (uint8_t)0;
|
||||
}
|
||||
|
||||
// for imported tokens
|
||||
uint8_t DecodeTokenImportOpRet(const CScript &scriptPubKey, std::vector<uint8_t> &origpubkey, std::string &name, std::string &description, uint256 &srctokenid, std::vector<std::pair<uint8_t, vscript_t>> &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<CPubKey> &voutPubkeys, std::vector<std::pair<uint8_t, vscript_t>> &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<CC*> pks;
|
||||
pks.push_back(CCNewSecp256k1(pk1));
|
||||
pks.push_back(CCNewSecp256k1(pk2));
|
||||
|
||||
std::vector<CC*> 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<CC*> pks;
|
||||
pks.push_back(CCNewSecp256k1(pk));
|
||||
|
||||
std::vector<CC*> 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);
|
||||
}
|
||||
|
||||
@@ -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<CC*> pks;
|
||||
pks.push_back(CCNewSecp256k1(pk1));
|
||||
pks.push_back(CCNewSecp256k1(pk2));
|
||||
|
||||
std::vector<CC*> 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<CC*> pks;
|
||||
pks.push_back(CCNewSecp256k1(pk));
|
||||
|
||||
std::vector<CC*> 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<unsigned char> valtype;
|
||||
std::vector<valtype> 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<uint8_t> 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<uint8_t> origpubkey;
|
||||
|
||||
@@ -20,9 +20,18 @@
|
||||
#include "primitives/transaction.h"
|
||||
#include "cc/CCinclude.h"
|
||||
#include <openssl/sha.h>
|
||||
#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;
|
||||
@@ -61,64 +70,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);
|
||||
@@ -127,9 +137,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;
|
||||
@@ -141,38 +151,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;
|
||||
}
|
||||
|
||||
@@ -476,7 +497,7 @@ int32_t CheckPUBKEYimport(TxProof proof,std::vector<uint8_t> rawproof,CTransacti
|
||||
}
|
||||
|
||||
//ac_pubkey check:
|
||||
if (CheckVin0PubKey(sourcetx) < 0) {
|
||||
if (!CheckVinPubKey(sourcetx, 0, ASSETCHAINS_OVERRIDE_PUBKEY33)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -485,9 +506,11 @@ int32_t CheckPUBKEYimport(TxProof proof,std::vector<uint8_t> 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;
|
||||
}
|
||||
|
||||
@@ -502,21 +525,154 @@ int32_t CheckPUBKEYimport(TxProof proof,std::vector<uint8_t> 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<uint8_t> params,const CTransaction &importTx,unsigned int nIn)
|
||||
bool CheckMigration(Eval *eval, const CTransaction &importTx, const CTransaction &burnTx, std::vector<CTxOut> & payouts, const ImportProof &proof, const std::vector<uint8_t> &rawproof)
|
||||
{
|
||||
TxProof proof; CTransaction burnTx; std::vector<CTxOut> payouts; int64_t txfee = 10000, amount; int32_t height,burnvout; std::vector<CPubKey> publishers;
|
||||
uint32_t targetCcid; std::string targetSymbol,srcaddr,destaddr,receipt,rawburntx; uint256 payoutsHash,bindtxid,burntxid; std::vector<uint8_t> rawproof;
|
||||
std::vector<uint256> txids; CPubKey destpub;
|
||||
vscript_t vimportOpret;
|
||||
if (!GetOpReturnData(importTx.vout.back().scriptPubKey, vimportOpret) ||
|
||||
vimportOpret.empty())
|
||||
return eval->Invalid("invalid-import-tx-no-opret");
|
||||
|
||||
if ( importTx.vout.size() < 2 )
|
||||
|
||||
uint256 tokenid = zeroid;
|
||||
if (vimportOpret.begin()[0] == EVAL_TOKENS) { // for tokens (new opret with tokens)
|
||||
struct CCcontract_info *cpTokens, CCtokens_info;
|
||||
std::vector<std::pair<uint8_t, vscript_t>> oprets;
|
||||
uint8_t evalCodeInOpret;
|
||||
std::vector<CPubKey> 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<std::pair<uint8_t, vscript_t>> oprets;
|
||||
uint8_t evalCodeInOpret;
|
||||
std::vector<CPubKey> 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<std::pair<uint8_t, vscript_t>> 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<std::pair<uint8_t, vscript_t>> 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<uint256> 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");
|
||||
}
|
||||
|
||||
/* if (vimportOpret.begin()[0] == EVAL_TOKENS)
|
||||
return eval->Invalid("test-invalid-tokens-are-good!!");
|
||||
else
|
||||
return eval->Invalid("test-invalid-coins-are-good!!"); */
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Eval::ImportCoin(const std::vector<uint8_t> params, const CTransaction &importTx, unsigned int nIn)
|
||||
{
|
||||
ImportProof proof;
|
||||
CTransaction burnTx;
|
||||
std::vector<CTxOut> payouts;
|
||||
CAmount txfee = 10000, amount;
|
||||
int32_t height, burnvout;
|
||||
std::vector<CPubKey> publishers;
|
||||
uint32_t targetCcid;
|
||||
std::string targetSymbol, srcaddr, destaddr, receipt, rawburntx;
|
||||
uint256 payoutsHash, bindtxid, burntxid;
|
||||
std::vector<uint8_t> rawproof;
|
||||
std::vector<uint256> txids;
|
||||
CPubKey destpub;
|
||||
|
||||
LOGSTREAM("importcoin", CCLOG_DEBUG1, stream << "Validating import tx..., txid=" << importTx.GetHash().GetHex() << std::endl);
|
||||
|
||||
if (strcmp(ASSETCHAINS_SYMBOL, "CFEKDIMXY6") == 0 && chainActive.Height() <= 44693)
|
||||
return true;
|
||||
|
||||
if (importTx.vout.size() < 2)
|
||||
return Invalid("too-few-vouts");
|
||||
// params
|
||||
if (!UnmarshalImportTx(importTx, proof, burnTx, payouts))
|
||||
@@ -528,38 +684,47 @@ bool Eval::ImportCoin(const std::vector<uint8_t> 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<importTx.vout.size(); i++)
|
||||
totalOut += importTx.vout[i].nValue;
|
||||
if (totalOut > 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 (!CheckMigration(this, importTx, burnTx, payouts, proof, rawproof))
|
||||
return false; // eval->Invalid() is called in the func
|
||||
}
|
||||
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" )
|
||||
@@ -573,7 +738,7 @@ bool Eval::ImportCoin(const std::vector<uint8_t> 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
|
||||
@@ -584,5 +749,9 @@ bool Eval::ImportCoin(const std::vector<uint8_t> 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);
|
||||
|
||||
return Valid();
|
||||
}
|
||||
|
||||
@@ -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<CTxOut> payouts; std::vector<uint8_t> rawproof;
|
||||
ImportProof proof; CTransaction burnTx; std::vector<CTxOut> payouts; std::vector<uint8_t> 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<uint256> & notaryTxids)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
// get notaries:
|
||||
uint8_t notaries_pubkeys[64][33];
|
||||
std::vector< std::vector<uint8_t> > 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<uint8_t> vopret;
|
||||
if (!notarytx.vout.empty() && GetOpReturnData(notarytx.vout.back().scriptPubKey, vopret)) {
|
||||
std::vector<uint8_t> txoutproof;
|
||||
|
||||
if (E_UNMARSHAL(vopret, ss >> txoutproof)) {
|
||||
CMerkleBlock merkleBlock;
|
||||
std::vector<uint256> 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<uint8_t> 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
|
||||
|
||||
@@ -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<uint256> & notaryTxids);
|
||||
|
||||
#endif /* CROSSCHAIN_H */
|
||||
|
||||
@@ -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<CTxOut> payouts, uint32_t nExpiryHeightOverride)
|
||||
// makes import tx for either coins or tokens
|
||||
CTransaction MakeImportCoinTransaction(const ImportProof proof, const CTransaction burnTx, const std::vector<CTxOut> payouts, uint32_t nExpiryHeightOverride)
|
||||
{
|
||||
std::vector<uint8_t> payload = E_MARSHAL(ss << EVAL_IMPORTCOIN);
|
||||
//std::vector<uint8_t> 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<uint8_t> vorigpubkey;
|
||||
uint8_t funcId;
|
||||
std::vector <std::pair<uint8_t, vscript_t>> 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<CTxOut> payouts,std::vector<uint8_t> rawproof)
|
||||
CTxOut MakeBurnOutput(CAmount value, uint32_t targetCCid, const std::string targetSymbol, const std::vector<CTxOut> payouts, const std::vector<uint8_t> rawproof)
|
||||
{
|
||||
std::vector<uint8_t> 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);
|
||||
@@ -56,7 +91,8 @@ CTxOut MakeBurnOutput(CAmount value, uint32_t targetCCid, std::string targetSymb
|
||||
uint256 bindtxid,std::vector<CPubKey> publishers,std::vector<uint256> txids,uint256 burntxid,int32_t height,int32_t burnvout,std::string rawburntx,CPubKey destpub, int64_t amount)
|
||||
{
|
||||
std::vector<uint8_t> opret;
|
||||
opret = E_MARSHAL(ss << VARINT(targetCCid);
|
||||
opret = E_MARSHAL(ss << (uint8_t)EVAL_IMPORTCOIN;
|
||||
ss << VARINT(targetCCid);
|
||||
ss << targetSymbol;
|
||||
ss << SerializeHash(payouts);
|
||||
ss << rawproof;
|
||||
@@ -77,7 +113,8 @@ CTxOut MakeBurnOutput(CAmount value, uint32_t targetCCid, std::string targetSymb
|
||||
std::string receipt)
|
||||
{
|
||||
std::vector<uint8_t> opret;
|
||||
opret = E_MARSHAL(ss << VARINT(targetCCid);
|
||||
opret = E_MARSHAL(ss << (uint8_t)EVAL_IMPORTCOIN;
|
||||
ss << VARINT(targetCCid);
|
||||
ss << targetSymbol;
|
||||
ss << SerializeHash(payouts);
|
||||
ss << rawproof;
|
||||
@@ -87,39 +124,114 @@ CTxOut MakeBurnOutput(CAmount value, uint32_t targetCCid, std::string targetSymb
|
||||
}
|
||||
|
||||
|
||||
bool UnmarshalImportTx(const CTransaction importTx, TxProof &proof, CTransaction &burnTx,
|
||||
std::vector<CTxOut> &payouts)
|
||||
bool UnmarshalImportTx(const CTransaction importTx, ImportProof &proof, CTransaction &burnTx, std::vector<CTxOut> &payouts)
|
||||
{
|
||||
std::vector<uint8_t> vData;
|
||||
GetOpReturnData(importTx.vout[importTx.vout.size()-1].scriptPubKey, vData);
|
||||
if (importTx.vout.size() < 1) return false;
|
||||
payouts = std::vector<CTxOut>(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<uint8_t> 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<std::pair<uint8_t, vscript_t>> oprets;
|
||||
std::vector<uint8_t> 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<std::pair<uint8_t, vscript_t>>::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<CTxOut>(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<CTxOut>(importTx.vout.begin()+1, importTx.vout.end()); // see next
|
||||
payouts = std::vector<CTxOut>(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<uint8_t>&rawproof)
|
||||
{
|
||||
std::vector<uint8_t> burnOpret; uint32_t ccid = 0; bool isEof=true;
|
||||
std::vector<uint8_t> 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<std::pair<uint8_t, vscript_t>> oprets;
|
||||
uint256 tokenid;
|
||||
uint8_t evalCodeInOpret;
|
||||
std::vector<CPubKey> 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;
|
||||
bool isEof = true;
|
||||
return E_UNMARSHAL(vburnOpret, ss >> evalCode;
|
||||
ss >> VARINT(*targetCCid);
|
||||
ss >> targetSymbol;
|
||||
ss >> payoutsHash;
|
||||
ss >> rawproof; isEof = ss.eof();) || !isEof; // if isEof == false it means we have successfully read the vars upto 'rawproof'
|
||||
// and it might be additional data further that we do not need here so we allow !isEof
|
||||
}
|
||||
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)
|
||||
{
|
||||
std::vector<uint8_t> burnOpret,rawproof; bool isEof=true;
|
||||
std::string targetSymbol; uint32_t targetCCid; uint256 payoutsHash;
|
||||
uint8_t evalCode;
|
||||
|
||||
if (burnTx.vout.size() == 0) return false;
|
||||
GetOpReturnData(burnTx.vout.back().scriptPubKey, burnOpret);
|
||||
return (E_UNMARSHAL(burnOpret, ss >> VARINT(targetCCid);
|
||||
return (E_UNMARSHAL(burnOpret, ss >> evalCode;
|
||||
ss >> VARINT(targetCCid);
|
||||
ss >> targetSymbol;
|
||||
ss >> payoutsHash;
|
||||
ss >> rawproof;
|
||||
@@ -131,10 +243,12 @@ bool UnmarshalBurnTx(const CTransaction burnTx,uint256 &bindtxid,std::vector<CPu
|
||||
{
|
||||
std::vector<uint8_t> burnOpret,rawproof; bool isEof=true;
|
||||
uint32_t targetCCid; uint256 payoutsHash; std::string targetSymbol;
|
||||
uint8_t evalCode;
|
||||
|
||||
if (burnTx.vout.size() == 0) return false;
|
||||
GetOpReturnData(burnTx.vout.back().scriptPubKey, burnOpret);
|
||||
return (E_UNMARSHAL(burnOpret, ss >> VARINT(targetCCid);
|
||||
return (E_UNMARSHAL(burnOpret, ss >> evalCode;
|
||||
ss >> VARINT(targetCCid);
|
||||
ss >> targetSymbol;
|
||||
ss >> payoutsHash;
|
||||
ss >> rawproof;
|
||||
@@ -155,16 +269,55 @@ bool UnmarshalBurnTx(const CTransaction burnTx,uint256 &bindtxid,std::vector<CPu
|
||||
*/
|
||||
CAmount GetCoinImportValue(const CTransaction &tx)
|
||||
{
|
||||
TxProof proof;
|
||||
ImportProof proof;
|
||||
CTransaction burnTx;
|
||||
std::vector<CTxOut> 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<CPubKey> voutTokenPubkeys;
|
||||
std::vector<std::pair<uint8_t, vscript_t>> 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.
|
||||
|
||||
@@ -22,13 +22,81 @@
|
||||
#include "script/interpreter.h"
|
||||
#include <cryptoconditions.h>
|
||||
|
||||
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<uint256> notaryTxids;
|
||||
std::vector<uint8_t> proofBlock;
|
||||
|
||||
public:
|
||||
ImportProof() { proofKind = PROOF_NONE; }
|
||||
ImportProof(const TxProof &_proofBranch) {
|
||||
proofKind = PROOF_MERKLEBRANCH; proofBranch = _proofBranch;
|
||||
}
|
||||
ImportProof(const std::vector<uint256> &_notaryTxids) {
|
||||
proofKind = PROOF_NOTARYTXIDS; notaryTxids = _notaryTxids;
|
||||
}
|
||||
ImportProof(const std::vector<uint8_t> &_proofBlock) {
|
||||
proofKind = PROOF_MERKLEBLOCK; proofBlock = _proofBlock;
|
||||
}
|
||||
|
||||
ADD_SERIALIZE_METHODS
|
||||
|
||||
template <typename Stream, typename Operation>
|
||||
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);
|
||||
else
|
||||
proofKind = PROOF_NONE; // if we have read some trash
|
||||
}
|
||||
|
||||
bool IsMerkleBranch(TxProof &_proofBranch) const {
|
||||
if (proofKind == PROOF_MERKLEBRANCH) {
|
||||
_proofBranch = proofBranch;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
bool IsNotaryTxids(std::vector<uint256> &_notaryTxids) const {
|
||||
if (proofKind == PROOF_NOTARYTXIDS) {
|
||||
_notaryTxids = notaryTxids;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
bool IsMerkleBlock(std::vector<uint8_t> &_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<CTxOut> payouts, uint32_t nExpiryHeightOverride = 0);
|
||||
CTransaction MakeImportCoinTransaction(const ImportProof proof, const CTransaction burnTx, const std::vector<CTxOut> payouts, uint32_t nExpiryHeightOverride = 0);
|
||||
|
||||
CTxOut MakeBurnOutput(CAmount value, uint32_t targetCCid, std::string targetSymbol, const std::vector<CTxOut> payouts,std::vector<uint8_t> rawproof);
|
||||
CTxOut MakeBurnOutput(CAmount value, uint32_t targetCCid, const std::string targetSymbol, const std::vector<CTxOut> payouts, const std::vector<uint8_t> rawproof);
|
||||
CTxOut MakeBurnOutput(CAmount value, uint32_t targetCCid, std::string targetSymbol, const std::vector<CTxOut> payouts,std::vector<uint8_t> rawproof,
|
||||
uint256 bindtxid,std::vector<CPubKey> publishers,std::vector<uint256>txids,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<CTxOut> payouts,std::vector<uint8_t> rawproof,std::string srcaddr,
|
||||
@@ -37,7 +105,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<uint8_t> &rawproof);
|
||||
bool UnmarshalBurnTx(const CTransaction burnTx, std::string &srcaddr, std::string &receipt);
|
||||
bool UnmarshalBurnTx(const CTransaction burnTx,uint256 &bindtxid,std::vector<CPubKey> &publishers,std::vector<uint256> &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<CTxOut> &payouts);
|
||||
bool UnmarshalImportTx(const CTransaction importTx, ImportProof &proof, CTransaction &burnTx,std::vector<CTxOut> &payouts);
|
||||
|
||||
bool VerifyCoinImport(const CScript& scriptSig, TransactionSignatureChecker& checker, CValidationState &state);
|
||||
|
||||
@@ -45,4 +113,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 */
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
|
||||
#include "key_io.h"
|
||||
#include "cc/CCImportGateway.h"
|
||||
#include "cc/CCtokens.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <univalue.h>
|
||||
@@ -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<CTxOut> 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<tx.vout.size(); i++) burnAmount += tx.vout[i].nValue;
|
||||
if (burnAmount <= 0)
|
||||
throw JSONRPCError(RPC_TYPE_ERROR, "Cannot export a negative or zero value.");
|
||||
// This is due to MAX MONEY in target. We set it at min 1 million coins, so you cant export more than 1 million,
|
||||
// without knowing the MAX money on the target this was the easiest solution.
|
||||
if (burnAmount > 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<rawproof.size(); i++)
|
||||
ptr[i] = ASSETCHAINS_SYMBOL[i];
|
||||
ptr[i] = ASSETCHAINS_SYMBOL[i]; */
|
||||
const std::string chainSymbol(ASSETCHAINS_SYMBOL);
|
||||
rawproof = E_MARSHAL(ss << chainSymbol); // add src chain name
|
||||
|
||||
CTxOut burnOut = MakeBurnOutput(burnAmount+txfee, ccid, targetSymbol, tx.vout,rawproof);
|
||||
UniValue ret(UniValue::VOBJ);
|
||||
ret.push_back(Pair("payouts", HexStr(E_MARSHAL(ss << tx.vout))));
|
||||
@@ -228,26 +242,308 @@ UniValue migrate_converttoexport(const UniValue& params, bool fHelp)
|
||||
return ret;
|
||||
}
|
||||
|
||||
// creates burn tx as an alternative to 'migrate_converttoexport()'
|
||||
UniValue migrate_createburntransaction(const UniValue& params, bool fHelp)
|
||||
{
|
||||
UniValue ret(UniValue::VOBJ);
|
||||
//uint8_t *ptr;
|
||||
//uint8_t i;
|
||||
uint32_t ccid = ASSETCHAINS_CC;
|
||||
int64_t txfee = 10000;
|
||||
|
||||
if (fHelp || params.size() != 3 && params.size() != 4)
|
||||
throw runtime_error(
|
||||
"migrate_createburntransaction dest_symbol dest_addr amount [tokenid]\n"
|
||||
"\nCreates a raw burn transaction to make a cross-chain coin or non-fungible token transfer.\n"
|
||||
"The parameters:\n"
|
||||
"dest_symbol destination chain ac_name\n"
|
||||
"dest_addr address on the destination chain where coins are to be sent or pubkey if tokens are to be sent\n"
|
||||
"amount amount in coins to be burned on the source chain and sent to the destination address/pubkey on the destination chain, for tokens should be equal to 1\n"
|
||||
"tokenid token id, if tokens are transferred (optional). Only non-fungible tokens are supported\n"
|
||||
"\n"
|
||||
"The transaction should be sent using sendrawtransaction to the source chain\n"
|
||||
"The finished burn transaction and payouts should be also passed to "
|
||||
"the \"migrate_createimporttransaction\" method to get the corresponding import transaction.\n"
|
||||
);
|
||||
|
||||
if (ASSETCHAINS_CC < KOMODO_FIRSTFUNGIBLEID)
|
||||
throw runtime_error("-ac_cc < KOMODO_FIRSTFUNGIBLEID");
|
||||
|
||||
if (ASSETCHAINS_SYMBOL[0] == 0)
|
||||
throw runtime_error("Must be called on assetchain");
|
||||
|
||||
// if -pubkey not set it sends change to null pubkey.
|
||||
// we need a better way to return errors from this function!
|
||||
if (ensure_CCrequirements(225) < 0)
|
||||
throw runtime_error("You need to set -pubkey, or run setpukbey RPC, or imports are disabled on this chain.");
|
||||
|
||||
string targetSymbol = params[0].get_str();
|
||||
if (targetSymbol.size() == 0 || targetSymbol.size() > 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<uint8_t> 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<uint8_t> vorigpubkey, vdestpubkey;
|
||||
std::string name, description;
|
||||
std::vector<std::pair<uint8_t, vscript_t>> 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<std::pair<uint8_t, vscript_t>> 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<CPubKey> 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<uint8_t> 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<CPubKey> voutPubkeys;
|
||||
std::vector<std::pair<uint8_t, vscript_t>> 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<uint8_t> origpubkey;
|
||||
std::string name, description;
|
||||
std::vector<std::pair<uint8_t, vscript_t>> 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<CTxOut> 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<uint256> 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<uint8_t> proofData = ParseHex(params[1].get_str());
|
||||
CMerkleBlock merkleBlock;
|
||||
std::vector<uint256> 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<CTxOut> vouts;
|
||||
std::vector<uint8_t> rawproof, rawproofEmpty;
|
||||
int32_t ivout = 0;
|
||||
CScript scriptPubKey;
|
||||
TxProof proof;
|
||||
std::vector<uint8_t> 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;
|
||||
}
|
||||
@@ -940,14 +1307,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<CTxOut> 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<CTxOut> 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)
|
||||
@@ -960,8 +1328,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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -974,3 +1348,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<CAccountingEntry> 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<uint8_t> 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<std::pair<uint8_t, vscript_t>> oprets;
|
||||
uint256 tokenid;
|
||||
uint8_t evalCodeInOpret;
|
||||
std::vector<CPubKey> 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<uint8_t> vorigpubkey;
|
||||
std::string name, description;
|
||||
std::vector<std::pair<uint8_t, vscript_t>> 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<UniValue> arrTmp = ret.getValues();
|
||||
|
||||
vector<UniValue>::iterator first = arrTmp.begin();
|
||||
vector<UniValue>::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;
|
||||
}
|
||||
@@ -203,7 +203,7 @@ void TxToJSONExpanded(const CTransaction& tx, const uint256 hashBlock, UniValue&
|
||||
in.push_back(Pair("coinbase", HexStr(txin.scriptSig.begin(), txin.scriptSig.end())));
|
||||
else if (tx.IsCoinImport()) {
|
||||
in.push_back(Pair("is_import", "1"));
|
||||
TxProof proof; CTransaction burnTx; std::vector<CTxOut> payouts; CTxDestination importaddress;
|
||||
ImportProof proof; CTransaction burnTx; std::vector<CTxOut> payouts; CTxDestination importaddress;
|
||||
if (UnmarshalImportTx(tx, proof, burnTx, payouts))
|
||||
{
|
||||
if (burnTx.vout.size() == 0)
|
||||
|
||||
@@ -350,9 +350,13 @@ 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_createburntransaction", &migrate_createburntransaction, 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
|
||||
|
||||
@@ -478,9 +478,13 @@ 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_createburntransaction(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);
|
||||
|
||||
Reference in New Issue
Block a user