Browse Source
This is a simple utility that provides command line manipulation of a hex-encoded TX. The utility takes a hex string on the command line as input, performs zero or more mutations, and outputs a hex string to standard output. This utility is also an intentional exercise of the "bitcoin library" concept. It is designed to require minimal libraries, and works entirely without need for any RPC or P2P communication. See "bitcoin-tx --help" for command and options summary.0.10
Jeff Garzik
10 years ago
13 changed files with 1655 additions and 3 deletions
@ -0,0 +1,597 @@ |
|||||||
|
// Copyright (c) 2009-2014 The Bitcoin developers
|
||||||
|
// Distributed under the MIT/X11 software license, see the accompanying
|
||||||
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
#include "base58.h" |
||||||
|
#include "util.h" |
||||||
|
#include "core.h" |
||||||
|
#include "main.h" // for MAX_BLOCK_SIZE |
||||||
|
#include "keystore.h" |
||||||
|
#include "ui_interface.h" // for _(...) |
||||||
|
#include "univalue/univalue.h" |
||||||
|
#include "core_io.h" |
||||||
|
|
||||||
|
#include <stdio.h> |
||||||
|
#include <boost/assign/list_of.hpp> |
||||||
|
|
||||||
|
using namespace std; |
||||||
|
using namespace boost::assign; |
||||||
|
|
||||||
|
static bool fCreateBlank; |
||||||
|
static map<string,UniValue> registers; |
||||||
|
CClientUIInterface uiInterface; |
||||||
|
|
||||||
|
static bool AppInitRawTx(int argc, char* argv[]) |
||||||
|
{ |
||||||
|
//
|
||||||
|
// Parameters
|
||||||
|
//
|
||||||
|
ParseParameters(argc, argv); |
||||||
|
|
||||||
|
// Check for -testnet or -regtest parameter (Params() calls are only valid after this clause)
|
||||||
|
if (!SelectParamsFromCommandLine()) { |
||||||
|
fprintf(stderr, "Error: Invalid combination of -regtest and -testnet.\n"); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
fCreateBlank = GetBoolArg("-create", false); |
||||||
|
|
||||||
|
if (argc<2 || mapArgs.count("-?") || mapArgs.count("-help")) |
||||||
|
{ |
||||||
|
// First part of help message is specific to this utility
|
||||||
|
std::string strUsage = _("Bitcoin Core bitcoin-tx utility version") + " " + FormatFullVersion() + "\n\n" + |
||||||
|
_("Usage:") + "\n" + |
||||||
|
" bitcoin-tx [options] <hex-tx> [commands] " + _("Update hex-encoded bitcoin transaction") + "\n" + |
||||||
|
" bitcoin-tx [options] -create [commands] " + _("Create hex-encoded bitcoin transaction") + "\n" + |
||||||
|
"\n"; |
||||||
|
|
||||||
|
fprintf(stdout, "%s", strUsage.c_str()); |
||||||
|
|
||||||
|
strUsage = _("Options:") + "\n"; |
||||||
|
strUsage += " -? " + _("This help message") + "\n"; |
||||||
|
strUsage += " -create " + _("Create new, empty TX.") + "\n"; |
||||||
|
strUsage += " -json " + _("Select JSON output") + "\n"; |
||||||
|
strUsage += " -regtest " + _("Enter regression test mode, which uses a special chain in which blocks can be solved instantly.") + "\n"; |
||||||
|
strUsage += " -testnet " + _("Use the test network") + "\n"; |
||||||
|
strUsage += "\n"; |
||||||
|
|
||||||
|
fprintf(stdout, "%s", strUsage.c_str()); |
||||||
|
|
||||||
|
|
||||||
|
strUsage = _("Commands:") + "\n"; |
||||||
|
strUsage += " delin=N " + _("Delete input N from TX") + "\n"; |
||||||
|
strUsage += " delout=N " + _("Delete output N from TX") + "\n"; |
||||||
|
strUsage += " in=TXID:VOUT " + _("Add input to TX") + "\n"; |
||||||
|
strUsage += " locktime=N " + _("Set TX lock time to N") + "\n"; |
||||||
|
strUsage += " nversion=N " + _("Set TX version to N") + "\n"; |
||||||
|
strUsage += " outaddr=VALUE:ADDRESS " + _("Add address-based output to TX") + "\n"; |
||||||
|
strUsage += " outscript=VALUE:SCRIPT " + _("Add raw script output to TX") + "\n"; |
||||||
|
strUsage += " sign=SIGHASH-FLAGS " + _("Add zero or more signatures to transaction") + "\n"; |
||||||
|
strUsage += " This command requires JSON registers:\n"; |
||||||
|
strUsage += " prevtxs=JSON object\n"; |
||||||
|
strUsage += " privatekeys=JSON object\n"; |
||||||
|
strUsage += " See signrawtransaction docs for format of sighash flags, JSON objects.\n"; |
||||||
|
strUsage += "\n"; |
||||||
|
fprintf(stdout, "%s", strUsage.c_str()); |
||||||
|
|
||||||
|
strUsage = _("Register Commands:") + "\n"; |
||||||
|
strUsage += " load=NAME:FILENAME " + _("Load JSON file FILENAME into register NAME") + "\n"; |
||||||
|
strUsage += " set=NAME:JSON-STRING " + _("Set register NAME to given JSON-STRING") + "\n"; |
||||||
|
strUsage += "\n"; |
||||||
|
fprintf(stdout, "%s", strUsage.c_str()); |
||||||
|
|
||||||
|
return false; |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
static void RegisterSetJson(const string& key, const string& rawJson) |
||||||
|
{ |
||||||
|
UniValue val; |
||||||
|
if (!val.read(rawJson)) { |
||||||
|
string strErr = "Cannot parse JSON for key " + key; |
||||||
|
throw runtime_error(strErr); |
||||||
|
} |
||||||
|
|
||||||
|
registers[key] = val; |
||||||
|
} |
||||||
|
|
||||||
|
static void RegisterSet(const string& strInput) |
||||||
|
{ |
||||||
|
// separate NAME:VALUE in string
|
||||||
|
size_t pos = strInput.find(':'); |
||||||
|
if ((pos == string::npos) || |
||||||
|
(pos == 0) || |
||||||
|
(pos == (strInput.size() - 1))) |
||||||
|
throw runtime_error("Register input requires NAME:VALUE"); |
||||||
|
|
||||||
|
string key = strInput.substr(0, pos); |
||||||
|
string valStr = strInput.substr(pos + 1, string::npos); |
||||||
|
|
||||||
|
RegisterSetJson(key, valStr); |
||||||
|
} |
||||||
|
|
||||||
|
static void RegisterLoad(const string& strInput) |
||||||
|
{ |
||||||
|
// separate NAME:FILENAME in string
|
||||||
|
size_t pos = strInput.find(':'); |
||||||
|
if ((pos == string::npos) || |
||||||
|
(pos == 0) || |
||||||
|
(pos == (strInput.size() - 1))) |
||||||
|
throw runtime_error("Register load requires NAME:FILENAME"); |
||||||
|
|
||||||
|
string key = strInput.substr(0, pos); |
||||||
|
string filename = strInput.substr(pos + 1, string::npos); |
||||||
|
|
||||||
|
FILE *f = fopen(filename.c_str(), "r"); |
||||||
|
if (!f) { |
||||||
|
string strErr = "Cannot open file " + filename; |
||||||
|
throw runtime_error(strErr); |
||||||
|
} |
||||||
|
|
||||||
|
// load file chunks into one big buffer
|
||||||
|
string valStr; |
||||||
|
while ((!feof(f)) && (!ferror(f))) { |
||||||
|
char buf[4096]; |
||||||
|
int bread = fread(buf, 1, sizeof(buf), f); |
||||||
|
if (bread <= 0) |
||||||
|
break; |
||||||
|
|
||||||
|
valStr.insert(valStr.size(), buf, bread); |
||||||
|
} |
||||||
|
|
||||||
|
if (ferror(f)) { |
||||||
|
string strErr = "Error reading file " + filename; |
||||||
|
throw runtime_error(strErr); |
||||||
|
} |
||||||
|
|
||||||
|
fclose(f); |
||||||
|
|
||||||
|
// evaluate as JSON buffer register
|
||||||
|
RegisterSetJson(key, valStr); |
||||||
|
} |
||||||
|
|
||||||
|
static void MutateTxVersion(CMutableTransaction& tx, const string& cmdVal) |
||||||
|
{ |
||||||
|
int64_t newVersion = atoi64(cmdVal); |
||||||
|
if (newVersion < 1 || newVersion > CTransaction::CURRENT_VERSION) |
||||||
|
throw runtime_error("Invalid TX version requested"); |
||||||
|
|
||||||
|
tx.nVersion = (int) newVersion; |
||||||
|
} |
||||||
|
|
||||||
|
static void MutateTxLocktime(CMutableTransaction& tx, const string& cmdVal) |
||||||
|
{ |
||||||
|
int64_t newLocktime = atoi64(cmdVal); |
||||||
|
if (newLocktime < 0LL || newLocktime > 0xffffffffLL) |
||||||
|
throw runtime_error("Invalid TX locktime requested"); |
||||||
|
|
||||||
|
tx.nLockTime = (unsigned int) newLocktime; |
||||||
|
} |
||||||
|
|
||||||
|
static void MutateTxAddInput(CMutableTransaction& tx, const string& strInput) |
||||||
|
{ |
||||||
|
// separate TXID:VOUT in string
|
||||||
|
size_t pos = strInput.find(':'); |
||||||
|
if ((pos == string::npos) || |
||||||
|
(pos == 0) || |
||||||
|
(pos == (strInput.size() - 1))) |
||||||
|
throw runtime_error("TX input missing separator"); |
||||||
|
|
||||||
|
// extract and validate TXID
|
||||||
|
string strTxid = strInput.substr(0, pos); |
||||||
|
if ((strTxid.size() != 64) || !IsHex(strTxid)) |
||||||
|
throw runtime_error("invalid TX input txid"); |
||||||
|
uint256 txid(strTxid); |
||||||
|
|
||||||
|
static const unsigned int minTxOutSz = 9; |
||||||
|
static const unsigned int maxVout = MAX_BLOCK_SIZE / minTxOutSz; |
||||||
|
|
||||||
|
// extract and validate vout
|
||||||
|
string strVout = strInput.substr(pos + 1, string::npos); |
||||||
|
int vout = atoi(strVout); |
||||||
|
if ((vout < 0) || (vout > (int)maxVout)) |
||||||
|
throw runtime_error("invalid TX input vout"); |
||||||
|
|
||||||
|
// append to transaction input list
|
||||||
|
CTxIn txin(txid, vout); |
||||||
|
tx.vin.push_back(txin); |
||||||
|
} |
||||||
|
|
||||||
|
static void MutateTxAddOutAddr(CMutableTransaction& tx, const string& strInput) |
||||||
|
{ |
||||||
|
// separate VALUE:ADDRESS in string
|
||||||
|
size_t pos = strInput.find(':'); |
||||||
|
if ((pos == string::npos) || |
||||||
|
(pos == 0) || |
||||||
|
(pos == (strInput.size() - 1))) |
||||||
|
throw runtime_error("TX output missing separator"); |
||||||
|
|
||||||
|
// extract and validate VALUE
|
||||||
|
string strValue = strInput.substr(0, pos); |
||||||
|
int64_t value; |
||||||
|
if (!ParseMoney(strValue, value)) |
||||||
|
throw runtime_error("invalid TX output value"); |
||||||
|
|
||||||
|
// extract and validate ADDRESS
|
||||||
|
string strAddr = strInput.substr(pos + 1, string::npos); |
||||||
|
CBitcoinAddress addr(strAddr); |
||||||
|
if (!addr.IsValid()) |
||||||
|
throw runtime_error("invalid TX output address"); |
||||||
|
|
||||||
|
// build standard output script via SetDestination()
|
||||||
|
CScript scriptPubKey; |
||||||
|
scriptPubKey.SetDestination(addr.Get()); |
||||||
|
|
||||||
|
// construct TxOut, append to transaction output list
|
||||||
|
CTxOut txout(value, scriptPubKey); |
||||||
|
tx.vout.push_back(txout); |
||||||
|
} |
||||||
|
|
||||||
|
static void MutateTxAddOutScript(CMutableTransaction& tx, const string& strInput) |
||||||
|
{ |
||||||
|
// separate VALUE:SCRIPT in string
|
||||||
|
size_t pos = strInput.find(':'); |
||||||
|
if ((pos == string::npos) || |
||||||
|
(pos == 0) || |
||||||
|
(pos == (strInput.size() - 1))) |
||||||
|
throw runtime_error("TX output missing separator"); |
||||||
|
|
||||||
|
// extract and validate VALUE
|
||||||
|
string strValue = strInput.substr(0, pos); |
||||||
|
int64_t value; |
||||||
|
if (!ParseMoney(strValue, value)) |
||||||
|
throw runtime_error("invalid TX output value"); |
||||||
|
|
||||||
|
// extract and validate script
|
||||||
|
string strScript = strInput.substr(pos + 1, string::npos); |
||||||
|
CScript scriptPubKey = ParseScript(strScript); // throws on err
|
||||||
|
|
||||||
|
// construct TxOut, append to transaction output list
|
||||||
|
CTxOut txout(value, scriptPubKey); |
||||||
|
tx.vout.push_back(txout); |
||||||
|
} |
||||||
|
|
||||||
|
static void MutateTxDelInput(CMutableTransaction& tx, const string& strInIdx) |
||||||
|
{ |
||||||
|
// parse requested deletion index
|
||||||
|
int inIdx = atoi(strInIdx); |
||||||
|
if (inIdx < 0 || inIdx >= (int)tx.vin.size()) { |
||||||
|
string strErr = "Invalid TX input index '" + strInIdx + "'"; |
||||||
|
throw runtime_error(strErr.c_str()); |
||||||
|
} |
||||||
|
|
||||||
|
// delete input from transaction
|
||||||
|
tx.vin.erase(tx.vin.begin() + inIdx); |
||||||
|
} |
||||||
|
|
||||||
|
static void MutateTxDelOutput(CMutableTransaction& tx, const string& strOutIdx) |
||||||
|
{ |
||||||
|
// parse requested deletion index
|
||||||
|
int outIdx = atoi(strOutIdx); |
||||||
|
if (outIdx < 0 || outIdx >= (int)tx.vout.size()) { |
||||||
|
string strErr = "Invalid TX output index '" + strOutIdx + "'"; |
||||||
|
throw runtime_error(strErr.c_str()); |
||||||
|
} |
||||||
|
|
||||||
|
// delete output from transaction
|
||||||
|
tx.vout.erase(tx.vout.begin() + outIdx); |
||||||
|
} |
||||||
|
|
||||||
|
static const unsigned int N_SIGHASH_OPTS = 6; |
||||||
|
static const struct { |
||||||
|
const char *flagStr; |
||||||
|
int flags; |
||||||
|
} sighashOptions[N_SIGHASH_OPTS] = { |
||||||
|
"ALL", SIGHASH_ALL, |
||||||
|
"NONE", SIGHASH_NONE, |
||||||
|
"SINGLE", SIGHASH_SINGLE, |
||||||
|
"ALL|ANYONECANPAY", SIGHASH_ALL|SIGHASH_ANYONECANPAY, |
||||||
|
"NONE|ANYONECANPAY", SIGHASH_NONE|SIGHASH_ANYONECANPAY, |
||||||
|
"SINGLE|ANYONECANPAY", SIGHASH_SINGLE|SIGHASH_ANYONECANPAY, |
||||||
|
}; |
||||||
|
|
||||||
|
static bool findSighashFlags(int& flags, const string& flagStr) |
||||||
|
{ |
||||||
|
flags = 0; |
||||||
|
|
||||||
|
for (unsigned int i = 0; i < N_SIGHASH_OPTS; i++) { |
||||||
|
if (flagStr == sighashOptions[i].flagStr) { |
||||||
|
flags = sighashOptions[i].flags; |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
uint256 ParseHashUO(map<string,UniValue>& o, string strKey) |
||||||
|
{ |
||||||
|
if (!o.count(strKey)) |
||||||
|
return 0; |
||||||
|
return ParseHashUV(o[strKey], strKey); |
||||||
|
} |
||||||
|
|
||||||
|
vector<unsigned char> ParseHexUO(map<string,UniValue>& o, string strKey) |
||||||
|
{ |
||||||
|
if (!o.count(strKey)) { |
||||||
|
vector<unsigned char> emptyVec; |
||||||
|
return emptyVec; |
||||||
|
} |
||||||
|
return ParseHexUV(o[strKey], strKey); |
||||||
|
} |
||||||
|
|
||||||
|
static void MutateTxSign(CMutableTransaction& tx, const string& flagStr) |
||||||
|
{ |
||||||
|
int nHashType = SIGHASH_ALL; |
||||||
|
|
||||||
|
if (flagStr.size() > 0) |
||||||
|
if (!findSighashFlags(nHashType, flagStr)) |
||||||
|
throw runtime_error("unknown sighash flag/sign option"); |
||||||
|
|
||||||
|
vector<CTransaction> txVariants; |
||||||
|
txVariants.push_back(tx); |
||||||
|
|
||||||
|
// mergedTx will end up with all the signatures; it
|
||||||
|
// starts as a clone of the raw tx:
|
||||||
|
CMutableTransaction mergedTx(txVariants[0]); |
||||||
|
bool fComplete = true; |
||||||
|
CCoinsView viewDummy; |
||||||
|
CCoinsViewCache view(viewDummy); |
||||||
|
|
||||||
|
if (!registers.count("privatekeys")) |
||||||
|
throw runtime_error("privatekeys register variable must be set."); |
||||||
|
bool fGivenKeys = false; |
||||||
|
CBasicKeyStore tempKeystore; |
||||||
|
UniValue keysObj = registers["privatekeys"]; |
||||||
|
fGivenKeys = true; |
||||||
|
|
||||||
|
for (unsigned int kidx = 0; kidx < keysObj.count(); kidx++) { |
||||||
|
if (!keysObj[kidx].isStr()) |
||||||
|
throw runtime_error("privatekey not a string"); |
||||||
|
CBitcoinSecret vchSecret; |
||||||
|
bool fGood = vchSecret.SetString(keysObj[kidx].getValStr()); |
||||||
|
if (!fGood) |
||||||
|
throw runtime_error("privatekey not valid"); |
||||||
|
|
||||||
|
CKey key = vchSecret.GetKey(); |
||||||
|
tempKeystore.AddKey(key); |
||||||
|
} |
||||||
|
|
||||||
|
// Add previous txouts given in the RPC call:
|
||||||
|
if (!registers.count("prevtxs")) |
||||||
|
throw runtime_error("prevtxs register variable must be set."); |
||||||
|
UniValue prevtxsObj = registers["privatekeys"]; |
||||||
|
{ |
||||||
|
for (unsigned int previdx = 0; previdx < prevtxsObj.count(); previdx++) { |
||||||
|
UniValue prevOut = prevtxsObj[previdx]; |
||||||
|
if (!prevOut.isObject()) |
||||||
|
throw runtime_error("expected prevtxs internal object"); |
||||||
|
|
||||||
|
map<string,UniValue::VType> types = map_list_of("txid", UniValue::VSTR)("vout",UniValue::VNUM)("scriptPubKey",UniValue::VSTR); |
||||||
|
if (!prevOut.checkObject(types)) |
||||||
|
throw runtime_error("prevtxs internal object typecheck fail"); |
||||||
|
|
||||||
|
uint256 txid = ParseHashUV(prevOut, "txid"); |
||||||
|
|
||||||
|
int nOut = atoi(prevOut["vout"].getValStr()); |
||||||
|
if (nOut < 0) |
||||||
|
throw runtime_error("vout must be positive"); |
||||||
|
|
||||||
|
vector<unsigned char> pkData(ParseHexUV(prevOut, "scriptPubKey")); |
||||||
|
CScript scriptPubKey(pkData.begin(), pkData.end()); |
||||||
|
|
||||||
|
CCoins coins; |
||||||
|
if (view.GetCoins(txid, coins)) { |
||||||
|
if (coins.IsAvailable(nOut) && coins.vout[nOut].scriptPubKey != scriptPubKey) { |
||||||
|
string err("Previous output scriptPubKey mismatch:\n"); |
||||||
|
err = err + coins.vout[nOut].scriptPubKey.ToString() + "\nvs:\n"+ |
||||||
|
scriptPubKey.ToString(); |
||||||
|
throw runtime_error(err); |
||||||
|
} |
||||||
|
// what todo if txid is known, but the actual output isn't?
|
||||||
|
} |
||||||
|
if ((unsigned int)nOut >= coins.vout.size()) |
||||||
|
coins.vout.resize(nOut+1); |
||||||
|
coins.vout[nOut].scriptPubKey = scriptPubKey; |
||||||
|
coins.vout[nOut].nValue = 0; // we don't know the actual output value
|
||||||
|
view.SetCoins(txid, coins); |
||||||
|
|
||||||
|
// if redeemScript given and private keys given,
|
||||||
|
// add redeemScript to the tempKeystore so it can be signed:
|
||||||
|
if (fGivenKeys && scriptPubKey.IsPayToScriptHash() && |
||||||
|
prevOut.exists("redeemScript")) { |
||||||
|
UniValue v = prevOut["redeemScript"]; |
||||||
|
vector<unsigned char> rsData(ParseHexUV(v, "redeemScript")); |
||||||
|
CScript redeemScript(rsData.begin(), rsData.end()); |
||||||
|
tempKeystore.AddCScript(redeemScript); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const CKeyStore& keystore = tempKeystore; |
||||||
|
|
||||||
|
bool fHashSingle = ((nHashType & ~SIGHASH_ANYONECANPAY) == SIGHASH_SINGLE); |
||||||
|
|
||||||
|
// Sign what we can:
|
||||||
|
for (unsigned int i = 0; i < mergedTx.vin.size(); i++) { |
||||||
|
CTxIn& txin = mergedTx.vin[i]; |
||||||
|
CCoins coins; |
||||||
|
if (!view.GetCoins(txin.prevout.hash, coins) || !coins.IsAvailable(txin.prevout.n)) { |
||||||
|
fComplete = false; |
||||||
|
continue; |
||||||
|
} |
||||||
|
const CScript& prevPubKey = coins.vout[txin.prevout.n].scriptPubKey; |
||||||
|
|
||||||
|
txin.scriptSig.clear(); |
||||||
|
// Only sign SIGHASH_SINGLE if there's a corresponding output:
|
||||||
|
if (!fHashSingle || (i < mergedTx.vout.size())) |
||||||
|
SignSignature(keystore, prevPubKey, mergedTx, i, nHashType); |
||||||
|
|
||||||
|
// ... and merge in other signatures:
|
||||||
|
BOOST_FOREACH(const CTransaction& txv, txVariants) { |
||||||
|
txin.scriptSig = CombineSignatures(prevPubKey, mergedTx, i, txin.scriptSig, txv.vin[i].scriptSig); |
||||||
|
} |
||||||
|
if (!VerifyScript(txin.scriptSig, prevPubKey, mergedTx, i, STANDARD_SCRIPT_VERIFY_FLAGS, 0)) |
||||||
|
fComplete = false; |
||||||
|
} |
||||||
|
|
||||||
|
if (fComplete) { |
||||||
|
// do nothing... for now
|
||||||
|
// perhaps store this for later optional JSON output
|
||||||
|
} |
||||||
|
|
||||||
|
tx = mergedTx; |
||||||
|
} |
||||||
|
|
||||||
|
static void MutateTx(CMutableTransaction& tx, const string& command, |
||||||
|
const string& commandVal) |
||||||
|
{ |
||||||
|
if (command == "nversion") |
||||||
|
MutateTxVersion(tx, commandVal); |
||||||
|
else if (command == "locktime") |
||||||
|
MutateTxLocktime(tx, commandVal); |
||||||
|
|
||||||
|
else if (command == "delin") |
||||||
|
MutateTxDelInput(tx, commandVal); |
||||||
|
else if (command == "in") |
||||||
|
MutateTxAddInput(tx, commandVal); |
||||||
|
|
||||||
|
else if (command == "delout") |
||||||
|
MutateTxDelOutput(tx, commandVal); |
||||||
|
else if (command == "outaddr") |
||||||
|
MutateTxAddOutAddr(tx, commandVal); |
||||||
|
else if (command == "outscript") |
||||||
|
MutateTxAddOutScript(tx, commandVal); |
||||||
|
|
||||||
|
else if (command == "sign") |
||||||
|
MutateTxSign(tx, commandVal); |
||||||
|
|
||||||
|
else if (command == "load") |
||||||
|
RegisterLoad(commandVal); |
||||||
|
|
||||||
|
else if (command == "set") |
||||||
|
RegisterSet(commandVal); |
||||||
|
|
||||||
|
else |
||||||
|
throw runtime_error("unknown command"); |
||||||
|
} |
||||||
|
|
||||||
|
static void OutputTxJSON(const CTransaction& tx) |
||||||
|
{ |
||||||
|
UniValue entry(UniValue::VOBJ); |
||||||
|
TxToUniv(tx, 0, entry); |
||||||
|
|
||||||
|
string jsonOutput = entry.write(4); |
||||||
|
fprintf(stdout, "%s\n", jsonOutput.c_str()); |
||||||
|
} |
||||||
|
|
||||||
|
static void OutputTxHex(const CTransaction& tx) |
||||||
|
{ |
||||||
|
string strHex = EncodeHexTx(tx); |
||||||
|
|
||||||
|
fprintf(stdout, "%s\n", strHex.c_str()); |
||||||
|
} |
||||||
|
|
||||||
|
static void OutputTx(const CTransaction& tx) |
||||||
|
{ |
||||||
|
if (GetBoolArg("-json", false)) |
||||||
|
OutputTxJSON(tx); |
||||||
|
else |
||||||
|
OutputTxHex(tx); |
||||||
|
} |
||||||
|
|
||||||
|
static int CommandLineRawTx(int argc, char* argv[]) |
||||||
|
{ |
||||||
|
string strPrint; |
||||||
|
int nRet = 0; |
||||||
|
try { |
||||||
|
// Skip switches
|
||||||
|
while (argc > 1 && IsSwitchChar(argv[1][0])) { |
||||||
|
argc--; |
||||||
|
argv++; |
||||||
|
} |
||||||
|
|
||||||
|
CTransaction txDecodeTmp; |
||||||
|
int startArg; |
||||||
|
|
||||||
|
if (!fCreateBlank) { |
||||||
|
// require at least one param
|
||||||
|
if (argc < 2) |
||||||
|
throw runtime_error("too few parameters"); |
||||||
|
|
||||||
|
// param: hex-encoded bitcoin transaction
|
||||||
|
string strHexTx(argv[1]); |
||||||
|
|
||||||
|
if (!DecodeHexTx(txDecodeTmp, strHexTx)) |
||||||
|
throw runtime_error("invalid transaction encoding"); |
||||||
|
|
||||||
|
startArg = 2; |
||||||
|
} else |
||||||
|
startArg = 1; |
||||||
|
|
||||||
|
CMutableTransaction tx(txDecodeTmp); |
||||||
|
|
||||||
|
for (int i = startArg; i < argc; i++) { |
||||||
|
string arg = argv[i]; |
||||||
|
string key, value; |
||||||
|
size_t eqpos = arg.find('='); |
||||||
|
if (eqpos == string::npos) |
||||||
|
key = arg; |
||||||
|
else { |
||||||
|
key = arg.substr(0, eqpos); |
||||||
|
value = arg.substr(eqpos + 1); |
||||||
|
} |
||||||
|
|
||||||
|
MutateTx(tx, key, value); |
||||||
|
} |
||||||
|
|
||||||
|
OutputTx(tx); |
||||||
|
} |
||||||
|
|
||||||
|
catch (boost::thread_interrupted) { |
||||||
|
throw; |
||||||
|
} |
||||||
|
catch (std::exception& e) { |
||||||
|
strPrint = string("error: ") + e.what(); |
||||||
|
nRet = EXIT_FAILURE; |
||||||
|
} |
||||||
|
catch (...) { |
||||||
|
PrintExceptionContinue(NULL, "CommandLineRawTx()"); |
||||||
|
throw; |
||||||
|
} |
||||||
|
|
||||||
|
if (strPrint != "") { |
||||||
|
fprintf((nRet == 0 ? stdout : stderr), "%s\n", strPrint.c_str()); |
||||||
|
} |
||||||
|
return nRet; |
||||||
|
} |
||||||
|
|
||||||
|
int main(int argc, char* argv[]) |
||||||
|
{ |
||||||
|
SetupEnvironment(); |
||||||
|
|
||||||
|
try { |
||||||
|
if(!AppInitRawTx(argc, argv)) |
||||||
|
return EXIT_FAILURE; |
||||||
|
} |
||||||
|
catch (std::exception& e) { |
||||||
|
PrintExceptionContinue(&e, "AppInitRawTx()"); |
||||||
|
return EXIT_FAILURE; |
||||||
|
} catch (...) { |
||||||
|
PrintExceptionContinue(NULL, "AppInitRawTx()"); |
||||||
|
return EXIT_FAILURE; |
||||||
|
} |
||||||
|
|
||||||
|
int ret = EXIT_FAILURE; |
||||||
|
try { |
||||||
|
ret = CommandLineRawTx(argc, argv); |
||||||
|
} |
||||||
|
catch (std::exception& e) { |
||||||
|
PrintExceptionContinue(&e, "CommandLineRawTx()"); |
||||||
|
} catch (...) { |
||||||
|
PrintExceptionContinue(NULL, "CommandLineRawTx()"); |
||||||
|
} |
||||||
|
return ret; |
||||||
|
} |
||||||
|
|
@ -0,0 +1,233 @@ |
|||||||
|
// Copyright 2014 BitPay Inc.
|
||||||
|
// Distributed under the MIT/X11 software license, see the accompanying
|
||||||
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
#include <stdint.h> |
||||||
|
#include <ctype.h> |
||||||
|
#include <sstream> |
||||||
|
#include "univalue.h" |
||||||
|
|
||||||
|
using namespace std; |
||||||
|
|
||||||
|
static const UniValue nullValue; |
||||||
|
|
||||||
|
void UniValue::clear() |
||||||
|
{ |
||||||
|
typ = VNULL; |
||||||
|
val.clear(); |
||||||
|
keys.clear(); |
||||||
|
values.clear(); |
||||||
|
} |
||||||
|
|
||||||
|
bool UniValue::setNull() |
||||||
|
{ |
||||||
|
clear(); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
bool UniValue::setBool(bool val_) |
||||||
|
{ |
||||||
|
clear(); |
||||||
|
typ = VBOOL; |
||||||
|
if (val_) |
||||||
|
val = "1"; |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
static bool validNumStr(const string& s) |
||||||
|
{ |
||||||
|
string tokenVal; |
||||||
|
unsigned int consumed; |
||||||
|
enum jtokentype tt = getJsonToken(tokenVal, consumed, s.c_str()); |
||||||
|
return (tt == JTOK_NUMBER); |
||||||
|
} |
||||||
|
|
||||||
|
bool UniValue::setNumStr(const string& val_) |
||||||
|
{ |
||||||
|
if (!validNumStr(val)) |
||||||
|
return false; |
||||||
|
|
||||||
|
clear(); |
||||||
|
typ = VNUM; |
||||||
|
val = val_; |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
bool UniValue::setInt(uint64_t val) |
||||||
|
{ |
||||||
|
string s; |
||||||
|
ostringstream oss; |
||||||
|
|
||||||
|
oss << val; |
||||||
|
|
||||||
|
return setNumStr(oss.str()); |
||||||
|
} |
||||||
|
|
||||||
|
bool UniValue::setInt(int64_t val) |
||||||
|
{ |
||||||
|
string s; |
||||||
|
ostringstream oss; |
||||||
|
|
||||||
|
oss << val; |
||||||
|
|
||||||
|
return setNumStr(oss.str()); |
||||||
|
} |
||||||
|
|
||||||
|
bool UniValue::setFloat(double val) |
||||||
|
{ |
||||||
|
string s; |
||||||
|
ostringstream oss; |
||||||
|
|
||||||
|
oss << val; |
||||||
|
|
||||||
|
return setNumStr(oss.str()); |
||||||
|
} |
||||||
|
|
||||||
|
bool UniValue::setStr(const string& val_) |
||||||
|
{ |
||||||
|
clear(); |
||||||
|
typ = VSTR; |
||||||
|
val = val_; |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
bool UniValue::setArray() |
||||||
|
{ |
||||||
|
clear(); |
||||||
|
typ = VARR; |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
bool UniValue::setObject() |
||||||
|
{ |
||||||
|
clear(); |
||||||
|
typ = VOBJ; |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
bool UniValue::push_back(const UniValue& val) |
||||||
|
{ |
||||||
|
if (typ != VARR) |
||||||
|
return false; |
||||||
|
|
||||||
|
values.push_back(val); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
bool UniValue::push_backV(const std::vector<UniValue>& vec) |
||||||
|
{ |
||||||
|
if (typ != VARR) |
||||||
|
return false; |
||||||
|
|
||||||
|
values.insert(values.end(), vec.begin(), vec.end()); |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
bool UniValue::pushKV(const std::string& key, const UniValue& val) |
||||||
|
{ |
||||||
|
if (typ != VOBJ) |
||||||
|
return false; |
||||||
|
|
||||||
|
keys.push_back(key); |
||||||
|
values.push_back(val); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
bool UniValue::pushKVs(const UniValue& obj) |
||||||
|
{ |
||||||
|
if (typ != VOBJ || obj.typ != VOBJ) |
||||||
|
return false; |
||||||
|
|
||||||
|
for (unsigned int i = 0; i < obj.keys.size(); i++) { |
||||||
|
keys.push_back(obj.keys[i]); |
||||||
|
values.push_back(obj.values[i]); |
||||||
|
} |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
bool UniValue::getArray(std::vector<UniValue>& arr) |
||||||
|
{ |
||||||
|
if (typ != VARR) |
||||||
|
return false; |
||||||
|
|
||||||
|
arr = values; |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
bool UniValue::getObject(std::map<std::string,UniValue>& obj) |
||||||
|
{ |
||||||
|
if (typ != VOBJ) |
||||||
|
return false; |
||||||
|
|
||||||
|
obj.clear(); |
||||||
|
for (unsigned int i = 0; i < keys.size(); i++) { |
||||||
|
obj[keys[i]] = values[i]; |
||||||
|
} |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
int UniValue::findKey(const std::string& key) const |
||||||
|
{ |
||||||
|
for (unsigned int i = 0; i < keys.size(); i++) { |
||||||
|
if (keys[i] == key) |
||||||
|
return (int) i; |
||||||
|
} |
||||||
|
|
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
bool UniValue::checkObject(const std::map<std::string,UniValue::VType>& t) |
||||||
|
{ |
||||||
|
for (std::map<std::string,UniValue::VType>::const_iterator it = t.begin(); |
||||||
|
it != t.end(); it++) { |
||||||
|
int idx = findKey(it->first); |
||||||
|
if (idx < 0) |
||||||
|
return false; |
||||||
|
|
||||||
|
if (values[idx].getType() != it->second) |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
const UniValue& UniValue::operator[](const std::string& key) const |
||||||
|
{ |
||||||
|
if (typ != VOBJ) |
||||||
|
return nullValue; |
||||||
|
|
||||||
|
int index = findKey(key); |
||||||
|
if (index < 0) |
||||||
|
return nullValue; |
||||||
|
|
||||||
|
return values[index]; |
||||||
|
} |
||||||
|
|
||||||
|
const UniValue& UniValue::operator[](unsigned int index) const |
||||||
|
{ |
||||||
|
if (typ != VOBJ && typ != VARR) |
||||||
|
return nullValue; |
||||||
|
if (index >= values.size()) |
||||||
|
return nullValue; |
||||||
|
|
||||||
|
return values[index]; |
||||||
|
} |
||||||
|
|
||||||
|
const char *uvTypeName(UniValue::VType t) |
||||||
|
{ |
||||||
|
switch (t) { |
||||||
|
case UniValue::VNULL: return "null"; |
||||||
|
case UniValue::VBOOL: return "bool"; |
||||||
|
case UniValue::VOBJ: return "object"; |
||||||
|
case UniValue::VARR: return "array"; |
||||||
|
case UniValue::VSTR: return "string"; |
||||||
|
case UniValue::VNUM: return "number"; |
||||||
|
} |
||||||
|
|
||||||
|
// not reached
|
||||||
|
return NULL; |
||||||
|
} |
||||||
|
|
@ -0,0 +1,157 @@ |
|||||||
|
// Copyright 2014 BitPay Inc.
|
||||||
|
// Distributed under the MIT/X11 software license, see the accompanying
|
||||||
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
#ifndef __UNIVALUE_H__ |
||||||
|
#define __UNIVALUE_H__ |
||||||
|
|
||||||
|
#include <stdint.h> |
||||||
|
#include <string> |
||||||
|
#include <vector> |
||||||
|
#include <map> |
||||||
|
#include <cassert> |
||||||
|
|
||||||
|
class UniValue { |
||||||
|
public: |
||||||
|
enum VType { VNULL, VOBJ, VARR, VSTR, VNUM, VBOOL, }; |
||||||
|
|
||||||
|
UniValue() { typ = VNULL; } |
||||||
|
UniValue(UniValue::VType initialType, const std::string& initialStr = "") { |
||||||
|
typ = initialType; |
||||||
|
val = initialStr; |
||||||
|
} |
||||||
|
UniValue(uint64_t val_) { |
||||||
|
setInt(val_); |
||||||
|
} |
||||||
|
UniValue(int64_t val_) { |
||||||
|
setInt(val_); |
||||||
|
} |
||||||
|
UniValue(int val_) { |
||||||
|
setInt(val_); |
||||||
|
} |
||||||
|
UniValue(double val_) { |
||||||
|
setFloat(val_); |
||||||
|
} |
||||||
|
UniValue(const std::string& val_) { |
||||||
|
setStr(val_); |
||||||
|
} |
||||||
|
UniValue(const char *val_) { |
||||||
|
std::string s(val_); |
||||||
|
setStr(s); |
||||||
|
} |
||||||
|
~UniValue() {} |
||||||
|
|
||||||
|
void clear(); |
||||||
|
|
||||||
|
bool setNull(); |
||||||
|
bool setBool(bool val); |
||||||
|
bool setNumStr(const std::string& val); |
||||||
|
bool setInt(uint64_t val); |
||||||
|
bool setInt(int64_t val); |
||||||
|
bool setInt(int val) { return setInt((int64_t)val); } |
||||||
|
bool setFloat(double val); |
||||||
|
bool setStr(const std::string& val); |
||||||
|
bool setArray(); |
||||||
|
bool setObject(); |
||||||
|
|
||||||
|
enum VType getType() const { return typ; } |
||||||
|
std::string getValStr() const { return val; } |
||||||
|
bool empty() const { return (values.size() == 0); } |
||||||
|
|
||||||
|
size_t count() const { return values.size(); } |
||||||
|
|
||||||
|
bool getBool() const { return isTrue(); } |
||||||
|
bool getArray(std::vector<UniValue>& arr); |
||||||
|
bool getObject(std::map<std::string,UniValue>& obj); |
||||||
|
bool checkObject(const std::map<std::string,UniValue::VType>& memberTypes); |
||||||
|
const UniValue& operator[](const std::string& key) const; |
||||||
|
const UniValue& operator[](unsigned int index) const; |
||||||
|
bool exists(const std::string& key) const { return (findKey(key) >= 0); } |
||||||
|
|
||||||
|
bool isNull() const { return (typ == VNULL); } |
||||||
|
bool isTrue() const { return (typ == VBOOL) && (val == "1"); } |
||||||
|
bool isFalse() const { return (!isTrue()); } |
||||||
|
bool isBool() const { return (typ == VBOOL); } |
||||||
|
bool isStr() const { return (typ == VSTR); } |
||||||
|
bool isNum() const { return (typ == VNUM); } |
||||||
|
bool isArray() const { return (typ == VARR); } |
||||||
|
bool isObject() const { return (typ == VOBJ); } |
||||||
|
|
||||||
|
bool push_back(const UniValue& val); |
||||||
|
bool push_back(const std::string& val_) { |
||||||
|
UniValue tmpVal(VSTR, val_); |
||||||
|
return push_back(tmpVal); |
||||||
|
} |
||||||
|
bool push_back(const char *val_) { |
||||||
|
std::string s(val_); |
||||||
|
return push_back(s); |
||||||
|
} |
||||||
|
bool push_backV(const std::vector<UniValue>& vec); |
||||||
|
|
||||||
|
bool pushKV(const std::string& key, const UniValue& val); |
||||||
|
bool pushKV(const std::string& key, const std::string& val) { |
||||||
|
UniValue tmpVal(VSTR, val); |
||||||
|
return pushKV(key, tmpVal); |
||||||
|
} |
||||||
|
bool pushKV(const std::string& key, const char *val_) { |
||||||
|
std::string val(val_); |
||||||
|
return pushKV(key, val); |
||||||
|
} |
||||||
|
bool pushKV(const std::string& key, int64_t val) { |
||||||
|
UniValue tmpVal(val); |
||||||
|
return pushKV(key, tmpVal); |
||||||
|
} |
||||||
|
bool pushKV(const std::string& key, uint64_t val) { |
||||||
|
UniValue tmpVal(val); |
||||||
|
return pushKV(key, tmpVal); |
||||||
|
} |
||||||
|
bool pushKV(const std::string& key, int val) { |
||||||
|
UniValue tmpVal((int64_t)val); |
||||||
|
return pushKV(key, tmpVal); |
||||||
|
} |
||||||
|
bool pushKV(const std::string& key, double val) { |
||||||
|
UniValue tmpVal(val); |
||||||
|
return pushKV(key, tmpVal); |
||||||
|
} |
||||||
|
bool pushKVs(const UniValue& obj); |
||||||
|
|
||||||
|
std::string write(unsigned int prettyIndent = 0, |
||||||
|
unsigned int indentLevel = 0) const; |
||||||
|
|
||||||
|
bool read(const char *raw); |
||||||
|
bool read(const std::string& rawStr) { |
||||||
|
return read(rawStr.c_str()); |
||||||
|
} |
||||||
|
|
||||||
|
private: |
||||||
|
UniValue::VType typ; |
||||||
|
std::string val; // numbers are stored as C++ strings
|
||||||
|
std::vector<std::string> keys; |
||||||
|
std::vector<UniValue> values; |
||||||
|
|
||||||
|
int findKey(const std::string& key) const; |
||||||
|
void writeArray(unsigned int prettyIndent, unsigned int indentLevel, std::string& s) const; |
||||||
|
void writeObject(unsigned int prettyIndent, unsigned int indentLevel, std::string& s) const; |
||||||
|
}; |
||||||
|
|
||||||
|
enum jtokentype { |
||||||
|
JTOK_ERR = -1, |
||||||
|
JTOK_NONE = 0, // eof
|
||||||
|
JTOK_OBJ_OPEN, |
||||||
|
JTOK_OBJ_CLOSE, |
||||||
|
JTOK_ARR_OPEN, |
||||||
|
JTOK_ARR_CLOSE, |
||||||
|
JTOK_COLON, |
||||||
|
JTOK_COMMA, |
||||||
|
JTOK_KW_NULL, |
||||||
|
JTOK_KW_TRUE, |
||||||
|
JTOK_KW_FALSE, |
||||||
|
JTOK_NUMBER, |
||||||
|
JTOK_STRING, |
||||||
|
}; |
||||||
|
|
||||||
|
extern enum jtokentype getJsonToken(std::string& tokenVal, |
||||||
|
unsigned int& consumed, const char *raw); |
||||||
|
extern const char *uvTypeName(UniValue::VType t); |
||||||
|
|
||||||
|
#endif // __UNIVALUE_H__
|
@ -0,0 +1,390 @@ |
|||||||
|
// Copyright 2014 BitPay Inc.
|
||||||
|
// Distributed under the MIT/X11 software license, see the accompanying
|
||||||
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
#include <string.h> |
||||||
|
#include <vector> |
||||||
|
#include <stdio.h> |
||||||
|
#include "univalue.h" |
||||||
|
|
||||||
|
using namespace std; |
||||||
|
|
||||||
|
// convert hexadecimal string to unsigned integer
|
||||||
|
static const char *hatoui(const char *first, const char *last, |
||||||
|
unsigned int& out) |
||||||
|
{ |
||||||
|
unsigned int result = 0; |
||||||
|
for (; first != last; ++first) |
||||||
|
{ |
||||||
|
int digit; |
||||||
|
if (isdigit(*first)) |
||||||
|
digit = *first - '0'; |
||||||
|
|
||||||
|
else if (*first >= 'a' && *first <= 'f') |
||||||
|
digit = *first - 'a' + 10; |
||||||
|
|
||||||
|
else if (*first >= 'A' && *first <= 'F') |
||||||
|
digit = *first - 'A' + 10; |
||||||
|
|
||||||
|
else |
||||||
|
break; |
||||||
|
|
||||||
|
result = 16 * result + digit; |
||||||
|
} |
||||||
|
out = result; |
||||||
|
|
||||||
|
return first; |
||||||
|
} |
||||||
|
|
||||||
|
enum jtokentype getJsonToken(string& tokenVal, unsigned int& consumed, |
||||||
|
const char *raw) |
||||||
|
{ |
||||||
|
tokenVal.clear(); |
||||||
|
consumed = 0; |
||||||
|
|
||||||
|
const char *rawStart = raw; |
||||||
|
|
||||||
|
while ((*raw) && (isspace(*raw))) // skip whitespace
|
||||||
|
raw++; |
||||||
|
|
||||||
|
switch (*raw) { |
||||||
|
|
||||||
|
case 0: |
||||||
|
return JTOK_NONE; |
||||||
|
|
||||||
|
case '{': |
||||||
|
raw++; |
||||||
|
consumed = (raw - rawStart); |
||||||
|
return JTOK_OBJ_OPEN; |
||||||
|
case '}': |
||||||
|
raw++; |
||||||
|
consumed = (raw - rawStart); |
||||||
|
return JTOK_OBJ_CLOSE; |
||||||
|
case '[': |
||||||
|
raw++; |
||||||
|
consumed = (raw - rawStart); |
||||||
|
return JTOK_ARR_OPEN; |
||||||
|
case ']': |
||||||
|
raw++; |
||||||
|
consumed = (raw - rawStart); |
||||||
|
return JTOK_ARR_CLOSE; |
||||||
|
|
||||||
|
case ':': |
||||||
|
raw++; |
||||||
|
consumed = (raw - rawStart); |
||||||
|
return JTOK_COLON; |
||||||
|
case ',': |
||||||
|
raw++; |
||||||
|
consumed = (raw - rawStart); |
||||||
|
return JTOK_COMMA; |
||||||
|
|
||||||
|
case 'n': |
||||||
|
case 't': |
||||||
|
case 'f': |
||||||
|
if (!strncmp(raw, "null", 4)) { |
||||||
|
raw += 4; |
||||||
|
consumed = (raw - rawStart); |
||||||
|
return JTOK_KW_NULL; |
||||||
|
} else if (!strncmp(raw, "true", 4)) { |
||||||
|
raw += 4; |
||||||
|
consumed = (raw - rawStart); |
||||||
|
return JTOK_KW_TRUE; |
||||||
|
} else if (!strncmp(raw, "false", 5)) { |
||||||
|
raw += 5; |
||||||
|
consumed = (raw - rawStart); |
||||||
|
return JTOK_KW_FALSE; |
||||||
|
} else |
||||||
|
return JTOK_ERR; |
||||||
|
|
||||||
|
case '-': |
||||||
|
case '0': |
||||||
|
case '1': |
||||||
|
case '2': |
||||||
|
case '3': |
||||||
|
case '4': |
||||||
|
case '5': |
||||||
|
case '6': |
||||||
|
case '7': |
||||||
|
case '8': |
||||||
|
case '9': { |
||||||
|
// part 1: int
|
||||||
|
string numStr; |
||||||
|
|
||||||
|
const char *first = raw; |
||||||
|
|
||||||
|
const char *firstDigit = first; |
||||||
|
if (!isdigit(*firstDigit)) |
||||||
|
firstDigit++; |
||||||
|
if ((*firstDigit == '0') && isdigit(firstDigit[1])) |
||||||
|
return JTOK_ERR; |
||||||
|
|
||||||
|
numStr += *raw; // copy first char
|
||||||
|
raw++; |
||||||
|
|
||||||
|
if ((*first == '-') && (!isdigit(*raw))) |
||||||
|
return JTOK_ERR; |
||||||
|
|
||||||
|
while ((*raw) && isdigit(*raw)) { // copy digits
|
||||||
|
numStr += *raw; |
||||||
|
raw++; |
||||||
|
} |
||||||
|
|
||||||
|
// part 2: frac
|
||||||
|
if (*raw == '.') { |
||||||
|
numStr += *raw; // copy .
|
||||||
|
raw++; |
||||||
|
|
||||||
|
if (!isdigit(*raw)) |
||||||
|
return JTOK_ERR; |
||||||
|
while ((*raw) && isdigit(*raw)) { // copy digits
|
||||||
|
numStr += *raw; |
||||||
|
raw++; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// part 3: exp
|
||||||
|
if (*raw == 'e' || *raw == 'E') { |
||||||
|
numStr += *raw; // copy E
|
||||||
|
raw++; |
||||||
|
|
||||||
|
if (*raw == '-' || *raw == '+') { // copy +/-
|
||||||
|
numStr += *raw; |
||||||
|
raw++; |
||||||
|
} |
||||||
|
|
||||||
|
if (!isdigit(*raw)) |
||||||
|
return JTOK_ERR; |
||||||
|
while ((*raw) && isdigit(*raw)) { // copy digits
|
||||||
|
numStr += *raw; |
||||||
|
raw++; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
tokenVal = numStr; |
||||||
|
consumed = (raw - rawStart); |
||||||
|
return JTOK_NUMBER; |
||||||
|
} |
||||||
|
|
||||||
|
case '"': { |
||||||
|
raw++; // skip "
|
||||||
|
|
||||||
|
string valStr; |
||||||
|
|
||||||
|
while (*raw) { |
||||||
|
if (*raw < 0x20) |
||||||
|
return JTOK_ERR; |
||||||
|
|
||||||
|
else if (*raw == '\\') { |
||||||
|
raw++; // skip backslash
|
||||||
|
|
||||||
|
switch (*raw) { |
||||||
|
case '"': valStr += "\""; break; |
||||||
|
case '\\': valStr += "\\"; break; |
||||||
|
case '/': valStr += "/"; break; |
||||||
|
case 'b': valStr += "\b"; break; |
||||||
|
case 'f': valStr += "\f"; break; |
||||||
|
case 'n': valStr += "\n"; break; |
||||||
|
case 'r': valStr += "\r"; break; |
||||||
|
case 't': valStr += "\t"; break; |
||||||
|
|
||||||
|
case 'u': { |
||||||
|
char buf[4] = {0,0,0,0}; |
||||||
|
char *last = &buf[0]; |
||||||
|
unsigned int codepoint; |
||||||
|
if (hatoui(raw + 1, raw + 1 + 4, codepoint) != |
||||||
|
raw + 1 + 4) |
||||||
|
return JTOK_ERR; |
||||||
|
|
||||||
|
if (codepoint <= 0x7f) |
||||||
|
*last = (char)codepoint; |
||||||
|
else if (codepoint <= 0x7FF) { |
||||||
|
*last++ = (char)(0xC0 | (codepoint >> 6)); |
||||||
|
*last = (char)(0x80 | (codepoint & 0x3F)); |
||||||
|
} else if (codepoint <= 0xFFFF) { |
||||||
|
*last++ = (char)(0xE0 | (codepoint >> 12)); |
||||||
|
*last++ = (char)(0x80 | ((codepoint >> 6) & 0x3F)); |
||||||
|
*last = (char)(0x80 | (codepoint & 0x3F)); |
||||||
|
} |
||||||
|
|
||||||
|
valStr += buf; |
||||||
|
raw += 4; |
||||||
|
break; |
||||||
|
} |
||||||
|
default: |
||||||
|
return JTOK_ERR; |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
raw++; // skip esc'd char
|
||||||
|
} |
||||||
|
|
||||||
|
else if (*raw == '"') { |
||||||
|
raw++; // skip "
|
||||||
|
break; // stop scanning
|
||||||
|
} |
||||||
|
|
||||||
|
else { |
||||||
|
valStr += *raw; |
||||||
|
raw++; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
tokenVal = valStr; |
||||||
|
consumed = (raw - rawStart); |
||||||
|
return JTOK_STRING; |
||||||
|
} |
||||||
|
|
||||||
|
default: |
||||||
|
return JTOK_ERR; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
bool UniValue::read(const char *raw) |
||||||
|
{ |
||||||
|
clear(); |
||||||
|
|
||||||
|
bool expectName = false; |
||||||
|
bool expectColon = false; |
||||||
|
vector<UniValue*> stack; |
||||||
|
|
||||||
|
enum jtokentype tok = JTOK_NONE; |
||||||
|
enum jtokentype last_tok = JTOK_NONE; |
||||||
|
while (1) { |
||||||
|
last_tok = tok; |
||||||
|
|
||||||
|
string tokenVal; |
||||||
|
unsigned int consumed; |
||||||
|
tok = getJsonToken(tokenVal, consumed, raw); |
||||||
|
if (tok == JTOK_NONE || tok == JTOK_ERR) |
||||||
|
break; |
||||||
|
raw += consumed; |
||||||
|
|
||||||
|
switch (tok) { |
||||||
|
|
||||||
|
case JTOK_OBJ_OPEN: |
||||||
|
case JTOK_ARR_OPEN: { |
||||||
|
VType utyp = (tok == JTOK_OBJ_OPEN ? VOBJ : VARR); |
||||||
|
if (!stack.size()) { |
||||||
|
if (utyp == VOBJ) |
||||||
|
setObject(); |
||||||
|
else |
||||||
|
setArray(); |
||||||
|
stack.push_back(this); |
||||||
|
} else { |
||||||
|
UniValue tmpVal(utyp); |
||||||
|
UniValue *top = stack.back(); |
||||||
|
top->values.push_back(tmpVal); |
||||||
|
|
||||||
|
UniValue *newTop = &(top->values.back()); |
||||||
|
stack.push_back(newTop); |
||||||
|
} |
||||||
|
|
||||||
|
if (utyp == VOBJ) |
||||||
|
expectName = true; |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
case JTOK_OBJ_CLOSE: |
||||||
|
case JTOK_ARR_CLOSE: { |
||||||
|
if (!stack.size() || expectColon || (last_tok == JTOK_COMMA)) |
||||||
|
return false; |
||||||
|
|
||||||
|
VType utyp = (tok == JTOK_OBJ_CLOSE ? VOBJ : VARR); |
||||||
|
UniValue *top = stack.back(); |
||||||
|
if (utyp != top->getType()) |
||||||
|
return false; |
||||||
|
|
||||||
|
stack.pop_back(); |
||||||
|
expectName = false; |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
case JTOK_COLON: { |
||||||
|
if (!stack.size() || expectName || !expectColon) |
||||||
|
return false; |
||||||
|
|
||||||
|
UniValue *top = stack.back(); |
||||||
|
if (top->getType() != VOBJ) |
||||||
|
return false; |
||||||
|
|
||||||
|
expectColon = false; |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
case JTOK_COMMA: { |
||||||
|
if (!stack.size() || expectName || expectColon || |
||||||
|
(last_tok == JTOK_COMMA) || (last_tok == JTOK_ARR_OPEN)) |
||||||
|
return false; |
||||||
|
|
||||||
|
UniValue *top = stack.back(); |
||||||
|
if (top->getType() == VOBJ) |
||||||
|
expectName = true; |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
case JTOK_KW_NULL: |
||||||
|
case JTOK_KW_TRUE: |
||||||
|
case JTOK_KW_FALSE: { |
||||||
|
if (!stack.size() || expectName || expectColon) |
||||||
|
return false; |
||||||
|
|
||||||
|
UniValue tmpVal; |
||||||
|
switch (tok) { |
||||||
|
case JTOK_KW_NULL: |
||||||
|
// do nothing more
|
||||||
|
break; |
||||||
|
case JTOK_KW_TRUE: |
||||||
|
tmpVal.setBool(true); |
||||||
|
break; |
||||||
|
case JTOK_KW_FALSE: |
||||||
|
tmpVal.setBool(false); |
||||||
|
break; |
||||||
|
default: /* impossible */ break; |
||||||
|
} |
||||||
|
|
||||||
|
UniValue *top = stack.back(); |
||||||
|
top->values.push_back(tmpVal); |
||||||
|
|
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
case JTOK_NUMBER: { |
||||||
|
if (!stack.size() || expectName || expectColon) |
||||||
|
return false; |
||||||
|
|
||||||
|
UniValue tmpVal(VNUM, tokenVal); |
||||||
|
UniValue *top = stack.back(); |
||||||
|
top->values.push_back(tmpVal); |
||||||
|
|
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
case JTOK_STRING: { |
||||||
|
if (!stack.size()) |
||||||
|
return false; |
||||||
|
|
||||||
|
UniValue *top = stack.back(); |
||||||
|
|
||||||
|
if (expectName) { |
||||||
|
top->keys.push_back(tokenVal); |
||||||
|
expectName = false; |
||||||
|
expectColon = true; |
||||||
|
} else { |
||||||
|
UniValue tmpVal(VSTR, tokenVal); |
||||||
|
top->values.push_back(tmpVal); |
||||||
|
} |
||||||
|
|
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
default: |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (stack.size() != 0) |
||||||
|
return false; |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
@ -0,0 +1,145 @@ |
|||||||
|
// Copyright 2014 BitPay Inc.
|
||||||
|
// Distributed under the MIT/X11 software license, see the accompanying
|
||||||
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
#include <ctype.h> |
||||||
|
#include <stdio.h> |
||||||
|
#include "univalue.h" |
||||||
|
|
||||||
|
// TODO: Using UTF8
|
||||||
|
|
||||||
|
using namespace std; |
||||||
|
|
||||||
|
static bool initEscapes; |
||||||
|
static const char *escapes[256]; |
||||||
|
|
||||||
|
static void initJsonEscape() |
||||||
|
{ |
||||||
|
escapes['"'] = "\\\""; |
||||||
|
escapes['\\'] = "\\\\"; |
||||||
|
escapes['/'] = "\\/"; |
||||||
|
escapes['\b'] = "\\b"; |
||||||
|
escapes['\f'] = "\\f"; |
||||||
|
escapes['\n'] = "\\n"; |
||||||
|
escapes['\r'] = "\\r"; |
||||||
|
escapes['\t'] = "\\t"; |
||||||
|
|
||||||
|
initEscapes = true; |
||||||
|
} |
||||||
|
|
||||||
|
static string json_escape(const string& inS) |
||||||
|
{ |
||||||
|
if (!initEscapes) |
||||||
|
initJsonEscape(); |
||||||
|
|
||||||
|
string outS; |
||||||
|
outS.reserve(inS.size() * 2); |
||||||
|
|
||||||
|
for (unsigned int i = 0; i < inS.size(); i++) { |
||||||
|
unsigned char ch = inS[i]; |
||||||
|
const char *escStr = escapes[ch]; |
||||||
|
|
||||||
|
if (escStr) |
||||||
|
outS += escStr; |
||||||
|
|
||||||
|
else if (isprint(ch)) |
||||||
|
outS += ch; |
||||||
|
|
||||||
|
else { |
||||||
|
char tmpesc[16]; |
||||||
|
sprintf(tmpesc, "\\u%04x", ch); |
||||||
|
outS += tmpesc; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return outS; |
||||||
|
} |
||||||
|
|
||||||
|
string UniValue::write(unsigned int prettyIndent, |
||||||
|
unsigned int indentLevel) const |
||||||
|
{ |
||||||
|
string s; |
||||||
|
s.reserve(1024); |
||||||
|
|
||||||
|
unsigned int modIndent = indentLevel; |
||||||
|
if (modIndent == 0) |
||||||
|
modIndent = 1; |
||||||
|
|
||||||
|
switch (typ) { |
||||||
|
case VNULL: |
||||||
|
s += "null"; |
||||||
|
break; |
||||||
|
case VOBJ: |
||||||
|
writeObject(prettyIndent, modIndent, s); |
||||||
|
break; |
||||||
|
case VARR: |
||||||
|
writeArray(prettyIndent, modIndent, s); |
||||||
|
break; |
||||||
|
case VSTR: |
||||||
|
s += "\"" + json_escape(val) + "\""; |
||||||
|
break; |
||||||
|
case VNUM: |
||||||
|
s += val; |
||||||
|
break; |
||||||
|
case VBOOL: |
||||||
|
s += (val == "1" ? "true" : "false"); |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
return s; |
||||||
|
} |
||||||
|
|
||||||
|
static string spaceStr; |
||||||
|
|
||||||
|
static string indentStr(unsigned int prettyIndent, unsigned int indentLevel) |
||||||
|
{ |
||||||
|
unsigned int spaces = prettyIndent * indentLevel; |
||||||
|
while (spaceStr.size() < spaces) |
||||||
|
spaceStr += " "; |
||||||
|
|
||||||
|
return spaceStr.substr(0, spaces); |
||||||
|
} |
||||||
|
|
||||||
|
void UniValue::writeArray(unsigned int prettyIndent, unsigned int indentLevel, string& s) const |
||||||
|
{ |
||||||
|
s += "["; |
||||||
|
if (prettyIndent) |
||||||
|
s += "\n"; |
||||||
|
|
||||||
|
for (unsigned int i = 0; i < values.size(); i++) { |
||||||
|
if (prettyIndent) |
||||||
|
s += indentStr(prettyIndent, indentLevel); |
||||||
|
s += values[i].write(prettyIndent, indentLevel + 1); |
||||||
|
if (i != (values.size() - 1)) |
||||||
|
s += ", "; |
||||||
|
if (prettyIndent) |
||||||
|
s += "\n"; |
||||||
|
} |
||||||
|
|
||||||
|
if (prettyIndent) |
||||||
|
s += indentStr(prettyIndent, indentLevel - 1); |
||||||
|
s += "]"; |
||||||
|
} |
||||||
|
|
||||||
|
void UniValue::writeObject(unsigned int prettyIndent, unsigned int indentLevel, string& s) const |
||||||
|
{ |
||||||
|
s += "{"; |
||||||
|
if (prettyIndent) |
||||||
|
s += "\n"; |
||||||
|
|
||||||
|
for (unsigned int i = 0; i < keys.size(); i++) { |
||||||
|
if (prettyIndent) |
||||||
|
s += indentStr(prettyIndent, indentLevel); |
||||||
|
s += "\"" + json_escape(keys[i]) + "\": "; |
||||||
|
s += values[i].write(prettyIndent, indentLevel + 1); |
||||||
|
if (i != (values.size() - 1)) |
||||||
|
s += ","; |
||||||
|
if (prettyIndent) |
||||||
|
s += "\n"; |
||||||
|
} |
||||||
|
|
||||||
|
if (prettyIndent) |
||||||
|
s += indentStr(prettyIndent, indentLevel - 1); |
||||||
|
s += "}"; |
||||||
|
} |
||||||
|
|
Loading…
Reference in new issue