cp->ismyvin = IsServiceRewardInput;
break;
- case EVAL_RESERVEIMPORT:
- case EVAL_RESERVEEXPORT:
- case EVAL_RESERVEOUTPUT:
+ case EVAL_INSTANTSPEND:
+ case EVAL_CROSSCHAIN_INPUT:
+ case EVAL_CROSSCHAIN_OUTPUT:
+ case EVAL_CROSSCHAIN_IMPORT:
+ case EVAL_CROSSCHAIN_EXPORT:
assert(false);
break;
std::vector<uint8_t> vparams(cond->code+1, cond->code+cond->codeLength);
switch ( ecode )
{
- case EVAL_STAKEGUARD:
case EVAL_PBAASDEFINITION:
case EVAL_SERVICEREWARD:
case EVAL_EARNEDNOTARIZATION:
case EVAL_ACCEPTEDNOTARIZATION:
case EVAL_FINALIZENOTARIZATION:
- case EVAL_RESERVEOUTPUT:
- case EVAL_RESERVEEXPORT:
- case EVAL_RESERVEIMPORT:
+ case EVAL_CROSSCHAIN_OUTPUT:
+ case EVAL_CROSSCHAIN_EXPORT:
+ case EVAL_CROSSCHAIN_IMPORT:
+ case EVAL_INSTANTSPEND:
+ case EVAL_CROSSCHAIN_INPUT:
+ if (!chainActive.LastTip() || CConstVerusSolutionVector::activationHeight.ActiveVersion(chainActive.LastTip()->GetHeight()) < CActivationHeight::SOLUTION_VERUSV3)
+ {
+ // if chain is not able to process this yet, don't drop through to do so
+ break;
+ }
+
+ case EVAL_STAKEGUARD:
return(ProcessCC(cp,this, vparams, txTo, nIn));
break;
EVAL(EVAL_EARNEDNOTARIZATION, 0x4) \
EVAL(EVAL_ACCEPTEDNOTARIZATION, 0x5) \
EVAL(EVAL_FINALIZENOTARIZATION, 0x6) \
- EVAL(EVAL_RESERVEOUTPUT, 0x7) \
- EVAL(EVAL_RESERVEEXPORT, 0x8) \
- EVAL(EVAL_RESERVEIMPORT, 0x9) \
+ EVAL(EVAL_INSTANTSPEND, 0x7) \
+ EVAL(EVAL_CROSSCHAIN_INPUT, 0x8) \
+ EVAL(EVAL_CROSSCHAIN_OUTPUT, 0x9) \
+ EVAL(EVAL_CROSSCHAIN_EXPORT, 0xa) \
+ EVAL(EVAL_CROSSCHAIN_IMPORT, 0xb) \
EVAL(EVAL_IMPORTPAYOUT, 0xe1) \
EVAL(EVAL_IMPORTCOIN, 0xe2) \
EVAL(EVAL_ASSETS, 0xe3) \
{
txnouttype tp;
std::vector<std::vector<unsigned char>> vvch = std::vector<std::vector<unsigned char>>();
- // solve all outputs to check that destinations all go only to the pk
+ // solve all outputs to check that non-instantspend destinations all go only to the pk
// specified in the stake params
if ((!supportInstantSpend || !vout.scriptPubKey.IsInstantSpend()) &&
(!Solver(vout.scriptPubKey, tp, vvch) ||
{
return ::AsVector(*this);
}
+
+ UniValue ToUniValue() const
+ {
+ UniValue ret(UniValue::VOBJ);
+ ret.push_back(Pair("confirmedinput", confirmedInput));
+ return ret;
+ }
};
class CChainNotarizationData
}
+CCrossChainInput::CCrossChainInput(const CTransaction &tx)
+{
+ // TODO - finish
+}
+
+CCrossChainInput::CCrossChainInput(const UniValue &obj)
+{
+ // TODO - finish
+}
+
uint160 CPBaaSChainDefinition::GetChainID(std::string name)
{
const char *chainName = name.c_str();
static const uint32_t PBAAS_VERSION_INVALID = 0;
static const uint32_t PBAAS_NODESPERNOTARIZATION = 2; // number of nodes to reference in each notarization
static const int64_t PBAAS_MINNOTARIZATIONOUTPUT = 10000; // enough for one fee worth to finalization and notarization thread
-static const int64_t PBAAS_MAXNOTARIZATIONINPUTS = 50; // max inputs on a notarization tx
static const int32_t PBAAS_MINSTARTBLOCKDELTA = 100; // minimum number of blocks to wait for starting a chain after definition
static const int32_t PBAAS_MAXPRIORBLOCKS = 16; // maximum prior block commitments to include in prior blocks chain object
static const int64_t MIN_PER_BLOCK_NOTARIZATION = 1000000; // 0.01 VRSC per block notarization minimum
static const int64_t MIN_BILLING_PERIOD = 480; // 8 hour minimum billing period for notarization, typically expect days/weeks/months
static const int64_t DEFAULT_OUTPUT_VALUE = 100000; // 0.001 VRSC default output value
+ static const int32_t OPTION_RESERVE = 1; // allows reserve conversion when set
uint32_t nVersion; // version of this chain definition data structure to allow for extensions (not daemon version)
std::string name; // chain name, maximum 64 characters
CKeyID address; // non-purchased/converted premine and fee recipient address
int64_t premine; // initial supply that is distributed to the premine output address, but not purchased
- int64_t conversion; // factor / 100000000 for conversion of VRSC to coin on launch
+ int64_t conversion; // factor / 100000000 for conversion of VRSC to/from coin
int64_t launchFee; // ratio of satoshis to send from contribution to convertible to fee address
int32_t startBlock; // parent chain block # that must kickoff the notarization of block 0, cannot be before this block
int32_t endBlock; // block after which this is considered end-of-lifed
}
};
-// This is used on a PBaaS chain to manage reserves of VRSC. type of reserve is assumed to be $VRSC
-class CReserveOutput
+// this is used on either the VRSC chain or PBaaS chain to send transactions to an alternate chain, which will
+// be realized on the other chain using the specified output script.
+// To actually realize the transfer of funds to or from a PBaaS chain, inputs must be aggregated by a miner as inputs to
+// a CCrossChainExport transaction, which may then be imported to the PBaaS chain with one transaction proof proving all inputs
+// being converted to outputs in bulk. This avoids creating the overhead of many redundant transaction proofs
+// which can move a large number of smaller transactions across chains.
+class CCrossChainInput
{
public:
- CAmount nValue; // amount of reserve coin in this output
- CScript scrOut; // output script, spend is validated as with a normal bitcoin spend
+ CAmount finalValue; // difference between actual output of this tx and final is fee paid
+ CScript scriptPubKey; // output script, spend is validated as with a normal bitcoin spend
- CReserveOutput(const std::vector<unsigned char> &asVector)
+ CCrossChainInput() : finalValue(-1) {}
+
+ CCrossChainInput(const std::vector<unsigned char> &asVector)
{
FromVector(asVector, *this);
}
- CReserveOutput(const CAmount value, const CScript &rScrOut)
- {
- nValue = value;
- scrOut = rScrOut;
- }
+ CCrossChainInput(const CScript &rScrOut, const CAmount finalout) : scriptPubKey(rScrOut), finalValue(finalout) { }
+
+ CCrossChainInput(const CTransaction &tx);
+ CCrossChainInput(const UniValue &obj);
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action) {
- READWRITE(VARINT(nValue));
- READWRITE(*(CScriptBase*)(&scrOut));
+ READWRITE(finalValue);
+ READWRITE(*(CScriptBase*)(&scriptPubKey));
}
std::vector<unsigned char> AsVector()
bool IsValid()
{
- return scrOut.size() != 0;
+ return scriptPubKey.size() != 0 && finalValue >= 0;
+ }
+
+ UniValue ToUniValue()
+ {
+ UniValue ret(UniValue::VOBJ);
+ ret.push_back(Pair("finalvalue", finalValue));
+ ret.push_back(Pair("scriptpubkey", scriptPubKey.ToString()));
+ return ret;
}
};
-// import $VRSC from another chain transaction
-class CReserveImport
+// This is used on a PBaaS chain to transact with reserves of VRSC as a token that can freely convert between
+// the native coin and the token type. type of reserve is assumed to be $VRSC
+class CCrossChainOutput
{
public:
- CAmount nValue; // amount of proxy coin in this output
- CCrossChainProof transferProof;
+ uint160 chainID; // from what chain
+ CAmount nValue; // amount of token in this output
+ CScript scriptPubKey; // output script, spend is validated as with a normal spend
- CReserveImport(const std::vector<unsigned char> &asVector)
+ CCrossChainOutput(uint160 cID, const CScript &rScrOut, const CAmount value) : chainID(cID), nValue(value), scriptPubKey(rScrOut) { }
+ CCrossChainOutput(const std::vector<unsigned char> &asVector)
{
FromVector(asVector, *this);
}
- CReserveImport(const CAmount value, CCrossChainProof &rTransferProof)
+ ADD_SERIALIZE_METHODS;
+
+ template <typename Stream, typename Operation>
+ inline void SerializationOp(Stream& s, Operation ser_action) {
+ READWRITE(chainID);
+ READWRITE(VARINT(nValue));
+ READWRITE(*(CScriptBase*)(&scriptPubKey));
+ }
+
+ std::vector<unsigned char> AsVector()
+ {
+ return ::AsVector(*this);
+ }
+
+ bool IsValid()
+ {
+ return scriptPubKey.size() != 0 && !chainID.IsNull() || nValue >= 0;
+ }
+};
+
+// import transactions from another chain
+// an import transaction on a fractional reserve chain will have an instant spend input of EVAL_CHAIN_IMPORT from the coinbase,
+// which provides for import of a reserve currency or cross-chain token, or auto-conversion to the native currency.
+class CCrossChainImport
+{
+public:
+ uint160 chainID; // usually the reserve currency, but here for generality
+ CAmount nValue; // amount of proxy coin for final output (difference from actual output divided into 2 fees)
+
+ CCrossChainImport() : nValue(0) { }
+ CCrossChainImport(const CAmount value) : nValue(value) { }
+
+ CCrossChainImport(const std::vector<unsigned char> &asVector)
{
- nValue = value;
- transferProof = rTransferProof;
+ FromVector(asVector, *this);
}
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action) {
READWRITE(VARINT(nValue));
- READWRITE(transferProof);
}
std::vector<unsigned char> AsVector()
bool IsValid()
{
- return nValue != 0 && transferProof.IsValid();
+ return nValue >= 0 && !chainID.IsNull();
}
};
-// send $VRSC or VRSCTOKEN to another chain
-class CReserveExport
+// send some amount of $VRSC as VRSCTOKEN to another chain for distribution to original senders, which are
+// determined by the inputs to this transaction and stored in the opret of this transaction, so they can be
+// validated on the destination chain as one transaction, yet used as the output scripts of the transaction
+// itself
+// The export type determines how the coins are realized on the destination chain, but the destination
+// script will be used in any case.
+class CCrossChainExport
{
public:
+ enum EXPORT_TYPE {
+ EXPORT_INVALID = 0,
+ EXPORT_CONVERSION = 1, // realized on the destination chain as the destination chain currency at market conversion rate
+ EXPORT_SEND = 2 // realized on destination chain as a representative reserve token, unconverted
+ };
+ uint8_t exportType;
CAmount nValue;
- uint256 chainDest;
+ uint160 chainID;
- CReserveExport(const std::vector<unsigned char> &asVector)
+ CCrossChainExport() : exportType(EXPORT_INVALID), nValue(0) {}
+
+ CCrossChainExport(const std::vector<unsigned char> &asVector)
{
FromVector(asVector, *this);
}
- CReserveExport(const CAmount value, const uint256 &rChainDest)
- {
- nValue = value;
- chainDest = rChainDest;
- }
+ CCrossChainExport(EXPORT_TYPE exporttype, const CAmount value, const uint160 &rChainDest) : exportType(exporttype), nValue(value), chainID(rChainDest) {}
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action) {
+ READWRITE(exportType);
READWRITE(VARINT(nValue));
- READWRITE(chainDest);
+ READWRITE(chainID);
}
std::vector<unsigned char> AsVector()
bool IsValid()
{
- return nValue != 0 && !chainDest.IsNull();
+ return exportType != EXPORT_INVALID && nValue >= 0 && !chainID.IsNull();
}
};
class CReserveExchange
{
public:
- CScript scrOut; // output script for resulting coinbase output
+ uint32_t flags; // control of direction and constraints
+ CScript scriptPubKey; // output script for resulting coinbase output
CAmount nLimit; // lowest or highest price to sell or buy coin output, may fail if including this tx in block makes price out of range
uint32_t nValidBefore; // if not filled in this block, mine tx, but refund input
- uint256 chainID; // currently supports convert from or to reserve according to conversion rules, this is ouput type
+ uint160 chainID; // currently supports convert from or to reserve according to conversion rules, this is ouput type
CReserveExchange(const std::vector<unsigned char> &asVector)
{
FromVector(asVector, *this);
}
- CReserveExchange(const CScript &rScrOut, const CAmount Limit, uint32_t ValidBefore, uint256 ChainID)
- {
- scrOut = rScrOut;
- nLimit = Limit;
- nValidBefore = ValidBefore;
- chainID = ChainID;
- }
+ CReserveExchange() : flags(0), nLimit(0), nValidBefore(0) { }
+
+ CReserveExchange(uint32_t Flags, const CScript &rScrOut, const CAmount Limit, uint32_t ValidBefore, uint160 ChainID) :
+ flags(Flags), scriptPubKey(rScrOut), nLimit(Limit), nValidBefore(ValidBefore), chainID(ChainID) { }
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action) {
- READWRITE(*(CScriptBase*)(&scrOut));
+ READWRITE(flags);
+ READWRITE(*(CScriptBase*)(&scriptPubKey));
READWRITE(VARINT(nLimit));
READWRITE(nValidBefore);
READWRITE(chainID);
bool IsValid()
{
// this needs an actual check
- return nValidBefore != 0 && scrOut.size() != 0 && !chainID.IsNull();
+ return nValidBefore != 0 && scriptPubKey.size() != 0 && !chainID.IsNull();
}
};
return true;
}
+ if (!IsVerusActive())
+ {
+ if (ConnectedChains.NotaryChain().IsValid() && (chainID == ConnectedChains.NotaryChain().chainDefinition.GetChainID()))
+ {
+ chainDef = ConnectedChains.NotaryChain().chainDefinition;
+ return true;
+ }
+ }
+
// make the chain definition output
cp = CCinit(&CC, EVAL_PBAASDEFINITION);
return ret;
}
+UniValue sendtochain(const UniValue& params, bool fHelp)
+{
+ if (fHelp || params.size() != 1)
+ {
+ throw runtime_error(
+ "sendtochain '[{\"name\": \"PBAASCHAIN\", \"paymentaddress\": \"RRehdmUV7oEAqoZnzEGBH34XysnWaBatct\", \"amount\": 5.0}]'\n"
+ "\nThis sends a Verus output as a JSON object or lists of Verus outputs as a list of objects to multiple chains or back.\n"
+ "\nFunds are sourced automatically from the current wallet, which must be present, as in sendtoaddress.\n"
+
+ "\nArguments\n"
+ " {\n"
+ " \"chain\" : \"xxxx\", (string, required) unique Verus ecosystem-wide name/symbol of this PBaaS chain\n"
+ " \"paymentaddress\" : \"Rxxx\", (string, required) premine and launch fee recipient\n"
+ " \"amount\" : \"n\", (int64, required) amount of coins that will be premined and distributed to premine address\n"
+ " \"convert\" : \"false\", (bool, optional) auto-convert to PBaaS currency at current price\n"
+ " }\n"
+
+ "\nResult:\n"
+ " \"txid\" : \"transactionid\" (string) The transaction id.\n"
+
+ "\nExamples:\n"
+ + HelpExampleCli("sendtochain", "'[{\"name\": \"PBAASCHAIN\", \"paymentaddress\": \"RRehdmUV7oEAqoZnzEGBH34XysnWaBatct\", \"amount\": 5.0}]'")
+ + HelpExampleRpc("sendtochain", "'[{\"name\": \"PBAASCHAIN\", \"paymentaddress\": \"RRehdmUV7oEAqoZnzEGBH34XysnWaBatct\", \"amount\": 5.0}]'")
+ );
+ }
+
+ // each object represents a send, and all sends are aggregated into one transaction to improve potential for scaling when moving funds between
+ // and across multiple chains.
+ //
+ // each output will require an additional standard cross-chain fee that will be divided evenly in two ways,
+ // between the transaction aggregator -- the miner or staker who creates the aggregating export,
+ // and the transaction importer on the alternate chain who posts each exported bundle.
+ //
+ vector<CRecipient> outputs;
+ vector<bool> vConvert;
+
+ if (params.size() != 1 || (!params[0].isArray() && !params[0].isObject()))
+ {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameters. Must provide a single object or single list of objects that represent valid outputs. see help.");
+ }
+
+ const UniValue *pOutputArr = ¶ms[0];
+ UniValue substArr(UniValue::VARR);
+ if (params[0].isObject())
+ {
+ substArr.push_back(params[0]);
+ pOutputArr = &substArr;
+ }
+ const UniValue &objArr = *pOutputArr;
+
+ // convert all entries to CRecipient
+ // any failure fails all
+ for (int i = 0; i < objArr.size(); i++)
+ {
+ // default double fee for miner of chain definition tx
+ // one output for definition, one for finalization
+ string name = uni_get_str(find_value(params[0], "chain"), "");
+ string paymentAddr = uni_get_str(find_value(params[0], "paymentaddress"), "");
+ CAmount amount = uni_get_int64(find_value(params[0], "amount"), -1);
+ bool convert = uni_get_int(find_value(params[0], "convert"), false);
+
+ if (name == "" || paymentAddr == "" || amount < 0)
+ {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameters for object #" + to_string(i));
+ }
+
+ CBitcoinAddress ba(DecodeDestination(paymentAddr));
+ CKeyID kID;
+
+ if (!ba.IsValid() || !ba.GetKeyID(kID))
+ {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid payment address in object #" + to_string(i));
+ }
+
+ uint160 chainID = CCrossChainRPCData::GetChainID(name);
+ CPBaaSChainDefinition chainDef;
+ // validate that the target chain is still running
+ if (!GetChainDefinition(chainID, chainDef) || !chainDef.IsValid())
+ {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Chain specified in object #" + to_string(i) + " is not a valid chain");
+ }
+
+ // validate that the entry is a valid chain being notarized
+ CChainNotarizationData nData;
+ if (GetNotarizationData(chainID, IsVerusActive() ? EVAL_ACCEPTEDNOTARIZATION : EVAL_ACCEPTEDNOTARIZATION, nData))
+ {
+ // if the chain is being notarized and cannot confirm before its end, refuse to send
+ // also, if it hasn't been notarized as recently as the active notarization threshold, refuse as well
+ // TODO: define threshold, for now, only check that last notarization is at least minimum confirmation
+ // distance
+ if ((nData.vtx.size() &&
+ (nData.vtx[nData.bestChain].second.notarizationHeight + (CPBaaSNotarization::MIN_BLOCKS_BETWEEN_ACCEPTED * CPBaaSNotarization::FINAL_CONFIRMATIONS) >
+ chainDef.endBlock)) ||
+ (!chainDef.eraOptions.size() || !(chainDef.eraOptions[0] & CPBaaSChainDefinition::OPTION_RESERVE)))
+ {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Chain specified in object #" + to_string(i) + " is not a valid chain");
+ }
+ }
+ else
+ {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Chain specified in object #" + to_string(i) + " is not notarized");
+ }
+
+ // make the output script, either a normal output or a conversion
+
+ CCcontract_info CC;
+ CCcontract_info *cp;
+ cp = CCinit(&CC, EVAL_CROSSCHAIN_INPUT);
+
+ CPubKey pk = CPubKey(ParseHex(CC.CChexstr));
+ // TODO: determine dests properly
+ std::vector<CTxDestination> dests = std::vector<CTxDestination>({CKeyID(chainDef.GetConditionID(EVAL_CROSSCHAIN_INPUT))});
+ CCrossChainInput cci; // TODO fill with payment script and amount (adjust amount for fees)
+ CTxOut ccOut = MakeCC1of1Vout(EVAL_CROSSCHAIN_INPUT, amount, pk, dests, cci);
+ outputs.push_back(CRecipient({ccOut.scriptPubKey, amount, false}));
+ }
+ // send the specified amount to chain ID as an EVAL_CROSSCHAIN_INPUT to the chain ID
+ // the transaction holds the ultimate destination address, and until the transaction
+ // is packaged into an EVAL_CROSSCHAIN_EXPORT bundle, the output can be spent by
+ // the original sender
+ // once bundled, transaction outputs can be transferred to the other chain through a proof of the bundle by anyone and is considered irreversible
+ // all bundled outputs can be moved to and spent on the destination chain as soon as a notarization of the same block
+ // or later has been confirmed. bundling transactions can be done at any time, but moving an export bundle
+ // happens only after is is in a block behind a confirmed notarization.
+}
+
UniValue definechain(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() != 1)
void RegisterPBaaSRPCCommands(CRPCTable &tableRPC)
{
- for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++)
- tableRPC.appendCommand(commands[vcidx].name, &commands[vcidx]);
+ //for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++)
+ // tableRPC.appendCommand(commands[vcidx].name, &commands[vcidx]);
}
#include "script/sign.h"
#include "script/standard.h"
#include "uint256.h"
+
+#include "cc/CCinclude.h"
+#include "cc/eval.h"
+#include "pbaas/notarization.h"
+
#ifdef ENABLE_WALLET
#include "wallet/wallet.h"
#endif
ScriptPubKeyToJSON(txout.scriptPubKey, o, true);
out.push_back(Pair("scriptPubKey", o));
vout.push_back(out);
+ COptCCParams p;
+ if (IsPayToCryptoCondition(txout.scriptPubKey, p) && p.version >= COptCCParams::VERSION_V2)
+ {
+ switch(p.evalCode)
+ {
+ case EVAL_PBAASDEFINITION:
+ {
+ CPBaaSChainDefinition definition;
+
+ if (p.vData.size() && (definition = CPBaaSChainDefinition(p.vData[0])).IsValid())
+ {
+ out.push_back(Pair("pbaasChainDefinition", definition.ToUniValue()));
+ }
+ break;
+ }
+
+ case EVAL_SERVICEREWARD:
+ {
+ CServiceReward reward;
+
+ if (p.vData.size() && (reward = CServiceReward(p.vData[0])).IsValid())
+ {
+ out.push_back(Pair("pbaasServiceReward", reward.ToUniValue()));
+ }
+ break;
+ }
+
+ case EVAL_EARNEDNOTARIZATION:
+ case EVAL_ACCEPTEDNOTARIZATION:
+ {
+ CPBaaSNotarization notarization;
+
+ if (p.vData.size() && (notarization = CPBaaSNotarization(p.vData[0])).IsValid())
+ {
+ out.push_back(Pair("pbaasNotarization", notarization.ToUniValue()));
+ }
+ break;
+ }
+
+ case EVAL_FINALIZENOTARIZATION:
+ {
+ CNotarizationFinalization finalization;
+
+ if (p.vData.size() && (finalization = CNotarizationFinalization(p.vData[0])).IsValid())
+ {
+ out.push_back(Pair("pbaasFinalization", finalization.ToUniValue()));
+ }
+ break;
+ }
+
+ case EVAL_INSTANTSPEND:
+ case EVAL_CROSSCHAIN_INPUT:
+ case EVAL_CROSSCHAIN_OUTPUT:
+ case EVAL_CROSSCHAIN_EXPORT:
+ case EVAL_CROSSCHAIN_IMPORT:
+ case EVAL_STAKEGUARD:
+ break;
+ }
+ }
}
entry.push_back(Pair("vout", vout));