diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | src/Makefile.am | 26 | ||||
-rw-r--r-- | src/Makefile.qt.include | 2 | ||||
-rw-r--r-- | src/Makefile.qttest.include | 2 | ||||
-rw-r--r-- | src/Makefile.test.include | 2 | ||||
-rw-r--r-- | src/bitcoin-cli.cpp | 31 | ||||
-rw-r--r-- | src/bitcoin-tx.cpp | 597 | ||||
-rw-r--r-- | src/core_io.h | 23 | ||||
-rw-r--r-- | src/core_read.cpp | 127 | ||||
-rw-r--r-- | src/core_write.cpp | 89 | ||||
-rw-r--r-- | src/rpcmining.cpp | 5 | ||||
-rw-r--r-- | src/rpcrawtransaction.cpp | 120 | ||||
-rw-r--r-- | src/rpcwallet.cpp | 5 | ||||
-rw-r--r-- | src/test/script_tests.cpp | 71 | ||||
-rw-r--r-- | src/test/transaction_tests.cpp | 2 | ||||
-rw-r--r-- | src/univalue/univalue.cpp | 233 | ||||
-rw-r--r-- | src/univalue/univalue.h | 157 | ||||
-rw-r--r-- | src/univalue/univalue_read.cpp | 390 | ||||
-rw-r--r-- | src/univalue/univalue_write.cpp | 145 |
19 files changed, 1846 insertions, 182 deletions
diff --git a/.gitignore b/.gitignore index 4169a2d96c..e21ea92552 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ src/bitcoin src/bitcoind src/bitcoin-cli +src/bitcoin-tx src/test/test_bitcoin src/qt/test/test_bitcoin-qt diff --git a/src/Makefile.am b/src/Makefile.am index ff23747592..2d84eeba1c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -34,6 +34,7 @@ LIBBITCOIN_COMMON=libbitcoin_common.a LIBBITCOIN_CLI=libbitcoin_cli.a LIBBITCOIN_UTIL=libbitcoin_util.a LIBBITCOIN_CRYPTO=crypto/libbitcoin_crypto.a +LIBBITCOIN_UNIVALUE=univalue/libbitcoin_univalue.a LIBBITCOINQT=qt/libbitcoinqt.a noinst_LIBRARIES = \ @@ -41,6 +42,7 @@ noinst_LIBRARIES = \ libbitcoin_common.a \ libbitcoin_cli.a \ libbitcoin_util.a \ + univalue/libbitcoin_univalue.a \ crypto/libbitcoin_crypto.a if ENABLE_WALLET BITCOIN_INCLUDES += $(BDB_CPPFLAGS) @@ -58,6 +60,8 @@ if BUILD_BITCOIN_CLI bin_PROGRAMS += bitcoin-cli endif +bin_PROGRAMS += bitcoin-tx + .PHONY: FORCE # bitcoin core # BITCOIN_CORE_H = \ @@ -75,6 +79,7 @@ BITCOIN_CORE_H = \ coins.h \ compat.h \ core.h \ + core_io.h \ crypter.h \ db.h \ hash.h \ @@ -177,6 +182,13 @@ crypto_libbitcoin_crypto_a_SOURCES = \ crypto/sha1.h \ crypto/ripemd160.h +# univalue JSON library +univalue_libbitcoin_univalue_a_SOURCES = \ + univalue/univalue.cpp \ + univalue/univalue_read.cpp \ + univalue/univalue_write.cpp \ + univalue/univalue.h + # common: shared between bitcoind, and bitcoin-qt and non-server tools libbitcoin_common_a_CPPFLAGS = $(BITCOIN_INCLUDES) libbitcoin_common_a_SOURCES = \ @@ -185,6 +197,8 @@ libbitcoin_common_a_SOURCES = \ chainparams.cpp \ coins.cpp \ core.cpp \ + core_read.cpp \ + core_write.cpp \ hash.cpp \ key.cpp \ keystore.cpp \ @@ -226,6 +240,7 @@ nodist_libbitcoin_util_a_SOURCES = $(srcdir)/obj/build.h bitcoind_LDADD = \ $(LIBBITCOIN_SERVER) \ $(LIBBITCOIN_COMMON) \ + $(LIBBITCOIN_UNIVALUE) \ $(LIBBITCOIN_UTIL) \ $(LIBBITCOIN_CRYPTO) \ $(LIBLEVELDB) \ @@ -264,6 +279,17 @@ endif bitcoin_cli_CPPFLAGS = $(BITCOIN_INCLUDES) # +# bitcoin-tx binary # +bitcoin_tx_LDADD = \ + $(LIBBITCOIN_UNIVALUE) \ + $(LIBBITCOIN_COMMON) \ + $(LIBBITCOIN_UTIL) \ + $(LIBBITCOIN_CRYPTO) \ + $(BOOST_LIBS) +bitcoin_tx_SOURCES = bitcoin-tx.cpp +bitcoin_tx_CPPFLAGS = $(BITCOIN_INCLUDES) +# + if TARGET_WINDOWS bitcoin_cli_SOURCES += bitcoin-cli-res.rc endif diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 2772bc753a..2052264dc9 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -359,7 +359,7 @@ qt_bitcoin_qt_LDADD = qt/libbitcoinqt.a $(LIBBITCOIN_SERVER) if ENABLE_WALLET qt_bitcoin_qt_LDADD += $(LIBBITCOIN_WALLET) endif -qt_bitcoin_qt_LDADD += $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBLEVELDB) $(LIBMEMENV) \ +qt_bitcoin_qt_LDADD += $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBBITCOIN_UNIVALUE) $(LIBLEVELDB) $(LIBMEMENV) \ $(BOOST_LIBS) $(QT_LIBS) $(QT_DBUS_LIBS) $(QR_LIBS) $(PROTOBUF_LIBS) $(BDB_LIBS) if USE_LIBSECP256K1 qt_bitcoin_qt_LDADD += secp256k1/libsecp256k1.la diff --git a/src/Makefile.qttest.include b/src/Makefile.qttest.include index 51ce006fc1..d49b2240ea 100644 --- a/src/Makefile.qttest.include +++ b/src/Makefile.qttest.include @@ -30,7 +30,7 @@ qt_test_test_bitcoin_qt_LDADD = $(LIBBITCOINQT) $(LIBBITCOIN_SERVER) if ENABLE_WALLET qt_test_test_bitcoin_qt_LDADD += $(LIBBITCOIN_WALLET) endif -qt_test_test_bitcoin_qt_LDADD += $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBLEVELDB) \ +qt_test_test_bitcoin_qt_LDADD += $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBBITCOIN_UNIVALUE) $(LIBLEVELDB) \ $(LIBMEMENV) $(BOOST_LIBS) $(QT_DBUS_LIBS) $(QT_TEST_LIBS) $(QT_LIBS) \ $(QR_LIBS) $(PROTOBUF_LIBS) $(BDB_LIBS) if USE_LIBSECP256K1 diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 12b90adca3..72451fba9a 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -64,7 +64,7 @@ endif test_test_bitcoin_SOURCES = $(BITCOIN_TESTS) $(JSON_TEST_FILES) $(RAW_TEST_FILES) test_test_bitcoin_CPPFLAGS = $(BITCOIN_INCLUDES) -I$(builddir)/test/ $(TESTDEFS) -test_test_bitcoin_LDADD = $(LIBBITCOIN_SERVER) $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBLEVELDB) $(LIBMEMENV) \ +test_test_bitcoin_LDADD = $(LIBBITCOIN_SERVER) $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBBITCOIN_UNIVALUE) $(LIBLEVELDB) $(LIBMEMENV) \ $(BOOST_LIBS) $(BOOST_UNIT_TEST_FRAMEWORK_LIB) if ENABLE_WALLET test_test_bitcoin_LDADD += $(LIBBITCOIN_WALLET) diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index 016b2f50f5..3b991f9276 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -50,8 +50,7 @@ static bool AppInitRPC(int argc, char* argv[]) // Parameters // ParseParameters(argc, argv); - if (!boost::filesystem::is_directory(GetDataDir(false))) - { + if (!boost::filesystem::is_directory(GetDataDir(false))) { fprintf(stderr, "Error: Specified data directory \"%s\" does not exist.\n", mapArgs["-datadir"].c_str()); return false; } @@ -66,11 +65,9 @@ static bool AppInitRPC(int argc, char* argv[]) fprintf(stderr, "Error: Invalid combination of -regtest and -testnet.\n"); return false; } - if (argc<2 || mapArgs.count("-?") || mapArgs.count("-help") || mapArgs.count("-version")) - { + if (argc<2 || mapArgs.count("-?") || mapArgs.count("-help") || mapArgs.count("-version")) { std::string strUsage = _("Bitcoin Core RPC client version") + " " + FormatFullVersion() + "\n"; - if (!mapArgs.count("-version")) - { + if (!mapArgs.count("-version")) { strUsage += "\n" + _("Usage:") + "\n" + " bitcoin-cli [options] <command> [params] " + _("Send command to Bitcoin Core") + "\n" + " bitcoin-cli [options] help " + _("List commands") + "\n" + @@ -153,11 +150,9 @@ int CommandLineRPC(int argc, char *argv[]) { string strPrint; int nRet = 0; - try - { + try { // Skip switches - while (argc > 1 && IsSwitchChar(argv[1][0])) - { + while (argc > 1 && IsSwitchChar(argv[1][0])) { argc--; argv++; } @@ -178,15 +173,12 @@ int CommandLineRPC(int argc, char *argv[]) const Value& result = find_value(reply, "result"); const Value& error = find_value(reply, "error"); - if (error.type() != null_type) - { + if (error.type() != null_type) { // Error strPrint = "error: " + write_string(error, false); int code = find_value(error.get_obj(), "code").get_int(); nRet = abs(code); - } - else - { + } else { // Result if (result.type() == null_type) strPrint = ""; @@ -208,8 +200,7 @@ int CommandLineRPC(int argc, char *argv[]) throw; } - if (strPrint != "") - { + if (strPrint != "") { fprintf((nRet == 0 ? stdout : stderr), "%s\n", strPrint.c_str()); } return nRet; @@ -219,8 +210,7 @@ int main(int argc, char* argv[]) { SetupEnvironment(); - try - { + try { if(!AppInitRPC(argc, argv)) return EXIT_FAILURE; } @@ -233,8 +223,7 @@ int main(int argc, char* argv[]) } int ret = EXIT_FAILURE; - try - { + try { ret = CommandLineRPC(argc, argv); } catch (std::exception& e) { diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp new file mode 100644 index 0000000000..299315424b --- /dev/null +++ b/src/bitcoin-tx.cpp @@ -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; +} + diff --git a/src/core_io.h b/src/core_io.h new file mode 100644 index 0000000000..8a7d580570 --- /dev/null +++ b/src/core_io.h @@ -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__ diff --git a/src/core_read.cpp b/src/core_read.cpp new file mode 100644 index 0000000000..0f06bb6952 --- /dev/null +++ b/src/core_read.cpp @@ -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); +} + diff --git a/src/core_write.cpp b/src/core_write.cpp new file mode 100644 index 0000000000..2eb220779f --- /dev/null +++ b/src/core_write.cpp @@ -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()); +} + diff --git a/src/rpcmining.cpp b/src/rpcmining.cpp index cbb4ab2f8b..edab427cbf 100644 --- a/src/rpcmining.cpp +++ b/src/rpcmining.cpp @@ -10,6 +10,7 @@ #include "main.h" #include "miner.h" #include "pow.h" +#include "core_io.h" #ifdef ENABLE_WALLET #include "db.h" #include "wallet.h" @@ -472,9 +473,7 @@ Value getblocktemplate(const Array& params, bool fHelp) Object entry; - CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); - ssTx << tx; - entry.push_back(Pair("data", HexStr(ssTx.begin(), ssTx.end()))); + entry.push_back(Pair("data", EncodeHexTx(tx))); entry.push_back(Pair("hash", txHash.GetHex())); diff --git a/src/rpcrawtransaction.cpp b/src/rpcrawtransaction.cpp index 1efe38e830..763615120a 100644 --- a/src/rpcrawtransaction.cpp +++ b/src/rpcrawtransaction.cpp @@ -5,6 +5,7 @@ #include "base58.h" #include "core.h" +#include "core_io.h" #include "init.h" #include "keystore.h" #include "main.h" @@ -36,8 +37,7 @@ void ScriptPubKeyToJSON(const CScript& scriptPubKey, Object& out, bool fIncludeH if (fIncludeHex) out.push_back(Pair("hex", HexStr(scriptPubKey.begin(), scriptPubKey.end()))); - if (!ExtractDestinations(scriptPubKey, type, addresses, nRequired)) - { + if (!ExtractDestinations(scriptPubKey, type, addresses, nRequired)) { out.push_back(Pair("type", GetTxnOutputType(type))); return; } @@ -57,13 +57,11 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, Object& entry) entry.push_back(Pair("version", tx.nVersion)); entry.push_back(Pair("locktime", (int64_t)tx.nLockTime)); Array vin; - BOOST_FOREACH(const CTxIn& txin, tx.vin) - { + BOOST_FOREACH(const CTxIn& txin, tx.vin) { Object in; if (tx.IsCoinBase()) in.push_back(Pair("coinbase", HexStr(txin.scriptSig.begin(), txin.scriptSig.end()))); - else - { + else { in.push_back(Pair("txid", txin.prevout.hash.GetHex())); in.push_back(Pair("vout", (int64_t)txin.prevout.n)); Object o; @@ -76,8 +74,7 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, Object& entry) } entry.push_back(Pair("vin", vin)); Array vout; - for (unsigned int i = 0; i < tx.vout.size(); i++) - { + for (unsigned int i = 0; i < tx.vout.size(); i++) { const CTxOut& txout = tx.vout[i]; Object out; out.push_back(Pair("value", ValueFromAmount(txout.nValue))); @@ -89,15 +86,12 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, Object& entry) } entry.push_back(Pair("vout", vout)); - if (hashBlock != 0) - { + if (hashBlock != 0) { entry.push_back(Pair("blockhash", hashBlock.GetHex())); map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(hashBlock); - if (mi != mapBlockIndex.end() && (*mi).second) - { + if (mi != mapBlockIndex.end() && (*mi).second) { CBlockIndex* pindex = (*mi).second; - if (chainActive.Contains(pindex)) - { + if (chainActive.Contains(pindex)) { entry.push_back(Pair("confirmations", 1 + chainActive.Height() - pindex->nHeight)); entry.push_back(Pair("time", pindex->GetBlockTime())); entry.push_back(Pair("blocktime", pindex->GetBlockTime())); @@ -182,9 +176,7 @@ Value getrawtransaction(const Array& params, bool fHelp) if (!GetTransaction(hash, tx, hashBlock, true)) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No information available about transaction"); - CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); - ssTx << tx; - string strHex = HexStr(ssTx.begin(), ssTx.end()); + string strHex = EncodeHexTx(tx); if (!fVerbose) return strHex; @@ -245,11 +237,9 @@ Value listunspent(const Array& params, bool fHelp) nMaxDepth = params[1].get_int(); set<CBitcoinAddress> setAddress; - if (params.size() > 2) - { + if (params.size() > 2) { Array inputs = params[2].get_array(); - BOOST_FOREACH(Value& input, inputs) - { + BOOST_FOREACH(Value& input, inputs) { CBitcoinAddress address(input.get_str()); if (!address.IsValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, string("Invalid Bitcoin address: ")+input.get_str()); @@ -263,13 +253,11 @@ Value listunspent(const Array& params, bool fHelp) vector<COutput> vecOutputs; assert(pwalletMain != NULL); pwalletMain->AvailableCoins(vecOutputs, false); - BOOST_FOREACH(const COutput& out, vecOutputs) - { + BOOST_FOREACH(const COutput& out, vecOutputs) { if (out.nDepth < nMinDepth || out.nDepth > nMaxDepth) continue; - if (setAddress.size()) - { + if (setAddress.size()) { CTxDestination address; if (!ExtractDestination(out.tx->vout[out.i].scriptPubKey, address)) continue; @@ -284,18 +272,15 @@ Value listunspent(const Array& params, bool fHelp) entry.push_back(Pair("txid", out.tx->GetHash().GetHex())); entry.push_back(Pair("vout", out.i)); CTxDestination address; - if (ExtractDestination(out.tx->vout[out.i].scriptPubKey, address)) - { + if (ExtractDestination(out.tx->vout[out.i].scriptPubKey, address)) { entry.push_back(Pair("address", CBitcoinAddress(address).ToString())); if (pwalletMain->mapAddressBook.count(address)) entry.push_back(Pair("account", pwalletMain->mapAddressBook[address].name)); } entry.push_back(Pair("scriptPubKey", HexStr(pk.begin(), pk.end()))); - if (pk.IsPayToScriptHash()) - { + if (pk.IsPayToScriptHash()) { CTxDestination address; - if (ExtractDestination(pk, address)) - { + if (ExtractDestination(pk, address)) { const CScriptID& hash = boost::get<const CScriptID&>(address); CScript redeemScript; if (pwalletMain->GetCScript(hash, redeemScript)) @@ -352,8 +337,7 @@ Value createrawtransaction(const Array& params, bool fHelp) CMutableTransaction rawTx; - BOOST_FOREACH(const Value& input, inputs) - { + BOOST_FOREACH(const Value& input, inputs) { const Object& o = input.get_obj(); uint256 txid = ParseHashO(o, "txid"); @@ -370,8 +354,7 @@ Value createrawtransaction(const Array& params, bool fHelp) } set<CBitcoinAddress> setAddress; - BOOST_FOREACH(const Pair& s, sendTo) - { + BOOST_FOREACH(const Pair& s, sendTo) { CBitcoinAddress address(s.name_); if (!address.IsValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, string("Invalid Bitcoin address: ")+s.name_); @@ -388,9 +371,7 @@ Value createrawtransaction(const Array& params, bool fHelp) rawTx.vout.push_back(out); } - CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); - ss << rawTx; - return HexStr(ss.begin(), ss.end()); + return EncodeHexTx(rawTx); } Value decoderawtransaction(const Array& params, bool fHelp) @@ -444,15 +425,12 @@ Value decoderawtransaction(const Array& params, bool fHelp) + HelpExampleRpc("decoderawtransaction", "\"hexstring\"") ); - vector<unsigned char> txData(ParseHexV(params[0], "argument")); - CDataStream ssData(txData, SER_NETWORK, PROTOCOL_VERSION); + RPCTypeCheck(params, list_of(str_type)); + CTransaction tx; - try { - ssData >> tx; - } - catch (std::exception &e) { + + if (!DecodeHexTx(tx, params[0].get_str())) throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); - } Object result; TxToJSON(tx, 0, result); @@ -556,8 +534,7 @@ Value signrawtransaction(const Array& params, bool fHelp) vector<unsigned char> txData(ParseHexV(params[0], "argument 1")); CDataStream ssData(txData, SER_NETWORK, PROTOCOL_VERSION); vector<CMutableTransaction> txVariants; - while (!ssData.empty()) - { + while (!ssData.empty()) { try { CMutableTransaction tx; ssData >> tx; @@ -596,12 +573,10 @@ Value signrawtransaction(const Array& params, bool fHelp) bool fGivenKeys = false; CBasicKeyStore tempKeystore; - if (params.size() > 2 && params[2].type() != null_type) - { + if (params.size() > 2 && params[2].type() != null_type) { fGivenKeys = true; Array keys = params[2].get_array(); - BOOST_FOREACH(Value k, keys) - { + BOOST_FOREACH(Value k, keys) { CBitcoinSecret vchSecret; bool fGood = vchSecret.SetString(k.get_str()); if (!fGood) @@ -616,11 +591,9 @@ Value signrawtransaction(const Array& params, bool fHelp) #endif // Add previous txouts given in the RPC call: - if (params.size() > 1 && params[1].type() != null_type) - { + if (params.size() > 1 && params[1].type() != null_type) { Array prevTxs = params[1].get_array(); - BOOST_FOREACH(Value& p, prevTxs) - { + BOOST_FOREACH(Value& p, prevTxs) { if (p.type() != obj_type) throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "expected object with {\"txid'\",\"vout\",\"scriptPubKey\"}"); @@ -655,12 +628,10 @@ Value signrawtransaction(const Array& params, bool fHelp) // if redeemScript given and not using the local wallet (private keys // given), add redeemScript to the tempKeystore so it can be signed: - if (fGivenKeys && scriptPubKey.IsPayToScriptHash()) - { + if (fGivenKeys && scriptPubKey.IsPayToScriptHash()) { RPCTypeCheck(prevOut, map_list_of("txid", str_type)("vout", int_type)("scriptPubKey", str_type)("redeemScript",str_type)); Value v = find_value(prevOut, "redeemScript"); - if (!(v == Value::null)) - { + if (!(v == Value::null)) { vector<unsigned char> rsData(ParseHexV(v, "redeemScript")); CScript redeemScript(rsData.begin(), rsData.end()); tempKeystore.AddCScript(redeemScript); @@ -676,8 +647,7 @@ Value signrawtransaction(const Array& params, bool fHelp) #endif int nHashType = SIGHASH_ALL; - if (params.size() > 3 && params[3].type() != null_type) - { + if (params.size() > 3 && params[3].type() != null_type) { static map<string, int> mapSigHashValues = boost::assign::map_list_of (string("ALL"), int(SIGHASH_ALL)) @@ -697,12 +667,10 @@ Value signrawtransaction(const Array& params, bool fHelp) bool fHashSingle = ((nHashType & ~SIGHASH_ANYONECANPAY) == SIGHASH_SINGLE); // Sign what we can: - for (unsigned int i = 0; i < mergedTx.vin.size(); i++) - { + 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)) - { + if (!view.GetCoins(txin.prevout.hash, coins) || !coins.IsAvailable(txin.prevout.n)) { fComplete = false; continue; } @@ -714,8 +682,7 @@ Value signrawtransaction(const Array& params, bool fHelp) SignSignature(keystore, prevPubKey, mergedTx, i, nHashType); // ... and merge in other signatures: - BOOST_FOREACH(const CMutableTransaction& txv, txVariants) - { + BOOST_FOREACH(const CMutableTransaction& 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)) @@ -723,9 +690,7 @@ Value signrawtransaction(const Array& params, bool fHelp) } Object result; - CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); - ssTx << mergedTx; - result.push_back(Pair("hex", HexStr(ssTx.begin(), ssTx.end()))); + result.push_back(Pair("hex", EncodeHexTx(mergedTx))); result.push_back(Pair("complete", fComplete)); return result; @@ -754,25 +719,18 @@ Value sendrawtransaction(const Array& params, bool fHelp) + HelpExampleRpc("sendrawtransaction", "\"signedhex\"") ); + RPCTypeCheck(params, list_of(str_type)(bool_type)); // parse hex string from parameter - vector<unsigned char> txData(ParseHexV(params[0], "parameter")); - CDataStream ssData(txData, SER_NETWORK, PROTOCOL_VERSION); CTransaction tx; + if (!DecodeHexTx(tx, params[0].get_str())) + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); + uint256 hashTx = tx.GetHash(); bool fOverrideFees = false; if (params.size() > 1) fOverrideFees = params[1].get_bool(); - // deserialize binary data stream - try { - ssData >> tx; - } - catch (std::exception &e) { - throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); - } - const uint256 &hashTx = tx.GetHash(); - CCoinsViewCache &view = *pcoinsTip; CCoins existingCoins; bool fHaveMempool = mempool.exists(hashTx); diff --git a/src/rpcwallet.cpp b/src/rpcwallet.cpp index 5b83fe900e..667ca33ce1 100644 --- a/src/rpcwallet.cpp +++ b/src/rpcwallet.cpp @@ -4,6 +4,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "base58.h" +#include "core_io.h" #include "rpcserver.h" #include "init.h" #include "net.h" @@ -1550,9 +1551,7 @@ Value gettransaction(const Array& params, bool fHelp) ListTransactions(wtx, "*", 0, false, details, filter); entry.push_back(Pair("details", details)); - CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); - ssTx << static_cast<CTransaction>(wtx); - string strHex = HexStr(ssTx.begin(), ssTx.end()); + string strHex = EncodeHexTx(static_cast<CTransaction>(wtx)); entry.push_back(Pair("hex", strHex)); return entry; diff --git a/src/test/script_tests.cpp b/src/test/script_tests.cpp index cba582e941..5e35875a8e 100644 --- a/src/test/script_tests.cpp +++ b/src/test/script_tests.cpp @@ -10,6 +10,7 @@ #include "key.h" #include "keystore.h" #include "main.h" +#include "core_io.h" #include <fstream> #include <stdint.h> @@ -36,76 +37,6 @@ extern uint256 SignatureHash(const CScript &scriptCode, const CTransaction& txTo static const unsigned int flags = SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_STRICTENC; -CScript -ParseScript(string s) -{ - CScript result; - - static map<string, opcodetype> mapOpNames; - - if (mapOpNames.size() == 0) - { - 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); - - BOOST_FOREACH(string w, words) - { - if (w.size() == 0) - { - // 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") && 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 - { - BOOST_ERROR("Parse error: " << s); - return CScript(); - } - } - - return result; -} - Array read_json(const std::string& jsondata) { diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index 238033f407..03919e7c7d 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -9,6 +9,7 @@ #include "keystore.h" #include "main.h" #include "script.h" +#include "core_io.h" #include <map> #include <string> @@ -24,7 +25,6 @@ using namespace boost::algorithm; // In script_tests.cpp extern Array read_json(const std::string& jsondata); -extern CScript ParseScript(string s); unsigned int ParseFlags(string strFlags){ unsigned int flags = 0; diff --git a/src/univalue/univalue.cpp b/src/univalue/univalue.cpp new file mode 100644 index 0000000000..e577aa8ee4 --- /dev/null +++ b/src/univalue/univalue.cpp @@ -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; +} + diff --git a/src/univalue/univalue.h b/src/univalue/univalue.h new file mode 100644 index 0000000000..5e94b6ba23 --- /dev/null +++ b/src/univalue/univalue.h @@ -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__ diff --git a/src/univalue/univalue_read.cpp b/src/univalue/univalue_read.cpp new file mode 100644 index 0000000000..405be3e81f --- /dev/null +++ b/src/univalue/univalue_read.cpp @@ -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; +} + diff --git a/src/univalue/univalue_write.cpp b/src/univalue/univalue_write.cpp new file mode 100644 index 0000000000..1818f5c6f9 --- /dev/null +++ b/src/univalue/univalue_write.cpp @@ -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 += "}"; +} + |