Jeff Garzik
10 years ago
19 changed files with 1846 additions and 182 deletions
@ -0,0 +1,597 @@
@@ -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,23 @@
@@ -0,0 +1,23 @@
|
||||
#ifndef __BITCOIN_CORE_IO_H__ |
||||
#define __BITCOIN_CORE_IO_H__ |
||||
|
||||
#include <string> |
||||
|
||||
class uint256; |
||||
class CScript; |
||||
class CTransaction; |
||||
class UniValue; |
||||
|
||||
// core_read.cpp
|
||||
extern CScript ParseScript(std::string s); |
||||
extern bool DecodeHexTx(CTransaction& tx, const std::string& strHexTx); |
||||
extern uint256 ParseHashUV(const UniValue& v, const std::string& strName); |
||||
extern std::vector<unsigned char> ParseHexUV(const UniValue& v, const std::string& strName); |
||||
|
||||
// core_write.cpp
|
||||
extern std::string EncodeHexTx(const CTransaction& tx); |
||||
extern void ScriptPubKeyToUniv(const CScript& scriptPubKey, |
||||
UniValue& out, bool fIncludeHex); |
||||
extern void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry); |
||||
|
||||
#endif // __BITCOIN_CORE_IO_H__
|
@ -0,0 +1,127 @@
@@ -0,0 +1,127 @@
|
||||
|
||||
#include <vector> |
||||
#include "core_io.h" |
||||
#include "core.h" |
||||
#include "serialize.h" |
||||
#include "script.h" |
||||
#include "util.h" |
||||
|
||||
#include <boost/assign/list_of.hpp> |
||||
#include <boost/algorithm/string/classification.hpp> |
||||
#include <boost/algorithm/string/predicate.hpp> |
||||
#include <boost/algorithm/string/split.hpp> |
||||
#include <boost/algorithm/string/replace.hpp> |
||||
#include "univalue/univalue.h" |
||||
|
||||
using namespace std; |
||||
using namespace boost; |
||||
using namespace boost::algorithm; |
||||
|
||||
CScript ParseScript(std::string s) |
||||
{ |
||||
CScript result; |
||||
|
||||
static map<string, opcodetype> mapOpNames; |
||||
|
||||
if (mapOpNames.empty()) |
||||
{ |
||||
for (int op = 0; op <= OP_NOP10; op++) |
||||
{ |
||||
// Allow OP_RESERVED to get into mapOpNames
|
||||
if (op < OP_NOP && op != OP_RESERVED) |
||||
continue; |
||||
|
||||
const char* name = GetOpName((opcodetype)op); |
||||
if (strcmp(name, "OP_UNKNOWN") == 0) |
||||
continue; |
||||
string strName(name); |
||||
mapOpNames[strName] = (opcodetype)op; |
||||
// Convenience: OP_ADD and just ADD are both recognized:
|
||||
replace_first(strName, "OP_", ""); |
||||
mapOpNames[strName] = (opcodetype)op; |
||||
} |
||||
} |
||||
|
||||
vector<string> words; |
||||
split(words, s, is_any_of(" \t\n"), token_compress_on); |
||||
|
||||
for (std::vector<std::string>::const_iterator w = words.begin(); w != words.end(); ++w) |
||||
{ |
||||
if (w->empty()) |
||||
{ |
||||
// Empty string, ignore. (boost::split given '' will return one word)
|
||||
} |
||||
else if (all(*w, is_digit()) || |
||||
(starts_with(*w, "-") && all(string(w->begin()+1, w->end()), is_digit()))) |
||||
{ |
||||
// Number
|
||||
int64_t n = atoi64(*w); |
||||
result << n; |
||||
} |
||||
else if (starts_with(*w, "0x") && (w->begin()+2 != w->end()) && IsHex(string(w->begin()+2, w->end()))) |
||||
{ |
||||
// Raw hex data, inserted NOT pushed onto stack:
|
||||
std::vector<unsigned char> raw = ParseHex(string(w->begin()+2, w->end())); |
||||
result.insert(result.end(), raw.begin(), raw.end()); |
||||
} |
||||
else if (w->size() >= 2 && starts_with(*w, "'") && ends_with(*w, "'")) |
||||
{ |
||||
// Single-quoted string, pushed as data. NOTE: this is poor-man's
|
||||
// parsing, spaces/tabs/newlines in single-quoted strings won't work.
|
||||
std::vector<unsigned char> value(w->begin()+1, w->end()-1); |
||||
result << value; |
||||
} |
||||
else if (mapOpNames.count(*w)) |
||||
{ |
||||
// opcode, e.g. OP_ADD or ADD:
|
||||
result << mapOpNames[*w]; |
||||
} |
||||
else |
||||
{ |
||||
throw runtime_error("script parse error"); |
||||
} |
||||
} |
||||
|
||||
return result; |
||||
} |
||||
|
||||
bool DecodeHexTx(CTransaction& tx, const std::string& strHexTx) |
||||
{ |
||||
if (!IsHex(strHexTx)) |
||||
return false; |
||||
|
||||
vector<unsigned char> txData(ParseHex(strHexTx)); |
||||
CDataStream ssData(txData, SER_NETWORK, PROTOCOL_VERSION); |
||||
try { |
||||
ssData >> tx; |
||||
} |
||||
catch (std::exception &e) { |
||||
return false; |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
uint256 ParseHashUV(const UniValue& v, const string& strName) |
||||
{ |
||||
string strHex; |
||||
if (v.isStr()) |
||||
strHex = v.getValStr(); |
||||
if (!IsHex(strHex)) // Note: IsHex("") is false
|
||||
throw runtime_error(strName+" must be hexadecimal string (not '"+strHex+"')"); |
||||
|
||||
uint256 result; |
||||
result.SetHex(strHex); |
||||
return result; |
||||
} |
||||
|
||||
vector<unsigned char> ParseHexUV(const UniValue& v, const string& strName) |
||||
{ |
||||
string strHex; |
||||
if (v.isStr()) |
||||
strHex = v.getValStr(); |
||||
if (!IsHex(strHex)) |
||||
throw runtime_error(strName+" must be hexadecimal string (not '"+strHex+"')"); |
||||
return ParseHex(strHex); |
||||
} |
||||
|
@ -0,0 +1,89 @@
@@ -0,0 +1,89 @@
|
||||
|
||||
#include <vector> |
||||
#include "core_io.h" |
||||
#include "univalue/univalue.h" |
||||
#include "script.h" |
||||
#include "core.h" |
||||
#include "serialize.h" |
||||
#include "util.h" |
||||
#include "base58.h" |
||||
|
||||
using namespace std; |
||||
|
||||
string EncodeHexTx(const CTransaction& tx) |
||||
{ |
||||
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); |
||||
ssTx << tx; |
||||
return HexStr(ssTx.begin(), ssTx.end()); |
||||
} |
||||
|
||||
void ScriptPubKeyToUniv(const CScript& scriptPubKey, |
||||
UniValue& out, bool fIncludeHex) |
||||
{ |
||||
txnouttype type; |
||||
vector<CTxDestination> addresses; |
||||
int nRequired; |
||||
|
||||
out.pushKV("asm", scriptPubKey.ToString()); |
||||
if (fIncludeHex) |
||||
out.pushKV("hex", HexStr(scriptPubKey.begin(), scriptPubKey.end())); |
||||
|
||||
if (!ExtractDestinations(scriptPubKey, type, addresses, nRequired)) { |
||||
out.pushKV("type", GetTxnOutputType(type)); |
||||
return; |
||||
} |
||||
|
||||
out.pushKV("reqSigs", nRequired); |
||||
out.pushKV("type", GetTxnOutputType(type)); |
||||
|
||||
UniValue a(UniValue::VARR); |
||||
BOOST_FOREACH(const CTxDestination& addr, addresses) |
||||
a.push_back(CBitcoinAddress(addr).ToString()); |
||||
out.pushKV("addresses", a); |
||||
} |
||||
|
||||
void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry) |
||||
{ |
||||
entry.pushKV("txid", tx.GetHash().GetHex()); |
||||
entry.pushKV("version", tx.nVersion); |
||||
entry.pushKV("locktime", (int64_t)tx.nLockTime); |
||||
|
||||
UniValue vin(UniValue::VARR); |
||||
BOOST_FOREACH(const CTxIn& txin, tx.vin) { |
||||
UniValue in(UniValue::VOBJ); |
||||
if (tx.IsCoinBase()) |
||||
in.pushKV("coinbase", HexStr(txin.scriptSig.begin(), txin.scriptSig.end())); |
||||
else { |
||||
in.pushKV("txid", txin.prevout.hash.GetHex()); |
||||
in.pushKV("vout", (int64_t)txin.prevout.n); |
||||
UniValue o(UniValue::VOBJ); |
||||
o.pushKV("asm", txin.scriptSig.ToString()); |
||||
o.pushKV("hex", HexStr(txin.scriptSig.begin(), txin.scriptSig.end())); |
||||
in.pushKV("scriptSig", o); |
||||
} |
||||
in.pushKV("sequence", (int64_t)txin.nSequence); |
||||
vin.push_back(in); |
||||
} |
||||
entry.pushKV("vin", vin); |
||||
|
||||
UniValue vout(UniValue::VARR); |
||||
for (unsigned int i = 0; i < tx.vout.size(); i++) { |
||||
const CTxOut& txout = tx.vout[i]; |
||||
|
||||
UniValue out(UniValue::VOBJ); |
||||
|
||||
UniValue outValue(UniValue::VNUM, FormatMoney(txout.nValue)); |
||||
out.pushKV("value", outValue); |
||||
out.pushKV("n", (int64_t)i); |
||||
|
||||
UniValue o(UniValue::VOBJ); |
||||
ScriptPubKeyToUniv(txout.scriptPubKey, o, true); |
||||
out.pushKV("scriptPubKey", o); |
||||
vout.push_back(out); |
||||
} |
||||
entry.pushKV("vout", vout); |
||||
|
||||
if (hashBlock != 0) |
||||
entry.pushKV("blockhash", hashBlock.GetHex()); |
||||
} |
||||
|
@ -0,0 +1,233 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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