forked from hush/hush3
![dimxy@komodoplatform.com](/assets/img/avatar_default.png)
17 changed files with 3594 additions and 594 deletions
@ -0,0 +1,487 @@ |
|||
/******************************************************************************
|
|||
* Copyright © 2014-2018 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. * |
|||
* * |
|||
******************************************************************************/ |
|||
|
|||
#include "CCtokens.h" |
|||
|
|||
/* TODO: correct this:
|
|||
----------------------------- |
|||
The SetTokenFillamounts() and ValidateTokenRemainder() work in tandem to calculate the vouts for a fill and to validate the vouts, respectively. |
|||
|
|||
This pair of functions are critical to make sure the trading is correct and is the trickiest part of the tokens contract. |
|||
|
|||
//vin.0: normal input
|
|||
//vin.1: unspendable.(vout.0 from buyoffer) buyTx.vout[0]
|
|||
//vin.2+: valid CC output satisfies buyoffer (*tx.vin[2])->nValue
|
|||
//vout.0: remaining amount of bid to unspendable
|
|||
//vout.1: vin.1 value to signer of vin.2
|
|||
//vout.2: vin.2 tokenoshis to original pubkey
|
|||
//vout.3: CC output for tokenoshis change (if any)
|
|||
//vout.4: normal output for change (if any)
|
|||
//vout.n-1: opreturn [EVAL_ASSETS] ['B'] [tokenid] [remaining token required] [origpubkey]
|
|||
ValidateTokenRemainder(remaining_price,tx.vout[0].nValue,nValue,tx.vout[1].nValue,tx.vout[2].nValue,totalunits); |
|||
|
|||
Yes, this is quite confusing... |
|||
|
|||
In ValudateTokenRemainder the naming convention is nValue is the coin/token with the offer on the books and "units" is what it is being paid in. The high level check is to make sure we didnt lose any coins or tokens, the harder to validate is the actual price paid as the "orderbook" is in terms of the combined nValue for the combined totalunits. |
|||
|
|||
We assume that the effective unit cost in the orderbook is valid and that that amount was paid and also that any remainder will be close enough in effective unit cost to not matter. At the edge cases, this will probably be not true and maybe some orders wont be practically fillable when reduced to fractional state. However, the original pubkey that created the offer can always reclaim it. |
|||
------------------------------ |
|||
*/ |
|||
|
|||
|
|||
// NOTE: this inital tx won't be used by other contract
|
|||
// for tokens to be used there should be at least one 't' tx with other contract's custom opret
|
|||
CScript EncodeTokenCreateOpRet(uint8_t funcid,std::vector<uint8_t> origpubkey,std::string name,std::string description) |
|||
{ |
|||
CScript opret; uint8_t evalcode = EVAL_TOKENS; |
|||
opret << OP_RETURN << E_MARSHAL(ss << evalcode << funcid << origpubkey << name << description); |
|||
return(opret); |
|||
} |
|||
|
|||
// this is for other contracts which use tokens and build customized extra payloads to token's opret:
|
|||
CScript EncodeTokenOpRet(uint8_t tokenFuncId, uint8_t evalCodeInOpret, uint256 tokenid, std::vector<uint8_t> payload) |
|||
{ |
|||
CScript opret; |
|||
//uint8_t evalcode = EVAL_TOKENS;
|
|||
tokenid = revuint256(tokenid); |
|||
//uint8_t tokenFuncId = (isTransferrable) ? (uint8_t)'t' : (uint8_t)'l';
|
|||
|
|||
opret << OP_RETURN << E_MARSHAL(ss << evalCodeInOpret << tokenFuncId << tokenid << payload); |
|||
return(opret); |
|||
} |
|||
|
|||
uint8_t DecodeTokenCreateOpRet(const CScript &scriptPubKey,std::vector<uint8_t> &origpubkey,std::string &name,std::string &description) |
|||
{ |
|||
std::vector<uint8_t> vopret; uint8_t dummyEvalcode, funcid, *script; |
|||
|
|||
GetOpReturnData(scriptPubKey, vopret); |
|||
script = (uint8_t *)vopret.data(); |
|||
if ( script != 0 && vopret.size() > 2 && script[0] == EVAL_TOKENS && script[1] == 'c' ) |
|||
{ |
|||
if ( E_UNMARSHAL(vopret, ss >> dummyEvalcode; ss >> funcid; ss >> origpubkey; ss >> name; ss >> description) != 0 ) |
|||
return(funcid); |
|||
} |
|||
return (uint8_t)0; |
|||
} |
|||
|
|||
uint8_t DecodeTokenOpRet(const CScript scriptPubKey, uint8_t &evalCode, uint256 &tokenid, std::vector<uint8_t> &vopretExtra) |
|||
{ |
|||
std::vector<uint8_t> vopret, extra, dummyPubkey; |
|||
uint8_t funcid=0, *script, e, dummyFuncId; |
|||
std::string dummyName; std::string dummyDescription; |
|||
|
|||
GetOpReturnData(scriptPubKey, vopret); |
|||
script = (uint8_t *)vopret.data(); |
|||
tokenid = zeroid; |
|||
|
|||
if (script != 0 /*enable all evals: && script[0] == EVAL_TOKENS*/) |
|||
{ |
|||
bool isEof = true; |
|||
evalCode = script[0]; |
|||
funcid = script[1]; |
|||
//fprintf(stderr,"decode.[%c]\n",funcid);
|
|||
switch ( funcid ) |
|||
{ |
|||
case 'c': |
|||
return DecodeTokenCreateOpRet(scriptPubKey, dummyPubkey, dummyName, dummyDescription); |
|||
//break;
|
|||
case 't': |
|||
//not used yet: case 'l':
|
|||
if (E_UNMARSHAL(vopret, ss >> e; ss >> dummyFuncId; ss >> tokenid; isEof = ss.eof(); vopretExtra = std::vector<uint8_t>(ss.begin(), ss.end())) || !isEof) |
|||
{ |
|||
tokenid = revuint256(tokenid); |
|||
return(funcid); |
|||
} |
|||
std::cerr << "DecodeTokenOpRet() isEof=" << isEof << std::endl; |
|||
fprintf(stderr, "DecodeTokenOpRet() bad opret format\n"); // this may be just check, no error logging
|
|||
return (uint8_t)0; |
|||
|
|||
default: |
|||
fprintf(stderr, "DecodeTokenOpRet() illegal funcid.%02x\n", funcid); |
|||
return (uint8_t)0; |
|||
} |
|||
} |
|||
return (uint8_t)0; |
|||
} |
|||
|
|||
|
|||
|
|||
// tx validation
|
|||
bool TokensValidate(struct CCcontract_info *cp, Eval* eval, const CTransaction &tx, uint32_t nIn) |
|||
{ |
|||
static uint256 zero; |
|||
CTxDestination address; CTransaction vinTx, createTx; uint256 hashBlock, tokenid, tokenid2; |
|||
int32_t i, starti, numvins, numvouts, preventCCvins, preventCCvouts; |
|||
int64_t remaining_price, nValue, tokenoshis, outputs, inputs, tmpprice, totalunits, ignore; std::vector<uint8_t> origpubkey, tmporigpubkey, ignorepubkey; |
|||
uint8_t funcid, evalCodeInOpret; |
|||
char destaddr[64], origaddr[64], CCaddr[64]; |
|||
|
|||
numvins = tx.vin.size(); |
|||
numvouts = tx.vout.size(); |
|||
outputs = inputs = 0; |
|||
preventCCvins = preventCCvouts = -1; |
|||
|
|||
if ((funcid = DecodeTokenOpRet(tx.vout[numvouts - 1].scriptPubKey, evalCodeInOpret, tokenid, origpubkey)) == 0) |
|||
return eval->Invalid("TokenValidate: invalid opreturn payload"); |
|||
|
|||
fprintf(stderr, "TokensValidate (%c)\n", funcid); |
|||
|
|||
if (eval->GetTxUnconfirmed(tokenid, createTx, hashBlock) == 0) |
|||
return eval->Invalid("cant find token create txid"); |
|||
else if (IsCCInput(tx.vin[0].scriptSig) != 0) |
|||
return eval->Invalid("illegal token vin0"); // why? (dimxy)
|
|||
else if (numvouts < 1) |
|||
return eval->Invalid("no vouts"); |
|||
else if (funcid != 'c') |
|||
{ |
|||
if (tokenid == zeroid) |
|||
return eval->Invalid("illegal tokenid"); |
|||
else if (!TokensExactAmounts(true, cp, inputs, outputs, eval, tx, tokenid)) { |
|||
if (!eval->Valid()) |
|||
return false; //TokenExactAmounts must call eval->Invalid()!
|
|||
else |
|||
return eval->Invalid("tokens cc inputs != cc outputs"); |
|||
} |
|||
} |
|||
|
|||
// init for forwarding validation call
|
|||
struct CCcontract_info *cpOther = NULL, C; |
|||
if (evalCodeInOpret != EVAL_TOKENS) |
|||
cpOther = CCinit(&C, evalCodeInOpret); |
|||
|
|||
switch (funcid) |
|||
{ |
|||
case 'c': // create wont be called to be verified as it has no CC inputs
|
|||
//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; |
|||
|
|||
case 't': // transfer
|
|||
//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>
|
|||
if (inputs == 0) |
|||
return eval->Invalid("no token inputs for transfer"); |
|||
|
|||
fprintf(stderr, "token transfer preliminarily validated %.8f -> %.8f (%d %d)\n", (double)inputs / COIN, (double)outputs / COIN, preventCCvins, preventCCvouts); |
|||
break; // breaking to other contract validation...
|
|||
|
|||
default: |
|||
fprintf(stderr, "illegal tokens funcid.(%c)\n", funcid); |
|||
return eval->Invalid("unexpected token funcid"); |
|||
} |
|||
|
|||
// forward validation if evalcode in opret is not EVAL_TOKENS
|
|||
if (cpOther) |
|||
return cpOther->validate(cpOther, eval, tx, nIn); |
|||
else |
|||
return eval->Invalid("unsupported evalcode in opret"); |
|||
|
|||
// what does this do?
|
|||
// return(PreventCC(eval,tx,preventCCvins,numvins,preventCCvouts,numvouts));
|
|||
} |
|||
|
|||
|
|||
|
|||
// this is just for log messages indentation fur debugging recursive calls:
|
|||
thread_local uint32_t tokenValIndentSize = 0; |
|||
|
|||
// validates opret for token tx:
|
|||
bool ValidateTokenOpret(CTransaction tx, int32_t v, uint256 tokenid, std::vector<uint8_t> &vopretExtra) { |
|||
|
|||
uint256 tokenidOpret, tokenidOpret2; |
|||
uint8_t funcid, evalCode; |
|||
|
|||
// this is just for log messages indentation fur debugging recursive calls:
|
|||
std::string indentStr = std::string().append(tokenValIndentSize, '.'); |
|||
|
|||
int32_t n = tx.vout.size(); |
|||
|
|||
if ((funcid = DecodeTokenOpRet(tx.vout[n - 1].scriptPubKey, evalCode, tokenidOpret, vopretExtra)) == 0) |
|||
{ |
|||
std::cerr << indentStr << "ValidateTokenOpret() DecodeOpret returned null for n-1=" << n - 1 << " txid=" << tx.GetHash().GetHex() << std::endl; |
|||
return(false); |
|||
} |
|||
else if (funcid == 'c') |
|||
{ |
|||
if (tokenid != zeroid && tokenid == tx.GetHash() && v == 0) { |
|||
//std::cerr << indentStr << "ValidateTokenOpret() this is the tokenbase 'c' tx, txid=" << tx.GetHash().GetHex() << " vout=" << v << " returning true" << std::endl;
|
|||
return(true); |
|||
} |
|||
} |
|||
else if (funcid == 't') |
|||
{ |
|||
//std::cerr << indentStr << "ValidateTokenOpret() tokenid=" << tokenid.GetHex() << " tokenIdOpret=" << tokenidOpret.GetHex() << " txid=" << tx.GetHash().GetHex() << std::endl;
|
|||
if (tokenid != zeroid && tokenid == tokenidOpret) { |
|||
//std::cerr << indentStr << "ValidateTokenOpret() this is a transfer 't' tx, txid=" << tx.GetHash().GetHex() << " vout=" << v << " returning true" << std::endl;
|
|||
return(true); |
|||
} |
|||
} |
|||
//std::cerr << indentStr << "ValidateTokenOpret() return false funcid=" << (char)funcid << " tokenid=" << tokenid.GetHex() << " tokenIdOpret=" << tokenidOpret.GetHex() << " txid=" << tx.GetHash().GetHex() << std::endl;
|
|||
return false; |
|||
} |
|||
|
|||
|
|||
// Checks if the vout is a really Tokens CC vout
|
|||
// compareTotals == true, the func also validates the passed transaction itself:
|
|||
// it should be either sum(cc vins) == sum(cc vouts) or the transaction is the 'tokenbase' ('c') tx
|
|||
int64_t IsTokensvout(bool compareTotals, struct CCcontract_info *cp, Eval* eval, std::vector<uint8_t> &vopretExtra, const CTransaction& tx, int32_t v, uint256 reftokenid) |
|||
{ |
|||
|
|||
// this is just for log messages indentation fur debugging recursive calls:
|
|||
std::string indentStr = std::string().append(tokenValIndentSize, '.'); |
|||
//std::cerr << indentStr << "IsTokensvout() entered for txid=" << tx.GetHash().GetHex() << " v=" << v << " for tokenid=" << reftokenid.GetHex() << std::endl;
|
|||
|
|||
//TODO: validate cc vouts are EVAL_TOKENS!
|
|||
if (tx.vout[v].scriptPubKey.IsPayToCryptoCondition() != 0) // maybe check address too? dimxy: possibly no, because there are too many cases with different addresses here
|
|||
{ |
|||
int32_t n = tx.vout.size(); |
|||
// just check boundaries:
|
|||
if (v >= n - 1) { // just moved this up (dimxy)
|
|||
std::cerr << indentStr << "isTokensvout() internal err: (v >= n - 1), returning 0" << std::endl; |
|||
return(0); |
|||
} |
|||
|
|||
if (compareTotals) { |
|||
//std::cerr << indentStr << "IsTokensvout() maxTokenExactAmountDepth=" << maxTokenExactAmountDepth << std::endl;
|
|||
//validate all tx
|
|||
int64_t myCCVinsAmount = 0, myCCVoutsAmount = 0; |
|||
|
|||
tokenValIndentSize++; |
|||
// false --> because we already at the 1-st level ancestor tx and do not need to dereference ancestors of next levels
|
|||
bool isEqual = TokensExactAmounts(false, cp, myCCVinsAmount, myCCVoutsAmount, eval, tx, reftokenid); |
|||
tokenValIndentSize--; |
|||
|
|||
if (!isEqual) { |
|||
// if ccInputs != ccOutputs and it is not the tokenbase tx
|
|||
// this means it is possibly a fake tx (dimxy):
|
|||
if (reftokenid != tx.GetHash()) { // checking that this is the true tokenbase tx, by verifying that funcid=c, is done further in this function (dimxy)
|
|||
std::cerr << indentStr << "IsTokensvout() warning: for the verified tx detected a bad vintx=" << tx.GetHash().GetHex() << ": cc inputs != cc outputs and not the 'tokenbase' tx, skipping the verified tx" << std::endl; |
|||
return 0; |
|||
} |
|||
} |
|||
} |
|||
|
|||
// moved opret checking to this new reusable func (dimxy):
|
|||
const bool valOpret = ValidateTokenOpret(tx, v, reftokenid, vopretExtra); |
|||
//std::cerr << indentStr << "IsTokensvout() ValidateTokenOpret returned=" << std::boolalpha << valOpret << " for txid=" << tx.GetHash().GetHex() << " for tokenid=" << reftokenid.GetHex() << std::endl;
|
|||
if (valOpret) { |
|||
//std::cerr << indentStr << "IsTokensvout() ValidateTokenOpret returned true, returning nValue=" << tx.vout[v].nValue << " for txid=" << tx.GetHash().GetHex() << " for tokenid=" << reftokenid.GetHex() << std::endl;
|
|||
return tx.vout[v].nValue; |
|||
} |
|||
|
|||
//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); |
|||
} |
|||
|
|||
// compares cc inputs vs cc outputs (to prevent feeding vouts from normal inputs)
|
|||
bool TokensExactAmounts(bool compareTotals, struct CCcontract_info *cpTokens, int64_t &inputs, int64_t &outputs, Eval* eval, const CTransaction &tx, uint256 tokenid) |
|||
{ |
|||
CTransaction vinTx; uint256 hashBlock, id, id2; int32_t flag; int64_t tokenoshis; std::vector<uint8_t> tmporigpubkey; int64_t tmpprice; |
|||
int32_t numvins = tx.vin.size(); |
|||
int32_t numvouts = tx.vout.size(); |
|||
inputs = outputs = 0; |
|||
|
|||
// this is just for log messages indentation for debugging recursive calls:
|
|||
std::string indentStr = std::string().append(tokenValIndentSize, '.'); |
|||
|
|||
for (int32_t i = 0; i<numvins; i++) |
|||
{ // check for additional contracts which may send tokens to the Tokens contract
|
|||
if ((*cpTokens->ismyvin)(tx.vin[i].scriptSig) /*|| IsVinAllowed(tx.vin[i].scriptSig) != 0*/) |
|||
{ |
|||
//std::cerr << indentStr << "TokensExactAmounts() eval is true=" << (eval != NULL) << " ismyvin=ok for_i=" << i << std::endl;
|
|||
// we are not inside the validation code -- dimxy
|
|||
if ((eval && eval->GetTxUnconfirmed(tx.vin[i].prevout.hash, vinTx, hashBlock) == 0) || (!eval && !myGetTransaction(tx.vin[i].prevout.hash, vinTx, hashBlock))) |
|||
{ |
|||
std::cerr << indentStr << "TokensExactAmounts() cannot read vintx for i." << i << " numvins." << numvins << std::endl; |
|||
return (!eval) ? false : eval->Invalid("always should find vin tx, but didnt"); |
|||
|
|||
} |
|||
else { |
|||
tokenValIndentSize++; |
|||
// validate vouts of vintx
|
|||
//std::cerr << indentStr << "TokenExactAmounts() check vin i=" << i << " nValue=" << vinTx.vout[tx.vin[i].prevout.n].nValue << std::endl;
|
|||
tokenoshis = IsTokensvout(compareTotals, cpTokens, eval, tmporigpubkey, vinTx, tx.vin[i].prevout.n, tokenid); |
|||
tokenValIndentSize--; |
|||
if (tokenoshis != 0) |
|||
{ |
|||
std::cerr << indentStr << "TokensExactAmounts() vin i=" << i << " tokenoshis=" << tokenoshis << std::endl; |
|||
inputs += tokenoshis; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
for (int32_t i = 0; i<numvouts; i++) |
|||
{ |
|||
tokenValIndentSize++; |
|||
// Note: we pass in here 'false' because we don't need to call TokenExactAmounts() recursively from IsTokenvout
|
|||
// indeed, in this case we'll be checking this tx again
|
|||
tokenoshis = IsTokensvout(false, cpTokens, eval, tmporigpubkey, tx, i, tokenid); |
|||
tokenValIndentSize--; |
|||
|
|||
if (tokenoshis != 0) |
|||
{ |
|||
std::cerr << indentStr << "TokensExactAmounts() vout i=" << i << " tokenoshis=" << tokenoshis << std::endl; |
|||
outputs += tokenoshis; |
|||
} |
|||
} |
|||
|
|||
//std::cerr << indentStr << "TokensExactAmounts() inputs=" << inputs << " outputs=" << outputs << " for txid=" << tx.GetHash().GetHex() << std::endl;
|
|||
|
|||
if (inputs != outputs) { |
|||
if (tx.GetHash() != tokenid) |
|||
std::cerr << indentStr << "TokenExactAmounts() found unequal inputs=" << inputs << " vs outputs=" << outputs << " for txid=" << tx.GetHash().GetHex() << " and this is not create tx" << std::endl; |
|||
return false; // do not call eval->Invalid() here!
|
|||
} |
|||
else |
|||
return true; |
|||
} |
|||
|
|||
// add inputs from token cc addr
|
|||
int64_t AddTokenCCInputs(struct CCcontract_info *cp, CMutableTransaction &mtx, CPubKey pk, uint256 tokenid, int64_t total, int32_t maxinputs) |
|||
{ |
|||
char coinaddr[64], destaddr[64]; |
|||
int64_t threshold, nValue, price, totalinputs = 0; |
|||
uint256 txid, hashBlock; |
|||
std::vector<uint8_t> vopretExtra; |
|||
CTransaction vintx; |
|||
int32_t j, vout, n = 0; |
|||
std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue> > unspentOutputs; |
|||
|
|||
GetCCaddress(cp, coinaddr, pk); |
|||
SetCCunspents(unspentOutputs, coinaddr); |
|||
|
|||
threshold = total / (maxinputs != 0 ? maxinputs : 64); // TODO: is maxinputs really could not be over 64? what if i want to calc total balance?
|
|||
|
|||
for (std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue> >::const_iterator it = unspentOutputs.begin(); it != unspentOutputs.end(); it++) |
|||
{ |
|||
txid = it->first.txhash; |
|||
vout = (int32_t)it->first.index; |
|||
if (it->second.satoshis < threshold) |
|||
continue; |
|||
for (j = 0; j<mtx.vin.size(); j++) |
|||
if (txid == mtx.vin[j].prevout.hash && vout == mtx.vin[j].prevout.n) |
|||
break; |
|||
if (j != mtx.vin.size()) |
|||
continue; |
|||
if (GetTransaction(txid, vintx, hashBlock, false) != 0) |
|||
{ |
|||
Getscriptaddress(destaddr, vintx.vout[vout].scriptPubKey); |
|||
if (strcmp(destaddr, coinaddr) != 0 && strcmp(destaddr, cp->unspendableCCaddr) != 0 && strcmp(destaddr, cp->unspendableaddr2) != 0) |
|||
continue; |
|||
fprintf(stderr, "AddTokenCCInputs() check destaddress=%s vout amount=%.8f\n", destaddr, (double)vintx.vout[vout].nValue / COIN); |
|||
if ((nValue = IsTokensvout(true, cp, NULL, vopretExtra, vintx, vout, tokenid)) > 0 && myIsutxo_spentinmempool(txid, vout) == 0) |
|||
{ |
|||
if (total != 0 && maxinputs != 0) |
|||
mtx.vin.push_back(CTxIn(txid, vout, CScript())); |
|||
nValue = it->second.satoshis; |
|||
totalinputs += nValue; |
|||
//std::cerr << "AddTokenInputs() adding input nValue=" << nValue << std::endl;
|
|||
n++; |
|||
if ((total > 0 && totalinputs >= total) || (maxinputs > 0 && n >= maxinputs)) |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
//std::cerr << "AddTokenInputs() found totalinputs=" << totalinputs << std::endl;
|
|||
return(totalinputs); |
|||
} |
|||
|
|||
|
|||
std::string CreateToken(int64_t txfee, int64_t assetsupply, std::string name, std::string description) |
|||
{ |
|||
CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), komodo_nextheight()); |
|||
CPubKey mypk; struct CCcontract_info *cp, C; |
|||
if (assetsupply < 0) |
|||
{ |
|||
fprintf(stderr, "negative assetsupply %lld\n", (long long)assetsupply); |
|||
return(""); |
|||
} |
|||
|
|||
cp = CCinit(&C, EVAL_TOKENS); |
|||
if (name.size() > 32 || description.size() > 4096) |
|||
{ |
|||
fprintf(stderr, "name.%d or description.%d is too big\n", (int32_t)name.size(), (int32_t)description.size()); |
|||
return(""); |
|||
} |
|||
if (txfee == 0) |
|||
txfee = 10000; |
|||
mypk = pubkey2pk(Mypubkey()); |
|||
|
|||
if (AddNormalinputs(mtx, mypk, assetsupply + 2 * txfee, 64) > 0) |
|||
{ |
|||
mtx.vout.push_back(MakeCC1vout(EVAL_TOKENS, assetsupply, mypk)); |
|||
mtx.vout.push_back(CTxOut(txfee, CScript() << ParseHex(cp->CChexstr) << OP_CHECKSIG)); |
|||
return(FinalizeCCTx(0, cp, mtx, mypk, txfee, EncodeTokenCreateOpRet('c', Mypubkey(), name, description))); |
|||
} |
|||
return(""); |
|||
} |
|||
|
|||
|
|||
std::string TokenTransfer(int64_t txfee, uint256 assetid, std::vector<uint8_t> destpubkey, int64_t total) |
|||
{ |
|||
CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), komodo_nextheight()); |
|||
CPubKey mypk; uint64_t mask; int64_t CCchange = 0, inputs = 0; struct CCcontract_info *cp, C; |
|||
std::vector<uint8_t> emptyExtraOpret; |
|||
|
|||
if (total < 0) |
|||
{ |
|||
fprintf(stderr, "negative total %lld\n", (long long)total); |
|||
return(""); |
|||
} |
|||
cp = CCinit(&C, EVAL_TOKENS); |
|||
if (txfee == 0) |
|||
txfee = 10000; |
|||
mypk = pubkey2pk(Mypubkey()); |
|||
if (AddNormalinputs(mtx, mypk, txfee, 3) > 0) |
|||
{ |
|||
//n = outputs.size();
|
|||
//if ( n == amounts.size() )
|
|||
//{
|
|||
// for (i=0; i<n; i++)
|
|||
// total += amounts[i];
|
|||
mask = ~((1LL << mtx.vin.size()) - 1); |
|||
if ((inputs = AddTokenCCInputs(cp, mtx, mypk, assetid, total, 60)) > 0) |
|||
{ |
|||
|
|||
if (inputs < total) { //added dimxy
|
|||
std::cerr << "AssetTransfer(): insufficient funds" << std::endl; |
|||
return (""); |
|||
} |
|||
if (inputs > total) |
|||
CCchange = (inputs - total); |
|||
//for (i=0; i<n; i++)
|
|||
mtx.vout.push_back(MakeCC1vout(EVAL_ASSETS, total, pubkey2pk(destpubkey))); |
|||
if (CCchange != 0) |
|||
mtx.vout.push_back(MakeCC1vout(EVAL_ASSETS, CCchange, mypk)); |
|||
return(FinalizeCCTx(mask, cp, mtx, mypk, txfee, EncodeTokenOpRet('t', EVAL_TOKENS, assetid, emptyExtraOpret))); // By setting EVA_TOKENS we're getting out from assets validation code
|
|||
} |
|||
else fprintf(stderr, "not enough CC asset inputs for %.8f\n", (double)total / COIN); |
|||
//} else fprintf(stderr,"numoutputs.%d != numamounts.%d\n",n,(int32_t)amounts.size());
|
|||
} |
|||
return(""); |
|||
} |
@ -0,0 +1,76 @@ |
|||
/******************************************************************************
|
|||
* Copyright © 2014-2018 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. * |
|||
* * |
|||
******************************************************************************/ |
|||
|
|||
|
|||
/*
|
|||
CCassetstx has the functions that create the EVAL_ASSETS transactions. It is expected that rpc calls would call these functions. For EVAL_ASSETS, the rpc functions are in rpcwallet.cpp |
|||
|
|||
CCassetsCore has functions that are used in two contexts, both during rpc transaction create time and also during the blockchain validation. Using the identical functions is a good way to prevent them from being mismatched. The must match or the transaction will get rejected. |
|||
*/ |
|||
|
|||
#ifndef CC_TOKENS_H |
|||
#define CC_TOKENS_H |
|||
|
|||
#include "CCinclude.h" |
|||
|
|||
// CCcustom
|
|||
bool TokensValidate(struct CCcontract_info *cp,Eval* eval,const CTransaction &tx, uint32_t nIn); |
|||
bool TokensExactAmounts(bool compareTotals, struct CCcontract_info *cpTokens, int64_t &inputs, int64_t &outputs, Eval* eval, const CTransaction &tx, uint256 tokenid); |
|||
int64_t IsTokensvout(bool compareTotals, struct CCcontract_info *cp, Eval* eval, std::vector<uint8_t> &origpubkey, const CTransaction& tx, int32_t v, uint256 reftokenid); |
|||
std::string CreateToken(int64_t txfee, int64_t assetsupply, std::string name, std::string description); |
|||
std::string TokenTransfer(int64_t txfee, uint256 assetid, std::vector<uint8_t> destpubkey, int64_t total); |
|||
|
|||
//this is in CCinclude.h int64_t AddTokenInputs(struct CCcontract_info *cp, CMutableTransaction &mtx, CPubKey pk, uint256 tokenid, int64_t total, int32_t maxinputs);
|
|||
|
|||
//this is in CCinclude.h uint8_t DecodeTokenCreateOpRet(const CScript &scriptPubKey,std::vector<uint8_t> &origpubkey,std::string &name,std::string &description);
|
|||
|
|||
/*
|
|||
// CCassetsCore
|
|||
CScript EncodeAssetCreateOpRet(uint8_t funcid,std::vector<uint8_t> origpubkey,std::string name,std::string description); |
|||
CScript EncodeAssetOpRet(uint8_t funcid,uint256 assetid,uint256 assetid2,int64_t price,std::vector<uint8_t> origpubkey); |
|||
bool DecodeAssetCreateOpRet(const CScript &scriptPubKey,std::vector<uint8_t> &origpubkey,std::string &name,std::string &description); |
|||
//uint8_t DecodeAssetOpRet(const CScript &scriptPubKey, uint8_t &evalCode, uint256 &assetid, uint256 &assetid2, int64_t &price, std::vector<uint8_t> &origpubkey);
|
|||
bool SetAssetOrigpubkey(std::vector<uint8_t> &origpubkey,int64_t &price,const CTransaction &tx); |
|||
int64_t IsAssetvout(bool compareTotals, struct CCcontract_info *cp, Eval* eval, int64_t &price, std::vector<uint8_t> &origpubkey, const CTransaction& tx, int32_t v, uint256 refassetid); |
|||
bool ValidateBidRemainder(int64_t remaining_price,int64_t remaining_nValue,int64_t orig_nValue,int64_t received_nValue,int64_t paidprice,int64_t totalprice); |
|||
bool ValidateAskRemainder(int64_t remaining_price,int64_t remaining_nValue,int64_t orig_nValue,int64_t received_nValue,int64_t paidprice,int64_t totalprice); |
|||
bool ValidateSwapRemainder(int64_t remaining_price,int64_t remaining_nValue,int64_t orig_nValue,int64_t received_nValue,int64_t paidprice,int64_t totalprice); |
|||
bool SetBidFillamounts(int64_t &paid,int64_t &remaining_price,int64_t orig_nValue,int64_t &received,int64_t totalprice); |
|||
bool SetAskFillamounts(int64_t &paid,int64_t &remaining_price,int64_t orig_nValue,int64_t &received,int64_t totalprice); |
|||
bool SetSwapFillamounts(int64_t &paid,int64_t &remaining_price,int64_t orig_nValue,int64_t &received,int64_t totalprice); |
|||
int64_t AssetValidateBuyvin(struct CCcontract_info *cp,Eval* eval,int64_t &tmpprice,std::vector<uint8_t> &tmporigpubkey,char *CCaddr,char *origaddr,const CTransaction &tx,uint256 refassetid); |
|||
int64_t AssetValidateSellvin(struct CCcontract_info *cp,Eval* eval,int64_t &tmpprice,std::vector<uint8_t> &tmporigpubkey,char *CCaddr,char *origaddr,const CTransaction &tx,uint256 assetid); |
|||
bool AssetExactAmounts(bool compareTotals, struct CCcontract_info *cpAssets, int64_t &inputs, int64_t &outputs, Eval* eval, const CTransaction &tx, uint256 assetid); |
|||
|
|||
// CCassetstx
|
|||
int64_t GetAssetBalance(CPubKey pk,uint256 tokenid); |
|||
int64_t AddAssetInputs(CMutableTransaction &mtx,CPubKey pk,uint256 assetid,int64_t total,int32_t maxinputs); |
|||
UniValue AssetOrders(uint256 tokenid); |
|||
UniValue AssetInfo(uint256 tokenid); |
|||
UniValue AssetList(); |
|||
std::string CreateAsset(int64_t txfee,int64_t assetsupply,std::string name,std::string description); |
|||
std::string AssetTransfer(int64_t txfee,uint256 assetid,std::vector<uint8_t> destpubkey,int64_t total); |
|||
std::string AssetConvert(int64_t txfee,uint256 assetid,std::vector<uint8_t> destpubkey,int64_t total,int32_t evalcode); |
|||
|
|||
std::string CreateBuyOffer(int64_t txfee,int64_t bidamount,uint256 assetid,int64_t pricetotal); |
|||
std::string CancelBuyOffer(int64_t txfee,uint256 assetid,uint256 bidtxid); |
|||
std::string FillBuyOffer(int64_t txfee,uint256 assetid,uint256 bidtxid,int64_t fillamount); |
|||
std::string CreateSell(int64_t txfee,int64_t askamount,uint256 assetid,int64_t pricetotal); |
|||
std::string CreateSwap(int64_t txfee,int64_t askamount,uint256 assetid,uint256 assetid2,int64_t pricetotal); |
|||
std::string CancelSell(int64_t txfee,uint256 assetid,uint256 asktxid); |
|||
std::string FillSell(int64_t txfee,uint256 assetid,uint256 assetid2,uint256 asktxid,int64_t fillamount); |
|||
*/ |
|||
|
|||
#endif |
File diff suppressed because it is too large
@ -0,0 +1,622 @@ |
|||
#ifndef HEIR_VALIDATE_H |
|||
#define HEIR_VALIDATE_H |
|||
|
|||
#include "CCinclude.h" |
|||
#include "CCHeir.h" |
|||
|
|||
#define NOT_HEIR (-1) |
|||
#define HEIR_COINS 1 |
|||
#define HEIR_TOKENS 2 |
|||
#define IS_CHARINSTR(c, str) (std::string(str).find((char)(c)) != std::string::npos) |
|||
|
|||
|
|||
// makes coin initial tx opret
|
|||
CScript EncodeHeirCreateOpRet(uint8_t eval, uint8_t funcid, CPubKey ownerPubkey, CPubKey heirPubkey, int64_t inactivityTimeSec, std::string heirName); |
|||
// makes coin additional tx opret
|
|||
CScript EncodeHeirOpRet(uint8_t eval, uint8_t funcid, uint256 fundingtxid); |
|||
|
|||
CScript EncodeHeirAssetsCreateOpRet(uint8_t eval, uint8_t funcid, uint256 tokenid, CPubKey ownerPubkey, CPubKey heirPubkey, int64_t inactivityTimeSec, std::string hearName); |
|||
CScript EncodeHeirAssetsOpRet(uint8_t eval, uint8_t funcid, uint256 tokenid, uint256 fundingtxid); |
|||
//CScript EncodeHeirConvertedAssetsOpRet(uint8_t eval, uint8_t funcid, uint256 tokenid, CPubKey ownerPubkey, CPubKey heirPubkey, int64_t inactivityTimeSec, uint256 fundingtxid);
|
|||
|
|||
template <class Helper> uint256 FindLatestFundingTx(uint256 fundingtxid, uint256 &tokenid, CScript& opRetScript, bool &isHeirSpendingBegan); |
|||
template <class Helper> uint8_t DecodeHeirOpRet(CScript scriptPubKey, uint256 &tokenid, uint256& fundingtxid, bool noLogging = false); |
|||
//template <class Helper> uint8_t DecodeHeirOpRet(CScript scriptPubKey, uint256 &tokenid, CPubKey& ownerPubkey, CPubKey& heirPubkey, int64_t& inactivityTime, std::string& heirName, bool noLogging = false);
|
|||
template <class Helper> uint8_t DecodeHeirOpRet(CScript scriptPubKey, uint256 &tokenid, CPubKey& ownerPubkey, CPubKey& heirPubkey, int64_t& inactivityTime, std::string& heirName, bool noLogging = false); |
|||
|
|||
//int64_t AddHeirTokenInputs(struct CCcontract_info *cp, CMutableTransaction &mtx, CPubKey pk, uint256 reftokenid, int64_t total, int32_t maxinputs);
|
|||
|
|||
|
|||
// helper class to allow polymorphic behaviour for HeirXXX() functions in case of coins
|
|||
class CoinHelper { |
|||
public: |
|||
|
|||
static bool isMyFuncId(uint8_t funcid) { return IS_CHARINSTR(funcid, "FAC"); } |
|||
static uint8_t getMyEval() { return EVAL_HEIR; } |
|||
static int64_t addOwnerInputs(struct CCcontract_info* cp, uint256 dummyid, CMutableTransaction& mtx, CPubKey ownerPubkey, int64_t total, int32_t maxinputs) { |
|||
return AddNormalinputs(mtx, ownerPubkey, total, maxinputs); |
|||
} |
|||
|
|||
static CScript makeCreateOpRet(uint256 dummyid, CPubKey ownerPubkey, CPubKey heirPubkey, int64_t inactivityTimeSec, std::string heirName) { |
|||
return EncodeHeirCreateOpRet((uint8_t)EVAL_HEIR, (uint8_t)'F', ownerPubkey, heirPubkey, inactivityTimeSec, heirName); |
|||
} |
|||
static CScript makeAddOpRet(uint256 dummyid, uint256 fundingtxid) { |
|||
return EncodeHeirOpRet((uint8_t)EVAL_HEIR, (uint8_t)'A', fundingtxid); |
|||
} |
|||
static CScript makeClaimOpRet(uint256 dummyid, uint256 fundingtxid) { |
|||
return EncodeHeirOpRet((uint8_t)EVAL_HEIR, (uint8_t)'C', fundingtxid); |
|||
} |
|||
|
|||
static void UnmarshalOpret(std::vector<uint8_t> vopret, uint8_t &e, uint8_t &funcId, uint256 &dummytokenid, CPubKey& ownerPubkey, CPubKey& heirPubkey, int64_t& inactivityTime, std::string& heirName, uint256& fundingTxidInOpret) { |
|||
E_UNMARSHAL(vopret, { ss >> e; ss >> funcId; ss >> ownerPubkey; ss >> heirPubkey; ss >> inactivityTime; if (IS_CHARINSTR(funcId, "F")) { ss >> heirName; } if (IS_CHARINSTR(funcId, "AC")) { ss >> fundingTxidInOpret; } }); |
|||
} |
|||
|
|||
static bool isSpendingTx(uint8_t funcid) { return (funcid == 'C'); } |
|||
|
|||
static CTxOut makeUserVout(int64_t amount, CPubKey myPubkey) { |
|||
return CTxOut(amount, CScript() << ParseHex(HexStr(myPubkey)) << OP_CHECKSIG); |
|||
} |
|||
static CTxOut makeClaimerVout(int64_t amount, CPubKey myPubkey) { |
|||
return CTxOut(amount, CScript() << ParseHex(HexStr(myPubkey)) << OP_CHECKSIG); |
|||
} |
|||
}; |
|||
|
|||
// helper class to allow polymorphic behaviour for HeirXXX() functions in case of tokens
|
|||
class TokenHelper { |
|||
public: |
|||
|
|||
static bool isMyFuncId(uint8_t funcid) { return IS_CHARINSTR(funcid, "FAC"); } |
|||
static uint8_t getMyEval() { return EVAL_TOKENS; } |
|||
static int64_t addOwnerInputs(struct CCcontract_info* cp, uint256 tokenid, CMutableTransaction& mtx, CPubKey ownerPubkey, int64_t total, int32_t maxinputs) { |
|||
return AddTokenCCInputs(cp, mtx, ownerPubkey, tokenid, total, maxinputs); |
|||
} |
|||
|
|||
static CScript makeCreateOpRet(uint256 tokenid, CPubKey ownerPubkey, CPubKey heirPubkey, int64_t inactivityTimeSec, std::string heirName) { |
|||
return EncodeHeirAssetsCreateOpRet((uint8_t)EVAL_HEIR, (uint8_t)'F', tokenid, ownerPubkey, heirPubkey, inactivityTimeSec, heirName); |
|||
} |
|||
static CScript makeAddOpRet(uint256 tokenid, uint256 fundingtxid) { |
|||
return EncodeHeirAssetsOpRet((uint8_t)EVAL_HEIR, (uint8_t)'A', tokenid, fundingtxid); |
|||
} |
|||
static CScript makeClaimOpRet(uint256 tokenid, uint256 fundingtxid) { |
|||
return EncodeHeirAssetsOpRet((uint8_t)EVAL_HEIR, (uint8_t)'C', tokenid, fundingtxid); |
|||
} |
|||
|
|||
static void UnmarshalOpret(std::vector<uint8_t> vopret, uint8_t &e, uint8_t &funcId, uint256 &tokenid, CPubKey& ownerPubkey, CPubKey& heirPubkey, int64_t& inactivityTime, std::string& heirName, uint256& fundingtxidInOpret) { |
|||
uint8_t assetFuncId = '\0'; |
|||
bool result = E_UNMARSHAL(vopret, { ss >> e; ss >> assetFuncId; ss >> tokenid; ss >> funcId; if (IS_CHARINSTR(funcId, "F")) { ss >> ownerPubkey; ss >> heirPubkey; ss >> inactivityTime; ss >> heirName; } if (IS_CHARINSTR(funcId, "AC")) { ss >> fundingtxidInOpret; } }); |
|||
if (!result /*|| assetFuncId != 't' -- any tx is ok*/) |
|||
funcId = 0; |
|||
} |
|||
static bool isSpendingTx(uint8_t funcid) { return (funcid == 'C'); } |
|||
|
|||
static CTxOut makeUserVout(int64_t amount, CPubKey myPubkey) { |
|||
return MakeCC1vout(EVAL_TOKENS, amount, myPubkey); |
|||
} |
|||
static CTxOut makeClaimerVout(int64_t amount, CPubKey myPubkey) { |
|||
return MakeCC1vout(EVAL_TOKENS, amount, myPubkey); |
|||
} |
|||
}; |
|||
|
|||
//#define OPTIONAL_VOUT 0 // if vout is optional then in a validation plan it will be skipped without error, if all validators return false
|
|||
|
|||
|
|||
|
|||
/**
|
|||
* Small framework for vins and vouts validation implementing a variation of 'chain of responsibility' pattern: |
|||
* It consists of two classes CInputValidationPlan and COutputValidationPlan which both are configured with an array of vectors of validators |
|||
* (These validators are derived from the class CValidatorBase). |
|||
* |
|||
* A example of a validator may verify for a vout if its public key corresponds to the public key which is stored in opreturn. |
|||
* Or, vin validator may check if this vin depicts correctly to the CC contract's address. |
|||
* |
|||
* For validating vins CInputValidator additionally is provided with an instance of a class derived from the CInputIdentifierBase class. |
|||
* this identifier class allows to select identical vins (for example, normal vins or cc input vins) and apply validators from the corresponding vector to it. |
|||
* Note: CInputValidator treats that at least one identified vin should be present, otherwise it returns eval->invalid() and false. |
|||
* |
|||
* For validating vouts COutputValidator is configured for each vector of validators with the vout index to which these validators are applied |
|||
* (see constructors of both CInputValidator and COutputValidator) |
|||
* |
|||
* |
|||
* Base class for all validators |
|||
*/ |
|||
/**
|
|||
* base class for all validators |
|||
*/ |
|||
class CValidatorBase |
|||
{ |
|||
public: |
|||
CValidatorBase(CCcontract_info* cp) : m_cp(cp) {} |
|||
virtual bool isVinValidator() const = 0; |
|||
virtual bool validateVin(CTxIn vin, CTxOut prevVout, std::string& message) const = 0; |
|||
virtual bool validateVout(CTxOut vout, std::string& message) const = 0; |
|||
|
|||
protected: |
|||
CCcontract_info * m_cp; |
|||
}; |
|||
|
|||
|
|||
/**
|
|||
* Base class for classes which identify vins as normal or cc inputs |
|||
*/ |
|||
class CInputIdentifierBase |
|||
{ |
|||
public: |
|||
CInputIdentifierBase(CCcontract_info* cp) : m_cp(cp) {} |
|||
virtual std::string inputName() const = 0; |
|||
virtual bool identifyInput(CTxIn vin) const = 0; |
|||
protected: |
|||
CCcontract_info * m_cp; |
|||
}; |
|||
|
|||
|
|||
|
|||
|
|||
/**
|
|||
* Encapsulates an array containing rows of validators |
|||
* Each row is a vector of validators (zero is possible) for validating vins or prev tx's vouts |
|||
* this validation plan is used for validating tx inputs |
|||
*/ |
|||
template <typename TValidatorBase> |
|||
class CInputValidationPlan |
|||
{ |
|||
using ValidatorsRow = std::vector<TValidatorBase*>; |
|||
|
|||
public: |
|||
|
|||
// Pushes a row of validators for validating a vin or vout
|
|||
// @param CInputIdentifierBase* pointer to class-identifier which determines several identical adjacent vins (like in schema "vin.0+: normal inputs")
|
|||
// @param pargs parameter pack of zero or more pointer to validator objects
|
|||
// Why pointers? because we store the base class in validators' row and then call its virtual functions
|
|||
template <typename TValidatorBaseX, typename... ARGS> |
|||
void pushValidators(CInputIdentifierBase *identifier, ARGS*... pargs) // validators row passed as variadic arguments CValidatorX *val1, CValidatorY *val2 ...
|
|||
{ |
|||
ValidatorsRow vValidators({ (TValidatorBase*)pargs... }); |
|||
m_arrayValidators.push_back(std::make_pair(identifier, vValidators)); |
|||
} |
|||
|
|||
// validate tx inputs and corresponding prev tx vouts
|
|||
bool validate(const CTransaction& tx, Eval* eval) |
|||
{ |
|||
std::string message = "<empty>"; |
|||
//std::cerr << "CInputValidationPlan::validate() starting vins validation..." << std::endl;
|
|||
|
|||
int32_t ival = 0; |
|||
int32_t iv = 0; |
|||
int32_t numv = tx.vin.size(); |
|||
int32_t numValidators = m_arrayValidators.size(); |
|||
|
|||
// run over vins:
|
|||
while (iv < numv && ival < numValidators) { |
|||
|
|||
int32_t identifiedCount = 0; |
|||
CInputIdentifierBase *identifier = m_arrayValidators[ival].first; |
|||
// check if this is 'our' input:
|
|||
while (iv < numv && identifier->identifyInput(tx.vin[iv])) { |
|||
|
|||
// get prev tx:
|
|||
CTransaction prevTx, *pPrevTxOrNull = NULL; |
|||
uint256 hashBlock; |
|||
|
|||
if (!eval->GetTxUnconfirmed(tx.vin[iv].prevout.hash, prevTx, hashBlock)) { |
|||
std::ostringstream stream; |
|||
stream << "can't find vinTx for vin=" << iv << "."; |
|||
return eval->Invalid(stream.str().c_str()); |
|||
} |
|||
pPrevTxOrNull = &prevTx; // TODO: get prev tx only if it required (i.e. if vout validators are present)
|
|||
|
|||
// exec 'validators' from validator row of ival index, for tx.vin[iv]
|
|||
if (!execValidatorsInRow(&tx, pPrevTxOrNull, iv, ival, message)) { |
|||
std::ostringstream stream; |
|||
stream << "invalid tx vin[" << iv << "]:" << message; |
|||
return eval->Invalid(stream.str().c_str()); // ... if not, return 'invalid'
|
|||
} |
|||
|
|||
identifiedCount++; // how many vins we identified
|
|||
iv++; // advance to the next vin
|
|||
} |
|||
|
|||
// CInputValidationPlan treats that there must be at least one identified vin for configured validators' row
|
|||
// like in 'vin.0: normal input'
|
|||
if (identifiedCount == 0) { |
|||
std::ostringstream stream; |
|||
stream << "can't find required vins for " << identifier->inputName() << "."; |
|||
return eval->Invalid(stream.str().c_str()); |
|||
} |
|||
|
|||
ival++; // advance to the next validator row
|
|||
// and it will try the same vin with the new CInputIdentifierBase and validators row
|
|||
} |
|||
|
|||
// validation is successful if all validators have been used (i.e. ival = numValidators)
|
|||
if (ival < numValidators) { |
|||
std::cerr << "CInputValidationPlan::validate() incorrect tx" << " ival=" << ival << " numValidators=" << numValidators << std::endl; |
|||
return eval->Invalid("incorrect tx structure: not all required vins are present."); |
|||
} |
|||
|
|||
std::cerr << "CInputValidationPlan::validate() returns with true" << std::endl; |
|||
return true; |
|||
} |
|||
|
|||
private: |
|||
// Executes validators from the requested row of validators (selected by iValidators) for selected vin or vout (selected by iv)
|
|||
bool execValidatorsInRow(const CTransaction* pTx, const CTransaction* pPrevTx, int32_t iv, int32_t ival, std::string& refMessage) const |
|||
{ |
|||
// check boundaries:
|
|||
if (ival < 0 || ival >= m_arrayValidators.size()) { |
|||
std::cerr << "CInputValidationPlan::execValidatorsInRow() internal error: incorrect param ival=" << ival << " size=" << m_arrayValidators.size(); |
|||
refMessage = "internal error: incorrect param ival index"; |
|||
return false; |
|||
} |
|||
|
|||
if (iv < 0 || iv >= pTx->vin.size()) { |
|||
std::cerr << "CInputValidationPlan::execValidatorsInRow() internal error: incorrect param iv=" << iv << " size=" << m_arrayValidators.size(); |
|||
refMessage = "internal error: incorrect param iv index"; |
|||
return false; |
|||
} |
|||
|
|||
// get requested row of validators:
|
|||
ValidatorsRow vValidators = m_arrayValidators[ival].second; |
|||
|
|||
std::cerr << "CInputValidationPlan::execValidatorsInRow() calling validators" << " for vin iv=" << iv << " ival=" << ival << std::endl; |
|||
|
|||
for (auto v : vValidators) { |
|||
bool result; |
|||
|
|||
if (v->isVinValidator()) |
|||
// validate this vin and previous vout:
|
|||
result = v->validateVin(pTx->vin[iv], pPrevTx->vout[pTx->vin[iv].prevout.n], refMessage); |
|||
else |
|||
// if it is vout validator pass the previous tx vout:
|
|||
result = v->validateVout( pPrevTx->vout[pTx->vin[iv].prevout.n], refMessage); |
|||
if (!result) { |
|||
return result; |
|||
} |
|||
} |
|||
return true; // validation OK
|
|||
} |
|||
|
|||
|
|||
private: |
|||
//std::map<CInputIdentifierBase*, ValidatorsRow> m_arrayValidators;
|
|||
std::vector< std::pair<CInputIdentifierBase*, ValidatorsRow> > m_arrayValidators; |
|||
|
|||
}; |
|||
|
|||
|
|||
/**
|
|||
* Encapsulates an array containing rows of validators |
|||
* Each row is a vector of validators (zero is possible) for validating vouts |
|||
* this validation plan is used for validating tx outputs |
|||
*/ |
|||
template <typename TValidatorBase> |
|||
class COutputValidationPlan |
|||
{ |
|||
using ValidatorsRow = std::vector<TValidatorBase*>; |
|||
|
|||
public: |
|||
// Pushes a row of validators for validating a vout
|
|||
// @param ivout index to vout to validate
|
|||
// @param pargs parameter pack of zero or more pointer to validator objects
|
|||
// Why pointers? because we store base class and call its virtual functions
|
|||
|
|||
template <typename TValidatorBaseX, typename... ARGS> |
|||
void pushValidators(int32_t ivout, ARGS*... pargs) // validators row passed as variadic arguments CValidatorX *val1, CValidatorY *val2 ...
|
|||
{ |
|||
ValidatorsRow vValidators({ (TValidatorBase*)pargs... }); |
|||
m_arrayValidators.push_back(std::make_pair(ivout, vValidators)); |
|||
} |
|||
|
|||
// validate tx outputs
|
|||
bool validate(const CTransaction& tx, Eval* eval) |
|||
{ |
|||
std::string message = "<empty>"; |
|||
//std::cerr << "COutputValidationPlan::validateOutputs() starting vouts validation..." << std::endl;
|
|||
|
|||
int32_t ival = 0; |
|||
int32_t numVouts = tx.vout.size(); |
|||
int32_t numValidators = m_arrayValidators.size(); |
|||
|
|||
// run over vouts:
|
|||
while (ival < numValidators) { |
|||
|
|||
int32_t ivout = m_arrayValidators[ival].first; |
|||
if (ivout >= numVouts) { |
|||
std::cerr << "COutputValidationPlan::validate() incorrect tx" << "for ival=" << ival << " in tx.vout no such ivout=" << ivout << std::endl; |
|||
return eval->Invalid("incorrect tx structure: not all required vouts are present."); |
|||
} |
|||
else |
|||
{ |
|||
// exec 'validators' from validator row of ival index, for tx.vout[ivout]
|
|||
if (!execValidatorsInRow(&tx, ivout, ival, message)) { |
|||
std::ostringstream stream; |
|||
stream << "invalid tx vout[" << ivout << "]:" << message; |
|||
return eval->Invalid(stream.str().c_str()); // ... if not, return 'invalid'
|
|||
} |
|||
} |
|||
|
|||
ival++; // advance to the next vout
|
|||
|
|||
} |
|||
|
|||
std::cerr << "COutputValidationPlan::validate() returns with true" << std::endl; |
|||
return true; |
|||
} |
|||
|
|||
private: |
|||
// Executes validators from the requested row of validators (selected by iValidators) for selected vin or vout (selected by iv)
|
|||
bool execValidatorsInRow(const CTransaction* pTx, int32_t iv, int32_t ival, std::string& refMessage) const |
|||
{ |
|||
// check boundaries:
|
|||
if (ival < 0 || ival >= m_arrayValidators.size()) { |
|||
std::cerr << "COutputValidationPlan::execValidatorsInRow() internal error: incorrect param ival=" << ival << " size=" << m_arrayValidators.size(); |
|||
refMessage = "internal error: incorrect param ival index"; |
|||
return false; |
|||
} |
|||
|
|||
if (iv < 0 || iv >= pTx->vout.size()) { |
|||
std::cerr << "COutputValidationPlan::execValidatorsInRow() internal error: incorrect param iv=" << iv << " size=" << m_arrayValidators.size(); |
|||
refMessage = "internal error: incorrect param iv index"; |
|||
return false; |
|||
} |
|||
|
|||
// get requested row of validators:
|
|||
ValidatorsRow vValidators = m_arrayValidators[ival].second; |
|||
|
|||
std::cerr << "COutputValidationPlan::execRow() calling validators" << " for vout iv=" << iv << " ival=" << ival << std::endl; |
|||
|
|||
for (auto v : vValidators) { |
|||
|
|||
if (!v->isVinValidator()) { |
|||
// if this is a 'in' validation plan then pass the previous tx vout:
|
|||
bool result = v->validateVout(pTx->vout[iv], refMessage); |
|||
if (!result) |
|||
return result; |
|||
} |
|||
} |
|||
return true; // validation OK
|
|||
} |
|||
|
|||
|
|||
private: |
|||
//std::map<int32_t, ValidatorsRow> m_mapValidators;
|
|||
std::vector< std::pair<int32_t, ValidatorsRow> > m_arrayValidators; |
|||
|
|||
}; |
|||
|
|||
|
|||
class CNormalInputIdentifier : CInputIdentifierBase { |
|||
public: |
|||
CNormalInputIdentifier(CCcontract_info* cp) : CInputIdentifierBase(cp) {} |
|||
virtual std::string inputName() const { return std::string("normal input"); } |
|||
virtual bool identifyInput(CTxIn vin) const { |
|||
return !IsCCInput(vin.scriptSig); |
|||
} |
|||
}; |
|||
|
|||
class CCCInputIdentifier : CInputIdentifierBase { |
|||
public: |
|||
CCCInputIdentifier(CCcontract_info* cp) : CInputIdentifierBase(cp) {} |
|||
virtual std::string inputName() const { return std::string("CC input"); } |
|||
virtual bool identifyInput(CTxIn vin) const { |
|||
return IsCCInput(vin.scriptSig); |
|||
} |
|||
}; |
|||
|
|||
|
|||
/**
|
|||
* Validates 1of2address for vout (may be used for either this or prev tx) |
|||
*/ |
|||
template <class Helper> class CCC1of2AddressValidator : CValidatorBase |
|||
{ |
|||
public: |
|||
CCC1of2AddressValidator(CCcontract_info* cp, CScript opRetScript, std::string customMessage = "") : |
|||
m_fundingOpretScript(opRetScript), m_customMessage(customMessage), CValidatorBase(cp) {} |
|||
|
|||
virtual bool isVinValidator() const { return false; } |
|||
virtual bool validateVout(CTxOut vout, std::string& message) const |
|||
{ |
|||
//std::cerr << "CCC1of2AddressValidator::validateVout() entered" << std::endl;
|
|||
uint8_t funcId; |
|||
CPubKey ownerPubkey, heirPubkey; |
|||
int64_t inactivityTime; |
|||
std::string heirName; |
|||
uint256 tokenid; |
|||
|
|||
if ((funcId = DecodeHeirOpRet<Helper>(m_fundingOpretScript, tokenid, ownerPubkey, heirPubkey, inactivityTime, heirName)) == 0) { |
|||
message = m_customMessage + std::string(" invalid opreturn format"); |
|||
std::cerr << "CCC1of2AddressValidator::validateVout() exits with false: " << message << std::endl; |
|||
return false; |
|||
} |
|||
|
|||
char shouldBeAddr[65], ccAddr[65]; |
|||
|
|||
GetCCaddress1of2(m_cp, shouldBeAddr, ownerPubkey, heirPubkey); |
|||
if (vout.scriptPubKey.IsPayToCryptoCondition()) { |
|||
if (Getscriptaddress(ccAddr, vout.scriptPubKey) && strcmp(shouldBeAddr, ccAddr) == 0) { |
|||
std::cerr << "CCC1of2AddressValidator::validateVout() exits with true" << std::endl; |
|||
return true; |
|||
} |
|||
else { |
|||
message = m_customMessage + std::string(" incorrect heir funding address: incorrect pubkey(s)"); |
|||
} |
|||
} |
|||
else { |
|||
message = m_customMessage + std::string(" incorrect heir funding address: not a 1of2addr"); |
|||
} |
|||
|
|||
std::cerr << "CCC1of2AddressValidator::validateVout() exits with false: " << message << std::endl; |
|||
return false; |
|||
} |
|||
virtual bool validateVin(CTxIn vin, CTxOut prevVout, std::string& message) const { return false; } |
|||
|
|||
private: |
|||
CScript m_fundingOpretScript; |
|||
std::string m_customMessage; |
|||
}; |
|||
|
|||
|
|||
/**
|
|||
* Validates if this is vout to owner or heir from opret (funding or change) |
|||
*/ |
|||
template <class Helper> class CMyPubkeyVoutValidator : CValidatorBase |
|||
{ |
|||
public: |
|||
CMyPubkeyVoutValidator(CCcontract_info* cp, CScript opRetScript, bool checkNormals) |
|||
: m_fundingOpretScript(opRetScript), m_checkNormals(checkNormals), CValidatorBase(cp) { } |
|||
|
|||
virtual bool isVinValidator() const { return false; } |
|||
virtual bool validateVout(CTxOut vout, std::string& message) const |
|||
{ |
|||
//std::cerr << "CMyPubkeyVoutValidator::validateVout() entered" << std::endl;
|
|||
|
|||
uint8_t funcId; |
|||
CPubKey ownerPubkey, heirPubkey; |
|||
int64_t inactivityTime; |
|||
std::string heirName; |
|||
uint256 tokenid; |
|||
|
|||
///std::cerr << "CMyPubkeyVoutValidator::validateVout() m_opRetScript=" << m_opRetScript.ToString() << std::endl;
|
|||
// get both pubkeys:
|
|||
if ((funcId = DecodeHeirOpRet<Helper>(m_fundingOpretScript, tokenid, ownerPubkey, heirPubkey, inactivityTime, heirName)) == 0) { |
|||
message = std::string("invalid opreturn format"); |
|||
return false; |
|||
} |
|||
|
|||
CScript ownerScript; |
|||
CScript heirScript; |
|||
if (m_checkNormals) { |
|||
ownerScript = CoinHelper::makeUserVout(vout.nValue, ownerPubkey).scriptPubKey; |
|||
heirScript = CoinHelper::makeUserVout(vout.nValue, heirPubkey).scriptPubKey; |
|||
} |
|||
else { |
|||
ownerScript = Helper::makeUserVout(vout.nValue, ownerPubkey).scriptPubKey; |
|||
heirScript = Helper::makeUserVout(vout.nValue, heirPubkey).scriptPubKey; |
|||
} |
|||
|
|||
//std::cerr << "CMyPubkeyVoutValidator::validateVout() vout.scriptPubKey=" << vout.scriptPubKey.ToString() << " makeUserVout=" << Helper::makeUserVout(vout.nValue, ownerPubkey).scriptPubKey.ToString() << std::endl;
|
|||
|
|||
// recreate scriptPubKey for owner and heir and compare it with that of the vout to check:
|
|||
if (vout.scriptPubKey == ownerScript || vout.scriptPubKey == heirScript) { |
|||
// this is vout to owner or heir addr:
|
|||
std::cerr << "CMyPubkeyVoutValidator::validateVout() exits with true" << std::endl; |
|||
return true; |
|||
|
|||
} |
|||
|
|||
std::cerr << "CMyPubkeyVoutValidator::validateVout() exits with false (not the owner's or heir's addresses)" << std::endl; |
|||
return false; |
|||
} |
|||
virtual bool validateVin(CTxIn vin, CTxOut prevVout, std::string& message) const { return true; } |
|||
|
|||
private: |
|||
CScript m_fundingOpretScript; |
|||
//uint256 m_lasttxid;
|
|||
bool m_checkNormals; |
|||
}; |
|||
|
|||
/**
|
|||
* Check if the user is the heir and the heir is allowed to spend (duration > inactivityTime) |
|||
*/ |
|||
template <class Helper> class CHeirSpendValidator : CValidatorBase |
|||
{ |
|||
public: |
|||
CHeirSpendValidator(CCcontract_info* cp, CScript opRetScript, uint256 latesttxid, bool isHeirSpendingBegan) |
|||
: m_fundingOpretScript(opRetScript), m_latesttxid(latesttxid), m_isHeirSpendingBegan(isHeirSpendingBegan), CValidatorBase(cp) {} |
|||
|
|||
virtual bool isVinValidator() const { return false; } |
|||
virtual bool validateVout(CTxOut vout, std::string& message) const |
|||
{ |
|||
//std::cerr << "CHeirSpendValidator::validateVout() entered" << std::endl;
|
|||
|
|||
uint8_t funcId; |
|||
CPubKey ownerPubkey, heirPubkey; |
|||
int64_t inactivityTime; |
|||
std::string heirName; |
|||
uint256 tokenid; |
|||
|
|||
|
|||
// get heir pubkey:
|
|||
if ((funcId = DecodeHeirOpRet<Helper>(m_fundingOpretScript, tokenid, ownerPubkey, heirPubkey, inactivityTime, heirName, false)) == 0) { |
|||
message = std::string("invalid opreturn format"); |
|||
return false; |
|||
} |
|||
|
|||
int32_t numblocks; |
|||
int64_t durationSec = CCduration(numblocks, m_latesttxid); |
|||
|
|||
// recreate scriptPubKey for heir and compare it with that of the vout:
|
|||
if (vout.scriptPubKey == Helper::makeClaimerVout(vout.nValue, heirPubkey).scriptPubKey) { |
|||
// this is the heir is trying to spend
|
|||
if (!m_isHeirSpendingBegan && durationSec <= inactivityTime) { |
|||
message = "heir is not allowed yet to spend funds"; |
|||
std::cerr << "CHeirSpendValidator::validateVout() heir is not allowed yet to spend funds" << std::endl; |
|||
return false; |
|||
} |
|||
else { |
|||
// heir is allowed to spend
|
|||
return true; |
|||
} |
|||
} |
|||
|
|||
std::cerr << "CHeirSpendValidator::validateVout() exits with true" << std::endl; |
|||
|
|||
// this is not heir:
|
|||
return true; |
|||
} |
|||
virtual bool validateVin(CTxIn vin, CTxOut prevVout, std::string& message) const { return true; } |
|||
|
|||
private: |
|||
CScript m_fundingOpretScript; |
|||
uint256 m_latesttxid; |
|||
bool m_isHeirSpendingBegan; |
|||
}; |
|||
|
|||
/**
|
|||
* Validates this opreturn and compares it with the opreturn from the previous tx |
|||
*/ |
|||
template <class Helper> class COpRetValidator : CValidatorBase |
|||
{ |
|||
public: |
|||
COpRetValidator(CCcontract_info* cp, CScript opret) |
|||
: m_fundingOpretScript(opret), CValidatorBase(cp) {} |
|||
|
|||
virtual bool isVinValidator() const { return false; } |
|||
virtual bool validateVout(CTxOut vout, std::string& message) const |
|||
{ |
|||
//std::cerr << "COpRetValidator::validateVout() entered" << std::endl;
|
|||
|
|||
uint8_t funcId, initialFuncId; // do not check heir name
|
|||
uint256 fundingTxidInOpret = zeroid, dummyTxid, tokenid, initialTokenid; |
|||
|
|||
if ((funcId = DecodeHeirOpRet<Helper>(vout.scriptPubKey, tokenid, fundingTxidInOpret)) == 0) { |
|||
message = std::string("invalid opreturn format"); |
|||
return false; |
|||
} |
|||
if ((initialFuncId = DecodeHeirOpRet<Helper>(m_fundingOpretScript, initialTokenid, dummyTxid)) == 0) { |
|||
message = std::string("invalid initial tx opreturn format"); |
|||
return false; |
|||
} |
|||
|
|||
// validation rules:
|
|||
if (!Helper::isMyFuncId(funcId)) { |
|||
message = std::string("invalid funcid in opret"); |
|||
return false; |
|||
} |
|||
|
|||
if(tokenid != initialTokenid ) { |
|||
message = std::string("invalid tokenid in opret"); |
|||
return false; |
|||
} |
|||
|
|||
std::cerr << "COpRetValidator::validateVout() exits with true" << std::endl; |
|||
return true; |
|||
} |
|||
virtual bool validateVin(CTxIn vin, CTxOut prevVout, std::string& message) const { return true; } |
|||
|
|||
private: |
|||
CScript m_fundingOpretScript; |
|||
}; |
|||
|
|||
|
|||
|
|||
#endif |
Loading…
Reference in new issue