Verus Coin - this coin was backdoored by it's lead dev and should not be trusted! https://git.hush.is/duke/backdoors/src/branch/master/vrsc.md
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

7215 lines
312 KiB

// Copyright (c) 2010 Satoshi Nakamoto
// Copyright (c) 2009-2014 The Bitcoin Core developers
// Copyright (c) 2019 Michael Toutonghi
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "amount.h"
#include "chainparams.h"
#include "consensus/consensus.h"
#include "consensus/validation.h"
#include "core_io.h"
#ifdef ENABLE_MINING
#include "crypto/equihash.h"
#endif
#include "init.h"
#include "main.h"
#include "metrics.h"
#include "miner.h"
#include "net.h"
#include "pow.h"
#include "rpc/server.h"
#include "txmempool.h"
#include "util.h"
#include "validationinterface.h"
#include "wallet/wallet.h"
#include "asyncrpcqueue.h"
#include "asyncrpcoperation.h"
#include "wallet/asyncrpcoperation_sendmany.h"
#include "timedata.h"
#include <stdint.h>
#include <boost/assign/list_of.hpp>
#include <univalue.h>
#include "rpc/pbaasrpc.h"
#include "transaction_builder.h"
using namespace std;
extern uint32_t ASSETCHAINS_ALGO;
extern int32_t ASSETCHAINS_EQUIHASH, ASSETCHAINS_LWMAPOS;
extern char ASSETCHAINS_SYMBOL[KOMODO_ASSETCHAIN_MAXLEN];
extern uint64_t ASSETCHAINS_STAKED;
extern int32_t KOMODO_MININGTHREADS;
extern bool VERUS_MINTBLOCKS;
extern uint8_t NOTARY_PUBKEY33[33];
extern uint160 ASSETCHAINS_CHAINID;
extern uint160 VERUS_CHAINID;
extern std::string VERUS_CHAINNAME;
extern int32_t USE_EXTERNAL_PUBKEY;
extern std::string NOTARY_PUBKEY;
#define _ASSETCHAINS_TIMELOCKOFF 0xffffffffffffffff
extern uint64_t ASSETCHAINS_TIMELOCKGTE, ASSETCHAINS_TIMEUNLOCKFROM, ASSETCHAINS_TIMEUNLOCKTO;
extern int64_t ASSETCHAINS_SUPPLY;
extern uint64_t ASSETCHAINS_REWARD[3], ASSETCHAINS_DECAY[3], ASSETCHAINS_HALVING[3], ASSETCHAINS_ENDSUBSIDY[3], ASSETCHAINS_ERAOPTIONS[3];
extern int32_t PBAAS_STARTBLOCK, PBAAS_ENDBLOCK, ASSETCHAINS_LWMAPOS;
extern uint32_t ASSETCHAINS_ALGO, ASSETCHAINS_VERUSHASH, ASSETCHAINS_LASTERA;
extern std::string VERUS_CHAINNAME;
arith_uint256 komodo_PoWtarget(int32_t *percPoSp,arith_uint256 target,int32_t height,int32_t goalperc);
std::set<uint160> ClosedPBaaSChains({});
// NOTE: Assumes a conclusive result; if result is inconclusive, it must be handled by caller
static UniValue BIP22ValidationResult(const CValidationState& state)
{
if (state.IsValid())
return NullUniValue;
std::string strRejectReason = state.GetRejectReason();
if (state.IsError())
throw JSONRPCError(RPC_VERIFY_ERROR, strRejectReason);
if (state.IsInvalid())
{
if (strRejectReason.empty())
return "rejected";
return strRejectReason;
}
// Should be impossible
return "valid?";
}
class submitblock_StateCatcher : public CValidationInterface
{
public:
uint256 hash;
bool found;
CValidationState state;
submitblock_StateCatcher(const uint256 &hashIn) : hash(hashIn), found(false), state() {};
protected:
virtual void BlockChecked(const CBlock& block, const CValidationState& stateIn) {
if (block.GetHash() != hash)
return;
found = true;
state = stateIn;
};
};
bool GetCurrencyDefinition(const uint160 &chainID, CCurrencyDefinition &chainDef, int32_t *pDefHeight, bool checkMempool, CUTXORef *pUTXO, std::vector<CNodeData> *pGoodNodes)
{
bool isVerusActive = IsVerusActive();
static bool thisChainLoaded = false;
static bool localDefined = false;
std::vector<CNodeData> _goodNodes;
std::vector<CNodeData> &goodNodes = pGoodNodes ? *pGoodNodes : _goodNodes;
std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue> > unspentOutputs;
uint160 lookupKey = CCrossChainRPCData::GetConditionID(chainID, CCurrencyDefinition::CurrencyDefinitionKey());
if (!localDefined &&
chainID == ConnectedChains.ThisChain().GetID() &&
isVerusActive &&
GetAddressUnspent(lookupKey, CScript::P2IDX, unspentOutputs) &&
unspentOutputs.size())
{
localDefined = true;
unspentOutputs.clear();
}
if (!localDefined && chainID == ConnectedChains.ThisChain().GetID() && (thisChainLoaded || chainActive.Height() < 1 || isVerusActive))
{
chainDef = ConnectedChains.ThisChain();
if (pDefHeight)
{
*pDefHeight = 0;
}
if (pUTXO)
{
*pUTXO = CUTXORef();
}
if (pGoodNodes)
{
goodNodes = GetGoodNodes();
}
return true;
}
else if (!isVerusActive && chainActive.Height() == 0)
{
if (ConnectedChains.FirstNotaryChain().IsValid() && (chainID == ConnectedChains.FirstNotaryChain().chainDefinition.GetID()))
{
chainDef = ConnectedChains.FirstNotaryChain().chainDefinition;
if (pDefHeight)
{
*pDefHeight = 0;
}
if (pUTXO)
{
*pUTXO = CUTXORef();
}
return true;
}
}
std::vector<std::pair<uint160, int>> addresses = {{lookupKey, CScript::P2IDX}};
std::vector<std::pair<CMempoolAddressDeltaKey, CMempoolAddressDelta> > results;
CCurrencyDefinition foundDef;
if (!ClosedPBaaSChains.count(chainID) && GetAddressUnspent(lookupKey, CScript::P2IDX, unspentOutputs) && unspentOutputs.size())
{
for (auto &currencyDefOut : unspentOutputs)
{
if ((foundDef = CCurrencyDefinition(currencyDefOut.second.script)).IsValid())
{
chainDef = foundDef;
if (pDefHeight)
{
*pDefHeight = currencyDefOut.second.blockHeight;
}
if (pUTXO)
{
*pUTXO = CUTXORef(currencyDefOut.first.txhash, currencyDefOut.first.index);
}
if (pGoodNodes)
{
std::vector<CNodeData> nodes;
CTransaction nTx;
uint256 blockHash;
if (!myGetTransaction(currencyDefOut.first.txhash, nTx, blockHash))
{
LogPrintf("%s: Cannot load currency definition transaction, txid %s\n", __func__, currencyDefOut.first.txhash.GetHex().c_str());
continue;
}
for (int i = currencyDefOut.first.index + 1; i < nTx.vout.size(); i++)
{
COptCCParams p;
CPBaaSNotarization pbn;
if (nTx.vout[i].scriptPubKey.IsPayToCryptoCondition(p) &&
p.IsValid() &&
(p.evalCode == EVAL_ACCEPTEDNOTARIZATION || p.evalCode == EVAL_EARNEDNOTARIZATION) &&
p.vData.size() &&
(pbn = CPBaaSNotarization(p.vData[0])).IsValid() &&
pbn.currencyID == chainID)
{
goodNodes = pbn.nodes;
break;
}
}
}
break;
}
}
}
else if (checkMempool && !ClosedPBaaSChains.count(chainID) && mempool.getAddressIndex(addresses, results) && results.size())
{
for (auto &currencyDefOut : results)
{
CTransaction tx;
if (mempool.lookup(currencyDefOut.first.txhash, tx) &&
(foundDef = CCurrencyDefinition(tx.vout[currencyDefOut.first.index].scriptPubKey)).IsValid())
{
chainDef = foundDef;
if (pDefHeight)
{
*pDefHeight = 0;
}
if (pUTXO)
{
*pUTXO = CUTXORef(currencyDefOut.first.txhash, currencyDefOut.first.index);
}
if (pGoodNodes)
{
std::vector<CNodeData> nodes;
uint256 blockHash;
for (int i = currencyDefOut.first.index + 1; i < tx.vout.size(); i++)
{
COptCCParams p;
CPBaaSNotarization pbn;
if (tx.vout[i].scriptPubKey.IsPayToCryptoCondition(p) &&
p.IsValid() &&
(p.evalCode == EVAL_ACCEPTEDNOTARIZATION || p.evalCode == EVAL_EARNEDNOTARIZATION) &&
p.vData.size() &&
(pbn = CPBaaSNotarization(p.vData[0])).IsValid() &&
pbn.currencyID == chainID)
{
goodNodes = pbn.nodes;
break;
}
}
}
break;
}
}
}
if (chainID == ASSETCHAINS_CHAINID && foundDef.IsValid())
{
if (pGoodNodes)
{
goodNodes = GetGoodNodes();
}
if (!thisChainLoaded)
{
thisChainLoaded = true;
ConnectedChains.ThisChain() = foundDef;
}
}
return foundDef.IsValid();
}
bool GetCurrencyDefinition(const std::string &name, CCurrencyDefinition &chainDef)
{
return GetCurrencyDefinition(CCrossChainRPCData::GetID(name), chainDef);
}
CTxDestination ValidateDestination(const std::string &destStr)
{
CTxDestination destination = DecodeDestination(destStr);
if (destination.which() == COptCCParams::ADDRTYPE_ID)
{
AssertLockHeld(cs_main);
if (!CIdentity::LookupIdentity(GetDestinationID(destination)).IsValid())
{
return CTxDestination();
}
}
return destination;
}
// returns non-null value, if this is a gateway destination
std::pair<uint160, CTransferDestination> ValidateTransferDestination(const std::string &destStr)
{
uint160 parent;
uint160 destID;
CTxDestination destination;
AssertLockHeld(cs_main);
// One case where the transfer destination is valid, but will not be located on chain
// is when the destination is a gateway. In that case, alternate format destinations
// can be used. Each format type has its own validation.
if (std::count(destStr.begin(), destStr.end(), '@') == 1)
{
std::string str = CleanName(destStr, parent);
if (str != "")
{
destID = CIdentityID(CIdentity::GetID(str, parent));
if (CIdentity::LookupIdentity(destID).IsValid())
{
return std::make_pair(uint160(), DestinationToTransferDestination(CIdentityID(destID)));
}
// we haven't found an ID, so this may be a transfer address, but only if
// it's parent is a gateway currency and it validates
auto gatewayPair = ConnectedChains.GetGateway(parent);
if (gatewayPair.first.IsValid() && gatewayPair.second->ValidateDestination(str));
{
return std::make_pair(parent, gatewayPair.second->ToTransferDestination(str));
}
}
}
else
{
destination = DecodeDestination(destStr);
if (destination.which() == COptCCParams::ADDRTYPE_ID)
{
if (!CIdentity::LookupIdentity(GetDestinationID(destination)).IsValid())
{
destination = CTxDestination();
}
}
}
return std::make_pair(uint160(), DestinationToTransferDestination(destination));
}
// set default peer nodes in the current connected chains
bool SetPeerNodes(const UniValue &nodes)
{
if (mapArgs.count("-connect") && mapMultiArgs["-connect"].size() > 0)
{
printf("%s: Ignoring seednodes due to nodes specified in \"-connect\" parameter\n", __func__);
LogPrintf("%s: Ignoring seednodes due to nodes specified in \"-connect\" parameter\n", __func__);
std::vector<std::string> connectNodes = mapMultiArgs["-connect"];
for (int i = 0; i < connectNodes.size(); i++)
{
CNodeData oneNode = CNodeData(connectNodes[i], "");
if (oneNode.networkAddress != "")
{
ConnectedChains.defaultPeerNodes.push_back(oneNode);
}
}
}
else
{
if (!nodes.isArray() || nodes.size() == 0)
{
return false;
}
LOCK(ConnectedChains.cs_mergemining);
ConnectedChains.defaultPeerNodes.clear();
for (int i = 0; i < nodes.size(); i++)
{
CNodeData oneNode(nodes[i]);
if (oneNode.networkAddress != "")
{
ConnectedChains.defaultPeerNodes.push_back(oneNode);
}
}
std::vector<std::string> seedNodes = mapMultiArgs["-seednode"];
for (int i = 0; i < seedNodes.size(); i++)
{
CNodeData oneNode = CNodeData(seedNodes[i], "");
if (oneNode.networkAddress != "")
{
ConnectedChains.defaultPeerNodes.push_back(oneNode);
}
}
}
std::vector<std::string> addNodes = mapMultiArgs["-addnode"];
for (int i = 0; i < addNodes.size(); i++)
{
CNodeData oneNode = CNodeData(addNodes[i], "");
if (oneNode.networkAddress != "")
{
ConnectedChains.defaultPeerNodes.push_back(oneNode);
}
}
// set all command line parameters into mapArgs from chain definition
vector<string> nodeStrs;
for (auto node : ConnectedChains.defaultPeerNodes)
{
nodeStrs.push_back(node.networkAddress);
}
if (!(mapArgs.count("-connect") && mapMultiArgs["-connect"].size() > 0))
{
mapMultiArgs["-seednode"] = nodeStrs;
}
for (auto &oneNode : nodeStrs)
{
AddOneShot(oneNode);
}
if (int port = ConnectedChains.GetThisChainPort())
{
mapArgs["-port"] = to_string(port);
}
return true;
}
// adds the chain definition for this chain and nodes as well
// this also sets up the notarization chain, if there is one
uint256 CurrencyDefHash()
{
return ::GetHash(ConnectedChains.ThisChain());
}
// adds the chain definition for this chain and nodes as well
// this also sets up the notarization chain, if there is one
bool SetThisChain(const UniValue &chainDefinition)
{
ConnectedChains.ThisChain() = CCurrencyDefinition(chainDefinition);
if (!ConnectedChains.ThisChain().IsValid())
{
return false;
}
SetPeerNodes(find_value(chainDefinition, "nodes"));
memset(ASSETCHAINS_SYMBOL, 0, sizeof(ASSETCHAINS_SYMBOL));
assert(ConnectedChains.ThisChain().name.size() < sizeof(ASSETCHAINS_SYMBOL));
strcpy(ASSETCHAINS_SYMBOL, ConnectedChains.ThisChain().name.c_str());
if (!IsVerusActive())
{
CCurrencyDefinition notaryChainDef;
// we set the notary chain to either Verus or VerusTest
notaryChainDef.nVersion = CCurrencyDefinition::VERSION_CURRENT;
if (PBAAS_TESTMODE)
{
// setup Verus test parameters
notaryChainDef.name = "VRSCTEST";
notaryChainDef.preAllocation = {std::make_pair(uint160(), 5000000000000000)};
notaryChainDef.rewards = std::vector<int64_t>({1200000000});
notaryChainDef.rewardsDecay = std::vector<int64_t>({0});
Split(GetArg("-ac_halving",""), ASSETCHAINS_HALVING, 0);
notaryChainDef.halving = std::vector<int32_t>({(int32_t)(ASSETCHAINS_HALVING[0])});
notaryChainDef.eraEnd = std::vector<int32_t>({0});
}
else
{
// first setup Verus parameters
notaryChainDef.name = "VRSC";
notaryChainDef.rewards = std::vector<int64_t>({0,38400000000,2400000000});
notaryChainDef.rewardsDecay = std::vector<int64_t>({100000000,0,0});
notaryChainDef.halving = std::vector<int32_t>({1,43200,1051920});
notaryChainDef.eraEnd = std::vector<int32_t>({10080,226080,0});
}
notaryChainDef.options = (notaryChainDef.OPTION_PBAAS |
notaryChainDef.OPTION_CANBERESERVE |
notaryChainDef.OPTION_ID_ISSUANCE |
notaryChainDef.OPTION_ID_REFERRALS);
notaryChainDef.idRegistrationFees = CCurrencyDefinition::DEFAULT_ID_REGISTRATION_AMOUNT;
notaryChainDef.idReferralLevels = CCurrencyDefinition::DEFAULT_ID_REFERRAL_LEVELS;
VERUS_CHAINNAME = notaryChainDef.name;
notaryChainDef.systemID = notaryChainDef.GetID();
ASSETCHAINS_CHAINID = ConnectedChains.ThisChain().GetID();
ASSETCHAINS_TIMELOCKGTE = _ASSETCHAINS_TIMELOCKOFF;
ASSETCHAINS_TIMEUNLOCKFROM = 0;
ASSETCHAINS_TIMEUNLOCKTO = 0;
//printf("%s: %s\n", __func__, EncodeDestination(CIdentityID(notaryChainDef.GetID())).c_str());
ConnectedChains.notarySystems[notaryChainDef.GetID()] =
CNotarySystemInfo(0, CRPCChainData(notaryChainDef, PBAAS_HOST, PBAAS_PORT, PBAAS_USERPASS), CCurrencyDefinition(), CPBaaSNotarization());
CCurrencyState currencyState = ConnectedChains.GetCurrencyState(0);
ASSETCHAINS_SUPPLY = currencyState.supply;
}
else
{
ConnectedChains.ThisChain().options = (CCurrencyDefinition::OPTION_PBAAS | CCurrencyDefinition::OPTION_CANBERESERVE | CCurrencyDefinition::OPTION_ID_REFERRALS);
ConnectedChains.ThisChain().systemID = ConnectedChains.ThisChain().GetID();
}
auto numEras = ConnectedChains.ThisChain().rewards.size();
ASSETCHAINS_LASTERA = numEras - 1;
mapArgs["-ac_eras"] = to_string(numEras);
mapArgs["-ac_end"] = "";
mapArgs["-ac_reward"] = "";
mapArgs["-ac_halving"] = "";
mapArgs["-ac_decay"] = "";
mapArgs["-ac_options"] = "";
for (int j = 0; j < ASSETCHAINS_MAX_ERAS; j++)
{
if (j > ASSETCHAINS_LASTERA)
{
ASSETCHAINS_REWARD[j] = ASSETCHAINS_REWARD[j-1];
ASSETCHAINS_DECAY[j] = ASSETCHAINS_DECAY[j-1];
ASSETCHAINS_HALVING[j] = ASSETCHAINS_HALVING[j-1];
ASSETCHAINS_ENDSUBSIDY[j] = 0;
ASSETCHAINS_ERAOPTIONS[j] = 0;
}
else
{
ASSETCHAINS_REWARD[j] = ConnectedChains.ThisChain().rewards[j];
ASSETCHAINS_DECAY[j] = ConnectedChains.ThisChain().rewardsDecay[j];
ASSETCHAINS_HALVING[j] = ConnectedChains.ThisChain().halving[j];
ASSETCHAINS_ENDSUBSIDY[j] = ConnectedChains.ThisChain().eraEnd[j];
ASSETCHAINS_ERAOPTIONS[j] = ConnectedChains.ThisChain().options;
if (j == 0)
{
mapArgs["-ac_reward"] = to_string(ASSETCHAINS_REWARD[j]);
mapArgs["-ac_decay"] = to_string(ASSETCHAINS_DECAY[j]);
mapArgs["-ac_halving"] = to_string(ASSETCHAINS_HALVING[j]);
mapArgs["-ac_end"] = to_string(ASSETCHAINS_ENDSUBSIDY[j]);
mapArgs["-ac_options"] = to_string(ASSETCHAINS_ERAOPTIONS[j]);
}
else
{
mapArgs["-ac_reward"] += "," + to_string(ASSETCHAINS_REWARD[j]);
mapArgs["-ac_decay"] += "," + to_string(ASSETCHAINS_DECAY[j]);
mapArgs["-ac_halving"] += "," + to_string(ASSETCHAINS_HALVING[j]);
mapArgs["-ac_end"] += "," + to_string(ASSETCHAINS_ENDSUBSIDY[j]);
mapArgs["-ac_options"] += "," + to_string(ASSETCHAINS_ERAOPTIONS[j]);
}
}
}
PBAAS_STARTBLOCK = ConnectedChains.ThisChain().startBlock;
mapArgs["-startblock"] = to_string(PBAAS_STARTBLOCK);
PBAAS_ENDBLOCK = ConnectedChains.ThisChain().endBlock;
mapArgs["-endblock"] = to_string(PBAAS_ENDBLOCK);
mapArgs["-ac_supply"] = to_string(ASSETCHAINS_SUPPLY);
return true;
}
void CurrencySystemTypeQuery(const uint160 queryID,
std::map<CUTXORef, int> &currenciesFound,
std::vector<std::pair<std::pair<CUTXORef, std::vector<CNodeData>>, CCurrencyDefinition>> &curDefVec,
uint32_t startBlock=0,
uint32_t endBlock=0)
{
if (startBlock || endBlock)
{
std::vector<CAddressIndexDbEntry> systemAddressIndex;
if (GetAddressIndex(queryID, CScript::P2IDX, systemAddressIndex, startBlock, endBlock) && systemAddressIndex.size())
{
for (auto &oneOut : systemAddressIndex)
{
CUTXORef oneRef(oneOut.first.txhash, oneOut.first.index);
if (!currenciesFound.count(oneRef))
{
currenciesFound.insert(std::make_pair(oneRef, -1));
}
}
}
}
else
{
std::vector<CAddressUnspentDbEntry> unspentAddressIndex;
if (GetAddressUnspent(queryID, CScript::P2IDX, unspentAddressIndex) && unspentAddressIndex.size())
{
for (auto &oneOut : unspentAddressIndex)
{
CCurrencyDefinition curDef(oneOut.second.script);
if (!curDef.IsValid())
{
LogPrintf("%s: invalid currency definition found in index, txid %s, vout: %d\n", __func__, oneOut.first.txhash.GetHex().c_str(), (int)oneOut.first.index);
continue;
}
CUTXORef oneRef(oneOut.first.txhash, oneOut.first.index);
if (!currenciesFound.count(oneRef) || currenciesFound[oneRef] == -1)
{
std::vector<CNodeData> nodes;
CTransaction nTx;
uint256 blockHash;
uint160 curDefID = curDef.GetID();
if (!myGetTransaction(oneRef.hash, nTx, blockHash))
{
LogPrintf("%s: Cannot load currency definition transaction, txid %s\n", __func__, oneOut.first.txhash.GetHex().c_str());
continue;
}
for (int i = oneRef.n + 1; i < nTx.vout.size(); i++)
{
COptCCParams p;
CPBaaSNotarization pbn;
if (nTx.vout[i].scriptPubKey.IsPayToCryptoCondition(p) &&
p.IsValid() &&
(p.evalCode == EVAL_ACCEPTEDNOTARIZATION || p.evalCode == EVAL_EARNEDNOTARIZATION) &&
p.vData.size() &&
(pbn = CPBaaSNotarization(p.vData[0])).IsValid() &&
pbn.currencyID == curDefID)
{
nodes = pbn.nodes;
break;
}
}
currenciesFound.insert(std::make_pair(oneRef, curDefVec.size()));
curDefVec.push_back(std::make_pair(std::make_pair(oneRef, nodes), curDef));
}
}
}
}
}
void CurrencyNotarizationTypeQuery(CCurrencyDefinition::EQueryOptions launchStateQuery,
std::map<CUTXORef, int> &currenciesFound,
std::vector<std::pair<std::pair<CUTXORef, std::vector<CNodeData>>, CCurrencyDefinition>> &curDefVec,
uint32_t startBlock=0,
uint32_t endBlock=0)
{
uint160 queryID;
bool checkUnspent = false;
if (launchStateQuery == CCurrencyDefinition::QUERY_LAUNCHSTATE_PRELAUNCH)
{
queryID = CCrossChainRPCData::GetConditionID(ASSETCHAINS_CHAINID, CPBaaSNotarization::LaunchPrelaunchKey());
checkUnspent = true;
}
else if (launchStateQuery == CCurrencyDefinition::QUERY_LAUNCHSTATE_REFUND)
{
queryID = CCrossChainRPCData::GetConditionID(ASSETCHAINS_CHAINID, CPBaaSNotarization::LaunchRefundKey());
}
else if (launchStateQuery == CCurrencyDefinition::QUERY_LAUNCHSTATE_COMPLETE)
{
queryID = CCrossChainRPCData::GetConditionID(ASSETCHAINS_CHAINID, CPBaaSNotarization::LaunchCompleteKey());
}
else if (launchStateQuery == CCurrencyDefinition::QUERY_LAUNCHSTATE_CONFIRM)
{
queryID = CCrossChainRPCData::GetConditionID(ASSETCHAINS_CHAINID, CPBaaSNotarization::LaunchConfirmKey());
}
else if (launchStateQuery == CCurrencyDefinition::QUERY_ISCONVERTER)
{
queryID = CCrossChainRPCData::GetConditionID(ASSETCHAINS_CHAINID, CCoinbaseCurrencyState::IndexConverterKey(ASSETCHAINS_CHAINID));
checkUnspent = true;
}
if (launchStateQuery != CCurrencyDefinition::QUERY_LAUNCHSTATE_PRELAUNCH && (startBlock || endBlock))
{
std::vector<CAddressIndexDbEntry> notarizationAddressIndex;
std::map<uint160, CPBaaSNotarization> notarizationsFound;
if (GetAddressIndex(queryID, CScript::P2IDX, notarizationAddressIndex, startBlock, endBlock) && notarizationAddressIndex.size())
{
for (auto &oneOut : notarizationAddressIndex)
{
CTransaction notarizationTx;
uint256 blockHash;
if (!myGetTransaction(oneOut.first.txhash, notarizationTx, blockHash) ||
notarizationTx.vout.size() <= oneOut.first.index)
{
LogPrintf("%s: Error reading transaction %s\n", __func__, oneOut.first.txhash.GetHex().c_str());
continue;
}
CPBaaSNotarization pbn(notarizationTx.vout[oneOut.first.index].scriptPubKey);
if (!pbn.IsValid())
{
LogPrintf("%s: Invalid notarization on transaction %s, vout: %d\n", __func__, oneOut.first.txhash.GetHex().c_str(), (int)oneOut.first.index);
continue;
}
CCurrencyDefinition curDef;
int32_t currencyHeight;
CUTXORef curDefUTXO;
std::vector<CNodeData> goodNodes;
if (!GetCurrencyDefinition(pbn.currencyID, curDef, &currencyHeight, false, &curDefUTXO, &goodNodes))
{
LogPrintf("%s: Error getting currency definition %s\n", __func__, EncodeDestination(CIdentityID(pbn.currencyID)).c_str());
continue;
}
if (!currenciesFound.count(curDefUTXO))
{
currenciesFound[curDefUTXO] = curDefVec.size();
curDefVec.push_back(std::make_pair(std::make_pair(curDefUTXO, goodNodes), curDef));
}
}
}
}
else
{
std::vector<CAddressUnspentDbEntry> unspentAddressIndex;
if (GetAddressUnspent(queryID, CScript::P2IDX, unspentAddressIndex) && unspentAddressIndex.size())
{
for (auto &oneOut : unspentAddressIndex)
{
CPBaaSNotarization pbn(oneOut.second.script);
if (!pbn.IsValid())
{
LogPrintf("%s: Invalid notarization in index for %s, vout: %d\n", __func__, oneOut.first.txhash.GetHex().c_str(), (int)oneOut.first.index);
continue;
}
CCurrencyDefinition curDef;
int32_t currencyHeight;
CUTXORef curDefUTXO;
std::vector<CNodeData> goodNodes;
if (!GetCurrencyDefinition(pbn.currencyID, curDef, &currencyHeight, false, &curDefUTXO, &goodNodes))
{
LogPrintf("%s: Error getting currency definition %s\n", __func__, EncodeDestination(CIdentityID(pbn.currencyID)).c_str());
continue;
}
if (!currenciesFound.count(curDefUTXO) &&
(!endBlock || currencyHeight < endBlock) &&
(!startBlock || curDef.startBlock >= startBlock))
{
currenciesFound[curDefUTXO] = curDefVec.size();
curDefVec.push_back(std::make_pair(std::make_pair(curDefUTXO, goodNodes), curDef));
}
}
}
}
}
void GetCurrencyDefinitions(std::vector<std::pair<std::pair<CUTXORef, std::vector<CNodeData>>, CCurrencyDefinition>> &chains,
CCurrencyDefinition::EQueryOptions launchStateQuery,
CCurrencyDefinition::EQueryOptions systemTypeQuery,
bool isConverter,
uint32_t startBlock=0,
uint32_t endBlock=0)
{
CCcontract_info CC;
CCcontract_info *cp;
std::map<CUTXORef, int> currenciesFound;
std::vector<std::pair<std::pair<CUTXORef, std::vector<CNodeData>>, CCurrencyDefinition>> curDefVec;
if (systemTypeQuery == CCurrencyDefinition::QUERY_SYSTEMTYPE_LOCAL)
{
uint160 queryID = CCrossChainRPCData::GetConditionID(ASSETCHAINS_CHAINID, CCurrencyDefinition::CurrencySystemKey());
CurrencySystemTypeQuery(queryID, currenciesFound, curDefVec, startBlock, endBlock);
}
else if (systemTypeQuery == CCurrencyDefinition::QUERY_SYSTEMTYPE_PBAAS)
{
uint160 queryID = CCrossChainRPCData::GetConditionID(ASSETCHAINS_CHAINID, CCurrencyDefinition::PBaaSChainKey());
CurrencySystemTypeQuery(queryID, currenciesFound, curDefVec, startBlock, endBlock);
}
else if (systemTypeQuery == CCurrencyDefinition::QUERY_SYSTEMTYPE_GATEWAY)
{
uint160 queryID = CCrossChainRPCData::GetConditionID(ASSETCHAINS_CHAINID, CCurrencyDefinition::CurrencyGatewayKey());
CurrencySystemTypeQuery(queryID, currenciesFound, curDefVec, startBlock, endBlock);
}
bool narrowBySystem = systemTypeQuery != CCurrencyDefinition::QUERY_NULL;
bool narrowByLaunch = launchStateQuery != CCurrencyDefinition::QUERY_NULL;
if (narrowBySystem && !currenciesFound.size())
{
return;
}
if (narrowByLaunch)
{
std::map<CUTXORef, int> launchStateCurrenciesFound;
std::vector<std::pair<std::pair<CUTXORef, std::vector<CNodeData>>, CCurrencyDefinition>> launchStateCurVec;
static std::set<CCurrencyDefinition::EQueryOptions> validLaunchOptions({CCurrencyDefinition::QUERY_LAUNCHSTATE_PRELAUNCH,
CCurrencyDefinition::QUERY_LAUNCHSTATE_REFUND,
CCurrencyDefinition::QUERY_LAUNCHSTATE_CONFIRM,
CCurrencyDefinition::QUERY_LAUNCHSTATE_COMPLETE});
if (!validLaunchOptions.count(launchStateQuery))
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid launchStateQuery");
}
CurrencyNotarizationTypeQuery(launchStateQuery, launchStateCurrenciesFound, launchStateCurVec, startBlock, endBlock);
if (launchStateCurrenciesFound.size())
{
// remove all that are not in the system type of interest
if (narrowBySystem)
{
std::vector<CUTXORef> toRemove;
for (auto &oneFound : currenciesFound)
{
if (!launchStateCurrenciesFound.count(oneFound.first))
{
toRemove.push_back(oneFound.first);
}
else if (currenciesFound[oneFound.first] == -1)
{
currenciesFound[oneFound.first] = curDefVec.size();
curDefVec.push_back(launchStateCurVec[launchStateCurrenciesFound[oneFound.first]]);
}
}
if (currenciesFound.size() == toRemove.size())
{
return;
}
for (auto &oneUtxo : toRemove)
{
currenciesFound.erase(oneUtxo);
}
}
else
{
curDefVec.insert(curDefVec.end(), launchStateCurVec.begin(), launchStateCurVec.end());
currenciesFound = launchStateCurrenciesFound;
}
}
else
{
return;
}
}
bool isNarrowing = narrowBySystem || narrowByLaunch;
if (isNarrowing && !currenciesFound.size())
{
return;
}
// two options are is converter as narrowing or just is converter
// for narrowing, we ignore start and end blocks to determine state now
if (isConverter)
{
if (isNarrowing)
{
std::map<CUTXORef, int> converterCurrenciesFound;
std::vector<std::pair<std::pair<CUTXORef, std::vector<CNodeData>>, CCurrencyDefinition>> converterCurVec;
// get converters and return only those already found that are converters
CurrencyNotarizationTypeQuery(CCurrencyDefinition::QUERY_ISCONVERTER, converterCurrenciesFound, converterCurVec, startBlock, endBlock);
std::vector<CUTXORef> toRemove;
for (auto &oneFound : currenciesFound)
{
if (!converterCurrenciesFound.count(oneFound.first))
{
toRemove.push_back(oneFound.first);
}
else if (currenciesFound[oneFound.first] == -1)
{
currenciesFound[oneFound.first] = curDefVec.size();
curDefVec.push_back(converterCurVec[converterCurrenciesFound[oneFound.first]]);
}
}
if (currenciesFound.size() == toRemove.size())
{
return;
}
for (auto &oneUtxo : toRemove)
{
currenciesFound.erase(oneUtxo);
}
}
else
{
// the only query is converters
CurrencyNotarizationTypeQuery(CCurrencyDefinition::QUERY_ISCONVERTER, currenciesFound, curDefVec, startBlock, endBlock);
}
}
else if (!isNarrowing)
{
// no qualifiers, so we default to this system currencies and retrieve all currencies in the specified block range
uint160 queryID = CCrossChainRPCData::GetConditionID(ASSETCHAINS_CHAINID, CCurrencyDefinition::CurrencySystemKey());
CurrencySystemTypeQuery(queryID, currenciesFound, curDefVec, startBlock, endBlock);
}
// now, loop through the found currencies and load the currency definition if not loaded, then store all
// in the return vector
for (auto &oneCur : currenciesFound)
{
if (oneCur.second == -1)
{
CTransaction tx;
uint256 blkHash;
if (!myGetTransaction(oneCur.first.hash, tx, blkHash) ||
tx.vout.size() <= oneCur.first.n)
{
continue;
}
CCurrencyDefinition oneCurDef(tx.vout[oneCur.first.n].scriptPubKey);
std::vector<CNodeData> nodes;
if (oneCurDef.IsValid())
{
for (int i = oneCur.first.n + 1; i < tx.vout.size(); i++)
{
COptCCParams p;
CPBaaSNotarization pbn;
if (tx.vout[i].scriptPubKey.IsPayToCryptoCondition(p) &&
p.IsValid() &&
(p.evalCode == EVAL_ACCEPTEDNOTARIZATION || p.evalCode == EVAL_EARNEDNOTARIZATION) &&
p.vData.size() &&
(pbn = CPBaaSNotarization(p.vData[0])).IsValid() &&
pbn.currencyID == oneCurDef.GetID())
{
nodes = pbn.nodes;
break;
}
}
chains.push_back(std::make_pair(std::make_pair(oneCur.first, nodes), oneCurDef));
}
}
else
{
chains.push_back(curDefVec[oneCur.second]);
}
}
}
bool CConnectedChains::GetNotaryCurrencies(const CRPCChainData notaryChain,
const std::set<uint160> &currencyIDs,
std::map<uint160, std::pair<CCurrencyDefinition,CPBaaSNotarization>> &currencyDefs)
{
for (auto &curID : currencyIDs)
{
CCurrencyDefinition oneDef;
UniValue params(UniValue::VARR);
params.push_back(EncodeDestination(CIdentityID(curID)));
UniValue result;
try
{
result = find_value(RPCCallRoot("getcurrency", params), "result");
} catch (exception e)
{
result = NullUniValue;
}
if (!result.isNull())
{
oneDef = CCurrencyDefinition(result);
}
if (!oneDef.IsValid())
{
// no matter what happens, we should be able to get a valid currency state of some sort, if not, fail
LogPrintf("Unable to get currency definition for %s\n", EncodeDestination(CIdentityID(curID)).c_str());
printf("Unable to get currency definition for %s\n", EncodeDestination(CIdentityID(curID)).c_str());
return false;
}
{
CChainNotarizationData cnd;
UniValue result;
try
{
result = find_value(RPCCallRoot("getnotarizationdata", params), "result");
} catch (exception e)
{
result = NullUniValue;
}
if (!result.isNull())
{
cnd = CChainNotarizationData(result);
}
if (!cnd.IsValid())
{
// no matter what happens, we should be able to get a valid currency state of some sort, if not, fail
LogPrintf("Invalid notarization data for %s\n", EncodeDestination(CIdentityID(curID)).c_str());
printf("Invalid notarization data for %s\n", EncodeDestination(CIdentityID(curID)).c_str());
return false;
}
LOCK(cs_mergemining);
currencyDefs[oneDef.GetID()].first = oneDef;
if (cnd.IsConfirmed())
{
currencyDefs[oneDef.GetID()].second = cnd.vtx[cnd.lastConfirmed].second;
currencyDefs[oneDef.GetID()].second.SetBlockOneNotarization();
}
}
}
return true;
}
bool CConnectedChains::GetNotaryIDs(const CRPCChainData notaryChain, const std::set<uint160> &idIDs, std::map<uint160, CIdentity> &identities)
{
for (auto &curID : idIDs)
{
CIdentity oneDef;
UniValue params(UniValue::VARR);
params.push_back(EncodeDestination(CIdentityID(curID)));
UniValue result;
try
{
result = find_value(RPCCallRoot("getidentity", params), "result");
} catch (exception e)
{
result = NullUniValue;
}
if (!result.isNull())
{
oneDef = CIdentity(find_value(result, "identity"));
}
if (!oneDef.IsValid())
{
// no matter what happens, we should be able to get a valid currency state of some sort, if not, fail
LogPrintf("Unable to get identity for %s\n", EncodeDestination(CIdentityID(curID)).c_str());
printf("Unable to get identity for %s\n", EncodeDestination(CIdentityID(curID)).c_str());
return false;
}
{
identities[oneDef.GetID()] = oneDef;
}
}
// if we have a currency converter, create a new ID as a clone of the main chain ID with revocation and recovery as main chain ID
if (!ConnectedChains.ThisChain().GatewayConverterID().IsNull())
{
CIdentity newConverterIdentity = identities[ASSETCHAINS_CHAINID];
assert(newConverterIdentity.IsValid());
newConverterIdentity.parent = newConverterIdentity.GetID();
newConverterIdentity.name = ConnectedChains.ThisChain().gatewayConverterName;
newConverterIdentity.contentMap.clear();
newConverterIdentity.revocationAuthority = newConverterIdentity.recoveryAuthority = ASSETCHAINS_CHAINID;
identities[ConnectedChains.ThisChain().GatewayConverterID()] = newConverterIdentity;
}
return true;
}
bool CConnectedChains::GetLastImport(const uint160 &currencyID,
CTransaction &lastImport,
int32_t &outputNum)
{
std::vector<CAddressUnspentDbEntry> unspentOutputs;
std::vector<std::pair<CMempoolAddressDeltaKey, CMempoolAddressDelta>> memPoolOutputs;
LOCK2(cs_main, mempool.cs);
uint160 importKey = CCrossChainRPCData::GetConditionID(currencyID, CCrossChainImport::CurrencyImportKey());
if (mempool.getAddressIndex(std::vector<std::pair<uint160, int32_t>>({std::make_pair(importKey, CScript::P2IDX)}), memPoolOutputs) &&
memPoolOutputs.size())
{
// make sure it isn't just a burned transaction to that address, drop out on first match
COptCCParams p;
CAddressUnspentDbEntry foundOutput;
std::set<uint256> spentTxOuts;
std::set<uint256> txOuts;
for (const auto &oneOut : memPoolOutputs)
{
// get last one in spending list
if (oneOut.first.spending)
{
spentTxOuts.insert(oneOut.first.txhash);
}
}
for (auto &oneOut : memPoolOutputs)
{
if (!spentTxOuts.count(oneOut.first.txhash))
{
lastImport = mempool.mapTx.find(oneOut.first.txhash)->GetTx();
outputNum = oneOut.first.index;
return true;
}
}
}
// get last import from the specified chain
if (!GetAddressUnspent(importKey, CScript::P2IDX, unspentOutputs))
{
return false;
}
// make sure it isn't just a burned transaction to that address, drop out on first match
const std::pair<CAddressUnspentKey, CAddressUnspentValue> *pOutput = NULL;
COptCCParams p;
CAddressUnspentDbEntry foundOutput;
for (const auto &output : unspentOutputs)
{
if (output.second.script.IsPayToCryptoCondition(p) && p.IsValid() &&
p.evalCode == EVAL_CROSSCHAIN_IMPORT &&
p.vData.size())
{
foundOutput = output;
pOutput = &foundOutput;
break;
}
}
if (!pOutput)
{
return false;
}
uint256 hashBlk;
CCurrencyDefinition newCur;
if (!myGetTransaction(pOutput->first.txhash, lastImport, hashBlk))
{
return false;
}
outputNum = pOutput->first.index;
return true;
}
bool CConnectedChains::GetLastSourceImport(const uint160 &sourceSystemID,
CTransaction &lastImport,
int32_t &outputNum)
{
std::vector<CAddressUnspentDbEntry> unspentOutputs;
std::vector<std::pair<CMempoolAddressDeltaKey, CMempoolAddressDelta>> memPoolOutputs;
LOCK2(cs_main, mempool.cs);
uint160 importKey = CCrossChainRPCData::GetConditionID(sourceSystemID, CCrossChainImport::CurrencySystemImportKey());
if (mempool.getAddressIndex(std::vector<std::pair<uint160, int32_t>>({std::make_pair(importKey, CScript::P2IDX)}), memPoolOutputs) &&
memPoolOutputs.size())
{
// make sure it isn't just a burned transaction to that address, drop out on first match
COptCCParams p;
CAddressUnspentDbEntry foundOutput;
std::set<uint256> spentTxOuts;
std::set<uint256> txOuts;
for (const auto &oneOut : memPoolOutputs)
{
// get last one in spending list
if (oneOut.first.spending)
{
spentTxOuts.insert(oneOut.first.txhash);
}
}
for (auto &oneOut : memPoolOutputs)
{
if (!spentTxOuts.count(oneOut.first.txhash))
{
lastImport = mempool.mapTx.find(oneOut.first.txhash)->GetTx();
outputNum = oneOut.first.index;
return true;
}
}
}
// get last import from the specified chain
if (!GetAddressUnspent(importKey, CScript::P2IDX, unspentOutputs))
{
return false;
}
// make sure it isn't just a burned transaction to that address, drop out on first match
const std::pair<CAddressUnspentKey, CAddressUnspentValue> *pOutput = NULL;
COptCCParams p;
CAddressUnspentDbEntry foundOutput;
for (const auto &output : unspentOutputs)
{
if (output.second.script.IsPayToCryptoCondition(p) && p.IsValid() &&
p.evalCode == EVAL_CROSSCHAIN_IMPORT &&
p.vData.size())
{
foundOutput = output;
pOutput = &foundOutput;
break;
}
}
if (!pOutput)
{
return false;
}
uint256 hashBlk;
CCurrencyDefinition newCur;
if (!myGetTransaction(pOutput->first.txhash, lastImport, hashBlk))
{
return false;
}
outputNum = pOutput->first.index;
return true;
}
void CheckPBaaSAPIsValid()
{
//printf("Solution version running: %d\n\n", CConstVerusSolutionVector::activationHeight.ActiveVersion(chainActive.LastTip()->GetHeight()));
if (!chainActive.LastTip() ||
CConstVerusSolutionVector::activationHeight.ActiveVersion(chainActive.LastTip()->GetHeight()) < CConstVerusSolutionVector::activationHeight.ACTIVATE_PBAAS)
{
throw JSONRPCError(RPC_INVALID_REQUEST, "PBaaS not activated on blockchain.");
}
}
void CheckIdentityAPIsValid()
{
if (!chainActive.LastTip() ||
CConstVerusSolutionVector::activationHeight.ActiveVersion(chainActive.LastTip()->GetHeight()) < CConstVerusSolutionVector::activationHeight.ACTIVATE_IDENTITY)
{
throw JSONRPCError(RPC_INVALID_REQUEST, "Identity APIs not activated on blockchain.");
}
}
uint160 ValidateCurrencyName(std::string currencyStr, bool ensureCurrencyValid=false, CCurrencyDefinition *pCurrencyDef=NULL)
{
std::string extraName;
uint160 retVal;
currencyStr = TrimSpaces(currencyStr);
if (!currencyStr.size())
{
return retVal;
}
ParseSubNames(currencyStr, extraName, true);
if (currencyStr.back() == '@')
{
return retVal;
}
CTxDestination currencyDest = DecodeDestination(currencyStr);
if (currencyDest.which() == COptCCParams::ADDRTYPE_INVALID)
{
currencyDest = DecodeDestination(currencyStr + "@");
}
uint160 currencyID = GetDestinationID(currencyDest);
if (currencyDest.which() != COptCCParams::ADDRTYPE_INVALID)
{
if (currencyID == ConnectedChains.ThisChain().GetID() && (chainActive.Height() < 1 || _IsVerusActive()))
{
if (pCurrencyDef)
{
*pCurrencyDef = ConnectedChains.ThisChain();
}
return ConnectedChains.ThisChain().GetID();
}
// make sure there is such a currency defined on this chain
if (ensureCurrencyValid)
{
CCurrencyDefinition currencyDef;
if (!GetCurrencyDefinition(currencyID, currencyDef) || !currencyDef.IsValid())
{
return retVal;
}
retVal = currencyDef.GetID();
if (pCurrencyDef)
{
*pCurrencyDef = currencyDef;
}
}
else
{
retVal = currencyID;
}
}
return retVal;
}
uint160 GetChainIDFromParam(const UniValue &param, CCurrencyDefinition *pCurrencyDef=NULL)
{
return ValidateCurrencyName(uni_get_str(param), true, pCurrencyDef);
}
UniValue getcurrency(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() != 1)
{
throw runtime_error(
"getcurrency \"chainname\"\n"
"\nReturns a complete definition for any given chain if it is registered on the blockchain. If the chain requested\n"
"\nis NULL, chain definition of the current chain is returned.\n"
"\nArguments\n"
"1. \"chainname\" (string, optional) name of the chain to look for. no parameter returns current chain in daemon.\n"
"\nResult:\n"
" {\n"
" \"version\" : n, (int) version of this chain definition\n"
" \"name\" : \"string\", (string) name or symbol of the chain, same as passed\n"
" \"address\" : \"string\", (string) cryptocurrency address to send fee and non-converted premine\n"
" \"currencyid\" : \"i-address\", (string) string that represents the currency ID, same as the ID behind the currency\n"
" \"premine\" : n, (int) amount of currency paid out to the premine address in block #1, may be smart distribution\n"
" \"convertible\" : \"xxxx\" (bool) if this currency is a fractional reserve currency of Verus\n"
" \"startblock\" : n, (int) block # on this chain, which must be notarized into block one of the chain\n"
" \"endblock\" : n, (int) block # after which, this chain's useful life is considered to be over\n"
" \"eras\" : \"[obj, ...]\", (objarray) different chain phases of rewards and convertibility\n"
" {\n"
" \"reward\" : \"[n, ...]\", (int) reward start for each era in native coin\n"
" \"decay\" : \"[n, ...]\", (int) exponential or linear decay of rewards during each era\n"
" \"halving\" : \"[n, ...]\", (int) blocks between halvings during each era\n"
" \"eraend\" : \"[n, ...]\", (int) block marking the end of each era\n"
" \"eraoptions\" : \"[n, ...]\", (int) options (reserved)\n"
" }\n"
" \"nodes\" : \"[obj, ..]\", (objectarray, optional) up to 8 nodes that can be used to connect to the blockchain"
" [{\n"
" \"nodeidentity\" : \"txid\", (string, optional) internet, TOR, or other supported address for node\n"
" \"paymentaddress\" : n, (int, optional) rewards payment address\n"
" }, .. ]\n"
" \"lastconfirmedcurrencystate\" : {\n"
" }\n"
" \"besttxid\" : \"txid\"\n"
" }\n"
" \"confirmednotarization\" : {\n"
" }\n"
" \"confirmedtxid\" : \"txid\"\n"
" }\n"
"\nExamples:\n"
+ HelpExampleCli("getcurrency", "\"chainname\"")
+ HelpExampleRpc("getcurrency", "\"chainname\"")
);
}
CheckPBaaSAPIsValid();
LOCK2(cs_main, mempool.cs);
UniValue ret(UniValue::VOBJ);
uint32_t height = chainActive.Height();
CCurrencyDefinition chainDef;
uint160 chainID = GetChainIDFromParam(params[0], &chainDef);
if (chainID.IsNull())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid currency name or ID");
}
if (chainDef.IsValid())
{
ret = chainDef.ToUniValue();
int32_t defHeight;
CUTXORef defUTXO;
std::vector<CNodeData> nodes;
if (!GetCurrencyDefinition(chainID, chainDef, &defHeight, false, &defUTXO, &nodes))
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Currency not found");
}
if (defUTXO.IsValid())
{
ret.push_back(Pair("definitiontxid", defUTXO.hash.GetHex()));
ret.push_back(Pair("definitiontxout", (int)defUTXO.n));
}
UniValue lastStateUni = ConnectedChains.GetCurrencyState(chainDef, height + 1, defHeight).ToUniValue();
if ((chainDef.IsToken() && chainDef.systemID == ASSETCHAINS_CHAINID) ||
(chainDef.launchSystemID == ASSETCHAINS_CHAINID && height < chainDef.startBlock))
{
ret.push_back(Pair("bestheight", chainActive.Height()));
ret.push_back(Pair("lastconfirmedheight", chainActive.Height()));
ret.push_back(Pair("bestcurrencystate", lastStateUni));
ret.push_back(Pair("lastconfirmedcurrencystate", lastStateUni));
}
else
{
CChainNotarizationData cnd;
if (GetNotarizationData(chainDef.systemID, cnd) && cnd.IsConfirmed() &&
cnd.vtx[cnd.lastConfirmed].second.currencyStates.count(chainID))
{
ret.push_back(Pair("bestheight", (int64_t)cnd.vtx[cnd.lastConfirmed].second.notarizationHeight));
ret.push_back(Pair("lastconfirmedheight", (int64_t)cnd.vtx[cnd.lastConfirmed].second.notarizationHeight));
ret.push_back(Pair("bestcurrencystate", lastStateUni));
ret.push_back(Pair("lastconfirmedcurrencystate", lastStateUni));
}
else
{
GetNotarizationData(chainID, cnd);
int32_t confirmedHeight = -1, bestHeight = -1;
std::map<std::string, CNodeData> vNNodes;
if (cnd.forks.size())
{
// get all nodes from notarizations of the best chain into a vector
for (auto &oneNot : cnd.forks[cnd.bestChain])
{
for (auto &oneNode : cnd.vtx[oneNot].second.nodes)
{
vNNodes.insert(std::make_pair(oneNode.networkAddress, oneNode));
}
}
for (auto oneNode : nodes)
{
vNNodes[oneNode.networkAddress] = oneNode;
}
confirmedHeight = cnd.vtx.size() && cnd.lastConfirmed != -1 ? cnd.vtx[cnd.lastConfirmed].second.notarizationHeight : -1;
bestHeight = cnd.vtx.size() && cnd.bestChain != -1 ? cnd.vtx[cnd.forks[cnd.bestChain].back()].second.notarizationHeight : -1;
// shuffle and take 8 nodes,
// up to two of which will be from the last confirmed notarization if present
int numConfirmedNodes = cnd.lastConfirmed == -1 ? 0 : cnd.vtx[cnd.lastConfirmed].second.nodes.size();
int numToRemove = vNNodes.size() - (8 - numConfirmedNodes);
if (numToRemove > 0)
{
for (int i = 0; i < numToRemove; i++)
{
int idxToRemove = (insecure_rand() % vNNodes.size());
auto removeIt = vNNodes.begin();
int j;
for (j = 0; j < idxToRemove; removeIt++, j++);
vNNodes.erase(removeIt);
}
}
if (numConfirmedNodes)
{
for (int i = 0; i < numConfirmedNodes; i++)
{
vNNodes.insert(std::make_pair(cnd.vtx[cnd.lastConfirmed].second.nodes[i].networkAddress, cnd.vtx[cnd.lastConfirmed].second.nodes[i]));
}
}
if (vNNodes.size())
{
UniValue nodeArr(UniValue::VARR);
for (auto &oneNode : vNNodes)
{
nodeArr.push_back(oneNode.second.ToUniValue());
}
ret.push_back(Pair("nodes", nodeArr));
}
}
if (chainID == ASSETCHAINS_CHAINID)
{
int64_t curHeight = chainActive.Height();
ret.push_back(Pair("lastconfirmedheight", curHeight));
ret.push_back(Pair("lastconfirmedcurrencystate", lastStateUni));
ret.push_back(Pair("bestheight", curHeight));
}
else
{
if (!chainDef.IsToken())
{
ret.push_back(Pair("lastconfirmedheight", confirmedHeight == -1 ? 0 : confirmedHeight));
if (confirmedHeight != -1)
{
ret.push_back(Pair("lastconfirmedtxid", cnd.vtx[cnd.lastConfirmed].first.hash.GetHex().c_str()));
ret.push_back(Pair("lastconfirmedcurrencystate", cnd.vtx[cnd.lastConfirmed].second.currencyState.ToUniValue()));
}
}
ret.push_back(Pair("bestheight", bestHeight == -1 ? 0 : bestHeight));
if (bestHeight != -1)
{
ret.push_back(Pair("besttxid", cnd.vtx[cnd.forks[cnd.bestChain].back()].first.hash.GetHex().c_str()));
ret.push_back(Pair("bestcurrencystate", cnd.vtx[cnd.forks[cnd.bestChain].back()].second.currencyState.ToUniValue()));
}
}
}
}
return ret;
}
else
{
return NullUniValue;
}
}
UniValue getreservedeposits(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() != 1)
{
throw runtime_error(
"getreservedeposits \"currencyname\"\n"
"\nReturns all deposits under control of the specified currency or chain. If the currency is of an external system\n"
"or chain, all deposits will be under the control of that system or chain only, not its independent currencies.\n"
"\nArguments\n"
"1. \"currencyname\" (string, optional) full name or i-ID of controlling currency\n"
"\nResult:\n"
" {\n"
" }\n"
"\nExamples:\n"
+ HelpExampleCli("getreservedeposits", "\"currencyname\"")
+ HelpExampleRpc("getreservedeposits", "\"currencyname\"")
);
}
CheckPBaaSAPIsValid();
LOCK(cs_main);
CCurrencyDefinition chainDef;
uint160 chainID = ValidateCurrencyName(uni_get_str(params[0]), true, &chainDef);
if (chainID.IsNull())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid currency or currency not found " + uni_get_str(params[0]));
}
int32_t defHeight;
std::vector<CInputDescriptor> reserveDeposits;
{
LOCK(mempool.cs);
CCoinsView dummy;
CCoinsViewCache view(&dummy);
CCoinsViewMemPool viewMemPool(pcoinsTip, mempool);
view.SetBackend(viewMemPool);
if (!ConnectedChains.GetReserveDeposits(chainID, view, reserveDeposits))
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Error getting reserve deposits for on-chain currency");
}
}
CCurrencyValueMap totalReserveDeposits;
for (auto &oneDeposit : reserveDeposits)
{
CReserveDeposit rd;
COptCCParams p;
if (oneDeposit.scriptPubKey.IsPayToCryptoCondition(p) &&
p.IsValid() &&
p.evalCode == EVAL_RESERVE_DEPOSIT &&
p.vData.size() &&
(rd = CReserveDeposit(p.vData[0])).IsValid())
{
rd.reserveValues.valueMap[ASSETCHAINS_CHAINID] = oneDeposit.nValue;
totalReserveDeposits += rd.reserveValues;
}
}
UniValue ret(UniValue::VOBJ);
if (totalReserveDeposits.valueMap.size())
{
for (auto &oneBalance : totalReserveDeposits.valueMap)
{
ret.push_back(make_pair(EncodeDestination(CIdentityID(oneBalance.first)), ValueFromAmount(oneBalance.second)));
}
}
return ret;
}
UniValue getpendingtransfers(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() != 1)
{
throw runtime_error(
"getpendingtransfers \"chainname\"\n"
"\nReturns all pending transfers for a particular chain that have not yet been aggregated into an export\n"
"\nArguments\n"
"1. \"chainname\" (string, optional) name of the chain to look for. no parameter returns current chain in daemon.\n"
"\nResult:\n"
" {\n"
" }\n"
"\nExamples:\n"
+ HelpExampleCli("getpendingtransfers", "\"chainname\"")
+ HelpExampleRpc("getpendingtransfers", "\"chainname\"")
);
}
CheckPBaaSAPIsValid();
LOCK(cs_main);
uint160 chainID = GetChainIDFromParam(params[0]);
if (chainID.IsNull())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid chain name or chain ID");
}
CCurrencyDefinition chainDef;
int32_t defHeight;
if (GetCurrencyDefinition(chainID, chainDef, &defHeight))
{
// look for new exports
multimap<uint160, ChainTransferData> inputDescriptors;
if (GetUnspentChainTransfers(inputDescriptors, chainID))
{
UniValue ret(UniValue::VARR);
for (auto &desc : inputDescriptors)
{
UniValue oneExport(UniValue::VOBJ);
uint32_t inpHeight = std::get<0>(desc.second);
CInputDescriptor inpDesc = std::get<1>(desc.second);
oneExport.push_back(Pair("currencyid", EncodeDestination(CIdentityID(desc.first))));
oneExport.push_back(Pair("height", (int64_t)inpHeight));
oneExport.push_back(Pair("txid", inpDesc.txIn.prevout.hash.GetHex()));
oneExport.push_back(Pair("n", (int32_t)inpDesc.txIn.prevout.n));
oneExport.push_back(Pair("valueout", inpDesc.nValue));
oneExport.push_back(Pair("reservetransfer", std::get<2>(desc.second).ToUniValue()));
ret.push_back(oneExport);
}
if (ret.size())
{
return ret;
}
}
}
else
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Unrecognized currency name or ID");
}
return NullUniValue;
}
UniValue getexports(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() < 1 || params.size() > 3)
{
throw runtime_error(
"getexports \"chainname\" (heightstart) (heightend)\n"
"\nReturns pending export transfers to the specified currency from start height to end height if specified\n"
"\nArguments\n"
"\"chainname\" (string, required) name/ID of the currency to look for. no parameter returns current chain\n"
"\"heightstart\" (int, optional) default=0 only return exports at or above this height\n"
"\"heightend\" (int, optional) dedfault=maxheight only return exports below or at this height\n"
"\nResult:\n"
" [{\n"
" \"height\": n,"
" \"txid\": \"hexid\","
" \"txoutnum\": n,"
" \"partialtransactionproof\": \"hexstr\"," // proof's are relative to the heightend, if specified. if not, they are invalid
" \"transfers\": [{transfer1}, {transfer2},...]"
" }, ...]\n"
"\nExamples:\n"
+ HelpExampleCli("getexports", "\"chainname\" (heightstart) (heightend)")
+ HelpExampleRpc("getexports", "\"chainname\" (heightstart) (heightend)")
);
}
CheckPBaaSAPIsValid();
LOCK2(cs_main, mempool.cs);
uint160 currencyID;
CCurrencyDefinition curDef;
std::vector<std::pair<std::pair<CInputDescriptor, CPartialTransactionProof>, std::vector<CReserveTransfer>>> exports;
if ((currencyID = ValidateCurrencyName(uni_get_str(params[0]), true, &curDef)).IsNull())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid chain name or chain ID");
}
uint32_t fromHeight = 0, toHeight = INT32_MAX;
uint32_t proofHeight = 0;
uint32_t nHeight = chainActive.Height();
if (params.size() > 1)
{
fromHeight = uni_get_int64(params[1]);
}
if (params.size() > 2)
{
toHeight = uni_get_int64(params[2]);
proofHeight = toHeight != 0 && (toHeight < nHeight) ? toHeight : nHeight;
toHeight = proofHeight;
}
if ((curDef.IsGateway() && curDef.gatewayID == currencyID) ||
(curDef.systemID == currencyID))
{
ConnectedChains.GetSystemExports(currencyID, exports, fromHeight, toHeight, true);
}
else
{
ConnectedChains.GetCurrencyExports(currencyID, exports, fromHeight, toHeight);
}
UniValue retVal(UniValue::VARR);
for (auto &oneExport : exports)
{
UniValue oneObj(UniValue::VOBJ);
CTransaction tx;
uint256 blkHash;
if (!myGetTransaction(oneExport.first.first.txIn.prevout.hash, tx, blkHash) || blkHash.IsNull())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid export transaction");
}
auto indexIt = mapBlockIndex.find(blkHash);
if (indexIt == mapBlockIndex.end())
{
throw JSONRPCError(RPC_INTERNAL_ERROR, "transaction for export not found in main block index");
}
oneObj.push_back(Pair("height", indexIt->second->GetHeight()));
oneObj.push_back(Pair("txid", oneExport.first.first.txIn.prevout.hash.GetHex()));
oneObj.push_back(Pair("txoutnum", (int64_t)oneExport.first.first.txIn.prevout.n));
CCrossChainExport ccx(oneExport.first.first.scriptPubKey);
oneObj.push_back(Pair("exportinfo", ccx.ToUniValue()));
if (oneExport.first.second.IsValid())
{
oneObj.push_back(Pair("partialtransactionproof", oneExport.first.second.ToUniValue()));
}
UniValue transferArr(UniValue::VARR);
for (auto &oneTransfer : oneExport.second)
{
//printf("%s: onetransfer: %s\n", __func__, oneTransfer.ToUniValue().write(1,2).c_str());
transferArr.push_back(oneTransfer.ToUniValue());
}
oneObj.push_back(Pair("transfers", transferArr));
retVal.push_back(oneObj);
}
return retVal;
}
UniValue submitimports(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() != 1)
{
throw runtime_error(
"submitimports '{\"sourcesystemid\":\"systemid\", \"notarizationtxid\":\"txid\", \"notarizationtxoutnum\":n,\n"
"\"exports\":[{\"txid\":\"hexid\", \"txoutnum\":n, \"partialtransactionproof\":\"hexstr\", \n"
"\"transfers\": [{transfer1}, {transfer2},...]}, ...]}'\n\n"
"\nAccepts a set of exports from another system to post to the " + VERUS_CHAINNAME + " network.\n"
"\nArguments\n"
" {\n"
" \"sourcesystemid\":\"systemid\" ()\n"
" \"notarizationtxid\":\"txid\" ()\n"
" \"notarizationtxoutnum\":n ()\n"
" \"exports\": [{\n"
" \"height\": n,\n" // height on the other system of this export
" \"txid\": \"hexid\",\n" // export txid on the other system
" \"txoutnum\": n,\n" // export tx out num on the other system
" \"partialtransactionproof\": \"hexstr\",\n" // transaction proof, relative to the specified notarization
" \"transfers\": [{transfer1}, {transfer2},...]\n" // all reserve transfers for this export
" }, ...]\n"
" }\n"
"\nResult:\n"
" [{\n" // list of transactions and the specific cross chain outputs created
" \"currency\": \"currencyid\"\n" // destination currency
" \"txid\": \"hexid\",\n" // import txid
" \"txoutnum\": n\n" // txoutnum on transaction
" }, ...]\n"
"\nExamples:\n"
+ HelpExampleCli("submitimports", "{\"sourcesystemid\":\"systemid\", \"notarizationtxid\":\"txid\", \"notarizationtxoutnum\":n, \"exports\":[{\"height\":n, \"txid\":\"hexid\", \"txoutnum\":n, \"partialtransactionproof\":\"hexstr\", \"transfers\": [{transfer1}, {transfer2},...]}, ...]}")
+ HelpExampleRpc("submitimports", "{\"sourcesystemid\":\"systemid\", \"notarizationtxid\":\"txid\", \"notarizationtxoutnum\":n, \"exports\":[{\"height\":n, \"txid\":\"hexid\", \"txoutnum\":n, \"partialtransactionproof\":\"hexstr\", \"transfers\": [{transfer1}, {transfer2},...]}, ...]}")
);
}
CheckPBaaSAPIsValid();
if (!params[0].isObject())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameters. see help.");
}
LOCK2(cs_main, mempool.cs);
CCurrencyDefinition curDef;
uint160 sourceSystemID = ValidateCurrencyName(uni_get_str(find_value(params[0], "sourcesystemid")), true, &curDef);
sourceSystemID = curDef.IsGateway() ? curDef.gatewayID : curDef.systemID;
// source system must be a different system and an actual system
if (sourceSystemID.IsNull() ||
curDef.GetID() != sourceSystemID ||
sourceSystemID == ASSETCHAINS_CHAINID)
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid chain name or chain ID");
}
uint256 notarizationTxId = uint256S(uni_get_str(find_value(params[0], "notarizationtxid")));
int notarizationTxOutNum = uni_get_int(find_value(params[0], "notarizationtxoutnum"));
CTransaction notarizationTx;
uint256 blkHash;
COptCCParams p;
CPBaaSNotarization lastConfirmed;
if (!(!notarizationTxId.IsNull() &&
myGetTransaction(notarizationTxId, notarizationTx, blkHash) &&
!blkHash.IsNull() &&
notarizationTxOutNum >= 0 &&
notarizationTx.vout.size() > notarizationTxOutNum &&
notarizationTx.vout[notarizationTxOutNum].scriptPubKey.IsPayToCryptoCondition(p) &&
p.IsValid() &&
(p.evalCode == EVAL_EARNEDNOTARIZATION || p.evalCode == EVAL_ACCEPTEDNOTARIZATION) &&
p.vData.size() &&
(lastConfirmed = CPBaaSNotarization(p.vData[0])).IsValid()))
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid notarization transaction id or transaction");
}
std::vector<std::pair<std::pair<CInputDescriptor, CPartialTransactionProof>, std::vector<CReserveTransfer>>> exports;
UniValue exportsUni = find_value(params[0], "exports");
if (!exportsUni.isArray() ||
!exportsUni.size())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "parameters must include valid exports to import");
}
for (int i = 0; i < exportsUni.size(); i++)
{
// create one import at a time
uint256 exportTxId = uint256S(uni_get_str(find_value(exportsUni[i], "txid")));
int32_t exportTxOutNum = uni_get_int(find_value(exportsUni[i], "txoutnum"));
CPartialTransactionProof txProof = CPartialTransactionProof(find_value(exportsUni[i], "partialtransactionproof"));
UniValue transferArrUni = find_value(exportsUni[i], "transfers");
if (exportTxId.IsNull() ||
exportTxOutNum == -1 ||
!transferArrUni.isArray())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid export from " + uni_get_str(params[0]));
}
CTransaction exportTx;
uint256 blkHash;
auto proofRootIt = lastConfirmed.proofRoots.find(sourceSystemID);
if (!(txProof.IsValid() &&
!txProof.GetPartialTransaction(exportTx).IsNull() &&
exportTxId == txProof.TransactionHash() &&
proofRootIt != lastConfirmed.proofRoots.end() &&
proofRootIt->second.stateRoot == txProof.CheckPartialTransaction(exportTx) &&
exportTx.vout.size() > exportTxOutNum))
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid export 1 from " + uni_get_str(params[0]));
}
std::pair<std::pair<CInputDescriptor, CPartialTransactionProof>, std::vector<CReserveTransfer>> oneExport =
std::make_pair(std::make_pair(CInputDescriptor(exportTx.vout[exportTxOutNum].scriptPubKey,
exportTx.vout[exportTxOutNum].nValue,
CTxIn(exportTxId, exportTxOutNum)),
txProof),
std::vector<CReserveTransfer>());
for (int j = 0; j < transferArrUni.size(); j++)
{
oneExport.second.push_back(CReserveTransfer(transferArrUni[j]));
if (!oneExport.second.back().IsValid())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid reserve transfers from export of " + uni_get_str(params[0]));
}
}
exports.push_back(oneExport);
}
std::map<uint160, std::vector<std::pair<int, CTransaction>>> newImports;
ConnectedChains.CreateLatestImports(curDef, CUTXORef(notarizationTxId, notarizationTxOutNum), exports, newImports);
UniValue retVal(UniValue::VARR);
for (auto &oneExportCurrency : newImports)
{
for (auto &oneExport : oneExportCurrency.second)
{
retVal.push_back(Pair("currencyid", EncodeDestination(CIdentityID(oneExportCurrency.first))));
retVal.push_back(Pair("txid", oneExport.second.GetHash().GetHex()));
retVal.push_back(Pair("txoutnum", oneExport.first));
}
}
return retVal;
}
UniValue getlastimportfrom(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() != 1)
{
throw runtime_error(
"getlastimportfrom \"systemname\"\n"
"\nReturns the last import from a specific originating system.\n"
"\nArguments\n"
"1. \"systemname\" (string, optional) name or ID of the system to retrieve the last import from\n"
"\nResult:\n"
" {\n"
" \"lastimport\" : (object) last import from the indicated system on this chain\n"
" {\n"
" }\n"
" \"lastconfirmednotarization\" : (object) last confirmed notarization of the indicated system on this chain\n"
" {\n"
" }\n"
" }\n"
"\nExamples:\n"
+ HelpExampleCli("getlastimportfrom", "\"systemname\"")
+ HelpExampleRpc("getlastimportfrom", "\"systemname\"")
);
}
CheckPBaaSAPIsValid();
LOCK(cs_main);
uint160 chainID;
CCurrencyDefinition chainDef;
int32_t defHeight;
if ((chainID = ValidateCurrencyName(uni_get_str(params[0]), true, &chainDef)).IsNull())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid chain name or chain ID");
}
if ((chainDef.IsToken() && !chainDef.IsGateway()) || chainID == ASSETCHAINS_CHAINID)
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "getlastimportfrom retrieves the last import from an external, not local system");
}
CChainNotarizationData cnd;
if (!GetNotarizationData(chainID, cnd) || !cnd.IsConfirmed())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot locate confirmed notarization data for system or chain");
}
std::vector<CAddressUnspentDbEntry> unspentOutputs;
CCrossChainImport lastCCI;
bool found = false;
CAddressUnspentDbEntry foundEntry;
if (GetAddressUnspent(CKeyID(CCrossChainRPCData::GetConditionID(chainID, CCrossChainImport::CurrencySystemImportKey())), CScript::P2IDX, unspentOutputs) &&
unspentOutputs.size())
{
for (auto &txidx : unspentOutputs)
{
COptCCParams p;
if (txidx.second.script.IsPayToCryptoCondition(p) &&
p.IsValid() &&
p.evalCode == EVAL_CROSSCHAIN_IMPORT &&
p.vData.size() &&
(lastCCI = CCrossChainImport(p.vData[0])).IsValid())
{
found = true;
foundEntry = txidx;
break;
}
}
}
else
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "No import thread found for currency");
}
UniValue retVal(UniValue::VOBJ);
if (found)
{
retVal.pushKV("lastimport", lastCCI.ToUniValue());
retVal.pushKV("lastimportutxo", CUTXORef(foundEntry.first.txhash, foundEntry.first.index).ToUniValue());
}
retVal.pushKV("lastconfirmednotarization", cnd.vtx[cnd.lastConfirmed].second.ToUniValue());
retVal.pushKV("lastconfirmedutxo", cnd.vtx[cnd.lastConfirmed].first.ToUniValue());
return retVal;
}
UniValue getimports(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() != 1)
{
throw runtime_error(
"getimports \"chainname\" (startheight) (endheight)\n"
"\nReturns all imports into a specific currency, optionally that were imported between a specific block range.\n"
"\nArguments\n"
"1. \"chainname\" (string, optional) name of the chain to look for. no parameter returns current chain in daemon.\n"
"\nResult:\n"
" {\n"
" }\n"
"\nExamples:\n"
+ HelpExampleCli("getimports", "\"chainname\"")
+ HelpExampleRpc("getimports", "\"chainname\"")
);
}
CheckPBaaSAPIsValid();
LOCK(cs_main);
uint160 chainID;
CCurrencyDefinition chainDef;
int32_t defHeight;
if ((chainID = ValidateCurrencyName(uni_get_str(params[0]), true, &chainDef)).IsNull())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid chain name or chain ID");
}
uint32_t fromHeight = 0, toHeight = INT32_MAX;
uint32_t proofHeight = 0;
uint32_t nHeight = chainActive.Height();
if (params.size() > 1)
{
fromHeight = uni_get_int64(params[1]);
}
if (params.size() > 2)
{
toHeight = uni_get_int64(params[2]);
proofHeight = toHeight < nHeight ? toHeight : nHeight;
toHeight = proofHeight;
}
if (GetCurrencyDefinition(chainID, chainDef, &defHeight))
{
// which transaction are we in this block?
std::vector<std::pair<CAddressIndexKey, CAmount>> addressIndex;
uint160 searchKey = CCrossChainRPCData::GetConditionID(chainID, CCrossChainImport::CurrencyImportKey());
CBlockIndex *pIndex;
CChainNotarizationData cnd;
// get all import transactions including and since this one up to the confirmed height
if (GetAddressIndex(searchKey, CScript::P2IDX, addressIndex, fromHeight, toHeight))
{
UniValue ret(UniValue::VARR);
for (auto &idx : addressIndex)
{
uint256 blkHash;
CTransaction importTx;
if (!idx.first.spending && myGetTransaction(idx.first.txhash, importTx, blkHash))
{
CCrossChainExport ccx;
CCrossChainImport cci;
int32_t sysCCIOut;
CPBaaSNotarization importNotarization;
int32_t importNotOut;
int32_t evidenceOutStart, evidenceOutEnd;
std::vector<CReserveTransfer> reserveTransfers;
uint32_t importHeight = 0;
auto importBlockIdxIt = mapBlockIndex.find(blkHash);
if (importBlockIdxIt != mapBlockIndex.end() && chainActive.Contains(importBlockIdxIt->second))
{
importHeight = importBlockIdxIt->second->GetHeight();
}
else
{
continue;
}
/* UniValue scrOut(UniValue::VOBJ);
ScriptPubKeyToUniv(importTx.vout[idx.first.index].scriptPubKey, scrOut, false);
printf("%s: scriptOut: %s\n", __func__, scrOut.write(1,2).c_str()); */
CCrossChainImport sysCCI;
if ((cci = CCrossChainImport(importTx.vout[idx.first.index].scriptPubKey)).IsValid() &&
cci.GetImportInfo(importTx, importHeight, idx.first.index, ccx, sysCCI, sysCCIOut, importNotarization, importNotOut, evidenceOutStart, evidenceOutEnd, reserveTransfers))
{
UniValue oneImportUni(UniValue::VOBJ);
oneImportUni.push_back(Pair("importheight", (int64_t)importHeight));
oneImportUni.push_back(Pair("importtxid", idx.first.txhash.GetHex()));
oneImportUni.push_back(Pair("importvout", (int64_t)idx.first.index));
oneImportUni.push_back(Pair("import", cci.ToUniValue()));
if (sysCCIOut != -1)
{
oneImportUni.push_back(Pair("sysimport", sysCCI.ToUniValue()));
}
oneImportUni.push_back(Pair("importnotarization", importNotarization.ToUniValue()));
UniValue transferArr(UniValue::VARR);
for (auto &oneTransfer : reserveTransfers)
{
transferArr.push_back(oneTransfer.ToUniValue());
}
oneImportUni.push_back(Pair("transfers", transferArr));
ret.push_back(oneImportUni);
}
}
}
if (ret.size())
{
return ret;
}
}
}
else
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Unrecognized currency name or ID");
}
return NullUniValue;
}
UniValue listcurrencies(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() > 3)
{
throw runtime_error(
"listcurrencies ({query object}) startblock endblock\n"
"\nReturns a complete definition for any given chain if it is registered on the blockchain. If the chain requested\n"
"\nis NULL, chain definition of the current chain is returned.\n"
"\nArguments\n"
"{ (json, optional) specify valid query conditions\n"
" \"launchstate\" : (\"prelaunch\" | \"launched\" | \"refund\" | \"complete\") (optional) return only currencies in that state\n"
" \"systemtype\" : (\"local\" | \"gateway\" | \"pbaas\")\n"
" \"converter\": bool (bool, optional) default false, only return fractional currency converters\n"
"}\n"
"\nResult:\n"
"[\n"
" {\n"
" \"version\" : n, (int) version of this chain definition\n"
" \"name\" : \"string\", (string) name or symbol of the chain, same as passed\n"
" \"address\" : \"string\", (string) cryptocurrency address to send fee and non-converted premine\n"
" \"currencyid\" : \"hex-string\", (string) i-address that represents the chain ID, same as the ID that launched the chain\n"
" \"premine\" : n, (int) amount of currency paid out to the premine address in block #1, may be smart distribution\n"
" \"convertible\" : \"xxxx\" (bool) if this currency is a fractional reserve currency of Verus\n"
" \"startblock\" : n, (int) block # on this chain, which must be notarized into block one of the chain\n"
" \"endblock\" : n, (int) block # after which, this chain's useful life is considered to be over\n"
" \"eras\" : \"[obj, ...]\", (objarray) different chain phases of rewards and convertibility\n"
" {\n"
" \"reward\" : \"[n, ...]\", (int) reward start for each era in native coin\n"
" \"decay\" : \"[n, ...]\", (int) exponential or linear decay of rewards during each era\n"
" \"halving\" : \"[n, ...]\", (int) blocks between halvings during each era\n"
" \"eraend\" : \"[n, ...]\", (int) block marking the end of each era\n"
" \"eraoptions\" : \"[n, ...]\", (int) options (reserved)\n"
" }\n"
" \"nodes\" : \"[obj, ..]\", (objectarray, optional) up to 2 nodes that can be used to connect to the blockchain"
" [{\n"
" \"nodeaddress\" : \"txid\", (string, optional) internet, TOR, or other supported address for node\n"
" \"paymentaddress\" : n, (int, optional) rewards payment address\n"
" }, .. ]\n"
" }, ...\n"
"]\n"
"\nExamples:\n"
+ HelpExampleCli("listcurrencies", "true")
+ HelpExampleRpc("listcurrencies", "true")
);
}
CheckPBaaSAPIsValid();
UniValue ret(UniValue::VARR);
uint160 querySystem;
static std::map<std::string, CCurrencyDefinition::EQueryOptions> launchStates(
{{"prelaunch", CCurrencyDefinition::QUERY_LAUNCHSTATE_PRELAUNCH},
{"launched", CCurrencyDefinition::QUERY_LAUNCHSTATE_CONFIRM},
{"refund", CCurrencyDefinition::QUERY_LAUNCHSTATE_REFUND},
{"complete", CCurrencyDefinition::QUERY_LAUNCHSTATE_COMPLETE}}
);
static std::map<std::string, CCurrencyDefinition::EQueryOptions> systemTypes(
{{"local", CCurrencyDefinition::QUERY_SYSTEMTYPE_LOCAL},
{"gateway", CCurrencyDefinition::QUERY_SYSTEMTYPE_GATEWAY},
{"pbaas", CCurrencyDefinition::QUERY_SYSTEMTYPE_PBAAS}}
);
CCurrencyDefinition::EQueryOptions launchStateQuery = CCurrencyDefinition::QUERY_NULL;
CCurrencyDefinition::EQueryOptions systemTypeQuery = CCurrencyDefinition::QUERY_NULL;
bool isConverter = false;
uint32_t startBlock = 0;
uint32_t endBlock = 0;
if (params.size())
{
if (!params[0].isObject())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid query object parameter");
}
int numKeys = params[0].getKeys().size();
std::string launchState = uni_get_str(find_value(params[0], "launchstate"));
std::string systemType = uni_get_str(find_value(params[0], "systemtype"));
UniValue isConverterUni = find_value(params[0], "converter");
if (!isConverterUni.isNull())
{
numKeys--;
}
isConverter = uni_get_bool(isConverterUni);
if (launchStates.count(launchState))
{
launchStateQuery = launchStates[launchState];
numKeys--;
}
if (systemTypes.count(systemType))
{
systemTypeQuery = systemTypes[systemType];
numKeys--;
}
if (numKeys)
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid query object parameter, use \"listidentities help\"");
}
if (params.size() > 1)
{
startBlock = uni_get_int64(params[1]);
}
if (params.size() > 2)
{
endBlock = uni_get_int64(params[2]);
}
}
std::vector<std::pair<std::pair<CUTXORef, std::vector<CNodeData>>, CCurrencyDefinition>> chains;
{
LOCK2(cs_main, mempool.cs);
GetCurrencyDefinitions(chains, launchStateQuery, systemTypeQuery, isConverter, startBlock, endBlock);
}
for (auto oneDef : chains)
{
LOCK2(cs_main, mempool.cs);
CCurrencyDefinition &def = oneDef.second;
UniValue oneChain(UniValue::VOBJ);
UniValue oneDefUni = def.ToUniValue();
if (oneDef.first.first.IsValid())
{
ret.push_back(Pair("definitiontxid", oneDef.first.first.hash.GetHex()));
ret.push_back(Pair("definitiontxout", (int)oneDef.first.first.n));
}
if (oneDef.first.second.size())
{
UniValue nodesUni(UniValue::VARR);
for (auto node : oneDef.first.second)
{
nodesUni.push_back(node.ToUniValue());
}
oneDefUni.push_back(Pair("nodes", nodesUni));
}
oneChain.push_back(Pair("currencydefinition", oneDefUni));
CChainNotarizationData cnd;
GetNotarizationData(def.GetID(), cnd);
int32_t confirmedHeight = -1, bestHeight = -1;
confirmedHeight = cnd.vtx.size() && cnd.lastConfirmed != -1 ? cnd.vtx[cnd.lastConfirmed].second.notarizationHeight : -1;
bestHeight = cnd.vtx.size() && cnd.bestChain != -1 ? cnd.vtx[cnd.forks[cnd.bestChain].back()].second.notarizationHeight : -1;
if (!def.IsToken())
{
oneChain.push_back(Pair("lastconfirmedheight", confirmedHeight == -1 ? 0 : confirmedHeight));
if (confirmedHeight != -1)
{
oneChain.push_back(Pair("lastconfirmedtxid", cnd.vtx[cnd.lastConfirmed].first.hash.GetHex().c_str()));
oneChain.push_back(Pair("lastconfirmedtxout", (uint64_t)cnd.vtx[cnd.lastConfirmed].first.n));
oneChain.push_back(Pair("lastconfirmednotarization", cnd.vtx[cnd.lastConfirmed].second.ToUniValue()));
}
}
oneChain.push_back(Pair("bestheight", bestHeight == -1 ? 0 : bestHeight));
if (bestHeight != -1)
{
oneChain.push_back(Pair("besttxid", cnd.vtx[cnd.forks[cnd.bestChain].back()].first.hash.GetHex().c_str()));
oneChain.push_back(Pair("besttxout", (uint64_t)cnd.vtx[cnd.forks[cnd.bestChain].back()].first.n));
oneChain.push_back(Pair("bestcurrencystate", cnd.vtx[cnd.forks[cnd.bestChain].back()].second.currencyState.ToUniValue()));
}
ret.push_back(oneChain);
}
return ret;
}
// returns all chain transfer outputs, both spent and unspent between a specific start and end block with an optional chainFilter. if the chainFilter is not
// NULL, only transfers to that system are returned
bool GetChainTransfers(multimap<uint160, pair<CInputDescriptor, CReserveTransfer>> &inputDescriptors, uint160 chainFilter, int start, int end, uint32_t flags)
{
if (!flags)
{
flags = CReserveTransfer::VALID;
}
bool nofilter = chainFilter.IsNull();
// which transaction are we in this block?
std::vector<std::pair<CAddressIndexKey, CAmount>> addressIndex;
LOCK2(cs_main, mempool.cs);
if (!GetAddressIndex(CReserveTransfer::ReserveTransferKey(),
CScript::P2IDX,
addressIndex,
start,
end))
{
return false;
}
else
{
for (auto it = addressIndex.begin(); it != addressIndex.end(); it++)
{
CTransaction ntx;
uint256 blkHash;
if (it->first.spending)
{
continue;
}
if (myGetTransaction(it->first.txhash, ntx, blkHash))
{
COptCCParams p, m;
CReserveTransfer rt;
if (ntx.vout[it->first.index].scriptPubKey.IsPayToCryptoCondition(p) &&
p.evalCode == EVAL_RESERVE_TRANSFER &&
p.vData.size() > 1 && (rt = CReserveTransfer(p.vData[0])).IsValid() &&
(m = COptCCParams(p.vData[1])).IsValid() &&
(nofilter || ((rt.flags & rt.IMPORT_TO_SOURCE) ? rt.FirstCurrency() : rt.destCurrencyID) == chainFilter) &&
(rt.flags & flags) == flags)
{
inputDescriptors.insert(make_pair(((rt.flags & rt.IMPORT_TO_SOURCE) ? rt.FirstCurrency() : rt.destCurrencyID),
make_pair(CInputDescriptor(ntx.vout[it->first.index].scriptPubKey, ntx.vout[it->first.index].nValue, CTxIn(COutPoint(it->first.txhash, it->first.index))),
rt)));
}
/*
uint256 hashBlk;
UniValue univTx(UniValue::VOBJ);
TxToUniv(ntx, hashBlk, univTx);
printf("tx: %s\n", univTx.write(1,2).c_str());
*/
}
else
{
LogPrintf("%s: cannot retrieve transaction %s\n", __func__, it->first.txhash.GetHex().c_str());
printf("%s: cannot retrieve transaction %s\n", __func__, it->first.txhash.GetHex().c_str());
return false;
}
}
return true;
}
}
// returns all unspent chain transfer outputs with an optional chainFilter. if the chainFilter is not
// NULL, only transfers to that chain are returned
bool GetUnspentChainTransfers(std::multimap<uint160, ChainTransferData> &inputDescriptors, uint160 chainFilter)
{
bool nofilter = chainFilter.IsNull();
std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue> > unspentOutputs;
LOCK(cs_main);
if (!GetAddressUnspent(CReserveTransfer::ReserveTransferKey(), CScript::P2IDX, unspentOutputs))
{
return false;
}
else
{
CCoinsViewCache view(pcoinsTip);
for (auto it = unspentOutputs.begin(); it != unspentOutputs.end(); it++)
{
CCoins coins;
if (view.GetCoins(it->first.txhash, coins))
{
if (coins.IsAvailable(it->first.index))
{
// if this is a transfer output, optionally to this chain, add it to the input vector
// chain filter was applied in index search
COptCCParams p;
COptCCParams m;
CReserveTransfer rt;
uint160 destCID;
if (coins.vout[it->first.index].scriptPubKey.IsPayToCryptoCondition(p) &&
p.evalCode == EVAL_RESERVE_TRANSFER &&
p.vData.size() &&
p.version >= p.VERSION_V3 &&
(m = COptCCParams(p.vData.back())).IsValid() &&
(rt = CReserveTransfer(p.vData[0])).IsValid() &&
!(destCID = ((rt.flags & rt.IMPORT_TO_SOURCE) ? rt.FirstCurrency() : rt.destCurrencyID)).IsNull() &&
(nofilter || destCID == chainFilter))
{
inputDescriptors.insert(make_pair(destCID,
ChainTransferData(coins.nHeight,
CInputDescriptor(coins.vout[it->first.index].scriptPubKey,
coins.vout[it->first.index].nValue,
CTxIn(COutPoint(it->first.txhash, it->first.index))),
rt)));
}
}
}
else
{
printf("%s: cannot retrieve transaction %s\n", __func__, it->first.txhash.GetHex().c_str());
}
}
return true;
}
}
bool GetNotarizationData(const uint160 &currencyID, CChainNotarizationData &notarizationData, vector<pair<CTransaction, uint256>> *optionalTxOut)
{
notarizationData = CChainNotarizationData(std::vector<std::pair<CUTXORef, CPBaaSNotarization>>());
std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue>> unspentFinalizations;
CCurrencyDefinition chainDef = ConnectedChains.GetCachedCurrency(currencyID);
if (!chainDef.IsValid())
{
LogPrintf("Cannot retrieve currency %s, may need to reindex\n", EncodeDestination(CIdentityID(currencyID)).c_str());
printf("Cannot retrieve currency %s, may need to reindex\n", EncodeDestination(CIdentityID(currencyID)).c_str());
return false;
}
// if we are being asked for a notarization of the current chain, we make one
if (currencyID == ASSETCHAINS_CHAINID)
{
CIdentityID proposer = VERUS_NOTARYID.IsNull() ? (VERUS_DEFAULTID.IsNull() ? VERUS_NODEID : VERUS_DEFAULTID) : VERUS_NOTARYID;
uint32_t height = chainActive.Height();
std::map<uint160, CProofRoot> proofRoots;
proofRoots[ASSETCHAINS_CHAINID] = CProofRoot::GetProofRoot(height);
//printf("%s: returning proof root: %s\n", __func__, proofRoots[ASSETCHAINS_CHAINID].ToUniValue().write(1,2).c_str());
CPBaaSNotarization bestNotarization(currencyID,
ConnectedChains.GetCurrencyState(height),
height,
CUTXORef(),
0,
std::vector<CNodeData>(),
std::map<uint160, CCoinbaseCurrencyState>(),
DestinationToTransferDestination(proposer),
proofRoots,
CPBaaSNotarization::VERSION_CURRENT,
CPBaaSNotarization::FLAG_LAUNCH_CONFIRMED);
notarizationData.vtx.push_back(std::make_pair(CUTXORef(), bestNotarization));
notarizationData.lastConfirmed = 0;
notarizationData.forks.push_back(std::vector<int>({0}));
notarizationData.bestChain = 0;
return true;
}
// look for unspent, confirmed finalizations first
uint160 finalizeNotarizationKey = CCrossChainRPCData::GetConditionID(currencyID, CObjectFinalization::ObjectFinalizationNotarizationKey());
uint160 confirmedNotarizationKey = CCrossChainRPCData::GetConditionID(finalizeNotarizationKey, CObjectFinalization::ObjectFinalizationConfirmedKey());
if (GetAddressUnspent(confirmedNotarizationKey, CScript::P2IDX, unspentFinalizations) &&
unspentFinalizations.size())
{
// get the latest, confirmed notarization
auto bestIt = unspentFinalizations.begin();
for (auto oneIt = bestIt; oneIt != unspentFinalizations.end(); oneIt++)
{
if (oneIt->second.blockHeight > bestIt->second.blockHeight)
{
bestIt = oneIt;
}
}
CTransaction nTx;
uint256 blkHash;
COptCCParams p;
if (!bestIt->second.script.IsPayToCryptoCondition(p) ||
!p.IsValid() ||
!(p.evalCode == EVAL_FINALIZE_NOTARIZATION || p.evalCode == EVAL_EARNEDNOTARIZATION || p.evalCode == EVAL_ACCEPTEDNOTARIZATION) ||
!p.vData.size())
{
LogPrintf("Invalid finalization or notarization on transaction %s, output %ld may need to reindex\n", bestIt->first.txhash.GetHex().c_str(), bestIt->first.index);
printf("Invalid finalization or notarization on transaction %s, output %ld may need to reindex\n", bestIt->first.txhash.GetHex().c_str(), bestIt->first.index);
return false;
}
CUTXORef txInfo(bestIt->first.txhash, bestIt->first.index);
// if this is actually a finalization, get the notarization it is for
if (p.evalCode == EVAL_FINALIZE_NOTARIZATION)
{
CObjectFinalization finalization(p.vData[0]);
if (!finalization.output.hash.IsNull())
{
txInfo.hash = finalization.output.hash;
}
txInfo.n = finalization.output.n;
if (myGetTransaction(txInfo.hash, nTx, blkHash) && nTx.vout.size() > txInfo.n)
{
CPBaaSNotarization thisNotarization(nTx.vout[txInfo.n].scriptPubKey);
if (!thisNotarization.IsValid())
{
LogPrintf("Invalid notarization on transaction %s, output %u may need to reindex\n", txInfo.hash.GetHex().c_str(), txInfo.n);
printf("Invalid finalization on transaction %s, output %u may need to reindex\n", txInfo.hash.GetHex().c_str(), txInfo.n);
return false;
}
notarizationData.vtx.push_back(std::make_pair(txInfo, thisNotarization));
notarizationData.forks = std::vector<std::vector<int>>({{0}});
notarizationData.bestChain = 0;
notarizationData.lastConfirmed = 0;
if (optionalTxOut)
{
optionalTxOut->push_back(make_pair(nTx, blkHash));
}
}
}
else
{
// straightforward, get the notarization and return
CPBaaSNotarization thisNotarization(p.vData[0]);
if (!thisNotarization.IsValid())
{
LogPrintf("Invalid notarization on index entry for %s, output %u may need to reindex\n", txInfo.hash.GetHex().c_str(), txInfo.n);
printf("Invalid finalization on index entry for %s, output %u may need to reindex\n", txInfo.hash.GetHex().c_str(), txInfo.n);
return false;
}
notarizationData.vtx.push_back(std::make_pair(txInfo, thisNotarization));
notarizationData.forks = std::vector<std::vector<int>>({{0}});
notarizationData.bestChain = 0;
notarizationData.lastConfirmed = 0;
if (optionalTxOut)
{
if (myGetTransaction(txInfo.hash, nTx, blkHash) && nTx.vout.size() > txInfo.n)
{
CPBaaSNotarization thisNotarization(nTx.vout[txInfo.n].scriptPubKey);
if (!thisNotarization.IsValid())
{
LogPrintf("Invalid notarization on transaction %s, output %u\n", txInfo.hash.GetHex().c_str(), txInfo.n);
printf("Invalid finalization on transaction %s, output %u\n", txInfo.hash.GetHex().c_str(), txInfo.n);
return false;
}
optionalTxOut->push_back(make_pair(nTx, blkHash));
}
else
{
LogPrintf("Cannot retrieve transaction %s, may need to reindex\n", txInfo.hash.GetHex().c_str());
printf("Cannot retrieve transaction %s, may need to reindex\n", txInfo.hash.GetHex().c_str());
return false;
}
}
// if this is a token, we're done, otherwise, get pending below as well
if (chainDef.IsToken())
{
return true;
}
}
}
if (!notarizationData.vtx.size())
{
LogPrintf("%s: failure to find confirmed notarization starting point\n", __func__);
return false;
}
std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue>> pendingFinalizations;
// now, add all pending notarizations, if present, and sort them out
if (GetAddressUnspent(CCrossChainRPCData::GetConditionID(finalizeNotarizationKey, CObjectFinalization::ObjectFinalizationPendingKey()),
CScript::P2IDX,
pendingFinalizations) &&
pendingFinalizations.size())
{
uint160 pendingNotarizationKey = CCrossChainRPCData::GetConditionID(finalizeNotarizationKey, CObjectFinalization::ObjectFinalizationPendingKey());
// all pending finalizations must be later than the last confirmed transaction and
// refer to a previous valid / confirmable, not necessarily confirmed, notarization
multimap<uint32_t, pair<CUTXORef, CPBaaSNotarization>> sorted;
multimap<uint32_t, pair<CTransaction, uint256>> sortedTxs;
std::multimap<CUTXORef, std::pair<CUTXORef, CPBaaSNotarization>> notarizationReferences;
std::map<CUTXORef, std::pair<CTransaction, uint256>> referencedTxes;
CTransaction nTx;
uint256 blkHash;
COptCCParams p;
CObjectFinalization f;
CPBaaSNotarization n;
BlockMap::iterator blockIt;
for (auto it = pendingFinalizations.begin(); it != pendingFinalizations.end(); it++)
{
if (!(it->second.script.IsPayToCryptoCondition(p) &&
p.IsValid() &&
p.evalCode == EVAL_FINALIZE_NOTARIZATION &&
p.vData.size() &&
(f = CObjectFinalization(p.vData[0])).IsValid() &&
myGetTransaction(f.output.hash.IsNull() ? it->first.txhash : f.output.hash, nTx, blkHash) &&
nTx.vout.size() > f.output.n &&
nTx.vout[f.output.n].scriptPubKey.IsPayToCryptoCondition(p) &&
p.IsValid() &&
(p.evalCode == EVAL_ACCEPTEDNOTARIZATION || p.evalCode == EVAL_EARNEDNOTARIZATION) &&
p.vData.size() &&
(n = CPBaaSNotarization(p.vData[0])).IsValid() &&
!blkHash.IsNull() &&
(blockIt = mapBlockIndex.find(blkHash)) != mapBlockIndex.end() &&
chainActive.Contains(blockIt->second)))
{
LogPrintf("%s: invalid, indexed finalization on transaction %s, output %d\n", __func__, it->first.txhash.GetHex().c_str(), (int)it->first.index);
continue;
}
// if finalization is on same transaction as notarization, set it
if (f.output.hash.IsNull())
{
f.output.hash = it->first.txhash;
}
notarizationReferences.insert(std::make_pair(n.prevNotarization, std::make_pair(f.output, n)));
if (optionalTxOut)
{
referencedTxes.insert(std::make_pair(f.output, std::make_pair(nTx, blkHash)));
}
}
// now that we have all pending notarizations (not finalizations) in a sorted list, keep all those which
// directly or indirectly refer to the last confirmed notarization
// all others should be pruned
bool somethingAdded = true;
while (somethingAdded && notarizationReferences.size())
{
somethingAdded = false;
int numForks = notarizationData.forks.size();
int forkNum;
for (forkNum = 0; forkNum < numForks; forkNum++)
{
CUTXORef searchRef = notarizationData.vtx[notarizationData.forks[forkNum].back()].first;
std::multimap<CUTXORef, std::pair<CUTXORef, CPBaaSNotarization>>::iterator pendingIt;
bool newFork = false;
for (pendingIt = notarizationReferences.lower_bound(searchRef);
pendingIt != notarizationReferences.end() && pendingIt->first == searchRef;
pendingIt++)
{
notarizationData.vtx.push_back(pendingIt->second);
if (optionalTxOut)
{
optionalTxOut->push_back(referencedTxes[pendingIt->second.first]);
}
if (newFork)
{
notarizationData.forks.push_back(notarizationData.forks[forkNum]);
notarizationData.forks.back().back() = notarizationData.vtx.size() - 1;
}
else
{
notarizationData.forks[forkNum].push_back(notarizationData.vtx.size() - 1);
newFork = true;
somethingAdded = true;
}
}
notarizationReferences.erase(searchRef);
}
}
// now, we should have all forks in vectors
// they should all have roots that point to the same confirmed or initial notarization, which should be enforced by chain rules
// the best chain should simply be the tip with most power
notarizationData.bestChain = 0;
CChainPower best;
for (int i = 0; i < notarizationData.forks.size(); i++)
{
if (notarizationData.vtx[notarizationData.forks[i].back()].second.proofRoots.count(currencyID))
{
CChainPower curPower =
CChainPower::ExpandCompactPower(notarizationData.vtx[notarizationData.forks[i].back()].second.proofRoots[currencyID].compactPower, i);
if (curPower > best)
{
best = curPower;
notarizationData.bestChain = i;
}
}
else
{
printf("%s: invalid notarization expecting proofroot for %s:\n%s\n",
__func__,
EncodeDestination(CIdentityID(currencyID)).c_str(),
notarizationData.vtx[notarizationData.forks[i].back()].second.ToUniValue().write(1,2).c_str());
LogPrintf("%s: invalid notarization on transaction %s, output %u\n", __func__,
notarizationData.vtx[notarizationData.forks[i].back()].first.hash.GetHex().c_str(),
notarizationData.vtx[notarizationData.forks[i].back()].first.n);
//assert(false);
}
}
}
return notarizationData.vtx.size() != 0;
}
UniValue getnotarizationdata(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() != 1)
{
throw runtime_error(
"getnotarizationdata \"currencyid\"\n"
"\nReturns the latest PBaaS notarization data for the specifed currencyid.\n"
"\nArguments\n"
"1. \"currencyid\" (string, required) the hex-encoded ID or string name search for notarizations on\n"
"\nResult:\n"
"{\n"
" \"version\" : n, (numeric) The notarization protocol version\n"
"}\n"
"\nExamples:\n"
+ HelpExampleCli("getnotarizationdata", "\"currencyid\"")
+ HelpExampleRpc("getnotarizationdata", "\"currencyid\"")
);
}
CheckPBaaSAPIsValid();
uint160 chainID;
CChainNotarizationData nData;
LOCK2(cs_main, mempool.cs);
chainID = GetChainIDFromParam(params[0]);
if (chainID.IsNull())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid currencyid");
}
if (GetNotarizationData(chainID, nData))
{
return nData.ToUniValue();
}
else
{
return NullUniValue;
}
}
UniValue getlaunchinfo(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() < 1 || params.size() > 2)
{
throw runtime_error(
"getlaunchinfo \"currencyid\"\n"
"\nReturns the launch notarization data and partial transaction proof of the \n"
"launch notarization for the specifed currencyid.\n"
"\nArguments\n"
"1. \"currencyid\" (string, required) the hex-encoded ID or string name search for notarizations on\n"
"\nResult:\n"
"{\n"
" \"currencydefinition\" : {}, (json) Full currency definition\n"
" \"txid\" : \"hexstr\", (hexstr) transaction ID\n"
" \"voutnum\" : \"n\", (number) vout index of the launch notarization\n"
" \"transactionproof\" : {}, (json) Partial transaction proof of the launch transaction and output\n"
" \"launchnotarization\" : {}, (json) Final CPBaaSNotarization clearing launch or refund\n"
" \"notarynotarization\" : {}, (json) Current notarization of this chain\n"
"}\n"
"\nExamples:\n"
+ HelpExampleCli("getlaunchinfo", "\"currencyid\"")
+ HelpExampleRpc("getlaunchinfo", "\"currencyid\"")
);
}
CheckPBaaSAPIsValid();
uint160 chainID;
LOCK(cs_main);
CCurrencyDefinition curDef;
chainID = ValidateCurrencyName(uni_get_str(params[0]), true, &curDef);
if (chainID.IsNull())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid currencyid or name");
}
std::pair<CInputDescriptor, CPartialTransactionProof> notarizationTx;
CPBaaSNotarization launchNotarization, notaryNotarization;
if (!ConnectedChains.GetLaunchNotarization(curDef, notarizationTx, launchNotarization, notaryNotarization))
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Valid notarization not found");
}
std::vector<std::pair<std::pair<CInputDescriptor, CPartialTransactionProof>, std::vector<CReserveTransfer>>> exports;
ConnectedChains.GetSystemExports(curDef.systemID, exports, 0, notarizationTx.second.GetBlockHeight(), true);
std::pair<std::pair<CInputDescriptor, CPartialTransactionProof>, std::vector<CReserveTransfer>> foundExport;
bool isExportFound = false;
if (exports.size())
{
for (auto &oneExport : exports)
{
CCrossChainExport oneCCX(oneExport.first.first.scriptPubKey);
if (oneCCX.IsValid() &&
oneCCX.destCurrencyID == chainID)
{
foundExport = oneExport;
isExportFound = true;
break;
}
}
}
if (!isExportFound)
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "No valid export found");
}
UniValue retVal(UniValue::VOBJ);
retVal.pushKV("currencydefinition", curDef.ToUniValue());
retVal.pushKV("notarizationtxid", notarizationTx.first.txIn.prevout.hash.GetHex());
retVal.pushKV("notarizationvoutnum", (int64_t)notarizationTx.first.txIn.prevout.n);
retVal.pushKV("notarizationproof", notarizationTx.second.ToUniValue());
retVal.pushKV("exporttxid", foundExport.first.first.txIn.prevout.hash.GetHex());
retVal.pushKV("exportvoutnum", (int64_t)foundExport.first.first.txIn.prevout.n);
retVal.pushKV("exportproof", foundExport.first.second.ToUniValue());
if (foundExport.second.size())
{
UniValue exportTransfers(UniValue::VARR);
for (auto &oneTransfer : foundExport.second)
{
exportTransfers.push_back(oneTransfer.ToUniValue());
}
retVal.pushKV("exporttransfers", exportTransfers);
}
retVal.pushKV("launchnotarization", launchNotarization.ToUniValue());
retVal.pushKV("notarynotarization", notaryNotarization.ToUniValue());
return retVal;
}
UniValue getbestproofroot(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() != 1 || params[0].getKeys().size() < 2)
{
throw runtime_error(
"getbestproofroot '{\"proofroots\":[\"version\":n,\"type\":n,\"systemid\":\"currencyidorname\",\"height\":n,\n"
" \"stateroot\":\"hex\",\"blockhash\":\"hex\",\"power\":\"hex\"],\"lastconfirmed\":n}'\n"
"\nDetermines and returns the index of the best (most recent, valid, qualified) proof root in the list of proof roots,\n"
"and the most recent, valid proof root.\n"
"\nArguments\n"
"{\n"
" \"proofroots\": (array, required/may be empty) ordered array of proof roots, indexed on return\n"
" [\n"
" {\n"
" \"version\":n (int, required) version of this proof root data structure\n"
" \"type\":n (int, required) type of proof root (chain or system specific)\n"
" \"systemid\":\"hexstr\" (hexstr, required) system the proof root is for\n"
" \"height\":n (uint32_t, required) height of this proof root\n"
" \"stateroot\":\"hexstr\" (hexstr, required) Merkle or merkle-style tree root for the specified block/sequence\n"
" \"blockhash\":\"hexstr\" (hexstr, required) hash identifier for the specified block/sequence\n"
" \"power\":\"hexstr\" (hexstr, required) work, stake, or combination of the two for most-work/most-power rule\n"
" }\n"
" .\n"
" .\n"
" .\n"
" ]\n"
" \"currencies\":[\"id1\"] (array, optional) currencies to query for currency states\n"
" \"lastconfirmed\":n (int, required) index into the proof root array indicating the last confirmed root"
"}\n"
"\nResult:\n"
"\"bestindex\" (int) index of best proof root not confirmed that is provided, confirmed index, or -1"
"\"latestproofroot\" (object) latest valid proof root of chain"
"\"currencystates\" (int) currency states of target currency and published bridges"
"\nExamples:\n"
+ HelpExampleCli("getbestproofroot", "\"{\"proofroots\":[\"version\":n,\"type\":n,\"systemid\":\"currencyidorname\",\"height\":n,\"stateroot\":\"hex\",\"blockhash\":\"hex\",\"power\":\"hex\"],\"lastconfirmed\":n}\"")
+ HelpExampleRpc("getbestproofroot", "\"{\"proofroots\":[\"version\":n,\"type\":n,\"systemid\":\"currencyidorname\",\"height\":n,\"stateroot\":\"hex\",\"blockhash\":\"hex\",\"power\":\"hex\"],\"lastconfirmed\":n}\"")
);
}
CheckPBaaSAPIsValid();
std::vector<std::string> paramKeys = params[0].getKeys();
UniValue currenciesUni;
if (paramKeys.size() > 3 ||
(paramKeys.size() == 3) && !(currenciesUni = find_value(params[0], "currencies")).isArray())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "too many members in object or invalid currencies array");
}
int lastConfirmed = uni_get_int(find_value(params[0], "lastconfirmed"), -1);
if (lastConfirmed == -1)
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid lastconfirmed");
}
std::vector<CProofRoot> proofRootArr;
UniValue uniProofRoots = find_value(params[0], "proofroots");
if (!uniProofRoots.isArray())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid proof root array parameter");
}
for (int i = 0; i < uniProofRoots.size(); i++)
{
proofRootArr.push_back(CProofRoot(uniProofRoots[i]));
if (!proofRootArr.back().IsValid())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid proof root in array");
}
}
LOCK(cs_main);
uint32_t nHeight = chainActive.Height();
uint32_t curHeight = 0;
std::map<uint32_t, int32_t> validRoots; // height, index (only return the first valid at each height)
for (int i = 0; i < proofRootArr.size(); i++)
{
// proof roots must be valid and in height order, though heights can overlap
const CProofRoot &checkRoot = proofRootArr[i];
if (checkRoot.rootHeight <= curHeight ||
checkRoot.systemID != ASSETCHAINS_CHAINID)
{
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("invalid proof root array parameter for %s", EncodeDestination(CIdentityID(ASSETCHAINS_CHAINID))));
}
// ignore potential dups
if (validRoots.count(checkRoot.rootHeight))
{
continue;
}
if (checkRoot == checkRoot.GetProofRoot(checkRoot.rootHeight))
{
validRoots.insert(std::make_pair(checkRoot.rootHeight, i));
}
}
UniValue retVal(UniValue::VOBJ);
if (validRoots.size())
{
UniValue validArr(UniValue::VARR);
for (auto &oneRoot : validRoots)
{
validArr.push_back(oneRoot.second);
}
retVal.pushKV("validindexes", validArr);
retVal.pushKV("bestindex", validRoots.rbegin()->second);
}
// get the latest proof root and currency states
retVal.pushKV("latestproofroot", CProofRoot::GetProofRoot(nHeight).ToUniValue());
std::set<uint160> currenciesSet({ASSETCHAINS_CHAINID});
CCurrencyDefinition targetCur;
uint160 targetCurID;
UniValue currencyStatesUni(UniValue::VARR);
if ((targetCurID = ValidateCurrencyName(EncodeDestination(CIdentityID(ASSETCHAINS_CHAINID)), true, &targetCur)).IsNull())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("invalid currency state request for %s", EncodeDestination(CIdentityID(ASSETCHAINS_CHAINID))));
}
currencyStatesUni.push_back(ConnectedChains.GetCurrencyState(targetCur, nHeight).ToUniValue());
for (int i = 0; i < currenciesUni.size(); i++)
{
if ((targetCurID = ValidateCurrencyName(uni_get_str(currenciesUni[i]), true, &targetCur)).IsNull())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("invalid currency state request for %s", uni_get_str(currenciesUni[i])));
}
if (!currenciesSet.count(targetCurID))
{
currencyStatesUni.push_back(ConnectedChains.GetCurrencyState(targetCur, nHeight).ToUniValue());
}
}
retVal.pushKV("currencystates", currencyStatesUni);
return retVal;
}
UniValue submitacceptednotarization(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() != 2)
{
throw runtime_error(
"submitacceptednotarization \"{earnednotarization}\" \"{notaryevidence}\"\n"
"\nFinishes an almost complete notarization transaction based on the notary chain and the current wallet or pubkey.\n"
"\nIf successful in submitting the transaction based on all rules, a transaction ID is returned, otherwise, NULL.\n"
"\nArguments\n"
"\"earnednotarization\" (object, required) notarization earned on the other system, which is the basis for this\n"
"\"notaryevidence\" (object, required) evidence and notary signatures validating the notarization\n"
"\nResult:\n"
"txid (hexstring) transaction ID of submitted transaction\n"
"\nExamples:\n"
+ HelpExampleCli("submitacceptednotarization", "\"{earnednotarization}\" \"{notaryevidence}\"")
+ HelpExampleRpc("submitacceptednotarization", "\"{earnednotarization}\" \"{notaryevidence}\"")
);
}
CheckPBaaSAPIsValid();
LOCK2(cs_main, mempool.cs);
uint32_t nHeight = chainActive.Height();
// decode the transaction and ensure that it is formatted as expected
CPBaaSNotarization pbn;
CNotaryEvidence evidence;
CCurrencyDefinition chainDef;
int32_t chainDefHeight;
/* CPBaaSNotarization checkPbn(params[0]);
printf("%s: checknotarization before:\n%s\n", __func__, checkPbn.ToUniValue().write(1,2).c_str());
checkPbn.SetMirror();
printf("%s: checknotarization mirrored:\n%s\n", __func__, checkPbn.ToUniValue().write(1,2).c_str());
checkPbn.SetMirror(false);
printf("%s: checknotarization after:\n%s\n", __func__, checkPbn.ToUniValue().write(1,2).c_str()); */
if (!(pbn = CPBaaSNotarization(params[0])).IsValid() ||
!pbn.SetMirror() ||
!GetCurrencyDefinition(pbn.currencyID, chainDef, &chainDefHeight) ||
chainDef.systemID == ASSETCHAINS_CHAINID ||
!(chainDef.IsPBaaSChain() || chainDef.IsGateway()))
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid earned notarization");
}
if (!(evidence = CNotaryEvidence(params[1])).IsValid() ||
!evidence.signatures.size() ||
evidence.systemID != pbn.currencyID)
{
printf("%s: invalid evidence %s\n", __func__, evidence.ToUniValue().write(1,2).c_str());
throw JSONRPCError(RPC_INVALID_PARAMETER, "insufficient notarization evidence");
}
// printf("%s: evidence: %s\n", __func__, evidence.ToUniValue().write(1,2).c_str());
// now, make a new notarization based on this earned notarization, mirrored, so it reflects a notarization on this chain,
// but can be verified with the cross-chain signatures and evidence
// flip back to normal earned notarization as before
pbn.SetMirror(false);
CValidationState state;
TransactionBuilder tb(Params().GetConsensus(), nHeight, pwalletMain);
if (!pbn.CreateAcceptedNotarization(chainDef, pbn, evidence, state, tb))
{
//printf("%s: unable to create accepted notarization: %s\n", __func__, state.GetRejectReason().c_str());
throw JSONRPCError(RPC_INVALID_PARAMETER, state.GetRejectReason());
}
// get the new notarization transaction
tb.SetFee(0);
auto buildResult = tb.Build();
CTransaction newTx;
if (buildResult.IsTx())
{
newTx = buildResult.GetTxOrThrow();
}
else
{
throw JSONRPCError(RPC_INVALID_PARAMETER, buildResult.GetError());
}
std::list<CTransaction> removed;
mempool.removeConflicts(newTx, removed);
// add to mem pool and relay
if (myAddtomempool(newTx))
{
RelayTransaction(newTx);
return newTx.GetHash().GetHex();
}
return NullUniValue;
}
// this must be called after all initial contributions are updated in the currency definition.
CCoinbaseCurrencyState GetInitialCurrencyState(const CCurrencyDefinition &chainDef)
{
bool isFractional = chainDef.IsFractional();
CCurrencyState cState;
uint160 cID = chainDef.GetID();
// calculate contributions and conversions
const std::vector<CAmount> reserveFees(chainDef.currencies.size());
std::vector<int64_t> conversions = chainDef.conversions;
CAmount nativeFees = 0;
if (isFractional)
{
cState = CCurrencyState(cID,
chainDef.currencies,
chainDef.weights,
std::vector<int64_t>(chainDef.currencies.size(), 0),
chainDef.initialFractionalSupply,
0,
chainDef.initialFractionalSupply,
CCurrencyState::FLAG_FRACTIONAL);
//cState.UpdateWithEmission(chainDef.GetTotalPreallocation());
conversions = cState.PricesInReserve();
}
else
{
cState.currencies = chainDef.currencies;
cState.reserves = conversions;
CAmount PreconvertedNative = cState.ReserveToNative(CCurrencyValueMap(chainDef.currencies, chainDef.preconverted));
cState = CCurrencyState(cID,
chainDef.currencies,
std::vector<int32_t>(0),
conversions,
0,
PreconvertedNative,
PreconvertedNative);
//cState.UpdateWithEmission(chainDef.GetTotalPreallocation());
}
CCoinbaseCurrencyState retVal(cState,
0,
0,
0,
chainDef.preconverted,
std::vector<int64_t>(chainDef.currencies.size()),
std::vector<int64_t>(chainDef.currencies.size()),
conversions,
reserveFees,
reserveFees);
return retVal;
}
std::vector<CAddressUnspentDbEntry> GetFractionalNotarizationsForReserve(const uint160 &currencyID)
{
std::vector<CAddressUnspentDbEntry> fractionalNotarizations;
CIndexID indexKey = CCoinbaseCurrencyState::IndexConverterKey(currencyID);
if (!GetAddressUnspent(indexKey, CScript::P2IDX, fractionalNotarizations))
{
LogPrintf("%s: Error reading unspent index\n", __func__);
}
return fractionalNotarizations;
}
UniValue getcurrencyconverters(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() < 1 || params.size() > CCurrencyState::MAX_RESERVE_CURRENCIES)
{
throw runtime_error(
"getcurrencyconverters \"currency1\" \"currency2\" ...\n"
"\nRetrieves all currencies that have at least 1000 VRSC in reserve, are >10% VRSC reserve ratio, and have all listed currencies as reserves\n"
"\nArguments\n"
" \"currencyname\" : \"string\" ... (string(s), one or more) all selected currencies are returned with their current state"
"\nResult:\n"
" \"[{currency1}, {currency2}]\" : \"array of objects\" (string) All currencies and the last notarization, which are valid converters.\n"
"\nExamples:\n"
+ HelpExampleCli("getcurrencyconverters", "'[\"currency1\",\"currency2\",...]'")
+ HelpExampleRpc("getcurrencyconverters", "'[\"currency1\",\"currency2\",...]'")
);
}
CheckPBaaSAPIsValid();
std::map<uint160, CCurrencyDefinition> reserves;
for (int i = 0; i < params.size(); i++)
{
std::string oneName = uni_get_str(params[i]);
CCurrencyDefinition oneCurrency;
uint160 oneCurrencyID;
if (!oneName.size() ||
(oneCurrencyID = ValidateCurrencyName(oneName, true, &oneCurrency)).IsNull() ||
reserves.count(oneCurrencyID))
{
throw JSONRPCError(RPC_INVALID_PARAMS, "Each reserve currency specified must be a valid, unique currency");
}
reserves[oneCurrencyID] = oneCurrency;
}
// get all currencies that contain all specified reserves in our fractionalsFound set
// use latest notarizations of the currencies to do so
std::vector<CAddressUnspentDbEntry> activeFractionals;
std::set<int32_t> toRemove;
auto resIt = reserves.begin();
if (reserves.size() &&
(activeFractionals = GetFractionalNotarizationsForReserve(resIt->first)).size())
{
resIt++;
for (int i = 0; i < activeFractionals.size(); i++)
{
CPBaaSNotarization pbn(activeFractionals[i].second.script);
if (!pbn.IsValid())
{
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Cannot read currency notarization in transaction " + activeFractionals[i].first.txhash.GetHex());
}
auto curMap = pbn.currencyState.GetReserveMap();
for (auto it = resIt; it != reserves.end(); it++)
{
if (!curMap.count(it->first))
{
toRemove.insert(i);
break;
}
}
}
for (auto oneIdx = toRemove.rbegin(); oneIdx != toRemove.rend(); oneIdx++)
{
activeFractionals.erase(activeFractionals.begin() + *oneIdx);
}
}
UniValue ret(UniValue::VARR);
for (int i = 0; i < activeFractionals.size(); i++)
{
CPBaaSNotarization pbn(activeFractionals[i].second.script);
CCurrencyDefinition oneCur;
if (!GetCurrencyDefinition(pbn.currencyID, oneCur))
{
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Cannot get currency definition for currency " + EncodeDestination(CIdentityID(pbn.currencyID)));
}
UniValue oneCurrency(UniValue::VOBJ);
oneCurrency.push_back(Pair(oneCur.name, oneCur.ToUniValue()));
oneCurrency.push_back(Pair("lastnotarization", pbn.ToUniValue()));
ret.push_back(oneCurrency);
}
return ret;
}
UniValue estimateconversion(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() != 1)
{
throw runtime_error(
"estimateconversion '{\"currency\":\"name\",\"convertto\":\"name\",\"amount\":n}'\n"
"\nThis estimates conversion from one currency to another, taking into account pending conversions, fees and slippage.\n"
"\nArguments\n"
"1. {\n"
" \"currency\": \"name\" (string, required) Name of the source currency to send in this output, defaults to native of chain\n"
" \"amount\":amount (numeric, required) The numeric amount of currency, denominated in source currency\n"
" \"convertto\":\"name\", (string, optional) Valid currency to convert to, either a reserve of a fractional, or fractional\n"
" \"preconvert\":\"false\", (bool, optional) convert to currency at market price (default=false), only works if transaction is mined before start of currency\n"
" \"via\":\"name\", (string, optional) If source and destination currency are reserves, via is a common fractional to convert through\n"
" }\n"
"\nResult:\n"
" \"txid\" : \"transactionid\" (string) The transaction id if (returntx) is false\n"
" \"hextx\" : \"hex\" (string) The hexadecimal, serialized transaction if (returntx) is true\n"
"\nExamples:\n"
+ HelpExampleCli("estimateconversion", "'{\"currency\":\"name\",\"convertto\":\"name\",\"amount\":n}'")
+ HelpExampleRpc("estimateconversion", "'{\"currency\":\"name\",\"convertto\":\"name\",\"amount\":n}'")
);
}
CheckPBaaSAPIsValid();
bool isVerusActive = IsVerusActive();
CCurrencyDefinition &thisChain = ConnectedChains.ThisChain();
uint160 thisChainID = thisChain.GetID();
bool toFractional = false;
bool reserveToReserve = false;
auto currencyStr = TrimSpaces(uni_get_str(find_value(params[0], "currency")));
CAmount sourceAmount = AmountFromValue(find_value(params[0], "amount"));
auto convertToStr = TrimSpaces(uni_get_str(find_value(params[0], "convertto")));
auto viaStr = TrimSpaces(uni_get_str(find_value(params[0], "via")));
bool preConvert = uni_get_bool(find_value(params[0], "preconvert"));
LOCK(cs_main);
uint32_t nHeight = chainActive.Height();
CCurrencyDefinition sourceCurrencyDef;
uint160 sourceCurrencyID;
if (currencyStr != "")
{
sourceCurrencyID = ValidateCurrencyName(currencyStr, true, &sourceCurrencyDef);
if (sourceCurrencyID.IsNull())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "If source currency is specified, it must be valid.");
}
}
else
{
sourceCurrencyDef = thisChain;
sourceCurrencyID = sourceCurrencyDef.GetID();
currencyStr = thisChain.name;
}
CCurrencyDefinition convertToCurrencyDef;
uint160 convertToCurrencyID;
if (convertToStr == "")
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Must specify a \"convertto\" currency for conversion estimation");
}
else
{
convertToCurrencyID = ValidateCurrencyName(convertToStr, true, &convertToCurrencyDef);
if (convertToCurrencyID == sourceCurrencyID)
{
convertToCurrencyID.SetNull();
convertToCurrencyDef = CCurrencyDefinition();
}
else
{
if (convertToCurrencyID.IsNull())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid \"convertto\" currency " + convertToStr + " specified");
}
}
}
CCurrencyDefinition secondCurrencyDef;
uint160 secondCurrencyID;
if (viaStr != "")
{
secondCurrencyID = ValidateCurrencyName(viaStr, true, &secondCurrencyDef);
std::map<uint160, int32_t> viaIdxMap = secondCurrencyDef.GetCurrenciesMap();
if (secondCurrencyID.IsNull() ||
sourceCurrencyID.IsNull() ||
convertToCurrencyID.IsNull() ||
secondCurrencyID == sourceCurrencyID ||
secondCurrencyID == convertToCurrencyID ||
sourceCurrencyID == convertToCurrencyID ||
!viaIdxMap.count(sourceCurrencyID) ||
!viaIdxMap.count(convertToCurrencyID))
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "To specify a fractional currency converter, \"currency\" and \"convertto\" must both be reserves of \"via\"");
}
if (preConvert)
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot combine reserve to reserve conversion with preconversion");
}
CCurrencyDefinition tempDef = convertToCurrencyDef;
convertToCurrencyDef = secondCurrencyDef;
secondCurrencyDef = tempDef;
convertToCurrencyID = convertToCurrencyDef.GetID();
secondCurrencyID = secondCurrencyDef.GetID();
}
// if this is reserve to reserve "via" another currency, ensure that both "from" and "to" are reserves of the "via" currency
CReserveTransfer checkTransfer;
CCurrencyDefinition *pFractionalCurrency;
if (secondCurrencyDef.IsValid())
{
std::map<uint160, int32_t> checkMap = convertToCurrencyDef.GetCurrenciesMap();
if (!checkMap.count(sourceCurrencyID) || !checkMap.count(secondCurrencyID))
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "If \"via\" is specified, it must be a fractional currency with reserves of both source and \"convertto\" currency");
}
reserveToReserve = true;
pFractionalCurrency = &convertToCurrencyDef;
}
else
{
// figure out if fractional to reserve, reserve to fractional, or error
toFractional = convertToCurrencyDef.GetCurrenciesMap().count(sourceCurrencyID);
if (toFractional)
{
pFractionalCurrency = &convertToCurrencyDef;
}
else if (!sourceCurrencyDef.GetCurrenciesMap().count(convertToCurrencyID))
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Source currency cannot be converted to destination");
}
else
{
pFractionalCurrency = &sourceCurrencyDef;
}
}
if (!pFractionalCurrency->IsFractional() && (!preConvert || pFractionalCurrency->startBlock >= nHeight))
{
throw JSONRPCError(RPC_INVALID_PARAMETER, pFractionalCurrency->name + " must be a fractional currency or prior to start block to estimate a conversion price");
}
// now, get last notarization and all pending conversion transactions, calculate new conversions, including the new one to estimate and
// return results
CPBaaSNotarization notarization;
uint160 fractionalCurrencyID = pFractionalCurrency->GetID();
CUTXORef lastUnspentUTXO;
CTransaction lastUnspentTx;
if (pFractionalCurrency->systemID == ASSETCHAINS_CHAINID)
{
notarization.GetLastUnspentNotarization(fractionalCurrencyID,
lastUnspentUTXO.hash,
*((int32_t *)&lastUnspentUTXO.n),
&lastUnspentTx);
}
else if (pFractionalCurrency->systemID != ASSETCHAINS_CHAINID)
{
CChainNotarizationData cnd;
if (!GetNotarizationData(fractionalCurrencyID, cnd))
{
notarization = cnd.vtx[cnd.forks[cnd.bestChain].back()].second;
}
}
if (!notarization.IsValid())
{
throw JSONRPCError(RPC_TRANSACTION_ERROR, "Cannot find valid notarization for " + pFractionalCurrency->name);
}
if (preConvert)
{
// estimate preconversion
}
else
{
// estimate normal fractional conversion
// get all pending chain transfers, virtually process all the should be processed in order, put this
// one into the export that it should go into, and calculate the final price of transfers in that export
}
return NullUniValue;
}
UniValue sendcurrency(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() < 2 || params.size() > 3)
{
throw runtime_error(
"sendcurrency \"fromaddress\" '[{\"address\":... ,\"amount\":...},...]' (feeamount)\n"
"\nThis sends one or many Verus outputs to one or many addresses on the same or another chain.\n"
"Funds are sourced automatically from the current wallet, which must be present, as in sendtoaddress.\n"
"If \"fromaddress\" is specified, all funds will be taken from that address, otherwise funds may come\n"
"from any source set of UTXOs controlled by the wallet.\n"
"\nArguments\n"
"1. \"fromaddress\" (string, required) The Sapling, VerusID, or wildcard address to send funds from. \"*\", \"R*\", or \"i*\" are valid wildcards\n"
"2. \"outputs\" (array, required) An array of json objects representing currencies, amounts, and destinations to send.\n"
" [{\n"
" \"currency\": \"name\" (string, required) Name of the source currency to send in this output, defaults to native of chain\n"
" \"amount\":amount (numeric, required) The numeric amount of currency, denominated in source currency\n"
" \"convertto\":\"name\", (string, optional) Valid currency to convert to, either a reserve of a fractional, or fractional\n"
" \"exportto\":\"name\", (string, optional) Valid chain or system name or ID to export to\n"
" \"feecurrency\":\"name\", (string, optional) Valid currency that should be pulled from the current wallet and used to pay fee\n"
" \"via\":\"name\", (string, optional) If source and destination currency are reserves, via is a common fractional to convert through\n"
" \"address\":\"dest\" (string, required) The address and optionally chain/system after the \"@\" as a system specific destination\n"
" \"refundto\":\"dest\" (string, optional) For pre-conversions, this is where refunds will go, defaults to fromaddress\n"
" \"memo\":memo (string, optional) If destination is a zaddr (not supported on testnet), a string message (not hexadecimal) to include.\n"
" \"preconvert\":\"false\", (bool, optional) convert to currency at market price (default=false), only works if transaction is mined before start of currency\n"
" \"burn\":\"false\", (bool, optional) destroy the currency and subtract it from the supply. Currency must be a token.\n"
" \"mintnew\":\"false\", (bool, optional) if the transaction is sent from the currency ID of a centralized currency, this creates new currency to send\n"
" }, ... ]\n"
"3. \"feeamount\" (bool, optional) specific fee amount requested instead of default miner's fee\n"
"\nResult:\n"
" \"txid\" : \"transactionid\" (string) The transaction id if (returntx) is false\n"
" \"hextx\" : \"hex\" (string) The hexadecimal, serialized transaction if (returntx) is true\n"
"\nExamples:\n"
+ HelpExampleCli("sendcurrency", "\"*\" '[{\"currency\":\"btc\",\"address\":\"RRehdmUV7oEAqoZnzEGBH34XysnWaBatct\" ,\"amount\":500.0},...]'")
+ HelpExampleRpc("sendcurrency", "\"bob@\" '[{\"currency\":\"btc\", \"address\":\"alice@quad\", \"amount\":500.0},...]'")
);
}
std::string sourceAddress = uni_get_str(params[0]);
CTxDestination sourceDest;
bool wildCardTransparentAddress = sourceAddress == "*";
bool wildCardRAddress = sourceAddress == "R*";
bool wildCardiAddress = sourceAddress == "i*";
bool wildCardAddress = wildCardTransparentAddress || wildCardRAddress || wildCardiAddress;
bool isVerusActive = IsVerusActive();
CCurrencyDefinition &thisChain = ConnectedChains.ThisChain();
uint160 thisChainID = thisChain.GetID();
bool toFractional = false;
bool fromFractional = false;
std::vector<CRecipient> outputs;
std::set<libzcash::PaymentAddress> zaddrDestSet;
LOCK2(cs_main, mempool.cs);
libzcash::PaymentAddress zaddress;
bool hasZSource = !wildCardAddress && pwalletMain->GetAndValidateSaplingZAddress(sourceAddress, zaddress);
// if we have a z-address as a source, re-encode it to a string, which is used
// by the async operation, to ensure that we don't need to lookup IDs in that operation
if (hasZSource)
{
zaddrDestSet.insert(zaddress);
sourceAddress = EncodePaymentAddress(zaddress);
}
if (!(hasZSource ||
wildCardAddress ||
(sourceDest = DecodeDestination(sourceAddress)).which() != COptCCParams::ADDRTYPE_INVALID))
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameters. First parameter must be sapling address, transparent address, identity, \"*\", \"R*\",, or \"i*\",. See help.");
}
if (!params[1].isArray() || !params[1].size())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameters. Second parameter must be array of outputs. See help.");
}
CAmount feeAmount = DEFAULT_TRANSACTION_FEE;
if (params.size() > 2)
{
feeAmount = AmountFromValue(params[2]);
}
const UniValue &uniOutputs = params[1];
uint32_t height = chainActive.Height();
TransactionBuilder tb(Params().GetConsensus(), height + 1, pwalletMain);
std::vector<SendManyRecipient> tOutputs;
std::vector<SendManyRecipient> zOutputs;
try
{
for (int i = 0; i < uniOutputs.size(); i++)
{
auto currencyStr = TrimSpaces(uni_get_str(find_value(uniOutputs[i], "currency")));
CAmount sourceAmount = AmountFromValue(find_value(uniOutputs[i], "amount"));
auto convertToStr = TrimSpaces(uni_get_str(find_value(uniOutputs[i], "convertto")));
auto exportToStr = TrimSpaces(uni_get_str(find_value(uniOutputs[i], "exportto")));
auto feeCurrencyStr = TrimSpaces(uni_get_str(find_value(uniOutputs[i], "feecurrency")));
auto viaStr = TrimSpaces(uni_get_str(find_value(uniOutputs[i], "via")));
auto destStr = TrimSpaces(uni_get_str(find_value(uniOutputs[i], "address")));
auto refundToStr = TrimSpaces(uni_get_str(find_value(uniOutputs[i], "refundto")));
auto memoStr = uni_get_str(find_value(uniOutputs[i], "memo"));
bool preConvert = uni_get_bool(find_value(uniOutputs[i], "preconvert"));
bool burnCurrency = uni_get_bool(find_value(uniOutputs[i], "burn"));
bool mintNew = uni_get_bool(find_value(uniOutputs[i], "mintnew"));
if (currencyStr.size() ||
convertToStr.size() ||
refundToStr.size() ||
memoStr.size() ||
preConvert ||
mintNew ||
burnCurrency)
{
CheckPBaaSAPIsValid();
}
CCurrencyDefinition sourceCurrencyDef;
uint160 sourceCurrencyID;
if (currencyStr != "")
{
sourceCurrencyID = ValidateCurrencyName(currencyStr, true, &sourceCurrencyDef);
if (sourceCurrencyID.IsNull())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "If source currency is specified, it must be valid.");
}
}
else
{
sourceCurrencyDef = thisChain;
sourceCurrencyID = sourceCurrencyDef.GetID();
currencyStr = thisChain.name;
}
if (hasZSource && sourceCurrencyID != ASSETCHAINS_CHAINID)
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Only native " + thisChain.name + " currency can be sourced from a z-address.");
}
libzcash::PaymentAddress zaddressDest;
bool hasZDest = pwalletMain->GetAndValidateSaplingZAddress(destStr, zaddressDest);
if (hasZDest &&
(convertToStr.size() ||
viaStr.size() ||
exportToStr.size() ||
burnCurrency ||
mintNew ||
preConvert ||
sourceCurrencyID != ASSETCHAINS_CHAINID))
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot convert, preconvert, mint, cross-chain send, burn or send non-native currency when sending to a z-address.");
}
// re-encode destination, in case it is specified as the private address of an ID
if (hasZDest)
{
// no duplicate z-address destinations
if (zaddrDestSet.count(zaddressDest))
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot duplicate private address source or destination");
}
zaddrDestSet.insert(zaddressDest);
destStr = EncodePaymentAddress(zaddressDest);
}
CCurrencyDefinition convertToCurrencyDef;
uint160 convertToCurrencyID;
if (convertToStr != "")
{
convertToCurrencyID = ValidateCurrencyName(convertToStr, true, &convertToCurrencyDef);
if (convertToCurrencyID == sourceCurrencyID)
{
convertToCurrencyID.SetNull();
convertToCurrencyDef = CCurrencyDefinition();
}
else
{
if (convertToCurrencyID.IsNull())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "If currency conversion is requested, destination currency must be valid.");
}
if (burnCurrency)
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot convert and burn currency in a single operation. First convert, then burn.");
}
}
}
CCurrencyDefinition secondCurrencyDef;
uint160 secondCurrencyID;
bool isVia = false;
if (viaStr != "")
{
secondCurrencyID = ValidateCurrencyName(viaStr, true, &secondCurrencyDef);
std::map<uint160, int32_t> viaIdxMap = secondCurrencyDef.GetCurrenciesMap();
if (secondCurrencyID.IsNull() ||
sourceCurrencyID.IsNull() ||
convertToCurrencyID.IsNull() ||
secondCurrencyID == sourceCurrencyID ||
secondCurrencyID == convertToCurrencyID ||
sourceCurrencyID == convertToCurrencyID ||
!viaIdxMap.count(sourceCurrencyID) ||
!viaIdxMap.count(convertToCurrencyID))
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "To specify a fractional currency converter, \"currency\" and \"convertto\" must both be reserves of \"via\"");
}
if (burnCurrency || mintNew || preConvert)
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot combine reserve to reserve conversion with burning, minting, or preconversion");
}
CCurrencyDefinition tempDef = convertToCurrencyDef;
convertToCurrencyDef = secondCurrencyDef;
secondCurrencyDef = tempDef;
convertToCurrencyID = convertToCurrencyDef.GetID();
secondCurrencyID = secondCurrencyDef.GetID();
isVia = true;
}
bool isConversion = false;
if (!convertToCurrencyID.IsNull())
{
isConversion = true;
}
// send a reserve transfer preconvert
uint32_t flags = CReserveTransfer::VALID;
if (burnCurrency)
{
if (mintNew ||
!convertToCurrencyID.IsNull() ||
!(sourceCurrencyDef.IsFractional() || sourceCurrencyDef.IsToken()))
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot convert and burn currency in a single operation. First convert, then burn.");
}
flags |= CReserveTransfer::BURN_CHANGE_PRICE;
convertToCurrencyID = sourceCurrencyID;
convertToCurrencyDef = sourceCurrencyDef;
}
std::string systemDestStr;
uint160 destSystemID = thisChainID;
CCurrencyDefinition destSystemDef;
std::vector<std::string> subNames;
CCurrencyDefinition exportToCurrencyDef;
uint160 exportToCurrencyID;
toFractional = convertToCurrencyDef.IsValid() && convertToCurrencyDef.IsFractional() && convertToCurrencyDef.GetCurrenciesMap().count(sourceCurrencyID);
fromFractional = !toFractional &&
sourceCurrencyDef.IsFractional() && !convertToCurrencyID.IsNull() && sourceCurrencyDef.GetCurrenciesMap().count(convertToCurrencyID);
if (toFractional || preConvert)
{
destSystemID = convertToCurrencyDef.systemID;
}
else if (fromFractional)
{
destSystemID = sourceCurrencyDef.systemID;
}
else if (!hasZDest)
{
// check for explicit system name specified
subNames = ParseSubNames(destStr, systemDestStr, true);
if (systemDestStr != "")
{
destSystemID = ValidateCurrencyName(systemDestStr, true, &destSystemDef);
if (destSystemID.IsNull() || destSystemDef.IsToken() || destSystemDef.systemID != destSystemDef.GetID())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "If destination system is specified, destination system or chain must be registered.");
}
if (exportToStr == "")
{
exportToStr = systemDestStr;
exportToCurrencyID = destSystemID;
exportToCurrencyDef = destSystemDef;
}
}
}
else
{
// things you can't do with a z-destination yet
if (exportToStr.size() ||
isConversion ||
burnCurrency ||
preConvert ||
mintNew)
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid operations for z-address destination");
}
}
if (!destSystemDef.IsValid() && !destSystemID.IsNull())
{
destSystemDef = ConnectedChains.GetCachedCurrency(destSystemID);
}
uint160 converterID = secondCurrencyID.IsNull() ? convertToCurrencyID : secondCurrencyID;
CCurrencyDefinition &converterDef = secondCurrencyID.IsNull() ? convertToCurrencyDef : secondCurrencyDef;
// see if we should send this currency off-chain. if our target is a fractional currency and can convert but lives on another system,
// we will not implicitly send it off chain for conversion, even if via is specified. "exportto" requests an explicit system
// export/import before the operation.
CCurrencyDefinition exportSystemDef;
if (exportToStr != "")
{
uint160 explicitExportID = ValidateCurrencyName(exportToStr, true, &exportToCurrencyDef);
if (!exportToCurrencyDef.IsValid() || (!exportToCurrencyID.IsNull() && exportToCurrencyID != explicitExportID))
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Duplicate or invalid export system/currency destinations that do not match");
}
// if we have a converter currency on the same chain as the explicit export, the converter is
// our actual export currency
if (converterDef.systemID == exportToCurrencyDef.systemID)
{
exportToCurrencyDef = converterDef;
exportToCurrencyID = converterID;
}
else
{
exportToCurrencyID = explicitExportID;
}
if (exportToCurrencyDef.systemID == ASSETCHAINS_CHAINID)
{
exportToStr = "";
exportToCurrencyID.SetNull();
}
}
if (!exportToCurrencyID.IsNull())
{
if (exportToCurrencyID == exportToCurrencyDef.systemID ||
(exportToCurrencyDef.IsGateway() && exportToCurrencyID == exportToCurrencyDef.GetID()))
{
exportSystemDef = exportToCurrencyDef;
}
else
{
exportSystemDef = ConnectedChains.GetCachedCurrency(exportToCurrencyDef.systemID);
if (!exportSystemDef.IsValid() ||
(exportSystemDef.systemID != exportSystemDef.GetID() &&
!(exportSystemDef.IsGateway() && exportSystemDef.systemID == thisChainID && exportSystemDef.gatewayID == exportSystemDef.GetID())))
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid export system definition");
}
}
}
// if we have no explicit destination system and non-null export, make export
// our destination system
if (destSystemID == ASSETCHAINS_CHAINID &&
!(converterDef.IsValid() && converterDef.systemID == ASSETCHAINS_CHAINID) &&
!exportToCurrencyID.IsNull())
{
destSystemID = exportSystemDef.GetID();
destSystemDef = exportSystemDef;
}
// this only applies to the first step, if
// first step is on-chain convert, then second is off chain, this will be false
bool sendOffChain = (destSystemID != ASSETCHAINS_CHAINID) || (!exportToCurrencyID.IsNull() && exportToCurrencyID != ASSETCHAINS_CHAINID);
bool convertBeforeOffChain = sendOffChain && (destSystemID == ASSETCHAINS_CHAINID);
uint160 feeCurrencyID;
CCurrencyDefinition feeCurrencyDef;
if (feeCurrencyStr != "")
{
feeCurrencyID = ValidateCurrencyName(feeCurrencyStr, true, &feeCurrencyDef);
if (!feeCurrencyID.IsNull())
{
feeCurrencyID = destSystemID;
feeCurrencyDef = destSystemDef;
}
else
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid fee currency specified");
}
}
else
{
feeCurrencyDef = ConnectedChains.ThisChain();
feeCurrencyID = thisChainID;
}
// if we are already converting or processing through some currency, that can only be done on its native system
// and may imply an export off-chain. before creating an off-chain export, we need an explicit "exportto" command that matches.
// we may also have an "exportafter" command, which enables funding a second leg to up to one more system
// ensure that any initial export is explicit
if (sendOffChain && !exportToCurrencyID.IsNull() &&
!(exportToCurrencyDef.systemID == destSystemID || (convertBeforeOffChain && destSystemID == ASSETCHAINS_CHAINID)))
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Conflicting target system for send -- \"exportto\" must be consistent with any other cross-chain currency targets");
}
else if (!exportToCurrencyID.IsNull() &&
exportToCurrencyID != thisChainID &&
!preConvert)
{
if (mintNew)
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cross-system mint operations not supported");
}
if (isConversion &&
!((converterDef.systemID == ASSETCHAINS_CHAINID &&
converterID != exportToCurrencyID &&
exportToCurrencyDef.IsGateway() || exportToCurrencyDef.IsPBaaSChain() &&
converterDef.IsFractional() &&
converterDef.GetCurrenciesMap().count(exportToCurrencyDef.systemID)) ||
(converterID == exportToCurrencyID &&
exportToCurrencyDef.systemID == destSystemID)))
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid export syntax. Fractional converter must be from current chain before \"exportto\" a system currency or on the alternate system and the same destination as \"exportto\".");
}
// if fee currency is the export system destination
// don't see if we should route through a converter
if (feeCurrencyID == destSystemID)
{
// if we also have an explicit conversion, we must verify that we can either do that on this chain
// first and then pass through or pass to the converter currency on the other system
if (!convertToCurrencyID.IsNull())
{
if (convertToCurrencyDef.systemID != destSystemID &&
(convertToCurrencyDef.systemID != ASSETCHAINS_CHAINID ||
!convertToCurrencyDef.IsFractional()))
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Currency " +
EncodeDestination(CIdentityID(convertToCurrencyID)) +
" is not capable of converting " +
EncodeDestination(CIdentityID(feeCurrencyID)) +
" to " +
EncodeDestination(CIdentityID(ASSETCHAINS_CHAINID)));
}
}
}
else if (convertToCurrencyID.IsNull())
{
convertToCurrencyID = (exportToCurrencyDef.IsFractional() ? exportToCurrencyID : exportToCurrencyDef.GatewayConverterID());
if (convertToCurrencyID.IsNull() && (convertToCurrencyID = ConnectedChains.ThisChain().GatewayConverterID()).IsNull())
{
convertToCurrencyID = exportToCurrencyID;
convertToCurrencyDef = exportToCurrencyDef;
}
else
{
// get gateway converter and set as fee converter/exportto currency
convertToCurrencyDef = ConnectedChains.GetCachedCurrency(convertToCurrencyID);
}
if (!convertToCurrencyDef.IsValid())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "No available fee converter.");
}
if (convertToCurrencyID != exportToCurrencyID && convertToCurrencyDef.systemID == exportToCurrencyID)
{
exportToCurrencyID = convertToCurrencyID;
exportToCurrencyDef = convertToCurrencyDef;
}
bool toCurrencyIsFractional = convertToCurrencyDef.IsFractional();
if (!convertToCurrencyDef.IsValid() ||
(!((convertToCurrencyDef.IsPBaaSChain() && (feeCurrencyID == destSystemDef.launchSystemID ||
feeCurrencyID == destSystemID))) &&
!(toCurrencyIsFractional && convertToCurrencyDef.GetCurrenciesMap().count(feeCurrencyID) ||
convertToCurrencyDef.GetID() == feeCurrencyID)))
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid fee currency for system destination.");
}
// if we are inserting a converter on the current chain, before the destination, adjust
if (convertToCurrencyDef.systemID == ASSETCHAINS_CHAINID)
{
convertBeforeOffChain = true;
destSystemID = thisChainID;
destSystemDef = thisChain;
}
}
}
if (mintNew &&
(!(sourceCurrencyDef.IsToken() &&
GetDestinationID(sourceDest) == sourceCurrencyID &&
sourceCurrencyDef.proofProtocol == sourceCurrencyDef.PROOF_CHAINID &&
destSystemID == thisChainID &&
!preConvert &&
convertToCurrencyID.IsNull())))
{
// attempt to mint currency that isn't under the source ID's control
throw JSONRPCError(RPC_INVALID_PARAMETER, "Only the ID of a mintable currency can mint such a currency. Minting cannot be combined with conversion.");
}
if (hasZDest)
{
// if memo starts with "#", convert it from a string to a hex value
if (memoStr.size() > 1 && memoStr[0] == '#')
{
// make a hex string out of the chars without the "#"
memoStr = HexBytes((const unsigned char *)&(memoStr[1]), memoStr.size());
}
if (memoStr.size() && !IsHex(memoStr))
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected memo data in hexadecimal format or as a non-zero length string, starting with \"#\".");
}
if (memoStr.length() > ZC_MEMO_SIZE*2) {
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter, size of memo is larger than maximum allowed %d", ZC_MEMO_SIZE ));
}
zOutputs.push_back(SendManyRecipient(destStr, sourceAmount, memoStr, CScript()));
}
else
{
if (memoStr.size() > 0)
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Memo is only an option for z-address destinations.");
}
CTxDestination destination = ValidateDestination(destStr);
if (convertToCurrencyDef.IsValid() &&
convertToCurrencyDef.systemID != destSystemID)
{
// send to the converter on the destination system
}
else
{
// first convert, include embedded fees, then send
}
CTransferDestination transferDestination;
if (destination.which() == COptCCParams::ADDRTYPE_INVALID)
{
if (destSystemDef.IsGateway())
{
std::vector<unsigned char> rawDestBytes;
for (int i = 0; i < subNames.size(); i++)
{
if (i)
{
rawDestBytes.push_back('.');
}
rawDestBytes.insert(rawDestBytes.end(), subNames[i].begin(), subNames[i].end());
}
if (!rawDestBytes.size())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Specified destination must be valid.");
}
transferDestination = CTransferDestination(CTransferDestination::FLAG_DEST_GATEWAY + CTransferDestination::DEST_RAW, rawDestBytes);
}
else
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Specified destination must be valid.");
}
}
CTxDestination refundDestination = DecodeDestination(refundToStr);
if (refundDestination.which() == COptCCParams::ADDRTYPE_ID &&
GetDestinationID(refundDestination) != GetDestinationID(destination))
{
if (!CIdentity::LookupIdentity(GetDestinationID(refundDestination)).IsValid())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "When refunding to an ID, the ID must be valid.");
}
}
else if (refundDestination.which() == COptCCParams::ADDRTYPE_INVALID)
{
refundDestination = destination;
}
// make one output
CRecipient oneOutput;
if (preConvert)
{
flags |= CReserveTransfer::PRECONVERT;
}
if (isConversion && !burnCurrency && !convertToCurrencyID.IsNull())
{
flags |= CReserveTransfer::CONVERT;
}
else if (mintNew)
{
flags |= CReserveTransfer::MINT_CURRENCY;
convertToCurrencyID = sourceCurrencyID;
convertToCurrencyDef = sourceCurrencyDef;
}
if (isVia)
{
flags |= CReserveTransfer::RESERVE_TO_RESERVE;
}
// are we a system/chain transfer with or without conversion?
if (destSystemID != thisChainID || (!exportToCurrencyID.IsNull() && exportToCurrencyID != thisChainID))
{
// possible cases:
// 1. sending currency to another chain, paying with native currencies and no converter
// 2. sending currency with or without conversion, paying with fees converted via converter on source or dest system
// 3. preconvert on launch of new system
//
// converting with fees via a converter on the source chain first converts fees and optionally more,
// then uses case 1 above
// check for potentially unknown currencies or IDs being sent across
// for now, we can only send IDs and currencies that were involved in the launch
std::set<uint160> validCurrencies;
std::set<uint160> validIDs;
CChainNotarizationData cnd;
CCurrencyDefinition nonVerusChainDef = IsVerusActive() ?
(destSystemID != thisChainID ? destSystemDef : exportToCurrencyDef) :
(ConnectedChains.ThisChain());
uint160 offChainID = IsVerusActive() ? nonVerusChainDef.GetID() : VERUS_CHAINID;
if (!GetNotarizationData(offChainID, cnd) || !cnd.IsConfirmed())
{
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Cannot retrieve notarization data for import system " + nonVerusChainDef.name + " (" + EncodeDestination(CIdentityID(offChainID)) + ")");
}
if (!preConvert && cnd.vtx[cnd.lastConfirmed].second.IsPreLaunch() && !cnd.vtx[cnd.lastConfirmed].second.IsLaunchCleared())
{
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Cannot send non-preconvert transfers to import system " + nonVerusChainDef.name + " (" + EncodeDestination(CIdentityID(offChainID)) + ") until after launch");
}
if (cnd.vtx[cnd.lastConfirmed].second.IsRefunding())
{
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Cannot send to import system " + nonVerusChainDef.name + " (" + EncodeDestination(CIdentityID(offChainID)) + ") that is in a refunding state");
}
validCurrencies.insert(ASSETCHAINS_CHAINID);
validCurrencies.insert(offChainID);
if (IsVerusActive())
{
validIDs.insert(offChainID);
if ((nonVerusChainDef.IsPBaaSChain() || nonVerusChainDef.IsGateway()) && !nonVerusChainDef.GatewayConverterID().IsNull())
{
validIDs.insert(nonVerusChainDef.GatewayConverterID());
}
}
for (auto &oneValidID : nonVerusChainDef.preAllocation)
{
validIDs.insert(oneValidID.first);
}
for (auto &oneValidCurrency : nonVerusChainDef.currencies)
{
validCurrencies.insert(oneValidCurrency);
}
if ((nonVerusChainDef.IsPBaaSChain() || nonVerusChainDef.IsGateway()) && !nonVerusChainDef.GatewayConverterID().IsNull())
{
CCurrencyDefinition gatewayDef = ConnectedChains.GetCachedCurrency(nonVerusChainDef.GatewayConverterID());
if (!gatewayDef.IsValid())
{
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Error retrieving gateway currency for " + nonVerusChainDef.name + " (" + EncodeDestination(CIdentityID(offChainID)) + ")");
}
validCurrencies.insert(nonVerusChainDef.GatewayConverterID());
for (auto &oneValidCurrency : gatewayDef.currencies)
{
validCurrencies.insert(oneValidCurrency);
}
for (auto &oneValidID : gatewayDef.preAllocation)
{
validIDs.insert(oneValidID.first);
}
}
for (auto &oneCurrencyState : cnd.vtx[cnd.lastConfirmed].second.currencyStates)
{
validCurrencies.insert(oneCurrencyState.second.GetID());
for (auto &oneReserve : oneCurrencyState.second.currencies)
{
validCurrencies.insert(oneReserve);
}
}
if (destination.which() == COptCCParams::ADDRTYPE_ID && !validIDs.count(GetDestinationID(destination)))
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot send to ID " + EncodeDestination(destination) + ", which is unregistered on specified system");
}
if (!validCurrencies.count(sourceCurrencyID))
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Currency " + sourceCurrencyDef.name + " (" + EncodeDestination(CIdentityID(sourceCurrencyID)) + ") cannot be sent to specified system");
}
if (!convertToCurrencyID.IsNull() && !validCurrencies.count(convertToCurrencyID))
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Currency " + sourceCurrencyDef.name + " (" + EncodeDestination(CIdentityID(sourceCurrencyID)) + ") cannot be currency destination on specified system");
}
CCcontract_info CC;
CCcontract_info *cp;
cp = CCinit(&CC, EVAL_RESERVE_TRANSFER);
CPubKey pk = CPubKey(ParseHex(CC.CChexstr));
if (preConvert)
{
if (convertToCurrencyID.IsNull())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot preconvert to unspecified or invalid currency.");
}
auto validConversionCurrencies = convertToCurrencyDef.GetCurrenciesMap();
if (!validConversionCurrencies.count(sourceCurrencyID))
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot convert " + sourceCurrencyDef.name + " to " + convertToStr + ".");
}
if (convertToCurrencyDef.launchSystemID == ASSETCHAINS_CHAINID &&
convertToCurrencyDef.startBlock <= (height + 1))
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Too late to convert " + sourceCurrencyDef.name + " to " + convertToStr + ", as pre-launch is over.");
}
CReserveTransfer rt = CReserveTransfer(flags,
sourceCurrencyID,
sourceAmount,
ASSETCHAINS_CHAINID,
0,
convertToCurrencyID,
DestinationToTransferDestination(destination));
rt.nFees = rt.CalculateTransferFee();
std::vector<CTxDestination> dests = std::vector<CTxDestination>({pk.GetID(), refundDestination});
oneOutput.nAmount = sourceCurrencyID == thisChainID ? sourceAmount + rt.CalculateTransferFee() : rt.CalculateTransferFee();
oneOutput.scriptPubKey = MakeMofNCCScript(CConditionObj<CReserveTransfer>(EVAL_RESERVE_TRANSFER, dests, 1, &rt));
}
// through a converter before or after conversion for actual conversion and/or fee conversion
else if (isConversion || (destSystemID != exportToCurrencyID && !convertToCurrencyID.IsNull()))
{
// if we convert first, then export, put the follow-on export in an output
// to be converted with a cross-chain fee
CTransferDestination dest = DestinationToTransferDestination(destination);
if (convertToCurrencyDef.systemID == ASSETCHAINS_CHAINID)
{
// if we're converting and then sending, we don't need an initial fee, so all
// fees go into the final destination
dest.type |= dest.FLAG_DEST_GATEWAY;
dest.gatewayID = exportSystemDef.GetID();
CChainNotarizationData cnd;
if (!GetNotarizationData(convertToCurrencyID, cnd) ||
!cnd.IsConfirmed())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot get notarization/pricing information for " + exportToCurrencyDef.name);
}
auto currencyMap = cnd.vtx[cnd.lastConfirmed].second.currencyState.GetReserveMap();
if (!currencyMap.count(destSystemID) || !currencyMap.count(feeCurrencyID))
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Local converter convert " + feeCurrencyDef.name + " to " + destSystemDef.name + ".");
}
dest.fees = ((exportSystemDef.transactionImportFee + thisChain.transactionExportFee) *
cnd.vtx[cnd.lastConfirmed].second.currencyState.PriceInReserve(currencyMap[destSystemID]))
/ cnd.vtx[cnd.lastConfirmed].second.currencyState.PriceInReserve(currencyMap[feeCurrencyID]);
printf("%s: setting transfer fees in currency %s to %ld\n", __func__, EncodeDestination(CIdentityID(feeCurrencyID)).c_str(), dest.fees);
}
else
{
flags |= CReserveTransfer::CROSS_SYSTEM;
}
auto reserveMap = convertToCurrencyDef.GetCurrenciesMap();
if (!reserveMap.count(feeCurrencyID))
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot convert fees " + EncodeDestination(CIdentityID(feeCurrencyID)) + " to " + destSystemDef.name + ". 3");
}
// converting from reserve to a fractional of that reserve
auto fees = CReserveTransfer::CalculateTransferFee(dest, flags);
CReserveTransfer rt = CReserveTransfer(flags,
sourceCurrencyID,
sourceAmount,
feeCurrencyID,
fees,
convertToCurrencyID,
dest,
secondCurrencyID,
exportSystemDef.GetID());
std::vector<CTxDestination> dests = std::vector<CTxDestination>({pk.GetID(), refundDestination});
oneOutput.nAmount = rt.TotalCurrencyOut().valueMap[ASSETCHAINS_CHAINID];
oneOutput.scriptPubKey = MakeMofNCCScript(CConditionObj<CReserveTransfer>(EVAL_RESERVE_TRANSFER, dests, 1, &rt));
}
else // direct to another system paying with acceptable fee currency
{
flags |= CReserveTransfer::CROSS_SYSTEM;
auto dest = DestinationToTransferDestination(destination); // add refundDestination to destination
auto fees = CReserveTransfer::CalculateTransferFee(dest, flags);
CReserveTransfer rt = CReserveTransfer(flags,
sourceCurrencyID,
sourceAmount,
feeCurrencyID,
fees,
exportToCurrencyID,
dest,
secondCurrencyID,
exportSystemDef.GetID());
std::vector<CTxDestination> dests = std::vector<CTxDestination>({pk.GetID(), refundDestination});
oneOutput.nAmount = rt.TotalCurrencyOut().valueMap[ASSETCHAINS_CHAINID];
oneOutput.scriptPubKey = MakeMofNCCScript(CConditionObj<CReserveTransfer>(EVAL_RESERVE_TRANSFER, dests, 1, &rt));
}
}
// a currency conversion without transfer?
else if (!convertToCurrencyID.IsNull())
{
if (convertToCurrencyDef.IsToken() && preConvert)
{
if (convertToCurrencyDef.startBlock <= (height + 1))
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Too late to convert " + sourceCurrencyDef.name + " to " + convertToStr + ", as pre-launch is over.");
}
CCurrencyValueMap validConversionCurrencies = CCurrencyValueMap(convertToCurrencyDef.currencies,
std::vector<CAmount>(convertToCurrencyDef.currencies.size(), 1));
if (!convertToCurrencyDef.GetCurrenciesMap().count(sourceCurrencyID))
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot convert " + sourceCurrencyDef.name + " to " + convertToStr + ". 1");
}
CCcontract_info CC;
CCcontract_info *cp;
cp = CCinit(&CC, EVAL_RESERVE_TRANSFER);
CPubKey pk = CPubKey(ParseHex(CC.CChexstr));
std::vector<CTxDestination> dests = std::vector<CTxDestination>({pk.GetID(), refundDestination});
auto dest = DestinationToTransferDestination(destination);
auto fees = CReserveTransfer::CalculateTransferFee(dest, flags);
CReserveTransfer rt = CReserveTransfer(flags,
sourceCurrencyID,
sourceAmount,
feeCurrencyID,
fees,
convertToCurrencyID,
dest);
rt.nFees = rt.CalculateTransferFee();
oneOutput.nAmount = rt.TotalCurrencyOut().valueMap[ASSETCHAINS_CHAINID];
oneOutput.scriptPubKey = MakeMofNCCScript(CConditionObj<CReserveTransfer>(EVAL_RESERVE_TRANSFER, dests, 1, &rt));
}
else if (!preConvert && (mintNew || burnCurrency || toFractional || fromFractional))
{
// the following cases end up here:
// 1. we are minting or burning currency
// 2. we are converting from a fractional currency to its reserve or back
// 3. we are converting from one reserve of a fractional currency to another reserve of the same fractional
CCcontract_info CC;
CCcontract_info *cp;
cp = CCinit(&CC, EVAL_RESERVE_TRANSFER);
CPubKey pk = CPubKey(ParseHex(CC.CChexstr));
if (mintNew || burnCurrency)
{
// we only allow minting/burning of tokens right now
// TODO: support centralized minting of native AND fractional currency
// minting of fractional currency should emit coins without changing price by
// adjusting reserve ratio
if (!convertToCurrencyDef.IsToken() || convertToCurrencyDef.systemID != ASSETCHAINS_CHAINID || convertToCurrencyDef.IsGateway())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot mint or burn currency " + convertToCurrencyDef.name);
}
std::vector<CTxDestination> dests = std::vector<CTxDestination>({pk.GetID()});
if (burnCurrency)
{
flags |= CReserveTransfer::IMPORT_TO_SOURCE;
}
CReserveTransfer rt = CReserveTransfer(flags,
burnCurrency ? sourceCurrencyID : thisChainID,
sourceAmount,
ASSETCHAINS_CHAINID,
0,
convertToCurrencyID,
DestinationToTransferDestination(destination));
rt.nFees = rt.CalculateTransferFee();
oneOutput.nAmount = rt.CalculateTransferFee();
oneOutput.scriptPubKey = MakeMofNCCScript(CConditionObj<CReserveTransfer>(EVAL_RESERVE_TRANSFER, dests, 1, &rt));
}
else
{
flags |= CReserveTransfer::CONVERT;
// determine the currency that is the fractional currency, whether that is the source
// or destination
CCurrencyDefinition *pFractionalCurrency = &sourceCurrencyDef;
// determine the reserve currency of the destination that we are relevant to,
// again, whether source or destination
CCurrencyDefinition *pReserveCurrency = &convertToCurrencyDef;
// is our destination currency, the conversion destination?
if (toFractional)
{
pReserveCurrency = pFractionalCurrency;
pFractionalCurrency = &convertToCurrencyDef;
}
else
{
flags |= CReserveTransfer::IMPORT_TO_SOURCE;
}
if (!secondCurrencyID.IsNull())
{
flags |= CReserveTransfer::RESERVE_TO_RESERVE;
}
if (pFractionalCurrency->launchSystemID == ASSETCHAINS_CHAINID && pFractionalCurrency->startBlock > (height + 1))
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot convert " + sourceCurrencyDef.name + " to " + convertToStr + " except through preconvert before the startblock has passed.");
}
auto reserveMap = pFractionalCurrency->GetCurrenciesMap();
auto reserveIndexIt = reserveMap.find(pReserveCurrency->GetID());
if (reserveIndexIt == reserveMap.end())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot convert " + sourceCurrencyDef.name + " to " + convertToStr + ". Must have reserve<->fractional relationship.");
}
// converting from reserve to a fractional of that reserve
auto dest = DestinationToTransferDestination(destination);
auto fees = CReserveTransfer::CalculateTransferFee(dest, flags);
/*
In order to accept fees in any currency, we need to pin ourselves to an easily accessible, objective price of the
fee currency in native currency of the target system. In order to support an objective and hard line of meeting
fee requirements, we define the exchange rate of a currency to verify required import fees for any operation to
be determined by the last confirmed notarization of the importing currency state, if it is fractional, as of the
last export transaction to that currency from this system. An export transaction also includes a predicted
notarization, which is not typically finalized, except for the launch notarization, but always includes the last
known state based on notarization.
Enabling this will also enable us to generate transactions which require Verus to be posted at all, but once
posted, use tokens, which are convenient and already in the transaction to pay for all remaining fees on this
system or others.
Until then, we use the same standard prices and defaults for all currencies.
CChainNotarizationData cnd;
if (!GetNotarizationData(pFractionalCurrency->GetID(), EVAL_ACCEPTEDNOTARIZATION, cnd))
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Unable to get reserve currency data for " + pFractionalCurrency->name + ".");
}
*/
CReserveTransfer rt = CReserveTransfer(flags,
sourceCurrencyID,
sourceAmount,
sourceCurrencyID,
fees,
convertToCurrencyID,
dest,
secondCurrencyID);
std::vector<CTxDestination> dests = std::vector<CTxDestination>({pk.GetID(), refundDestination});
oneOutput.nAmount = rt.TotalCurrencyOut().valueMap[ASSETCHAINS_CHAINID];
oneOutput.scriptPubKey = MakeMofNCCScript(CConditionObj<CReserveTransfer>(EVAL_RESERVE_TRANSFER, dests, 1, &rt));
}
}
else
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot convert " + sourceCurrencyDef.name + " to " + convertToStr + ". 4");
}
}
// or a normal native or reserve output?
else
{
if (sourceCurrencyID == thisChainID)
{
oneOutput.nAmount = sourceAmount;
oneOutput.scriptPubKey = GetScriptForDestination(destination);
}
else
{
oneOutput.nAmount = 0;
std::vector<CTxDestination> dests = std::vector<CTxDestination>({destination});
CTokenOutput to(sourceCurrencyID, sourceAmount);
oneOutput.scriptPubKey = MakeMofNCCScript(CConditionObj<CTokenOutput>(EVAL_RESERVE_OUTPUT, dests, 1, &to));
}
}
if (!oneOutput.scriptPubKey.size())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Failure to make currency output");
}
tOutputs.push_back(SendManyRecipient(destStr, oneOutput.nAmount, "", oneOutput.scriptPubKey));
}
}
}
catch(const std::exception& e)
{
std::cerr << e.what() << '\n';
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameters.");
}
// Create operation and add to global queue
CMutableTransaction contextualTx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), height + 1);
std::shared_ptr<AsyncRPCQueue> q = getAsyncRPCQueue();
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(tb,
contextualTx,
sourceAddress,
tOutputs,
zOutputs,
hasZSource ? 1 : 0,
feeAmount,
uniOutputs,
true) );
q->addOperation(operation);
AsyncRPCOperationId operationId = operation->getId();
return operationId;
}
UniValue refundfailedlaunch(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() != 1)
{
throw runtime_error(
"refundfailedlaunch \"currencyid\"\n"
"\nRefunds any funds sent to the chain if they are eligible for refund.\n"
"This attempts to refund all transactions for all contributors.\n"
"\nArguments\n"
"\"currencyid\" (iaddress or full chain name, required) the chain to refund contributions to\n"
"\nResult:\n"
"\nExamples:\n"
+ HelpExampleCli("refundfailedlaunch", "\"currencyid\"")
+ HelpExampleRpc("refundfailedlaunch", "\"currencyid\"")
);
}
CheckPBaaSAPIsValid();
uint160 chainID;
{
LOCK(cs_main);
chainID = GetChainIDFromParam(params[0]);
}
if (chainID.IsNull())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid PBaaS name or currencyid");
}
if (chainID == ConnectedChains.ThisChain().GetID() || chainID == ConnectedChains.FirstNotaryChain().chainDefinition.GetID())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot refund the specified chain");
}
CTransaction lastImportTx;
std::vector<CTransaction> refundTxes;
std::string failReason;
//if (!RefundFailedLaunch(chainID, lastImportTx, refundTxes, failReason))
{
throw JSONRPCError(RPC_INVALID_REQUEST, failReason);
}
uint32_t consensusBranchId = CurrentEpochBranchId(chainActive.LastTip()->GetHeight(), Params().GetConsensus());
UniValue ret(UniValue::VARR);
CCoinsViewCache view(pcoinsTip);
// sign and commit the transactions
for (auto tx : refundTxes)
{
LOCK2(cs_main, mempool.cs);
CMutableTransaction newTx(tx);
// sign the transaction and submit
bool signSuccess;
for (int i = 0; i < tx.vin.size(); i++)
{
SignatureData sigdata;
CAmount value;
CScript outputScript;
if (tx.vin[i].prevout.hash == lastImportTx.GetHash())
{
value = lastImportTx.vout[tx.vin[i].prevout.n].nValue;
outputScript = lastImportTx.vout[tx.vin[i].prevout.n].scriptPubKey;
}
else
{
CCoinsViewCache view(pcoinsTip);
CCoins coins;
if (!view.GetCoins(tx.vin[i].prevout.hash, coins))
{
fprintf(stderr,"refundfailedlaunch: cannot get input coins from tx: %s, output: %d\n", tx.vin[i].prevout.hash.GetHex().c_str(), tx.vin[i].prevout.n);
LogPrintf("refundfailedlaunch: cannot get input coins from tx: %s, output: %d\n", tx.vin[i].prevout.hash.GetHex().c_str(), tx.vin[i].prevout.n);
break;
}
value = coins.vout[tx.vin[i].prevout.n].nValue;
outputScript = coins.vout[tx.vin[i].prevout.n].scriptPubKey;
}
signSuccess = ProduceSignature(TransactionSignatureCreator(pwalletMain, &tx, i, value, SIGHASH_ALL), outputScript, sigdata, consensusBranchId);
if (!signSuccess)
{
fprintf(stderr,"refundfailedlaunch: failure to sign refund transaction\n");
LogPrintf("refundfailedlaunch: failure to sign refund transaction\n");
break;
} else {
UpdateTransaction(newTx, i, sigdata);
}
}
if (signSuccess)
{
// push to local node and sync with wallets
CValidationState state;
bool fMissingInputs;
CTransaction signedTx(newTx);
if (!AcceptToMemoryPool(mempool, state, signedTx, false, &fMissingInputs)) {
if (state.IsInvalid()) {
fprintf(stderr,"refundfailedlaunch: rejected by memory pool for %s\n", state.GetRejectReason().c_str());
LogPrintf("refundfailedlaunch: rejected by memory pool for %s\n", state.GetRejectReason().c_str());
} else {
if (fMissingInputs) {
fprintf(stderr,"refundfailedlaunch: missing inputs\n");
LogPrintf("refundfailedlaunch: missing inputs\n");
}
else
{
fprintf(stderr,"refundfailedlaunch: rejected by memory pool for\n");
LogPrintf("refundfailedlaunch: rejected by memory pool for\n");
}
}
break;
}
else
{
RelayTransaction(signedTx);
ret.push_back(signedTx.GetHash().GetHex());
}
}
}
return ret;
}
UniValue getinitialcurrencystate(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() != 1)
{
throw runtime_error(
"getinitialcurrencystate \"name\"\n"
"\nReturns the total amount of preconversions that have been confirmed on the blockchain for the specified PBaaS chain.\n"
"This should be used to get information about chains that are not this chain, but are being launched by it.\n"
"\nArguments\n"
" \"name\" (string, required) name or chain ID of the chain to get the export transactions for\n"
"\nResult:\n"
" [\n"
" {\n"
" \"flags\" : n,\n"
" \"initialratio\" : n,\n"
" \"initialsupply\" : n,\n"
" \"emitted\" : n,\n"
" \"supply\" : n,\n"
" \"reserve\" : n,\n"
" \"currentratio\" : n,\n"
" },\n"
" ]\n"
"\nExamples:\n"
+ HelpExampleCli("getinitialcurrencystate", "name")
+ HelpExampleRpc("getinitialcurrencystate", "name")
);
}
CheckPBaaSAPIsValid();
LOCK(cs_main);
uint160 chainID = GetChainIDFromParam(params[0]);
if (chainID.IsNull())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid chain name or chain ID");
}
CCurrencyDefinition chainDef;
int32_t definitionHeight;
if (!GetCurrencyDefinition(chainID, chainDef, &definitionHeight))
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Chain " + params[0].get_str() + " not found");
}
return ConnectedChains.GetCurrencyState(chainDef.GetID(), chainDef.startBlock - 1).ToUniValue();
}
UniValue getcurrencystate(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() > 1)
{
throw runtime_error(
"getcurrencystate \"n\"\n"
"\nReturns the total amount of preconversions that have been confirmed on the blockchain for the specified chain.\n"
"\nArguments\n"
" \"n\" or \"m,n\" or \"m,n,o\" (int or string, optional) height or inclusive range with optional step at which to get the currency state\n"
" If not specified, the latest currency state and height is returned\n"
"\nResult:\n"
" [\n"
" {\n"
" \"height\": n,\n"
" \"blocktime\": n,\n"
" \"currencystate\": {\n"
" \"flags\" : n,\n"
" \"initialratio\" : n,\n"
" \"initialsupply\" : n,\n"
" \"emitted\" : n,\n"
" \"supply\" : n,\n"
" \"reserve\" : n,\n"
" \"currentratio\" : n,\n"
" \"}\n"
" },\n"
" ]\n"
"\nExamples:\n"
+ HelpExampleCli("getcurrencystate", "name")
+ HelpExampleRpc("getcurrencystate", "name")
);
}
throw JSONRPCError(RPC_METHOD_NOT_FOUND, "This function not yet implemented, use getcurrency or listcurrency");
CheckPBaaSAPIsValid();
uint64_t lStart;
uint64_t startEnd[3] = {0};
lStart = startEnd[1] = startEnd[0] = chainActive.LastTip() ? chainActive.LastTip()->GetHeight() : 1;
if (params.size() == 1)
{
if (uni_get_int(params[0], -1) == -1 && params[0].isStr())
{
Split(params[0].get_str(), startEnd, startEnd[0], 3);
}
}
if (startEnd[0] > startEnd[1])
{
startEnd[0] = startEnd[1];
}
if (startEnd[1] > lStart)
{
startEnd[1] = lStart;
}
if (startEnd[1] < startEnd[0])
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid block range for currency state");
}
if (startEnd[2] == 0)
{
startEnd[2] = 1;
}
if (startEnd[2] > INT_MAX)
{
startEnd[2] = INT_MAX;
}
uint32_t start = startEnd[0], end = startEnd[1], step = startEnd[2];
UniValue ret(UniValue::VARR);
for (int i = start; i <= end; i += step)
{
LOCK(cs_main);
CCoinbaseCurrencyState currencyState = ConnectedChains.GetCurrencyState(i);
UniValue entry(UniValue::VOBJ);
entry.push_back(Pair("height", i));
entry.push_back(Pair("blocktime", (uint64_t)chainActive.LastTip()->nTime));
CAmount price;
entry.push_back(Pair("currencystate", currencyState.ToUniValue()));
ret.push_back(entry);
}
return ret;
}
UniValue getsaplingtree(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() > 1)
{
throw runtime_error(
"getsaplingtree \"n\"\n"
"\nReturns the entries for a light wallet Sapling tree state.\n"
"\nArguments\n"
" \"n\" or \"m,n\" or \"m,n,o\" (int or string, optional) height or inclusive range with optional step at which to get the Sapling tree state\n"
" If not specified, the latest currency state and height is returned\n"
"\nResult:\n"
" [\n"
" {\n"
" \"network\": \"VRSC\",\n"
" \"height\": n,\n"
" \"hash\": \"hex\"\n"
" \"time\": n,\n"
" \"tree\": \"hex\"\n"
" },\n"
" ]\n"
"\nExamples:\n"
+ HelpExampleCli("getsaplingtree", "name")
+ HelpExampleRpc("getsaplingtree", "name")
);
}
uint64_t lStart;
uint64_t startEnd[3] = {0};
lStart = startEnd[1] = startEnd[0] = chainActive.LastTip() ? chainActive.LastTip()->GetHeight() : 1;
if (params.size() == 1)
{
if (uni_get_int(params[0], -1) == -1 && params[0].isStr())
{
Split(params[0].get_str(), startEnd, startEnd[0], 3);
}
}
if (startEnd[0] > startEnd[1])
{
startEnd[0] = startEnd[1];
}
if (startEnd[1] > lStart)
{
startEnd[1] = lStart;
}
if (startEnd[1] < startEnd[0])
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid block range for currency state");
}
if (startEnd[2] == 0)
{
startEnd[2] = 1;
}
if (startEnd[2] > INT_MAX)
{
startEnd[2] = INT_MAX;
}
uint32_t start = startEnd[0], end = startEnd[1], step = startEnd[2];
UniValue ret(UniValue::VARR);
LOCK(cs_main);
CCoinsViewCache view(pcoinsTip);
SaplingMerkleTree tree;
for (int i = start; i <= end; i += step)
{
CBlockIndex &blkIndex = *(chainActive[i]);
if (view.GetSaplingAnchorAt(blkIndex.hashFinalSaplingRoot, tree))
{
UniValue entry(UniValue::VOBJ);
entry.push_back(Pair("network", ConnectedChains.ThisChain().name));
entry.push_back(Pair("height", blkIndex.GetHeight()));
entry.push_back(Pair("hash", blkIndex.GetBlockHash().GetHex()));
entry.push_back(Pair("time", (uint64_t)chainActive.LastTip()->nTime));
std::vector<unsigned char> treeBytes = ::AsVector(tree);
entry.push_back(Pair("tree", HexBytes(treeBytes.data(), treeBytes.size())));
ret.push_back(entry);
}
}
return ret;
}
extern void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry);
CCurrencyDefinition ValidateNewUnivalueCurrencyDefinition(const UniValue &uniObj, uint32_t height, const uint160 systemID)
{
CCurrencyDefinition newCurrency(uniObj);
if (!newCurrency.IsValid())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid currency definition. see help.");
}
LOCK2(cs_main, pwalletMain->cs_wallet);
CCurrencyDefinition checkDef;
int32_t defHeight;
if (GetCurrencyDefinition(newCurrency.GetID(), checkDef, &defHeight, true) && !(newCurrency.GetID() == ASSETCHAINS_CHAINID && !defHeight))
{
throw JSONRPCError(RPC_INVALID_PARAMETER, newCurrency.name + " chain already defined. see help.");
}
bool currentChainDefinition = newCurrency.GetID() == ASSETCHAINS_CHAINID && !defHeight && _IsVerusActive();
if (newCurrency.parent.IsNull() && !currentChainDefinition)
{
throw JSONRPCError(RPC_INVALID_PARAMETER, newCurrency.name + " invalid chain name.");
}
for (auto &oneID : newCurrency.preAllocation)
{
if (currentChainDefinition)
{
newCurrency = checkDef;
}
else if (!CIdentity::LookupIdentity(CIdentityID(oneID.first)).IsValid())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "attempting to pre-allocate currency to a non-existent ID.");
}
}
// a new currency definition must spend an ID that currently has no active currency, which sets a semaphore that "blocks"
// that ID from having more than one at once. Before submitting the transaction, it must be properly signed by the primary authority.
// This also has the effect of piggybacking on the ID protocol's deconfliction between mined blocks to avoid name conflicts,
// as the ID can only have its bit set or unset by one transaction at any time and only as part of a transaction that changes the
// the state of a potentially active currency.
if (newCurrency.IsToken())
{
if (newCurrency.rewards.size())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "currency cannot be both a token and also specify a mining and staking rewards schedule.");
}
// if this is a token definition, set systemID
newCurrency.systemID = systemID;
}
else
{
// it is a PBaaS chain, and it is its own system, responsible for its own communication and currency control
newCurrency.systemID = newCurrency.GetID();
std::vector<CNodeData> vNodes;
UniValue nodeArr = find_value(uniObj, "nodes");
if (nodeArr.isArray() && nodeArr.size())
{
for (int i = 0; i < nodeArr.size(); i++)
{
CNodeData nd(nodeArr[i]);
if (nd.IsValid())
{
vNodes.push_back(nd);
if (vNodes.size() == 2)
{
break;
}
}
}
}
}
if (currentChainDefinition)
{
return newCurrency;
}
// refunding a currency after its launch is aborted, or shutting it down after the endblock has passed must be completed
// to fully decommission a blockchain and clear the active blockchain bit from an ID
//if (!newCurrency.startBlock || newCurrency.startBlock < (chainActive.Height() + PBAAS_MINSTARTBLOCKDELTA))
//{
// newCurrency.startBlock = chainActive.Height() + (PBAAS_MINSTARTBLOCKDELTA + 5); // give a little time to send the tx
//}
if (!newCurrency.startBlock || newCurrency.startBlock < (chainActive.Height() + 10))
{
newCurrency.startBlock = chainActive.Height() + 15; // give a little time to send the tx
}
if (newCurrency.endBlock && newCurrency.endBlock < (newCurrency.startBlock + CCurrencyDefinition::MIN_CURRENCY_LIFE))
{
throw JSONRPCError(RPC_INVALID_PARAMS, "If endblock (" + to_string(newCurrency.endBlock) +
") is specified, it must be at least " + to_string(CCurrencyDefinition::MIN_CURRENCY_LIFE) +
" blocks after startblock (" + to_string(newCurrency.startBlock) + ")\n");
}
if (!newCurrency.IsToken())
{
// if we have no emission parameters, this is not a PBaaS blockchain, it is a controlled or bridged token.
// controlled tokens can be centrally or algorithmically controlled.
if (newCurrency.rewards.empty())
{
throw JSONRPCError(RPC_INVALID_PARAMS, "A currency must either be based on a token protocol or must specify blockchain rewards, even if 0\n");
}
if (newCurrency.IsFractional())
{
throw JSONRPCError(RPC_INVALID_PARAMS, "Fractional currencies must be tokens.\n");
}
// we need to also be able to set PBaaS converter and gateway
if (!(newCurrency.options & newCurrency.OPTION_GATEWAY))
{
newCurrency.options |= newCurrency.OPTION_PBAAS;
}
}
// if this is a fractional reserve currency, ensure that all reserves are currently active
// with at least as long of a life as this currency and that at least one of the currencies
// is VRSC or VRSCTEST.
std::vector<CCurrencyDefinition> reserveCurrencies;
bool hasCoreReserve = false;
if (newCurrency.IsFractional())
{
if (newCurrency.currencies.empty())
{
throw JSONRPCError(RPC_INVALID_PARAMS, "Fractional reserve currencies must specify at least one reserve currency\n");
}
if (newCurrency.contributions.size() != newCurrency.currencies.size())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "All reserves must have non-zero initial contributions for each reserve for fractional currency " + newCurrency.name);
}
newCurrency.preconverted = newCurrency.contributions;
for (auto &currency : newCurrency.currencies)
{
if (currency == ASSETCHAINS_CHAINID)
{
hasCoreReserve = true;
}
if (newCurrency.systemID == currency)
{
continue;
}
reserveCurrencies.push_back(CCurrencyDefinition());
if (!GetCurrencyDefinition(currency, reserveCurrencies.back()) ||
(reserveCurrencies.back().launchSystemID == ASSETCHAINS_CHAINID && reserveCurrencies.back().startBlock >= height))
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "All reserve currencies of a fractional currency must be valid and past the start block " + EncodeDestination(CIdentityID(currency)));
}
if (reserveCurrencies.back().endBlock && (!newCurrency.endBlock || reserveCurrencies.back().endBlock < newCurrency.endBlock))
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Reserve currency " + EncodeDestination(CIdentityID(currency)) + " ends its life before the fractional currency's endblock");
}
if (!reserveCurrencies.back().CanBeReserve())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Currency " + EncodeDestination(CIdentityID(currency)) + " may not be used as a reserve");
}
}
if (!hasCoreReserve)
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Fractional currency requires a reserve of " + std::string(ASSETCHAINS_SYMBOL) + " in addition to any other reserves");
}
}
return newCurrency;
}
UniValue definecurrency(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() < 1 || params.size() > 2)
{
throw runtime_error(
"definecurrency '{\"name\": \"coinortokenname\", ..., \"nodes\":[{\"networkaddress\":\"identity\"},..]}'\\\n"
" '({\"name\": \"fractionalgatewayname\", ..., })'\n"
"\nThis defines a blockchain currency, either as an independent blockchain, or as a token on this blockchain. It also spends\n"
"the identity after which this currency is named and sets a bit indicating that it has a currently active blockchain in its name.\n"
"\nTo create a currency of any kind, the identity it is named after must be minted on the blockchain on which the currency is created.\n"
"Once a currency is activated for an identity name, the same symbol may not be reused for another currency or blockchain, even\n"
"if the identity is transferred, revoked or recovered, unless there is an endblock specified and the currency or blockchain has\n"
"deactivated as of that end block.\n"
"\nAll funds to start the currency and for initial conversion amounts must be available to spend from the identity with the same\n"
"name and ID as the currency being defined.\n"
"\nArguments\n"
" {\n"
" \"options\" : n, (int, optional) bits (in hexadecimal):\n"
" 1 = FRACTIONAL\n"
" 2 = IDRESTRICTED\n"
" 4 = IDSTAKING\n"
" 8 = IDREFERRALS\n"
" 0x10 = IDREFERRALSREQUIRED\n"
" 0x20 = TOKEN\n"
" 0x40 = CANBERESERVE\n"
" 0x100 = IS_PBAAS_CHAIN\n"
"\n"
" \"name\" : \"xxxx\", (string, required) name of existing identity with no active or pending blockchain\n"
" \"idregistrationprice\" : \"xx.xx\", (value, required) price of an identity in native currency\n"
" \"idreferrallevels\" : n, (int, required) how many levels ID referrals go back in reward\n"
" \"notaries\" : \"[identity,..]\", (list, optional) list of identities that are assigned as chain notaries\n"
" \"minnotariesconfirm\" : n, (int, optional) unique notary signatures required to confirm an auto-notarization\n"
" \"notarizationreward\" : \"xx.xx\", (value, required) default VRSC notarization reward total for first billing period\n"
" \"billingperiod\" : n, (int, optional) number of blocks in each billing period\n"
" \"proofprotocol\" : n, (int, optional) if 2, currency can be minted by whoever controls the ID\n"
" \"startblock\" : n, (int, optional) VRSC block must be notarized into block 1 of PBaaS chain, default curheight + 100\n"
" \"endblock\" : n, (int, optional) chain is considered inactive after this block height, and a new one may be started\n"
" \"currencies\" : \"[\"VRSC\",..]\", (list, optional) reserve currencies backing this chain in equal amounts\n"
" \"conversions\" : \"[\"xx.xx\",..]\", (list, optional) if present, must be same size as currencies. pre-launch conversion ratio overrides\n"
" \"minpreconversion\" : \"[\"xx.xx\",..]\", (list, optional) must be same size as currencies. minimum in each currency to launch\n"
" \"maxpreconversion\" : \"[\"xx.xx\",..]\", (list, optional) maximum in each currency allowed\n"
" \"initialcontributions\" : \"[\"xx.xx\",..]\", (list, optional) initial contribution in each currency\n"
" \"prelaunchdiscount\" : \"xx.xx\" (value, optional) for fractional reserve currencies less than 100%, discount on final price at launch\n"
" \"initialsupply\" : \"xx.xx\" (value, required for fractional) supply after conversion of contributions, before preallocation\n"
" \"prelaunchcarveout\" : \"0.xx\", (value, optional) identities and % of pre-converted amounts from each reserve currency\n"
" \"preallocations\" : \"[{\"identity\":xx.xx}..]\", (list, optional) list of identities and amounts from pre-allocation\n"
" \"gatewayconvertername\" : \"name\", (string, optional) if this is a PBaaS chain, this names a co-launched gateway converter currency\n"
" \"eras\" : \"objarray\", (array, optional) data specific to each era, maximum 3\n"
" {\n"
" \"reward\" : n, (int64, required) native initial block rewards in each period\n"
" \"decay\" : n, (int64, optional) reward decay for each era\n"
" \"halving\" : n, (int, optional) halving period for each era\n"
" \"eraend\" : n, (int, optional) ending block of each era\n"
" }\n"
" \"nodes\" : \"[obj, ..]\", (objectarray, optional) up to 5 nodes that can be used to connect to the blockchain"
" [{\n"
" \"networkaddress\" : \"ip:port\", (string, optional) internet, TOR, or other supported address for node\n"
" \"nodeidentity\" : \"name@\", (string, optional) published node identity\n"
" }, .. ]\n"
" }\n"
"\nResult:\n"
"{\n"
" \"txid\" : \"transactionid\", (string) The transaction id\n"
" \"tx\" : \"json\", (json) The transaction decoded as a transaction\n"
" \"hex\" : \"data\" (string) Raw data for signed transaction\n"
"}\n"
"\nExamples:\n"
+ HelpExampleCli("definecurrency", "jsondefinition")
+ HelpExampleRpc("definecurrency", "jsondefinition")
);
}
CheckPBaaSAPIsValid();
bool isVerusActive = IsVerusActive();
uint160 thisChainID = ConnectedChains.ThisChain().GetID();
if (!params[0].isObject())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "JSON object required. see help.");
}
if (!pwalletMain)
{
throw JSONRPCError(RPC_WALLET_ERROR, "must have active wallet to define PBaaS chain");
}
UniValue valStr(UniValue::VSTR);
if (!valStr.read(params[0].write()))
{
throw JSONRPCError(RPC_INVALID_PARAMS, "Invalid characters in blockchain definition");
}
LOCK2(cs_main, pwalletMain->cs_wallet);
uint32_t height = chainActive.Height();
CCurrencyDefinition newChain(ValidateNewUnivalueCurrencyDefinition(params[0], height, ASSETCHAINS_CHAINID));
if ((newChain.GetID() == ASSETCHAINS_CHAINID && ASSETCHAINS_CHAINID != VERUS_CHAINID) ||
(newChain.parent != thisChainID && !(newChain.GetID() == ASSETCHAINS_CHAINID && newChain.parent.IsNull())))
{
// parent chain must be current chain or be VRSC or VRSCTEST registered by the owner of the associated ID
throw JSONRPCError(RPC_INVALID_PARAMETER, "attempting to define a chain relative to a parent that is not the current chain.");
}
uint160 newChainID = newChain.GetID();
CIdentity launchIdentity;
uint32_t idHeight = 0;
CTxIn idTxIn;
bool canSign = false, canSpend = false;
std::pair<CIdentityMapKey, CIdentityMapValue> keyAndIdentity;
if (pwalletMain->GetIdentity(newChainID, keyAndIdentity))
{
canSign = keyAndIdentity.first.flags & keyAndIdentity.first.CAN_SIGN;
canSpend = keyAndIdentity.first.flags & keyAndIdentity.first.CAN_SPEND;
launchIdentity = static_cast<CIdentity>(keyAndIdentity.second);
}
if (!canSign)
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot sign for ID " + newChain.name);
}
if (!(launchIdentity = CIdentity::LookupIdentity(newChainID, 0, &idHeight, &idTxIn)).IsValidUnrevoked() || launchIdentity.HasActiveCurrency())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "ID " + newChain.name + " not found, is revoked, or already has an active currency defined");
}
if (launchIdentity.parent != ASSETCHAINS_CHAINID && !(isVerusActive && newChain.GetID() == ASSETCHAINS_CHAINID))
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Currency can only be defined using an ID issued by " + VERUS_CHAINNAME);
}
CTransaction idTx;
uint256 blockHash;
if (!GetTransaction(idTxIn.prevout.hash, idTx, blockHash, true))
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot load ID transaction for " + VERUS_CHAINNAME);
}
TransactionBuilder tb = TransactionBuilder(Params().GetConsensus(), height + 1, pwalletMain);
tb.AddTransparentInput(idTxIn.prevout, idTx.vout[idTxIn.prevout.n].scriptPubKey, idTx.vout[idTxIn.prevout.n].nValue);
// if this is a PBaaS chain definition, and we have a gateway converter currency to also start,
// validate and start the converter currency as well
CCurrencyDefinition newGatewayConverter;
std::vector<CNodeData> startupNodes;
if (newChain.IsPBaaSChain() || newChain.IsGateway())
{
UniValue launchNodesUni = find_value(params[0], "nodes");
if (launchNodesUni.isArray() && launchNodesUni.size())
{
for (int i = 0; i < launchNodesUni.size(); i++)
{
std::string networkAddress;
std::string nodeIdentity;
CNodeData oneNode;
if (launchNodesUni[i].isObject() &&
(oneNode = CNodeData(launchNodesUni[i])).IsValid())
{
startupNodes.push_back(oneNode);
}
}
}
if (startupNodes.size() > CCurrencyDefinition::MAX_STARTUP_NODES)
{
startupNodes.resize(CCurrencyDefinition::MAX_STARTUP_NODES);
}
if (!startupNodes.size())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Must specify valid, initial launch nodes for a PBaaS chain");
}
if (!newChain.gatewayConverterName.empty())
{
// create a set of default, necessary parameters
// then apply the parameters passed, which will simplify the
// specification with defaults
std::map<std::string, UniValue> gatewayConverterMap;
gatewayConverterMap.insert(std::make_pair("options", CCurrencyDefinition::OPTION_CANBERESERVE +
CCurrencyDefinition::OPTION_FRACTIONAL +
CCurrencyDefinition::OPTION_TOKEN +
CCurrencyDefinition::OPTION_PBAAS_CONVERTER));
gatewayConverterMap.insert(std::make_pair("parent", EncodeDestination(CIdentityID(newChainID))));
gatewayConverterMap.insert(std::make_pair("name", newChain.gatewayConverterName));
gatewayConverterMap.insert(std::make_pair("launchsystemid", EncodeDestination(CIdentityID(thisChainID))));
// if this is a gateway, the converter runs on the launching chain by defaylt
// if PBaaS chain, on the new system
if (newChain.IsGateway())
{
gatewayConverterMap.insert(std::make_pair("systemid", EncodeDestination(CIdentityID(thisChainID))));
}
else
{
gatewayConverterMap.insert(std::make_pair("systemid", EncodeDestination(CIdentityID(newChainID))));
}
UniValue currenciesUni(UniValue::VARR);
currenciesUni.push_back(EncodeDestination(CIdentityID(thisChainID)));
currenciesUni.push_back(EncodeDestination(CIdentityID(newChainID)));
gatewayConverterMap.insert(std::make_pair("currencies", currenciesUni));
if (params.size() > 1)
{
auto curKeys = params[1].getKeys();
auto curValues = params[1].getValues();
for (int i = 0; i < curKeys.size(); i++)
{
gatewayConverterMap[curKeys[i]] = curValues[i];
}
}
// set start block and gateway converter issuance
gatewayConverterMap["startblock"] = newChain.startBlock;
gatewayConverterMap["gatewayconverterissuance"] = newChain.gatewayConverterIssuance;
UniValue newCurUni(UniValue::VOBJ);
for (auto oneProp : gatewayConverterMap)
{
newCurUni.pushKV(oneProp.first, oneProp.second);
}
uint32_t converterOptions = uni_get_int64(gatewayConverterMap["options"]);
converterOptions &= ~(CCurrencyDefinition::OPTION_GATEWAY + CCurrencyDefinition::OPTION_PBAAS);
converterOptions |= CCurrencyDefinition::OPTION_FRACTIONAL +
CCurrencyDefinition::OPTION_TOKEN +
CCurrencyDefinition::OPTION_PBAAS_CONVERTER;
gatewayConverterMap["options"] = (int64_t)converterOptions;
//printf("%s: gatewayConverter definition:\n%s\n", __func__, newCurUni.write(1,2).c_str());
// set the parent and system of the new gateway converter to the new currency
newGatewayConverter = ValidateNewUnivalueCurrencyDefinition(newCurUni, height, newChainID);
// check that basics are correct, fractional that includes correct currencies, etc.
if (newGatewayConverter.parent != newChainID ||
!newGatewayConverter.IsFractional() ||
!newGatewayConverter.IsToken())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "A gateway currency must have the PBaaS chain as parent and be a fractional token");
}
auto currencyMap = newGatewayConverter.GetCurrenciesMap();
if (!currencyMap.count(ASSETCHAINS_CHAINID) ||
!currencyMap.count(newChainID) ||
newGatewayConverter.weights[currencyMap[ASSETCHAINS_CHAINID]] < (SATOSHIDEN / 10) ||
newGatewayConverter.weights[currencyMap[newChainID]] < (SATOSHIDEN / 10))
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "A gateway currency must be a fractional token that includes both the launch coin and PBaaS native coin at 10% or greater ratio each");
}
}
}
else if (!newChain.gatewayConverterName.empty() || params.size() > 1)
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "A second currency definition is only supported as a gateway currency for a PBaaS chain");
}
// now, we have one or two currencies. if we have two, it is because the first is a PBaaS chain, and the second
// is its gateway currency. We will create an ID and currency launch definition for the new currency. The ID will
// be controlled by the same primary addresses and have the same revocation and recovery IDs as the primary ID.
//
// Create the outputs:
// 1. Updated identity with active currency
// 2. Currency definition
// 3. Notarization thread
// 4. Export thread - working to deprecate
// 4. Import thread (if PBaaS, this is for imports from the PBaaS chain)
// 5. Initial contribution exports
// (optional for PBaaS chain):
// 6. Gateway currency ID with active currency
// 7. Gateway currency definition for start on the new PBaaS chain, pre-launching from current chain
// 3. Gateway notarization thread
// 4. Gateway export thread - working to deprecate
// 8. Gateway import thread (for imports to gateway currency from PBaaS chain for this chain as well)
// ensure that the appropriate identity is an input to the transaction,
// and fund the transaction
// first, we need the identity output with currency activated
launchIdentity.ActivateCurrency();
tb.AddTransparentOutput(launchIdentity.IdentityUpdateOutputScript(height + 1), 0);
// now, create the currency definition output
CCcontract_info CC;
CCcontract_info *cp;
cp = CCinit(&CC, EVAL_CURRENCY_DEFINITION);
CPubKey pk(ParseHex(CC.CChexstr));
std::vector<CTxDestination> dests({pk});
tb.AddTransparentOutput(MakeMofNCCScript(CConditionObj<CCurrencyDefinition>(EVAL_CURRENCY_DEFINITION, dests, 1, &newChain)),
CCurrencyDefinition::DEFAULT_OUTPUT_VALUE);
// create import and export outputs
cp = CCinit(&CC, EVAL_CROSSCHAIN_IMPORT);
pk = CPubKey(ParseHex(CC.CChexstr));
if (newChain.proofProtocol == newChain.PROOF_PBAASMMR ||
newChain.proofProtocol == newChain.PROOF_ETHNOTARIZATION ||
newChain.proofProtocol == newChain.PROOF_CHAINID)
{
dests = std::vector<CTxDestination>({pk.GetID()});
}
else
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid notarization protocol specified");
}
uint32_t lastImportHeight = newChain.IsPBaaSChain() || newChain.IsGateway() ? 1 : height;
CCrossChainImport cci = CCrossChainImport(newChain.systemID, lastImportHeight, newChainID, CCurrencyValueMap(), CCurrencyValueMap());
cci.SetSameChain(newChain.systemID == ASSETCHAINS_CHAINID);
cci.SetDefinitionImport(true);
if (newChainID == ASSETCHAINS_CHAINID)
{
cci.SetPostLaunch();
cci.SetInitialLaunchImport();
}
cci.exportTxOutNum = tb.mtx.vout.size() + 2;
tb.AddTransparentOutput(MakeMofNCCScript(CConditionObj<CCrossChainImport>(EVAL_CROSSCHAIN_IMPORT, dests, 1, &cci)), 0);
// get initial currency state at this height
CCoinbaseCurrencyState newCurrencyState = ConnectedChains.GetCurrencyState(newChain, chainActive.Height());
newCurrencyState.SetPrelaunch();
CPBaaSNotarization pbn = CPBaaSNotarization(newChainID,
newCurrencyState,
height,
CUTXORef(),
0);
pbn.SetSameChain();
pbn.SetPreLaunch();
pbn.SetDefinitionNotarization();
pbn.nodes = startupNodes;
if (newCurrencyState.GetID() == ASSETCHAINS_CHAINID)
{
newChain.startBlock = 1;
newCurrencyState.SetPrelaunch(false);
newCurrencyState.SetLaunchConfirmed();
newCurrencyState.SetLaunchCompleteMarker();
pbn.SetPreLaunch(false);
pbn.SetLaunchCleared();
pbn.SetLaunchConfirmed();
pbn.SetLaunchComplete();
}
// make the first chain notarization output
cp = CCinit(&CC, EVAL_ACCEPTEDNOTARIZATION);
CTxDestination notarizationDest;
if (newChain.notarizationProtocol == newChain.NOTARIZATION_AUTO || newChain.notarizationProtocol == newChain.NOTARIZATION_NOTARY_CONFIRM)
{
notarizationDest = CPubKey(ParseHex(CC.CChexstr));
}
else if (newChain.notarizationProtocol == newChain.NOTARIZATION_NOTARY_CHAINID)
{
notarizationDest = CIdentityID(newChainID);
}
else
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "None or notarization protocol specified");
}
dests = std::vector<CTxDestination>({notarizationDest});
tb.AddTransparentOutput(MakeMofNCCScript(CConditionObj<CPBaaSNotarization>(EVAL_ACCEPTEDNOTARIZATION, dests, 1, &pbn)),
CPBaaSNotarization::MIN_NOTARIZATION_OUTPUT);
// export thread
cp = CCinit(&CC, EVAL_CROSSCHAIN_EXPORT);
dests = std::vector<CTxDestination>({CPubKey(ParseHex(CC.CChexstr))});
CAmount mainImportFee = ConnectedChains.ThisChain().LaunchFeeImportShare(newChain.options);
CCurrencyValueMap mainImportFees(std::vector<uint160>({thisChainID}), std::vector<CAmount>({mainImportFee}));
CAmount converterImportFee = 0;
CCurrencyValueMap converterImportFees;
CCrossChainExport ccx = CCrossChainExport(thisChainID, 0, height, newChain.systemID, newChainID, 0, mainImportFees, mainImportFees, uint256());
ccx.SetChainDefinition();
if (newCurrencyState.GetID() == ASSETCHAINS_CHAINID)
{
ccx.SetPreLaunch(false);
ccx.SetPostLaunch();
}
else
{
ccx.SetPreLaunch();
}
tb.AddTransparentOutput(MakeMofNCCScript(CConditionObj<CCrossChainExport>(EVAL_CROSSCHAIN_EXPORT, dests, 1, &ccx)), 0);
// make the outputs for initial contributions
if (newChain.contributions.size() && newChain.contributions.size() == newChain.currencies.size())
{
for (int i = 0; i < newChain.currencies.size(); i++)
{
if (newChain.contributions[i] > 0)
{
CAmount contribution = newChain.contributions[i] +
CReserveTransactionDescriptor::CalculateAdditionalConversionFee(newChain.contributions[i]);
CAmount fee = CReserveTransfer::DEFAULT_PER_STEP_FEE << 1;
CReserveTransfer rt = CReserveTransfer(CReserveTransfer::VALID + CReserveTransfer::PRECONVERT,
newChain.currencies[i],
contribution,
ASSETCHAINS_CHAINID,
fee,
newChainID,
DestinationToTransferDestination(CIdentityID(newChainID)));
cp = CCinit(&CC, EVAL_RESERVE_TRANSFER);
CPubKey pk(ParseHex(CC.CChexstr));
dests = std::vector<CTxDestination>({pk});
tb.AddTransparentOutput(MakeMofNCCScript(CConditionObj<CReserveTransfer>(EVAL_RESERVE_TRANSFER, dests, 1, &rt)),
newChain.currencies[i] == thisChainID ? contribution + fee : fee);
}
}
}
// now, setup the gateway converter currency, if appropriate
if ((newChain.IsPBaaSChain() || newChain.IsGateway()) && newGatewayConverter.IsValid())
{
newGatewayConverter.gatewayConverterIssuance = newChain.gatewayConverterIssuance;
cp = CCinit(&CC, EVAL_CURRENCY_DEFINITION);
std::vector<CTxDestination> dests({CPubKey(ParseHex(CC.CChexstr))});
tb.AddTransparentOutput(MakeMofNCCScript(CConditionObj<CCurrencyDefinition>(EVAL_CURRENCY_DEFINITION, dests, 1, &newGatewayConverter)),
CCurrencyDefinition::DEFAULT_OUTPUT_VALUE);
// get initial currency state at this height
CCoinbaseCurrencyState gatewayCurrencyState = ConnectedChains.GetCurrencyState(newGatewayConverter, chainActive.Height());
int currencyIndex = gatewayCurrencyState.GetReserveMap()[newChainID];
gatewayCurrencyState.reserveIn[currencyIndex] += newChain.gatewayConverterIssuance;
uint160 gatewayCurrencyID = newGatewayConverter.GetID();
CPBaaSNotarization gatewayPbn = CPBaaSNotarization(gatewayCurrencyID,
gatewayCurrencyState,
height,
CUTXORef(),
0);
// launch notarizations are on this chain
gatewayPbn.SetSameChain();
gatewayPbn.SetPreLaunch();
gatewayPbn.SetDefinitionNotarization();
// create import and export outputs
cp = CCinit(&CC, EVAL_CROSSCHAIN_IMPORT);
pk = CPubKey(ParseHex(CC.CChexstr));
if (newGatewayConverter.proofProtocol == newGatewayConverter.PROOF_PBAASMMR || newGatewayConverter.proofProtocol == newGatewayConverter.PROOF_CHAINID)
{
dests = std::vector<CTxDestination>({pk.GetID()});
}
else
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "None or notarization protocol specified");
}
// if this is a token on this chain, the transfer that is output here is burned through the export
// and merges with the import thread. we multiply new input times 2, to cover both the import thread output
// and the reserve transfer outputs.
CCrossChainImport gatewayCci = CCrossChainImport(newGatewayConverter.systemID, lastImportHeight, gatewayCurrencyID, CCurrencyValueMap(), CCurrencyValueMap());
gatewayCci.SetSameChain(newGatewayConverter.systemID == ASSETCHAINS_CHAINID);
gatewayCci.SetDefinitionImport(true);
tb.AddTransparentOutput(MakeMofNCCScript(CConditionObj<CCrossChainImport>(EVAL_CROSSCHAIN_IMPORT, dests, 1, &gatewayCci)), 0);
// make the first chain notarization output
cp = CCinit(&CC, EVAL_ACCEPTEDNOTARIZATION);
CTxDestination notarizationDest;
if (newGatewayConverter.notarizationProtocol == newGatewayConverter.NOTARIZATION_AUTO ||
newGatewayConverter.notarizationProtocol == newGatewayConverter.NOTARIZATION_NOTARY_CONFIRM)
{
notarizationDest = CPubKey(ParseHex(CC.CChexstr));
}
else if (newGatewayConverter.notarizationProtocol == newGatewayConverter.NOTARIZATION_NOTARY_CHAINID)
{
notarizationDest = CIdentityID(gatewayCurrencyID);
}
else
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "None or notarization protocol specified");
}
dests = std::vector<CTxDestination>({notarizationDest});
tb.AddTransparentOutput(MakeMofNCCScript(CConditionObj<CPBaaSNotarization>(EVAL_ACCEPTEDNOTARIZATION, dests, 1, &gatewayPbn)),
CPBaaSNotarization::MIN_NOTARIZATION_OUTPUT);
converterImportFee = ConnectedChains.ThisChain().LaunchFeeImportShare(newGatewayConverter.options);
converterImportFees.valueMap[thisChainID] += converterImportFee;
// export thread
cp = CCinit(&CC, EVAL_CROSSCHAIN_EXPORT);
dests = std::vector<CTxDestination>({CPubKey(ParseHex(CC.CChexstr))});
CCrossChainExport gatewayCcx = CCrossChainExport(thisChainID, 0, height, newGatewayConverter.systemID, gatewayCurrencyID, 0, converterImportFees, converterImportFees, uint256());
gatewayCcx.SetPreLaunch();
gatewayCcx.SetChainDefinition();
tb.AddTransparentOutput(MakeMofNCCScript(CConditionObj<CCrossChainExport>(EVAL_CROSSCHAIN_EXPORT, dests, 1, &gatewayCcx)), 0);
// make the outputs for initial contributions
if (newGatewayConverter.contributions.size() && newGatewayConverter.contributions.size() == newGatewayConverter.currencies.size())
{
for (int i = 0; i < newGatewayConverter.currencies.size(); i++)
{
if (newGatewayConverter.contributions[i] > 0)
{
CAmount contribution = newGatewayConverter.contributions[i] +
CReserveTransactionDescriptor::CalculateAdditionalConversionFee(newGatewayConverter.contributions[i]);
CAmount fee = CReserveTransfer::DEFAULT_PER_STEP_FEE << 1;
CReserveTransfer rt = CReserveTransfer(CReserveTransfer::VALID + CReserveTransfer::PRECONVERT,
newGatewayConverter.currencies[i],
contribution,
ASSETCHAINS_CHAINID,
fee,
gatewayCurrencyID,
DestinationToTransferDestination(CIdentityID(gatewayCurrencyID)));
cp = CCinit(&CC, EVAL_RESERVE_TRANSFER);
CPubKey pk(ParseHex(CC.CChexstr));
dests = std::vector<CTxDestination>({pk});
tb.AddTransparentOutput(MakeMofNCCScript(CConditionObj<CReserveTransfer>(EVAL_RESERVE_TRANSFER, dests, 1, &rt)),
newGatewayConverter.currencies[i] == thisChainID ? contribution + fee : fee);
}
}
}
}
// figure all launch fees, including export and import
CAmount totalLaunchFee = ConnectedChains.ThisChain().GetCurrencyRegistrationFee(newChain.options);
if (converterImportFee)
{
totalLaunchFee += ConnectedChains.ThisChain().GetCurrencyRegistrationFee(newGatewayConverter.options);
}
CAmount totalLaunchExportFee = totalLaunchFee - (mainImportFee + converterImportFee);
if (newCurrencyState.GetID() != ASSETCHAINS_CHAINID)
{
cp = CCinit(&CC, EVAL_RESERVE_DEPOSIT);
pk = CPubKey(ParseHex(CC.CChexstr));
dests = std::vector<CTxDestination>({pk});
CReserveDeposit launchDeposit = CReserveDeposit(newChainID, CCurrencyValueMap());
tb.AddTransparentOutput(MakeMofNCCScript(CConditionObj<CReserveDeposit>(EVAL_RESERVE_DEPOSIT, dests, 1, &launchDeposit)),
mainImportFee);
}
tb.SetFee(totalLaunchExportFee);
tb.SendChangeTo(launchIdentity.GetID());
if (newGatewayConverter.IsValid())
{
uint160 gatewayDepositCurrencyID = newGatewayConverter.systemID == thisChainID ?
newGatewayConverter.GetID() :
newGatewayConverter.systemID;
CReserveDeposit launchDeposit = CReserveDeposit(gatewayDepositCurrencyID, CCurrencyValueMap());
tb.AddTransparentOutput(MakeMofNCCScript(CConditionObj<CReserveDeposit>(EVAL_RESERVE_DEPOSIT, dests, 1, &launchDeposit)),
converterImportFee);
}
// create the transaction
CReserveTransactionDescriptor rtxd;
{
LOCK(mempool.cs);
CCoinsView dummy;
CCoinsViewCache view(&dummy);
CCoinsViewMemPool viewMemPool(pcoinsTip, mempool);
view.SetBackend(viewMemPool);
rtxd = CReserveTransactionDescriptor(tb.mtx, view, height + 1);
}
// get a native currency input capable of paying a fee, and make our notary ID the change address
std::set<std::pair<const CWalletTx *, unsigned int>> setCoinsRet;
std::vector<COutput> vCoins;
CCurrencyValueMap totalReservesNeeded = rtxd.ReserveOutputMap();
CCurrencyValueMap totalCurrenciesNeeded = totalReservesNeeded;
totalCurrenciesNeeded.valueMap[ASSETCHAINS_CHAINID] = rtxd.nativeOut + totalLaunchExportFee;
CTxDestination fromID(CIdentityID(launchIdentity.GetID()));
pwalletMain->AvailableReserveCoins(vCoins,
false,
nullptr,
true,
true,
&fromID,
&totalCurrenciesNeeded,
false);
CCurrencyValueMap reservesUsed;
CAmount nativeUsed;
if (!pwalletMain->SelectReserveCoinsMinConf(rtxd.ReserveOutputMap(),
rtxd.nativeOut + totalLaunchExportFee,
0,
1,
vCoins,
setCoinsRet,
reservesUsed,
nativeUsed))
{
throw JSONRPCError(RPC_TRANSACTION_ERROR, "Insufficient funds held by " + launchIdentity.name + " identity.");
}
for (auto &oneInput : setCoinsRet)
{
tb.AddTransparentInput(COutPoint(oneInput.first->GetHash(), oneInput.second),
oneInput.first->vout[oneInput.second].scriptPubKey,
oneInput.first->vout[oneInput.second].nValue);
}
auto builtTxResult = tb.Build();
if (!builtTxResult.IsTx())
{
throw JSONRPCError(RPC_TRANSACTION_ERROR, newChain.name + ": " + builtTxResult.GetError());
}
CTransaction retTx = builtTxResult.GetTxOrThrow();
UniValue uvret(UniValue::VOBJ);
UniValue txJSon(UniValue::VOBJ);
TxToJSON(retTx, uint256(), txJSon);
uvret.push_back(Pair("tx", txJSon));
string strHex = EncodeHexTx(retTx);
uvret.push_back(Pair("hex", strHex));
return uvret;
}
UniValue registernamecommitment(const UniValue& params, bool fHelp)
{
if (fHelp || (params.size() < 2 && params.size() > 3))
{
throw runtime_error(
"registernamecommitment \"name\" \"controladdress\" (\"referralidentity\")\n"
"\nRegisters a name commitment, which is required as a source for the name to be used when registering an identity. The name commitment hides the name itself\n"
"while ensuring that the miner who mines in the registration cannot front-run the name unless they have also registered a name commitment for the same name or\n"
"are willing to forfeit the offer of payment for the chance that a commitment made now will allow them to register the name in the future.\n"
"\nArguments\n"
"\"name\" (string, required) the unique name to commit to. creating a name commitment is not a registration, and if one is\n"
" created for a name that exists, it may succeed, but will never be able to be used.\n"
"\"controladdress\" (address, required) address that will control this commitment\n"
"\"referralidentity\" (identity, optional)friendly name or identity address that is provided as a referral mechanism and to lower network cost of the ID\n"
"\nResult: obj\n"
"{\n"
" \"txid\" : \"hexid\"\n"
" \"namereservation\" :\n"
" {\n"
" \"name\" : \"namestr\", (string) the unique name in this commitment\n"
" \"salt\" : \"hexstr\", (hex) salt used to hide the commitment\n"
" \"referral\": \"identityaddress\", (base58) address of the referring identity if there is one\n"
" \"parent\" : \"namestr\", (string) name of the parent if not Verus or Verus test\n"
" \"nameid\" : \"address\", (base58) identity address for this identity if it is created\n"
" }\n"
"}\n"
"\nExamples:\n"
+ HelpExampleCli("registernamecommitment", "\"name\"")
+ HelpExampleRpc("registernamecommitment", "\"name\"")
);
}
CheckIdentityAPIsValid();
uint160 parent;
std::string name = CleanName(uni_get_str(params[0]), parent, true, false);
uint160 idID = GetDestinationID(DecodeDestination(name + "@"));
if (idID == ASSETCHAINS_CHAINID &&
IsVerusActive())
{
name = VERUS_CHAINNAME;
}
else
{
parent = ASSETCHAINS_CHAINID;
}
// if either we have an invalid name or an implied parent, that is not valid
if (!(idID == VERUS_CHAINID && IsVerusActive() && parent.IsNull()) &&
(name == "" || parent != ASSETCHAINS_CHAINID || name != uni_get_str(params[0])))
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid name for commitment. Names must not have leading or trailing spaces and must not include any of the following characters between parentheses (\\/:*?\"<>|@)");
}
CTxDestination dest = DecodeDestination(uni_get_str(params[1]));
if (dest.which() == COptCCParams::ADDRTYPE_INVALID)
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid control address for commitment");
}
// create the transaction with native coin as input
LOCK2(cs_main, pwalletMain->cs_wallet);
CIdentityID referrer;
if (params.size() > 2)
{
CTxDestination referDest = DecodeDestination(uni_get_str(params[2]));
if (referDest.which() != COptCCParams::ADDRTYPE_ID)
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid referral identity for commitment, must be a currently registered friendly name or i-address");
}
referrer = CIdentityID(GetDestinationID(referDest));
CIdentity referrerIdentity = CIdentity::LookupIdentity(referrer);
if (!referrerIdentity.IsValidUnrevoked())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Referral identity for commitment must be a currently valid, unrevoked friendly name or i-address");
}
if (referrerIdentity.parent != ASSETCHAINS_CHAINID)
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Referrals cannot refer to the chain identity or an identity defined on another chain");
}
}
CNameReservation nameRes(name, referrer, GetRandHash());
CCommitmentHash commitment(nameRes.GetCommitment());
CConditionObj<CCommitmentHash> condObj(EVAL_IDENTITY_COMMITMENT, std::vector<CTxDestination>({dest}), 1, &commitment);
std::vector<CRecipient> outputs = std::vector<CRecipient>({{MakeMofNCCScript(condObj, &dest), CCommitmentHash::DEFAULT_OUTPUT_AMOUNT, false}});
CWalletTx wtx;
if (CIdentity::LookupIdentity(CIdentity::GetID(name, parent)).IsValid())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Identity already exists.");
}
CReserveKey reserveKey(pwalletMain);
CAmount fee;
int nChangePos;
string failReason;
if (!pwalletMain->CreateTransaction(outputs, wtx, reserveKey, fee, nChangePos, failReason))
{
throw JSONRPCError(RPC_TRANSACTION_ERROR, "Failed to create commitment transaction: " + failReason);
}
if (!pwalletMain->CommitTransaction(wtx, reserveKey))
{
throw JSONRPCError(RPC_TRANSACTION_ERROR, "Could not commit transaction " + wtx.GetHash().GetHex());
}
UniValue ret(UniValue::VOBJ);
ret.push_back(Pair("txid", wtx.GetHash().GetHex()));
ret.push_back(Pair("namereservation", nameRes.ToUniValue()));
return ret;
}
UniValue registeridentity(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() < 1 || params.size() > 2)
{
throw runtime_error(
"registeridentity \"jsonidregistration\" feeoffer\n"
"\n\n"
"\nArguments\n"
"{\n"
" \"txid\" : \"hexid\", (hex) the transaction ID of the name commitment for this ID name\n"
" \"namereservation\" :\n"
" {\n"
" \"name\": \"namestr\", (string) the unique name in this commitment\n"
" \"salt\": \"hexstr\", (hex) salt used to hide the commitment\n"
" \"referrer\": \"identityID\", (name@ or address) must be a valid ID to use as a referrer to receive a discount\n"
" },\n"
" \"identity\" :\n"
" {\n"
" \"name\": \"namestr\", (string) the unique name for this identity\n"
" ...\n"
" }\n"
"}\n"
"feeoffer (amount, optional) amount to offer miner/staker for the registration fee, if missing, uses standard price\n\n"
"\nResult:\n"
" transactionid (hexstr)\n"
"\nExamples:\n"
+ HelpExampleCli("registeridentity", "jsonidregistration")
+ HelpExampleRpc("registeridentity", "jsonidregistration")
);
}
CheckIdentityAPIsValid();
// all names have a parent of the current chain
uint160 parent = ConnectedChains.ThisChain().GetID();
uint256 txid = uint256S(uni_get_str(find_value(params[0], "txid")));
CNameReservation reservation(find_value(params[0], "namereservation"));
// lookup commitment to be sure that we can register this identity
LOCK2(cs_main, pwalletMain->cs_wallet);
uint32_t height = chainActive.Height();
UniValue rawID = find_value(params[0], "identity");
CIdentity newID(rawID);
if (!newID.IsValid())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid identity");
}
if (CConstVerusSolutionVector::GetVersionByHeight(height + 1) >= CActivationHeight::ACTIVATE_PBAAS)
{
newID.SetVersion(CIdentity::VERSION_PBAAS);
}
else
{
newID.SetVersion(CIdentity::VERSION_VERUSID);
}
if (IsVerusActive())
{
CIdentity checkIdentity(newID);
checkIdentity.parent.SetNull();
if (checkIdentity.GetID() == ASSETCHAINS_CHAINID)
{
newID.parent.SetNull();
parent.SetNull();
}
}
else
{
newID.parent = parent;
}
newID.systemID = ASSETCHAINS_CHAINID;
uint160 newIDID = newID.GetID();
if (find_value(rawID, "revocationauthority").isNull())
{
newID.revocationAuthority = newID.GetID();
}
if (find_value(rawID, "recoveryauthority").isNull())
{
newID.recoveryAuthority = newID.GetID();
}
CAmount feeOffer;
CAmount minFeeOffer = reservation.referral.IsNull() ?
ConnectedChains.ThisChain().IDFullRegistrationAmount() :
ConnectedChains.ThisChain().IDReferredRegistrationAmount();
if (params.size() > 1)
{
feeOffer = AmountFromValue(params[1]);
}
else
{
feeOffer = minFeeOffer;
}
if (feeOffer < minFeeOffer)
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Fee offer must be at least " + ValueFromAmount(minFeeOffer).write());
}
uint160 impliedParent, resParent;
if (txid.IsNull() ||
CleanName(reservation.name, resParent) != CleanName(newID.name, impliedParent) ||
resParent != impliedParent)
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid identity description or mismatched reservation.");
}
uint256 hashBlk;
CTransaction txOut;
CCommitmentHash ch;
int commitmentOutput;
uint32_t commitmentHeight;
// make sure we have a revocation and recovery authority defined
CIdentity revocationAuth = newID.revocationAuthority == newIDID ? newID : newID.LookupIdentity(newID.revocationAuthority);
CIdentity recoveryAuth = newID.recoveryAuthority == newIDID ? newID : newID.LookupIdentity(newID.recoveryAuthority);
if (!recoveryAuth.IsValidUnrevoked() || !revocationAuth.IsValidUnrevoked())
{
if (newIDID == ASSETCHAINS_CHAINID)
{
revocationAuth = newID;
recoveryAuth = newID;
}
else
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid or revoked recovery, or revocation identity.");
}
}
// must be present and in a mined block
{
LOCK(mempool.cs);
if (!myGetTransaction(txid, txOut, hashBlk) || hashBlk.IsNull())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid or unconfirmed commitment transaction id");
}
auto indexIt = mapBlockIndex.find(hashBlk);
if (indexIt == mapBlockIndex.end() || indexIt->second->GetHeight() > chainActive.Height() || chainActive[indexIt->second->GetHeight()]->GetBlockHash() != indexIt->second->GetBlockHash())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid or unconfirmed commitment");
}
commitmentHeight = indexIt->second->GetHeight();
for (int i = 0; i < txOut.vout.size(); i++)
{
COptCCParams p;
if (txOut.vout[i].scriptPubKey.IsPayToCryptoCondition(p) && p.IsValid() && p.evalCode == EVAL_IDENTITY_COMMITMENT && p.vData.size())
{
commitmentOutput = i;
::FromVector(p.vData[0], ch);
break;
}
}
if (ch.hash.IsNull())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid commitment hash");
}
}
if (ch.hash != reservation.GetCommitment().hash)
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid commitment salt or referral ID");
}
// when creating an ID, the parent is generally the current chains, and it is invalid to specify a parent
if (newID.parent != parent)
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid to specify alternate parent when creating an identity. Parent is determined by the current blockchain.");
}
CIdentity dupID = newID.LookupIdentity(newID.GetID());
if (dupID.IsValid())
{
throw JSONRPCError(RPC_VERIFY_ALREADY_IN_CHAIN, "Identity already exists.");
}
// create the identity definition transaction & reservation key output
CConditionObj<CNameReservation> condObj(EVAL_IDENTITY_RESERVATION, std::vector<CTxDestination>({CIdentityID(newID.GetID())}), 1, &reservation);
std::vector<CRecipient> outputs = std::vector<CRecipient>({{newID.IdentityUpdateOutputScript(height), 0, false}});
// add referrals, Verus supports referrals
if ((ConnectedChains.ThisChain().IDReferrals() || IsVerusActive()) && !reservation.referral.IsNull())
{
uint32_t referralHeight;
CTxIn referralTxIn;
CTransaction referralIdTx;
auto referralIdentity = newID.LookupIdentity(reservation.referral, commitmentHeight - 1);
if (referralIdentity.IsValidUnrevoked() && referralIdentity.parent == ASSETCHAINS_CHAINID)
{
if (!newID.LookupFirstIdentity(reservation.referral, &referralHeight, &referralTxIn, &referralIdTx).IsValid())
{
throw JSONRPCError(RPC_DATABASE_ERROR, "Database or blockchain data error, \"" + referralIdentity.name + "\" seems valid, but first instance is not found in index");
}
// create outputs for this referral and up to n identities back in the referral chain
outputs.push_back({referralIdentity.TransparentOutput(referralIdentity.GetID()), ConnectedChains.ThisChain().IDReferralAmount(), false});
feeOffer -= ConnectedChains.ThisChain().IDReferralAmount();
if (referralHeight != 1)
{
int afterId = referralTxIn.prevout.n + 1;
for (int i = afterId; i < referralIdTx.vout.size() && (i - afterId) < (ConnectedChains.ThisChain().idReferralLevels - 1); i++)
{
CTxDestination nextID;
COptCCParams p, master;
if (referralIdTx.vout[i].scriptPubKey.IsPayToCryptoCondition(p) &&
p.IsValid() &&
p.evalCode == EVAL_NONE &&
p.vKeys.size() == 1 &&
(p.vData.size() == 1 ||
(p.vData.size() == 2 &&
p.vKeys[0].which() == COptCCParams::ADDRTYPE_ID &&
(master = COptCCParams(p.vData[1])).IsValid() &&
master.evalCode == EVAL_NONE)))
{
outputs.push_back({newID.TransparentOutput(CIdentityID(GetDestinationID(p.vKeys[0]))), ConnectedChains.ThisChain().IDReferralAmount(), false});
feeOffer -= ConnectedChains.ThisChain().IDReferralAmount();
}
else
{
break;
}
}
}
}
else
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid or revoked referral identity at time of commitment");
}
}
CScript reservationOutScript = MakeMofNCCScript(condObj);
outputs.push_back({reservationOutScript, CNameReservation::DEFAULT_OUTPUT_AMOUNT, false});
// make one dummy output, which CreateTransaction will leave as last, and we will remove to add its output to the fee
// this serves to keep the change output after our real reservation output
outputs.push_back({reservationOutScript, feeOffer, false});
CWalletTx wtx;
CReserveKey reserveKey(pwalletMain);
CAmount fee;
int nChangePos;
string failReason;
if (!pwalletMain->CreateTransaction(outputs, wtx, reserveKey, fee, nChangePos, failReason, nullptr, false))
{
throw JSONRPCError(RPC_TRANSACTION_ERROR, "Failed to create identity transaction: " + failReason);
}
// add commitment output
CMutableTransaction mtx(wtx);
mtx.vin.push_back(CTxIn(txid, commitmentOutput));
// remove the fee offer output
mtx.vout.pop_back();
*static_cast<CTransaction*>(&wtx) = CTransaction(mtx);
// now sign
CCoinsViewCache view(pcoinsTip);
for (int i = 0; i < wtx.vin.size(); i++)
{
bool signSuccess;
SignatureData sigdata;
CCoins coins;
if (!(view.GetCoins(wtx.vin[i].prevout.hash, coins) && coins.IsAvailable(wtx.vin[i].prevout.n)))
{
break;
}
CAmount value = coins.vout[wtx.vin[i].prevout.n].nValue;
signSuccess = ProduceSignature(TransactionSignatureCreator(pwalletMain, &wtx, i, value, coins.vout[wtx.vin[i].prevout.n].scriptPubKey), coins.vout[wtx.vin[i].prevout.n].scriptPubKey, sigdata, CurrentEpochBranchId(chainActive.Height(), Params().GetConsensus()));
if (!signSuccess)
{
LogPrintf("%s: failure to sign identity registration tx for input %d from output %d of %s\n", __func__, i, wtx.vin[i].prevout.n, wtx.vin[i].prevout.hash.GetHex().c_str());
printf("%s: failure to sign identity registration tx for input %d from output %d of %s\n", __func__, i, wtx.vin[i].prevout.n, wtx.vin[i].prevout.hash.GetHex().c_str());
throw JSONRPCError(RPC_TRANSACTION_ERROR, "Failed to sign transaction");
} else {
UpdateTransaction(mtx, i, sigdata);
}
}
*static_cast<CTransaction*>(&wtx) = CTransaction(mtx);
if (!pwalletMain->CommitTransaction(wtx, reserveKey))
{
throw JSONRPCError(RPC_TRANSACTION_ERROR, "Could not commit transaction " + wtx.GetHash().GetHex());
}
// including definitions and claims thread
return UniValue(wtx.GetHash().GetHex());
}
std::map<std::string, UniValue> UniObjectToMap(const UniValue &obj)
{
std::map<std::string, UniValue> retVal;
if (obj.isObject())
{
std::vector<std::string> keys = obj.getKeys();
std::vector<UniValue> values = obj.getValues();
for (int i = 0; i < keys.size(); i++)
{
retVal.insert(std::make_pair(keys[i], values[i]));
}
}
return retVal;
}
UniValue MapToUniObject(const std::map<std::string, UniValue> &uniMap)
{
UniValue retVal(UniValue::VOBJ);
for (auto &oneEl : uniMap)
{
retVal.pushKV(oneEl.first, oneEl.second);
}
return retVal;
}
UniValue updateidentity(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() < 1 || params.size() > 2)
{
throw runtime_error(
"updateidentity \"jsonidentity\" (returntx)\n"
"\n\n"
"\nArguments\n"
" \"returntx\" (bool, optional) defaults to false and transaction is sent, if true, transaction is signed by this wallet and returned\n"
"\nResult:\n"
"\nExamples:\n"
+ HelpExampleCli("updateidentity", "\'{\"name\" : \"myname\"}\'")
+ HelpExampleRpc("updateidentity", "\'{\"name\" : \"myname\"}\'")
);
}
CheckIdentityAPIsValid();
// get identity
bool returnTx = false;
if (params.size() > 1)
{
returnTx = uni_get_bool(params[1], false);
}
uint160 parentID = uint160(GetDestinationID(DecodeDestination(uni_get_str(find_value(params[0], "parent")))));
std::string nameStr = CleanName(uni_get_str(find_value(params[0], "name")), parentID);
uint160 newIDID = CIdentity::GetID(nameStr, parentID);
CTxIn idTxIn;
CIdentity oldID;
uint32_t idHeight;
LOCK2(cs_main, pwalletMain->cs_wallet);
uint32_t nHeight = chainActive.Height() + 1;
if (!(oldID = CIdentity::LookupIdentity(newIDID, 0, &idHeight, &idTxIn)).IsValid())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "identity, " + nameStr + " (" +EncodeDestination(CIdentityID(newIDID)) + "), not found ");
}
uint256 blkHash;
CTransaction oldIdTx;
if (!myGetTransaction(idTxIn.prevout.hash, oldIdTx, blkHash))
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "identity, " + nameStr + ", transaction not found ");
}
auto uniOldID = UniObjectToMap(oldID.ToUniValue());
// overwrite old elements
for (auto &oneEl : UniObjectToMap(params[0]))
{
uniOldID[oneEl.first] = oneEl.second;
}
if (CConstVerusSolutionVector::GetVersionByHeight(nHeight + 1) >= CActivationHeight::ACTIVATE_PBAAS)
{
uniOldID["version"] = (int64_t)oldID.VERSION_PBAAS;
if (oldID.nVersion < oldID.VERSION_PBAAS)
{
uniOldID["systemid"] = EncodeDestination(CIdentityID(parentID.IsNull() ? oldID.GetID() : parentID));
}
else
{
uniOldID["systemid"] = EncodeDestination(CIdentityID(parentID.IsNull() ? oldID.GetID() : parentID));
}
}
UniValue newUniID = MapToUniObject(uniOldID);
CIdentity newID(newUniID);
if (!newID.IsValid())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid JSON ID parameter");
}
// make sure we have a revocation and recovery authority defined
CIdentity revocationAuth = newID.revocationAuthority == newIDID ? newID : newID.LookupIdentity(newID.revocationAuthority);
CIdentity recoveryAuth = newID.recoveryAuthority == newIDID ? newID : newID.LookupIdentity(newID.recoveryAuthority);
if (!revocationAuth.IsValid() || !recoveryAuth.IsValid())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid revocation or recovery authority");
}
if (!recoveryAuth.IsValidUnrevoked() || !revocationAuth.IsValidUnrevoked())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid or revoked recovery, or revocation identity.");
}
CMutableTransaction txNew = CreateNewContextualCMutableTransaction(Params().GetConsensus(), nHeight);
if (CConstVerusSolutionVector::GetVersionByHeight(nHeight + 1) >= CActivationHeight::ACTIVATE_PBAAS)
{
newID.SetVersion(CIdentity::VERSION_PBAAS);
}
if (oldID.IsLocked() != newID.IsLocked())
{
bool newLocked = newID.IsLocked();
uint32_t unlockAfter = newID.unlockAfter;
newID.flags = (newID.flags & ~newID.FLAG_LOCKED) | (newID.IsRevoked() ? 0 : (oldID.flags & oldID.FLAG_LOCKED));
newID.unlockAfter = oldID.unlockAfter;
if (!newLocked)
{
newID.Unlock(nHeight, txNew.nExpiryHeight);
}
else
{
newID.Lock(unlockAfter);
}
}
// create the identity definition transaction
std::vector<CRecipient> outputs = std::vector<CRecipient>({{newID.IdentityUpdateOutputScript(nHeight), 0, false}});
CWalletTx wtx;
CReserveKey reserveKey(pwalletMain);
CAmount fee;
int nChangePos;
int nNumChangeOutputs = 0;
string failReason;
if (!pwalletMain->CreateTransaction(outputs, wtx, reserveKey, fee, nChangePos, failReason, nullptr, false))
{
throw JSONRPCError(RPC_TRANSACTION_ERROR, "Unable to create update transaction: " + failReason);
}
CMutableTransaction mtx(wtx);
// add the spend of the last ID transaction output
mtx.vin.push_back(idTxIn);
*static_cast<CTransaction*>(&wtx) = CTransaction(mtx);
// now sign
CCoinsViewCache view(pcoinsTip);
for (int i = 0; i < wtx.vin.size(); i++)
{
bool signSuccess;
SignatureData sigdata;
CCoins coins;
if (!(view.GetCoins(wtx.vin[i].prevout.hash, coins) && coins.IsAvailable(wtx.vin[i].prevout.n)))
{
break;
}
CAmount value = coins.vout[wtx.vin[i].prevout.n].nValue;
signSuccess = ProduceSignature(TransactionSignatureCreator(pwalletMain, &wtx, i, value, coins.vout[wtx.vin[i].prevout.n].scriptPubKey), coins.vout[wtx.vin[i].prevout.n].scriptPubKey, sigdata, CurrentEpochBranchId(chainActive.Height(), Params().GetConsensus()));
if (!signSuccess)
{
LogPrintf("%s: failure to sign identity recovery tx for input %d from output %d of %s\n", __func__, i, wtx.vin[i].prevout.n, wtx.vin[i].prevout.hash.GetHex().c_str());
printf("%s: failure to sign identity recovery tx for input %d from output %d of %s\n", __func__, i, wtx.vin[i].prevout.n, wtx.vin[i].prevout.hash.GetHex().c_str());
throw JSONRPCError(RPC_TRANSACTION_ERROR, "Failed to sign transaction");
} else {
UpdateTransaction(mtx, i, sigdata);
}
}
*static_cast<CTransaction*>(&wtx) = CTransaction(mtx);
if (returnTx)
{
return EncodeHexTx(wtx);
}
else if (!pwalletMain->CommitTransaction(wtx, reserveKey))
{
throw JSONRPCError(RPC_TRANSACTION_ERROR, "Could not commit transaction " + wtx.GetHash().GetHex());
}
return wtx.GetHash().GetHex();
}
UniValue revokeidentity(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() < 1 || params.size() > 2)
{
throw runtime_error(
"revokeidentity \"nameorID\" (returntx)\n"
"\n\n"
"\nArguments\n"
" \"returntx\" (bool, optional) defaults to false and transaction is sent, if true, transaction is signed by this wallet and returned\n"
"\nResult:\n"
"\nExamples:\n"
+ HelpExampleCli("revokeidentity", "\"nameorID\"")
+ HelpExampleRpc("revokeidentity", "\"nameorID\"")
);
}
CheckIdentityAPIsValid();
// get identity
bool returnTx = false;
CTxDestination idDest = DecodeDestination(uni_get_str(params[0]));
if (idDest.which() != COptCCParams::ADDRTYPE_ID)
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid JSON ID parameter");
}
CIdentityID idID(GetDestinationID(idDest));
if (params.size() > 1)
{
returnTx = uni_get_bool(params[1], false);
}
CTxIn idTxIn;
CIdentity oldID;
uint32_t idHeight;
LOCK2(cs_main, pwalletMain->cs_wallet);
if (!(oldID = CIdentity::LookupIdentity(idID, 0, &idHeight, &idTxIn)).IsValid())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "ID not found " + EncodeDestination(idID));
}
CIdentity newID(oldID);
newID.Revoke();
// create the identity definition transaction
std::vector<CRecipient> outputs = std::vector<CRecipient>({{newID.IdentityUpdateOutputScript(chainActive.Height()), 0, false}});
CWalletTx wtx;
CReserveKey reserveKey(pwalletMain);
CAmount fee;
int nChangePos;
string failReason;
if (!pwalletMain->CreateTransaction(outputs, wtx, reserveKey, fee, nChangePos, failReason, nullptr, false))
{
throw JSONRPCError(RPC_TRANSACTION_ERROR, "Unable to create update transaction: " + failReason);
}
CMutableTransaction mtx(wtx);
// add the spend of the last ID transaction output
mtx.vin.push_back(idTxIn);
// all of the reservation output is actually the fee offer, so zero the output
*static_cast<CTransaction*>(&wtx) = CTransaction(mtx);
// now sign
CCoinsViewCache view(pcoinsTip);
for (int i = 0; i < wtx.vin.size(); i++)
{
bool signSuccess;
SignatureData sigdata;
CCoins coins;
if (!(view.GetCoins(wtx.vin[i].prevout.hash, coins) && coins.IsAvailable(wtx.vin[i].prevout.n)))
{
break;
}
CAmount value = coins.vout[wtx.vin[i].prevout.n].nValue;
signSuccess = ProduceSignature(TransactionSignatureCreator(pwalletMain, &wtx, i, value, coins.vout[wtx.vin[i].prevout.n].scriptPubKey), coins.vout[wtx.vin[i].prevout.n].scriptPubKey, sigdata, CurrentEpochBranchId(chainActive.Height(), Params().GetConsensus()));
if (!signSuccess)
{
LogPrintf("%s: failure to sign identity revocation tx for input %d from output %d of %s\n", __func__, i, wtx.vin[i].prevout.n, wtx.vin[i].prevout.hash.GetHex().c_str());
printf("%s: failure to sign identity revocation tx for input %d from output %d of %s\n", __func__, i, wtx.vin[i].prevout.n, wtx.vin[i].prevout.hash.GetHex().c_str());
throw JSONRPCError(RPC_TRANSACTION_ERROR, "Failed to sign transaction");
} else {
UpdateTransaction(mtx, i, sigdata);
}
}
*static_cast<CTransaction*>(&wtx) = CTransaction(mtx);
if (returnTx)
{
return EncodeHexTx(wtx);
}
else if (!pwalletMain->CommitTransaction(wtx, reserveKey))
{
throw JSONRPCError(RPC_TRANSACTION_ERROR, "Could not commit transaction " + wtx.GetHash().GetHex());
}
return wtx.GetHash().GetHex();
}
UniValue recoveridentity(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() < 1 || params.size() > 2)
{
throw runtime_error(
"recoveridentity \"jsonidentity\" (returntx)\n"
"\n\n"
"\nArguments\n"
" \"returntx\" (bool, optional) defaults to false and transaction is sent, if true, transaction is signed by this wallet and returned\n"
"\nResult:\n"
"\nExamples:\n"
+ HelpExampleCli("recoveridentity", "\'{\"name\" : \"myname\"}\'")
+ HelpExampleRpc("recoveridentity", "\'{\"name\" : \"myname\"}\'")
);
}
CheckIdentityAPIsValid();
// get identity
bool returnTx = false;
CIdentity newID(params[0]);
if (!newID.IsValid())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid JSON ID parameter");
}
if (params.size() > 1)
{
returnTx = uni_get_bool(params[1], false);
}
CTxIn idTxIn;
CIdentity oldID;
uint32_t idHeight;
LOCK2(cs_main, pwalletMain->cs_wallet);
if (!(oldID = CIdentity::LookupIdentity(newID.GetID(), 0, &idHeight, &idTxIn)).IsValid())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "ID not found " + newID.ToUniValue().write());
}
if (!oldID.IsRevoked())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Identity must be revoked in order to recover : " + newID.name);
}
uint32_t nHeight = chainActive.Height();
newID.flags &= ~CIdentity::FLAG_REVOKED;
if (CConstVerusSolutionVector::GetVersionByHeight(nHeight + 1) >= CActivationHeight::ACTIVATE_PBAAS)
{
newID.SetVersion(CIdentity::VERSION_PBAAS);
}
// create the identity definition transaction
std::vector<CRecipient> outputs = std::vector<CRecipient>({{newID.IdentityUpdateOutputScript(nHeight), 0, false}});
CWalletTx wtx;
CReserveKey reserveKey(pwalletMain);
CAmount fee;
int nChangePos;
string failReason;
if (!pwalletMain->CreateTransaction(outputs, wtx, reserveKey, fee, nChangePos, failReason, nullptr, false))
{
throw JSONRPCError(RPC_TRANSACTION_ERROR, "Unable to create update transaction: " + failReason);
}
CMutableTransaction mtx(wtx);
// add the spend of the last ID transaction output
mtx.vin.push_back(idTxIn);
// all of the reservation output is actually the fee offer, so zero the output
*static_cast<CTransaction*>(&wtx) = CTransaction(mtx);
// now sign
CCoinsViewCache view(pcoinsTip);
for (int i = 0; i < wtx.vin.size(); i++)
{
bool signSuccess;
SignatureData sigdata;
CCoins coins;
if (!(view.GetCoins(wtx.vin[i].prevout.hash, coins) && coins.IsAvailable(wtx.vin[i].prevout.n)))
{
break;
}
CAmount value = coins.vout[wtx.vin[i].prevout.n].nValue;
signSuccess = ProduceSignature(TransactionSignatureCreator(pwalletMain, &wtx, i, value, coins.vout[wtx.vin[i].prevout.n].scriptPubKey), coins.vout[wtx.vin[i].prevout.n].scriptPubKey, sigdata, CurrentEpochBranchId(chainActive.Height(), Params().GetConsensus()));
if (!signSuccess)
{
LogPrintf("%s: failure to sign identity recovery tx for input %d from output %d of %s\n", __func__, i, wtx.vin[i].prevout.n, wtx.vin[i].prevout.hash.GetHex().c_str());
printf("%s: failure to sign identity recovery tx for input %d from output %d of %s\n", __func__, i, wtx.vin[i].prevout.n, wtx.vin[i].prevout.hash.GetHex().c_str());
throw JSONRPCError(RPC_TRANSACTION_ERROR, "Failed to sign transaction");
} else {
UpdateTransaction(mtx, i, sigdata);
}
}
*static_cast<CTransaction*>(&wtx) = CTransaction(mtx);
if (returnTx)
{
return EncodeHexTx(wtx);
}
else if (!pwalletMain->CommitTransaction(wtx, reserveKey))
{
throw JSONRPCError(RPC_TRANSACTION_ERROR, "Could not commit transaction " + wtx.GetHash().GetHex());
}
return wtx.GetHash().GetHex();
}
UniValue getidentity(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() != 1)
{
throw runtime_error(
"getidentity \"name\"\n"
"\n\n"
"\nArguments\n"
"\nResult:\n"
"\nExamples:\n"
+ HelpExampleCli("getidentity", "\"name@\"")
+ HelpExampleRpc("getidentity", "\"name@\"")
);
}
CheckIdentityAPIsValid();
CTxDestination idID = DecodeDestination(uni_get_str(params[0]));
if (idID.which() != COptCCParams::ADDRTYPE_ID)
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Identity parameter must be valid friendly name or identity address: \"" + uni_get_str(params[0]) + "\"");
}
CTxIn idTxIn;
uint32_t height;
CIdentity identity;
bool canSign = false, canSpend = false;
if (pwalletMain)
{
LOCK(pwalletMain->cs_wallet);
uint256 txID;
std::pair<CIdentityMapKey, CIdentityMapValue> keyAndIdentity;
if (pwalletMain->GetIdentity(GetDestinationID(idID), keyAndIdentity))
{
canSign = keyAndIdentity.first.flags & keyAndIdentity.first.CAN_SIGN;
canSpend = keyAndIdentity.first.flags & keyAndIdentity.first.CAN_SPEND;
identity = static_cast<CIdentity>(keyAndIdentity.second);
}
}
LOCK(cs_main);
uint160 identityID = GetDestinationID(idID);
identity = CIdentity::LookupIdentity(CIdentityID(identityID), 0, &height, &idTxIn);
if (!identity.IsValid() && identityID == VERUS_CHAINID)
{
std::vector<CTxDestination> primary({CTxDestination(CKeyID(uint160()))});
std::vector<std::pair<uint160, uint256>> contentmap;
identity = CIdentity(CIdentity::VERSION_PBAAS,
CIdentity::FLAG_ACTIVECURRENCY,
primary,
1,
ConnectedChains.ThisChain().parent,
VERUS_CHAINNAME,
contentmap,
ConnectedChains.ThisChain().GetID(),
ConnectedChains.ThisChain().GetID(),
std::vector<libzcash::SaplingPaymentAddress>());
}
UniValue ret(UniValue::VOBJ);
uint160 parent;
if (identity.IsValid() && identity.name == CleanName(identity.name, parent, true))
{
ret.push_back(Pair("identity", identity.ToUniValue()));
ret.push_back(Pair("status", identity.IsRevoked() ? "revoked" : "active"));
ret.push_back(Pair("canspendfor", canSpend));
ret.push_back(Pair("cansignfor", canSign));
ret.push_back(Pair("blockheight", (int64_t)height));
ret.push_back(Pair("txid", idTxIn.prevout.hash.GetHex()));
ret.push_back(Pair("vout", (int32_t)idTxIn.prevout.n));
return ret;
}
else
{
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Identity not found");
}
}
UniValue IdentityPairToUni(const std::pair<CIdentityMapKey, CIdentityMapValue> &identity)
{
UniValue oneID(UniValue::VOBJ);
if (identity.first.IsValid() && identity.second.IsValid())
{
oneID.push_back(Pair("identity", identity.second.ToUniValue()));
oneID.push_back(Pair("blockheight", (int64_t)identity.first.blockHeight));
oneID.push_back(Pair("txid", identity.second.txid.GetHex()));
if (identity.second.IsRevoked())
{
oneID.push_back(Pair("status", "revoked"));
oneID.push_back(Pair("canspendfor", bool(0)));
oneID.push_back(Pair("cansignfor", bool(0)));
}
else
{
oneID.push_back(Pair("status", "active"));
oneID.push_back(Pair("canspendfor", bool(identity.first.flags & identity.first.CAN_SPEND)));
oneID.push_back(Pair("cansignfor", bool(identity.first.flags & identity.first.CAN_SIGN)));
}
}
return oneID;
}
UniValue listidentities(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() > 3)
{
throw runtime_error(
"listidentities (includecansign) (includewatchonly)\n"
"\n\n"
"\nArguments\n"
" \"includecanspend\" (bool, optional, default=true) Include identities for which we can spend/authorize\n"
" \"includecansign\" (bool, optional, default=true) Include identities that we can only sign for but not spend\n"
" \"includewatchonly\" (bool, optional, default=false) Include identities that we can neither sign nor spend, but are either watched or are co-signers with us\n"
"\nResult:\n"
"\nExamples:\n"
+ HelpExampleCli("listidentities", "\'{\"name\" : \"myname\"}\'")
+ HelpExampleRpc("listidentities", "\'{\"name\" : \"myname\"}\'")
);
}
CheckIdentityAPIsValid();
std::vector<std::pair<CIdentityMapKey, CIdentityMapValue>> mine, imsigner, notmine;
CIdentity oneIdentity;
uint32_t oneIdentityHeight;
bool includeCanSpend = params.size() > 0 ? uni_get_bool(params[0], true) : true;
bool includeCanSign = params.size() > 1 ? uni_get_bool(params[1], true) : true;
bool includeWatchOnly = params.size() > 2 ? uni_get_bool(params[2], false) : false;
LOCK2(cs_main, pwalletMain->cs_wallet);
if (pwalletMain->GetIdentities(mine, imsigner, notmine))
{
UniValue ret(UniValue::VARR);
if (includeCanSpend)
{
for (auto identity : mine)
{
uint160 parent;
if (identity.second.IsValid() && identity.second.name == CleanName(identity.second.name, parent, true))
{
oneIdentity = CIdentity::LookupIdentity(identity.first.idID, 0, &oneIdentityHeight);
if (!oneIdentity.IsValid())
{
if (identity.first.idID != VERUS_CHAINID)
{
continue;
}
std::vector<CTxDestination> primary({CTxDestination(CKeyID(uint160()))});
std::vector<std::pair<uint160, uint256>> contentmap;
oneIdentity = CIdentity(CIdentity::VERSION_PBAAS,
CIdentity::FLAG_ACTIVECURRENCY,
primary,
1,
ConnectedChains.ThisChain().parent,
VERUS_CHAINNAME,
contentmap,
ConnectedChains.ThisChain().GetID(),
ConnectedChains.ThisChain().GetID(),
std::vector<libzcash::SaplingPaymentAddress>());
}
(*(CIdentity *)&identity.second) = oneIdentity;
// TODO: confirm that missing block order is fine for this API
identity.first.blockHeight = oneIdentityHeight;
ret.push_back(IdentityPairToUni(identity));
}
}
}
if (includeCanSign)
{
for (auto identity : imsigner)
{
uint160 parent;
if (identity.second.IsValid() && identity.second.name == CleanName(identity.second.name, parent, true))
{
oneIdentity = CIdentity::LookupIdentity(identity.first.idID, 0, &oneIdentityHeight);
if (!oneIdentity.IsValid())
{
if (identity.first.idID != VERUS_CHAINID)
{
continue;
}
std::vector<CTxDestination> primary({CTxDestination(CKeyID(uint160()))});
std::vector<std::pair<uint160, uint256>> contentmap;
oneIdentity = CIdentity(CIdentity::VERSION_PBAAS,
CIdentity::FLAG_ACTIVECURRENCY,
primary,
1,
ConnectedChains.ThisChain().parent,
VERUS_CHAINNAME,
contentmap,
ConnectedChains.ThisChain().GetID(),
ConnectedChains.ThisChain().GetID(),
std::vector<libzcash::SaplingPaymentAddress>());
}
(*(CIdentity *)&identity.second) = oneIdentity;
ret.push_back(IdentityPairToUni(identity));
}
}
}
if (includeWatchOnly)
{
for (auto identity : notmine)
{
uint160 parent;
if (identity.second.IsValid() && identity.second.name == CleanName(identity.second.name, parent, true))
{
oneIdentity = CIdentity::LookupIdentity(identity.first.idID, 0, &oneIdentityHeight);
if (!oneIdentity.IsValid())
{
if (identity.first.idID != VERUS_CHAINID)
{
continue;
}
std::vector<CTxDestination> primary({CTxDestination(CKeyID(uint160()))});
std::vector<std::pair<uint160, uint256>> contentmap;
oneIdentity = CIdentity(CIdentity::VERSION_PBAAS,
CIdentity::FLAG_ACTIVECURRENCY,
primary,
1,
ConnectedChains.ThisChain().parent,
VERUS_CHAINNAME,
contentmap,
ConnectedChains.ThisChain().GetID(),
ConnectedChains.ThisChain().GetID(),
std::vector<libzcash::SaplingPaymentAddress>());
}
(*(CIdentity *)&identity.second) = oneIdentity;
ret.push_back(IdentityPairToUni(identity));
}
}
}
return ret;
}
else
{
return NullUniValue;
}
}
UniValue addmergedblock(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() != 5)
{
throw runtime_error(
"addmergedblock \"hexdata\" ( \"jsonparametersobject\" )\n"
"\nAdds a fully prepared block and its header to the current merge mining queue of this daemon.\n"
"Parameters determine the action to take if adding this block would exceed the available merge mining slots.\n"
"Default action to take if adding would exceed available space is to replace the choice with the least ROI if this block provides more.\n"
"\nArguments\n"
"1. \"hexdata\" (string, required) the hex-encoded, complete, unsolved block data to add. nTime, and nSolution are replaced.\n"
"2. \"name\" (string, required) chain name symbol\n"
"3. \"rpchost\" (string, required) host address for RPC connection\n"
"4. \"rpcport\" (int, required) port address for RPC connection\n"
"5. \"userpass\" (string, required) credentials for login to RPC\n"
"\nResult:\n"
"\"deserialize-invalid\" - block could not be deserialized and was rejected as invalid\n"
"\"blocksfull\" - block did not exceed others in estimated ROI, and there was no room for an additional merge mined block\n"
"\nExamples:\n"
+ HelpExampleCli("addmergedblock", "\"hexdata\" \'{\"currencyid\" : \"hexstring\", \"rpchost\" : \"127.0.0.1\", \"rpcport\" : portnum}\'")
+ HelpExampleRpc("addmergedblock", "\"hexdata\" \'{\"currencyid\" : \"hexstring\", \"rpchost\" : \"127.0.0.1\", \"rpcport\" : portnum, \"estimatedroi\" : (verusreward/hashrate)}\'")
);
}
CheckPBaaSAPIsValid();
// check to see if we should replace any existing block or add a new one. if so, add this to the merge mine vector
string name = params[1].get_str();
if (name == "")
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "must provide chain name to merge mine");
}
string rpchost = params[2].get_str();
int32_t rpcport = params[3].get_int();
string rpcuserpass = params[4].get_str();
if (rpchost == "" || rpcport == 0 || rpcuserpass == "")
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "must provide valid RPC connection parameters to merge mine");
}
CCurrencyDefinition chainDef;
uint160 chainID = ValidateCurrencyName(name, true, &chainDef);
if (chainID.IsNull())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid chain for merge mining");
}
// confirm data from blockchain
CRPCChainData chainData;
if (ConnectedChains.GetChainInfo(chainID, chainData))
{
chainDef = chainData.chainDefinition;
}
if (!chainDef.IsValid())
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "chain not found");
}
CBlock blk;
if (!DecodeHexBlk(blk, params[0].get_str()))
return "deserialize-invalid";
CPBaaSMergeMinedChainData blkData = CPBaaSMergeMinedChainData(chainDef, rpchost, rpcport, rpcuserpass, blk);
return ConnectedChains.AddMergedBlock(blkData) ? NullUniValue : "blocksfull";
}
UniValue submitmergedblock(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() < 1 || params.size() > 2)
throw runtime_error(
"submitmergedblock \"hexdata\" ( \"jsonparametersobject\" )\n"
"\nAttempts to submit one more more new blocks to one or more networks.\n"
"Each merged block submission may be valid for Verus and/or up to 8 merge mined chains.\n"
"The submitted block consists of a valid block for this chain, along with embedded headers of up to 8 other chains.\n"
"If the hash for this header meets targets of other chains that have been added with 'addmergedblock', this API will\n"
"submit those blocks to the specified URL endpoints with an RPC 'submitblock' request."
"\nAttempts to submit one more more new blocks to one or more networks.\n"
"The 'jsonparametersobject' parameter is currently ignored.\n"
"See https://en.bitcoin.it/wiki/BIP_0022 for full specification.\n"
"\nArguments\n"
"1. \"hexdata\" (string, required) the hex-encoded block data to submit\n"
"2. \"jsonparametersobject\" (string, optional) object of optional parameters\n"
" {\n"
" \"workid\" : \"id\" (string, optional) if the server provided a workid, it MUST be included with submissions\n"
" }\n"
"\nResult:\n"
"\"duplicate\" - node already has valid copy of block\n"
"\"duplicate-invalid\" - node already has block, but it is invalid\n"
"\"duplicate-inconclusive\" - node already has block but has not validated it\n"
"\"inconclusive\" - node has not validated the block, it may not be on the node's current best chain\n"
"\"rejected\" - block was rejected as invalid\n"
"For more information on submitblock parameters and results, see: https://github.com/bitcoin/bips/blob/master/bip-0022.mediawiki#block-submission\n"
"\nExamples:\n"
+ HelpExampleCli("submitblock", "\"mydata\"")
+ HelpExampleRpc("submitblock", "\"mydata\"")
);
CheckPBaaSAPIsValid();
CBlock block;
//LogPrintStr("Hex block submission: " + params[0].get_str());
if (!DecodeHexBlk(block, params[0].get_str()))
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Block decode failed");
uint256 hash = block.GetHash();
bool fBlockPresent = false;
{
LOCK(cs_main);
BlockMap::iterator mi = mapBlockIndex.find(hash);
if (mi != mapBlockIndex.end()) {
CBlockIndex *pindex = mi->second;
if (pindex)
{
if (pindex->IsValid(BLOCK_VALID_SCRIPTS))
return "duplicate";
if (pindex->nStatus & BLOCK_FAILED_MASK)
return "duplicate-invalid";
// Otherwise, we might only have the header - process the block before returning
fBlockPresent = true;
}
}
}
CValidationState state;
submitblock_StateCatcher sc(block.GetHash());
RegisterValidationInterface(&sc);
//printf("submitblock, height=%d, coinbase sequence: %d, scriptSig: %s\n", chainActive.LastTip()->GetHeight()+1, block.vtx[0].vin[0].nSequence, block.vtx[0].vin[0].scriptSig.ToString().c_str());
bool fAccepted = ProcessNewBlock(1, chainActive.LastTip()->GetHeight()+1, state, Params(), NULL, &block, true, NULL);
UnregisterValidationInterface(&sc);
if (fBlockPresent)
{
if (fAccepted && !sc.found)
return "duplicate-inconclusive";
return "duplicate";
}
if (fAccepted)
{
if (!sc.found)
return "inconclusive";
state = sc.state;
}
return BIP22ValidationResult(state);
}
UniValue getmergedblocktemplate(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() > 1)
throw runtime_error(
"getblocktemplate ( \"jsonrequestobject\" )\n"
"\nIf the request parameters include a 'mode' key, that is used to explicitly select between the default 'template' request or a 'proposal'.\n"
"It returns data needed to construct a block to work on.\n"
"See https://en.bitcoin.it/wiki/BIP_0022 for full specification.\n"
"\nArguments:\n"
"1. \"jsonrequestobject\" (string, optional) A json object in the following spec\n"
" {\n"
" \"mode\":\"template\" (string, optional) This must be set to \"template\" or omitted\n"
" \"capabilities\":[ (array, optional) A list of strings\n"
" \"support\" (string) client side supported feature, 'longpoll', 'coinbasetxn', 'coinbasevalue', 'proposal', 'serverlist', 'workid'\n"
" ,...\n"
" ]\n"
" }\n"
"\n"
"\nResult:\n"
"{\n"
" \"version\" : n, (numeric) The block version\n"
" \"previousblockhash\" : \"xxxx\", (string) The hash of current highest block\n"
" \"finalsaplingroothash\" : \"xxxx\", (string) The hash of the final sapling root\n"
" \"transactions\" : [ (array) contents of non-coinbase transactions that should be included in the next block\n"
" {\n"
" \"data\" : \"xxxx\", (string) transaction data encoded in hexadecimal (byte-for-byte)\n"
" \"hash\" : \"xxxx\", (string) hash/id encoded in little-endian hexadecimal\n"
" \"depends\" : [ (array) array of numbers \n"
" n (numeric) transactions before this one (by 1-based index in 'transactions' list) that must be present in the final block if this one is\n"
" ,...\n"
" ],\n"
" \"fee\": n, (numeric) difference in value between transaction inputs and outputs (in Satoshis); for coinbase transactions, this is a negative Number of the total collected block fees (ie, not including the block subsidy); if key is not present, fee is unknown and clients MUST NOT assume there isn't one\n"
" \"sigops\" : n, (numeric) total number of SigOps, as counted for purposes of block limits; if key is not present, sigop count is unknown and clients MUST NOT assume there aren't any\n"
" \"required\" : true|false (boolean) if provided and true, this transaction must be in the final block\n"
" }\n"
" ,...\n"
" ],\n"
// " \"coinbaseaux\" : { (json object) data that should be included in the coinbase's scriptSig content\n"
// " \"flags\" : \"flags\" (string) \n"
// " },\n"
// " \"coinbasevalue\" : n, (numeric) maximum allowable input to coinbase transaction, including the generation award and transaction fees (in Satoshis)\n"
" \"coinbasetxn\" : { ... }, (json object) information for coinbase transaction\n"
" \"target\" : \"xxxx\", (string) The hash target\n"
" \"mintime\" : xxx, (numeric) The minimum timestamp appropriate for next block time in seconds since epoch (Jan 1 1970 GMT)\n"
" \"mutable\" : [ (array of string) list of ways the block template may be changed \n"
" \"value\" (string) A way the block template may be changed, e.g. 'time', 'transactions', 'prevblock'\n"
" ,...\n"
" ],\n"
" \"noncerange\" : \"00000000ffffffff\", (string) A range of valid nonces\n"
" \"sigoplimit\" : n, (numeric) limit of sigops in blocks\n"
" \"sizelimit\" : n, (numeric) limit of block size\n"
" \"curtime\" : ttt, (numeric) current timestamp in seconds since epoch (Jan 1 1970 GMT)\n"
" \"bits\" : \"xxx\", (string) compressed target of next block\n"
" \"height\" : n (numeric) The height of the next block\n"
"}\n"
"\nExamples:\n"
+ HelpExampleCli("getblocktemplate", "")
+ HelpExampleRpc("getblocktemplate", "")
);
CheckPBaaSAPIsValid();
LOCK(cs_main);
// Wallet or miner address is required because we support coinbasetxn
if (GetArg("-mineraddress", "").empty()) {
#ifdef ENABLE_WALLET
if (!pwalletMain) {
throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Wallet disabled and -mineraddress not set");
}
#else
throw JSONRPCError(RPC_METHOD_NOT_FOUND, "verusd compiled without wallet and -mineraddress not set");
#endif
}
std::string strMode = "template";
UniValue lpval = NullUniValue;
// TODO: Re-enable coinbasevalue once a specification has been written
bool coinbasetxn = true;
if (params.size() > 0)
{
const UniValue& oparam = params[0].get_obj();
const UniValue& modeval = find_value(oparam, "mode");
if (modeval.isStr())
strMode = modeval.get_str();
else if (modeval.isNull())
{
/* Do nothing */
}
else
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid mode");
lpval = find_value(oparam, "longpollid");
if (strMode == "proposal")
{
const UniValue& dataval = find_value(oparam, "data");
if (!dataval.isStr())
throw JSONRPCError(RPC_TYPE_ERROR, "Missing data String key for proposal");
CBlock block;
if (!DecodeHexBlk(block, dataval.get_str()))
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Block decode failed");
uint256 hash = block.GetHash();
BlockMap::iterator mi = mapBlockIndex.find(hash);
if (mi != mapBlockIndex.end()) {
CBlockIndex *pindex = mi->second;
if (pindex)
{
if (pindex->IsValid(BLOCK_VALID_SCRIPTS))
return "duplicate";
if (pindex->nStatus & BLOCK_FAILED_MASK)
return "duplicate-invalid";
}
return "duplicate-inconclusive";
}
CBlockIndex* const pindexPrev = chainActive.LastTip();
// TestBlockValidity only supports blocks built on the current Tip
if (block.hashPrevBlock != pindexPrev->GetBlockHash())
return "inconclusive-not-best-prevblk";
CValidationState state;
TestBlockValidity(state, Params(), block, pindexPrev, false, true);
return BIP22ValidationResult(state);
}
}
if (strMode != "template")
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid mode");
bool fvNodesEmpty;
{
LOCK(cs_vNodes);
fvNodesEmpty = vNodes.empty();
}
if (Params().MiningRequiresPeers() && (IsNotInSync() || fvNodesEmpty))
{
throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "Cannot get a block template while no peers are connected or chain not in sync!");
}
//if (IsInitialBlockDownload())
// throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "Zcash is downloading blocks...");
static unsigned int nTransactionsUpdatedLast;
if (!lpval.isNull())
{
// Wait to respond until either the best block changes, OR a minute has passed and there are more transactions
uint256 hashWatchedChain;
boost::system_time checktxtime;
unsigned int nTransactionsUpdatedLastLP;
if (lpval.isStr())
{
// Format: <hashBestChain><nTransactionsUpdatedLast>
std::string lpstr = lpval.get_str();
hashWatchedChain.SetHex(lpstr.substr(0, 64));
nTransactionsUpdatedLastLP = atoi64(lpstr.substr(64));
}
else
{
// NOTE: Spec does not specify behaviour for non-string longpollid, but this makes testing easier
hashWatchedChain = chainActive.LastTip()->GetBlockHash();
nTransactionsUpdatedLastLP = nTransactionsUpdatedLast;
}
// Release the wallet and main lock while waiting
LEAVE_CRITICAL_SECTION(cs_main);
{
checktxtime = boost::get_system_time() + boost::posix_time::minutes(1);
boost::unique_lock<boost::mutex> lock(csBestBlock);
while (chainActive.LastTip()->GetBlockHash() == hashWatchedChain && IsRPCRunning())
{
if (!cvBlockChange.timed_wait(lock, checktxtime))
{
// Timeout: Check transactions for update
if (mempool.GetTransactionsUpdated() != nTransactionsUpdatedLastLP)
break;
checktxtime += boost::posix_time::seconds(10);
}
}
}
ENTER_CRITICAL_SECTION(cs_main);
if (!IsRPCRunning())
throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "Shutting down");
// TODO: Maybe recheck connections/IBD and (if something wrong) send an expires-immediately template to stop miners?
}
// Update block
static CBlockIndex* pindexPrev;
static int64_t nStart;
static CBlockTemplate* pblocktemplate;
if (pindexPrev != chainActive.LastTip() ||
(mempool.GetTransactionsUpdated() != nTransactionsUpdatedLast && GetTime() - nStart > 5))
{
// Clear pindexPrev so future calls make a new block, despite any failures from here on
pindexPrev = NULL;
// Store the pindexBest used before CreateNewBlockWithKey, to avoid races
nTransactionsUpdatedLast = mempool.GetTransactionsUpdated();
CBlockIndex* pindexPrevNew = chainActive.LastTip();
nStart = GetTime();
// Create new block
if(pblocktemplate)
{
delete pblocktemplate;
pblocktemplate = NULL;
}
#ifdef ENABLE_WALLET
CReserveKey reservekey(pwalletMain);
pblocktemplate = CreateNewBlockWithKey(reservekey,chainActive.LastTip()->GetHeight()+1);
#else
pblocktemplate = CreateNewBlockWithKey();
#endif
if (!pblocktemplate)
throw JSONRPCError(RPC_OUT_OF_MEMORY, "Out of memory or no available utxo for staking");
// Need to update only after we know CreateNewBlockWithKey succeeded
pindexPrev = pindexPrevNew;
}
CBlock* pblock = &pblocktemplate->block; // pointer for convenience
// Update nTime
UpdateTime(pblock, Params().GetConsensus(), pindexPrev);
pblock->nNonce = uint256();
UniValue aCaps(UniValue::VARR); aCaps.push_back("proposal");
UniValue txCoinbase = NullUniValue;
UniValue transactions(UniValue::VARR);
map<uint256, int64_t> setTxIndex;
int i = 0;
BOOST_FOREACH (const CTransaction& tx, pblock->vtx) {
uint256 txHash = tx.GetHash();
setTxIndex[txHash] = i++;
if (tx.IsCoinBase() && !coinbasetxn)
continue;
UniValue entry(UniValue::VOBJ);
entry.push_back(Pair("data", EncodeHexTx(tx)));
entry.push_back(Pair("hash", txHash.GetHex()));
UniValue deps(UniValue::VARR);
BOOST_FOREACH (const CTxIn &in, tx.vin)
{
if (setTxIndex.count(in.prevout.hash))
deps.push_back(setTxIndex[in.prevout.hash]);
}
entry.push_back(Pair("depends", deps));
int index_in_template = i - 1;
entry.push_back(Pair("fee", pblocktemplate->vTxFees[index_in_template]));
entry.push_back(Pair("sigops", pblocktemplate->vTxSigOps[index_in_template]));
if (tx.IsCoinBase()) {
// Show founders' reward if it is required
//if (pblock->vtx[0].vout.size() > 1) {
// Correct this if GetBlockTemplate changes the order
// entry.push_back(Pair("foundersreward", (int64_t)tx.vout[1].nValue));
//}
CAmount nReward = GetBlockSubsidy(chainActive.LastTip()->GetHeight()+1, Params().GetConsensus());
entry.push_back(Pair("coinbasevalue", nReward));
entry.push_back(Pair("required", true));
txCoinbase = entry;
} else
transactions.push_back(entry);
}
UniValue aux(UniValue::VOBJ);
aux.push_back(Pair("flags", HexStr(COINBASE_FLAGS.begin(), COINBASE_FLAGS.end())));
arith_uint256 hashTarget = arith_uint256().SetCompact(pblock->nBits);
static UniValue aMutable(UniValue::VARR);
if (aMutable.empty())
{
aMutable.push_back("time");
aMutable.push_back("transactions");
aMutable.push_back("prevblock");
}
UniValue result(UniValue::VOBJ);
result.push_back(Pair("capabilities", aCaps));
result.push_back(Pair("version", pblock->nVersion));
result.push_back(Pair("previousblockhash", pblock->hashPrevBlock.GetHex()));
result.push_back(Pair("finalsaplingroothash", pblock->hashFinalSaplingRoot.GetHex()));
result.push_back(Pair("transactions", transactions));
if (coinbasetxn) {
assert(txCoinbase.isObject());
result.push_back(Pair("coinbasetxn", txCoinbase));
} else {
result.push_back(Pair("coinbaseaux", aux));
result.push_back(Pair("coinbasevalue", (int64_t)pblock->vtx[0].vout[0].nValue));
}
result.push_back(Pair("longpollid", chainActive.LastTip()->GetBlockHash().GetHex() + i64tostr(nTransactionsUpdatedLast)));
if ( ASSETCHAINS_STAKED != 0 )
{
arith_uint256 POWtarget; int32_t PoSperc;
POWtarget = komodo_PoWtarget(&PoSperc,hashTarget,(int32_t)(pindexPrev->GetHeight()+1),ASSETCHAINS_STAKED);
result.push_back(Pair("target", POWtarget.GetHex()));
result.push_back(Pair("PoSperc", (int64_t)PoSperc));
result.push_back(Pair("ac_staked", (int64_t)ASSETCHAINS_STAKED));
result.push_back(Pair("origtarget", hashTarget.GetHex()));
} else result.push_back(Pair("target", hashTarget.GetHex()));
result.push_back(Pair("mintime", (int64_t)pindexPrev->GetMedianTimePast()+1));
result.push_back(Pair("mutable", aMutable));
result.push_back(Pair("noncerange", "00000000ffffffff"));
result.push_back(Pair("sigoplimit", (int64_t)MAX_BLOCK_SIGOPS));
result.push_back(Pair("sizelimit", (int64_t)MAX_BLOCK_SIZE));
result.push_back(Pair("curtime", pblock->GetBlockTime()));
result.push_back(Pair("bits", strprintf("%08x", pblock->nBits)));
result.push_back(Pair("height", (int64_t)(pindexPrev->GetHeight()+1)));
//fprintf(stderr,"return complete template\n");
return result;
}
static const CRPCCommand commands[] =
{ // category name actor (function) okSafeMode
// --------------------- ------------------------ ----------------------- ----------
{ "identity", "registernamecommitment", &registernamecommitment, true },
{ "identity", "registeridentity", &registeridentity, true },
{ "identity", "updateidentity", &updateidentity, true },
{ "identity", "revokeidentity", &revokeidentity, true },
{ "identity", "recoveridentity", &recoveridentity, true },
{ "identity", "getidentity", &getidentity, true },
{ "identity", "listidentities", &listidentities, true },
{ "multichain", "definecurrency", &definecurrency, true },
{ "multichain", "listcurrencies", &listcurrencies, true },
{ "multichain", "getcurrencyconverters", &getcurrencyconverters, true },
{ "multichain", "getcurrency", &getcurrency, true },
{ "multichain", "getreservedeposits", &getreservedeposits, true },
{ "multichain", "getnotarizationdata", &getnotarizationdata, true },
{ "multichain", "getlaunchinfo", &getlaunchinfo, true },
{ "multichain", "getbestproofroot", &getbestproofroot, true },
{ "multichain", "submitacceptednotarization", &submitacceptednotarization, true },
{ "multichain", "submitimports", &submitimports, true },
{ "multichain", "getinitialcurrencystate", &getinitialcurrencystate, true },
{ "multichain", "getcurrencystate", &getcurrencystate, true },
{ "multichain", "getsaplingtree", &getsaplingtree, true },
{ "multichain", "sendcurrency", &sendcurrency, true },
{ "multichain", "getpendingtransfers", &getpendingtransfers, true },
{ "multichain", "getexports", &getexports, true },
{ "multichain", "getlastimportfrom", &getlastimportfrom, true },
{ "multichain", "getimports", &getimports, true },
{ "multichain", "refundfailedlaunch", &refundfailedlaunch, true },
{ "multichain", "refundfailedlaunch", &refundfailedlaunch, true },
{ "multichain", "getmergedblocktemplate", &getmergedblocktemplate, true },
{ "multichain", "addmergedblock", &addmergedblock, true }
};
void RegisterPBaaSRPCCommands(CRPCTable &tableRPC)
{
for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++)
tableRPC.appendCommand(commands[vcidx].name, &commands[vcidx]);
}