Browse Source

Pull in upstream npsv updates from jl777/komodo@1f874d46c5ddd24c0fd32ae6800f16d8006b9059

sietch
Duke Leto 5 years ago
parent
commit
10be224558
  1. 3
      src/cc/CCinclude.h
  2. 8
      src/komodo_bitcoind.h
  3. 14
      src/komodo_nSPV.h
  4. 10
      src/komodo_nSPV_defs.h
  5. 367
      src/komodo_nSPV_fullnode.h
  6. 53
      src/komodo_nSPV_superlite.h
  7. 9
      src/komodo_nSPV_wallet.h

3
src/cc/CCinclude.h

@ -325,5 +325,8 @@ void CCLogPrintStream(const char *category, int level, T print_to_stream)
// use: LOGSTREAM("yourcategory", your-debug-level, stream << "some log data" << data2 << data3 << ... << std::endl);
#define LOGSTREAM(category, level, logoperator) CCLogPrintStream( category, level, [=](std::ostringstream &stream) {logoperator;} )
int32_t CC_vinselect(int32_t *aboveip, int64_t *abovep, int32_t *belowip, int64_t *belowp, struct CC_utxo utxos[], int32_t numunspents, int64_t value);
#endif

8
src/komodo_bitcoind.h

@ -2539,3 +2539,11 @@ int32_t komodo_staked(CMutableTransaction &txNew,uint32_t nBits,uint32_t *blockt
}
return(siglen);
}
bool komodo_hardfork_active(uint32_t time)
{
//TODO: set hardfork height for HUSH
return false;
//return ( (ASSETCHAINS_SYMBOL[0] == 0 && chainActive.Height() > nDecemberHardforkHeight) || (ASSETCHAINS_SYMBOL[0] != 0 && time > nStake
}

14
src/komodo_nSPV.h

@ -431,6 +431,20 @@ void NSPV_broadcast_purge(struct NSPV_broadcastresp *ptr)
memset(ptr,0,sizeof(*ptr));
}
int32_t NSPV_rwremoterpcresp(int32_t rwflag,uint8_t *serialized,struct NSPV_remoterpcresp *ptr, int32_t slen)
{
int32_t len = 0;
len+=iguana_rwbuf(rwflag,&serialized[len],sizeof(ptr->method),(uint8_t*)ptr->method);
len+=iguana_rwbuf(rwflag,&serialized[len],slen-len,(uint8_t*)ptr->json);
return(len);
}
void NSPV_remoterpc_purge(struct NSPV_remoterpcresp *ptr)
{
if ( ptr != 0 )
memset(ptr,0,sizeof(*ptr));
}
// useful utility functions
uint256 NSPV_doublesha256(uint8_t *data,int32_t datalen)

10
src/komodo_nSPV_defs.h

@ -44,12 +44,16 @@
#define NSPV_TXIDSRESP 0x0f
#define NSPV_MEMPOOL 0x10
#define NSPV_MEMPOOLRESP 0x11
#define NSPV_CCMODULEUTXOS 0x12
#define NSPV_CCMODULEUTXOSRESP 0x13
#define NSPV_MEMPOOL_ALL 0
#define NSPV_MEMPOOL_ADDRESS 1
#define NSPV_MEMPOOL_ISSPENT 2
#define NSPV_MEMPOOL_INMEMPOOL 3
#define NSPV_MEMPOOL_CCEVALCODE 4
#define NSPV_CC_TXIDS 16
#define NSPV_REMOTERPC 0x14
#define NSPV_REMOTERPCRESP 0x15
int32_t NSPV_gettransaction(int32_t skipvalidation,int32_t vout,uint256 txid,int32_t height,CTransaction &tx,uint256 &hashblock,int32_t &txheight,int32_t &currentheight,int64_t extradata,uint32_t tiptime,int64_t &rewardsum);
UniValue NSPV_spend(char *srcaddr,char *destaddr,int64_t satoshis);
@ -179,4 +183,10 @@ struct NSPV_CCmtxinfo
struct NSPV_utxoresp used[NSPV_MAXVINS];
};
struct NSPV_remoterpcresp
{
char method[64];
char json[11000];
};
#endif // KOMODO_NSPV_DEFSH

367
src/komodo_nSPV_fullnode.h

@ -20,6 +20,13 @@
// NSPV_get... functions need to return the exact serialized length, which is the size of the structure minus size of pointers, plus size of allocated data
#include "notarisationdb.h"
#include "rpc/server.h"
static std::map<std::string,bool> nspv_remote_commands = {{"channelsopen", true},{"channelspayment", true},{"channelsclose", true},{"channelsrefund", true},
{"channelslist", true},{"channelsinfo", true},{"oraclescreate", true},{"oraclesfund", true},{"oraclesregister", true},{"oraclessubscribe", true},
{"oraclesdata", true},{"oraclesinfo", false},{"oracleslist", false},{"gatewaysbind", true},{"gatewaysdeposit", true},{"gatewaysclaim", true},{"gatewayswithdraw", true},
{"gatewayspartialsign", true},{"gatewayscompletesigning", true},{"gatewaysmarkdone", true},{"gatewayspendingdeposits", true},{"gatewayspendingwithdraws", true},
{"gatewaysprocessed", true},{"gatewaysinfo", false},{"gatewayslist", false},{"faucetfund", true},{"faucetget", true}};
struct NSPV_ntzargs
{
@ -206,6 +213,229 @@ int32_t NSPV_getaddressutxos(struct NSPV_utxosresp *ptr,char *coinaddr,bool isCC
return(0);
}
class BaseCCChecker {
public:
/// base check function
/// @param vouts vouts where checked vout and opret are
/// @param nvout vout index to check
/// @param evalcode which must be in the opret
/// @param funcids allowed funcids in string
/// @param filtertxid txid that should be in the opret (after funcid)
virtual bool checkCC(uint256 txid, const std::vector<CTxOut> &vouts, int32_t nvout, uint8_t evalcode, std::string funcids, uint256 filtertxid) = 0;
};
/// default cc vout checker for use in NSPV_getccmoduleutxos
/// checks if a vout is cc, has required evalcode, allowed funcids and txid
/// check both cc opret and last vout opret
/// maybe customized in via inheritance
class DefaultCCChecker : public BaseCCChecker {
private:
public:
DefaultCCChecker() { }
virtual bool checkCC(uint256 txid, const std::vector<CTxOut> &vouts, int32_t nvout, uint8_t evalcode, std::string funcids, uint256 filtertxid)
{
CScript opret, dummy;
std::vector< vscript_t > vParams;
vscript_t vopret;
if (nvout < vouts.size())
{
// first check if it is cc vout
if (vouts[nvout].scriptPubKey.IsPayToCryptoCondition(&dummy, vParams))
{
// try to find cc opret
if (vParams.size() > 0)
{
COptCCParams p(vParams[0]); // parse vout data
if (p.vData.size() > 0)
{
vopret = p.vData[0]; // get opret data
}
}
// if no cc opret check last vout opret
if (vopret.size() == 0)
{
GetOpReturnData(vouts.back().scriptPubKey, vopret);
}
if (vopret.size() > 2)
{
uint8_t opretEvalcode, opretFuncid;
uint256 opretTxid;
bool isEof = true;
bool isCreateTx = false;
// parse opret first 3 fields:
bool parseOk = E_UNMARSHAL(vopret,
ss >> opretEvalcode;
ss >> opretFuncid;
if (funcids.size() > 0 && opretFuncid == funcids[0]) // this means that we check txid only for second+ funcid in array (considering that the first funcid is the creation txid itself like tokens)
{
isCreateTx = true;
}
else
{
ss >> opretTxid;
isCreateTx = false;
}
isEof = ss.eof(); );
opretTxid = revuint256(opretTxid);
std::cerr << __func__ << " " << "opretEvalcode=" << opretEvalcode << " opretFuncid=" << (char)opretFuncid << " isCreateTx=" << isCreateTx << " opretTxid=" << opretTxid.GetHex() << std::endl;
if( parseOk /*parseOk=true if eof reached*/|| !isEof /*more data means okay*/)
{
if (evalcode == opretEvalcode && std::find(funcids.begin(), funcids.end(), (char)opretFuncid) != funcids.end() &&
(isCreateTx && filtertxid == txid || !isCreateTx && filtertxid == opretTxid))
{
return true;
}
}
}
}
}
return false;
}
};
static class DefaultCCChecker defaultCCChecker;
// table of pluggable cc vout checkers for usage in NSPV_getccmoduleutxos
// if the checker is not in the table for a evalcode then defaultCCChecker is used
static std::map<uint8_t, class BaseCCChecker*> ccCheckerTable =
{
};
// implements SPV server's part, gets cc module utxos, filtered by evalcode, funcid and txid on opret, for the specified amount
// if the amount param is 0 returns total available filtere utxo amount and returns no utxos
// first char funcid in the string param is considered as the creation tx funcid so filtertxid is compared to the creation txid itself
// for other funcids filtertxid is compared to the txid in opreturn
int32_t NSPV_getccmoduleutxos(struct NSPV_utxosresp *ptr, char *coinaddr, int64_t amount, uint8_t evalcode, std::string funcids, uint256 filtertxid)
{
int64_t total = 0, totaladded = 0;
uint32_t locktime;
int32_t tipheight=0, len, maxlen;
int32_t maxinputs = CC_MAXVINS;
std::vector<struct CC_utxo> utxoSelected;
utxoSelected.reserve(CC_MAXVINS);
std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue> > unspentOutputs;
SetCCunspents(unspentOutputs, coinaddr, true);
maxlen = MAX_BLOCK_SIZE(tipheight) - 512;
//maxlen /= sizeof(*ptr->utxos); // TODO why was this? we need maxlen in bytes, don't we?
//ptr->numutxos = (uint16_t)unspentOutputs.size();
//if (ptr->numutxos >= 0 && ptr->numutxos < maxlen)
//{
ptr->utxos = NULL;
ptr->numutxos = 0;
strncpy(ptr->coinaddr, coinaddr, sizeof(ptr->coinaddr) - 1);
ptr->CCflag = 1;
tipheight = chainActive.LastTip()->GetHeight();
ptr->nodeheight = tipheight; // will be checked in libnspv
//}
// select all appropriate utxos:
std::cerr << __func__ << " " << "searching addr=" << coinaddr << std::endl;
for (std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue> >::const_iterator it = unspentOutputs.begin(); it != unspentOutputs.end(); it++)
{
if (myIsutxo_spentinmempool(ignoretxid, ignorevin, it->first.txhash, (int32_t)it->first.index) == 0)
{
//const CCoins *pcoins = pcoinsTip->AccessCoins(it->first.txhash); <-- no opret in coins
CTransaction tx;
uint256 hashBlock;
int32_t nvout = it->first.index;
if (myGetTransaction(it->first.txhash, tx, hashBlock))
{
class BaseCCChecker *baseChecker = ccCheckerTable[evalcode];
// if a checker is set for evalcode use it otherwise use the default checker:
if (baseChecker && baseChecker->checkCC(it->first.txhash, tx.vout, nvout, evalcode, funcids, filtertxid) || defaultCCChecker.checkCC(it->first.txhash, tx.vout, nvout, evalcode, funcids, filtertxid))
{
std::cerr << __func__ << " " << "filtered utxo with amount=" << tx.vout[nvout].nValue << std::endl;
struct CC_utxo utxo;
utxo.txid = it->first.txhash;
utxo.vout = (int32_t)it->first.index;
utxo.nValue = it->second.satoshis;
//utxo.height = it->second.blockHeight;
utxoSelected.push_back(utxo);
total += it->second.satoshis;
}
}
else
std::cerr << __func__ << " " << "ERROR: cant load tx for txid, please reindex" << std::endl;
}
}
if (amount == 0) {
// just return total value
ptr->total = total;
len = (int32_t)(sizeof(*ptr) - sizeof(ptr->utxos)/*subtract not serialized part of NSPV_utxoresp*/);
return len;
}
// pick optimal utxos for the requested amount
CAmount remains = amount;
std::vector<struct CC_utxo> utxoAdded;
while (utxoSelected.size() > 0)
{
int64_t below = 0, above = 0;
int32_t abovei = -1, belowi = -1, ind = -1;
if (CC_vinselect(&abovei, &above, &belowi, &below, utxoSelected.data(), utxoSelected.size(), remains) < 0)
{
std::cerr << "error CC_vinselect" << " remains=" << remains << " amount=" << amount << " abovei=" << abovei << " belowi=" << belowi << " ind=" << " utxoSelected.size()=" << utxoSelected.size() << ind << std::endl;
return 0;
}
if (abovei >= 0) // best is 'above'
ind = abovei;
else if (belowi >= 0) // second try is 'below'
ind = belowi;
else
{
std::cerr << "error finding unspent" << " remains=" << remains << " amount=" << amount << " abovei=" << abovei << " belowi=" << belowi << " ind=" << " utxoSelected.size()=" << utxoSelected.size() << ind << std::endl;
return 0;
}
utxoAdded.push_back(utxoSelected[ind]);
total += utxoSelected[ind].nValue;
remains -= utxoSelected[ind].nValue;
// remove used utxo[ind]:
utxoSelected[ind] = utxoSelected.back();
utxoSelected.pop_back();
if (total >= amount) // found the requested amount
break;
if (utxoAdded.size() >= maxinputs) // reached maxinputs
break;
}
ptr->numutxos = (uint16_t)utxoAdded.size();
ptr->total = total;
ptr->utxos = (NSPV_utxoresp*)calloc(ptr->numutxos, sizeof(ptr->utxos[0]));
for (uint16_t i = 0; i < ptr->numutxos; i++)
{
ptr->utxos[i].satoshis = utxoAdded[i].nValue;
ptr->utxos[i].txid = utxoAdded[i].txid;
ptr->utxos[i].vout = utxoAdded[i].vout;
}
len = (int32_t)(sizeof(*ptr) - sizeof(ptr->utxos)/*subtract not serialized part of NSPV_utxoresp*/ + sizeof(*ptr->utxos)*ptr->numutxos);
if (len < maxlen)
return len; // good length
else
{
NSPV_utxosresp_purge(ptr);
return 0;
}
}
int32_t NSPV_getaddresstxids(struct NSPV_txidsresp *ptr,char *coinaddr,bool isCC,int32_t skipcount,uint32_t filter)
{
int32_t maxlen,txheight,ind=0,n = 0,len = 0; CTransaction tx; uint256 hashBlock;
@ -425,6 +655,63 @@ int32_t NSPV_mempooltxids(struct NSPV_mempoolresp *ptr,char *coinaddr,uint8_t is
return(0);
}
int32_t NSPV_remoterpc(struct NSPV_remoterpcresp *ptr,char *json,int n)
{
std::vector<uint256> txids; int32_t i,len = 0; UniValue result; std::string response;
UniValue request(UniValue::VOBJ),rpc_result(UniValue::VOBJ); JSONRequest jreq; CPubKey mypk;
try
{
request.read(json,n);
jreq.parse(request);
strcpy(ptr->method,jreq.strMethod.c_str());
len+=sizeof(ptr->method);
std::map<std::string, bool>::iterator it = nspv_remote_commands.find(jreq.strMethod);
if (it==nspv_remote_commands.end())
throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Method not supported!");
const CRPCCommand *cmd=tableRPC[jreq.strMethod];
if (!cmd)
throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Method not found");
if (it->second)
{
if (!request.exists("mypk"))
throw JSONRPCError(RPC_PARSE_ERROR, "No pubkey supplied in remote rpc request, necessary for this type of rpc");
std::string str=request["mypk"].get_str();
mypk=pubkey2pk(ParseHex(str));
if (!mypk.IsValid())
throw JSONRPCError(RPC_PARSE_ERROR, "Not valid pubkey passed in remote rpc call");
}
//TODO: if ((result = cmd->actor(jreq.params,false,mypk)).isObject() || result.isArray())
if ((result = cmd->actor(jreq.params,false)).isObject() || result.isArray())
{
rpc_result = JSONRPCReplyObj(result, NullUniValue, jreq.id);
response=rpc_result.write();
memcpy(ptr->json,response.c_str(),response.size());
len+=response.size();
return (len);
}
else throw JSONRPCError(RPC_MISC_ERROR, "Error in executing RPC on remote node");
}
catch (const UniValue& objError)
{
rpc_result = JSONRPCReplyObj(NullUniValue, objError, jreq.id);
response=rpc_result.write();
}
catch (const runtime_error& e)
{
rpc_result = JSONRPCReplyObj(NullUniValue,JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id);
response=rpc_result.write();
}
catch (const std::exception& e)
{
rpc_result = JSONRPCReplyObj(NullUniValue,JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id);
response=rpc_result.write();
}
memcpy(ptr->json,response.c_str(),response.size());
len+=response.size();
return (len);
}
uint8_t *NSPV_getrawtx(CTransaction &tx,uint256 &hashBlock,int32_t *txlenp,uint256 txid)
{
uint8_t *rawtx = 0;
@ -860,7 +1147,85 @@ void komodo_nSPVreq(CNode *pfrom,std::vector<uint8_t> request) // received a req
}
}
}
}
else if ( request[0] == NSPV_REMOTERPC )
{
if ( timestamp > pfrom->prevtimes[ind] )
{
struct NSPV_remoterpcresp R; int32_t p;
p = 1;
p+=iguana_rwnum(0,&request[p],sizeof(slen),&slen);
memset(&R,0,sizeof(R));
if (request.size() == p+slen && (slen=NSPV_remoterpc(&R,(char *)&request[p],slen))>0 )
{
response.resize(1 + slen);
response[0] = NSPV_REMOTERPCRESP;
NSPV_rwremoterpcresp(1,&response[1],&R,slen);
pfrom->PushMessage("nSPV",response);
pfrom->prevtimes[ind] = timestamp;
NSPV_remoterpc_purge(&R);
}
}
}
else if (request[0] == NSPV_CCMODULEUTXOS) // get cc module utxos from coinaddr for the requested amount, evalcode, funcid list and txid
{
//fprintf(stderr,"utxos: %u > %u, ind.%d, len.%d\n",timestamp,pfrom->prevtimes[ind],ind,len);
if (timestamp > pfrom->prevtimes[ind])
{
struct NSPV_utxosresp U;
char coinaddr[64];
int64_t amount;
uint8_t evalcode;
char funcids[27];
uint256 filtertxid;
bool errorFormat = false;
const int32_t BITCOINADDRESSMINLEN = 20;
int32_t minreqlen = sizeof(uint8_t) + sizeof(uint8_t) + BITCOINADDRESSMINLEN + sizeof(amount) + sizeof(evalcode) + sizeof(uint8_t) + sizeof(filtertxid);
int32_t maxreqlen = sizeof(uint8_t) + sizeof(uint8_t) + sizeof(coinaddr)-1 + sizeof(amount) + sizeof(evalcode) + sizeof(uint8_t) + sizeof(funcids)-1 + sizeof(filtertxid);
if (len >= minreqlen && len <= maxreqlen)
{
n = 1;
int32_t addrlen = request[n++];
if (addrlen < sizeof(coinaddr))
{
memcpy(coinaddr, &request[n], addrlen);
coinaddr[addrlen] = 0;
n += addrlen;
iguana_rwnum(0, &request[n], sizeof(amount), &amount);
n += sizeof(amount);
iguana_rwnum(0, &request[n], sizeof(evalcode), &evalcode);
n += sizeof(evalcode);
int32_t funcidslen = request[n++];
if (funcidslen < sizeof(funcids))
{
memcpy(funcids, &request[n], funcidslen);
funcids[funcidslen] = 0;
n += funcidslen;
iguana_rwbignum(0, &request[n], sizeof(filtertxid), (uint8_t *)&filtertxid);
std::cerr << __func__ << " " << "request addr=" << coinaddr << " amount=" << amount << " evalcode=" << (int)evalcode << " funcids=" << funcids << " filtertxid=" << filtertxid.GetHex() << std::endl;
memset(&U, 0, sizeof(U));
if ((slen = NSPV_getccmoduleutxos(&U, coinaddr, amount, evalcode, funcids, filtertxid)) > 0)
{
std::cerr << __func__ << " " << "created utxos, slen=" << slen << std::endl;
response.resize(1 + slen);
response[0] = NSPV_CCMODULEUTXOSRESP;
if (NSPV_rwutxosresp(1, &response[1], &U) == slen)
{
pfrom->PushMessage("nSPV", response);
pfrom->prevtimes[ind] = timestamp;
std::cerr << __func__ << " " << "returned nSPV response" << std::endl;
}
NSPV_utxosresp_purge(&U);
}
}
}
}
}
}
}
}
#endif // KOMODO_NSPVFULLNODE_H

53
src/komodo_nSPV_superlite.h

@ -203,6 +203,12 @@ void komodo_nSPVresp(CNode *pfrom,std::vector<uint8_t> response) // received a r
NSPV_rwbroadcastresp(0,&response[1],&NSPV_broadcastresult);
fprintf(stderr,"got broadcast response %u size.%d %s retcode.%d\n",timestamp,(int32_t)response.size(),NSPV_broadcastresult.txid.GetHex().c_str(),NSPV_broadcastresult.retcode);
break;
case NSPV_CCMODULEUTXOSRESP:
NSPV_utxosresp_purge(&NSPV_utxosresult);
NSPV_rwutxosresp(0, &response[1], &NSPV_utxosresult);
fprintf(stderr, "got cc module utxos response %u size.%d\n", timestamp, (int32_t)response.size());
break;
default: fprintf(stderr,"unexpected response %02x size.%d at %u\n",response[0],(int32_t)response.size(),timestamp);
break;
}
@ -925,4 +931,51 @@ UniValue NSPV_broadcast(char *hex)
return(NSPV_broadcast_json(&B,txid));
}
// gets cc utxos filtered by evalcode, funcid and txid in opret, for the specified amount
// if amount == 0 returns total and no utxos
// funcids is string of funcid symbols like "ct". The first symbol is considered as creation tx funcid and filtertxid will be compared to the creation tx id itself.
// For second+ funcids the filtertxid will be compared to txid in opret
UniValue NSPV_ccmoduleutxos(char *coinaddr, int64_t amount, uint8_t evalcode, std::string funcids, uint256 filtertxid)
{
UniValue result(UniValue::VOBJ); uint8_t msg[512]; int32_t i, iter, slen, len = 0;
uint8_t CCflag = 1;
NSPV_utxosresp_purge(&NSPV_utxosresult);
if (bitcoin_base58decode(msg, coinaddr) != 25)
{
result.push_back(Pair("result", "error"));
result.push_back(Pair("error", "invalid address"));
return(result);
}
msg[len++] = NSPV_CCMODULEUTXOS;
slen = (int32_t)strlen(coinaddr);
msg[len++] = slen;
memcpy(&msg[len], coinaddr, slen), len += slen;
len += iguana_rwnum(1, &msg[len], sizeof(amount), &amount);
len += iguana_rwnum(1, &msg[len], sizeof(evalcode), &evalcode);
slen = (int32_t)(funcids.size());
msg[len++] = slen;
memcpy(&msg[len], funcids.data(), slen), len += slen;
len += iguana_rwbignum(1, &msg[len], sizeof(filtertxid), (uint8_t *)&filtertxid);
for (iter = 0; iter<3; iter++)
if (NSPV_req(0, msg, len, NODE_ADDRINDEX, msg[0] >> 1) != 0)
{
for (i = 0; i<NSPV_POLLITERS; i++)
{
usleep(NSPV_POLLMICROS);
if ((NSPV_inforesult.height == 0 || NSPV_utxosresult.nodeheight >= NSPV_inforesult.height) && strcmp(coinaddr, NSPV_utxosresult.coinaddr) == 0 && CCflag == NSPV_utxosresult.CCflag)
return(NSPV_utxosresp_json(&NSPV_utxosresult));
}
}
else sleep(1);
result.push_back(Pair("result", "error"));
result.push_back(Pair("error", "no utxos result"));
result.push_back(Pair("lastpeer", NSPV_lastpeer));
return(result);
}
#endif // KOMODO_NSPVSUPERLITE_H

9
src/komodo_nSPV_wallet.h

@ -388,8 +388,13 @@ UniValue NSPV_spend(char *srcaddr,char *destaddr,int64_t satoshis) // what its a
mtx.nExpiryHeight = 0;
mtx.nVersionGroupId = SAPLING_VERSION_GROUP_ID;
mtx.nVersion = SAPLING_TX_VERSION;
if ( ASSETCHAINS_SYMBOL[0] == 0 )
mtx.nLockTime = (uint32_t)time(NULL) - 777;
if ( ASSETCHAINS_SYMBOL[0] == 0 ) {
if ( !komodo_hardfork_active((uint32_t)chainActive.LastTip()->nTime) )
mtx.nLockTime = (uint32_t)time(NULL) - 777;
else
mtx.nLockTime = (uint32_t)chainActive.Tip()->GetMedianTimePast();
}
memset(used,0,sizeof(used));
if ( NSPV_addinputs(used,mtx,satoshis+txfee,64,NSPV_utxosresult.utxos,NSPV_utxosresult.numutxos) > 0 )

Loading…
Cancel
Save