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.
12452 lines
554 KiB
12452 lines
554 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 "coincontrol.h"
|
|
|
|
#include <librustzcash.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 uint32_t ASSETCHAINS_STARTING_DIFF;
|
|
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 int64_t ASSETCHAINS_ISSUANCE;
|
|
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, bool notarizationCheck, CUTXORef *pUTXO, std::vector<CNodeData> *pGoodNodes)
|
|
{
|
|
static bool isVerusActive = IsVerusActive();
|
|
static bool thisChainLoaded = 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());
|
|
|
|
std::vector<std::pair<uint160, int>> addresses = {{lookupKey, CScript::P2IDX}};
|
|
std::vector<std::pair<CMempoolAddressDeltaKey, CMempoolAddressDelta> > results;
|
|
CCurrencyDefinition foundDef;
|
|
|
|
// TODO: HARDENING - consider converting chain filter to a currency blacklist and/or whitelist check
|
|
if (!ClosedPBaaSChains.count(chainID) && GetAddressUnspent(lookupKey, CScript::P2IDX, unspentOutputs) && unspentOutputs.size())
|
|
{
|
|
for (auto ¤cyDefOut : 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 ¤cyDefOut : results)
|
|
{
|
|
CTransaction tx;
|
|
|
|
if (mempool.lookup(currencyDefOut.first.txhash, tx) &&
|
|
(foundDef = CCurrencyDefinition(tx.vout[currencyDefOut.first.index].scriptPubKey)).IsValid())
|
|
{
|
|
chainDef = foundDef;
|
|
if (pDefHeight)
|
|
{
|
|
*pDefHeight = chainActive.Height() + 1;
|
|
}
|
|
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)
|
|
{
|
|
auto extraNodes = GetGoodNodes();
|
|
goodNodes.insert(goodNodes.end(), extraNodes.begin(), extraNodes.end());
|
|
}
|
|
if (!thisChainLoaded)
|
|
{
|
|
thisChainLoaded = true;
|
|
ConnectedChains.ThisChain() = foundDef;
|
|
ConnectedChains.UpdateCachedCurrency(foundDef, pDefHeight ? *pDefHeight : chainActive.Height());
|
|
}
|
|
}
|
|
else if (foundDef.IsValid())
|
|
{
|
|
// add to nodes with last confirmed notarization nodes
|
|
CChainNotarizationData cnd;
|
|
if (notarizationCheck && GetNotarizationData(chainID, cnd) && cnd.IsConfirmed())
|
|
{
|
|
auto extraNodes = cnd.vtx[cnd.lastConfirmed].second.nodes;
|
|
goodNodes.insert(goodNodes.end(), extraNodes.begin(), extraNodes.end());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (chainID == ASSETCHAINS_CHAINID && (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;
|
|
}
|
|
}
|
|
|
|
}
|
|
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;
|
|
}
|
|
|
|
CIdentity ValidateIdentityParameter(const std::string &idStr)
|
|
{
|
|
CIdentity retVal;
|
|
CTxDestination destination = DecodeDestination(idStr);
|
|
if (destination.which() == COptCCParams::ADDRTYPE_ID)
|
|
{
|
|
AssertLockHeld(cs_main);
|
|
retVal = CIdentity::LookupIdentity(GetDestinationID(destination));
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
// 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;
|
|
|
|
if (!GetBoolArg("-forcednsseed", false) && !(mapArgs.count("-connect") && mapMultiArgs["-connect"].size() > 0))
|
|
{
|
|
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
|
|
bool SetThisChain(const UniValue &chainDefinition, CCurrencyDefinition *retDef)
|
|
{
|
|
ConnectedChains.ThisChain() = CCurrencyDefinition(chainDefinition);
|
|
if (!ConnectedChains.ThisChain().IsValid())
|
|
{
|
|
return false;
|
|
}
|
|
if (retDef)
|
|
{
|
|
*retDef = ConnectedChains.ThisChain();
|
|
}
|
|
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());
|
|
|
|
ASSETCHAINS_STARTING_DIFF = ConnectedChains.ThisChain().initialBits;
|
|
//printf("Starting PBaaS chain:\n%s\n", ConnectedChains.ThisChain().ToUniValue().write(1,2).c_str());
|
|
|
|
if (!IsVerusActive())
|
|
{
|
|
// we set the notary chain to either Verus or VerusTest
|
|
// TODO: HARDENING - ensure that we don't need to check which name here and do the right thing in all cases
|
|
CCurrencyDefinition notaryChainDef = CCurrencyDefinition(PBAAS_TESTMODE ? "VRSCTEST" : "VRSC", PBAAS_TESTMODE);
|
|
|
|
VERUS_CHAINNAME = notaryChainDef.name;
|
|
VERUS_CHAINID = notaryChainDef.GetID();
|
|
|
|
ASSETCHAINS_CHAINID = ConnectedChains.ThisChain().GetID();
|
|
|
|
ConnectedChains.notarySystems[notaryChainDef.GetID()] =
|
|
CNotarySystemInfo(0, CRPCChainData(notaryChainDef, PBAAS_HOST, PBAAS_PORT, PBAAS_USERPASS), CPBaaSNotarization());
|
|
ASSETCHAINS_SUPPLY = ConnectedChains.ThisChain().GetTotalPreallocation();
|
|
ASSETCHAINS_ISSUANCE = ConnectedChains.ThisChain().gatewayConverterIssuance;
|
|
ASSETCHAINS_ERAOPTIONS[0] = ConnectedChains.ThisChain().ChainOptions();
|
|
}
|
|
|
|
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] = ConnectedChains.ThisChain().options;
|
|
}
|
|
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);
|
|
mapArgs["-gatewayconverterissuance"] = to_string(ASSETCHAINS_ISSUANCE);
|
|
return true;
|
|
}
|
|
|
|
void CurrencySystemTypeQuery(const uint160 queryID,
|
|
std::map<CUTXORef, int> ¤ciesFound,
|
|
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> ¤ciesFound,
|
|
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, ¤cyHeight, false, true, &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, ¤cyHeight, false, true, &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));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// can query and return currency definitions for currencies on this system or from a specific system specified in systemIDQualifier
|
|
void GetCurrencyDefinitions(const uint160 &systemIDQualifier,
|
|
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(systemIDQualifier, CCurrencyDefinition::CurrencySystemKey());
|
|
CurrencySystemTypeQuery(queryID, currenciesFound, curDefVec, startBlock, endBlock);
|
|
}
|
|
else if (systemTypeQuery == CCurrencyDefinition::QUERY_SYSTEMTYPE_IMPORTED)
|
|
{
|
|
uint160 queryID = CCrossChainRPCData::GetConditionID(ASSETCHAINS_CHAINID, CCurrencyDefinition::ExternalCurrencyKey());
|
|
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> ¤cyIDs,
|
|
std::map<uint160, std::pair<CCurrencyDefinition,CPBaaSNotarization>> ¤cyDefs)
|
|
{
|
|
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 ¤cyID,
|
|
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 CheckVerusVaultAPIsValid()
|
|
{
|
|
//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_VERUSVAULT)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_REQUEST, "Verus Vault and VerusID Marketplace 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.");
|
|
}
|
|
}
|
|
|
|
// returns an i-address string when given a 20 byte hex representation
|
|
std::string ConvertAlternateRepresentations(const std::string ¶mStr)
|
|
{
|
|
// to enable easy conversion from a hex value representation of a currency, which is used in PBaaS folders and
|
|
// conf file names to ensure case insensitive support for unique folders and file names, the prefix "hex:" is allowed
|
|
// on currency names, or even IDs.
|
|
if (paramStr.length() == 44 && paramStr.substr(0, 4) == "hex:")
|
|
{
|
|
std::string hexVal = paramStr.substr(4, 40);
|
|
if (IsHex(hexVal))
|
|
{
|
|
CIdentityID idID;
|
|
idID.SetHex(hexVal);
|
|
return EncodeDestination(idID);
|
|
}
|
|
}
|
|
return paramStr;
|
|
}
|
|
|
|
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 ¶m, CCurrencyDefinition *pCurrencyDef=NULL)
|
|
{
|
|
return ValidateCurrencyName(ConvertAlternateRepresentations(uni_get_str(param)), true, pCurrencyDef);
|
|
}
|
|
|
|
UniValue getcurrency(const UniValue& params, bool fHelp)
|
|
{
|
|
if (fHelp || params.size() != 1)
|
|
{
|
|
throw runtime_error(
|
|
"getcurrency \"currencyname\"\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. \"currencyname\" (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"
|
|
" \"fullyqualifiedname\" : \"string\", (string) name or symbol of the chain with all parent namespaces, separated by \".\"\n"
|
|
" \"currencyid\" : \"i-address\", (string) string that represents the currency ID, same as the ID behind the currency\n"
|
|
" \"currencyidhex\" : \"hex\", (string) hex representation of currency ID, getcurrency API supports \"hex:currencyidhex\"\n"
|
|
" \"parent\" : \"i-address\", (string) parent blockchain ID\n"
|
|
" \"systemid\" : \"i-address\", (string) system on which this currency is considered to run\n"
|
|
" \"launchsystemid\" : \"i-address\", (string) system from which this currency was launched\n"
|
|
" \"notarizationprotocol\" : n (int) protocol number that determines variations in cross-chain or bridged notarizations\n"
|
|
" \"proofprotocol\" : n (int) protocol number that determines variations in cross-chain or bridged proofs\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"
|
|
" \"currencies\" : \"[\"i-address\", ...]\", (stringarray) currencies that can be converted to this currency at launch or makeup a liquidity basket\n"
|
|
" \"weights\" : \"[n, ...]\", (numberarray) relative currency weights (only returned for a liquidity basket)\n"
|
|
" \"conversions\" : \"[n, ...]\", (numberarray) pre-launch conversion rates for non-fractional currencies\n"
|
|
" \"minpreconversion\" : \"[n, ...]\", (numberarray) minimum amounts required in pre-conversions for currency to launch\n"
|
|
" \"currencies\" : \"[\"i-address\", ...]\", (stringarray) currencies that can be converted to this currency at launch or makeup a liquidity basket\n"
|
|
" \"currencynames\" : \"{\"i-address\":\"fullname\",...}\", (obj) i-addresses mapped to fully qualified names of all sub-currencies\n"
|
|
" \"initialsupply\" : n, (number) initial currency supply for fractional currencies before preallocation or issuance\n"
|
|
" \"prelaunchcarveout\" : n, (number) pre-launch percentage of proceeds for fractional currency sent to launching ID\n"
|
|
" \"preallocations\" : \"[{\"i-address\":n}, ...]\", (objarray) VerusIDs and amounts for pre-allocation at launch\n"
|
|
" \"initialcontributions\" : \"[n, ...]\", (numberarray) amounts of pre-conversions reserved for launching ID\n"
|
|
" \"idregistrationfees\" : n, (number) base cost of IDs for this currency namespace in this currency\n"
|
|
" \"idreferrallevels\" : n, (int) levels of ID referrals (only for native PBaaS chains and IDs)\n"
|
|
" \"idimportfees\" : n, (number) fees required to import an ID to this system (only for native PBaaS chains and IDs)\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", "\"currencyname\"")
|
|
+ HelpExampleRpc("getcurrency", "\"currencyname\"")
|
|
);
|
|
}
|
|
|
|
LOCK2(cs_main, mempool.cs);
|
|
CheckPBaaSAPIsValid();
|
|
|
|
UniValue ret(UniValue::VOBJ);
|
|
uint32_t height = chainActive.Height();
|
|
|
|
CCurrencyDefinition chainDef;
|
|
int32_t defHeight = 0;
|
|
CUTXORef defUTXO;
|
|
std::vector<CNodeData> goodNodes;
|
|
|
|
uint160 chainID = ValidateCurrencyName(uni_get_str(params[0]));
|
|
|
|
if (!GetCurrencyDefinition(chainID, chainDef, &defHeight, false, true, &defUTXO, &goodNodes))
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid currency or currency not found");
|
|
}
|
|
|
|
if (chainID.IsNull())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid currency name or ID");
|
|
}
|
|
|
|
if (chainDef.IsValid())
|
|
{
|
|
ret = chainDef.ToUniValue();
|
|
ret.pushKV("currencyidhex", chainDef.GetID().GetHex());
|
|
ret.pushKV("fullyqualifiedname", ConnectedChains.GetFriendlyCurrencyName(chainID));
|
|
|
|
if (chainDef.currencies.size())
|
|
{
|
|
UniValue curNames(UniValue::VOBJ);
|
|
for (auto &oneCurID : chainDef.currencies)
|
|
{
|
|
curNames.pushKV(EncodeDestination(CIdentityID(oneCurID)), ConnectedChains.GetFriendlyCurrencyName(oneCurID));
|
|
}
|
|
ret.pushKV("currencynames", curNames);
|
|
}
|
|
|
|
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)
|
|
{
|
|
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));
|
|
if (cnd.vtx[cnd.lastConfirmed].second.IsPreLaunch() && cnd.vtx[cnd.lastConfirmed].second.IsLaunchConfirmed())
|
|
{
|
|
ret.push_back(Pair("lastconfirmedheight", 0));
|
|
}
|
|
else
|
|
{
|
|
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::set<std::string> nodeAddressSet;
|
|
if (goodNodes.size())
|
|
{
|
|
UniValue nodeArr(UniValue::VARR);
|
|
for (auto &oneNode : goodNodes)
|
|
{
|
|
if (!nodeAddressSet.count(oneNode.networkAddress))
|
|
{
|
|
nodeAddressSet.insert(oneNode.networkAddress);
|
|
nodeArr.push_back(oneNode.ToUniValue());
|
|
}
|
|
}
|
|
ret.push_back(Pair("nodes", nodeArr));
|
|
}
|
|
|
|
if (cnd.forks.size())
|
|
{
|
|
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 (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)
|
|
{
|
|
/* // check serialization discrepancies
|
|
if (oneTransfer.IsCurrencyExport() && oneTransfer.destination.TypeNoFlags() == oneTransfer.destination.DEST_REGISTERCURRENCY)
|
|
{
|
|
CCurrencyDefinition exportDef = CCurrencyDefinition(oneTransfer.destination.destination);
|
|
CCurrencyDefinition curDef = ConnectedChains.GetCachedCurrency(exportDef.GetID());
|
|
auto exportDefVec = ::AsVector(exportDef);
|
|
auto onChainDefVec = ::AsVector(curDef);
|
|
auto translatedDefVec = ::AsVector(CCurrencyDefinition(exportDef.ToUniValue()));
|
|
if (exportDefVec != onChainDefVec || onChainDefVec != translatedDefVec)
|
|
{
|
|
printf("Exported currency:\n%s\nOn-chain currency:\n%s\nSerialized export:\n%s\nSerialized on-chain:\n%s\nSerialized translated:\n%s\n",
|
|
exportDef.ToUniValue().write(1,2).c_str(),
|
|
curDef.ToUniValue().write(1,2).c_str(),
|
|
HexBytes(&(exportDefVec[0]), exportDefVec.size()).c_str(),
|
|
HexBytes(&(onChainDefVec[0]), onChainDefVec.size()).c_str(),
|
|
HexBytes(&(translatedDefVec[0]), translatedDefVec.size()).c_str());
|
|
}
|
|
auto transferVec = ::AsVector(oneTransfer);
|
|
printf("ReserveTransfer:\n%s\nSerialized:\n%s\n", oneTransfer.ToUniValue().write(1,2).c_str(), HexBytes(&(transferVec[0]), transferVec.size()).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.");
|
|
}
|
|
|
|
LOCK(cs_main);
|
|
|
|
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;
|
|
|
|
{
|
|
LOCK2(smartTransactionCS, mempool.cs);
|
|
|
|
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\" | \"imported\" | \"gateway\" | \"pbaas\")\n"
|
|
" \"fromsystem\" : (\"systemnameeorid\") default is the local chain, but if currency is from another system, specify here\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"
|
|
" \"fullyqualifiedname\" : \"string\", (string) name or symbol of the chain with all parent namespaces, separated by \".\"\n"
|
|
" \"currencyid\" : \"i-address\", (string) string that represents the currency ID, same as the ID behind the currency\n"
|
|
" \"currencyidhex\" : \"hex\", (string) hex representation of currency ID, getcurrency API supports \"hex:currencyidhex\"\n"
|
|
" \"parent\" : \"i-address\", (string) parent blockchain ID\n"
|
|
" \"systemid\" : \"i-address\", (string) system on which this currency is considered to run\n"
|
|
" \"launchsystemid\" : \"i-address\", (string) system from which this currency was launched\n"
|
|
" \"notarizationprotocol\" : n (int) protocol number that determines variations in cross-chain or bridged notarizations\n"
|
|
" \"proofprotocol\" : n (int) protocol number that determines variations in cross-chain or bridged proofs\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"
|
|
" \"currencies\" : \"[\"i-address\", ...]\", (stringarray) currencies that can be converted to this currency at launch or makeup a liquidity basket\n"
|
|
" \"weights\" : \"[n, ...]\", (numberarray) relative currency weights (only returned for a liquidity basket)\n"
|
|
" \"conversions\" : \"[n, ...]\", (numberarray) pre-launch conversion rates for non-fractional currencies\n"
|
|
" \"minpreconversion\" : \"[n, ...]\", (numberarray) minimum amounts required in pre-conversions for currency to launch\n"
|
|
" \"currencies\" : \"[\"i-address\", ...]\", (stringarray) currencies that can be converted to this currency at launch or makeup a liquidity basket\n"
|
|
" \"currencynames\" : \"{\"i-address\":\"fullname\",...}\", (obj) i-addresses mapped to fully qualified names of all sub-currencies\n"
|
|
" \"initialsupply\" : n, (number) initial currency supply for fractional currencies before preallocation or issuance\n"
|
|
" \"prelaunchcarveout\" : n, (number) pre-launch percentage of proceeds for fractional currency sent to launching ID\n"
|
|
" \"preallocations\" : \"[{\"i-address\":n}, ...]\", (objarray) VerusIDs and amounts for pre-allocation at launch\n"
|
|
" \"initialcontributions\" : \"[n, ...]\", (numberarray) amounts of pre-conversions reserved for launching ID\n"
|
|
" \"idregistrationfees\" : n, (number) base cost of IDs for this currency namespace in this currency\n"
|
|
" \"idreferrallevels\" : n, (int) levels of ID referrals (only for native PBaaS chains and IDs)\n"
|
|
" \"idimportfees\" : n, (number) fees required to import an ID to this system (only for native PBaaS chains and IDs)\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"
|
|
"]\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},
|
|
{"imported", CCurrencyDefinition::QUERY_SYSTEMTYPE_IMPORTED},
|
|
{"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;
|
|
uint160 fromSystemID = ASSETCHAINS_CHAINID;
|
|
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"));
|
|
std::string fromSystemIDStr = uni_get_str(find_value(params[0], "fromsystem"));
|
|
if (fromSystemIDStr.size())
|
|
{
|
|
fromSystemID = ValidateCurrencyName(fromSystemIDStr, true);
|
|
if (fromSystemID.IsNull())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Blockchain or gateway specified in fromsystem not found");
|
|
}
|
|
systemType = "local";
|
|
}
|
|
|
|
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(fromSystemID, 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();
|
|
oneDefUni.pushKV("currencyidhex", def.GetID().GetHex());
|
|
oneDefUni.pushKV("fullyqualifiedname", ConnectedChains.GetFriendlyCurrencyName(def.GetID()));
|
|
|
|
if (oneDef.first.first.IsValid())
|
|
{
|
|
oneDefUni.push_back(Pair("definitiontxid", oneDef.first.first.hash.GetHex()));
|
|
oneDefUni.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, std::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 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 GetChainTransfersUnspentBy(std::multimap<uint160, std::pair<CInputDescriptor, CReserveTransfer>> &inputDescriptors, uint160 chainFilter, uint32_t start, uint32_t end, uint32_t unspentBy, 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
|
|
{
|
|
// This call does not include outputs that were mined in as spent at the
|
|
// end height requested
|
|
for (auto it = addressIndex.begin(); it != addressIndex.end(); it++)
|
|
{
|
|
CTransaction ntx;
|
|
uint256 blkHash;
|
|
|
|
if (it->first.spending)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// no matter where we are getting the reserve transfers from, if they are otherwise spent
|
|
// in the block prior, they are not considered unspent by the "end" block, meaning they
|
|
CSpentIndexValue spentInfo;
|
|
CSpentIndexKey spentKey(it->first.txhash, it->first.index);
|
|
|
|
if (GetSpentIndex(spentKey, spentInfo) &&
|
|
!spentInfo.IsNull() &&
|
|
spentInfo.blockHeight < unspentBy)
|
|
{
|
|
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(std::make_pair(((rt.flags & rt.IMPORT_TO_SOURCE) ? rt.FirstCurrency() : rt.destCurrencyID),
|
|
std::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 ¤cyID, CChainNotarizationData ¬arizationData, 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())
|
|
{
|
|
LogPrint("notarization", "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
|
|
uint32_t height = chainActive.Height();
|
|
if ((IsVerusActive() || height == 0) && currencyID == ASSETCHAINS_CHAINID)
|
|
{
|
|
CIdentityID proposer = VERUS_NOTARYID.IsNull() ? (VERUS_DEFAULTID.IsNull() ? VERUS_NODEID : VERUS_DEFAULTID) : VERUS_NOTARYID;
|
|
|
|
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 for currency %s\n", __func__, chainDef.ToUniValue().write(1,2).c_str());
|
|
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())
|
|
{
|
|
// 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 the notarization is a mirror, it's prior notarization is on the alternate chain
|
|
CUTXORef priorRef = n.prevNotarization;
|
|
if (p.evalCode == EVAL_ACCEPTEDNOTARIZATION && n.IsMirror())
|
|
{
|
|
// we should have another finalization of our prior following the
|
|
// pending finalization
|
|
CTransaction finalizationTx;
|
|
if (f.output.hash.IsNull())
|
|
{
|
|
finalizationTx = nTx;
|
|
}
|
|
|
|
CObjectFinalization priorOF;
|
|
if (!(myGetTransaction(it->first.txhash, finalizationTx, blkHash) &&
|
|
(it->first.index + 1) < finalizationTx.vout.size() &&
|
|
finalizationTx.vout[it->first.index + 1].scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
p.evalCode == EVAL_FINALIZE_NOTARIZATION &&
|
|
p.vData.size() &&
|
|
(priorOF = CObjectFinalization(p.vData[0])).IsValid()))
|
|
{
|
|
LogPrintf("%s: invalid index for finalization %s, output %d\n", __func__, it->first.txhash.GetHex().c_str(), (int)it->first.index);
|
|
continue;
|
|
}
|
|
// we should have a finalization right after on the same TX, pointing to the prior notarization that we care about
|
|
priorRef = priorOF.output;
|
|
}
|
|
|
|
// 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(priorRef, 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 if (notarizationData.vtx[notarizationData.forks[i].back()].second.IsLaunchCleared() &&
|
|
notarizationData.vtx[notarizationData.forks[i].back()].second.IsPreLaunch() &&
|
|
notarizationData.vtx[notarizationData.forks[i].back()].second.IsLaunchConfirmed())
|
|
{
|
|
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) ||
|
|
!notaryNotarization.proofRoots.count(ASSETCHAINS_CHAINID))
|
|
{
|
|
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,"
|
|
" \"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"
|
|
"\"laststableproofroot\" (object) either tip-BLOCK_MATURITY or last notarized/witnessed tip"
|
|
"\"lastconfirmedproofroot\" (object) last proof root of chain that has been confirmed"
|
|
"\"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 < 0)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid lastconfirmed");
|
|
}
|
|
|
|
std::map<uint32_t, std::pair<int32_t, CProofRoot>> proofRootMap;
|
|
UniValue uniProofRoots = find_value(params[0], "proofroots");
|
|
if (!uniProofRoots.isArray())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid proof root array parameter");
|
|
}
|
|
|
|
CProofRoot lastConfirmedRoot, lastConfirmedRootClaim;
|
|
|
|
if (uniProofRoots.size() > lastConfirmed)
|
|
{
|
|
lastConfirmedRootClaim = CProofRoot(uniProofRoots[lastConfirmed]);
|
|
}
|
|
|
|
if (!lastConfirmedRootClaim.IsValid())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid last confirmed proof root");
|
|
}
|
|
|
|
LOCK(cs_main);
|
|
|
|
UniValue retVal(UniValue::VOBJ);
|
|
|
|
// no notarization can be considered confirmed by another chain or system, if it has not already been first confirmed
|
|
// by the first notary of this one. any confirmed proof root must map to a confirmed notarization on this chain that is
|
|
// correct and at least before the last confirmed one on this chain
|
|
std::vector<std::pair<CTransaction, uint256>> notaryTxVec;
|
|
CChainNotarizationData notaryCND;
|
|
if (ConnectedChains.FirstNotaryChain().IsValid())
|
|
{
|
|
if (GetNotarizationData(ConnectedChains.FirstNotaryChain().GetID(), notaryCND, ¬aryTxVec) &&
|
|
notaryCND.IsConfirmed() &&
|
|
notaryCND.vtx[notaryCND.lastConfirmed].second.proofRoots.count(ASSETCHAINS_CHAINID))
|
|
{
|
|
lastConfirmedRoot = notaryCND.vtx[notaryCND.lastConfirmed].second.proofRoots[ASSETCHAINS_CHAINID];
|
|
}
|
|
else
|
|
{
|
|
// if we have a valid first notary, then we should have a valid confirmed notarization, or something is wrong
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid notarization for confirmed proof of current chain");
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < uniProofRoots.size(); i++)
|
|
{
|
|
CProofRoot oneRoot(uniProofRoots[i]);
|
|
if (!oneRoot.IsValid())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid proof root in array");
|
|
}
|
|
if (oneRoot.systemID != ASSETCHAINS_CHAINID)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("incorrect systemid in proof root for %s", EncodeDestination(CIdentityID(ASSETCHAINS_CHAINID))));
|
|
}
|
|
// if we have no notary chain, we could have an invalid last confirmed
|
|
proofRootMap.insert(std::make_pair(oneRoot.rootHeight, std::make_pair(i, oneRoot)));
|
|
}
|
|
|
|
uint32_t nHeight = chainActive.Height();
|
|
|
|
std::map<uint32_t, int32_t> validRoots; // height, index (only return the first valid at each height)
|
|
|
|
for (auto it = proofRootMap.rbegin(); it != proofRootMap.rend(); it ++)
|
|
{
|
|
// ignore potential dups
|
|
if (validRoots.count(it->second.second.rootHeight))
|
|
{
|
|
continue;
|
|
}
|
|
if (it->second.second == it->second.second.GetProofRoot(it->second.second.rootHeight))
|
|
{
|
|
validRoots.insert(std::make_pair(it->second.second.rootHeight, it->second.first));
|
|
}
|
|
}
|
|
|
|
if (lastConfirmedRoot.IsValid() &&
|
|
(!validRoots.count(lastConfirmedRootClaim.rootHeight) || lastConfirmedRootClaim.rootHeight > lastConfirmedRoot.rootHeight))
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("incorrect claim of confirmed proof root for height %u, %s", lastConfirmedRoot.rootHeight, EncodeDestination(CIdentityID(ASSETCHAINS_CHAINID))));
|
|
}
|
|
|
|
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());
|
|
if (lastConfirmedRoot.IsValid() && validRoots.count(lastConfirmedRoot.rootHeight))
|
|
{
|
|
retVal.pushKV("lastconfirmedproofroot", lastConfirmedRoot.ToUniValue());
|
|
retVal.pushKV("laststableproofroot", lastConfirmedRoot.ToUniValue());
|
|
retVal.pushKV("lastconfirmedindex", validRoots[lastConfirmedRoot.rootHeight]);
|
|
}
|
|
else if (lastConfirmedRoot.IsValid())
|
|
{
|
|
retVal.pushKV("laststableproofroot", lastConfirmedRoot.ToUniValue());
|
|
}
|
|
else
|
|
{
|
|
retVal.pushKV("laststableproofroot", CProofRoot::GetProofRoot((nHeight - COINBASE_MATURITY) > 0 ? (nHeight - COINBASE_MATURITY) : 1).ToUniValue());
|
|
}
|
|
|
|
std::set<uint160> currenciesSet({ASSETCHAINS_CHAINID});
|
|
CCurrencyDefinition targetCur;
|
|
uint160 targetCurID;
|
|
UniValue currencyStatesUni(UniValue::VARR);
|
|
UniValue confirmedCurrencyStatesUni(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))));
|
|
}
|
|
if (lastConfirmedRoot.IsValid())
|
|
{
|
|
confirmedCurrencyStatesUni.push_back(ConnectedChains.GetCurrencyState(targetCur, lastConfirmedRoot.rootHeight).ToUniValue());
|
|
}
|
|
else
|
|
{
|
|
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());
|
|
if (lastConfirmedRoot.IsValid())
|
|
{
|
|
confirmedCurrencyStatesUni.push_back(ConnectedChains.GetCurrencyState(targetCur, lastConfirmedRoot.rootHeight).ToUniValue());
|
|
}
|
|
}
|
|
}
|
|
retVal.pushKV("currencystates", lastConfirmedRoot.IsValid() ? confirmedCurrencyStatesUni : currencyStatesUni);
|
|
retVal.pushKV("latestcurrencystates", 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"
|
|
"If 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();
|
|
|
|
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()); */
|
|
|
|
TransactionBuilder tb(Params().GetConsensus(), nHeight, pwalletMain);
|
|
|
|
{
|
|
LOCK2(cs_main, mempool.cs);
|
|
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.GetNotarySignatures().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");
|
|
}
|
|
|
|
// flip back to normal earned notarization as before
|
|
pbn.SetMirror(false);
|
|
|
|
// 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
|
|
|
|
CValidationState state;
|
|
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);
|
|
std::vector<TransactionBuilderResult> buildResultVec;
|
|
|
|
{
|
|
LOCK2(cs_main, pwalletMain->cs_wallet);
|
|
buildResultVec.push_back(tb.Build());
|
|
}
|
|
auto buildResult = buildResultVec[0];
|
|
CTransaction newTx;
|
|
if (buildResult.IsTx())
|
|
{
|
|
newTx = buildResult.GetTxOrThrow();
|
|
}
|
|
else
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, buildResult.GetError());
|
|
}
|
|
|
|
bool relayTx;
|
|
{
|
|
LOCK(cs_main);
|
|
LOCK2(smartTransactionCS, mempool.cs);
|
|
std::list<CTransaction> removed;
|
|
mempool.removeConflicts(newTx, removed);
|
|
|
|
// add to mem pool and relay
|
|
relayTx = myAddtomempool(newTx);
|
|
}
|
|
|
|
// add to mem pool and relay
|
|
if (relayTx)
|
|
{
|
|
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);
|
|
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);
|
|
}
|
|
|
|
CCoinbaseCurrencyState retVal(cState,
|
|
0,
|
|
0,
|
|
0,
|
|
std::vector<int64_t>(chainDef.currencies.size()),
|
|
std::vector<int64_t>(chainDef.currencies.size()),
|
|
std::vector<int64_t>(chainDef.currencies.size()),
|
|
conversions,
|
|
std::vector<int64_t>(chainDef.currencies.size()),
|
|
reserveFees,
|
|
reserveFees,
|
|
0,
|
|
std::vector<int32_t>(chainDef.currencies.size()));
|
|
|
|
return retVal;
|
|
}
|
|
|
|
std::vector<CAddressUnspentDbEntry> GetFractionalNotarizationsForReserve(const uint160 ¤cyID)
|
|
{
|
|
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\n"
|
|
" 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\n"
|
|
" transaction is mined before start of currency\n"
|
|
" \"via\":\"name\", (string, optional) If source and destination currency are reserves, via is a common fractional\n"
|
|
" to convert through\n"
|
|
" }\n"
|
|
|
|
"\nResult:\n"
|
|
" {\n"
|
|
" \"estimatereceived\": (value), Estimated amount of converted currency after conversion\n"
|
|
" \"estimatedslippage\": (value), Estimated percent slippage from conversion\n"
|
|
" \"transactionsperblock100\": (value), Transactions per block over last 100 blocks\n"
|
|
" \"transactionsperblock10\": (value), Transactions per block over last 10 blocks\n"
|
|
" }\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 (preConvert && !(pFractionalCurrency->IsGatewayConverter() && pFractionalCurrency->launchSystemID == ASSETCHAINS_CHAINID))
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Can only preconvert to currencies launching on the current chain");
|
|
}
|
|
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);
|
|
}
|
|
|
|
UniValue retVal(UniValue::VOBJ);
|
|
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;
|
|
}
|
|
|
|
bool find_utxos(const CTxDestination &fromtaddr_, std::vector<COutput> &t_inputs_)
|
|
{
|
|
std::set<CTxDestination> destinations;
|
|
|
|
bool wildCardPKH = false;
|
|
bool wildCardID = false;
|
|
bool isFromSpecificID = fromtaddr_.which() == COptCCParams::ADDRTYPE_ID && !GetDestinationID(fromtaddr_).IsNull();
|
|
|
|
// if no specific address type, wildcard outputs to all transparent addresses and IDs are valid to consider
|
|
if (fromtaddr_.which() == COptCCParams::ADDRTYPE_INVALID)
|
|
{
|
|
wildCardPKH = true;
|
|
wildCardID = true;
|
|
}
|
|
// wildcard for all transparent addresses, except IDs is null PKH
|
|
else if (fromtaddr_.which() == COptCCParams::ADDRTYPE_PKH && GetDestinationID(fromtaddr_).IsNull())
|
|
{
|
|
wildCardPKH = true;
|
|
}
|
|
// wildcard for all ID transparent outputs is null ID
|
|
else if (fromtaddr_.which() == COptCCParams::ADDRTYPE_ID && GetDestinationID(fromtaddr_).IsNull())
|
|
{
|
|
wildCardID = true;
|
|
}
|
|
else
|
|
{
|
|
destinations.insert(fromtaddr_);
|
|
}
|
|
|
|
vector<COutput> vecOutputs;
|
|
|
|
pwalletMain->AvailableReserveCoins(vecOutputs,
|
|
false,
|
|
NULL,
|
|
true,
|
|
true,
|
|
wildCardPKH || wildCardID ? nullptr : &fromtaddr_,
|
|
nullptr,
|
|
false);
|
|
|
|
for (COutput& out : vecOutputs)
|
|
{
|
|
CTxDestination dest;
|
|
|
|
if (!isFromSpecificID && !out.fSpendable) {
|
|
continue;
|
|
}
|
|
|
|
if (out.nDepth < 0) {
|
|
continue;
|
|
}
|
|
|
|
std::vector<CTxDestination> addresses;
|
|
int nRequired;
|
|
bool canSign, canSpend;
|
|
CTxDestination address;
|
|
txnouttype txType;
|
|
if (!ExtractDestinations(out.tx->vout[out.i].scriptPubKey, txType, addresses, nRequired, pwalletMain, &canSign, &canSpend))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (isFromSpecificID)
|
|
{
|
|
// if we have more address destinations than just this address and have specified from a single ID only,
|
|
// the condition must be such that the ID itself can spend, even if this wallet cannot due to a multisig
|
|
// ID. if the ID cannot spend, even given a valid multisig ID, then to select this as a source without
|
|
// an explicit, multisig match would cause potentially unwanted sourcing of funds. a spend just to this ID
|
|
// is fine.
|
|
|
|
COptCCParams p, m;
|
|
// if we can't spend and can only sign,
|
|
// ensure that this output is spendable by just this ID as a 1 of n and 1 of n at the master
|
|
// smart transaction level as well
|
|
if (!canSpend &&
|
|
(!canSign ||
|
|
!(out.tx->vout[out.i].scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
(p.version < COptCCParams::VERSION_V3 ||
|
|
(p.vData.size() &&
|
|
(m = COptCCParams(p.vData.back())).IsValid() &&
|
|
(m.m == 1 || m.m == 0))) &&
|
|
p.m == 1)))
|
|
{
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
out.fSpendable = true; // this may not really be spendable, but set it if its the correct ID source and can sign
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!out.fSpendable)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
bool keep = false;
|
|
std::pair<CIdentityMapKey, CIdentityMapValue> keyAndIdentity;
|
|
for (auto &address : addresses)
|
|
{
|
|
if (isFromSpecificID)
|
|
{
|
|
if (address == fromtaddr_)
|
|
{
|
|
keep = true;
|
|
}
|
|
}
|
|
else if (wildCardID || wildCardPKH)
|
|
{
|
|
if (wildCardPKH)
|
|
{
|
|
keep = (address.which() == COptCCParams::ADDRTYPE_PKH || address.which() == COptCCParams::ADDRTYPE_PK) &&
|
|
pwalletMain->HaveKey(GetDestinationID(address));
|
|
}
|
|
if (!keep && wildCardID)
|
|
{
|
|
keep = address.which() == COptCCParams::ADDRTYPE_ID &&
|
|
pwalletMain->GetIdentity(CIdentityID(GetDestinationID(address)), keyAndIdentity) &&
|
|
keyAndIdentity.first.CanSign();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
keep = destinations.count(address);
|
|
}
|
|
if (keep)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!keep)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
t_inputs_.push_back(out);
|
|
}
|
|
|
|
// sort in ascending order, so smaller utxos appear first
|
|
std::sort(t_inputs_.begin(), t_inputs_.end(), [](COutput i, COutput j) -> bool {
|
|
return ( i.tx->vout[i.i].nValue < j.tx->vout[j.i].nValue );
|
|
});
|
|
|
|
return t_inputs_.size() > 0;
|
|
}
|
|
|
|
std::vector<SaplingNoteEntry> find_unspent_notes(const libzcash::PaymentAddress &fromaddress_)
|
|
{
|
|
std::vector<SaplingNoteEntry> retVal;
|
|
|
|
std::vector<SproutNoteEntry> sproutEntries;
|
|
std::vector<SaplingNoteEntry> saplingEntries;
|
|
{
|
|
LOCK2(cs_main, pwalletMain->cs_wallet);
|
|
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, EncodePaymentAddress(fromaddress_));
|
|
}
|
|
|
|
sproutEntries.clear();
|
|
|
|
for (auto entry : saplingEntries) {
|
|
retVal.push_back(entry);
|
|
std::string data(entry.memo.begin(), entry.memo.end());
|
|
LogPrint("zrpcunsafe", "found unspent Sapling note (txid=%s, vShieldedSpend=%d, amount=%s, memo=%s)\n",
|
|
entry.op.hash.ToString().substr(0, 10),
|
|
entry.op.n,
|
|
ValueFromAmount(entry.note.value()).write(),
|
|
HexStr(data).substr(0, 10));
|
|
}
|
|
|
|
if (retVal.empty()) {
|
|
return retVal;
|
|
}
|
|
|
|
// sort in descending order, so big notes appear first
|
|
std::sort(retVal.begin(), retVal.end(),
|
|
[](SaplingNoteEntry i, SaplingNoteEntry j) -> bool {
|
|
return i.note.value() > j.note.value();
|
|
});
|
|
|
|
return retVal;
|
|
}
|
|
|
|
CAmount CalculateFractionalPrice(CAmount smallNumerator, CAmount smallDenominator, bool roundup)
|
|
{
|
|
static arith_uint256 bigZero(0);
|
|
static arith_uint256 BigSatoshi(SATOSHIDEN);
|
|
static arith_uint256 BigSatoshiSquared = BigSatoshi * BigSatoshi;
|
|
|
|
arith_uint256 denominator = smallDenominator * BigSatoshi;
|
|
arith_uint256 numerator = smallNumerator * BigSatoshiSquared;
|
|
arith_uint256 bigAnswer = numerator / denominator;
|
|
int64_t remainder = (numerator - (bigAnswer * denominator)).GetLow64();
|
|
CAmount answer = bigAnswer.GetLow64();
|
|
if (remainder && roundup)
|
|
{
|
|
answer++;
|
|
}
|
|
return answer;
|
|
}
|
|
|
|
bool GetOpRetChainOffer(const CTransaction &postedTx,
|
|
CTransaction &offerTx,
|
|
CTransaction &inputToOfferTx,
|
|
uint32_t height,
|
|
bool getUnexpired,
|
|
bool getExpired,
|
|
uint256 &offerBlockHash)
|
|
{
|
|
std::vector<CBaseChainObject *> opRetArray;
|
|
CPartialTransactionProof offerTxProof;
|
|
bool isPartial = false, incompleteTx = false;
|
|
COptCCParams p;
|
|
CSpentIndexKey spentKey = CSpentIndexKey(postedTx.GetHash(), 1);
|
|
CSpentIndexValue spentValue;
|
|
CTransaction opRetTx;
|
|
uint256 opRetBlockHash;
|
|
|
|
if (postedTx.vout.size() > 1 &&
|
|
postedTx.vout[0].scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
(p.evalCode == EVAL_IDENTITY_COMMITMENT || p.evalCode == EVAL_IDENTITY_PRIMARY) &&
|
|
postedTx.vout[0].nValue >= DEFAULT_TRANSACTION_FEE &&
|
|
((postedTx.vout.back().scriptPubKey.IsOpReturn() &&
|
|
(opRetArray = RetrieveOpRetArray((opRetTx = postedTx).vout.back().scriptPubKey)).size() == 1) ||
|
|
(GetSpentIndex(spentKey, spentValue) &&
|
|
myGetTransaction(spentValue.txid, opRetTx, opRetBlockHash) &&
|
|
(opRetArray = RetrieveOpRetArray(opRetTx.vout.back().scriptPubKey)).size() == 1)) &&
|
|
opRetArray[0]->objectType == CHAINOBJ_TRANSACTION_PROOF &&
|
|
(offerTxProof = ((CChainObject<CPartialTransactionProof> *)(opRetArray[0]))->object).IsValid() &&
|
|
!offerTxProof.GetPartialTransaction(offerTx, &isPartial).IsNull() &&
|
|
!isPartial &&
|
|
offerTx.vout.size() == 1 &&
|
|
offerTx.vin.size() == 1 &&
|
|
offerTx.vShieldedSpend.size() == 0 &&
|
|
((spentKey = CSpentIndexKey(offerTx.vin[0].prevout.hash, offerTx.vin[0].prevout.n), !GetSpentIndex(spentKey, spentValue))) &&
|
|
((getExpired && offerTx.nExpiryHeight <= height) || (getUnexpired && offerTx.nExpiryHeight > height)) &&
|
|
myGetTransaction(offerTx.vin[0].prevout.hash, inputToOfferTx, offerBlockHash))
|
|
{
|
|
return true;
|
|
}
|
|
else if (getExpired &&
|
|
!(offerTxProof.IsValid() && !isPartial && offerTx.nExpiryHeight > height) &&
|
|
p.IsValid() &&
|
|
p.evalCode == EVAL_IDENTITY_COMMITMENT &&
|
|
postedTx.vout[0].nValue >= DEFAULT_TRANSACTION_FEE &&
|
|
p.vData.size() > 1 &&
|
|
COptCCParams(p.vData.back()).vKeys.size() > 1 &&
|
|
myGetTransaction(postedTx.GetHash(), inputToOfferTx, offerBlockHash) &&
|
|
!offerBlockHash.IsNull() &&
|
|
mapBlockIndex.count(offerBlockHash) &&
|
|
(mapBlockIndex[offerBlockHash]->GetHeight() + DEFAULT_PRE_BLOSSOM_TX_EXPIRY_DELTA) < height)
|
|
{
|
|
CMutableTransaction mtx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), height);
|
|
mtx.vin.push_back(CTxIn(postedTx.GetHash(), 0));
|
|
mtx.vout.push_back(postedTx.vout[0]);
|
|
mtx.nExpiryHeight = std::min(height - 1, (uint32_t)0);
|
|
offerTx = mtx;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool GetOpRetChainOffer(const CTransaction &postedTx,
|
|
CTransaction &offerTx,
|
|
CTransaction &inputToOfferTx,
|
|
uint32_t height)
|
|
{
|
|
bool getUnexpired = true;
|
|
bool getExpired = false;
|
|
uint256 offerBlockHash;
|
|
return GetOpRetChainOffer(postedTx, offerTx, inputToOfferTx, height, getUnexpired, getExpired, offerBlockHash);
|
|
}
|
|
|
|
struct OfferInfo
|
|
{
|
|
public:
|
|
CTransaction offerTx;
|
|
CTransaction inputToOfferTx;
|
|
uint256 blockHash;
|
|
};
|
|
|
|
// returns a map with the first boolean being the "unexpired" state. if 0, the offer is expired
|
|
bool GetMyOffers(std::map<std::pair<bool, uint256>, OfferInfo> &myOffers, uint32_t height, bool getUnexpired, bool getExpired)
|
|
{
|
|
bool retVal = false;
|
|
LOCK(pwalletMain->cs_wallet);
|
|
for (auto &txPair : pwalletMain->mapWallet)
|
|
{
|
|
OfferInfo oneOfferInfo;
|
|
if (txPair.second.IsInMainChain() &&
|
|
txPair.second.vout.size() > 0 &&
|
|
!pwalletMain->IsSpent(txPair.second.GetHash(), 0) &&
|
|
GetOpRetChainOffer(txPair.second, oneOfferInfo.offerTx, oneOfferInfo.inputToOfferTx, height, getUnexpired, getExpired, oneOfferInfo.blockHash))
|
|
{
|
|
CSpentIndexKey spentKey = CSpentIndexKey(oneOfferInfo.inputToOfferTx.GetHash(), oneOfferInfo.offerTx.vin[0].prevout.n);
|
|
CSpentIndexValue spentValue;
|
|
if (!GetSpentIndex(spentKey, spentValue))
|
|
{
|
|
bool isExpired = (oneOfferInfo.offerTx.nExpiryHeight <= height);
|
|
myOffers.insert(std::make_pair(std::make_pair(!isExpired, txPair.first), oneOfferInfo));
|
|
}
|
|
retVal = true;
|
|
}
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
/** Pushes a JSON object for script verification or signing errors to vErrorsRet. */
|
|
void SigningErrorToJSON(const CTxIn& txin, UniValue& vErrorsRet, const std::string& strMessage)
|
|
{
|
|
UniValue entry(UniValue::VOBJ);
|
|
entry.push_back(Pair("txid", txin.prevout.hash.ToString()));
|
|
entry.push_back(Pair("vout", (uint64_t)txin.prevout.n));
|
|
entry.push_back(Pair("scriptSig", HexStr(txin.scriptSig.begin(), txin.scriptSig.end())));
|
|
entry.push_back(Pair("sequence", (uint64_t)txin.nSequence));
|
|
entry.push_back(Pair("error", strMessage));
|
|
vErrorsRet.push_back(entry);
|
|
}
|
|
|
|
UniValue makeoffer(const UniValue& params, bool fHelp)
|
|
{
|
|
if (fHelp || params.size() < 2 || params.size() > 4)
|
|
{
|
|
throw runtime_error(
|
|
"makeoffer fromaddress '{\"changeaddress\":\"transparentoriaddress\", \"expiryheight\":blockheight, \"offer\":{\"currency\":\"anycurrency\", \"amount\":...} | {\"identity\":\"idnameoriaddress\",...}', \"for\":{\"address\":..., \"currency\":\"anycurrency\", \"amount\":...} | {\"name\":\"identityforswap\",\"parent\":\"parentid\",\"primaryaddresses\":[\"R-address(s)\"],\"minimumsignatures\":1,...}}' (returntx) (feeamount)\n"
|
|
"\nThis sends a transaction which provides a completely decentralized, fully on-chain an atomic swap offer for\n"
|
|
"\"decentralized swapping of any blockchain asset, including any/multi currencies, NFTs, identities, contractual\n"
|
|
"\"agreements and rights transfers, or to be used as bids for an on-chain auction of any blockchain asset(s).\n"
|
|
"\"Sources and destination of funds for swaps can be any valid transparent address capable of holding or controlling\n"
|
|
"the specific asset.\n"
|
|
|
|
"\nArguments\n"
|
|
"1. \"fromaddress\" (string, required) The VerusID, or wildcard address to send funds from. \"*\", \"R*\", or \"i*\" are valid wildcards\n"
|
|
"2. {\n"
|
|
" \"changeaddress\" (string, required) Change destination when constructing transactions\n"
|
|
" \"expiryheight\" (number, optional) Block height at which this offer expires. Defaults to 20 blocks (avg 1/minute)\n"
|
|
" \"offer\" (object, required) Funds description or identity name, \"address\" in this object should be an address of the person making an offer for change\n"
|
|
" \"for\" (object, required) Funds description or full identity description\n"
|
|
" }\n"
|
|
"3. \"returntx\" (bool, optional) default = false, if true, returns a transaction waiting for taker completion instead of posting\n"
|
|
"4. \"feeamount\" (value, optional) default = 0.0001\n"
|
|
|
|
"\nResult:\n"
|
|
"{\n"
|
|
" \"txid\" : \"transactionid\", The hex transaction id on success\n"
|
|
" \"hex\" : \"serializedtx\" If hex is requested, hex serialization of partial transaction instead of txid is returned on success\n"
|
|
"}\n"
|
|
|
|
"\nExamples:\n"
|
|
+ HelpExampleCli("makeoffer", "fromaddress '{\"changeaddress\":\"transparentoriaddress\", \"expiryheight\":blockheight, \"offer\":{\"currency\":\"anycurrency\", \"amount\":...} | {\"identity\":\"idnameoriaddress\",...}', \"for\":{\"address\":..., \"currency\":\"anycurrency\", \"amount\":...} | {\"name\":\"identityforswap\",\"parent\":\"parentid\",\"primaryaddresses\":[\"R-address(s)\"],\"minimumsignatures\":1,...}}' (returntx) (feeamount)")
|
|
+ HelpExampleRpc("makeoffer", "fromaddress '{\"changeaddress\":\"transparentoriaddress\", \"expiryheight\":blockheight, \"offer\":{\"currency\":\"anycurrency\", \"amount\":...} | {\"identity\":\"idnameoriaddress\",...}', \"for\":{\"address\":..., \"currency\":\"anycurrency\", \"amount\":...} | {\"name\":\"identityforswap\",\"parent\":\"parentid\",\"primaryaddresses\":[\"R-address(s)\"],\"minimumsignatures\":1,...}}' (returntx) (feeamount)")
|
|
);
|
|
}
|
|
|
|
CheckVerusVaultAPIsValid();
|
|
|
|
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;
|
|
|
|
std::vector<CRecipient> outputs;
|
|
|
|
LOCK2(cs_main, pwalletMain->cs_wallet);
|
|
|
|
libzcash::PaymentAddress zaddressSource;
|
|
libzcash::SaplingExpandedSpendingKey expsk;
|
|
uint256 sourceOvk;
|
|
bool hasZSource = !wildCardAddress && pwalletMain->GetAndValidateSaplingZAddress(sourceAddress, zaddressSource);
|
|
// 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)
|
|
{
|
|
sourceAddress = EncodePaymentAddress(zaddressSource);
|
|
// We don't need to lock on the wallet as spending key related methods are thread-safe
|
|
if (!boost::apply_visitor(HaveSpendingKeyForPaymentAddress(pwalletMain), zaddressSource)) {
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid from address, no spending key found for zaddr");
|
|
}
|
|
|
|
auto spendingkey_ = boost::apply_visitor(GetSpendingKeyForPaymentAddress(pwalletMain), zaddressSource).get();
|
|
auto sk = boost::get<libzcash::SaplingExtendedSpendingKey>(spendingkey_);
|
|
expsk = sk.expsk;
|
|
sourceOvk = expsk.full_viewing_key().ovk;
|
|
}
|
|
|
|
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.");
|
|
}
|
|
|
|
CTxDestination from_taddress;
|
|
if (wildCardTransparentAddress)
|
|
{
|
|
from_taddress = CTxDestination();
|
|
}
|
|
else if (wildCardRAddress)
|
|
{
|
|
from_taddress = CTxDestination(CKeyID(uint160()));
|
|
}
|
|
else if (wildCardiAddress)
|
|
{
|
|
from_taddress = CTxDestination(CIdentityID(uint160()));
|
|
}
|
|
else
|
|
{
|
|
from_taddress = sourceDest;
|
|
}
|
|
|
|
if (!params[1].isObject())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameters. Second parameter must be object. See help.");
|
|
}
|
|
|
|
bool returnHex = false;
|
|
if (params.size() > 2)
|
|
{
|
|
returnHex = uni_get_bool(params[2], returnHex);
|
|
}
|
|
|
|
CAmount feeAmount = DEFAULT_TRANSACTION_FEE;
|
|
if (params.size() > 3)
|
|
{
|
|
feeAmount = AmountFromValue(params[3]);
|
|
}
|
|
|
|
UniValue offerValue = find_value(params[1], "offer");
|
|
UniValue forValue = find_value(params[1], "for");
|
|
if (!offerValue.isObject() || !forValue.isObject())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Both \"offer\" and \"for\" must be valid objects in the first parameter object");
|
|
}
|
|
|
|
uint32_t height = chainActive.Height();
|
|
|
|
CMutableTransaction offerTx = CreateNewContextualCMutableTransaction(Params().consensus, height + 1);
|
|
uint32_t expiryHeight = uni_get_int(find_value(params[1], "expiryheight"));
|
|
if (expiryHeight > offerTx.nExpiryHeight && expiryHeight < TX_EXPIRY_HEIGHT_THRESHOLD)
|
|
{
|
|
offerTx.nExpiryHeight = expiryHeight;
|
|
}
|
|
|
|
CInputDescriptor offerIn;
|
|
std::vector<CInputDescriptor> postedOfferIns;
|
|
CTxDestination changeDestination;
|
|
CTxDestination fundsDestination;
|
|
uint160 offerID;
|
|
uint160 offerCurrencyID;
|
|
CTxIn idTxIn;
|
|
CIdentity oldID;
|
|
uint32_t idHeight;
|
|
uint160 newIDID;
|
|
uint160 newCurrencyID;
|
|
CTransaction preTx;
|
|
|
|
bool hasZDest = false;
|
|
libzcash::PaymentAddress zaddressDest;
|
|
libzcash::SaplingPaymentAddress *saplingAddress;
|
|
void *saplingOutputCtx = nullptr;
|
|
|
|
auto changeAddressStr = TrimSpaces(uni_get_str(find_value(params[1], "changeaddress")));
|
|
if (changeAddressStr.empty() || (changeDestination = ValidateDestination(changeAddressStr)).which() == COptCCParams::ADDRTYPE_INVALID)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "changeaddress must be valid");
|
|
}
|
|
|
|
if (find_value(forValue, "name").isNull())
|
|
{
|
|
if (!find_value(forValue, "identity").isNull())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "To buy an identity, define the identity by the \"for\" object as you would when registering, with \"name\", \"primaryaddresses\", etc. Do not use an \"identity\" tag");
|
|
}
|
|
|
|
auto currencyStr = TrimSpaces(uni_get_str(find_value(forValue, "currency")));
|
|
CAmount destinationAmount = AmountFromValue(find_value(forValue, "amount"));
|
|
auto memoStr = TrimSpaces(uni_get_str(find_value(forValue, "memo")));
|
|
|
|
CCurrencyDefinition sourceCurrencyDef;
|
|
if (currencyStr.empty())
|
|
{
|
|
sourceCurrencyDef = ConnectedChains.ThisChain();
|
|
newCurrencyID = sourceCurrencyDef.GetID();
|
|
currencyStr = EncodeDestination(CIdentityID(ConnectedChains.ThisChain().GetID()));
|
|
}
|
|
else
|
|
{
|
|
newCurrencyID = ValidateCurrencyName(currencyStr, true, &sourceCurrencyDef);
|
|
if (newCurrencyID.IsNull())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "If source currency is specified, it must be valid.");
|
|
}
|
|
}
|
|
|
|
auto destStr = TrimSpaces(uni_get_str(find_value(forValue, "address")));
|
|
|
|
fundsDestination = ValidateDestination(destStr);
|
|
CTransferDestination dest;
|
|
|
|
if (fundsDestination.which() == COptCCParams::ADDRTYPE_INVALID)
|
|
{
|
|
// make the funds output that defines what we are willing to accept for the input we are offering
|
|
hasZDest = pwalletMain->GetAndValidateSaplingZAddress(destStr, zaddressDest);
|
|
if (hasZDest)
|
|
{
|
|
saplingAddress = boost::get<libzcash::SaplingPaymentAddress>(&zaddressDest);
|
|
}
|
|
else
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Specified \"for\" destination address must be valid");
|
|
}
|
|
if (saplingAddress == nullptr)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Only sapling addresses may be used as a private \"for\" destination");
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
uint160 parentID = uint160(GetDestinationID(DecodeDestination(uni_get_str(find_value(forValue, "parent")))));
|
|
if (parentID.IsNull() && (parentID = ValidateCurrencyName(uni_get_str(find_value(forValue, "parent")), true)).IsNull())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "To ensure reference to the correct identity, parent must be a valid, non-null value.");
|
|
}
|
|
|
|
CIdentity forID(forValue);
|
|
if (!forID.IsValid())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "New ID definition of the ID for which offer is being made must be valid.");
|
|
}
|
|
|
|
std::string nameStr = CleanName(uni_get_str(find_value(forValue, "name")), parentID);
|
|
newIDID = CIdentity::GetID(nameStr, parentID);
|
|
if (newIDID.IsNull())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "identity, " + nameStr + " specification is not valid -- " + (nameStr.empty() ? "must have valid name" : "maybe needs parent?"));
|
|
}
|
|
|
|
if (!(oldID = CIdentity::LookupIdentity(newIDID, 0, &idHeight, &idTxIn)).IsValid())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "identity, " + nameStr + " (" +EncodeDestination(CIdentityID(newIDID)) + "), not found ");
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
std:vector<COutput> vCoins;
|
|
|
|
// first, construct the "offer" input, which will either be funds or an ID from this wallet
|
|
if (find_value(offerValue, "identity").isNull())
|
|
{
|
|
auto currencyStr = TrimSpaces(uni_get_str(find_value(offerValue, "currency")));
|
|
CAmount sourceAmount = AmountFromValue(find_value(offerValue, "amount"));
|
|
|
|
if (!sourceAmount)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "A currency offer must include a valid amount");
|
|
}
|
|
|
|
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 = ConnectedChains.ThisChain();
|
|
sourceCurrencyID = sourceCurrencyDef.GetID();
|
|
currencyStr = EncodeDestination(CIdentityID(ConnectedChains.ThisChain().GetID()));
|
|
}
|
|
|
|
offerCurrencyID = sourceCurrencyID;
|
|
CRecipient oneOutput;
|
|
|
|
if (sourceCurrencyID == ASSETCHAINS_CHAINID)
|
|
{
|
|
oneOutput.nAmount = sourceAmount;
|
|
oneOutput.scriptPubKey = GetScriptForDestination(changeDestination);
|
|
}
|
|
else
|
|
{
|
|
oneOutput.nAmount = 0;
|
|
|
|
std::vector<CTxDestination> dests = std::vector<CTxDestination>({changeDestination});
|
|
CTokenOutput to(sourceCurrencyID, sourceAmount);
|
|
|
|
oneOutput.scriptPubKey = MakeMofNCCScript(CConditionObj<CTokenOutput>(EVAL_RESERVE_OUTPUT, dests, 1, &to));
|
|
}
|
|
|
|
bool success = false;
|
|
std::set<std::pair<const CWalletTx *, unsigned int>> setCoinsRet;
|
|
std::vector<SaplingNoteEntry> saplingNotes;
|
|
CCurrencyValueMap reserveValueOut;
|
|
CAmount nativeValueOut;
|
|
CAmount totalOriginationFees = feeAmount;
|
|
if (!returnHex)
|
|
{
|
|
totalOriginationFees += feeAmount;
|
|
// if we're posting, and it is not a native currency offer, add an extra fee
|
|
// to enable closing the offer
|
|
if (sourceCurrencyID != ASSETCHAINS_CHAINID)
|
|
{
|
|
// add one more fee to enable auto-cancellation when offering non-native currencies
|
|
totalOriginationFees += feeAmount;
|
|
}
|
|
}
|
|
|
|
// first make an input transaction to split the offer funds into an exact input and change, if needed
|
|
if (sourceCurrencyID == ASSETCHAINS_CHAINID)
|
|
{
|
|
if (hasZSource)
|
|
{
|
|
saplingNotes = find_unspent_notes(zaddressSource);
|
|
CAmount totalFound = 0;
|
|
int i;
|
|
for (i = 0; i < saplingNotes.size(); i++)
|
|
{
|
|
totalFound += saplingNotes[i].note.value();
|
|
if (totalFound >= (oneOutput.nAmount + totalOriginationFees))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
// remove all but the notes we'll use
|
|
if (i < saplingNotes.size())
|
|
{
|
|
saplingNotes.erase(saplingNotes.begin() + i + 1, saplingNotes.end());
|
|
success = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
success = find_utxos(from_taddress, vCoins) &&
|
|
pwalletMain->SelectCoinsMinConf(oneOutput.nAmount + totalOriginationFees, 0, 0, vCoins, setCoinsRet, nativeValueOut);
|
|
}
|
|
}
|
|
else if (hasZSource)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot source non-native currencies from a private address");
|
|
}
|
|
else
|
|
{
|
|
success = find_utxos(from_taddress, vCoins);
|
|
success = success && pwalletMain->SelectReserveCoinsMinConf(oneOutput.scriptPubKey.ReserveOutValue(),
|
|
totalOriginationFees,
|
|
0,
|
|
1,
|
|
vCoins,
|
|
setCoinsRet,
|
|
reserveValueOut,
|
|
nativeValueOut);
|
|
}
|
|
if (!success)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Insufficient funds for offer");
|
|
}
|
|
|
|
// we need one output to create the proper index entry
|
|
CKeyID offerIDKey = COnChainOffer::OnChainCurrencyOfferKey(offerCurrencyID);
|
|
CKeyID forIDKey = newIDID.IsNull() ?
|
|
COnChainOffer::OnChainOfferForCurrencyKey(newCurrencyID) :
|
|
COnChainOffer::OnChainOfferForIdentityKey(newIDID);
|
|
|
|
// use the transaction builder to properly make change of native and reserves
|
|
TransactionBuilder tb(Params().consensus, height + 1, pwalletMain);
|
|
|
|
CCommitmentHash ch;
|
|
std::vector<CTxDestination> dests({changeDestination});
|
|
std::vector<CTxDestination> masterKeyDest({forIDKey, offerIDKey});
|
|
if (sourceCurrencyID == ASSETCHAINS_CHAINID)
|
|
{
|
|
CCommitmentHash commitment = CCommitmentHash(uint256());
|
|
tb.AddTransparentOutput(MakeMofNCCScript(
|
|
CConditionObj<CCommitmentHash>(EVAL_IDENTITY_COMMITMENT, dests, 1, &commitment), returnHex ? nullptr : &masterKeyDest),
|
|
oneOutput.nAmount);
|
|
}
|
|
else
|
|
{
|
|
CCommitmentHash commitment = CCommitmentHash(uint256(), CTokenOutput(oneOutput.scriptPubKey.ReserveOutValue()));
|
|
tb.AddTransparentOutput(MakeMofNCCScript(
|
|
CConditionObj<CCommitmentHash>(EVAL_IDENTITY_COMMITMENT, dests, 1, &commitment), returnHex ? nullptr : &masterKeyDest),
|
|
returnHex ? oneOutput.nAmount : oneOutput.nAmount + feeAmount);
|
|
}
|
|
|
|
// aggregate all inputs into one output with only the offer coins and offer indexes
|
|
if (saplingNotes.size())
|
|
{
|
|
std::vector<SaplingOutPoint> notes;
|
|
for (auto &oneNoteInfo : saplingNotes)
|
|
{
|
|
notes.push_back(oneNoteInfo.op);
|
|
}
|
|
// Fetch Sapling anchor and witnesses
|
|
uint256 anchor;
|
|
std::vector<boost::optional<SaplingWitness>> witnesses;
|
|
{
|
|
LOCK2(cs_main, pwalletMain->cs_wallet);
|
|
pwalletMain->GetSaplingNoteWitnesses(notes, witnesses, anchor);
|
|
}
|
|
|
|
// Add Sapling spends
|
|
for (size_t i = 0; i < saplingNotes.size(); i++)
|
|
{
|
|
tb.AddSaplingSpend(expsk, saplingNotes[i].note, anchor, witnesses[i].get());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (auto &oneInput : setCoinsRet)
|
|
{
|
|
tb.AddTransparentInput(COutPoint(oneInput.first->GetHash(), oneInput.second),
|
|
oneInput.first->vout[oneInput.second].scriptPubKey,
|
|
oneInput.first->vout[oneInput.second].nValue);
|
|
}
|
|
}
|
|
tb.SendChangeTo(changeDestination);
|
|
tb.SetFee(feeAmount);
|
|
TransactionBuilderResult preResult = tb.Build();
|
|
preTx = preResult.GetTxOrThrow();
|
|
|
|
LOCK2(smartTransactionCS, mempool.cs);
|
|
|
|
bool relayTx;
|
|
CValidationState state;
|
|
{
|
|
LOCK2(smartTransactionCS, mempool.cs);
|
|
relayTx = myAddtomempool(preTx, &state);
|
|
}
|
|
|
|
// add to mem pool and relay
|
|
if (!relayTx)
|
|
{
|
|
throw JSONRPCError(RPC_TRANSACTION_REJECTED, "Unable to prepare offer tx: " + state.GetRejectReason());
|
|
}
|
|
else
|
|
{
|
|
RelayTransaction(preTx);
|
|
}
|
|
|
|
offerIn = CInputDescriptor(preTx.vout[0].scriptPubKey, preTx.vout[0].nValue, CTxIn(preTx.GetHash(), 0));
|
|
offerTx.vin.push_back(offerIn.txIn);
|
|
|
|
if (!returnHex)
|
|
{
|
|
postedOfferIns.push_back(CInputDescriptor(preTx.vout[1].scriptPubKey, preTx.vout[1].nValue, CTxIn(preTx.GetHash(), 1)));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CTxDestination idDest = DecodeDestination(uni_get_str(find_value(offerValue, "identity")));
|
|
if (idDest.which() != COptCCParams::ADDRTYPE_ID)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Identity parameter must be valid friendly name or i-address: \"" + uni_get_str(params[0]) + "\"");
|
|
}
|
|
CIdentityID idID = CIdentityID(GetDestinationID(idDest));
|
|
// we must have the offer value ID in our wallet to offer it
|
|
std::pair<CIdentityMapKey, CIdentityMapValue> keyAndIdentity;
|
|
if (!pwalletMain->GetIdentity(idID, keyAndIdentity) && keyAndIdentity.first.CanSign())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "To offer an identity, this wallet must have signing authority over the identity offered");
|
|
}
|
|
|
|
offerID = idID;
|
|
|
|
uint32_t idHeight;
|
|
CTxIn idTxIn;
|
|
CIdentity sourceIdentity = CIdentity::LookupIdentity(idID, 0, &idHeight, &idTxIn);
|
|
if (!sourceIdentity.IsValid() && !idTxIn.prevout.hash.IsNull())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot find identity to offer");
|
|
}
|
|
|
|
CTransaction idTx;
|
|
uint256 blockHash;
|
|
if (!myGetTransaction(idTxIn.prevout.hash, idTx, blockHash))
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot find identity to offer");
|
|
}
|
|
|
|
if (returnHex)
|
|
{
|
|
offerIn.txIn = idTxIn;
|
|
offerIn.nValue = idTx.vout[idTxIn.prevout.n].nValue;
|
|
offerIn.scriptPubKey = idTx.vout[idTxIn.prevout.n].scriptPubKey;
|
|
offerTx.vin.push_back(offerIn.txIn);
|
|
}
|
|
else
|
|
{
|
|
bool success;
|
|
std::set<std::pair<const CWalletTx *, unsigned int>> setCoinsRet;
|
|
CCurrencyValueMap reserveValueOut;
|
|
CAmount nativeValueOut;
|
|
CAmount totalOriginationFees = feeAmount * 2;
|
|
|
|
success = find_utxos(from_taddress, vCoins) &&
|
|
pwalletMain->SelectCoinsMinConf(totalOriginationFees, 0, 0, vCoins, setCoinsRet, nativeValueOut);
|
|
|
|
// first make an input transaction to split the offer funds into an exact input and change, if needed
|
|
if (!success)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Insufficient funds for posting offer for identity on chain");
|
|
}
|
|
|
|
// we need one output to create the proper index entry
|
|
CKeyID offerIDKey = COnChainOffer::OnChainIdentityOfferKey(offerID);
|
|
CKeyID forIDKey = newIDID.IsNull() ?
|
|
COnChainOffer::OnChainOfferForCurrencyKey(newCurrencyID) :
|
|
COnChainOffer::OnChainOfferForIdentityKey(newIDID);
|
|
|
|
// use the transaction builder to properly make change of native and reserves
|
|
TransactionBuilder tb(Params().consensus, height + 1, pwalletMain);
|
|
|
|
CCommitmentHash ch;
|
|
std::vector<CTxDestination> dests({changeDestination});
|
|
std::vector<CTxDestination> indexDests({forIDKey, offerIDKey});
|
|
|
|
tb.AddTransparentInput(COutPoint(idTxIn.prevout.hash, idTxIn.prevout.n), idTx.vout[idTxIn.prevout.n].scriptPubKey, idTx.vout[idTxIn.prevout.n].nValue);
|
|
sourceIdentity.UpgradeVersion(height + 1);
|
|
tb.AddTransparentOutput(sourceIdentity.IdentityUpdateOutputScript(height + 1, &indexDests), feeAmount);
|
|
|
|
// aggregate all inputs into one output with only the offer coins and offer indexes
|
|
for (auto &oneInput : setCoinsRet)
|
|
{
|
|
tb.AddTransparentInput(COutPoint(oneInput.first->GetHash(), oneInput.second),
|
|
oneInput.first->vout[oneInput.second].scriptPubKey,
|
|
oneInput.first->vout[oneInput.second].nValue);
|
|
}
|
|
tb.SendChangeTo(changeDestination);
|
|
tb.SetFee(feeAmount);
|
|
TransactionBuilderResult preResult = tb.Build();
|
|
preTx = preResult.GetTxOrThrow();
|
|
|
|
bool relayTx;
|
|
CValidationState state;
|
|
{
|
|
LOCK2(smartTransactionCS, mempool.cs);
|
|
relayTx = myAddtomempool(preTx, &state);
|
|
}
|
|
|
|
if (!relayTx)
|
|
{
|
|
throw JSONRPCError(RPC_TRANSACTION_REJECTED, "Unable to prepare offer tx for identity: " + state.GetRejectReason());
|
|
}
|
|
else
|
|
{
|
|
RelayTransaction(preTx);
|
|
}
|
|
|
|
offerIn = CInputDescriptor(preTx.vout[0].scriptPubKey, preTx.vout[0].nValue, CTxIn(preTx.GetHash(), 0));
|
|
offerTx.vin.push_back(offerIn.txIn);
|
|
|
|
postedOfferIns.push_back(CInputDescriptor(preTx.vout[1].scriptPubKey, preTx.vout[1].nValue, CTxIn(preTx.GetHash(), 1)));
|
|
}
|
|
}
|
|
|
|
CRecipient requestOutput;
|
|
|
|
// now we have made and added the offer input, make the output of what we want to exchange directed to us
|
|
// then, sign the transaction, put it in an opreturn, and make the transaction that contains it
|
|
if (find_value(forValue, "name").isNull())
|
|
{
|
|
auto currencyStr = TrimSpaces(uni_get_str(find_value(forValue, "currency")));
|
|
CAmount destinationAmount = AmountFromValue(find_value(forValue, "amount"));
|
|
auto memoStr = TrimSpaces(uni_get_str(find_value(forValue, "memo")));
|
|
|
|
if (hasZDest && newCurrencyID != ASSETCHAINS_CHAINID)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot send non-native currency when sending proceeds to a private z-address");
|
|
}
|
|
if (!hasZDest && !memoStr.empty())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot include memo when sending proceeds to a transparent address or ID");
|
|
}
|
|
if (hasZDest)
|
|
{
|
|
requestOutput.nAmount = DEFAULT_TRANSACTION_FEE;
|
|
requestOutput.scriptPubKey = GetScriptForDestination(changeDestination);
|
|
|
|
// 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, "Expected memo data in hexadecimal format or as a non-zero length text string, starting with \"#\".");
|
|
}
|
|
|
|
std::array<unsigned char, ZC_MEMO_SIZE> hexMemo;
|
|
|
|
if (memoStr.length() > ZC_MEMO_SIZE*2) {
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Size of memo is larger than maximum allowed %d", ZC_MEMO_SIZE));
|
|
}
|
|
else if (memoStr.length() > 0)
|
|
{
|
|
hexMemo = AsyncRPCOperation_sendmany::get_memo_from_hex_string(memoStr);
|
|
}
|
|
|
|
// make the z-output
|
|
uint256 ovk;
|
|
HDSeed seed;
|
|
if (!pwalletMain->GetHDSeed(seed)) {
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "wallet seed unavailable for z-address output");
|
|
}
|
|
ovk = ovkForShieldingFromTaddr(seed);
|
|
|
|
saplingOutputCtx = librustzcash_sapling_proving_ctx_init();
|
|
auto note = libzcash::SaplingNote(*saplingAddress, destinationAmount);
|
|
OutputDescriptionInfo output(ovk, note, hexMemo);
|
|
offerTx.valueBalance -= destinationAmount;
|
|
|
|
auto cm = output.note.cm();
|
|
if (!cm) {
|
|
librustzcash_sapling_proving_ctx_free(saplingOutputCtx);
|
|
throw JSONRPCError(RPC_TRANSACTION_REJECTED, "failed attempt to create private output");
|
|
}
|
|
|
|
libzcash::SaplingNotePlaintext notePlaintext(output.note, output.memo);
|
|
|
|
auto res = notePlaintext.encrypt(output.note.pk_d);
|
|
if (!res) {
|
|
librustzcash_sapling_proving_ctx_free(saplingOutputCtx);
|
|
throw JSONRPCError(RPC_TRANSACTION_REJECTED, "failed to encrypt note with memo");
|
|
}
|
|
auto enc = res.get();
|
|
auto encryptor = enc.second;
|
|
|
|
OutputDescription odesc;
|
|
if (!librustzcash_sapling_output_proof(
|
|
saplingOutputCtx,
|
|
encryptor.get_esk().begin(),
|
|
output.note.d.data(),
|
|
output.note.pk_d.begin(),
|
|
output.note.r.begin(),
|
|
output.note.value(),
|
|
odesc.cv.begin(),
|
|
odesc.zkproof.begin())) {
|
|
librustzcash_sapling_proving_ctx_free(saplingOutputCtx);
|
|
throw JSONRPCError(RPC_TRANSACTION_REJECTED, "output proof failed");
|
|
}
|
|
|
|
odesc.cm = *cm;
|
|
odesc.ephemeralKey = encryptor.get_epk();
|
|
odesc.encCiphertext = enc.first;
|
|
|
|
libzcash::SaplingOutgoingPlaintext outPlaintext(output.note.pk_d, encryptor.get_esk());
|
|
odesc.outCiphertext = outPlaintext.encrypt(
|
|
output.ovk,
|
|
odesc.cv,
|
|
odesc.cm,
|
|
encryptor);
|
|
offerTx.vShieldedOutput.push_back(odesc);
|
|
}
|
|
else
|
|
{
|
|
// make transparent output and complete transaction
|
|
if (newCurrencyID == ASSETCHAINS_CHAINID)
|
|
{
|
|
requestOutput.nAmount = destinationAmount;
|
|
requestOutput.scriptPubKey = GetScriptForDestination(fundsDestination);
|
|
}
|
|
else
|
|
{
|
|
requestOutput.nAmount = newCurrencyID == ASSETCHAINS_CHAINID ? destinationAmount : 0;
|
|
|
|
std::vector<CTxDestination> dests = std::vector<CTxDestination>({fundsDestination});
|
|
CTokenOutput to(newCurrencyID, destinationAmount);
|
|
|
|
requestOutput.scriptPubKey = MakeMofNCCScript(CConditionObj<CTokenOutput>(EVAL_RESERVE_OUTPUT, dests, 1, &to));
|
|
}
|
|
}
|
|
offerTx.vout.push_back(CTxOut(requestOutput.nAmount, requestOutput.scriptPubKey));
|
|
}
|
|
else
|
|
{
|
|
// create the desired ID output, which the asset this exchange is making an offer for
|
|
// to take the offer, a party in control of the identity defined by the output
|
|
// must provide them on input to turn this into a valid transaction
|
|
|
|
uint160 parentID = uint160(GetDestinationID(DecodeDestination(uni_get_str(find_value(forValue, "parent")))));
|
|
|
|
if (parentID.IsNull() && (parentID = ValidateCurrencyName(uni_get_str(find_value(forValue, "parent")), true)).IsNull())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "To ensure reference to the correct identity, parent must be a correct, non-null value.");
|
|
}
|
|
std::string nameStr = CleanName(uni_get_str(find_value(forValue, "name")), parentID);
|
|
newIDID = CIdentity::GetID(nameStr, parentID);
|
|
if (newIDID.IsNull())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "identity, " + nameStr + " specification is not valid -- " + (nameStr.empty() ? "must have valid name" : "maybe needs parent?"));
|
|
}
|
|
|
|
CTxIn idTxIn;
|
|
CIdentity oldID;
|
|
uint32_t idHeight;
|
|
|
|
if (!(oldID = CIdentity::LookupIdentity(newIDID, 0, &idHeight, &idTxIn)).IsValid())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "identity, " + nameStr + " (" +EncodeDestination(CIdentityID(newIDID)) + "), not found ");
|
|
}
|
|
|
|
oldID.revocationAuthority = oldID.GetID();
|
|
oldID.recoveryAuthority = oldID.GetID();
|
|
oldID.privateAddresses.clear();
|
|
oldID.primaryAddresses.clear();
|
|
oldID.minSigs = 1;
|
|
|
|
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(forValue))
|
|
{
|
|
uniOldID[oneEl.first] = oneEl.second;
|
|
}
|
|
|
|
uint32_t solVersion = CConstVerusSolutionVector::GetVersionByHeight(height + 1);
|
|
|
|
if (solVersion >= CActivationHeight::ACTIVATE_VERUSVAULT)
|
|
{
|
|
uniOldID["version"] = solVersion < CActivationHeight::ACTIVATE_PBAAS ? (int64_t)CIdentity::VERSION_VAULT : (int64_t)CIdentity::VERSION_PBAAS;
|
|
if (oldID.nVersion < CIdentity::VERSION_VAULT)
|
|
{
|
|
uniOldID["systemid"] = EncodeDestination(CIdentityID(parentID.IsNull() ? oldID.GetID() : parentID));
|
|
}
|
|
}
|
|
|
|
UniValue newUniID = MapToUniObject(uniOldID);
|
|
CIdentity newID(newUniID);
|
|
|
|
if (!newID.IsValid(true))
|
|
{
|
|
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 specified");
|
|
}
|
|
|
|
if (!recoveryAuth.IsValidUnrevoked() || !revocationAuth.IsValidUnrevoked())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid or revoked recovery, or revocation identity.");
|
|
}
|
|
|
|
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(height + 1, offerTx.nExpiryHeight);
|
|
}
|
|
else
|
|
{
|
|
newID.Lock(unlockAfter);
|
|
}
|
|
}
|
|
|
|
newID.UpgradeVersion(height + 1);
|
|
offerTx.vout.push_back(CTxOut(0, newID.IdentityUpdateOutputScript(height + 1)));
|
|
}
|
|
|
|
// now, the offer tx is complete, and we need to sign its input with SIGHASH_SINGLE
|
|
auto consensusBranchId = CurrentEpochBranchId(height, Params().consensus);
|
|
|
|
if (offerTx.vShieldedOutput.size())
|
|
{
|
|
// has for SIGHASH_SINGLE | SIGHASH_ANYONECANPAY binding signature for
|
|
// Sapling outputs. Final transaction must only include Sapling outputs, which
|
|
// have a binding signature bound to the single input along with its output and a zero
|
|
// amount. the zero amount allows us to generate and validate this hash without
|
|
// the output script of the prior transaction output, which is bound and verified by the
|
|
// hash signed on that input already
|
|
uint256 dataToBeSigned;
|
|
CScript scriptCode;
|
|
try {
|
|
dataToBeSigned = SignatureHash(scriptCode, offerTx, 0, SIGHASH_SINGLE | SIGHASH_ANYONECANPAY, 0, consensusBranchId);
|
|
} catch (std::logic_error ex) {
|
|
librustzcash_sapling_proving_ctx_free(saplingOutputCtx);
|
|
throw JSONRPCError(RPC_TRANSACTION_ERROR, "Could not construct signature hash");
|
|
}
|
|
|
|
librustzcash_sapling_binding_sig(
|
|
saplingOutputCtx,
|
|
offerTx.valueBalance,
|
|
dataToBeSigned.begin(),
|
|
offerTx.bindingSig.data());
|
|
|
|
librustzcash_sapling_proving_ctx_free(saplingOutputCtx);
|
|
}
|
|
|
|
CTransaction txNewConst(offerTx);
|
|
SignatureData sigdata;
|
|
|
|
bool signSuccess = ProduceSignature(
|
|
TransactionSignatureCreator(pwalletMain, &txNewConst, 0, offerIn.nValue, offerIn.scriptPubKey, height + 1, SIGHASH_SINGLE | SIGHASH_ANYONECANPAY), offerIn.scriptPubKey, sigdata, consensusBranchId);
|
|
|
|
if (signSuccess || sigdata.scriptSig.size())
|
|
{
|
|
UpdateTransaction(offerTx, 0, sigdata);
|
|
}
|
|
else
|
|
{
|
|
UniValue jsonTx(UniValue::VOBJ);
|
|
extern void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry);
|
|
TxToUniv(txNewConst, uint256(), jsonTx);
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Failed to sign for script:\n " + jsonTx.write(1,2) + "\n");
|
|
}
|
|
|
|
UniValue retVal(UniValue::VOBJ);
|
|
|
|
// if we're not just returning the hex tx, create a transaction from the postedOfferIns funds
|
|
if (!signSuccess || returnHex)
|
|
{
|
|
if (signSuccess)
|
|
{
|
|
retVal.pushKV("signstatus", "complete");
|
|
}
|
|
else
|
|
{
|
|
retVal.pushKV("signstatus", "incomplete");
|
|
}
|
|
retVal.pushKV("hex", EncodeHexTx(offerTx));
|
|
if (!returnHex)
|
|
{
|
|
retVal.pushKV("listingtransactionid", preTx.GetHash().GetHex());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!postedOfferIns.size())
|
|
{
|
|
throw JSONRPCError(RPC_TRANSACTION_ERROR, "Unable to make offer transaction on chain, try with returnhex as false");
|
|
}
|
|
TransactionBuilder tb(Params().consensus, height + 1, pwalletMain);
|
|
for (auto &oneIn : postedOfferIns)
|
|
{
|
|
tb.AddTransparentInput(COutPoint(oneIn.txIn.prevout.hash, oneIn.txIn.prevout.n), oneIn.scriptPubKey, oneIn.nValue);
|
|
}
|
|
|
|
// now, make the opret to contain this transaction
|
|
CCrossChainProof opRetProof;
|
|
opRetProof << CPartialTransactionProof(CMMRProof(), offerTx);
|
|
tb.AddOpRet(StoreOpRetArray(opRetProof.chainObjects));
|
|
tb.SendChangeTo(changeDestination);
|
|
tb.SetFee(feeAmount);
|
|
|
|
TransactionBuilderResult result = tb.Build();
|
|
CTransaction offerPostTx = result.GetTxOrThrow();
|
|
|
|
bool relayTx;
|
|
CValidationState state;
|
|
{
|
|
LOCK2(smartTransactionCS, mempool.cs);
|
|
relayTx = myAddtomempool(offerPostTx, &state);
|
|
}
|
|
|
|
if (!relayTx)
|
|
{
|
|
throw JSONRPCError(RPC_TRANSACTION_REJECTED, "Failed to add offer transaction to mempool");
|
|
}
|
|
else
|
|
{
|
|
retVal.pushKV("txid", preTx.GetHash().GetHex());
|
|
retVal.pushKV("oprettxid", offerPostTx.GetHash().GetHex());
|
|
RelayTransaction(offerPostTx);
|
|
}
|
|
}
|
|
|
|
return retVal;
|
|
}
|
|
catch(const std::exception& e)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot create offer: " + std::string(e.what()));
|
|
}
|
|
return NullUniValue;
|
|
}
|
|
|
|
UniValue takeoffer(const UniValue& params, bool fHelp)
|
|
{
|
|
if (fHelp || params.size() < 2 || params.size() > 4)
|
|
{
|
|
throw runtime_error(
|
|
"takeoffer fromaddress '{\"txid\":\"txid\" | \"tx\":\"hextx\", \"changeaddress\":\"transparentoriaddress\", \"deliver\":\"fullidnameoriaddresstodeliver\" | {\"currency\":\"currencynameorid\",\"amount\":n}, \"accept\":{\"address\":\"addressorid\",\"currency\":\"currencynameorid\",\"amount\":n} | {identitydefinition}}' (returntx) (feeamount)\n"
|
|
"\nIf the current wallet can afford the swap, this accepts a swap offer on the blockchain, creates a transaction\n"
|
|
"to execute it, and posts the transaction to the blockchain.\n"
|
|
|
|
"\nArguments\n"
|
|
"\"fromaddress\" (string, required) The Sapling, VerusID, or wildcard address to send funds from, including fees for ID swaps.\n"
|
|
" \"*\", \"R*\", or \"i*\" are valid wildcards\n"
|
|
"{\n"
|
|
"\"txid\" (string, required) The transaction ID for the offer to accept\n"
|
|
"\"tx\" (string, required) The hex transaction to complete in order to accept the offer\n"
|
|
"\"deliver\" (object, required) One of \"fullidnameoriaddresstotrade\" or {\"currency\":\"currencynameorid\", \"amount\":value}\n"
|
|
"\"accept\" (object, required) One of {\"address\":\"addressorid\",\"currency\":\"currencynameorid\",\"amount\"} or {identitydefinition}\n"
|
|
"\"feeamount\" (number, optional) Specific fee amount requested instead of default miner's fee\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("takeoffer", "fromaddress '{\"txid\":\"txid\" | \"tx\":\"hextx\", \"deliver\":\"fullidnameoriaddresstodeliver\" | {\"currency\":\"currencynameorid\",\"amount\":...}, \"accept\":{\"address\":\"addressorid\",\"currency\":\"currencynameorid\",\"amount\"} | {identitydefinition}}' (returntx) (feeamount)")
|
|
+ HelpExampleRpc("takeoffer", "fromaddress {\"txid\":\"txid\" | \"tx\":\"hextx\", \"deliver\":\"fullidnameoriaddresstodeliver\" | {\"currency\":\"currencynameorid\",\"amount\":...}, \"accept\":{\"address\":\"addressorid\",\"currency\":\"currencynameorid\",\"amount\"} | {identitydefinition}} (returntx) (feeamount)")
|
|
);
|
|
}
|
|
|
|
CheckVerusVaultAPIsValid();
|
|
|
|
bool returnHex = params.size() > 2 ? uni_get_bool(params[2]) : false;
|
|
CAmount feeAmount = params.size() > 3 ? AmountFromValue(params[3]) : DEFAULT_TRANSACTION_FEE;
|
|
|
|
std::string fundsSource = uni_get_str(params[0]);
|
|
if (fundsSource.empty())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "First parameter json object must include a currency funding \"source\", which may be an ID, transparent address, private address, or wildcards (*, R*, i*)");
|
|
}
|
|
|
|
CTxDestination sourceDest, changeAddress;
|
|
|
|
bool wildCardTransparentAddress = fundsSource == "*";
|
|
bool wildCardRAddress = fundsSource == "R*";
|
|
bool wildCardiAddress = fundsSource == "i*";
|
|
bool wildCardAddress = wildCardTransparentAddress || wildCardRAddress || wildCardiAddress;
|
|
|
|
std::vector<CRecipient> outputs;
|
|
|
|
libzcash::PaymentAddress zaddressSource;
|
|
libzcash::SaplingExpandedSpendingKey expsk;
|
|
std::vector<SpendDescriptionInfo> saplingSpends;
|
|
void *saplingSpendCtx = nullptr;
|
|
|
|
uint256 sourceOvk;
|
|
bool hasZSource = !wildCardAddress && pwalletMain->GetAndValidateSaplingZAddress(fundsSource, zaddressSource);
|
|
// 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)
|
|
{
|
|
// TODO: enable z-source for funds
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "z-address for payment, not yet implemented");
|
|
fundsSource = EncodePaymentAddress(zaddressSource);
|
|
// We don't need to lock on the wallet as spending key related methods are thread-safe
|
|
if (!boost::apply_visitor(HaveSpendingKeyForPaymentAddress(pwalletMain), zaddressSource)) {
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid from address, no spending key found for zaddr");
|
|
}
|
|
|
|
auto spendingkey_ = boost::apply_visitor(GetSpendingKeyForPaymentAddress(pwalletMain), zaddressSource).get();
|
|
auto sk = boost::get<libzcash::SaplingExtendedSpendingKey>(spendingkey_);
|
|
expsk = sk.expsk;
|
|
sourceOvk = expsk.full_viewing_key().ovk;
|
|
}
|
|
|
|
if (!(hasZSource ||
|
|
wildCardAddress ||
|
|
(sourceDest = DecodeDestination(fundsSource)).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.");
|
|
}
|
|
|
|
CTxDestination from_taddress;
|
|
if (wildCardTransparentAddress)
|
|
{
|
|
from_taddress = CTxDestination();
|
|
}
|
|
else if (wildCardRAddress)
|
|
{
|
|
from_taddress = CTxDestination(CKeyID(uint160()));
|
|
}
|
|
else if (wildCardiAddress)
|
|
{
|
|
from_taddress = CTxDestination(CIdentityID(uint160()));
|
|
}
|
|
else
|
|
{
|
|
from_taddress = sourceDest;
|
|
}
|
|
|
|
// now, we either have a z-address source, wild card, or single transparent source
|
|
const UniValue &takeOfferUni = params[1];
|
|
std::string txIdStringToTake = uni_get_str(find_value(takeOfferUni, "txid"));
|
|
std::string txStringToTake = uni_get_str(find_value(takeOfferUni, "tx"));
|
|
std::string changeAddressStr = uni_get_str(find_value(takeOfferUni, "changeaddress"));
|
|
|
|
{
|
|
LOCK(cs_main);
|
|
if ((changeAddress = ValidateDestination(changeAddressStr)).which() == COptCCParams::ADDRTYPE_INVALID)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "\"changeaddress\" must be specified as a transparent address or identity");
|
|
}
|
|
}
|
|
|
|
uint256 txIdToTake;
|
|
CTransaction txToTake, inputTxToOffer;
|
|
uint32_t height;
|
|
uint32_t consensusBranchId;
|
|
|
|
{
|
|
LOCK2(cs_main, mempool.cs);
|
|
height = chainActive.Height();
|
|
consensusBranchId = CurrentEpochBranchId(height + 1, Params().GetConsensus());
|
|
|
|
if (!txIdStringToTake.empty())
|
|
{
|
|
txIdToTake.SetHex(txIdStringToTake);
|
|
if (txIdToTake.IsNull())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid txid specified");
|
|
}
|
|
uint256 blockHash;
|
|
CTransaction postedTx;
|
|
if (!myGetTransaction(txIdToTake, postedTx, blockHash))
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Transaction " + txIdToTake.GetHex() + " not found");
|
|
}
|
|
|
|
// get the actual transaction from the op return
|
|
if (!GetOpRetChainOffer(postedTx, txToTake, inputTxToOffer, height))
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Unable to retrieve valid offer");
|
|
}
|
|
}
|
|
else if (!txStringToTake.empty())
|
|
{
|
|
uint256 blockHash;
|
|
if (!(DecodeHexTx(txToTake, txStringToTake) &&
|
|
txToTake.vout.size() == 1 &&
|
|
txToTake.vin.size() == 1 &&
|
|
txToTake.vShieldedSpend.size() == 0 &&
|
|
txToTake.nExpiryHeight > (height + 3) &&
|
|
myGetTransaction(txToTake.vin[0].prevout.hash, inputTxToOffer, blockHash)))
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Offer transaction supplied is not valid or expired");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Must either provide a transaction in \"tx\" or a transaction id in \"txid\" to specify the offer");
|
|
}
|
|
}
|
|
|
|
UniValue deliver = find_value(takeOfferUni, "deliver");
|
|
UniValue accept = find_value(takeOfferUni, "accept");
|
|
|
|
CIdentityID idIDToDeliver;
|
|
std::pair<CIdentityMapKey, CIdentityMapValue> keyAndIdentity;
|
|
CTxIn idInput;
|
|
CTxOut oldIdOutput;
|
|
CTxDestination acceptToAddress;
|
|
|
|
std::pair<CIdentityMapKey, CIdentityMapValue> keyAndRevocation;
|
|
std::pair<CIdentityMapKey, CIdentityMapValue> keyAndRecovery;
|
|
|
|
CIdentity acceptedIdentity;
|
|
CCurrencyValueMap acceptedCurrency, currencyToDeliver;
|
|
CAmount subsidizedFees = 0;
|
|
|
|
if (accept.isNull() || !accept.isObject())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Identity or currency to accept must be a valid json object");
|
|
}
|
|
|
|
if ((acceptedIdentity = CIdentity(accept)).IsValid())
|
|
{
|
|
uint160 parentID = uint160(GetDestinationID(DecodeDestination(uni_get_str(find_value(accept, "parent")))));
|
|
if (parentID.IsNull() && (parentID = ValidateCurrencyName(uni_get_str(find_value(accept, "parent")), true)).IsNull())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "To ensure acceptance of the correct identity, parent must be a correct, non-null value.");
|
|
}
|
|
|
|
CTxIn idTxIn;
|
|
CIdentity oldID;
|
|
uint32_t idHeight;
|
|
|
|
if (!(oldID = CIdentity::LookupIdentity(acceptedIdentity.GetID(), 0, &idHeight, &idTxIn)).IsValid())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "identity, " + acceptedIdentity.GetID().GetHex() + ", not found ");
|
|
}
|
|
|
|
oldID.revocationAuthority = oldID.GetID();
|
|
oldID.recoveryAuthority = oldID.GetID();
|
|
oldID.privateAddresses.clear();
|
|
oldID.primaryAddresses.clear();
|
|
oldID.minSigs = 1;
|
|
|
|
auto uniOldID = UniObjectToMap(oldID.ToUniValue());
|
|
|
|
// overwrite old elements
|
|
for (auto &oneEl : UniObjectToMap(accept))
|
|
{
|
|
uniOldID[oneEl.first] = oneEl.second;
|
|
}
|
|
|
|
uint32_t solVersion = CConstVerusSolutionVector::GetVersionByHeight(height + 1);
|
|
|
|
if (solVersion >= CActivationHeight::ACTIVATE_VERUSVAULT)
|
|
{
|
|
uniOldID["version"] = solVersion < CActivationHeight::ACTIVATE_PBAAS ? (int64_t)CIdentity::VERSION_VAULT : (int64_t)CIdentity::VERSION_PBAAS;
|
|
if (oldID.nVersion < CIdentity::VERSION_VAULT)
|
|
{
|
|
uniOldID["systemid"] = EncodeDestination(CIdentityID(parentID.IsNull() ? oldID.GetID() : parentID));
|
|
}
|
|
}
|
|
|
|
UniValue newUniID = MapToUniObject(uniOldID);
|
|
acceptedIdentity = CIdentity(newUniID);
|
|
acceptedIdentity.UpgradeVersion(height + 1);
|
|
}
|
|
else
|
|
{
|
|
std::string acceptCurrencyStr = uni_get_str(find_value(accept, "currency"));
|
|
std::string acceptToDestStr = uni_get_str(find_value(accept, "address"));
|
|
uint160 currencyID = ValidateCurrencyName(acceptCurrencyStr, true);
|
|
if (acceptCurrencyStr.empty())
|
|
{
|
|
currencyID = ASSETCHAINS_CHAINID;
|
|
}
|
|
CAmount currencyAmount;
|
|
if (currencyID.IsNull() || (currencyAmount = AmountFromValue(find_value(accept, "amount"))) <= 0)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "One of identity or currency to accept must be valid");
|
|
}
|
|
acceptedCurrency.valueMap[currencyID] = currencyAmount;
|
|
|
|
LOCK(cs_main);
|
|
if ((acceptToAddress = ValidateDestination(acceptToDestStr)).which() == COptCCParams::ADDRTYPE_INVALID)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameters. To accept an offer of currency, the accept address must be a transparent address or identity. See help.");
|
|
}
|
|
}
|
|
|
|
// for use later
|
|
CCoinsView viewDummy;
|
|
CCoinsViewCache view(&viewDummy);
|
|
CMutableTransaction mtx(txToTake);
|
|
|
|
int firstFundingInput = 0;
|
|
{
|
|
LOCK2(cs_main, pwalletMain->cs_wallet);
|
|
LOCK(mempool.cs);
|
|
|
|
if (deliver.isStr())
|
|
{
|
|
CIdentity revocationIdentity, recoveryIdentity;
|
|
|
|
// this needs to be a valid ID in our wallet
|
|
CTxDestination identityDest = DecodeDestination(uni_get_str(deliver));
|
|
if (identityDest.which() != COptCCParams::ADDRTYPE_ID)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "If a VerusID is specified to deliver when taking the offer, it must be a valid ID name or i-address on this chain");
|
|
}
|
|
bool idInWallet = pwalletMain->GetIdentity(GetDestinationID(identityDest), keyAndIdentity);
|
|
|
|
if (!idInWallet || !(*(CIdentity *)(&keyAndIdentity.second) = CIdentity::LookupIdentity(GetDestinationID(identityDest), 0, nullptr, &idInput)).IsValid())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "No authority over VerusID or ID not found");
|
|
}
|
|
|
|
uint256 blkHash;
|
|
CTransaction oldIdTx;
|
|
if (!myGetTransaction(idInput.prevout.hash, oldIdTx, blkHash))
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "identity, " + keyAndIdentity.second.name + " (" + EncodeDestination(CIdentityID(keyAndIdentity.second.GetID())) + "), transaction not found ");
|
|
}
|
|
oldIdOutput = oldIdTx.vout[idInput.prevout.n];
|
|
|
|
if (!keyAndIdentity.first.CanSign())
|
|
{
|
|
// we need either signing authority, revocation, recovery, or any combination to be able to create a delivery transaction
|
|
if (keyAndIdentity.second.revocationAuthority == idIDToDeliver && keyAndIdentity.second.recoveryAuthority == idIDToDeliver)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "No authority over VerusID specified for delivery");
|
|
}
|
|
// if recovery is different, get it and see if we have signing authority on that
|
|
if (keyAndIdentity.second.revocationAuthority != idIDToDeliver)
|
|
{
|
|
if (!pwalletMain->GetIdentity(keyAndIdentity.second.revocationAuthority, keyAndRevocation))
|
|
{
|
|
if (!(keyAndRevocation.second = CIdentityMapValue(CIdentity::LookupIdentity(keyAndIdentity.second.revocationAuthority))).IsValid())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Revocation authority not found for ID specified");
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
keyAndRevocation = keyAndIdentity;
|
|
}
|
|
|
|
// if recovery is different, get it and see if we have signing authority on that
|
|
if (keyAndIdentity.second.recoveryAuthority != idIDToDeliver)
|
|
{
|
|
if (keyAndIdentity.second.recoveryAuthority == keyAndIdentity.second.revocationAuthority)
|
|
{
|
|
keyAndRecovery = keyAndRevocation;
|
|
}
|
|
else if (!pwalletMain->GetIdentity(keyAndIdentity.second.recoveryAuthority, keyAndRecovery))
|
|
{
|
|
if (!(keyAndRecovery.second = CIdentityMapValue(CIdentity::LookupIdentity(keyAndIdentity.second.recoveryAuthority))).IsValid())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Recovery authority not found for ID specified");
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
keyAndRecovery = keyAndIdentity;
|
|
}
|
|
}
|
|
// if we can't sign for any authority on the ID, don't make a transaction
|
|
if (!keyAndIdentity.first.CanSign() && !keyAndRevocation.first.CanSign() && !keyAndRecovery.first.CanSign())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "This wallet has no authority to sign for any part of delivering the ID specified");
|
|
}
|
|
currencyToDeliver.valueMap[ASSETCHAINS_CHAINID] = feeAmount;
|
|
}
|
|
else if (deliver.isObject())
|
|
{
|
|
// determine the currency we are offering to deliver
|
|
auto currencyStr = TrimSpaces(uni_get_str(find_value(deliver, "currency")));
|
|
CAmount destinationAmount = AmountFromValue(find_value(deliver, "amount"));
|
|
uint160 curID;
|
|
if (!currencyStr.empty())
|
|
{
|
|
curID = ValidateCurrencyName(currencyStr, true);
|
|
if (curID.IsNull())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Currency specified for delivery not found");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
curID = ASSETCHAINS_CHAINID;
|
|
}
|
|
|
|
currencyToDeliver.valueMap[curID] = destinationAmount;
|
|
currencyToDeliver.valueMap[ASSETCHAINS_CHAINID] += feeAmount;
|
|
}
|
|
|
|
// now, ensure that our expected output and the input provided in the offer are the same
|
|
COptCCParams p;
|
|
CIdentity offeredIdentity;
|
|
CCurrencyValueMap offeredCurrency;
|
|
if (inputTxToOffer.vout[txToTake.vin[0].prevout.n].scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
p.evalCode == EVAL_IDENTITY_PRIMARY &&
|
|
p.vData.size() &&
|
|
(offeredIdentity = CIdentity(p.vData[0])).IsValid())
|
|
{
|
|
if (offeredIdentity.GetID() != acceptedIdentity.GetID())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Identity offered: " + EncodeDestination(CIdentityID(offeredIdentity.GetID())) + ", is not the same as accepted: " + EncodeDestination(CIdentityID(acceptedIdentity.GetID())));
|
|
}
|
|
}
|
|
else if (inputTxToOffer.vout[txToTake.vin[0].prevout.n].scriptPubKey.IsSpendableOutputType())
|
|
{
|
|
if (inputTxToOffer.vout[txToTake.vin[0].prevout.n].nValue > 0)
|
|
{
|
|
offeredCurrency.valueMap[ASSETCHAINS_CHAINID] = inputTxToOffer.vout[txToTake.vin[0].prevout.n].nValue;
|
|
}
|
|
offeredCurrency += inputTxToOffer.vout[txToTake.vin[0].prevout.n].scriptPubKey.ReserveOutValue();
|
|
if (offeredCurrency < acceptedCurrency)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Currency offered: " + offeredCurrency.ToUniValue().write() + ", is less than accepted: " + acceptedCurrency.ToUniValue().write());
|
|
}
|
|
}
|
|
|
|
CAmount additionalFees = feeAmount;
|
|
|
|
// add the output
|
|
if (acceptedIdentity.IsValid())
|
|
{
|
|
mtx.vout.push_back(CTxOut(0, acceptedIdentity.IdentityUpdateOutputScript(height + 1)));
|
|
}
|
|
else if ((acceptedCurrency = acceptedCurrency.CanonicalMap()).valueMap.size())
|
|
{
|
|
// if our accepted currency is native, no reserve output
|
|
CAmount nativeOut = acceptedCurrency.valueMap.count(ASSETCHAINS_CHAINID) ? acceptedCurrency.valueMap[ASSETCHAINS_CHAINID] : 0;
|
|
|
|
if (acceptedCurrency.valueMap.size() == 1 && nativeOut)
|
|
{
|
|
mtx.vout.push_back(CTxOut(nativeOut, GetScriptForDestination(acceptToAddress)));
|
|
}
|
|
else
|
|
{
|
|
acceptedCurrency.valueMap.erase(ASSETCHAINS_CHAINID);
|
|
std::vector<CTxDestination> dest({acceptToAddress});
|
|
CTokenOutput to(acceptedCurrency);
|
|
mtx.vout.push_back(CTxOut(nativeOut, MakeMofNCCScript(CConditionObj<CTokenOutput>(EVAL_RESERVE_OUTPUT, dest, 1, &to))));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Must specify valid identity or currency for exchange");
|
|
}
|
|
|
|
// if the identity has been initialized, we are delivering an identity in this transaction, input our identity to the
|
|
// transaction, and add an output for our return
|
|
std:vector<COutput> vCoins;
|
|
std::set<std::pair<const CWalletTx *, unsigned int>> setCoinsRet;
|
|
CAmount nativeValueOut;
|
|
CCurrencyValueMap reserveValueOut;
|
|
std::vector<SaplingNoteEntry> saplingNotes;
|
|
|
|
firstFundingInput = mtx.vin.size();
|
|
|
|
if (keyAndIdentity.second.IsValid())
|
|
{
|
|
bool success = true;
|
|
mtx.vin.push_back(idInput);
|
|
// one ID input to fund the transaction
|
|
if (additionalFees)
|
|
{
|
|
if (hasZSource)
|
|
{
|
|
saplingNotes = find_unspent_notes(zaddressSource);
|
|
CAmount totalFound = 0;
|
|
int i;
|
|
for (i = 0; i < saplingNotes.size(); i++)
|
|
{
|
|
totalFound += saplingNotes[i].note.value();
|
|
if (totalFound >= additionalFees)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
// remove all but the notes we'll use
|
|
if (i < saplingNotes.size())
|
|
{
|
|
saplingNotes.erase(saplingNotes.begin() + i + 1, saplingNotes.end());
|
|
success = true;
|
|
}
|
|
else
|
|
{
|
|
success = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
success = find_utxos(from_taddress, vCoins) &&
|
|
pwalletMain->SelectCoinsMinConf(additionalFees, 0, 0, vCoins, setCoinsRet, nativeValueOut);
|
|
}
|
|
}
|
|
if (!success)
|
|
{
|
|
throw JSONRPCError(RPC_TRANSACTION_ERROR, "Unable to fund delivery of identity");
|
|
}
|
|
}
|
|
else if (currencyToDeliver.valueMap.size())
|
|
{
|
|
COptCCParams p;
|
|
CCurrencyValueMap currencyRequested;
|
|
if (txToTake.vout[0].scriptPubKey.IsSpendableOutputType(p) || (p.IsValid() && p.evalCode == EVAL_IDENTITY_COMMITMENT))
|
|
{
|
|
if (txToTake.vout[0].nValue - txToTake.valueBalance > 0)
|
|
{
|
|
currencyRequested.valueMap[ASSETCHAINS_CHAINID] = txToTake.vout[0].nValue - txToTake.valueBalance;
|
|
}
|
|
currencyRequested += txToTake.vout[0].scriptPubKey.ReserveOutValue();
|
|
if (currencyToDeliver < currencyRequested)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Currency being delivered: " + currencyToDeliver.ToUniValue().write() + ", is less than requested: " + currencyRequested.ToUniValue().write());
|
|
}
|
|
currencyToDeliver = currencyRequested;
|
|
}
|
|
else
|
|
{
|
|
throw JSONRPCError(RPC_TRANSACTION_ERROR, "Invalid currency request from offer transaction");
|
|
}
|
|
|
|
// find enough currency from source to fund the acceptance
|
|
CAmount nativeValue = (currencyToDeliver.valueMap[ASSETCHAINS_CHAINID] + additionalFees);
|
|
currencyToDeliver.valueMap.erase(ASSETCHAINS_CHAINID);
|
|
|
|
bool success = false;
|
|
if (hasZSource)
|
|
{
|
|
if (currencyToDeliver.CanonicalMap().valueMap.size() != 0)
|
|
{
|
|
throw JSONRPCError(RPC_TRANSACTION_ERROR, "Private address source cannot be used for non-native currency delivery");
|
|
}
|
|
saplingNotes = find_unspent_notes(zaddressSource);
|
|
CAmount totalFound = 0;
|
|
int i;
|
|
for (i = 0; i < saplingNotes.size(); i++)
|
|
{
|
|
totalFound += saplingNotes[i].note.value();
|
|
if (totalFound >= additionalFees)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
// remove all but the notes we'll use
|
|
if (i < saplingNotes.size())
|
|
{
|
|
saplingNotes.erase(saplingNotes.begin() + i + 1, saplingNotes.end());
|
|
success = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
success = find_utxos(from_taddress, vCoins) &&
|
|
pwalletMain->SelectReserveCoinsMinConf(currencyToDeliver,
|
|
nativeValue,
|
|
0,
|
|
1,
|
|
vCoins,
|
|
setCoinsRet,
|
|
reserveValueOut,
|
|
nativeValueOut);
|
|
}
|
|
|
|
if (!success)
|
|
{
|
|
throw JSONRPCError(RPC_TRANSACTION_ERROR, "Unable to fund currency delivery");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "No identity or currency to deliver");
|
|
}
|
|
|
|
if (saplingNotes.size())
|
|
{
|
|
if (txToTake.vShieldedOutput.size())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Paying for an offer, which pays to a z-address with a z-address source is not yet implemented");
|
|
}
|
|
|
|
std::vector<SaplingOutPoint> notes;
|
|
for (size_t i = 0; i < saplingNotes.size(); i++)
|
|
{
|
|
notes.push_back(saplingNotes[i].op);
|
|
}
|
|
|
|
// Fetch Sapling anchor and witnesses
|
|
uint256 anchor;
|
|
std::vector<boost::optional<SaplingWitness>> witnesses;
|
|
{
|
|
LOCK2(cs_main, pwalletMain->cs_wallet);
|
|
pwalletMain->GetSaplingNoteWitnesses(notes, witnesses, anchor);
|
|
}
|
|
|
|
saplingSpendCtx = librustzcash_sapling_proving_ctx_init();
|
|
|
|
// Add Sapling spends
|
|
for (size_t i = 0; i < saplingNotes.size(); i++)
|
|
{
|
|
SpendDescriptionInfo spend(expsk, saplingNotes[i].note, anchor, boost::get(witnesses[i]));
|
|
//tb.AddSaplingSpend(expsk, saplingNotes[i].note, anchor, witnesses[i].get());
|
|
|
|
auto cm = spend.note.cm();
|
|
auto nf = spend.note.nullifier(
|
|
spend.expsk.full_viewing_key(), spend.witness.position());
|
|
if (!cm || !nf) {
|
|
librustzcash_sapling_proving_ctx_free(saplingSpendCtx);
|
|
throw JSONRPCError(RPC_TRANSACTION_REJECTED, "Spend is invalid");
|
|
}
|
|
|
|
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
|
|
ss << spend.witness.path();
|
|
std::vector<unsigned char> witness(ss.begin(), ss.end());
|
|
|
|
SpendDescription sdesc;
|
|
if (!librustzcash_sapling_spend_proof(
|
|
saplingSpendCtx,
|
|
spend.expsk.full_viewing_key().ak.begin(),
|
|
spend.expsk.nsk.begin(),
|
|
spend.note.d.data(),
|
|
spend.note.r.begin(),
|
|
spend.alpha.begin(),
|
|
spend.note.value(),
|
|
spend.anchor.begin(),
|
|
witness.data(),
|
|
sdesc.cv.begin(),
|
|
sdesc.rk.begin(),
|
|
sdesc.zkproof.data())) {
|
|
librustzcash_sapling_proving_ctx_free(saplingSpendCtx);
|
|
throw JSONRPCError(RPC_TRANSACTION_REJECTED, "Spend proof failed");
|
|
}
|
|
|
|
sdesc.anchor = spend.anchor;
|
|
sdesc.nullifier = *nf;
|
|
mtx.vShieldedSpend.push_back(sdesc);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// put all transparent inputs and spends on the transaction, sign and return or post
|
|
for (auto &oneInput : setCoinsRet)
|
|
{
|
|
mtx.vin.push_back(CTxIn(oneInput.first->GetHash(), oneInput.second));
|
|
}
|
|
}
|
|
|
|
// Fetch previous transactions (inputs):
|
|
CCoinsViewCache &viewChain = *pcoinsTip;
|
|
CCoinsViewMemPool viewMempool(&viewChain, mempool);
|
|
view.SetBackend(viewMempool); // temporarily switch cache backend to db+mempool view
|
|
for (auto &txin : mtx.vin)
|
|
{
|
|
const uint256& prevHash = txin.prevout.hash;
|
|
CCoins coins;
|
|
view.AccessCoins(prevHash); // this can fail
|
|
}
|
|
view.SetBackend(viewDummy); // switch back to avoid locking mempool for too long
|
|
}
|
|
|
|
CReserveTransactionDescriptor rtxd(mtx, view, height + 1);
|
|
if (!rtxd.IsValid())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Created invalid transaction");
|
|
}
|
|
CCurrencyValueMap reserveChange = rtxd.ReserveFees();
|
|
CAmount nativeChange = rtxd.NativeFees() - feeAmount;
|
|
if (nativeChange < DEFAULT_TRANSACTION_FEE)
|
|
{
|
|
nativeChange = 0;
|
|
}
|
|
if (reserveChange.valueMap.size())
|
|
{
|
|
std::vector<CTxDestination> dest({changeAddress});
|
|
CTokenOutput to(reserveChange);
|
|
mtx.vout.push_back(CTxOut(nativeChange, MakeMofNCCScript(CConditionObj<CTokenOutput>(EVAL_RESERVE_OUTPUT, dest, 1, &to))));
|
|
}
|
|
else if (nativeChange)
|
|
{
|
|
mtx.vout.push_back(CTxOut(nativeChange, GetScriptForDestination(changeAddress)));
|
|
}
|
|
|
|
CTransaction txConst(mtx);
|
|
UniValue vErrors(UniValue::VARR);
|
|
|
|
// Sign what we can
|
|
|
|
// first Sapling spends, if we have them
|
|
if (saplingSpends.size())
|
|
{
|
|
uint256 dataToBeSigned;
|
|
CScript scriptCode;
|
|
try {
|
|
dataToBeSigned = SignatureHash(scriptCode, mtx, NOT_AN_INPUT, SIGHASH_ALL, 0, consensusBranchId);
|
|
} catch (std::logic_error ex) {
|
|
librustzcash_sapling_proving_ctx_free(saplingSpendCtx);
|
|
throw JSONRPCError(RPC_TRANSACTION_ERROR, "Could not construct signature hash");
|
|
}
|
|
|
|
// Create Sapling spendAuth and binding signatures
|
|
for (size_t i = 0; i < saplingSpends.size(); i++) {
|
|
librustzcash_sapling_spend_sig(
|
|
saplingSpends[i].expsk.ask.begin(),
|
|
saplingSpends[i].alpha.begin(),
|
|
dataToBeSigned.begin(),
|
|
mtx.vShieldedSpend[i].spendAuthSig.data());
|
|
}
|
|
librustzcash_sapling_binding_sig(
|
|
saplingSpendCtx,
|
|
mtx.valueBalance,
|
|
dataToBeSigned.begin(),
|
|
mtx.bindingSig.data());
|
|
|
|
librustzcash_sapling_proving_ctx_free(saplingSpendCtx);
|
|
}
|
|
|
|
for (int i = firstFundingInput; i < mtx.vin.size(); i++)
|
|
{
|
|
CTxIn& txin = mtx.vin[i];
|
|
const CCoins* coins = view.AccessCoins(txin.prevout.hash);
|
|
if (coins == NULL || !coins->IsAvailable(txin.prevout.n)) {
|
|
SigningErrorToJSON(txin, vErrors, "Input not found or already spent");
|
|
continue;
|
|
}
|
|
const CScript& prevPubKey = CCoinsViewCache::GetSpendFor(coins, txin);
|
|
const CAmount& amount = coins->vout[txin.prevout.n].nValue;
|
|
|
|
SignatureData sigdata;
|
|
ProduceSignature(MutableTransactionSignatureCreator(pwalletMain, &mtx, i, amount, prevPubKey), prevPubKey, sigdata, consensusBranchId);
|
|
|
|
TransactionSignatureChecker checker(&txConst, i, amount);
|
|
sigdata = CombineSignatures(prevPubKey, checker, sigdata, DataFromTransaction(txConst, i), consensusBranchId);
|
|
|
|
UpdateTransaction(mtx, i, sigdata);
|
|
|
|
ScriptError serror = SCRIPT_ERR_OK;
|
|
if (!VerifyScript(txin.scriptSig, prevPubKey, STANDARD_SCRIPT_VERIFY_FLAGS, checker, consensusBranchId, &serror)) {
|
|
SigningErrorToJSON(txin, vErrors, ScriptErrorString(serror));
|
|
}
|
|
}
|
|
bool fComplete = vErrors.empty();
|
|
UniValue retVal(UniValue::VOBJ);
|
|
|
|
if (fComplete && !returnHex)
|
|
{
|
|
CValidationState state;
|
|
CTransaction finalTx = mtx;
|
|
LOCK(cs_main);
|
|
|
|
bool relayTx;
|
|
{
|
|
LOCK2(smartTransactionCS, mempool.cs);
|
|
relayTx = myAddtomempool(finalTx, &state);
|
|
}
|
|
|
|
if (!relayTx)
|
|
{
|
|
throw JSONRPCError(RPC_TRANSACTION_REJECTED, "Could not commit transaction - rejected");
|
|
}
|
|
else
|
|
{
|
|
//printf("%s: success adding %s to mempool\n", __func__, newImportTx.GetHash().GetHex().c_str());
|
|
RelayTransaction(finalTx);
|
|
}
|
|
retVal.pushKV("txid", finalTx.GetHash().GetHex());
|
|
}
|
|
else
|
|
{
|
|
// if this is an ID swap, spend ID to transaction and take funds or pay requird funds and take ID
|
|
// if this is a funds swap, pay required funds and take required funds
|
|
retVal.pushKV("tx", EncodeHexTx(mtx));
|
|
if (!vErrors.empty())
|
|
{
|
|
retVal.pushKV("errors", vErrors);
|
|
}
|
|
}
|
|
|
|
return retVal;
|
|
}
|
|
|
|
UniValue IdOfferInfo(const CIdentity &identityOffer)
|
|
{
|
|
UniValue retVal(UniValue::VOBJ);
|
|
retVal.pushKV("name", identityOffer.name);
|
|
retVal.pushKV("identityid", EncodeDestination(CIdentityID(identityOffer.GetID())));
|
|
retVal.pushKV("systemid", EncodeDestination(CIdentityID(identityOffer.systemID)));
|
|
retVal.pushKV("original", identityOffer.systemID == ASSETCHAINS_CHAINID);
|
|
return retVal;
|
|
}
|
|
|
|
UniValue getoffers(const UniValue& params, bool fHelp)
|
|
{
|
|
if (fHelp || params.size() < 1 || params.size() > 3)
|
|
{
|
|
throw runtime_error(
|
|
"getoffers \"currencyorid\" (iscurrency) (withtx)\n"
|
|
"\nReturns all open offers for a specific currency or ID\n"
|
|
|
|
"\nArguments\n"
|
|
"1. \"currencyorid\" (string, required) The currency or ID to check for offers, both sale and purchase\n"
|
|
"2. \"iscurrency\" (bool, optional) default=false, if false, this looks for ID offers, if true, currencies\n"
|
|
"3. \"withtx\" (bool, optional) default=false, if true, this returns serialized hex of the exchange transaction for signing\n"
|
|
|
|
"\nResult:\n"
|
|
"all available offers for or in the indicated currency or ID are displayed\n"
|
|
|
|
"\nExamples:\n"
|
|
+ HelpExampleCli("getoffers", "\"currencyorid\" (iscurrency)")
|
|
+ HelpExampleRpc("getoffers", "\"currencyorid\" (iscurrency)")
|
|
);
|
|
}
|
|
|
|
CheckVerusVaultAPIsValid();
|
|
|
|
bool isCurrency = false;
|
|
if (params.size() > 1)
|
|
{
|
|
isCurrency = uni_get_bool(params[1]);
|
|
}
|
|
|
|
bool withTx = false;
|
|
if (params.size() > 2)
|
|
{
|
|
withTx = uni_get_bool(params[2]);
|
|
}
|
|
|
|
uint160 lookupID, lookupForID;
|
|
|
|
CCurrencyDefinition currencyDef;
|
|
uint160 currencyOrIdID;
|
|
CIdentity identity;
|
|
std::string currencyOrIDStr(uni_get_str(params[0]));
|
|
|
|
LOCK(cs_main);
|
|
|
|
if (isCurrency)
|
|
{
|
|
lookupID = ValidateCurrencyName(currencyOrIDStr, true, ¤cyDef);
|
|
if (lookupID.IsNull())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Currency specified as source is not valid");
|
|
}
|
|
lookupForID = COnChainOffer::OnChainOfferForCurrencyKey(lookupID);
|
|
lookupID = COnChainOffer::OnChainCurrencyOfferKey(lookupID);
|
|
currencyOrIdID = currencyDef.GetID();
|
|
}
|
|
else
|
|
{
|
|
CTxDestination idDest = DecodeDestination(currencyOrIDStr);
|
|
if (idDest.which() != COptCCParams::ADDRTYPE_ID)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Identity specified as source is not valid");
|
|
}
|
|
lookupID = GetDestinationID(idDest);
|
|
if (!(identity = CIdentity::LookupIdentity(lookupID)).IsValid())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Source identity not found");
|
|
}
|
|
lookupForID = COnChainOffer::OnChainOfferForIdentityKey(lookupID);
|
|
lookupID = COnChainOffer::OnChainIdentityOfferKey(lookupID);
|
|
currencyOrIdID = identity.GetID();
|
|
}
|
|
|
|
std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue>> unspentOutputOffers;
|
|
std::vector<std::pair<CAddressUnspentKey, CAddressUnspentValue>> unspentOutputs;
|
|
|
|
uint32_t height = chainActive.Height();
|
|
|
|
// bool is "iscurrency" for the offer in a buy and for the request for payment in a sell
|
|
|
|
// offers to buy with IDs
|
|
std::multimap<std::pair<uint160, CAmount>, UniValue> uniBuyWithIDs;
|
|
std::multimap<std::pair<uint160, CAmount>, UniValue> uniSellToIDs;
|
|
|
|
std::multimap<std::pair<uint160, CAmount>, UniValue> uniBuyWithCurrency;
|
|
std::multimap<std::pair<uint160, CAmount>, UniValue> uniSellToCurrency;
|
|
|
|
//printf("%s: looking up keys: %s, %s\n", __func__, EncodeDestination(CKeyID(lookupID)).c_str(), EncodeDestination(CKeyID(lookupForID)).c_str());
|
|
|
|
if (!GetAddressUnspent(lookupID, CScript::P2PKH, unspentOutputOffers) || !GetAddressUnspent(lookupForID, CScript::P2PKH, unspentOutputs))
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
UniValue retVal(UniValue::VOBJ);
|
|
unspentOutputs.insert(unspentOutputs.end(), unspentOutputOffers.begin(), unspentOutputOffers.end());
|
|
|
|
for (auto &oneOffer : unspentOutputs)
|
|
{
|
|
CTransaction postedTx, offerTx, inputToOfferTx;
|
|
uint256 blockHash;
|
|
CPartialTransactionProof offerTxProof;
|
|
COptCCParams p;
|
|
if (myGetTransaction(oneOffer.first.txhash, postedTx, blockHash))
|
|
{
|
|
if (GetOpRetChainOffer(postedTx, offerTx, inputToOfferTx, height))
|
|
{
|
|
// find out what the transaction is requesting for payment
|
|
CCurrencyValueMap offerToPay, wePay;
|
|
CIdentity offerToTransfer, weTransfer;
|
|
|
|
std::pair<bool, CAmount> exchangeForurrencyOrID;
|
|
bool isBuy = true;
|
|
|
|
std::pair<CTxOut, CTxOut> offerOuts(std::make_pair(inputToOfferTx.vout[offerTx.vin[0].prevout.n], offerTx.vout[0]));
|
|
if (offerTx.vShieldedOutput.size() != 0)
|
|
{
|
|
offerOuts.second.nValue -= offerTx.valueBalance;
|
|
}
|
|
|
|
if (offerOuts.second.scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
p.evalCode == EVAL_IDENTITY_PRIMARY &&
|
|
p.vData.size())
|
|
{
|
|
if (!(weTransfer = CIdentity(p.vData[0])).IsValid())
|
|
{
|
|
continue;
|
|
}
|
|
// if this is not what we are looking for, then it must be a request to trade for what we are looking for
|
|
if (isCurrency || weTransfer.GetID() != currencyOrIdID)
|
|
{
|
|
// make sure the offer is offering what we are looking for
|
|
if (isCurrency)
|
|
{
|
|
// verify it is the currency we are looking for
|
|
p = COptCCParams();
|
|
if (!(offerOuts.first.scriptPubKey.IsSpendableOutputType(p) &&
|
|
((currencyOrIdID == ASSETCHAINS_CHAINID && offerOuts.first.nValue > 0) ||
|
|
((currencyOrIdID != ASSETCHAINS_CHAINID &&
|
|
(offerToPay = offerOuts.first.ReserveOutValue()).valueMap.count(currencyOrIdID) &&
|
|
offerToPay.valueMap[currencyOrIdID] > 0)))))
|
|
{
|
|
continue;
|
|
}
|
|
if (offerOuts.first.nValue > 0)
|
|
{
|
|
offerToPay.valueMap[ASSETCHAINS_CHAINID] = offerOuts.first.nValue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// verify it is the ID we are looking for
|
|
if (!(offerOuts.first.scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
p.evalCode == EVAL_IDENTITY_PRIMARY &&
|
|
p.vData.size() &&
|
|
(offerToTransfer = CIdentity(p.vData[0])).IsValid() &&
|
|
offerToTransfer.GetID() == currencyOrIdID))
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
UniValue offerJSON(UniValue::VOBJ);
|
|
offerJSON.pushKV("offer", offerToTransfer.IsValid() ? IdOfferInfo(offerToTransfer) : offerToPay.ToUniValue());
|
|
offerJSON.pushKV("accept", IdOfferInfo(weTransfer));
|
|
offerJSON.pushKV("blockexpiry", (int64_t)offerTx.nExpiryHeight);
|
|
if (withTx)
|
|
{
|
|
offerJSON.pushKV("tx", EncodeHexTx(offerTx));
|
|
}
|
|
offerJSON.pushKV("txid", postedTx.GetHash().GetHex());
|
|
if (offerToPay.valueMap.begin() != offerToPay.valueMap.end() && offerToPay.valueMap.begin()->first == currencyOrIdID)
|
|
{
|
|
uniSellToIDs.insert(std::make_pair(std::make_pair(weTransfer.GetID(), offerToPay.valueMap.begin()->second), offerJSON));
|
|
}
|
|
else // offer to transfer is our query ID
|
|
{
|
|
uniSellToIDs.insert(std::make_pair(std::make_pair(weTransfer.GetID(), offerOuts.second.nValue > 0 ? offerOuts.second.nValue : 0), offerJSON));
|
|
}
|
|
}
|
|
else // this is an offer to buy the referenced ID for either currency or another ID, which is in the first output
|
|
{
|
|
if (offerOuts.first.scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
p.evalCode == EVAL_IDENTITY_PRIMARY &&
|
|
p.vData.size() &&
|
|
(offerToTransfer = CIdentity(p.vData[0])).IsValid())
|
|
{
|
|
UniValue offerJSON(UniValue::VOBJ);
|
|
offerJSON.pushKV("offer", offerToTransfer.IsValid() ? IdOfferInfo(offerToTransfer) : offerToPay.ToUniValue());
|
|
offerJSON.pushKV("accept", weTransfer.IsValid() ? IdOfferInfo(weTransfer) : wePay.ToUniValue());
|
|
offerJSON.pushKV("blockexpiry", (int64_t)offerTx.nExpiryHeight);
|
|
if (withTx)
|
|
{
|
|
offerJSON.pushKV("tx", EncodeHexTx(offerTx));
|
|
}
|
|
offerJSON.pushKV("txid", postedTx.GetHash().GetHex());
|
|
uniBuyWithIDs.insert(std::make_pair(std::make_pair(offerToTransfer.GetID(), offerOuts.second.nValue > 0 ? offerOuts.second.nValue : 0), offerJSON));
|
|
}
|
|
else
|
|
{
|
|
// verify that there is a non-zero offer
|
|
p = COptCCParams();
|
|
if (!(offerOuts.first.scriptPubKey.IsSpendableOutputType(p) &&
|
|
(offerOuts.first.nValue > 0 ||
|
|
((offerToPay = offerOuts.first.ReserveOutValue()) > CCurrencyValueMap()))))
|
|
{
|
|
continue;
|
|
}
|
|
uint160 currencyID = offerOuts.first.nValue > 0 ? ASSETCHAINS_CHAINID : offerToPay.valueMap.begin()->first;
|
|
CAmount offerAmount = offerOuts.first.nValue > 0 ? offerOuts.first.nValue : offerToPay.valueMap.begin()->second;
|
|
if (offerOuts.first.nValue > 0)
|
|
{
|
|
offerToPay.valueMap[ASSETCHAINS_CHAINID] = offerOuts.first.nValue;
|
|
}
|
|
UniValue offerJSON(UniValue::VOBJ);
|
|
offerJSON.pushKV("offer", offerToTransfer.IsValid() ? IdOfferInfo(offerToTransfer) : offerToPay.ToUniValue());
|
|
offerJSON.pushKV("accept", weTransfer.IsValid() ? IdOfferInfo(weTransfer) : wePay.ToUniValue());
|
|
offerJSON.pushKV("blockexpiry", (int64_t)offerTx.nExpiryHeight);
|
|
if (withTx)
|
|
{
|
|
offerJSON.pushKV("tx", EncodeHexTx(offerTx));
|
|
}
|
|
offerJSON.pushKV("txid", postedTx.GetHash().GetHex());
|
|
uniBuyWithCurrency.insert(std::make_pair(std::make_pair(currencyID, offerAmount), offerJSON));
|
|
}
|
|
}
|
|
}
|
|
else if (offerOuts.second.scriptPubKey.IsSpendableOutputType(p))
|
|
{
|
|
// see if it is a buy with the currency we are querying for
|
|
wePay = offerOuts.second.ReserveOutValue();
|
|
if (offerOuts.second.nValue > 0)
|
|
{
|
|
wePay.valueMap[ASSETCHAINS_CHAINID] = offerOuts.second.nValue;
|
|
}
|
|
|
|
if (isCurrency && wePay.valueMap.count(currencyOrIdID) && wePay.valueMap[currencyOrIdID] > 0)
|
|
{
|
|
// if so, then it is a buy with whatever the input is
|
|
if (offerOuts.first.scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
p.evalCode == EVAL_IDENTITY_PRIMARY &&
|
|
p.vData.size() &&
|
|
(offerToTransfer = CIdentity(p.vData[0])).IsValid())
|
|
{
|
|
UniValue offerJSON(UniValue::VOBJ);
|
|
offerJSON.pushKV("offer", IdOfferInfo(offerToTransfer));
|
|
offerJSON.pushKV("accept", wePay.ToUniValue());
|
|
offerJSON.pushKV("blockexpiry", (int64_t)offerTx.nExpiryHeight);
|
|
if (withTx)
|
|
{
|
|
offerJSON.pushKV("tx", EncodeHexTx(offerTx));
|
|
}
|
|
offerJSON.pushKV("txid", postedTx.GetHash().GetHex());
|
|
uniBuyWithIDs.insert(std::make_pair(std::make_pair(offerToTransfer.GetID(), wePay.valueMap.begin()->second), offerJSON));
|
|
}
|
|
else
|
|
{
|
|
// verify that there is a non-zero offer
|
|
offerToPay = offerOuts.first.ReserveOutValue();
|
|
if (offerOuts.first.nValue > 0)
|
|
{
|
|
offerToPay.valueMap[ASSETCHAINS_CHAINID] = offerOuts.first.nValue;
|
|
}
|
|
|
|
p = COptCCParams();
|
|
if (!(offerOuts.first.scriptPubKey.IsSpendableOutputType(p) && offerToPay > CCurrencyValueMap()))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
bool nativeOffer = offerOuts.first.nValue > 0 && offerToPay.CanonicalMap().valueMap.size() == 1;
|
|
uint160 currencyID = nativeOffer ? ASSETCHAINS_CHAINID : offerToPay.valueMap.begin()->first;
|
|
CAmount offerAmount = nativeOffer ? offerOuts.first.nValue : offerToPay.valueMap.begin()->second;
|
|
|
|
UniValue offerJSON(UniValue::VOBJ);
|
|
offerJSON.pushKV("offer", offerToPay.ToUniValue());
|
|
offerJSON.pushKV("accept", wePay.ToUniValue());
|
|
offerJSON.pushKV("blockexpiry", (int64_t)offerTx.nExpiryHeight);
|
|
if (withTx)
|
|
{
|
|
offerJSON.pushKV("tx", EncodeHexTx(offerTx));
|
|
}
|
|
offerJSON.pushKV("txid", postedTx.GetHash().GetHex());
|
|
uniBuyWithCurrency.insert(std::make_pair(std::make_pair(currencyID, CalculateFractionalPrice(offerAmount, wePay.valueMap[currencyOrIdID], true)), offerJSON));
|
|
}
|
|
}
|
|
else if (isCurrency &&
|
|
offerOuts.first.scriptPubKey.IsSpendableOutputType(p) &&
|
|
((currencyOrIdID == ASSETCHAINS_CHAINID && offerOuts.first.nValue > 0) ||
|
|
((currencyOrIdID != ASSETCHAINS_CHAINID &&
|
|
(offerToPay = offerOuts.first.ReserveOutValue()).valueMap.count(currencyOrIdID) &&
|
|
offerToPay.valueMap[currencyOrIdID] > 0))))
|
|
{
|
|
if (offerOuts.first.nValue > 0)
|
|
{
|
|
offerToPay.valueMap[ASSETCHAINS_CHAINID] = offerOuts.first.nValue;
|
|
}
|
|
|
|
// offer to sell currency we are querying for the output's currency
|
|
bool nativePay = offerOuts.second.nValue > 0 && wePay.CanonicalMap().valueMap.size() == 1;
|
|
uint160 currencyID = nativePay ? ASSETCHAINS_CHAINID : wePay.valueMap.begin()->first;
|
|
CAmount payAmount = nativePay ? offerOuts.second.nValue : wePay.valueMap.begin()->second;
|
|
UniValue offerJSON(UniValue::VOBJ);
|
|
offerJSON.pushKV("offer", offerToPay.ToUniValue());
|
|
offerJSON.pushKV("accept", wePay.ToUniValue());
|
|
offerJSON.pushKV("blockexpiry", (int64_t)offerTx.nExpiryHeight);
|
|
if (withTx)
|
|
{
|
|
offerJSON.pushKV("tx", EncodeHexTx(offerTx));
|
|
}
|
|
offerJSON.pushKV("txid", postedTx.GetHash().GetHex());
|
|
uniSellToCurrency.insert(std::make_pair(std::make_pair(currencyID, CalculateFractionalPrice(payAmount, offerToPay.valueMap[currencyOrIdID], false)), offerJSON));
|
|
}
|
|
else if (!isCurrency &&
|
|
offerOuts.first.scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
p.evalCode == EVAL_IDENTITY_PRIMARY &&
|
|
p.vData.size() &&
|
|
(offerToTransfer = CIdentity(p.vData[0])).IsValid() &&
|
|
offerToTransfer.GetID() == currencyOrIdID)
|
|
{
|
|
bool nativePay = offerOuts.second.nValue > 0 && wePay.CanonicalMap().valueMap.size() == 1;
|
|
uint160 currencyID = nativePay ? ASSETCHAINS_CHAINID : wePay.valueMap.begin()->first;
|
|
CAmount payAmount = nativePay ? offerOuts.second.nValue : wePay.valueMap.begin()->second;
|
|
|
|
// offer to sell identity we are querying for the output's currency
|
|
UniValue offerJSON(UniValue::VOBJ);
|
|
offerJSON.pushKV("offer", IdOfferInfo(offerToTransfer));
|
|
offerJSON.pushKV("accept", wePay.ToUniValue());
|
|
offerJSON.pushKV("blockexpiry", (int64_t)offerTx.nExpiryHeight);
|
|
if (withTx)
|
|
{
|
|
offerJSON.pushKV("tx", EncodeHexTx(offerTx));
|
|
}
|
|
offerJSON.pushKV("txid", postedTx.GetHash().GetHex());
|
|
uniSellToCurrency.insert(std::make_pair(std::make_pair(currencyID, payAmount), offerJSON));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot retrieve transaction " + oneOffer.first.txhash.GetHex() + ", the local index is likely corrupted and either requires reindex, bootstrap, or resync");
|
|
}
|
|
}
|
|
if (isCurrency)
|
|
{
|
|
// for uniSellToIDs (same as offers in this currency for IDs), we will want to order by highest price to lowest price,
|
|
// as the query was only for things that could be traded or purchased from the currency. the long tail is likely
|
|
// less interesting. we do not end up with buy with IDs on a currency only query
|
|
UniValue oneCategory(UniValue::VARR);
|
|
for (auto rIT = uniSellToIDs.rbegin(); rIT != uniSellToIDs.rend(); rIT++)
|
|
{
|
|
UniValue oneOffer(UniValue::VOBJ);
|
|
oneOffer.pushKV("identityid", EncodeDestination(CIdentityID(rIT->first.first)));
|
|
oneOffer.pushKV("price", ValueFromAmount(rIT->first.second));
|
|
oneOffer.pushKV("offer", rIT->second);
|
|
oneCategory.push_back(oneOffer);
|
|
}
|
|
|
|
if (oneCategory.size())
|
|
{
|
|
retVal.pushKV("currency_" + EncodeDestination(CIdentityID(currencyOrIdID)) + "_for_ids", oneCategory);
|
|
oneCategory = UniValue(UniValue::VARR);
|
|
}
|
|
|
|
// buying currency with IDs is basically selling the ID for this currency
|
|
for (auto rIT = uniBuyWithIDs.rbegin(); rIT != uniBuyWithIDs.rend(); rIT++)
|
|
{
|
|
UniValue oneOffer(UniValue::VOBJ);
|
|
oneOffer.pushKV("identityid", EncodeDestination(CIdentityID(rIT->first.first)));
|
|
oneOffer.pushKV("price", ValueFromAmount(rIT->first.second));
|
|
oneOffer.pushKV("offer", rIT->second);
|
|
oneCategory.push_back(oneOffer);
|
|
}
|
|
|
|
if (oneCategory.size())
|
|
{
|
|
retVal.pushKV("ids_for_currency_" + EncodeDestination(CIdentityID(currencyOrIdID)), oneCategory);
|
|
oneCategory = UniValue(UniValue::VARR);
|
|
}
|
|
|
|
// we should order these by currency, showing both sellTo and buyWith in each currency together
|
|
auto rSellIT = uniSellToCurrency.rbegin();
|
|
auto rBuyIT = uniBuyWithCurrency.rbegin();
|
|
uint160 lastCurrencyID;
|
|
bool isBuyLast = false;
|
|
|
|
while (rSellIT != uniSellToCurrency.rend() || rBuyIT != uniBuyWithCurrency.rend())
|
|
{
|
|
for (;
|
|
rSellIT != uniSellToCurrency.rend() &&
|
|
(rBuyIT == uniBuyWithCurrency.rend() || (rSellIT->first.first.GetHex() >= rBuyIT->first.first.GetHex()));
|
|
rSellIT++)
|
|
{
|
|
uint160 newLast = rSellIT->first.first;
|
|
if (lastCurrencyID != newLast)
|
|
{
|
|
if (oneCategory.size())
|
|
{
|
|
retVal.pushKV("currency_" + EncodeDestination(CIdentityID(lastCurrencyID)) + "_offers_in_currency_" + EncodeDestination(CIdentityID(currencyOrIdID)), oneCategory);
|
|
oneCategory = UniValue(UniValue::VARR);
|
|
}
|
|
lastCurrencyID = newLast;
|
|
}
|
|
UniValue oneOffer(UniValue::VOBJ);
|
|
oneOffer.pushKV("currencyid", EncodeDestination(CIdentityID(rSellIT->first.first)));
|
|
oneOffer.pushKV("price", ValueFromAmount(rSellIT->first.second));
|
|
oneOffer.pushKV("offer", rSellIT->second);
|
|
oneCategory.push_back(oneOffer);
|
|
isBuyLast = false;
|
|
}
|
|
|
|
for (;
|
|
rBuyIT != uniBuyWithCurrency.rend() &&
|
|
(rSellIT == uniSellToCurrency.rend() || (rBuyIT->first.first.GetHex() >= lastCurrencyID.GetHex()));
|
|
rBuyIT++)
|
|
{
|
|
uint160 newLast = rBuyIT->first.first;
|
|
if (lastCurrencyID != newLast)
|
|
{
|
|
if (oneCategory.size())
|
|
{
|
|
retVal.pushKV("currency_" + EncodeDestination(CIdentityID(currencyOrIdID)) + "_offers_in_currency_" + EncodeDestination(CIdentityID(lastCurrencyID)), oneCategory);
|
|
oneCategory = UniValue(UniValue::VARR);
|
|
}
|
|
lastCurrencyID = newLast;
|
|
}
|
|
UniValue oneOffer(UniValue::VOBJ);
|
|
oneOffer.pushKV("currencyid", EncodeDestination(CIdentityID(rBuyIT->first.first)));
|
|
oneOffer.pushKV("price", ValueFromAmount(rBuyIT->first.second));
|
|
oneOffer.pushKV("offer", rBuyIT->second);
|
|
oneCategory.push_back(oneOffer);
|
|
isBuyLast = true;
|
|
}
|
|
}
|
|
if (oneCategory.size())
|
|
{
|
|
if (isBuyLast)
|
|
{
|
|
retVal.pushKV("currency_" + EncodeDestination(CIdentityID(currencyOrIdID)) + "_offers_in_currency_" + EncodeDestination(CIdentityID(lastCurrencyID)), oneCategory);
|
|
}
|
|
else
|
|
{
|
|
retVal.pushKV("currency_" + EncodeDestination(CIdentityID(lastCurrencyID)) + "_offers_in_currency_" + EncodeDestination(CIdentityID(currencyOrIdID)), oneCategory);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// for uniSellToIDs (same as offer to trade ID in question for other ID(s)) - offered in exchange for IDs
|
|
// for uniBuyWithIDs (same as offer to trade other IDs for the ID in question) - IDs offered in exchange for
|
|
|
|
// for uniSellToCurrency, offer(s) to sell for a specific currency at a specific price, list low to high in each currecny
|
|
// uniBuyWithCurrency, offers to buy with a specific currency for a specific price, list high to low
|
|
|
|
// for uniSellToIDs (same as this ID on offer for other IDs)
|
|
UniValue oneCategory(UniValue::VARR);
|
|
for (auto rIT = uniSellToIDs.rbegin(); rIT != uniSellToIDs.rend(); rIT++)
|
|
{
|
|
UniValue oneOffer(UniValue::VOBJ);
|
|
oneOffer.pushKV("identityid", EncodeDestination(CIdentityID(rIT->first.first)));
|
|
oneOffer.pushKV("price", ValueFromAmount(rIT->first.second));
|
|
oneOffer.pushKV("offer", rIT->second);
|
|
oneCategory.push_back(oneOffer);
|
|
}
|
|
|
|
if (oneCategory.size())
|
|
{
|
|
retVal.pushKV("id_" + EncodeDestination(CIdentityID(currencyOrIdID)) + "_for_ids", oneCategory);
|
|
oneCategory = UniValue(UniValue::VARR);
|
|
}
|
|
|
|
for (auto rIT = uniBuyWithIDs.rbegin(); rIT != uniBuyWithIDs.rend(); rIT++)
|
|
{
|
|
UniValue oneOffer(UniValue::VOBJ);
|
|
oneOffer.pushKV("identityid", EncodeDestination(CIdentityID(rIT->first.first)));
|
|
oneOffer.pushKV("price", ValueFromAmount(rIT->first.second));
|
|
oneOffer.pushKV("offer", rIT->second);
|
|
oneCategory.push_back(oneOffer);
|
|
}
|
|
|
|
if (oneCategory.size())
|
|
{
|
|
retVal.pushKV("ids_for_id_" + EncodeDestination(CIdentityID(currencyOrIdID)), oneCategory);
|
|
oneCategory = UniValue(UniValue::VARR);
|
|
}
|
|
|
|
// we should order these by currency, showing both sellTo and buyWith in each currency together
|
|
auto rSellIT = uniSellToCurrency.rbegin();
|
|
auto rBuyIT = uniBuyWithCurrency.rbegin();
|
|
uint160 lastCurrencyID;
|
|
bool isBuyLast = false;
|
|
|
|
while (rSellIT != uniSellToCurrency.rend() || rBuyIT != uniBuyWithCurrency.rend())
|
|
{
|
|
|
|
for (;
|
|
rSellIT != uniSellToCurrency.rend() &&
|
|
(rBuyIT == uniBuyWithCurrency.rend() || (rSellIT->first.first.GetHex() >= rBuyIT->first.first.GetHex()));
|
|
rSellIT++)
|
|
{
|
|
uint160 newLast = rSellIT->first.first;
|
|
if (lastCurrencyID != newLast)
|
|
{
|
|
if (oneCategory.size())
|
|
{
|
|
retVal.pushKV("id_" + EncodeDestination(CIdentityID(currencyOrIdID)) + "_for_currency_" + EncodeDestination(CIdentityID(lastCurrencyID)), oneCategory);
|
|
oneCategory = UniValue(UniValue::VARR);
|
|
}
|
|
lastCurrencyID = newLast;
|
|
}
|
|
UniValue oneOffer(UniValue::VOBJ);
|
|
oneOffer.pushKV("currencyid", EncodeDestination(CIdentityID(rSellIT->first.first)));
|
|
oneOffer.pushKV("price", ValueFromAmount(rSellIT->first.second));
|
|
oneOffer.pushKV("offer", rSellIT->second);
|
|
oneCategory.push_back(oneOffer);
|
|
isBuyLast = false;
|
|
}
|
|
|
|
for (;
|
|
rBuyIT != uniBuyWithCurrency.rend() &&
|
|
(rSellIT == uniSellToCurrency.rend() || (rBuyIT->first.first.GetHex() >= lastCurrencyID.GetHex()));
|
|
rBuyIT++)
|
|
{
|
|
uint160 newLast = rBuyIT->first.first;
|
|
if (lastCurrencyID != newLast)
|
|
{
|
|
if (oneCategory.size())
|
|
{
|
|
retVal.pushKV("currency_" + EncodeDestination(CIdentityID(lastCurrencyID)) + "_for_id_" + EncodeDestination(CIdentityID(currencyOrIdID)), oneCategory);
|
|
oneCategory = UniValue(UniValue::VARR);
|
|
}
|
|
lastCurrencyID = newLast;
|
|
}
|
|
UniValue oneOffer(UniValue::VOBJ);
|
|
oneOffer.pushKV("currencyid", EncodeDestination(CIdentityID(rBuyIT->first.first)));
|
|
oneOffer.pushKV("price", ValueFromAmount(rBuyIT->first.second));
|
|
oneOffer.pushKV("offer", rBuyIT->second);
|
|
oneCategory.push_back(oneOffer);
|
|
isBuyLast = true;
|
|
}
|
|
}
|
|
if (oneCategory.size())
|
|
{
|
|
if (isBuyLast)
|
|
{
|
|
retVal.pushKV("currency_" + EncodeDestination(CIdentityID(lastCurrencyID)) + "_for_id_" + EncodeDestination(CIdentityID(currencyOrIdID)), oneCategory);
|
|
}
|
|
else
|
|
{
|
|
retVal.pushKV("id_" + EncodeDestination(CIdentityID(currencyOrIdID)) + "_for_currency_" + EncodeDestination(CIdentityID(lastCurrencyID)), oneCategory);
|
|
}
|
|
}
|
|
}
|
|
return retVal;
|
|
}
|
|
return NullUniValue;
|
|
}
|
|
|
|
// close an offer by spending its source
|
|
bool CloseOneOffer(const OfferInfo &oneOffer, TransactionBuilder &tb, const CTxDestination &_dest, uint32_t height, const libzcash::PaymentAddress &zdest=libzcash::PaymentAddress());
|
|
bool CloseOneOffer(const OfferInfo &oneOffer, TransactionBuilder &tb, const CTxDestination &_dest, uint32_t height, const PaymentAddress &zdest)
|
|
{
|
|
CTxDestination dest = _dest;
|
|
// we just need to be able to spend the output to another input/output
|
|
CScript outScript(oneOffer.inputToOfferTx.vout[oneOffer.offerTx.vin[0].prevout.n].scriptPubKey);
|
|
CAmount value = oneOffer.inputToOfferTx.vout[oneOffer.offerTx.vin[0].prevout.n].nValue;
|
|
tb.AddTransparentInput(COutPoint(oneOffer.offerTx.vin[0].prevout.hash, oneOffer.offerTx.vin[0].prevout.n), outScript, value);
|
|
COptCCParams p;
|
|
if (outScript.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
(p.evalCode == EVAL_IDENTITY_PRIMARY || p.evalCode == EVAL_IDENTITY_COMMITMENT) &&
|
|
p.vData.size())
|
|
{
|
|
const libzcash::SaplingPaymentAddress *pSaplingAddress = boost::get<libzcash::SaplingPaymentAddress>(&zdest);
|
|
bool hasZDest = pSaplingAddress != nullptr;
|
|
bool hasTDest = dest.which() != COptCCParams::ADDRTYPE_INVALID;
|
|
bool hasTokens = false;
|
|
|
|
value -= std::min(DEFAULT_TRANSACTION_FEE, value);
|
|
|
|
uint256 ovk;
|
|
if (hasZDest)
|
|
{
|
|
HDSeed seed;
|
|
if (!pwalletMain->GetHDSeed(seed)) {
|
|
LogPrintf("%s: Wallet seed unavailable for z-address output\n", __func__);
|
|
return false;
|
|
}
|
|
ovk = ovkForShieldingFromTaddr(seed);
|
|
tb.SendChangeTo(*pSaplingAddress, ovk);
|
|
}
|
|
if (p.evalCode == EVAL_IDENTITY_PRIMARY)
|
|
{
|
|
CIdentity offeredIdentity(p.vData[0]);
|
|
if (!offeredIdentity.IsValid())
|
|
{
|
|
LogPrintf("%s: Invalid identity\n", __func__);
|
|
return false;
|
|
}
|
|
if (!hasTDest)
|
|
{
|
|
dest = CIdentityID(offeredIdentity.GetID());
|
|
hasTDest = true;
|
|
}
|
|
offeredIdentity.UpgradeVersion(height);
|
|
value -= std::min(DEFAULT_TRANSACTION_FEE, value);
|
|
tb.AddTransparentOutput(offeredIdentity.IdentityUpdateOutputScript(height + 1), value);
|
|
}
|
|
else if (p.evalCode == EVAL_IDENTITY_COMMITMENT)
|
|
{
|
|
if (!hasTDest)
|
|
{
|
|
if (p.vKeys.size() > 1)
|
|
{
|
|
LogPrintf("%s: No transparent destination specified and cannot determine from commitment\n", __func__);
|
|
return false;
|
|
}
|
|
dest = p.vKeys[0];
|
|
hasTDest = true;
|
|
}
|
|
CCommitmentHash ch(p.vData[0]);
|
|
if (ch.IsValid() && ch.reserveValues.valueMap.size())
|
|
{
|
|
hasTokens = true;
|
|
if (hasZDest)
|
|
{
|
|
tb.AddSaplingOutput(ovk, *pSaplingAddress, value);
|
|
tb.AddTransparentOutput(MakeMofNCCScript(CConditionObj<CTokenOutput>(EVAL_RESERVE_OUTPUT, {dest}, 1, (CTokenOutput *)&ch)), 0);
|
|
}
|
|
else if (dest.which() != COptCCParams::ADDRTYPE_INVALID)
|
|
{
|
|
tb.AddTransparentOutput(MakeMofNCCScript(CConditionObj<CTokenOutput>(EVAL_RESERVE_OUTPUT, {dest}, 1, (CTokenOutput *)&ch)), value);
|
|
}
|
|
else
|
|
{
|
|
LogPrintf("%s: Must close offers with valid, transparent funds destination when offer funds include non-native currency\n", __func__);
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (hasZDest)
|
|
{
|
|
tb.AddSaplingOutput(ovk, *pSaplingAddress, value);
|
|
}
|
|
else
|
|
{
|
|
tb.AddTransparentOutput(dest, value);
|
|
}
|
|
}
|
|
}
|
|
if (hasTokens || !hasZDest)
|
|
{
|
|
tb.SendChangeTo(dest);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
UniValue closeoffers(const UniValue& params, bool fHelp)
|
|
{
|
|
if (fHelp || params.size() > 3)
|
|
{
|
|
throw runtime_error(
|
|
"closeoffers ('[\"offer1_txid\", \"offer2_txid\", ...]') (transparentorprivatefundsdestination) (privatefundsdestination)\n"
|
|
"\nCloses all offers listed, if they are still valid and belong to this wallet.\n"
|
|
"Always closes expired offers, even if no parameters are given\n\n"
|
|
|
|
"\nArguments\n"
|
|
" [\"offer1_txid\", \"offer2_txid\", ...] (array, optional) array of hex tx ids of offers to close\n"
|
|
" transparentorprivatefundsdestination (transparent or private address, optional) destination for closing funds\n"
|
|
" privatefundsdestination (private address, optional) destination for native funds only\n"
|
|
|
|
"\nResult\n"
|
|
" null return\n"
|
|
);
|
|
}
|
|
CheckVerusVaultAPIsValid();
|
|
|
|
UniValue retVal;
|
|
std::set<uint256> txIds;
|
|
|
|
if (params.size() > 0)
|
|
{
|
|
if (!params[0].isArray())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "First parameter must be an array of transaction IDs that are offers from this wallet to close. It may be null '[]'");
|
|
}
|
|
for (int i = 0; i < params[0].size(); i++)
|
|
{
|
|
uint256 oneTxId;
|
|
oneTxId.SetHex(uni_get_str(params[0][i]));
|
|
if (oneTxId.IsNull())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid txid specified, txids must be hex strings with no prefix");
|
|
}
|
|
txIds.insert(oneTxId);
|
|
}
|
|
}
|
|
|
|
libzcash::PaymentAddress zaddressDest;
|
|
CTxDestination transparentDest;
|
|
std::string destStr;
|
|
|
|
if (params.size() > 1)
|
|
{
|
|
destStr = uni_get_str(params[1]);
|
|
if (destStr.empty())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Closing open offers requires a transparent and/or private address to send funds to when closing");
|
|
}
|
|
transparentDest = ValidateDestination(destStr);
|
|
}
|
|
|
|
bool hasTDest = transparentDest.which() != COptCCParams::ADDRTYPE_INVALID;
|
|
bool hasZDest = !hasTDest && pwalletMain->GetAndValidateSaplingZAddress(destStr, zaddressDest);
|
|
|
|
if (hasTDest)
|
|
{
|
|
if (params.size() > 2)
|
|
{
|
|
if (hasZDest)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Third parameter must be a private address and only one private address may be specified");
|
|
}
|
|
std::string zDestStr = uni_get_str(params[2]);
|
|
|
|
if (!(hasZDest = pwalletMain->GetAndValidateSaplingZAddress(zDestStr, zaddressDest)))
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Third parameter must be a valid private address");
|
|
}
|
|
}
|
|
}
|
|
|
|
std::map<std::pair<bool, uint256>, OfferInfo> myOffers;
|
|
uint32_t height;
|
|
{
|
|
LOCK2(cs_main, pwalletMain->cs_wallet);
|
|
LOCK(mempool.cs);
|
|
height = chainActive.Height();
|
|
GetMyOffers(myOffers, height, txIds.size() != 0, true);
|
|
}
|
|
|
|
for (auto &oneOffer : myOffers)
|
|
{
|
|
TransactionBuilder tb(Params().GetConsensus(), height + 1, pwalletMain);
|
|
CTransaction oneTx;
|
|
{
|
|
LOCK(pwalletMain->cs_wallet);
|
|
// if this is true, it is unexpired
|
|
if (oneOffer.first.first && !txIds.count(oneOffer.first.second))
|
|
{
|
|
continue;
|
|
}
|
|
if (!CloseOneOffer(oneOffer.second, tb, transparentDest, height, zaddressDest))
|
|
{
|
|
continue;
|
|
}
|
|
TransactionBuilderResult buildResult = tb.Build();
|
|
oneTx = buildResult.GetTxOrThrow();
|
|
}
|
|
LOCK(cs_main);
|
|
|
|
bool relayTx;
|
|
CValidationState state;
|
|
{
|
|
LOCK2(smartTransactionCS, mempool.cs);
|
|
relayTx = myAddtomempool(oneTx, &state);
|
|
}
|
|
|
|
if (!relayTx)
|
|
{
|
|
throw JSONRPCError(RPC_TRANSACTION_REJECTED, "Close offer transaction rejected: " + state.GetRejectReason());
|
|
}
|
|
else
|
|
{
|
|
RelayTransaction(oneTx);
|
|
}
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
UniValue listopenoffers(const UniValue& params, bool fHelp)
|
|
{
|
|
if (fHelp || params.size() > 2)
|
|
{
|
|
throw runtime_error(
|
|
"listopenoffers (unexpired) (expired)'\n"
|
|
"\nShows offers outstanding in this wallet\n"
|
|
|
|
"\nArguments\n"
|
|
" unexpired (bool, optional) default=true, list those offers in the wallet which are not expired\n"
|
|
" expired (bool, optional) default=true, list those offers in the wallet which are expired\n"
|
|
|
|
"\nResult\n"
|
|
" all open offers\n"
|
|
);
|
|
}
|
|
CheckVerusVaultAPIsValid();
|
|
|
|
UniValue retVal;
|
|
std::set<uint256> txIds;
|
|
|
|
bool listUnexpired = true;
|
|
bool listExpired = true;
|
|
if (params.size() > 0)
|
|
{
|
|
listUnexpired = uni_get_bool(params[0]);
|
|
}
|
|
if (params.size() > 1)
|
|
{
|
|
listExpired = uni_get_bool(params[1]);
|
|
}
|
|
|
|
std::map<std::pair<bool, uint256>, OfferInfo> myOffers;
|
|
|
|
LOCK2(cs_main, pwalletMain->cs_wallet);
|
|
LOCK(mempool.cs);
|
|
|
|
uint32_t height = chainActive.Height();
|
|
|
|
if (GetMyOffers(myOffers, height, listUnexpired, listExpired))
|
|
{
|
|
retVal = UniValue(UniValue::VARR);
|
|
for (auto &oneOffer : myOffers)
|
|
{
|
|
UniValue oneOfferUni(UniValue::VOBJ);
|
|
if (!oneOffer.first.first)
|
|
{
|
|
oneOfferUni.pushKV("expired", true);
|
|
}
|
|
else
|
|
{
|
|
oneOfferUni.pushKV("expires", (int64_t)oneOffer.second.offerTx.nExpiryHeight);
|
|
}
|
|
|
|
oneOfferUni.pushKV("txid", oneOffer.first.second.GetHex());
|
|
UniValue scriptPubKeyUni(UniValue::VOBJ);
|
|
ScriptPubKeyToUniv(oneOffer.second.inputToOfferTx.vout[oneOffer.second.offerTx.vin[0].prevout.n].scriptPubKey, scriptPubKeyUni, false);
|
|
scriptPubKeyUni.pushKV("nativeout", ValueFromAmount(oneOffer.second.inputToOfferTx.vout[oneOffer.second.offerTx.vin[0].prevout.n].nValue));
|
|
oneOfferUni.pushKV("offer", scriptPubKeyUni);
|
|
scriptPubKeyUni = UniValue(UniValue::VOBJ);
|
|
ScriptPubKeyToUniv(oneOffer.second.offerTx.vout[0].scriptPubKey, scriptPubKeyUni, false);
|
|
scriptPubKeyUni.pushKV("nativeout", ValueFromAmount(oneOffer.second.offerTx.vout[0].nValue));
|
|
oneOfferUni.pushKV("for", scriptPubKeyUni);
|
|
retVal.push_back(oneOfferUni);
|
|
}
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
bool IsValidExportCurrency(const CCurrencyDefinition &systemDest, const uint160 &exportCurrencyID, uint32_t height);
|
|
|
|
UniValue sendcurrency(const UniValue& params, bool fHelp)
|
|
{
|
|
if (fHelp || params.size() < 2 || params.size() > 4)
|
|
{
|
|
throw runtime_error(
|
|
"sendcurrency \"fromaddress\" '[{\"address\":... ,\"amount\":...},...]' (minconfs) (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"
|
|
" \"exportid\":\"false\", (bool, optional) if cross-chain export, export the full ID to the destination chain (will cost to export)\n"
|
|
" \"exportcurrency\":\"false\", (bool, optional) if cross-chain export, export the currency definition (will cost to export)\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. \"minconf\" (numeric, optional, default=1) only use funds confirmed at least this many times.\n"
|
|
"4. \"feeamount\" (number, 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, pwalletMain->cs_wallet);
|
|
LOCK(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.");
|
|
}
|
|
|
|
int minConfs = 0;
|
|
if (params.size() > 2)
|
|
{
|
|
minConfs = uni_get_int(params[2]);
|
|
}
|
|
|
|
uint32_t height = chainActive.Height();
|
|
|
|
if (sourceDest.which() == COptCCParams::ADDRTYPE_ID && !GetDestinationID(sourceDest).IsNull())
|
|
{
|
|
std::pair<CIdentityMapKey, CIdentityMapValue> keyAndIdentity;
|
|
if (!pwalletMain->GetIdentity(GetDestinationID(sourceDest), keyAndIdentity) ||
|
|
!keyAndIdentity.second.IsValid())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid identity or identity not in wallet");
|
|
}
|
|
if (keyAndIdentity.second.IsLocked(height + 1))
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot send currency from a locked identity");
|
|
}
|
|
}
|
|
|
|
CAmount feeAmount = DEFAULT_TRANSACTION_FEE;
|
|
if (params.size() > 3)
|
|
{
|
|
feeAmount = AmountFromValue(params[3]);
|
|
}
|
|
|
|
const UniValue &uniOutputs = params[1];
|
|
|
|
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 exportId = uni_get_bool(find_value(uniOutputs[i], "exportid"));
|
|
auto exportCurrency = uni_get_bool(find_value(uniOutputs[i], "exportcurrency"));
|
|
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")) || uni_get_bool(find_value(uniOutputs[i], "burnweight"));
|
|
bool burnWeight = uni_get_bool(find_value(uniOutputs[i], "burnweight"));
|
|
bool mintNew = uni_get_bool(find_value(uniOutputs[i], "mintnew"));
|
|
|
|
if (currencyStr.size() ||
|
|
convertToStr.size() ||
|
|
exportToStr.size() ||
|
|
feeCurrencyStr.size() ||
|
|
viaStr.size() ||
|
|
refundToStr.size() ||
|
|
preConvert ||
|
|
mintNew ||
|
|
exportId ||
|
|
exportCurrency ||
|
|
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;
|
|
bool isConversion = false;
|
|
|
|
if (viaStr != "")
|
|
{
|
|
secondCurrencyID = ValidateCurrencyName(viaStr, true, &secondCurrencyDef);
|
|
std::map<uint160, int32_t> viaIdxMap = secondCurrencyDef.GetCurrenciesMap();
|
|
if (secondCurrencyID.IsNull() ||
|
|
sourceCurrencyID.IsNull() ||
|
|
!secondCurrencyDef.IsFractional() ||
|
|
(!convertToCurrencyID.IsNull() &&
|
|
!burnCurrency &&
|
|
(secondCurrencyID == sourceCurrencyID ||
|
|
secondCurrencyID == convertToCurrencyID ||
|
|
sourceCurrencyID == convertToCurrencyID ||
|
|
!viaIdxMap.count(convertToCurrencyID))) ||
|
|
!(viaIdxMap.count(sourceCurrencyID) ||
|
|
sourceCurrencyID == secondCurrencyID ||
|
|
convertToStr.empty()))
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "To specify a fractional currency converter, \"currency\" and \"convertto\" must both be reserves of \"via\"");
|
|
}
|
|
if (mintNew || preConvert)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot combine reserve to reserve conversion with minting or preconversion");
|
|
}
|
|
if (convertToCurrencyID.IsNull())
|
|
{
|
|
convertToCurrencyDef = secondCurrencyDef;
|
|
convertToCurrencyID = convertToCurrencyDef.GetID();
|
|
secondCurrencyDef = CCurrencyDefinition();
|
|
secondCurrencyID = uint160();
|
|
}
|
|
else
|
|
{
|
|
CCurrencyDefinition tempDef = convertToCurrencyDef;
|
|
convertToCurrencyDef = secondCurrencyDef;
|
|
secondCurrencyDef = tempDef;
|
|
convertToCurrencyID = convertToCurrencyDef.GetID();
|
|
secondCurrencyID = secondCurrencyDef.GetID();
|
|
isConversion = true;
|
|
}
|
|
isVia = true;
|
|
}
|
|
else if (!convertToCurrencyID.IsNull())
|
|
{
|
|
isConversion = true;
|
|
}
|
|
|
|
// send a reserve transfer preconvert
|
|
uint32_t flags = CReserveTransfer::VALID;
|
|
if (burnCurrency)
|
|
{
|
|
if (convertToCurrencyDef.IsValid() && convertToCurrencyDef.IsFractional())
|
|
{
|
|
if (mintNew || isConversion ||
|
|
!(convertToCurrencyID == sourceCurrencyID || convertToCurrencyDef.GetCurrenciesMap().count(sourceCurrencyID)))
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot mint and burn currency in a single operation.");
|
|
}
|
|
if (convertToCurrencyID != sourceCurrencyID)
|
|
{
|
|
if (burnWeight)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Change weight while burning reserves not implemented.");
|
|
}
|
|
}
|
|
flags |= burnWeight ? CReserveTransfer::BURN_CHANGE_WEIGHT : CReserveTransfer::BURN_CHANGE_PRICE;
|
|
}
|
|
else
|
|
{
|
|
if (mintNew || isConversion ||
|
|
!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 |= burnWeight ? CReserveTransfer::BURN_CHANGE_WEIGHT : 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 = isConversion &&
|
|
convertToCurrencyDef.IsValid() &&
|
|
convertToCurrencyDef.IsFractional() &&
|
|
convertToCurrencyDef.GetCurrenciesMap().count(sourceCurrencyID);
|
|
fromFractional = isConversion &&
|
|
!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);
|
|
}
|
|
|
|
// 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 (convertToCurrencyDef.systemID == exportToCurrencyDef.SystemOrGatewayID())
|
|
{
|
|
exportToCurrencyDef = convertToCurrencyDef;
|
|
exportToCurrencyID = convertToCurrencyID;
|
|
}
|
|
else
|
|
{
|
|
exportToCurrencyID = explicitExportID;
|
|
}
|
|
|
|
if (exportToCurrencyDef.SystemOrGatewayID() == 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 &&
|
|
!(convertToCurrencyDef.IsValid() && convertToCurrencyDef.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())
|
|
{
|
|
CCurrencyValueMap validFeeCurrencies;
|
|
if (!destSystemDef.launchSystemID.IsNull() && destSystemDef.IsMultiCurrency())
|
|
{
|
|
validFeeCurrencies.valueMap[destSystemDef.launchSystemID] = 1;
|
|
}
|
|
|
|
CCurrencyDefinition tmpConverterDef;
|
|
if (!preConvert)
|
|
{
|
|
validFeeCurrencies.valueMap[destSystemID] = 1;
|
|
if (feeCurrencyID != destSystemID && convertToCurrencyID.IsNull())
|
|
{
|
|
tmpConverterDef =
|
|
exportToCurrencyDef.IsFractional() ?
|
|
exportToCurrencyDef :
|
|
(exportToCurrencyDef.GatewayConverterID().IsNull() &&
|
|
exportToCurrencyID == thisChain.launchSystemID && !thisChain.GatewayConverterID().IsNull() ?
|
|
ConnectedChains.GetCachedCurrency(thisChain.GatewayConverterID()) :
|
|
CCurrencyDefinition());
|
|
}
|
|
else
|
|
{
|
|
tmpConverterDef = convertToCurrencyDef;
|
|
}
|
|
}
|
|
|
|
if (tmpConverterDef.IsValid() &&
|
|
(tmpConverterDef.systemID == ASSETCHAINS_CHAINID || tmpConverterDef.systemID == destSystemID))
|
|
{
|
|
validFeeCurrencies.valueMap[tmpConverterDef.GetID()] = 1;
|
|
for (auto &oneCur : tmpConverterDef.currencies)
|
|
{
|
|
validFeeCurrencies.valueMap[oneCur] = 1;
|
|
}
|
|
}
|
|
if (!validFeeCurrencies.valueMap.count(feeCurrencyID))
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid fee currency specified");
|
|
}
|
|
}
|
|
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.IsGateway() &&
|
|
!(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 &&
|
|
!((convertToCurrencyDef.systemID == ASSETCHAINS_CHAINID &&
|
|
convertToCurrencyID != exportToCurrencyID &&
|
|
(exportToCurrencyDef.IsGateway() || exportToCurrencyDef.IsPBaaSChain()) &&
|
|
convertToCurrencyDef.IsFractional() &&
|
|
convertToCurrencyDef.GetCurrenciesMap().count(exportToCurrencyDef.systemID)) ||
|
|
(convertToCurrencyID == 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 if on the alternate system, then it must be 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().IsNull() &&
|
|
exportToCurrencyID == thisChain.launchSystemID && !thisChain.GatewayConverterID().IsNull() ?
|
|
thisChain.GatewayConverterID() :
|
|
(exportToCurrencyDef.GatewayConverterID().IsNull() ? uint160() : 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);
|
|
|
|
CTxDestination refundDestination = refundToStr.empty() ? CTxDestination() : 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.");
|
|
}
|
|
}
|
|
|
|
CTransferDestination dest;
|
|
if (destination.which() == COptCCParams::ADDRTYPE_INVALID)
|
|
{
|
|
if (destSystemDef.IsGateway())
|
|
{
|
|
// if we expect an ETH address, only accept that
|
|
if (exportSystemDef.proofProtocol == exportSystemDef.PROOF_ETHNOTARIZATION)
|
|
{
|
|
uint160 ethDestination = dest.DecodeEthDestination(destStr);
|
|
if (ethDestination.IsNull())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid Ethereum destination (null)");
|
|
}
|
|
dest = CTransferDestination(CTransferDestination::DEST_ETH, ::AsVector(ethDestination));
|
|
if (refundDestination.which() != COptCCParams::ADDRTYPE_INVALID)
|
|
{
|
|
dest.SetAuxDest(DestinationToTransferDestination(refundDestination), 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
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");
|
|
}
|
|
dest = CTransferDestination(CTransferDestination::FLAG_DEST_GATEWAY + CTransferDestination::DEST_RAW, rawDestBytes, destSystemID);
|
|
}
|
|
}
|
|
else if (exportSystemDef.IsValid() && exportSystemDef.proofProtocol == exportSystemDef.PROOF_ETHNOTARIZATION)
|
|
{
|
|
uint160 ethDestination = dest.DecodeEthDestination(destStr);
|
|
if (ethDestination.IsNull())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid Ethereum destination (null)");
|
|
}
|
|
dest = CTransferDestination(CTransferDestination::DEST_ETH, ::AsVector(ethDestination));
|
|
dest.type |= dest.FLAG_DEST_GATEWAY;
|
|
dest.gatewayID = exportSystemDef.GetID();
|
|
|
|
if (refundDestination.which() != COptCCParams::ADDRTYPE_INVALID)
|
|
{
|
|
dest.SetAuxDest(DestinationToTransferDestination(refundDestination), 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Specified destination must be valid.");
|
|
}
|
|
}
|
|
|
|
bool refundValid = refundDestination.which() != COptCCParams::ADDRTYPE_INVALID;
|
|
if (!refundValid)
|
|
{
|
|
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 && isConversion)
|
|
{
|
|
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
|
|
|
|
if (exportCurrency)
|
|
{
|
|
// validate no conflicts with currency export and create a destination of the currency
|
|
if (mintNew ||
|
|
isConversion ||
|
|
preConvert ||
|
|
exportId ||
|
|
sourceCurrencyID == ASSETCHAINS_CHAINID)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot mint or convert currency when exporting a currency definition cross-chain");
|
|
}
|
|
// confirm that we can export the intended currency
|
|
if (IsValidExportCurrency(exportToCurrencyDef, sourceCurrencyID, height + 1))
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Currency ( " + ConnectedChains.GetFriendlyCurrencyName(sourceCurrencyID) + ") already exported to destination system");
|
|
}
|
|
if (destSystemID != ASSETCHAINS_CHAINID ? !destSystemDef.IsMultiCurrency() : !exportToCurrencyDef.IsMultiCurrency())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot export currency to single currency system");
|
|
}
|
|
dest = CTransferDestination(CTransferDestination::DEST_REGISTERCURRENCY, ::AsVector(sourceCurrencyDef));
|
|
}
|
|
|
|
// if we should export the ID, make a full ID destination
|
|
if (!dest.IsValid())
|
|
{
|
|
dest = DestinationToTransferDestination(destination);
|
|
}
|
|
if (dest.TypeNoFlags() == dest.DEST_ID)
|
|
{
|
|
if (exportId)
|
|
{
|
|
// get and export the ID
|
|
CIdentity destIdentity = CIdentity::LookupIdentity(GetDestinationID(destination));
|
|
if (!destIdentity.IsValid())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot find identity to export (" + EncodeDestination(destination) + ")");
|
|
}
|
|
dest = CTransferDestination(CTransferDestination::DEST_FULLID, ::AsVector(destIdentity));
|
|
}
|
|
}
|
|
|
|
// check for potentially unknown currencies being sent across
|
|
// for now, we can only send 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;
|
|
const CCurrencyDefinition &offChainDef = (destSystemID != thisChainID ? destSystemDef : exportToCurrencyDef);
|
|
|
|
if (!offChainDef.IsValidTransferDestinationType(dest.TypeNoFlags()))
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER,
|
|
"Invalid destination address for export system " + offChainDef.name + " (" + dest.ToUniValue().write() + ")");
|
|
}
|
|
|
|
if (!GetNotarizationData(offChainID, cnd) || !cnd.IsConfirmed())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER,
|
|
"Cannot retrieve notarization data for export 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");
|
|
}
|
|
|
|
// TODO: HARDENING - ensure this gets into enforcement/protocol - check the target currency, if not system, for prelaunch & launch confirmed
|
|
|
|
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 = ValidExportCurrencies(offChainDef, height + 1);
|
|
|
|
if (exportCurrency)
|
|
{
|
|
CCurrencyValueMap canExport, cannotExport;
|
|
|
|
if (ConnectedChains.CurrencyExportStatus(
|
|
CCurrencyValueMap(std::vector<uint160>({sourceCurrencyID}), std::vector<int64_t>({1})),
|
|
ASSETCHAINS_CHAINID,
|
|
offChainID,
|
|
canExport,
|
|
cannotExport) &&
|
|
canExport.valueMap.size() &&
|
|
!cannotExport.valueMap.size())
|
|
{
|
|
if (validCurrencies.count(sourceCurrencyID))
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Unnecessary to export currency to system. Currency is already exported to destination network.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!CCurrencyDefinition::IsValidDefinitionImport(thisChain, offChainDef, sourceCurrencyDef.parent, height))
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot export currency to import system");
|
|
}
|
|
}
|
|
}
|
|
else 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) && convertToCurrencyDef.systemID != ASSETCHAINS_CHAINID)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Currency " + sourceCurrencyDef.name + " (" + EncodeDestination(CIdentityID(sourceCurrencyID)) + ") cannot be currency destination on specified system");
|
|
}
|
|
|
|
std::set<uint160> validFeeCurrencies;
|
|
validFeeCurrencies.insert(offChainID);
|
|
if (IsVerusActive() && offChainDef.IsPBaaSChain() && offChainDef.launchSystemID == ASSETCHAINS_CHAINID)
|
|
{
|
|
validFeeCurrencies.insert(ASSETCHAINS_CHAINID);
|
|
}
|
|
|
|
// calculate the price of the fee currency in the source and destination currencies
|
|
int64_t reversePriceInFeeCur = SATOSHIDEN;
|
|
|
|
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,
|
|
dest);
|
|
rt.nFees = rt.CalculateTransferFee();
|
|
|
|
std::vector<CTxDestination> dests = refundValid ? std::vector<CTxDestination>({pk.GetID(), refundDestination}) :
|
|
std::vector<CTxDestination>({pk.GetID()});
|
|
|
|
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 have pricing for converting fees, we add all convertible currencies to valid fee currencies
|
|
CPBaaSNotarization lastConfirmedNotarization = cnd.vtx[cnd.lastConfirmed].second;
|
|
CCurrencyValueMap feeConversionPrices;
|
|
CCoinbaseCurrencyState feePriceState;
|
|
|
|
bool sameChainConversion = convertToCurrencyDef.systemID == ASSETCHAINS_CHAINID;
|
|
|
|
uint160 converterID = convertToCurrencyID;
|
|
CCurrencyDefinition converterDef = convertToCurrencyDef;
|
|
|
|
if (!isVia && sourceCurrencyDef.IsFractional() && sourceCurrencyDef.GetCurrenciesMap().count(convertToCurrencyID))
|
|
{
|
|
flags |= CReserveTransfer::IMPORT_TO_SOURCE;
|
|
converterID = sourceCurrencyID;
|
|
converterDef = sourceCurrencyDef;
|
|
}
|
|
|
|
if (sameChainConversion)
|
|
{
|
|
// get the latest notarization for the converter on this chain
|
|
CChainNotarizationData localCND;
|
|
|
|
if (!GetNotarizationData(converterID, localCND) || !localCND.IsConfirmed())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid converter or converter not ready " + EncodeDestination(CIdentityID(converterID)));
|
|
}
|
|
|
|
feePriceState = localCND.vtx[localCND.lastConfirmed].second.currencyState;
|
|
}
|
|
else if (lastConfirmedNotarization.currencyStates.count(converterID))
|
|
{
|
|
feePriceState = lastConfirmedNotarization.currencyStates[converterID];
|
|
if (!feePriceState.IsValid())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid currency state for currency " + EncodeDestination(CIdentityID(converterID)));
|
|
}
|
|
}
|
|
|
|
if (feePriceState.IsValid())
|
|
{
|
|
if (sameChainConversion)
|
|
{
|
|
feeConversionPrices = feePriceState.TargetConversionPricesReverse(offChainID, true);
|
|
|
|
if (!feeConversionPrices.valueMap.count(offChainID))
|
|
{
|
|
feeConversionPrices.valueMap[offChainID] = SATOSHIDEN;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// get all currencies that may be converted through the converter to valid fees
|
|
validFeeCurrencies = BaseBridgeCurrencies(offChainDef, height + 1, true);
|
|
feeConversionPrices = feePriceState.TargetConversionPricesReverse(offChainID, true);
|
|
|
|
// TODO: HARDENING - confirm rules such that no currency could slip through here and have a 1:1
|
|
// conversion that could be exploited in some way
|
|
for (auto &oneCur : validFeeCurrencies)
|
|
{
|
|
// if we have a valid currency with no conversion, consider the conversion to be 1
|
|
if (!feeConversionPrices.valueMap.count(oneCur))
|
|
{
|
|
feeConversionPrices.valueMap[oneCur] = SATOSHIDEN;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (auto &oneCur : validFeeCurrencies)
|
|
{
|
|
feeConversionPrices.valueMap[oneCur] = SATOSHIDEN;
|
|
}
|
|
}
|
|
|
|
if (!feeConversionPrices.valueMap.count(feeCurrencyID))
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid fee currency for cross-chain transaction 1 " + ConnectedChains.GetFriendlyCurrencyName(feeCurrencyID));
|
|
}
|
|
|
|
// determine required fees
|
|
CAmount requiredFees;
|
|
if (feeConversionPrices.valueMap.count(feeCurrencyID))
|
|
{
|
|
reversePriceInFeeCur = feeConversionPrices.valueMap[feeCurrencyID];
|
|
}
|
|
if (exportCurrency)
|
|
{
|
|
// get source price for export and dest price for import to ensure we have enough fee currency
|
|
requiredFees =
|
|
CCurrencyState::ReserveToNativeRaw(offChainDef.GetCurrencyImportFee(sourceCurrencyDef.ChainOptions() & sourceCurrencyDef.OPTION_NFT_TOKEN),
|
|
reversePriceInFeeCur);
|
|
flags |= CReserveTransfer::CURRENCY_EXPORT;
|
|
}
|
|
else if (exportId)
|
|
{
|
|
requiredFees = CCurrencyState::ReserveToNativeRaw(offChainDef.IDImportFee(), reversePriceInFeeCur);
|
|
}
|
|
else
|
|
{
|
|
requiredFees = CCurrencyState::ReserveToNativeRaw(offChainDef.GetTransactionImportFee() << 1, reversePriceInFeeCur);
|
|
}
|
|
|
|
if (sameChainConversion)
|
|
{
|
|
// if same chain first, we need to ensure that we have enough fees on the other side or it will get
|
|
// refunded and fall short. add 20% buffer.
|
|
requiredFees += CCurrencyDefinition::CalculateRatioOfValue(requiredFees, SATOSHIDEN / 5);
|
|
|
|
// if we're converting and then sending, we don't need an initial fee, so all
|
|
// fees go into the final destination
|
|
|
|
// if we are sending a full ID, revert back to just ID and set the full ID bit to enable
|
|
// sending the full ID in the next leg without overhead in the first
|
|
if (dest.TypeNoFlags() == dest.DEST_FULLID)
|
|
{
|
|
dest = CTransferDestination(CTransferDestination::DEST_ID, ::AsVector(CIdentityID(GetDestinationID(destination))));
|
|
flags |= CReserveTransfer::IDENTITY_EXPORT;
|
|
}
|
|
else if (dest.TypeNoFlags() != dest.DEST_ID)
|
|
{
|
|
exportId = false;
|
|
}
|
|
|
|
if (dest.TypeNoFlags() == dest.DEST_REGISTERCURRENCY)
|
|
{
|
|
dest = destination.which() == COptCCParams::ADDRTYPE_INVALID ?
|
|
DestinationToTransferDestination(CIdentityID(CCurrencyDefinition(dest.destination).GetID())) :
|
|
DestinationToTransferDestination(destination);
|
|
}
|
|
|
|
dest.type |= dest.FLAG_DEST_GATEWAY;
|
|
dest.gatewayID = exportSystemDef.GetID();
|
|
CChainNotarizationData cnd;
|
|
if (!GetNotarizationData(converterID, 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(ASSETCHAINS_CHAINID) || (!currencyMap.count(feeCurrencyID) && feeCurrencyID != convertToCurrencyID))
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Local converter convert " + feeCurrencyDef.name + " to " + destSystemDef.name + ".");
|
|
}
|
|
|
|
dest.fees = requiredFees;
|
|
requiredFees = 0; // clear them, as they are used below as well when sending directly across
|
|
reversePriceInFeeCur = feePriceState.TargetConversionPricesReverse(ASSETCHAINS_CHAINID, true).valueMap[feeCurrencyID];
|
|
|
|
printf("%s: setting transfer fees in currency %s to %ld\n", __func__, EncodeDestination(CIdentityID(feeCurrencyID)).c_str(), dest.fees);
|
|
flags &= ~CReserveTransfer::CROSS_SYSTEM;
|
|
}
|
|
else
|
|
{
|
|
flags |= CReserveTransfer::CROSS_SYSTEM;
|
|
}
|
|
|
|
auto reserveMap = converterDef.GetCurrenciesMap();
|
|
if (feeCurrencyID != destSystemID &&
|
|
!(converterDef.IsFractional() && (feeCurrencyID == converterID || 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 = requiredFees + CCurrencyState::ReserveToNativeRaw(CReserveTransfer::CalculateTransferFee(dest, flags), reversePriceInFeeCur);
|
|
CReserveTransfer rt = CReserveTransfer(flags,
|
|
sourceCurrencyID,
|
|
sourceAmount,
|
|
feeCurrencyID,
|
|
fees,
|
|
convertToCurrencyID,
|
|
dest,
|
|
secondCurrencyID,
|
|
destSystemID);
|
|
|
|
std::vector<CTxDestination> dests = refundValid ? std::vector<CTxDestination>({pk.GetID(), refundDestination}) :
|
|
std::vector<CTxDestination>({pk.GetID()});
|
|
|
|
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
|
|
{
|
|
if (!validFeeCurrencies.count(feeCurrencyID))
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid fee currency for cross-chain transaction 2" + ConnectedChains.GetFriendlyCurrencyName(feeCurrencyID));
|
|
}
|
|
|
|
// determine required fees
|
|
CAmount requiredFees;
|
|
if (exportCurrency)
|
|
{
|
|
// get source price for export and dest price for import to ensure we have enough fee currency
|
|
requiredFees = CCurrencyState::ReserveToNativeRaw(offChainDef.GetCurrencyImportFee(sourceCurrencyDef.ChainOptions() & sourceCurrencyDef.OPTION_NFT_TOKEN), reversePriceInFeeCur);
|
|
flags |= CReserveTransfer::CURRENCY_EXPORT;
|
|
}
|
|
else if (exportId)
|
|
{
|
|
requiredFees = CCurrencyState::ReserveToNativeRaw(offChainDef.IDImportFee(), reversePriceInFeeCur);
|
|
}
|
|
else
|
|
{
|
|
requiredFees = CCurrencyState::ReserveToNativeRaw(offChainDef.GetTransactionImportFee(), reversePriceInFeeCur);
|
|
}
|
|
|
|
flags |= CReserveTransfer::CROSS_SYSTEM;
|
|
auto fees = requiredFees + CCurrencyState::ReserveToNativeRaw(CReserveTransfer::CalculateTransferFee(dest, flags), reversePriceInFeeCur);
|
|
CReserveTransfer rt = CReserveTransfer(flags,
|
|
sourceCurrencyID,
|
|
sourceAmount,
|
|
feeCurrencyID,
|
|
fees,
|
|
exportToCurrencyID,
|
|
dest,
|
|
secondCurrencyID,
|
|
destSystemID);
|
|
|
|
std::vector<CTxDestination> dests = refundValid ? std::vector<CTxDestination>({pk.GetID(), refundDestination}) :
|
|
std::vector<CTxDestination>({pk.GetID()});
|
|
|
|
oneOutput.nAmount = rt.TotalCurrencyOut().valueMap[ASSETCHAINS_CHAINID];
|
|
oneOutput.scriptPubKey = MakeMofNCCScript(CConditionObj<CReserveTransfer>(EVAL_RESERVE_TRANSFER, dests, 1, &rt));
|
|
}
|
|
}
|
|
else if (exportCurrency)
|
|
{
|
|
// invalid to export currency except off-chain
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot export " + sourceCurrencyDef.name + " without specifying \"exportto\"");
|
|
}
|
|
// 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 = refundValid ? std::vector<CTxDestination>({pk.GetID(), refundDestination}) :
|
|
std::vector<CTxDestination>({pk.GetID()});
|
|
|
|
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 && sourceCurrencyID == convertToCurrencyID)
|
|
{
|
|
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.TotalCurrencyOut().valueMap[ASSETCHAINS_CHAINID];
|
|
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.");
|
|
}
|
|
|
|
/*
|
|
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 + ".");
|
|
}
|
|
*/
|
|
|
|
CChainNotarizationData cnd;
|
|
if (!GetNotarizationData(pFractionalCurrency->GetID(), cnd) ||
|
|
!cnd.IsConfirmed())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot get notarization/pricing information for " + exportToCurrencyDef.name);
|
|
}
|
|
CCoinbaseCurrencyState &feePriceState = cnd.vtx[cnd.lastConfirmed].second.currencyState;
|
|
if (feeCurrencyID.IsNull())
|
|
{
|
|
feeCurrencyID = ASSETCHAINS_CHAINID;
|
|
}
|
|
CAmount reversePriceInFeeCur = feePriceState.TargetConversionPricesReverse(ASSETCHAINS_CHAINID, true).valueMap[feeCurrencyID];
|
|
|
|
// converting from reserve to a fractional of that reserve
|
|
auto dest = DestinationToTransferDestination(destination);
|
|
CAmount fees = CCurrencyState::ReserveToNativeRaw(CReserveTransfer::CalculateTransferFee(dest, flags), reversePriceInFeeCur);
|
|
|
|
CReserveTransfer rt = CReserveTransfer(flags,
|
|
sourceCurrencyID,
|
|
sourceAmount,
|
|
feeCurrencyID,
|
|
fees,
|
|
convertToCurrencyID,
|
|
dest,
|
|
secondCurrencyID);
|
|
|
|
std::vector<CTxDestination> dests = refundValid ? std::vector<CTxDestination>({pk.GetID(), refundDestination}) :
|
|
std::vector<CTxDestination>({pk.GetID()});
|
|
|
|
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
|
|
if (hasZSource && minConfs ==0)
|
|
{
|
|
minConfs = 1;
|
|
}
|
|
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,
|
|
minConfs,
|
|
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 || params.size() > 3)
|
|
{
|
|
throw runtime_error(
|
|
"getcurrencystate \"currencynameorid\" (\"n\") (\"connectedsystemid\")\n"
|
|
"\nReturns the currency state(s) on the blockchain for any specified currency, either with all changes on this chain or relative to another system.\n"
|
|
|
|
"\nArguments\n"
|
|
" \"currencynameorid\" (string) name or i-address of currency in question"
|
|
" \"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"
|
|
" (\"connectedchainid\") (string) optional\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", "\"currencynameorid\" (\"n\") (\"connectedchainid\")")
|
|
+ HelpExampleRpc("getcurrencystate", "\"currencynameorid\" (\"n\") (\"connectedchainid\")")
|
|
);
|
|
}
|
|
CheckPBaaSAPIsValid();
|
|
|
|
CCurrencyDefinition currencyToCheck;
|
|
|
|
std::string currencyStr = uni_get_str(params[0]);
|
|
if (currencyStr.empty() || ValidateCurrencyName(currencyStr, true, ¤cyToCheck).IsNull())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid currency specified");
|
|
}
|
|
uint160 currencyID = currencyToCheck.GetID();
|
|
|
|
LOCK(cs_main);
|
|
|
|
uint64_t lStart;
|
|
uint64_t startEnd[3] = {0};
|
|
|
|
lStart = startEnd[1] = startEnd[0] = chainActive.LastTip() ? chainActive.LastTip()->GetHeight() : 1;
|
|
|
|
if (params.size() > 1)
|
|
{
|
|
if (params[1].isStr())
|
|
{
|
|
Split(params[1].get_str(), startEnd, startEnd[0], 3);
|
|
}
|
|
else if (uni_get_int(params[1], -1) != -1)
|
|
{
|
|
lStart = startEnd[1] = startEnd[0] = uni_get_int(params[0], lStart);
|
|
}
|
|
}
|
|
|
|
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(currencyID, 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;
|
|
|
|
std::string networkIDName = EncodeDestination(CIdentityID(ASSETCHAINS_CHAINID));
|
|
|
|
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", networkIDName));
|
|
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, std::map<uint160, std::string> &requiredDefinitions, bool checkMempool)
|
|
{
|
|
CCurrencyDefinition newCurrency(uniObj);
|
|
if (find_value(uniObj, "launchsystemid").isNull() && newCurrency.GetID() != ASSETCHAINS_CHAINID && newCurrency.parent != ASSETCHAINS_CHAINID)
|
|
{
|
|
newCurrency.launchSystemID = systemID;
|
|
}
|
|
|
|
if (!newCurrency.IsValid())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid currency definition. see help.");
|
|
}
|
|
|
|
CCurrencyDefinition checkDef;
|
|
int32_t defHeight;
|
|
if (GetCurrencyDefinition(newCurrency.GetID(), checkDef, &defHeight, checkMempool) &&
|
|
defHeight < height &&
|
|
!(newCurrency.GetID() == ASSETCHAINS_CHAINID &&
|
|
!defHeight))
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, newCurrency.name + " chain already defined. see help.");
|
|
}
|
|
|
|
bool currentChainDefinition = newCurrency.GetID() == ASSETCHAINS_CHAINID && _IsVerusActive();
|
|
if (currentChainDefinition)
|
|
{
|
|
newCurrency = checkDef;
|
|
}
|
|
|
|
if (newCurrency.parent.IsNull() && !currentChainDefinition)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, newCurrency.name + " invalid chain name.");
|
|
}
|
|
|
|
for (auto &oneID : newCurrency.preAllocation)
|
|
{
|
|
if (!(newCurrency.IsPBaaSChain() && oneID.first.IsNull()) && !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() || newCurrency.IsGateway())
|
|
{
|
|
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 ((newCurrency.nativeCurrencyID.TypeNoFlags() == newCurrency.nativeCurrencyID.DEST_ETH && !newCurrency.IsGateway()) ||
|
|
newCurrency.nativeCurrencyID.TypeNoFlags() == newCurrency.nativeCurrencyID.DEST_ETHNFT)
|
|
{
|
|
if (newCurrency.IsPBaaSChain() ||
|
|
!newCurrency.IsToken() ||
|
|
newCurrency.IsFractional())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "mapped currency must be a token with no initial supply and cannot be otherwise functional");
|
|
}
|
|
if (newCurrency.proofProtocol != newCurrency.PROOF_ETHNOTARIZATION)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Ethereum mapped currency must have \"proofprotocol\":%d", (int)newCurrency.PROOF_ETHNOTARIZATION));
|
|
}
|
|
bool nonZeroSupply = (newCurrency.conversions.size() && !newCurrency.maxPreconvert.size()) || newCurrency.GetTotalPreallocation();
|
|
for (auto oneVal : newCurrency.maxPreconvert)
|
|
{
|
|
if (oneVal)
|
|
{
|
|
nonZeroSupply = true;
|
|
}
|
|
}
|
|
if (nonZeroSupply)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Mapped currency definition requires zero initial supply and no possible conversions");
|
|
}
|
|
CCurrencyDefinition systemCurrency = ConnectedChains.GetCachedCurrency(newCurrency.systemID);
|
|
if (systemCurrency.IsValid() &&
|
|
(!systemCurrency.IsGateway() ||
|
|
systemCurrency.launchSystemID != ASSETCHAINS_CHAINID ||
|
|
systemCurrency.proofProtocol != systemCurrency.PROOF_ETHNOTARIZATION))
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Ethereum protocol networks are the only mapped currency type currently supported");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// if this is a token or gateway 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();
|
|
}
|
|
|
|
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() + DEFAULT_PRE_BLOSSOM_TX_EXPIRY_DELTA; // 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.IsGateway() && 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.IsGateway())
|
|
{
|
|
newCurrency.options |= newCurrency.OPTION_PBAAS;
|
|
}
|
|
}
|
|
|
|
UniValue currencyNames = find_value(uniObj, "currencies");
|
|
std::set<uint160> currencySet;
|
|
|
|
for (int i = 0; i < currencyNames.size(); i++)
|
|
{
|
|
std::string oneCurName = uni_get_str(currencyNames[i]);
|
|
if (!oneCurName.size())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid (null) currency name");
|
|
}
|
|
uint160 oneCurID = ValidateCurrencyName(oneCurName, true);
|
|
if (oneCurID.IsNull())
|
|
{
|
|
if ((oneCurID = ValidateCurrencyName(oneCurName)).IsNull())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid currency " + oneCurName + " in \"currencies\" 1");
|
|
}
|
|
if (requiredDefinitions.count(oneCurID))
|
|
{
|
|
oneCurName = requiredDefinitions[oneCurID];
|
|
}
|
|
else
|
|
{
|
|
printf("%s: currency not found: name(%s), ID(%s)\nstored names and IDs:\n", __func__, oneCurName.c_str(), EncodeDestination(CIdentityID(oneCurID)).c_str());
|
|
for (auto &onePair : requiredDefinitions)
|
|
{
|
|
printf("ID(%s), name(%s)\n", EncodeDestination(CIdentityID(onePair.first)).c_str(), onePair.second.c_str());
|
|
}
|
|
}
|
|
// if the new currency is a PBaaS or gateway converter, and this is the PBaaS chain or gateway,
|
|
// it will be created in this tx as well
|
|
if (newCurrency.IsGatewayConverter() && oneCurID == newCurrency.parent)
|
|
{
|
|
currencySet.insert(oneCurID);
|
|
continue;
|
|
}
|
|
if (!(newCurrency.IsGatewayConverter() && systemID == ASSETCHAINS_CHAINID && newCurrency.parent != ASSETCHAINS_CHAINID))
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid currency " + oneCurName + " in \"currencies\"");
|
|
}
|
|
|
|
uint160 parent;
|
|
std::string cleanName = CleanName(oneCurName + "@", parent);
|
|
if (cleanName.empty())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot decode currency name " + oneCurName + " in \"currencies\"");
|
|
}
|
|
|
|
// the parent of the currency cannot require a new definition
|
|
if (oneCurID != newCurrency.parent)
|
|
{
|
|
if (parent != newCurrency.parent || cleanName == "")
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Unable to auto-create currency " + oneCurName + " in \"currencies\"");
|
|
}
|
|
requiredDefinitions[oneCurID] = oneCurName;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
requiredDefinitions.erase(oneCurID);
|
|
}
|
|
currencySet.insert(oneCurID);
|
|
}
|
|
|
|
if (currencyNames.size() != currencySet.size())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Duplicate currency specified in \"currencies\"");
|
|
}
|
|
|
|
// 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 ¤cy : newCurrency.currencies)
|
|
{
|
|
currencySet.insert(currency);
|
|
if (currency == ASSETCHAINS_CHAINID)
|
|
{
|
|
hasCoreReserve = true;
|
|
continue;
|
|
}
|
|
|
|
if (newCurrency.systemID == currency)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (newCurrency.parent == currency)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
reserveCurrencies.push_back(CCurrencyDefinition());
|
|
|
|
bool currencyIsRegistered = false;
|
|
if (!requiredDefinitions.count(currency))
|
|
{
|
|
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 (!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() > 14)
|
|
{
|
|
throw runtime_error(
|
|
"definecurrency '{\"name\": \"coinortokenname\", ..., \"nodes\":[{\"networkaddress\":\"identity\"},..]}'\\\n"
|
|
" '({\"name\": \"fractionalgatewayname\", ..., })' ({\"name\": \"reserveonename\", ..., }) ...\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 = RESERVED\n"
|
|
" 0x100 = IS_PBAAS_CHAIN\n"
|
|
"\n"
|
|
" \"name\" : \"xxxx\", (string, required) name of existing identity with no active or pending blockchain\n"
|
|
" \"idregistrationfees\" : \"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();
|
|
|
|
std::map<uint160, std::string> requiredCurrencyDefinitions;
|
|
CCurrencyDefinition newChain(ValidateNewUnivalueCurrencyDefinition(params[0], height, ASSETCHAINS_CHAINID, requiredCurrencyDefinitions));
|
|
|
|
if (requiredCurrencyDefinitions.size())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "currency definition has invalid or undefined currency references");
|
|
}
|
|
|
|
CCurrencyDefinition parentCurrency;
|
|
|
|
if (!newChain.parent.IsNull())
|
|
{
|
|
parentCurrency = ConnectedChains.GetCachedCurrency(newChain.parent);
|
|
}
|
|
else
|
|
{
|
|
parentCurrency = ConnectedChains.ThisChain();
|
|
}
|
|
|
|
bool invalidUnlessNFT = false;
|
|
if (newChain.parent != thisChainID &&
|
|
!(isVerusActive && newChain.GetID() == ASSETCHAINS_CHAINID && newChain.parent.IsNull()) &&
|
|
!(parentCurrency.IsGateway() && !parentCurrency.IsNameController() && parentCurrency.launchSystemID == ASSETCHAINS_CHAINID))
|
|
{
|
|
invalidUnlessNFT = true;
|
|
if (!newChain.IsNFTToken() )
|
|
{
|
|
// 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 currency relative to a parent that is not a valid gateway or 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.systemID != ASSETCHAINS_CHAINID)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot launch any currency or tokenized ID control unless the root identity is rooted on this system");
|
|
}
|
|
|
|
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::map<uint160, std::map<std::string, UniValue>> newReserveDefinitions;
|
|
std::map<uint160, CCurrencyDefinition> newReserveCurrencies;
|
|
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_FRACTIONAL +
|
|
CCurrencyDefinition::OPTION_TOKEN +
|
|
CCurrencyDefinition::OPTION_GATEWAY_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))));
|
|
gatewayConverterMap.insert(std::make_pair("gateway", EncodeDestination(CIdentityID(newChain.GetID()))));
|
|
|
|
// if this is a gateway, the converter runs on the launching chain by default
|
|
// 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
|
|
if (newChain.IsGateway())
|
|
{
|
|
CEthGateway gatewayCheck;
|
|
if (newChain.GetID() != gatewayCheck.GatewayID())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Ethereum is the only gateway supported at this time");
|
|
}
|
|
|
|
if (uni_get_int(gatewayConverterMap["startblock"]) < (int32_t)(height + DEFAULT_PRE_BLOSSOM_TX_EXPIRY_DELTA))
|
|
{
|
|
gatewayConverterMap["startblock"] = (int32_t)(height + DEFAULT_PRE_BLOSSOM_TX_EXPIRY_DELTA);
|
|
}
|
|
// gateways start already running
|
|
newChain.startBlock = 0;
|
|
}
|
|
else
|
|
{
|
|
gatewayConverterMap["startblock"] = newChain.startBlock;
|
|
}
|
|
|
|
gatewayConverterMap["gatewayconverterissuance"] = ValueFromAmount(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_GATEWAY_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
|
|
std::map<uint160, std::string> autoCurrencyNames;
|
|
newGatewayConverter = ValidateNewUnivalueCurrencyDefinition(newCurUni, height, newChain.systemID, autoCurrencyNames);
|
|
|
|
if (autoCurrencyNames.size())
|
|
{
|
|
if (!newChain.IsGateway())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Only a gateway definition may also define currencies to use as bridge converter reserves");
|
|
}
|
|
|
|
// any required currency definitions will include their text name and have a parent of the gateway
|
|
// to qualify for auto-definition. we also need to parse the remaining parameters
|
|
// and verify that they are only needed definitions
|
|
|
|
for (auto &oneCurrencyName : autoCurrencyNames)
|
|
{
|
|
// for each name, we will create a definition that has a systemID of the current system and parent of the gateway
|
|
std::map<std::string, UniValue> oneCurEntry;
|
|
oneCurEntry["name"] = oneCurrencyName.second;
|
|
oneCurEntry["parent"] = EncodeDestination(CIdentityID(newChainID));
|
|
oneCurEntry["systemid"] = EncodeDestination(CIdentityID(newChainID));
|
|
oneCurEntry["launchsystemid"] = EncodeDestination(CIdentityID(newChainID));
|
|
oneCurEntry["nativecurrencyid"] = DestinationToTransferDestination(CIdentityID(oneCurrencyName.first)).ToUniValue();
|
|
oneCurEntry["options"] = CCurrencyDefinition::OPTION_TOKEN;
|
|
oneCurEntry["proofprotocol"] = newChain.proofProtocol;
|
|
oneCurEntry["notarizationprotocol"] = CCurrencyDefinition::NOTARIZATION_AUTO;
|
|
|
|
newReserveDefinitions[oneCurrencyName.first] = oneCurEntry;
|
|
}
|
|
|
|
if (params.size() > 2)
|
|
{
|
|
for (int paramIdx = 2; paramIdx < params.size(); paramIdx++)
|
|
{
|
|
uint160 reserveParentID;
|
|
std::string checkName = CleanName(uni_get_str(find_value(params[paramIdx], "name")), reserveParentID);
|
|
if (reserveParentID != newChainID && reserveParentID != ASSETCHAINS_CHAINID)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Only reserves on the gateway may be defined as additional currency definition parameters");
|
|
}
|
|
reserveParentID = newChainID;
|
|
uint160 autoReserveID = CIdentity::GetID(checkName, reserveParentID);
|
|
if (!newReserveDefinitions.count(autoReserveID))
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Currency " + checkName + " is not a required reserve definition");
|
|
}
|
|
auto curKeys = params[paramIdx].getKeys();
|
|
auto curValues = params[paramIdx].getValues();
|
|
for (int i = 0; i < curKeys.size(); i++)
|
|
{
|
|
newReserveDefinitions[autoReserveID][curKeys[i]] = curValues[i];
|
|
}
|
|
}
|
|
}
|
|
// now, loop through all new currency definitions and define them
|
|
// disallow: pre-allocation, prelaunch discount, future start block, fractional currency, any form of launch
|
|
for (auto &oneDefinition : newReserveDefinitions)
|
|
{
|
|
UniValue oneDefUni(UniValue::VOBJ);
|
|
for (auto &oneProp : oneDefinition.second)
|
|
{
|
|
oneDefUni.pushKV(oneProp.first, oneProp.second);
|
|
}
|
|
CCurrencyDefinition newReserveDef(oneDefUni);
|
|
if (!(newReserveDef.IsValid() &&
|
|
newReserveDef.IsToken() &&
|
|
!newReserveDef.IsFractional() &&
|
|
newReserveDef.systemID == newChainID &&
|
|
newReserveDef.parent == newChainID &&
|
|
newReserveDef.nativeCurrencyID.IsValid() &&
|
|
newReserveDef.launchSystemID == newChainID &&
|
|
newReserveDef.startBlock == 0 &&
|
|
newReserveDef.endBlock == 0 &&
|
|
newReserveDef.proofProtocol == newChain.proofProtocol))
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid auto-reserve currency definition");
|
|
}
|
|
if (newChain.IsGateway())
|
|
{
|
|
newReserveDef.gatewayID = newChainID;
|
|
}
|
|
newReserveCurrencies[oneDefinition.first] = newReserveDef;
|
|
}
|
|
}
|
|
else if (params.size() > 2)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Too many parameters. Please see help.");
|
|
}
|
|
|
|
// 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 (params.size() > 1)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Too many parameters. Please see help.");
|
|
}
|
|
}
|
|
else if (!newChain.gatewayConverterName.empty() || params.size() > 1)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "A second currency definition is only supported as a converter currency for a PBaaS chain or gateway");
|
|
}
|
|
|
|
// 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 or gateway):
|
|
// 6. Converter currency ID with active currency
|
|
// 7. Converter currency definition for start on the new PBaaS chain, pre-launching from current chain
|
|
// 3. Converter notarization thread
|
|
// 4. Converter export thread - working to deprecate
|
|
// 8. Converter 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.UpgradeVersion(height);
|
|
launchIdentity.ActivateCurrency();
|
|
if (newChain.IsNFTToken())
|
|
{
|
|
launchIdentity.ActivateTokenizedControl();
|
|
}
|
|
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);
|
|
|
|
CAmount mainImportFee = ConnectedChains.ThisChain().LaunchFeeImportShare(newChain.options);
|
|
CCurrencyValueMap mainImportFees(std::vector<uint160>({thisChainID}), std::vector<CAmount>({mainImportFee}));
|
|
CAmount converterImportFee = 0;
|
|
CAmount newReserveImportFees = 0;
|
|
CCurrencyValueMap converterImportFees;
|
|
|
|
CCoinbaseCurrencyState newCurrencyState;
|
|
uint32_t lastImportHeight = newChain.IsPBaaSChain() || newChain.IsGateway() ? 1 : height;
|
|
|
|
// if it's a mapped currency, we don't need to add anything but the definition with no launch period
|
|
bool isMappedCurrency = newChain.IsToken() && !newChain.IsGateway() && !newChain.IsPBaaSChain() && !newChain.IsGatewayConverter() &&
|
|
newChain.nativeCurrencyID.IsValid() && parentCurrency.IsGateway() && newChain.systemID != ASSETCHAINS_CHAINID;
|
|
|
|
// 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");
|
|
}
|
|
|
|
CCrossChainImport cci = CCrossChainImport(newChain.SystemOrGatewayID(),
|
|
lastImportHeight,
|
|
newChainID,
|
|
CCurrencyValueMap(),
|
|
CCurrencyValueMap());
|
|
cci.SetSameChain(newChain.systemID == ASSETCHAINS_CHAINID);
|
|
cci.SetDefinitionImport(true);
|
|
if (newChainID == ASSETCHAINS_CHAINID || newChain.IsGateway())
|
|
{
|
|
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
|
|
newCurrencyState = ConnectedChains.GetCurrencyState(newChain, chainActive.Height());
|
|
|
|
newCurrencyState.SetPrelaunch();
|
|
|
|
CPBaaSNotarization pbn = CPBaaSNotarization(newChainID,
|
|
newCurrencyState,
|
|
height,
|
|
CUTXORef(),
|
|
0);
|
|
|
|
pbn.SetSameChain();
|
|
pbn.SetDefinitionNotarization();
|
|
pbn.nodes = startupNodes;
|
|
|
|
if (newCurrencyState.GetID() == ASSETCHAINS_CHAINID || newChain.IsGateway() || isMappedCurrency)
|
|
{
|
|
newChain.startBlock = 1;
|
|
newCurrencyState.SetPrelaunch(false);
|
|
newCurrencyState.SetLaunchConfirmed();
|
|
newCurrencyState.SetLaunchCompleteMarker();
|
|
pbn.currencyState = newCurrencyState;
|
|
pbn.SetPreLaunch(false);
|
|
pbn.SetLaunchCleared();
|
|
pbn.SetLaunchConfirmed();
|
|
pbn.SetLaunchComplete();
|
|
}
|
|
else
|
|
{
|
|
pbn.SetPreLaunch();
|
|
}
|
|
|
|
// 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))});
|
|
|
|
CCrossChainExport ccx = CCrossChainExport(thisChainID,
|
|
0,
|
|
height,
|
|
(newChain.IsPBaaSChain() || newChain.IsGateway() || newChain.IsGatewayConverter()) ?
|
|
newChain.SystemOrGatewayID() :
|
|
ASSETCHAINS_CHAINID,
|
|
newChainID,
|
|
0,
|
|
mainImportFees,
|
|
mainImportFees,
|
|
uint256());
|
|
ccx.SetChainDefinition();
|
|
if (newCurrencyState.GetID() == ASSETCHAINS_CHAINID || newChain.IsGateway() || isMappedCurrency)
|
|
{
|
|
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.GetID()});
|
|
|
|
tb.AddTransparentOutput(MakeMofNCCScript(CConditionObj<CReserveTransfer>(EVAL_RESERVE_TRANSFER, dests, 1, &rt)),
|
|
newChain.currencies[i] == thisChainID ? contribution + fee : fee);
|
|
}
|
|
}
|
|
}
|
|
|
|
CAmount totalLaunchFee = ConnectedChains.ThisChain().GetCurrencyRegistrationFee(newChain.options);
|
|
CAmount notaryFeeShare = 0;
|
|
|
|
// now, setup the gateway converter currency, if appropriate
|
|
if ((newChain.IsPBaaSChain() || newChain.IsGateway()))
|
|
{
|
|
if (newGatewayConverter.IsValid())
|
|
{
|
|
cp = CCinit(&CC, EVAL_CURRENCY_DEFINITION);
|
|
|
|
// create any new reserve currencies needed that are mapped to the gateway
|
|
// for now, there is only an import fee for these currencies, since they do not launch
|
|
for (auto oneNewReserve : newReserveCurrencies)
|
|
{
|
|
// define one currency
|
|
// and add one import currency fee for each new reserve
|
|
newReserveImportFees += ConnectedChains.ThisChain().currencyImportFee;
|
|
std::vector<CTxDestination> dests({CPubKey(ParseHex(CC.CChexstr))});
|
|
tb.AddTransparentOutput(MakeMofNCCScript(CConditionObj<CCurrencyDefinition>(EVAL_CURRENCY_DEFINITION, dests, 1, &oneNewReserve.second)),
|
|
CCurrencyDefinition::DEFAULT_OUTPUT_VALUE);
|
|
}
|
|
|
|
if (newChain.IsGateway() &&
|
|
newChain.notaries.size() &&
|
|
(newChain.notarizationProtocol == newChain.NOTARIZATION_AUTO ||
|
|
newChain.notarizationProtocol == newChain.NOTARIZATION_NOTARY_CONFIRM))
|
|
{
|
|
// notaries all get an even share of 10% of a currency launch fee to use for notarizing
|
|
notaryFeeShare = ConnectedChains.ThisChain().GetCurrencyRegistrationFee(newChain.options) / 100;
|
|
totalLaunchFee -= notaryFeeShare;
|
|
CAmount oneNotaryShare = notaryFeeShare / newChain.notaries.size();
|
|
CAmount notaryModExtra = notaryFeeShare % newChain.notaries.size();
|
|
for (auto &oneNotary : newChain.notaries)
|
|
{
|
|
tb.AddTransparentOutput(CIdentityID(oneNotary), notaryModExtra ? notaryModExtra--, oneNotaryShare + 1 : oneNotaryShare);
|
|
}
|
|
}
|
|
|
|
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 ||
|
|
newGatewayConverter.proofProtocol == newGatewayConverter.PROOF_ETHNOTARIZATION)
|
|
{
|
|
dests = std::vector<CTxDestination>({pk.GetID()});
|
|
}
|
|
else
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "None or invalid 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.GetID()});
|
|
|
|
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
|
|
if (converterImportFee)
|
|
{
|
|
totalLaunchFee += ConnectedChains.ThisChain().GetCurrencyRegistrationFee(newGatewayConverter.options);
|
|
}
|
|
totalLaunchFee += newReserveImportFees;
|
|
|
|
CAmount totalLaunchExportFee = totalLaunchFee - (mainImportFee + converterImportFee);
|
|
if (newCurrencyState.IsValid() && newCurrencyState.GetID() != ASSETCHAINS_CHAINID && !isMappedCurrency)
|
|
{
|
|
cp = CCinit(&CC, EVAL_RESERVE_DEPOSIT);
|
|
pk = CPubKey(ParseHex(CC.CChexstr));
|
|
dests = std::vector<CTxDestination>({pk});
|
|
|
|
CReserveDeposit launchDeposit = CReserveDeposit(newChainID, CCurrencyValueMap());
|
|
if (mainImportFee)
|
|
{
|
|
launchDeposit.reserveValues.valueMap[ASSETCHAINS_CHAINID] = mainImportFee;
|
|
}
|
|
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());
|
|
if (converterImportFee)
|
|
{
|
|
launchDeposit.reserveValues.valueMap[ASSETCHAINS_CHAINID] = converterImportFee;
|
|
}
|
|
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);
|
|
|
|
for (COutput &out : vCoins)
|
|
{
|
|
std::vector<CTxDestination> addresses;
|
|
int nRequired;
|
|
bool canSign, canSpend;
|
|
CTxDestination address;
|
|
txnouttype txType;
|
|
if (!ExtractDestinations(out.tx->vout[out.i].scriptPubKey, txType, addresses, nRequired, pwalletMain, &canSign, &canSpend))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// if we have more address destinations than just this address and have specified from a single ID only,
|
|
// the condition must be such that the ID itself can spend, even if this wallet cannot due to a multisig
|
|
// ID. if the ID cannot spend, even given a valid multisig ID, then to select this as a source without
|
|
// an explicit, multisig match would cause potentially unwanted sourcing of funds. a spend just to this ID
|
|
// is fine.
|
|
|
|
COptCCParams p, m;
|
|
// if we can't spend and can only sign,
|
|
// ensure that this output is spendable by just this ID as a 1 of n and 1 of n at the master
|
|
// smart transaction level as well
|
|
if (!canSpend &&
|
|
(!canSign ||
|
|
!(out.tx->vout[out.i].scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
(p.version < COptCCParams::VERSION_V3 ||
|
|
(p.vData.size() &&
|
|
(m = COptCCParams(p.vData.back())).IsValid() &&
|
|
(m.m == 1 || m.m == 0))) &&
|
|
p.m == 1)))
|
|
{
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
out.fSpendable = true; // this may not really be spendable, but set it if its the correct ID source and can sign
|
|
}
|
|
}
|
|
|
|
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(true);
|
|
|
|
CTransaction retTx;
|
|
bool partialSig = !builtTxResult.IsTx() && IsHex(builtTxResult.GetError()) && DecodeHexTx(retTx, builtTxResult.GetError());
|
|
|
|
if (!builtTxResult.IsTx() && !partialSig)
|
|
{
|
|
throw JSONRPCError(RPC_TRANSACTION_ERROR, newChain.name + ": " + builtTxResult.GetError());
|
|
}
|
|
else if (!partialSig)
|
|
{
|
|
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() > 5))
|
|
{
|
|
throw runtime_error(
|
|
"registernamecommitment \"name\" \"controladdress\" (\"referralidentity\") (\"parentnameorid\") (\"sourceoffunds\")\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"
|
|
"\"parentnameorid-pbaasonly\" (currency, optional)friendly name or currency i-address, which will be the parent of this ID and dictate issuance rules & pricing\n"
|
|
"\"sourceoffunds\" (addressorid, optional) optional address to use for source of funds. if not specified, transparent wildcard \"*\" is used\n\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();
|
|
|
|
// create the transaction with native coin as input
|
|
LOCK2(cs_main, pwalletMain->cs_wallet);
|
|
|
|
uint32_t height = chainActive.Height();
|
|
bool isPBaaS = CConstVerusSolutionVector::GetVersionByHeight(height) >= CActivationHeight::ACTIVATE_PBAAS;
|
|
|
|
CCurrencyDefinition parentCurrency = ConnectedChains.ThisChain();
|
|
uint160 parentID = parentCurrency.GetID();
|
|
|
|
if (params.size() > 3 && !uni_get_str(params[3]).empty())
|
|
{
|
|
if (!isPBaaS)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot issue identities from specified parent currencies until after PBaaS activates");
|
|
}
|
|
parentID = ValidateCurrencyName(uni_get_str(params[3]), true, &parentCurrency);
|
|
|
|
if (!parentCurrency.IsValid())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parent currency");
|
|
}
|
|
}
|
|
|
|
std::string sourceAddress;
|
|
CTxDestination sourceDest;
|
|
|
|
bool wildCardTransparentAddress = false;
|
|
bool wildCardRAddress = false;
|
|
bool wildCardiAddress = false;
|
|
bool wildCardAddress = false;
|
|
|
|
libzcash::PaymentAddress zaddressSource;
|
|
libzcash::SaplingExpandedSpendingKey expsk;
|
|
uint256 sourceOvk;
|
|
bool hasZSource = false;
|
|
|
|
if (params.size() > 4)
|
|
{
|
|
sourceAddress = uni_get_str(params[4]);
|
|
|
|
wildCardTransparentAddress = sourceAddress == "*";
|
|
wildCardRAddress = sourceAddress == "R*";
|
|
wildCardiAddress = sourceAddress == "i*";
|
|
wildCardAddress = wildCardTransparentAddress || wildCardRAddress || wildCardiAddress;
|
|
hasZSource = !wildCardAddress && pwalletMain->GetAndValidateSaplingZAddress(sourceAddress, zaddressSource);
|
|
|
|
// 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)
|
|
{
|
|
sourceAddress = EncodePaymentAddress(zaddressSource);
|
|
// We don't need to lock on the wallet as spending key related methods are thread-safe
|
|
if (!boost::apply_visitor(HaveSpendingKeyForPaymentAddress(pwalletMain), zaddressSource)) {
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid from address, no spending key found for zaddr");
|
|
}
|
|
|
|
auto spendingkey_ = boost::apply_visitor(GetSpendingKeyForPaymentAddress(pwalletMain), zaddressSource).get();
|
|
auto sk = boost::get<libzcash::SaplingExtendedSpendingKey>(spendingkey_);
|
|
expsk = sk.expsk;
|
|
sourceOvk = expsk.full_viewing_key().ovk;
|
|
}
|
|
|
|
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 we are registering an identity through a gateway, we can and should redirect fee payment through its
|
|
// fee converter currency
|
|
|
|
// TODO: PBAAS - enable PBaaS chains to issue IDs through their gateway converter IDs as gateways can
|
|
|
|
CCurrencyDefinition issuingCurrency = parentCurrency;
|
|
uint160 issuerID = parentID;
|
|
if (!parentCurrency.IsNameController() && parentCurrency.launchSystemID == ASSETCHAINS_CHAINID && !parentCurrency.GatewayConverterID().IsNull())
|
|
{
|
|
issuingCurrency = ConnectedChains.GetCachedCurrency(parentCurrency.GatewayConverterID());
|
|
if (!issuingCurrency.IsValid() || issuingCurrency.systemID != ASSETCHAINS_CHAINID)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid issuing currency for this network");
|
|
}
|
|
}
|
|
else if (parentCurrency.systemID != ASSETCHAINS_CHAINID)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parent currency for this network");
|
|
}
|
|
|
|
std::string name = CleanName(uni_get_str(params[0]), parentID, true, true);
|
|
if (parentID != parentCurrency.GetID())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid implied parent currency");
|
|
}
|
|
|
|
uint160 idID = GetDestinationID(DecodeDestination(name + "@"));
|
|
if (idID == ASSETCHAINS_CHAINID && IsVerusActive())
|
|
{
|
|
name = VERUS_CHAINNAME;
|
|
parentID.SetNull();
|
|
}
|
|
|
|
// if either we have an invalid name or an implied parent, that is not valid
|
|
if (!(idID == VERUS_CHAINID && IsVerusActive() && parentID.IsNull()) &&
|
|
(name == "" || parentID != parentCurrency.GetID() || 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");
|
|
}
|
|
|
|
CIdentityID referrer;
|
|
if (params.size() > 2 && !uni_get_str(params[2]).empty())
|
|
{
|
|
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 != parentID)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Referrals must be from an identity of the same parent");
|
|
}
|
|
}
|
|
|
|
CNameReservation nameRes;
|
|
CAdvancedNameReservation advNameRes;
|
|
|
|
if (isPBaaS && !parentID.IsNull())
|
|
{
|
|
advNameRes = CAdvancedNameReservation(name, parentID, referrer, GetRandHash());
|
|
}
|
|
else
|
|
{
|
|
nameRes = CNameReservation(name, referrer, GetRandHash());
|
|
}
|
|
|
|
if (CIdentity::LookupIdentity(CIdentity::GetID(name, parentID)).IsValid())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Identity already exists.");
|
|
}
|
|
|
|
if (params.size() <= 4)
|
|
{
|
|
wildCardTransparentAddress = true;
|
|
}
|
|
|
|
bool success = false;
|
|
std::vector<CRecipient> newInputs;
|
|
CTxDestination changeDest;
|
|
|
|
std::vector<CTxDestination> dests({dest});
|
|
int requiredSigs = 1;
|
|
|
|
if (parentCurrency.IDRequiresPermission())
|
|
{
|
|
dests.push_back(CIdentityID(parentID));
|
|
requiredSigs = 2;
|
|
}
|
|
else if (parentCurrency.IDReferralRequired())
|
|
{
|
|
dests.push_back(CIdentityID(referrer));
|
|
requiredSigs = 2;
|
|
}
|
|
|
|
CCommitmentHash commitment(advNameRes.IsValid() ? advNameRes.GetCommitment() : nameRes.GetCommitment());
|
|
CConditionObj<CCommitmentHash> condObj(EVAL_IDENTITY_COMMITMENT, dests, requiredSigs, &commitment);
|
|
std::vector<CRecipient> outputs = std::vector<CRecipient>({{MakeMofNCCScript(condObj, &dest), CCommitmentHash::DEFAULT_OUTPUT_AMOUNT, false}});
|
|
|
|
std::set<std::pair<const CWalletTx *, unsigned int>> setCoinsRet;
|
|
std::vector<SaplingNoteEntry> saplingNotes;
|
|
CCurrencyValueMap reserveValueOut;
|
|
CAmount nativeValueOut;
|
|
std::vector<COutput> vCoins;
|
|
|
|
CTxDestination from_taddress;
|
|
if (wildCardTransparentAddress)
|
|
{
|
|
from_taddress = CTxDestination();
|
|
}
|
|
else if (wildCardRAddress)
|
|
{
|
|
from_taddress = CTxDestination(CKeyID(uint160()));
|
|
}
|
|
else if (wildCardiAddress)
|
|
{
|
|
from_taddress = CTxDestination(CIdentityID(uint160()));
|
|
}
|
|
else
|
|
{
|
|
from_taddress = sourceDest;
|
|
}
|
|
|
|
CCurrencyValueMap reservesOut;
|
|
for (int i = 0; i < outputs.size(); i++)
|
|
{
|
|
CRecipient &oneOut = outputs[i];
|
|
|
|
CCurrencyValueMap oneOutReserves;
|
|
oneOutReserves += oneOut.scriptPubKey.ReserveOutValue();
|
|
if (oneOut.nAmount)
|
|
{
|
|
oneOutReserves.valueMap[ASSETCHAINS_CHAINID] = oneOut.nAmount;
|
|
}
|
|
else
|
|
{
|
|
oneOutReserves.valueMap.erase(ASSETCHAINS_CHAINID);
|
|
}
|
|
reservesOut += oneOutReserves;
|
|
}
|
|
|
|
reservesOut = reservesOut.CanonicalMap();
|
|
|
|
// use the transaction builder to properly make change of native and reserves
|
|
TransactionBuilder tb(Params().consensus, height + 1, pwalletMain);
|
|
|
|
// make sure we have enough
|
|
CAmount nativeNeeded = CCommitmentHash::DEFAULT_OUTPUT_AMOUNT + DEFAULT_TRANSACTION_FEE;
|
|
|
|
for (auto &oneOut : outputs)
|
|
{
|
|
tb.AddTransparentOutput(oneOut.scriptPubKey, oneOut.nAmount);
|
|
}
|
|
|
|
if (hasZSource)
|
|
{
|
|
saplingNotes = find_unspent_notes(zaddressSource);
|
|
CAmount totalFound = 0;
|
|
int i;
|
|
for (i = 0; i < saplingNotes.size(); i++)
|
|
{
|
|
totalFound += saplingNotes[i].note.value();
|
|
if (totalFound >= nativeNeeded)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
// remove all but the notes we'll use
|
|
if (i < saplingNotes.size())
|
|
{
|
|
saplingNotes.erase(saplingNotes.begin() + i + 1, saplingNotes.end());
|
|
success = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
success = find_utxos(from_taddress, vCoins) &&
|
|
pwalletMain->SelectCoinsMinConf(nativeNeeded, 0, 0, vCoins, setCoinsRet, nativeValueOut);
|
|
}
|
|
|
|
if (!success)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Insufficient funds for identity commitment registration");
|
|
}
|
|
|
|
// aggregate all inputs into one output with only the offer coins and offer indexes
|
|
if (saplingNotes.size())
|
|
{
|
|
std::vector<SaplingOutPoint> notes;
|
|
for (auto &oneNoteInfo : saplingNotes)
|
|
{
|
|
notes.push_back(oneNoteInfo.op);
|
|
}
|
|
|
|
// Fetch Sapling anchor and witnesses
|
|
uint256 anchor;
|
|
std::vector<boost::optional<SaplingWitness>> witnesses;
|
|
{
|
|
LOCK2(cs_main, pwalletMain->cs_wallet);
|
|
pwalletMain->GetSaplingNoteWitnesses(notes, witnesses, anchor);
|
|
}
|
|
|
|
// Add Sapling spends
|
|
for (size_t i = 0; i < saplingNotes.size(); i++)
|
|
{
|
|
tb.AddSaplingSpend(expsk, saplingNotes[i].note, anchor, witnesses[i].get());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (auto &oneInput : setCoinsRet)
|
|
{
|
|
tb.AddTransparentInput(COutPoint(oneInput.first->GetHash(), oneInput.second),
|
|
oneInput.first->vout[oneInput.second].scriptPubKey,
|
|
oneInput.first->vout[oneInput.second].nValue);
|
|
}
|
|
}
|
|
|
|
if (hasZSource)
|
|
{
|
|
tb.SendChangeTo(*boost::get<libzcash::SaplingPaymentAddress>(&zaddressSource), sourceOvk);
|
|
}
|
|
else if (sourceDest.which() != COptCCParams::ADDRTYPE_INVALID && !GetDestinationID(sourceDest).IsNull())
|
|
{
|
|
tb.SendChangeTo(sourceDest);
|
|
changeDest = sourceDest;
|
|
}
|
|
else
|
|
{
|
|
tb.SendChangeTo(dest);
|
|
changeDest = dest;
|
|
}
|
|
|
|
TransactionBuilderResult preResult = tb.Build();
|
|
CTransaction commitTx = preResult.GetTxOrThrow();
|
|
|
|
// add to mem pool and relay
|
|
LOCK(cs_main);
|
|
|
|
bool relayTx;
|
|
CValidationState state;
|
|
{
|
|
LOCK2(smartTransactionCS, mempool.cs);
|
|
relayTx = myAddtomempool(commitTx, &state);
|
|
}
|
|
|
|
if (!relayTx)
|
|
{
|
|
throw JSONRPCError(RPC_TRANSACTION_REJECTED, "Unable to prepare offer tx: " + state.GetRejectReason());
|
|
}
|
|
else
|
|
{
|
|
RelayTransaction(commitTx);
|
|
}
|
|
|
|
UniValue ret(UniValue::VOBJ);
|
|
ret.push_back(Pair("txid", commitTx.GetHash().GetHex()));
|
|
ret.push_back(Pair("namereservation", advNameRes.IsValid() ? advNameRes.ToUniValue() : nameRes.ToUniValue()));
|
|
return ret;
|
|
}
|
|
|
|
UniValue registeridentity(const UniValue& params, bool fHelp)
|
|
{
|
|
if (fHelp || params.size() < 1 || params.size() > 5)
|
|
{
|
|
throw runtime_error(
|
|
"registeridentity \"jsonidregistration\" (returntx) feeoffer sourceoffunds\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"
|
|
"returntx (bool, optional) default=false if true, return a transaction for additional signatures rather than committing it\n"
|
|
"feeoffer (amount, optional) amount to offer miner/staker for the registration fee, if missing, uses standard price\n"
|
|
"sourceoffunds (addressorid, optional) optional address to use for source of funds. if not specified, transparent wildcard \"*\" is used\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")));
|
|
|
|
UniValue nameResUni = 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();
|
|
bool isPBaaS = CConstVerusSolutionVector::GetVersionByHeight(height + 1) >= CActivationHeight::ACTIVATE_PBAAS;
|
|
|
|
CNameReservation reservation;
|
|
CAdvancedNameReservation advReservation;
|
|
if (!find_value(nameResUni, "version").isNull() && !find_value(nameResUni, "parent").isNull())
|
|
{
|
|
if (!isPBaaS)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Advanced identity reservations are only valid after PBaaS activates");
|
|
}
|
|
advReservation = CAdvancedNameReservation(nameResUni);
|
|
}
|
|
else
|
|
{
|
|
reservation = CNameReservation(nameResUni);
|
|
}
|
|
|
|
UniValue rawID = find_value(params[0], "identity");
|
|
|
|
if (uni_get_int(find_value(rawID,"version")) == 0)
|
|
{
|
|
rawID.pushKV("version",
|
|
CConstVerusSolutionVector::GetVersionByHeight(height + 1) >= CActivationHeight::ACTIVATE_VERUSVAULT ?
|
|
CIdentity::VERSION_VAULT :
|
|
CIdentity::VERSION_VERUSID);
|
|
}
|
|
|
|
if (uni_get_int(find_value(rawID,"minimumsignatures")) == 0)
|
|
{
|
|
rawID.pushKV("minimumsignatures", (int32_t)1);
|
|
}
|
|
|
|
CCurrencyDefinition parentCurrency = ConnectedChains.GetCachedCurrency(advReservation.IsValid() ? advReservation.parent : parent);
|
|
if (!parentCurrency.IsValid())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parent currency or currency not found");
|
|
}
|
|
|
|
CCurrencyDefinition issuingCurrency = parentCurrency;
|
|
parent = parentCurrency.GetID();
|
|
uint160 issuerID = issuingCurrency.GetID();
|
|
|
|
CIdentity newID(rawID);
|
|
if (!newID.IsValid(true))
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid identity");
|
|
}
|
|
|
|
uint32_t solVersion = CConstVerusSolutionVector::GetVersionByHeight(height + 1);
|
|
|
|
if (solVersion >= CActivationHeight::ACTIVATE_VERUSVAULT)
|
|
{
|
|
newID.SetVersion(solVersion < CActivationHeight::ACTIVATE_PBAAS ? CIdentity::VERSION_VAULT : 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();
|
|
}
|
|
|
|
bool returnTx = params.size() > 1 ? uni_get_bool(params[1]) : false;
|
|
|
|
// get the primary currency to price in and apply any conversion rates
|
|
uint160 parentID = parentCurrency.GetID();
|
|
issuerID = issuingCurrency.GetID();
|
|
uint160 feePricingCurrency = issuerID;
|
|
int64_t idReferralFee = issuingCurrency.IDReferralAmount();
|
|
int64_t idFullRegistrationFee = issuingCurrency.IDFullRegistrationAmount();
|
|
int64_t idReferredRegistrationFee = issuingCurrency.IDReferredRegistrationAmount();
|
|
CCurrencyValueMap burnAmount;
|
|
CCoinbaseCurrencyState pricingState;
|
|
|
|
// set currency and price, as well as burn requirement
|
|
// determine if we may use a gateway converter to issue
|
|
|
|
if (isPBaaS)
|
|
{
|
|
if (!issuingCurrency.IsNameController() && !issuingCurrency.GatewayConverterID().IsNull())
|
|
{
|
|
issuingCurrency = ConnectedChains.GetCachedCurrency(issuingCurrency.GatewayConverterID());
|
|
if (!(issuingCurrency.IsValid() &&
|
|
issuingCurrency.IsFractional() &&
|
|
issuingCurrency.IsGatewayConverter() &&
|
|
issuingCurrency.gatewayID == parentID))
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid converter for gateway to register identity");
|
|
}
|
|
issuerID = issuingCurrency.GetID();
|
|
}
|
|
else if (issuingCurrency.IsGatewayConverter())
|
|
{
|
|
if (issuingCurrency.gatewayID.IsNull())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid gateway converter for identity registration");
|
|
}
|
|
CCurrencyDefinition gatewayCurrency = ConnectedChains.GetCachedCurrency(issuingCurrency.gatewayID);
|
|
if (gatewayCurrency.GetID() != issuingCurrency.systemID)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid gateway converter system for identity registration");
|
|
}
|
|
}
|
|
if (!issuingCurrency.IsValid() || issuingCurrency.systemID != ASSETCHAINS_CHAINID)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid issuing currency to register identity");
|
|
}
|
|
|
|
if (issuingCurrency.IsFractional())
|
|
{
|
|
feePricingCurrency = issuingCurrency.FeePricingCurrency();
|
|
if (!(pricingState = ConnectedChains.GetCurrencyState(issuerID, height)).IsValid() ||
|
|
!pricingState.IsLaunchConfirmed())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid currency state for gateway converter to register identity");
|
|
}
|
|
if (feePricingCurrency != issuerID)
|
|
{
|
|
int32_t reserveIndex = pricingState.GetReserveMap()[feePricingCurrency];
|
|
idReferralFee = pricingState.ReserveToNative(idReferralFee, reserveIndex);
|
|
idFullRegistrationFee = pricingState.ReserveToNative(idFullRegistrationFee, reserveIndex);
|
|
idReferredRegistrationFee = pricingState.ReserveToNative(idReferredRegistrationFee, reserveIndex);
|
|
}
|
|
}
|
|
// aside from fractional currencies, centralized or native currencies can issue IDs
|
|
else if (!(issuingCurrency.GetID() == ASSETCHAINS_CHAINID || issuingCurrency.proofProtocol == issuingCurrency.PROOF_CHAINID))
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parent currency for identity registration on this chain");
|
|
}
|
|
}
|
|
|
|
CAmount feeOffer = 0;
|
|
CIdentityID referralID = advReservation.IsValid() ? advReservation.referral : reservation.referral;
|
|
CAmount minFeeOffer = referralID.IsNull() ? idFullRegistrationFee : idReferredRegistrationFee;
|
|
|
|
if (params.size() > 2)
|
|
{
|
|
feeOffer = AmountFromValue(params[2]);
|
|
}
|
|
if (feeOffer == 0)
|
|
{
|
|
feeOffer = minFeeOffer;
|
|
}
|
|
|
|
if (feeOffer < minFeeOffer)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Fee offer must be at least " + ValueFromAmount(minFeeOffer).write());
|
|
}
|
|
|
|
std::string sourceAddress;
|
|
CTxDestination sourceDest;
|
|
|
|
bool wildCardTransparentAddress = false;
|
|
bool wildCardRAddress = false;
|
|
bool wildCardiAddress = false;
|
|
bool wildCardAddress = false;
|
|
|
|
libzcash::PaymentAddress zaddressSource;
|
|
libzcash::SaplingExpandedSpendingKey expsk;
|
|
uint256 sourceOvk;
|
|
bool hasZSource = false;
|
|
|
|
if (params.size() > 3)
|
|
{
|
|
sourceAddress = uni_get_str(params[3]);
|
|
|
|
wildCardTransparentAddress = sourceAddress == "*";
|
|
wildCardRAddress = sourceAddress == "R*";
|
|
wildCardiAddress = sourceAddress == "i*";
|
|
wildCardAddress = wildCardTransparentAddress || wildCardRAddress || wildCardiAddress;
|
|
hasZSource = !wildCardAddress && pwalletMain->GetAndValidateSaplingZAddress(sourceAddress, zaddressSource);
|
|
|
|
// 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)
|
|
{
|
|
sourceAddress = EncodePaymentAddress(zaddressSource);
|
|
// We don't need to lock on the wallet as spending key related methods are thread-safe
|
|
if (!boost::apply_visitor(HaveSpendingKeyForPaymentAddress(pwalletMain), zaddressSource)) {
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid from address, no spending key found for zaddr");
|
|
}
|
|
|
|
auto spendingkey_ = boost::apply_visitor(GetSpendingKeyForPaymentAddress(pwalletMain), zaddressSource).get();
|
|
auto sk = boost::get<libzcash::SaplingExtendedSpendingKey>(spendingkey_);
|
|
expsk = sk.expsk;
|
|
sourceOvk = expsk.full_viewing_key().ovk;
|
|
}
|
|
|
|
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.");
|
|
}
|
|
}
|
|
|
|
// this is only used to actually create errors and is normally not
|
|
// a parameter used for anything except testing
|
|
enum {
|
|
ERRTEST_NONE = 0,
|
|
ERRTEST_UNDERPAYFEE = 1,
|
|
ERRTEST_UNDERPAYREFERRAL = 2,
|
|
ERRTEST_SKIPREFERRAL = 3,
|
|
ERRTEST_WRONGREFERRAL = 4,
|
|
ERRTEST_LAST = 4
|
|
};
|
|
int errorTest = ERRTEST_NONE;
|
|
if (params.size() > 4)
|
|
{
|
|
errorTest = uni_get_int(params[4]);
|
|
if (errorTest < ERRTEST_NONE || errorTest > ERRTEST_LAST)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Optional test parameter out of range");
|
|
}
|
|
}
|
|
|
|
uint160 impliedParent, resParent;
|
|
if (advReservation.IsValid())
|
|
{
|
|
resParent = advReservation.parent;
|
|
impliedParent = newID.parent;
|
|
if (txid.IsNull() ||
|
|
CleanName(advReservation.name, resParent) != CleanName(newID.name, impliedParent) ||
|
|
resParent != impliedParent)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid identity description or mismatched advanced reservation. Is " + CleanName(newID.name, impliedParent) + " should be " + CleanName(advReservation.name, resParent) + ".");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
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.");
|
|
}
|
|
}
|
|
|
|
CTxDestination commitmentOutDest;
|
|
|
|
// 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);
|
|
std::pair<CIdentityMapKey, CIdentityMapValue> keyAndIdentity;
|
|
for (auto &oneKey : p.vKeys)
|
|
{
|
|
if (oneKey.which() == COptCCParams::ADDRTYPE_ID)
|
|
{
|
|
std::pair<CIdentityMapKey, CIdentityMapValue> checkKeyAndIdentity;
|
|
if (pwalletMain->GetIdentity(GetDestinationID(oneKey), checkKeyAndIdentity))
|
|
{
|
|
if (checkKeyAndIdentity.first.CanSign())
|
|
{
|
|
keyAndIdentity = checkKeyAndIdentity;
|
|
if (keyAndIdentity.first.CanSpend())
|
|
{
|
|
commitmentOutDest = oneKey;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (oneKey.which() == COptCCParams::ADDRTYPE_PKH || oneKey.which() == COptCCParams::ADDRTYPE_PK)
|
|
{
|
|
if (pwalletMain->HaveKey(GetDestinationID(oneKey)))
|
|
{
|
|
commitmentOutDest = oneKey;
|
|
break;
|
|
}
|
|
}
|
|
else if (oneKey.which() == COptCCParams::ADDRTYPE_SH)
|
|
{
|
|
if (pwalletMain->HaveCScript(GetDestinationID(oneKey)))
|
|
{
|
|
commitmentOutDest = oneKey;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (commitmentOutDest.which() == COptCCParams::ADDRTYPE_INVALID && keyAndIdentity.second.IsValid() && keyAndIdentity.first.CanSign())
|
|
{
|
|
commitmentOutDest = keyAndIdentity.first.idID;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (ch.hash.IsNull() || commitmentOutDest.which() == COptCCParams::ADDRTYPE_INVALID)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid commitment hash");
|
|
}
|
|
}
|
|
|
|
if (ch.hash != (advReservation.IsValid() ? advReservation.GetCommitment().hash : reservation.GetCommitment().hash))
|
|
{
|
|
uint256 gotHash = ch.hash;
|
|
uint256 expectedHash = (advReservation.IsValid() ? advReservation.GetCommitment().hash : reservation.GetCommitment().hash);
|
|
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid commitment salt or referral ID, got hash: " + gotHash.GetHex() + ", expected: " + expectedHash.GetHex());
|
|
}
|
|
|
|
// until PBaaS, 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
|
|
std::vector<CRecipient> outputs = std::vector<CRecipient>({{newID.IdentityUpdateOutputScript(height + 1), 0, false}});
|
|
int32_t registrationPaymentOut = -1;
|
|
|
|
int64_t expectedFee = referralID.IsNull() ? feeOffer : feeOffer - idReferralFee;
|
|
|
|
if (issuingCurrency.proofProtocol == issuingCurrency.PROOF_CHAINID)
|
|
{
|
|
if (issuerID == ASSETCHAINS_CHAINID)
|
|
{
|
|
// make an output to the currency ID of the amount less referrers
|
|
registrationPaymentOut = outputs.size();
|
|
outputs.push_back({CIdentity::TransparentOutput(issuerID), expectedFee, false});
|
|
}
|
|
else
|
|
{
|
|
// make an output to the currency ID of the amount less referrers
|
|
CTokenOutput to(CCurrencyValueMap(std::vector<uint160>({issuerID}), std::vector<int64_t>({expectedFee})));
|
|
registrationPaymentOut = outputs.size();
|
|
outputs.push_back({MakeMofNCCScript(CConditionObj<CTokenOutput>(EVAL_RESERVE_OUTPUT, std::vector<CTxDestination>({CIdentityID(issuerID)}), 1, &to)), 0, false});
|
|
}
|
|
}
|
|
else if (issuingCurrency.IsFractional())
|
|
{
|
|
// make a burn output of this currency for the amount
|
|
CReserveTransfer rt(CReserveTransfer::VALID + CReserveTransfer::BURN_CHANGE_PRICE,
|
|
CCurrencyValueMap(std::vector<uint160>({issuerID}),
|
|
std::vector<int64_t>({expectedFee})),
|
|
ASSETCHAINS_CHAINID,
|
|
ConnectedChains.ThisChain().GetTransactionTransferFee(),
|
|
issuerID,
|
|
DestinationToTransferDestination(CIdentityID(issuerID)));
|
|
registrationPaymentOut = outputs.size();
|
|
|
|
CCcontract_info CC;
|
|
CCcontract_info *cp;
|
|
cp = CCinit(&CC, EVAL_RESERVE_TRANSFER);
|
|
CPubKey pk(ParseHex(CC.CChexstr));
|
|
|
|
std::vector<CTxDestination> dests = std::vector<CTxDestination>({pk.GetID()});
|
|
|
|
outputs.push_back({MakeMofNCCScript(CConditionObj<CReserveTransfer>(EVAL_RESERVE_TRANSFER, dests, 1, &rt)), ConnectedChains.ThisChain().GetTransactionTransferFee(), false});
|
|
}
|
|
|
|
// wrong referral refers to the source identity instead of specified referral address
|
|
if (errorTest == ERRTEST_WRONGREFERRAL)
|
|
{
|
|
referralID = GetDestinationID(sourceDest);
|
|
}
|
|
|
|
// add referrals if any
|
|
if (errorTest != ERRTEST_SKIPREFERRAL &&
|
|
!newID.parent.IsNull() &&
|
|
(parentCurrency.IDReferrals() || (newID.parent == ASSETCHAINS_CHAINID && IsVerusActive())) &&
|
|
!referralID.IsNull())
|
|
{
|
|
uint32_t referralHeight;
|
|
CTxIn referralTxIn;
|
|
CTransaction referralIdTx;
|
|
auto referralIdentity = newID.LookupIdentity(referralID, commitmentHeight - 1);
|
|
|
|
if (referralIdentity.IsValidUnrevoked() &&
|
|
referralIdentity.systemID == ASSETCHAINS_CHAINID &&
|
|
newID.systemID == ASSETCHAINS_CHAINID &&
|
|
(referralIdentity.parent == newID.parent || ((referralID != ASSETCHAINS_CHAINID || !ConnectedChains.ThisChain().parent.IsNull()) && referralID == newID.parent)))
|
|
{
|
|
if (!newID.LookupFirstIdentity(referralID, &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");
|
|
}
|
|
|
|
if (errorTest == ERRTEST_UNDERPAYREFERRAL)
|
|
{
|
|
idReferralFee >>= 1;
|
|
}
|
|
|
|
// create outputs for this referral and up to n identities back in the referral chain
|
|
if (issuerID == ASSETCHAINS_CHAINID)
|
|
{
|
|
outputs.push_back({newID.TransparentOutput(referralIdentity.GetID()), idReferralFee, false});
|
|
}
|
|
else
|
|
{
|
|
// make an output to the currency ID of the amount less referrers
|
|
CTokenOutput to(CCurrencyValueMap(std::vector<uint160>({issuerID}), std::vector<int64_t>({idReferralFee})));
|
|
outputs.push_back({MakeMofNCCScript(CConditionObj<CTokenOutput>(EVAL_RESERVE_OUTPUT, std::vector<CTxDestination>({referralIdentity.GetID()}), 1, &to)), 0, false});
|
|
}
|
|
feeOffer -= idReferralFee;
|
|
if (referralHeight != 1 && referralID != newID.parent)
|
|
{
|
|
int afterId = referralTxIn.prevout.n +
|
|
((parentCurrency.IsPBaaSChain() && parentCurrency.proofProtocol != parentCurrency.PROOF_CHAINID) ? 1 : 2);
|
|
for (int i = afterId; i < (referralIdTx.vout.size() - 1) && (i - afterId) < (parentCurrency.idReferralLevels - 1); i++)
|
|
{
|
|
CTxDestination nextID;
|
|
COptCCParams p, master;
|
|
|
|
if (referralIdTx.vout[i].scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
((p.evalCode == EVAL_NONE && issuingCurrency.GetID() == ASSETCHAINS_CHAINID) ||
|
|
(p.evalCode == EVAL_RESERVE_OUTPUT && issuingCurrency.GetID() != ASSETCHAINS_CHAINID)) &&
|
|
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)))
|
|
{
|
|
if (issuingCurrency.GetID() == ASSETCHAINS_CHAINID)
|
|
{
|
|
outputs.push_back({newID.TransparentOutput(CIdentityID(GetDestinationID(p.vKeys[0]))), idReferralFee, false});
|
|
}
|
|
else
|
|
{
|
|
// make an output to the currency ID of the amount less referrers
|
|
CTokenOutput to(CCurrencyValueMap(std::vector<uint160>({issuerID}), std::vector<int64_t>({idReferralFee})));
|
|
outputs.push_back({MakeMofNCCScript(CConditionObj<CTokenOutput>(EVAL_RESERVE_OUTPUT, std::vector<CTxDestination>({CIdentityID(GetDestinationID(p.vKeys[0]))}), 1, &to)), 0, false});
|
|
}
|
|
feeOffer -= idReferralFee;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid or revoked referral identity at time of commitment");
|
|
}
|
|
}
|
|
|
|
if (errorTest == ERRTEST_UNDERPAYFEE)
|
|
{
|
|
feeOffer >>= 1;
|
|
}
|
|
|
|
CScript reservationOutScript;
|
|
if (advReservation.IsValid())
|
|
{
|
|
reservationOutScript = MakeMofNCCScript(CConditionObj<CAdvancedNameReservation>(EVAL_IDENTITY_ADVANCEDRESERVATION, std::vector<CTxDestination>({CIdentityID(newID.GetID())}), 1, &advReservation));
|
|
}
|
|
else
|
|
{
|
|
reservationOutScript = MakeMofNCCScript(CConditionObj<CNameReservation>(EVAL_IDENTITY_RESERVATION, std::vector<CTxDestination>({CIdentityID(newID.GetID())}), 1, &reservation));
|
|
}
|
|
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
|
|
|
|
// use the transaction builder to properly make change of native and reserves
|
|
TransactionBuilder tb(Params().consensus, height + 1, pwalletMain);
|
|
|
|
CCurrencyValueMap reservesOut;
|
|
|
|
// if we have registration payments, fixup the output amount based on referrals adjustment
|
|
if (registrationPaymentOut >= 0)
|
|
{
|
|
if (issuingCurrency.proofProtocol == issuingCurrency.PROOF_CHAINID)
|
|
{
|
|
if (issuerID == ASSETCHAINS_CHAINID)
|
|
{
|
|
// make an output to the currency ID of the amount less referrers
|
|
outputs[registrationPaymentOut].nAmount = feeOffer;
|
|
}
|
|
else
|
|
{
|
|
// make an output to the currency ID of the amount less referrers
|
|
CTokenOutput to(CCurrencyValueMap(std::vector<uint160>({issuerID}), std::vector<int64_t>({feeOffer})));
|
|
outputs[registrationPaymentOut].scriptPubKey = MakeMofNCCScript(CConditionObj<CTokenOutput>(EVAL_RESERVE_OUTPUT,
|
|
std::vector<CTxDestination>({CIdentityID(issuerID)}),
|
|
1,
|
|
&to));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// make a burn output of this currency for the amount
|
|
CReserveTransfer rt(CReserveTransfer::VALID + CReserveTransfer::BURN_CHANGE_PRICE,
|
|
CCurrencyValueMap(std::vector<uint160>({issuerID}), std::vector<int64_t>({feeOffer})),
|
|
ASSETCHAINS_CHAINID,
|
|
ConnectedChains.ThisChain().GetTransactionTransferFee(),
|
|
issuerID,
|
|
DestinationToTransferDestination(CIdentityID(issuerID)));
|
|
|
|
CCcontract_info CC;
|
|
CCcontract_info *cp;
|
|
cp = CCinit(&CC, EVAL_RESERVE_TRANSFER);
|
|
CPubKey pk(ParseHex(CC.CChexstr));
|
|
|
|
std::vector<CTxDestination> dests = std::vector<CTxDestination>({pk.GetID()});
|
|
|
|
outputs[registrationPaymentOut].scriptPubKey = MakeMofNCCScript(CConditionObj<CReserveTransfer>(EVAL_RESERVE_TRANSFER,
|
|
dests,
|
|
1,
|
|
&rt));
|
|
}
|
|
|
|
// the fee for a non-native registration is the import fee, as the fee offer was paid to the issuing currency
|
|
tb.SetFee(ConnectedChains.ThisChain().IDImportFee());
|
|
reservesOut.valueMap[ASSETCHAINS_CHAINID] += ConnectedChains.ThisChain().IDImportFee();
|
|
}
|
|
else
|
|
{
|
|
tb.SetFee(feeOffer);
|
|
reservesOut.valueMap[ASSETCHAINS_CHAINID] += feeOffer;
|
|
}
|
|
|
|
if (params.size() <= 3)
|
|
{
|
|
wildCardTransparentAddress = true;
|
|
}
|
|
|
|
bool success = false;
|
|
std::set<std::pair<const CWalletTx *, unsigned int>> setCoinsRet;
|
|
std::vector<SaplingNoteEntry> saplingNotes;
|
|
CCurrencyValueMap reserveValueOut;
|
|
CAmount nativeValueOut;
|
|
std::vector<COutput> vCoins;
|
|
|
|
CTxDestination from_taddress;
|
|
if (wildCardTransparentAddress)
|
|
{
|
|
from_taddress = CTxDestination();
|
|
}
|
|
else if (wildCardRAddress)
|
|
{
|
|
from_taddress = CTxDestination(CKeyID(uint160()));
|
|
}
|
|
else if (wildCardiAddress)
|
|
{
|
|
from_taddress = CTxDestination(CIdentityID(uint160()));
|
|
}
|
|
else
|
|
{
|
|
from_taddress = sourceDest;
|
|
}
|
|
|
|
for (int i = 0; i < outputs.size(); i++)
|
|
{
|
|
CRecipient &oneOut = outputs[i];
|
|
tb.AddTransparentOutput(oneOut.scriptPubKey, oneOut.nAmount);
|
|
|
|
CCurrencyValueMap oneOutReserves;
|
|
oneOutReserves += oneOut.scriptPubKey.ReserveOutValue();
|
|
if (oneOut.nAmount)
|
|
{
|
|
oneOutReserves.valueMap[ASSETCHAINS_CHAINID] = oneOut.nAmount;
|
|
}
|
|
else
|
|
{
|
|
oneOutReserves.valueMap.erase(ASSETCHAINS_CHAINID);
|
|
}
|
|
reservesOut += oneOutReserves;
|
|
}
|
|
|
|
tb.AddTransparentInput(COutPoint(txid, commitmentOutput), txOut.vout[commitmentOutput].scriptPubKey, txOut.vout[commitmentOutput].nValue);
|
|
|
|
reservesOut -= txOut.vout[commitmentOutput].scriptPubKey.ReserveOutValue();
|
|
if (txOut.vout[commitmentOutput].nValue)
|
|
{
|
|
reservesOut = reservesOut.SubtractToZero(
|
|
CCurrencyValueMap(std::vector<uint160>({ASSETCHAINS_CHAINID}),
|
|
std::vector<int64_t>({txOut.vout[commitmentOutput].nValue}))).CanonicalMap();
|
|
}
|
|
else
|
|
{
|
|
reservesOut = reservesOut.CanonicalMap();
|
|
}
|
|
|
|
reservesOut.valueMap[ASSETCHAINS_CHAINID] += DEFAULT_TRANSACTION_FEE;
|
|
|
|
if (reservesOut.valueMap.size() == 1 && reservesOut.valueMap.count(ASSETCHAINS_CHAINID))
|
|
{
|
|
CAmount nativeNeeded = reservesOut.valueMap.begin()->second;
|
|
|
|
if (hasZSource)
|
|
{
|
|
saplingNotes = find_unspent_notes(zaddressSource);
|
|
CAmount totalFound = 0;
|
|
int i;
|
|
for (i = 0; i < saplingNotes.size(); i++)
|
|
{
|
|
totalFound += saplingNotes[i].note.value();
|
|
if (totalFound >= nativeNeeded)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
// remove all but the notes we'll use
|
|
if (i < saplingNotes.size())
|
|
{
|
|
saplingNotes.erase(saplingNotes.begin() + i + 1, saplingNotes.end());
|
|
success = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
success = find_utxos(from_taddress, vCoins) &&
|
|
pwalletMain->SelectCoinsMinConf(nativeNeeded, 0, 0, vCoins, setCoinsRet, nativeValueOut);
|
|
}
|
|
}
|
|
else if (hasZSource)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot source non-native currencies from a private address");
|
|
}
|
|
else
|
|
{
|
|
CAmount nativeNeeded = reservesOut.valueMap.count(ASSETCHAINS_CHAINID) ? reservesOut.valueMap[ASSETCHAINS_CHAINID] : 0;
|
|
reservesOut.valueMap.erase(ASSETCHAINS_CHAINID);
|
|
|
|
success = find_utxos(from_taddress, vCoins);
|
|
success = success && pwalletMain->SelectReserveCoinsMinConf(reservesOut,
|
|
nativeNeeded,
|
|
0,
|
|
1,
|
|
vCoins,
|
|
setCoinsRet,
|
|
reserveValueOut,
|
|
nativeValueOut);
|
|
}
|
|
if (!success)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Insufficient funds for identity registration");
|
|
}
|
|
|
|
// aggregate all inputs into one output with only the offer coins and offer indexes
|
|
if (saplingNotes.size())
|
|
{
|
|
std::vector<SaplingOutPoint> notes;
|
|
for (auto &oneNoteInfo : saplingNotes)
|
|
{
|
|
notes.push_back(oneNoteInfo.op);
|
|
}
|
|
// Fetch Sapling anchor and witnesses
|
|
uint256 anchor;
|
|
std::vector<boost::optional<SaplingWitness>> witnesses;
|
|
{
|
|
LOCK2(cs_main, pwalletMain->cs_wallet);
|
|
pwalletMain->GetSaplingNoteWitnesses(notes, witnesses, anchor);
|
|
}
|
|
|
|
// Add Sapling spends
|
|
for (size_t i = 0; i < saplingNotes.size(); i++)
|
|
{
|
|
tb.AddSaplingSpend(expsk, saplingNotes[i].note, anchor, witnesses[i].get());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (auto &oneInput : setCoinsRet)
|
|
{
|
|
tb.AddTransparentInput(COutPoint(oneInput.first->GetHash(), oneInput.second),
|
|
oneInput.first->vout[oneInput.second].scriptPubKey,
|
|
oneInput.first->vout[oneInput.second].nValue);
|
|
}
|
|
}
|
|
|
|
if (hasZSource)
|
|
{
|
|
tb.SendChangeTo(*boost::get<libzcash::SaplingPaymentAddress>(&zaddressSource), sourceOvk);
|
|
}
|
|
else if (sourceDest.which() != COptCCParams::ADDRTYPE_INVALID && !GetDestinationID(sourceDest).IsNull())
|
|
{
|
|
tb.SendChangeTo(sourceDest);
|
|
}
|
|
else
|
|
{
|
|
tb.SendChangeTo(commitmentOutDest);
|
|
}
|
|
|
|
TransactionBuilderResult preResult = tb.Build(true);
|
|
CTransaction commitTx = preResult.GetTxOrThrow();
|
|
|
|
if (returnTx)
|
|
{
|
|
return EncodeHexTx(commitTx);
|
|
}
|
|
else
|
|
{
|
|
// add to mem pool and relay
|
|
LOCK(cs_main);
|
|
|
|
bool relayTx;
|
|
CValidationState state;
|
|
{
|
|
LOCK2(smartTransactionCS, mempool.cs);
|
|
relayTx = myAddtomempool(commitTx, &state);
|
|
}
|
|
|
|
// add to mem pool and relay
|
|
if (!relayTx)
|
|
{
|
|
throw JSONRPCError(RPC_TRANSACTION_REJECTED, "Unable to commit identity registration transaction: " + state.GetRejectReason());
|
|
}
|
|
else
|
|
{
|
|
RelayTransaction(commitTx);
|
|
}
|
|
}
|
|
|
|
// including definitions and claims thread
|
|
return UniValue(commitTx.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() > 3)
|
|
{
|
|
throw runtime_error(
|
|
"updateidentity \"jsonidentity\" (returntx) (tokenupdate)\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"
|
|
" \"tokenupdate\" (bool, optional) defaults to false, if true, the tokenized ID control token, if one exists, will be used to update\n"
|
|
" which enables changing the revocation or recovery IDs, even if the wallet holding the token does not\n"
|
|
" control either.\n"
|
|
|
|
"\nResult:\n"
|
|
" hex string of either the txid if returnhex is false or the hex serialized transaction if returntx is true\n"
|
|
|
|
"\nExamples:\n"
|
|
+ HelpExampleCli("updateidentity", "\'{\"name\" : \"myname\"}\'")
|
|
+ HelpExampleRpc("updateidentity", "\'{\"name\" : \"myname\"}\'")
|
|
);
|
|
}
|
|
|
|
CheckIdentityAPIsValid();
|
|
|
|
bool returnTx = false;
|
|
bool tokenizedIDControl = false;
|
|
|
|
if (params.size() > 1)
|
|
{
|
|
returnTx = uni_get_bool(params[1], false);
|
|
}
|
|
|
|
if (params.size() > 2)
|
|
{
|
|
tokenizedIDControl = uni_get_bool(params[2], false);
|
|
}
|
|
|
|
uint160 parentID = uint160(GetDestinationID(DecodeDestination(uni_get_str(find_value(params[0], "parent")))));
|
|
if (parentID.IsNull())
|
|
{
|
|
parentID = ValidateCurrencyName(uni_get_str(find_value(params[0], "parent")), true);
|
|
}
|
|
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();
|
|
|
|
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;
|
|
}
|
|
|
|
uint32_t solVersion = CConstVerusSolutionVector::GetVersionByHeight(nHeight + 1);
|
|
|
|
if (solVersion >= CActivationHeight::ACTIVATE_VERUSVAULT)
|
|
{
|
|
uniOldID["version"] = solVersion < CActivationHeight::ACTIVATE_PBAAS ? (int64_t)CIdentity::VERSION_VAULT : (int64_t)CIdentity::VERSION_PBAAS;
|
|
if (oldID.nVersion < CIdentity::VERSION_VAULT)
|
|
{
|
|
uniOldID["systemid"] = EncodeDestination(CIdentityID(parentID.IsNull() ? oldID.GetID() : parentID));
|
|
}
|
|
}
|
|
|
|
UniValue newUniID = MapToUniObject(uniOldID);
|
|
CIdentity newID(newUniID);
|
|
|
|
newID.flags |= (oldID.flags & (oldID.FLAG_ACTIVECURRENCY + oldID.FLAG_TOKENIZED_CONTROL));
|
|
|
|
if (!newID.IsValid(true))
|
|
{
|
|
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 (tokenizedIDControl)
|
|
{
|
|
if (!oldID.HasTokenizedControl())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Can only used ID control token for ID that has tokenized ID control on this chain");
|
|
}
|
|
if (PBAAS_TESTMODE && (IsVerusActive() || ConnectedChains.ThisChain().name == "Gravity") && chainActive.Height() < TESTNET_FORK_HEIGHT)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Tokenized ID control has not yet activated on testnet");
|
|
}
|
|
}
|
|
|
|
if (!revocationAuth.IsValid() || !recoveryAuth.IsValid())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid revocation or recovery authority");
|
|
}
|
|
|
|
CMutableTransaction txNew = CreateNewContextualCMutableTransaction(Params().GetConsensus(), nHeight + 1);
|
|
|
|
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 + 1, txNew.nExpiryHeight);
|
|
if (unlockAfter > newID.unlockAfter)
|
|
{
|
|
newID.unlockAfter = unlockAfter;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
newID.Lock(unlockAfter);
|
|
}
|
|
}
|
|
|
|
// if we are supposed to get our authority from the token, make sure it is present and prepare to spend it
|
|
std::vector<COutput> controlTokenOuts;
|
|
CCurrencyValueMap tokenCurrencyControlMap(std::vector<uint160>({newIDID}), std::vector<int64_t>({1}));
|
|
|
|
if (tokenizedIDControl)
|
|
{
|
|
COptCCParams tcP;
|
|
CCurrencyValueMap reserveMap;
|
|
|
|
pwalletMain->AvailableReserveCoins(controlTokenOuts, true, nullptr, false, false, nullptr, &tokenCurrencyControlMap, false);
|
|
if (controlTokenOuts.size() == 1 && controlTokenOuts[0].fSpendable)
|
|
{
|
|
reserveMap = controlTokenOuts[0].tx->vout[controlTokenOuts[0].i].ReserveOutValue();
|
|
if (!reserveMap.valueMap.count(newIDID) || reserveMap.valueMap[newIDID] != 1)
|
|
{
|
|
LogPrint("tokenizedidcontrol", "%s: controlTokenOuts.size(): %d, controlTokenOuts[0].tx->vout[controlTokenOuts[0].i].ReserveOutValue(): %s, reserveMap: %s, reserveMap.valueMap[idID]: %ld\n", __func__, (int)controlTokenOuts.size(), controlTokenOuts[0].tx->vout[controlTokenOuts[0].i].ReserveOutValue().ToUniValue().write().c_str(), reserveMap.ToUniValue().write().c_str(), reserveMap.valueMap[newIDID]);
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot locate spendable tokenized ID control currency in wallet - if present, may require rescan");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "No spendable tokenized ID control currency for update in wallet");
|
|
}
|
|
}
|
|
|
|
// create the identity definition transaction
|
|
std::vector<CRecipient> outputs = std::vector<CRecipient>({{newID.IdentityUpdateOutputScript(nHeight + 1), 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);
|
|
|
|
// if we are using tokenized ID control, add the input and output to the transaction before signing
|
|
if (tokenizedIDControl)
|
|
{
|
|
mtx.vin.push_back(CTxIn(controlTokenOuts[0].tx->GetHash(), controlTokenOuts[0].i));
|
|
// just in case a change output was inserted, we loop to find the first output with zero out
|
|
// and put the spend just after
|
|
for (auto it = mtx.vout.begin(); it != mtx.vout.end(); it++)
|
|
{
|
|
if (!it->nValue)
|
|
{
|
|
it++;
|
|
mtx.vout.insert(it, CTxOut(controlTokenOuts[0].tx->vout[controlTokenOuts[0].i]));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
*static_cast<CTransaction*>(&wtx) = CTransaction(mtx);
|
|
|
|
if (tokenizedIDControl && LogAcceptCategory("tokenizedidcontrol"))
|
|
{
|
|
UniValue jsonTx(UniValue::VOBJ);
|
|
TxToUniv(wtx, uint256(), jsonTx);
|
|
LogPrintf("%s: updateidtx:\n%s\n", __func__, jsonTx.write(1,2).c_str());
|
|
}
|
|
|
|
// 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(nHeight, Params().GetConsensus()));
|
|
|
|
if (!signSuccess && !returnTx)
|
|
{
|
|
LogPrintf("%s: failure to sign identity update 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 update 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 if (sigdata.scriptSig.size()) {
|
|
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 setidentitytimelock(const UniValue& params, bool fHelp)
|
|
{
|
|
if (fHelp || params.size() < 2 || params.size() > 3)
|
|
{
|
|
throw runtime_error(
|
|
"setidentitytimelock \"id@\" '{\"unlockatblock\":absoluteblockheight || \"setunlockdelay\":numberofblocksdelayafterunlock}' (returntx)\n"
|
|
"\nEnables timelocking and unlocking of funds access for an on-chain VerusID. This does not affect the lock status of VerusIDs on other chains,\n"
|
|
"including VerusIDs with the same identity as this one, which has been exported to another chain.\n"
|
|
"\nUse \"setunlockdelay\" to set a time unlock delay on an identity, which means that once the identity has been unlocked,\n"
|
|
"numberofblocksdelayafterunlock must then pass before the identity will be able to spend funds on this blockchain. Services\n"
|
|
"which support VerusID authentication and recognize this setting may also choose to prevent funds transfers when an ID is locked.\n"
|
|
"\nUse \"unlockatblock\" to either unlock, by passing the current block, which will still require waiting for the specified unlock\n"
|
|
"delay, or to set a future unlock height that immediately begins counting down. Unlike an unlock delay, which only starts counting\n"
|
|
"down when the ID is unlocked, an \"unlockatblock\" time lock is absolute and will automatically unlock when the specified\n"
|
|
"block passes.\n"
|
|
|
|
"\nArguments - either \"unlockatblock\" or \"setunlockdelay\" must be specified and not both\n"
|
|
"{\n"
|
|
" \"unlockatblock\" (number, optional) unlock at an absolute block height, countdown starts when mined into a block\n"
|
|
" \"setunlockdelay\" (number, optional) delay this many blocks after unlock request to unlock, can only be\n"
|
|
" circumvented by revoke/recover\n"
|
|
"}\n"
|
|
|
|
"\nResult:\n"
|
|
" Hex string of either the txid if returnhex is false or the hex serialized transaction if returntx is true.\n"
|
|
" If returntx is true, the transaction will not have been submitted and must be sent with \"sendrawtransaction\"\n"
|
|
" after any necessary signatures are applied in the case of multisig.\n"
|
|
|
|
"\nExamples:\n"
|
|
+ HelpExampleCli("setidentitytimelock", "\"id@\" '{\"unlockatblock\":absoluteblockheight || \"setunlockdelay\":numberofblocksdelayafterunlock}' (returntx)")
|
|
+ HelpExampleRpc("setidentitytimelock", "\"id@\" '{\"unlockatblock\":absoluteblockheight || \"setunlockdelay\":numberofblocksdelayafterunlock}' (returntx)")
|
|
);
|
|
}
|
|
|
|
CheckIdentityAPIsValid();
|
|
|
|
bool returnTx = false;
|
|
if (params.size() > 2)
|
|
{
|
|
returnTx = uni_get_bool(params[2], false);
|
|
}
|
|
|
|
std::string idString = uni_get_str(params[0]);
|
|
CIdentity oldIdentity = ValidateIdentityParameter(idString);
|
|
if (!oldIdentity.IsValid())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Identity, " + idString + " not found ");
|
|
}
|
|
|
|
UniValue unlockDelayUni = find_value(params[1], "setunlockdelay");
|
|
UniValue absoluteUnlockUni = find_value(params[1], "unlockatblock");
|
|
|
|
if ((!unlockDelayUni.isNull() && !absoluteUnlockUni.isNull()) || (unlockDelayUni.isNull() && absoluteUnlockUni.isNull()))
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Either \"setunlockdelay\" or \"unlockatblock\" must have a non-zero value and not both");
|
|
}
|
|
|
|
uint32_t unlockDelay = uni_get_int64(unlockDelayUni);
|
|
uint32_t absoluteUnlock = uni_get_int64(absoluteUnlockUni);
|
|
|
|
{
|
|
LOCK(cs_main);
|
|
uint32_t nextHeight = chainActive.Height() + 1;
|
|
CMutableTransaction txNew = CreateNewContextualCMutableTransaction(Params().GetConsensus(), nextHeight);
|
|
|
|
if (unlockDelayUni.isNull())
|
|
{
|
|
oldIdentity.Unlock(nextHeight, txNew.nExpiryHeight);
|
|
oldIdentity.unlockAfter = absoluteUnlock;
|
|
}
|
|
else
|
|
{
|
|
oldIdentity.Lock(unlockDelay);
|
|
}
|
|
}
|
|
|
|
UniValue newParams(UniValue::VARR);
|
|
|
|
newParams.push_back(oldIdentity.ToUniValue());
|
|
if (params.size() > 2)
|
|
{
|
|
newParams.push_back(params[2]);
|
|
}
|
|
return updateidentity(newParams, fHelp);
|
|
}
|
|
|
|
UniValue revokeidentity(const UniValue& params, bool fHelp)
|
|
{
|
|
if (fHelp || params.size() < 1 || params.size() > 3)
|
|
{
|
|
throw runtime_error(
|
|
"revokeidentity \"nameorID\" (returntx) (tokenrevoke)\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"
|
|
" \"tokenrevoke\" (bool, optional) defaults to false, if true, the tokenized ID control token, if one exists, will be used to revoke\n"
|
|
|
|
"\nResult:\n"
|
|
|
|
"\nExamples:\n"
|
|
+ HelpExampleCli("revokeidentity", "\"nameorID\"")
|
|
+ HelpExampleRpc("revokeidentity", "\"nameorID\"")
|
|
);
|
|
}
|
|
|
|
CheckIdentityAPIsValid();
|
|
|
|
// get identity
|
|
bool returnTx = false;
|
|
bool tokenizedIDControl = 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);
|
|
}
|
|
|
|
if (params.size() > 2)
|
|
{
|
|
tokenizedIDControl = uni_get_bool(params[2], 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));
|
|
}
|
|
|
|
// if we are supposed to get our authority from the token, make sure it is present and prepare to spend it
|
|
std::vector<COutput> controlTokenOuts;
|
|
CCurrencyValueMap tokenCurrencyControlMap(std::vector<uint160>({idID}), std::vector<int64_t>({1}));
|
|
|
|
if (tokenizedIDControl)
|
|
{
|
|
if (PBAAS_TESTMODE && (IsVerusActive() || ConnectedChains.ThisChain().name == "Gravity") && chainActive.Height() < TESTNET_FORK_HEIGHT)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Tokenized ID control has not yet activated on testnet");
|
|
}
|
|
COptCCParams tcP;
|
|
CCurrencyValueMap reserveMap;
|
|
pwalletMain->AvailableReserveCoins(controlTokenOuts, true, nullptr, false, false, nullptr, &tokenCurrencyControlMap, false);
|
|
if (controlTokenOuts.size() == 1 && controlTokenOuts[0].fSpendable)
|
|
{
|
|
reserveMap = controlTokenOuts[0].tx->vout[controlTokenOuts[0].i].ReserveOutValue();
|
|
if (!reserveMap.valueMap.count(idID) || reserveMap.valueMap[idID] != 1)
|
|
{
|
|
LogPrint("tokenizedidcontrol", "%s: controlTokenOuts.size(): %d, controlTokenOuts[0].tx->vout[controlTokenOuts[0].i].ReserveOutValue(): %s, reserveMap: %s, reserveMap.valueMap[idID]: %ld\n", __func__, (int)controlTokenOuts.size(), controlTokenOuts[0].tx->vout[controlTokenOuts[0].i].ReserveOutValue().ToUniValue().write().c_str(), reserveMap.ToUniValue().write().c_str(), reserveMap.valueMap[idID]);
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot locate spendable tokenized ID control currency in wallet - if present, may require rescan");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "No spendable tokenized ID control currency for revoke in wallet");
|
|
}
|
|
}
|
|
|
|
CIdentity newID(oldID);
|
|
newID.UpgradeVersion(chainActive.Height() + 1);
|
|
newID.Revoke();
|
|
|
|
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);
|
|
|
|
// if we are using tokenized ID control, add the input and output to the transaction before signing
|
|
if (tokenizedIDControl)
|
|
{
|
|
mtx.vin.push_back(CTxIn(controlTokenOuts[0].tx->GetHash(), controlTokenOuts[0].i));
|
|
// just in case a change output was inserted, we loop to find the first output with zero out
|
|
// and put the spend just after
|
|
for (auto it = mtx.vout.begin(); it != mtx.vout.end(); it++)
|
|
{
|
|
if (!it->nValue)
|
|
{
|
|
it++;
|
|
mtx.vout.insert(it, CTxOut(controlTokenOuts[0].tx->vout[controlTokenOuts[0].i]));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
*static_cast<CTransaction*>(&wtx) = CTransaction(mtx);
|
|
if (tokenizedIDControl && LogAcceptCategory("tokenizedidcontrol"))
|
|
{
|
|
UniValue jsonTx(UniValue::VOBJ);
|
|
TxToUniv(wtx, uint256(), jsonTx);
|
|
LogPrintf("%s: revokeidtx:\n%s\n", __func__, jsonTx.write(1,2).c_str());
|
|
}
|
|
|
|
// 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 && !returnTx)
|
|
{
|
|
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 if (sigdata.scriptSig.size()) {
|
|
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() > 3)
|
|
{
|
|
throw runtime_error(
|
|
"recoveridentity \"jsonidentity\" (returntx) (tokenrecover)\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"
|
|
" \"tokenrecover\" (bool, optional) defaults to false, if true, the tokenized ID control token, if one exists, will be used to recover\n"
|
|
|
|
"\nResult:\n"
|
|
|
|
"\nExamples:\n"
|
|
+ HelpExampleCli("recoveridentity", "\'{\"name\" : \"myname\"}\'")
|
|
+ HelpExampleRpc("recoveridentity", "\'{\"name\" : \"myname\"}\'")
|
|
);
|
|
}
|
|
CheckIdentityAPIsValid();
|
|
|
|
// get identity
|
|
bool returnTx = false;
|
|
bool tokenizedIDControl = false;
|
|
|
|
LOCK2(cs_main, pwalletMain->cs_wallet);
|
|
|
|
uint32_t nHeight = chainActive.Height();
|
|
|
|
UniValue newUniIdentity = params[0];
|
|
if (uni_get_int(find_value(newUniIdentity,"version")) == 0)
|
|
{
|
|
newUniIdentity.pushKV("version",
|
|
CConstVerusSolutionVector::GetVersionByHeight(nHeight + 1) >= CActivationHeight::ACTIVATE_VERUSVAULT ?
|
|
CIdentity::VERSION_VAULT :
|
|
CIdentity::VERSION_VERUSID);
|
|
}
|
|
|
|
if (uni_get_int(find_value(newUniIdentity,"minimumsignatures")) == 0)
|
|
{
|
|
newUniIdentity.pushKV("minimumsignatures", (int32_t)1);
|
|
}
|
|
|
|
CIdentity newID(newUniIdentity);
|
|
uint160 newIDID = newID.GetID();
|
|
|
|
if (!newID.IsValid(true))
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid JSON ID parameter");
|
|
}
|
|
|
|
if (params.size() > 1)
|
|
{
|
|
returnTx = uni_get_bool(params[1], false);
|
|
}
|
|
|
|
if (params.size() > 2)
|
|
{
|
|
tokenizedIDControl = uni_get_bool(params[2], false);
|
|
}
|
|
|
|
CTxIn idTxIn;
|
|
CIdentity oldID;
|
|
uint32_t idHeight;
|
|
|
|
if (!(oldID = CIdentity::LookupIdentity(newIDID, 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);
|
|
}
|
|
|
|
// if we are supposed to get our authority from the token, make sure it is present and prepare to spend it
|
|
std::vector<COutput> controlTokenOuts;
|
|
CCurrencyValueMap tokenCurrencyControlMap(std::vector<uint160>({newIDID}), std::vector<int64_t>({1}));
|
|
|
|
if (tokenizedIDControl)
|
|
{
|
|
if (PBAAS_TESTMODE && (IsVerusActive() || ConnectedChains.ThisChain().name == "Gravity") && chainActive.Height() < TESTNET_FORK_HEIGHT)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Tokenized ID control has not yet activated on testnet");
|
|
}
|
|
|
|
COptCCParams tcP;
|
|
|
|
CCurrencyValueMap reserveMap;
|
|
pwalletMain->AvailableReserveCoins(controlTokenOuts, true, nullptr, false, false, nullptr, &tokenCurrencyControlMap, false);
|
|
if (controlTokenOuts.size() == 1 && controlTokenOuts[0].fSpendable)
|
|
{
|
|
reserveMap = controlTokenOuts[0].tx->vout[controlTokenOuts[0].i].ReserveOutValue();
|
|
if (!reserveMap.valueMap.count(newIDID) || reserveMap.valueMap[newIDID] != 1)
|
|
{
|
|
LogPrint("tokenizedidcontrol", "%s: controlTokenOuts.size(): %d, controlTokenOuts[0].tx->vout[controlTokenOuts[0].i].ReserveOutValue(): %s, reserveMap: %s, reserveMap.valueMap[idID]: %ld\n", __func__, (int)controlTokenOuts.size(), controlTokenOuts[0].tx->vout[controlTokenOuts[0].i].ReserveOutValue().ToUniValue().write().c_str(), reserveMap.ToUniValue().write().c_str(), reserveMap.valueMap[newIDID]);
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot locate spendable tokenized ID control currency in wallet - if present, may require rescan");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "No spendable tokenized ID control currency for recover in wallet");
|
|
}
|
|
}
|
|
|
|
newID.flags |= (oldID.flags & (oldID.FLAG_ACTIVECURRENCY + oldID.FLAG_TOKENIZED_CONTROL));
|
|
newID.flags &= ~CIdentity::FLAG_REVOKED;
|
|
newID.systemID = oldID.systemID;
|
|
newID.UpgradeVersion(nHeight + 1);
|
|
|
|
// create the identity definition transaction
|
|
std::vector<CRecipient> outputs = std::vector<CRecipient>({{newID.IdentityUpdateOutputScript(nHeight + 1), 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);
|
|
|
|
// if we are using tokenized ID control, add the input and output to the transaction before signing
|
|
if (tokenizedIDControl)
|
|
{
|
|
mtx.vin.push_back(CTxIn(controlTokenOuts[0].tx->GetHash(), controlTokenOuts[0].i));
|
|
// just in case a change output was inserted, we loop to find the first output with zero out
|
|
// and put the spend just after
|
|
for (auto it = mtx.vout.begin(); it != mtx.vout.end(); it++)
|
|
{
|
|
if (!it->nValue)
|
|
{
|
|
it++;
|
|
mtx.vout.insert(it, CTxOut(controlTokenOuts[0].tx->vout[controlTokenOuts[0].i]));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
*static_cast<CTransaction*>(&wtx) = CTransaction(mtx);
|
|
|
|
if (tokenizedIDControl && LogAcceptCategory("tokenizedidcontrol"))
|
|
{
|
|
UniValue jsonTx(UniValue::VOBJ);
|
|
TxToUniv(wtx, uint256(), jsonTx);
|
|
LogPrintf("%s: recoveridtx:\n%s\n", __func__, jsonTx.write(1,2).c_str());
|
|
}
|
|
|
|
// 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 && !returnTx)
|
|
{
|
|
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 if (sigdata.scriptSig.size()) {
|
|
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 || params.size() > 4)
|
|
{
|
|
throw runtime_error(
|
|
"getidentity \"name@ || iid\" (height) (txproof) (txproofheight)\n"
|
|
"\n\n"
|
|
|
|
"\nArguments\n"
|
|
" \"name@ || iid\" (string, required) name followed by \"@\" or i-address of an identity\n"
|
|
" \"height\" (number, optional) default=current height, return identity as of this height\n"
|
|
" \"txproof\" (bool, optional) default=false, if true, returns proof of ID\n"
|
|
" \"txproofheight\" (number, optional) default=\"height\", height from which to generate a proof\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]) + "\"");
|
|
}
|
|
|
|
LOCK(cs_main);
|
|
|
|
uint32_t lteHeight = chainActive.Height();
|
|
if (params.size() > 1)
|
|
{
|
|
uint32_t tmpHeight = uni_get_int64(params[1]);
|
|
if (tmpHeight > 0 && lteHeight > tmpHeight)
|
|
{
|
|
lteHeight = tmpHeight;
|
|
}
|
|
}
|
|
bool txProof = params.size() > 2 ? uni_get_bool(params[2]) : false;
|
|
uint32_t txProofHeight = params.size() > 3 ? uni_get_int64(params[1]) : 0;
|
|
if (txProofHeight < lteHeight)
|
|
{
|
|
txProofHeight = lteHeight;
|
|
}
|
|
|
|
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, lteHeight))
|
|
{
|
|
canSign = keyAndIdentity.first.flags & keyAndIdentity.first.CAN_SIGN;
|
|
canSpend = keyAndIdentity.first.flags & keyAndIdentity.first.CAN_SPEND;
|
|
identity = static_cast<CIdentity>(keyAndIdentity.second);
|
|
}
|
|
}
|
|
|
|
uint160 identityID = GetDestinationID(idID);
|
|
identity = CIdentity::LookupIdentity(CIdentityID(identityID), lteHeight, &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_VAULT,
|
|
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));
|
|
|
|
if (txProof &&
|
|
!idTxIn.prevout.hash.IsNull() &&
|
|
height > 0 &&
|
|
height <= chainActive.Height())
|
|
{
|
|
CTransaction idTx;
|
|
uint256 blkHash;
|
|
CBlockIndex *pIndex;
|
|
LOCK(mempool.cs);
|
|
if (!myGetTransaction(idTxIn.prevout.hash, idTx, blkHash) ||
|
|
blkHash.IsNull())
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMS, "Identity transacton not found or not indexed / committed");
|
|
}
|
|
auto blkMapIt = mapBlockIndex.find(blkHash);
|
|
if (blkMapIt == mapBlockIndex.end() ||
|
|
!chainActive.Contains(pIndex = blkMapIt->second))
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMS, "Identity transacton not indexed / committed");
|
|
}
|
|
CPartialTransactionProof idProof = CPartialTransactionProof(idTx,
|
|
std::vector<int>(),
|
|
std::vector<int>({(int)idTxIn.prevout.n}),
|
|
pIndex,
|
|
txProofHeight);
|
|
if (idProof.IsValid())
|
|
{
|
|
ret.push_back(Pair("proof", idProof.ToUniValue()));
|
|
}
|
|
}
|
|
|
|
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 (includecanspend) (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_VAULT,
|
|
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_VAULT,
|
|
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_VAULT,
|
|
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 getidentitieswithaddress(const UniValue& params, bool fHelp)
|
|
{
|
|
if (fHelp || params.size() != 1)
|
|
{
|
|
throw runtime_error(
|
|
"getidentitieswithaddress '{\"address\":\"validprimaryaddress\",\"fromheight\":height, \"toheight\":height, \"unspent\":false}'\n"
|
|
"\n\n"
|
|
|
|
"\nArguments\n"
|
|
"{\n"
|
|
" \"address\":\"validaddress\" (string, required) returns all identities that contain the specified address in its primary addresses\n"
|
|
" \"fromheight\":n (number, optional, default=0) Search for qualified identities modified from this height forward only\n"
|
|
" \"toheight\":n (number, optional, default=0) Search for qualified identities only up until this height (0 == no limit)\n"
|
|
" \"unspent\":bool (bool, optional, default=false) if true, this will only return active ID UTXOs as of the current block height\n"
|
|
"}\n"
|
|
|
|
"\nResult:\n"
|
|
"[ (array) array of matching identities\n"
|
|
" {identityobject}, (object) identity with additional member \"txout\" with txhash and output index\n"
|
|
" ...\n"
|
|
"]\n"
|
|
|
|
"\nExamples:\n"
|
|
+ HelpExampleCli("getidentitieswithaddress", "\'{\"address\":\"validprimaryaddress\",\"fromheight\":height, \"toheight\":height, \"unspent\":false}\'")
|
|
+ HelpExampleRpc("getidentitieswithaddress", "\'{\"address\":\"validprimaryaddress\",\"fromheight\":height, \"toheight\":height, \"unspent\":false}\'")
|
|
);
|
|
}
|
|
|
|
CheckIdentityAPIsValid();
|
|
if (!fIdIndex)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMS, "getidentitieswithaddress requires -idindex=1 when starting the daemon\n");
|
|
}
|
|
UniValue retVal(UniValue::VARR);
|
|
|
|
std::string addressString = uni_get_str(find_value(params[0], "address"));
|
|
CTxDestination addressDest = DecodeDestination(addressString);
|
|
if (addressDest.which() != COptCCParams::ADDRTYPE_PKH && addressDest.which() != COptCCParams::ADDRTYPE_PK)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "no valid PKH or PK address\n");
|
|
}
|
|
uint32_t fromHeight = uni_get_int64(find_value(params[0], "fromheight"));
|
|
uint32_t toHeight = uni_get_int64(find_value(params[0], "toheight"));
|
|
|
|
if (uni_get_bool(find_value(params[0], "unspent")))
|
|
{
|
|
std::map<uint160, std::pair<CAddressUnspentDbEntry, CIdentity>> identities;
|
|
if (CIdentity::GetActiveIdentitiesByPrimaryAddress(addressDest, identities))
|
|
{
|
|
for (auto &oneIdentity : identities)
|
|
{
|
|
if ((!fromHeight || oneIdentity.second.first.second.blockHeight >= fromHeight) &&
|
|
(!toHeight || oneIdentity.second.first.second.blockHeight <= toHeight))
|
|
{
|
|
UniValue idUni = oneIdentity.second.second.ToUniValue();
|
|
idUni.pushKV("txout", CUTXORef(oneIdentity.second.first.first.txhash, oneIdentity.second.first.first.index).ToUniValue());
|
|
retVal.push_back(idUni);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
std::map<uint160, std::pair<CAddressIndexDbEntry, CIdentity>> identities;
|
|
if (CIdentity::GetIdentityOutsByPrimaryAddress(addressDest, identities, fromHeight, toHeight))
|
|
{
|
|
for (auto &oneIdentity : identities)
|
|
{
|
|
UniValue idUni = oneIdentity.second.second.ToUniValue();
|
|
idUni.pushKV("txout", CUTXORef(oneIdentity.second.first.first.txhash, oneIdentity.second.first.first.index).ToUniValue());
|
|
retVal.push_back(idUni);
|
|
}
|
|
}
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
UniValue getidentitieswithrevocation(const UniValue& params, bool fHelp)
|
|
{
|
|
if (fHelp || params.size() != 1)
|
|
{
|
|
throw runtime_error(
|
|
"getidentitieswithrevocation '{\"identityid\":\"idori-address\", \"fromheight\":height, \"toheight\":height, \"unspent\":false}'\n"
|
|
"\n\n"
|
|
|
|
"\nArguments\n"
|
|
"{\n"
|
|
" \"identityid\":\"idori-address\" (string, required) returns all identities where this ID or i-address is the revocation authority\n"
|
|
" \"fromheight\":n (number, optional, default=0) Search for qualified identities modified from this height forward only\n"
|
|
" \"toheight\":n (number, optional, default=0) Search for qualified identities only up until this height (0 == no limit)\n"
|
|
" \"unspent\":bool (bool, optional, default=false) if true, this will only return active ID UTXOs as of the current block height\n"
|
|
"}\n"
|
|
|
|
"\nResult:\n"
|
|
"[ (array) array of matching identities\n"
|
|
" {identityobject}, (object) identity with additional member \"txout\" with txhash and output index\n"
|
|
" ...\n"
|
|
"]\n"
|
|
|
|
"\nExamples:\n"
|
|
+ HelpExampleCli("getidentitieswithrevocation", "\'{\"identityid\":\"idori-address\",\"fromheight\":height,\"toheight\":height,\"unspent\":false}\'")
|
|
+ HelpExampleRpc("getidentitieswithrevocation", "\'{\"identityid\":\"idori-address\",\"fromheight\":height,\"toheight\":height,\"unspent\":false}\'")
|
|
);
|
|
}
|
|
|
|
CheckIdentityAPIsValid();
|
|
if (!fIdIndex)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMS, "getidentitieswithrevocation requires -idindex=1 when starting the daemon\n");
|
|
}
|
|
UniValue retVal(UniValue::VARR);
|
|
|
|
std::string addressString = uni_get_str(find_value(params[0], "identityid"));
|
|
CTxDestination addressDest = DecodeDestination(addressString);
|
|
if (addressDest.which() != COptCCParams::ADDRTYPE_ID)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "no valid ID address\n");
|
|
}
|
|
|
|
CIdentityID idID = GetDestinationID(addressDest);
|
|
|
|
uint32_t fromHeight = uni_get_int64(find_value(params[0], "fromheight"));
|
|
uint32_t toHeight = uni_get_int64(find_value(params[0], "toheight"));
|
|
|
|
if (uni_get_bool(find_value(params[0], "unspent")))
|
|
{
|
|
std::map<uint160, std::pair<CAddressUnspentDbEntry, CIdentity>> identities;
|
|
if (CIdentity::GetActiveIdentitiesWithRevocationID(idID, identities))
|
|
{
|
|
for (auto &oneIdentity : identities)
|
|
{
|
|
if ((!fromHeight || oneIdentity.second.first.second.blockHeight >= fromHeight) &&
|
|
(!toHeight || oneIdentity.second.first.second.blockHeight <= toHeight))
|
|
{
|
|
UniValue idUni = oneIdentity.second.second.ToUniValue();
|
|
idUni.pushKV("txout", CUTXORef(oneIdentity.second.first.first.txhash, oneIdentity.second.first.first.index).ToUniValue());
|
|
retVal.push_back(idUni);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
std::map<uint160, std::pair<CAddressIndexDbEntry, CIdentity>> identities;
|
|
if (CIdentity::GetIdentityOutsWithRevocationID(idID, identities, fromHeight, toHeight))
|
|
{
|
|
for (auto &oneIdentity : identities)
|
|
{
|
|
UniValue idUni = oneIdentity.second.second.ToUniValue();
|
|
idUni.pushKV("txout", CUTXORef(oneIdentity.second.first.first.txhash, oneIdentity.second.first.first.index).ToUniValue());
|
|
retVal.push_back(idUni);
|
|
}
|
|
}
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
UniValue getidentitieswithrecovery(const UniValue& params, bool fHelp)
|
|
{
|
|
if (fHelp || params.size() != 1)
|
|
{
|
|
throw runtime_error(
|
|
"getidentitieswithrecovery '{\"identityid\":\"idori-address\", \"fromheight\":height, \"toheight\":height, \"unspent\":false}'\n"
|
|
"\n\n"
|
|
|
|
"\nArguments\n"
|
|
"{\n"
|
|
" \"identityid\":\"idori-address\" (string, required) returns all identities where this ID or i-address is the recovery authority\n"
|
|
" \"fromheight\":n (number, optional, default=0) Search for qualified identities modified from this height forward only\n"
|
|
" \"toheight\":n (number, optional, default=0) Search for qualified identities only up until this height (0 == no limit)\n"
|
|
" \"unspent\":bool (bool, optional, default=false) if true, this will only return active ID UTXOs as of the current block height\n"
|
|
"}\n"
|
|
|
|
"\nResult:\n"
|
|
"[ (array) array of matching identities\n"
|
|
" {identityobject}, (object) identity with additional member \"txout\" with txhash and output index\n"
|
|
" ...\n"
|
|
"]\n"
|
|
|
|
"\nExamples:\n"
|
|
+ HelpExampleCli("getidentitieswithrecovery", "\'{\"identityid\":\"idori-address\",\"fromheight\":height,\"toheight\":height,\"unspent\":false}\'")
|
|
+ HelpExampleRpc("getidentitieswithrecovery", "\'{\"identityid\":\"idori-address\",\"fromheight\":height,\"toheight\":height,\"unspent\":false}\'")
|
|
);
|
|
}
|
|
|
|
CheckIdentityAPIsValid();
|
|
if (!fIdIndex)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_PARAMS, "getidentitieswithrecovery requires -idindex=1 when starting the daemon\n");
|
|
}
|
|
UniValue retVal(UniValue::VARR);
|
|
|
|
std::string addressString = uni_get_str(find_value(params[0], "identityid"));
|
|
CTxDestination addressDest = DecodeDestination(addressString);
|
|
if (addressDest.which() != COptCCParams::ADDRTYPE_ID)
|
|
{
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "no valid ID address\n");
|
|
}
|
|
|
|
CIdentityID idID = GetDestinationID(addressDest);
|
|
|
|
uint32_t fromHeight = uni_get_int64(find_value(params[0], "fromheight"));
|
|
uint32_t toHeight = uni_get_int64(find_value(params[0], "toheight"));
|
|
|
|
if (uni_get_bool(find_value(params[0], "unspent")))
|
|
{
|
|
std::map<uint160, std::pair<CAddressUnspentDbEntry, CIdentity>> identities;
|
|
if (CIdentity::GetActiveIdentitiesWithRecoveryID(idID, identities))
|
|
{
|
|
for (auto &oneIdentity : identities)
|
|
{
|
|
if ((!fromHeight || oneIdentity.second.first.second.blockHeight >= fromHeight) &&
|
|
(!toHeight || oneIdentity.second.first.second.blockHeight <= toHeight))
|
|
{
|
|
UniValue idUni = oneIdentity.second.second.ToUniValue();
|
|
idUni.pushKV("txout", CUTXORef(oneIdentity.second.first.first.txhash, oneIdentity.second.first.first.index).ToUniValue());
|
|
retVal.push_back(idUni);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
std::map<uint160, std::pair<CAddressIndexDbEntry, CIdentity>> identities;
|
|
if (CIdentity::GetIdentityOutsWithRecoveryID(idID, identities, fromHeight, toHeight))
|
|
{
|
|
for (auto &oneIdentity : identities)
|
|
{
|
|
UniValue idUni = oneIdentity.second.second.ToUniValue();
|
|
idUni.pushKV("txout", CUTXORef(oneIdentity.second.first.first.txhash, oneIdentity.second.first.first.index).ToUniValue());
|
|
retVal.push_back(idUni);
|
|
}
|
|
}
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
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", ®isternamecommitment, true },
|
|
{ "identity", "registeridentity", ®isteridentity, true },
|
|
{ "identity", "updateidentity", &updateidentity, true },
|
|
{ "identity", "revokeidentity", &revokeidentity, true },
|
|
{ "identity", "setidentitytimelock", &setidentitytimelock, true },
|
|
{ "identity", "recoveridentity", &recoveridentity, true },
|
|
{ "identity", "getidentity", &getidentity, true },
|
|
{ "identity", "listidentities", &listidentities, true },
|
|
{ "identity", "getidentitieswithaddress", &getidentitieswithaddress, true },
|
|
{ "identity", "getidentitieswithrevocation", &getidentitieswithrevocation, true },
|
|
{ "identity", "getidentitieswithrecovery", &getidentitieswithrecovery, true },
|
|
{ "marketplace", "makeoffer", &makeoffer, true },
|
|
{ "marketplace", "takeoffer", &takeoffer, true },
|
|
{ "marketplace", "getoffers", &getoffers, true },
|
|
{ "marketplace", "listopenoffers", &listopenoffers, true },
|
|
{ "marketplace", "closeoffers", &closeoffers, 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]);
|
|
}
|
|
|