Permalink
Browse files

Merge script_debugger-mini

  • Loading branch information...
2 parents 1e4295e + ea69819 commit 0afed0460ec16a9d910447406bba61b94e33635b @luke-jr luke-jr committed Sep 14, 2017
View
@@ -180,7 +180,7 @@ bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
{
std::vector<std::vector<unsigned char> > stack;
// convert the scriptSig into a stack, so we can inspect the redeemScript
- if (!EvalScript(stack, tx.vin[i].scriptSig, SCRIPT_VERIFY_NONE, BaseSignatureChecker(), SIGVERSION_BASE))
+ if (!EvalScript(ScriptExecution::Context::Sig, stack, tx.vin[i].scriptSig, SCRIPT_VERIFY_NONE, BaseSignatureChecker(), SIGVERSION_BASE))
return false;
if (stack.empty())
return false;
@@ -216,7 +216,7 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
// If the scriptPubKey is P2SH, we try to extract the redeemScript casually by converting the scriptSig
// into a stack. We do not check IsPushOnly nor compare the hash as these will be done later anyway.
// If the check fails at this stage, we know that this txid must be a bad one.
- if (!EvalScript(stack, tx.vin[i].scriptSig, SCRIPT_VERIFY_NONE, BaseSignatureChecker(), SIGVERSION_BASE))
+ if (!EvalScript(ScriptExecution::Context::Sig, stack, tx.vin[i].scriptSig, SCRIPT_VERIFY_NONE, BaseSignatureChecker(), SIGVERSION_BASE))
return false;
if (stack.empty())
return false;
View
@@ -511,6 +511,17 @@ UniValue decoderawtransaction(const JSONRPCRequest& request)
return result;
}
+CScript ParseHexScript(const UniValue &v, std::string strName)
+{
+ if (v.get_str().size() > 0) {
+ std::vector<unsigned char> scriptData(ParseHexV(v, strName));
+ return CScript(scriptData.begin(), scriptData.end());
+ } else {
+ // Empty scripts are valid
+ }
+ return CScript();
+}
+
UniValue decodescript(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() != 1)
@@ -539,13 +550,7 @@ UniValue decodescript(const JSONRPCRequest& request)
RPCTypeCheck(request.params, {UniValue::VSTR});
UniValue r(UniValue::VOBJ);
- CScript script;
- if (request.params[0].get_str().size() > 0){
- std::vector<unsigned char> scriptData(ParseHexV(request.params[0], "argument"));
- script = CScript(scriptData.begin(), scriptData.end());
- } else {
- // Empty scripts are valid
- }
+ CScript script = ParseHexScript(request.params[0], "argument");
ScriptPubKeyToUniv(script, r, false);
UniValue type;
@@ -560,6 +565,236 @@ UniValue decodescript(const JSONRPCRequest& request)
return r;
}
+class verifyscript_ScriptExecutionDebugger final : public ScriptExecutionDebugger {
+private:
+ UniValue result;
+ UniValue current_script;
+ UniValue current_steps;
+
+ void CompleteScript() {
+ if (!current_script.isNull()) {
+ current_script.pushKV("steps", current_steps);
+ result.push_back(current_script);
+ }
+ }
+
+ static UniValue StackToUniValue(ScriptExecution::StackType& stack) {
+ UniValue rv(UniValue::VARR);
+ for (const auto elem : stack) {
+ rv.push_back(HexStr(elem.begin(), elem.end()));
+ }
+ return rv;
+ }
+
+ static UniValue vfExecToUniValue(std::vector<bool>& vfExec) {
+ UniValue rv(UniValue::VARR);
+ for (const auto elem : vfExec) {
+ bool elem_bool = elem;
+ rv.push_back(elem_bool);
+ }
+ return rv;
+ }
+
+ static void PushExInfo(UniValue& step_info, ScriptExecution& ex, const CScript::const_iterator& pos) {
+ step_info.pushKV("pos", (int64_t)(pos - ex.script.begin()));
+ step_info.pushKV("pos_codehash", (int64_t)(ex.pbegincodehash - ex.script.begin()));
+ step_info.pushKV("opcount", ex.nOpCount);
+ step_info.pushKV("stack", StackToUniValue(ex.stack));
+ step_info.pushKV("altstack", StackToUniValue(ex.altstack));
+ step_info.pushKV("exec", vfExecToUniValue(ex.vfExec));
+ }
+
+public:
+
+ verifyscript_ScriptExecutionDebugger() : result(UniValue::VARR) {}
+
+ void ScriptBegin(ScriptExecution& ex) {
+ CompleteScript();
+
+ current_script = UniValue(UniValue::VOBJ);
+
+ current_script.pushKV("context", ScriptExecution::ContextString(ex.context));
+
+ UniValue script_info(UniValue::VOBJ);
+ script_info.pushKV("asm", ScriptToAsmStr(ex.script));
+ script_info.pushKV("hex", HexStr(ex.script.begin(), ex.script.end()));
+ current_script.pushKV("script", script_info);
+
+ current_script.pushKV("sigversion", SigVersionString(ex.sigversion));
+ }
+
+ void ScriptPreStep(ScriptExecution& ex, const CScript::const_iterator& pos, opcodetype& opcode, ScriptExecution::StackElementType& pushelem) {
+ UniValue step_info(UniValue::VOBJ);
+ PushExInfo(step_info, ex, pos);
+
+ std::vector<uint8_t> opcode_arr;
+ opcode_arr.push_back(opcode);
+ step_info.pushKV("next_op", GetOpName(opcode));
+ step_info.pushKV("next_opcode", HexStr(opcode_arr.begin(), opcode_arr.end()));
+ step_info.pushKV("next_push", HexStr(pushelem.begin(), pushelem.end()));
+
+ current_steps.push_back(step_info);
+ }
+
+ void ScriptEOF(ScriptExecution& ex, const CScript::const_iterator& pos) {
+ UniValue step_info(UniValue::VOBJ);
+ PushExInfo(step_info, ex, pos);
+ current_steps.push_back(step_info);
+ }
+
+ const UniValue& GetResult() {
+ CompleteScript();
+ return result;
+ }
+};
+
+UniValue verifyscript(const JSONRPCRequest& request)
+{
+ if (request.fHelp || request.params.size() != 1)
+ throw std::runtime_error(
+ "verifyscript options\n"
+ "\nExecute a script and return the result.\n"
+ "\nArguments:\n"
+ "1. options (object, required)\n"
+ " {\n"
+ " \"input\": { (object, required) Information on the input being spent.\n"
+ " \"scriptPubKey\": \"hex\", (string, required) Hex encoded pubkey script.\n"
+ " \"amount\": value, (numeric, optional) The amount spent. Required for VERIFY_WITNESS flag.\n"
+ " \"txid\": \"id\", (string, this or \"transaction\" required) The input's transaction id.\n"
+ " \"transaction\": \"hex\", (string, this or \"txid\" required) The input's raw transaction.\n"
+ " \"vout\": n (numeric, required) The output number.\n"
+ " }\n"
+ " \"flags\": [ (array, optional) Zero or more of:"
+ " \"BIP16\", \"STRICTENC\", \"DERSIR\", \"LOW_S\", \"SIGPUSHONLY\", \"MINIMALDATA\", \"NULLDUMMY\",\"DISCOURAGE_UPGRADABLE_NOPS\", \"CLEANSTACK\", \"MINIMALIF\", \"NULLFAIL\", \"CHECKLOCKTIMEVERIFY\", \"CHECKSEQUENCEVERIFY\", \"WITNESS\", \"DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM\", \"WITNESS_PUBKEYTYPE\",\n"
+ " \"NONE\", Indicates explicitly that no flags should be enabled.\n"
+ " \"ALL\" Indicates all supported flags should be enabled.\n"
+ " ]\n"
+ " \"trace\": true|false, (boolean) Whether to trace execution and return \"trace\" in the result.\n"
+ " }\n"
+ "\nResult:\n"
+ "{\n"
+ " \"result\": true|false, (boolean) Whether the spend is allowed.\n"
+ " \"error\": \"code\", (string) What error, if any, caused the script to fail.\n"
+ " \"trace\": [\n"
+ " {\n"
+ " \"context\": \"Sig\"|\"PubKey\"|\"BIP16\"|\"Segwit\", (string) Context the script is being executed in.\n"
+ " \"script\": { (object) Script being executed.\n"
+ " \"asm\": \"asm\", (string) asm\n"
+ " \"hex\": \"hex\" (string) hex\n"
+ " },"
+ " \"sigversion\": \"Base\"|\"Witness_V0\", (string) Signature hashing algorithm.\n"
+ " \"steps\": [\n"
+ " {\n"
+ " \"pos\": n, (numeric) Execution position into script, in bytes.\n"
+ " \"pos_codehash\": n, (numeric) Code hashing start position into script, in bytes.\n"
+ " \"opcount\": n, (numeric) Number of sigops executed so far.\n"
+ " \"stack\": [ (array) Items on the stack.\n"
+ " \"hex\", ... (string) Hex encoded data on the stack.\n"
+ " ],\n"
+ " \"altstack\": [ (array) Items on the alternate stack.\n"
+ " \"hex\", ... (string) Hex encoded data on the stack.\n"
+ " ],\n"
+ " \"exec\": [ (array) State of active conditionals.\n"
+ " true|false, ... (boolean) If any of these are false, the next instruction will be skipped.\n"
+ " ],\n"
+ " \"next_op\": \"asm\", (string) Opcode to be processed next, as a single asm instruction.\n"
+ " \"next_opcode\": \"hex\", (string) Hex encoded opcode to be processed next.\n"
+ " \"next_push\": \"hex\", (string) Hex encoded data to be pushed with opcode.\n"
+ " }, ...\n"
+ " ]\n"
+ " }, ...\n"
+ " ]\n"
+ "}\n"
+ );
+
+ RPCTypeCheck(request.params, {UniValue::VOBJ});
+
+ const UniValue & options = request.params[0];
+
+ const UniValue & flags_uv = find_value(options, "flags");
+ unsigned int flags = 0;
+ if (flags_uv.isArray()) {
+ for (unsigned int i = flags_uv.size(); i-- > 0; ) {
+ const UniValue & flag_uv = flags_uv[i];
+ flags |= ParseScriptFlag(flag_uv.get_str());
+ }
+ } else if (!flags_uv.isNull()) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "expected array for \"flags\"");
+ }
+
+ const UniValue & input_uv = find_value(options, "input");
+ if (!input_uv.isObject()) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "expected object for \"input\"");
+ }
+ const UniValue & sPK_uv = find_value(input_uv, "scriptPubKey");
+ if (!sPK_uv.isStr()) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "expected string for \"input\" \"scriptPubKey\"");
+ }
+ const CScript scriptPubKey = ParseHexScript(sPK_uv, "scriptPubKey");
+ const UniValue & amount_uv = find_value(input_uv, "amount");
+ CAmount amount = 0;
+ if (amount_uv.isNum()) {
+ amount = amount_uv.get_int64();
+ } else if (amount_uv.isNull()) {
+ if (flags & SCRIPT_VERIFY_WITNESS) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "\"input\" \"amount\" is required for VERIFY_WITNESS flag");
+ }
+ } else {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "expected number for \"input\" \"amount\"");
+ }
+
+ const UniValue & txid_uv = find_value(input_uv, "txid");
+ const UniValue & rawtx_uv = find_value(input_uv, "transaction");
+ CMutableTransaction mtx;
+ CTransactionRef tx;
+ if (rawtx_uv.isStr()) {
+ if (!DecodeHexTx(mtx, rawtx_uv.get_str(), true)) {
+ throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
+ }
+ if (txid_uv.isStr()) {
+ const uint256 txid = ParseHashV(txid_uv, "txid");
+ if (mtx.GetHash() != txid) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "\"input\" \"txid\" and \"transaction\" strings contradict");
+ }
+ }
+ tx = MakeTransactionRef(std::move(mtx));
+ } else if (txid_uv.isStr()) {
+ const uint256 txid = ParseHashV(txid_uv, "txid");
+ uint256 hashBlock;
+ if (!GetTransaction(txid, tx, Params().GetConsensus(), hashBlock, true)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot find transaction specified by \"txid\"");
+ }
+ } else {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "expected \"input\" with either \"txid\" or \"transaction\" string");
+ }
+
+ const UniValue & vout_uv = find_value(input_uv, "vout");
+ if (!vout_uv.isNum()) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "expected number for \"input\" \"vout\"");
+ }
+ unsigned int vout = vout_uv.get_int64();
+
+ const bool do_trace = find_value(options, "trace").isTrue();
+ verifyscript_ScriptExecutionDebugger debugger;
+
+ PrecomputedTransactionData txdata(*tx);
+ ScriptError err;
+ const bool rv = VerifyScript(tx->vin[vout].scriptSig, scriptPubKey, &tx->vin[vout].scriptWitness, flags, TransactionSignatureChecker(&*tx, vout, amount, txdata), &err, (do_trace ? &debugger : nullptr));
+
+ UniValue result(UniValue::VOBJ);
+ result.push_back(Pair("result", rv));
+
+ if (!rv) {
+ result.push_back(Pair("error", std::string(ScriptErrorString(err))));
+ }
+
+ if (do_trace) {
+ result.pushKV("trace", debugger.GetResult());
+ }
+
+ return result;
+}
+
/** Pushes a JSON object for script verification or signing errors to vErrorsRet. */
static void TxInErrorToJSON(const CTxIn& txin, UniValue& vErrorsRet, const std::string& strMessage)
{
@@ -986,6 +1221,7 @@ static const CRPCCommand commands[] =
{ "rawtransactions", "createrawtransaction", &createrawtransaction, true, {"inputs","outputs","locktime","replaceable"} },
{ "rawtransactions", "decoderawtransaction", &decoderawtransaction, true, {"hexstring"} },
{ "rawtransactions", "decodescript", &decodescript, true, {"hexstring"} },
+ { "rawtransactions", "verifyscript", &verifyscript, true, {"options"} },
{ "rawtransactions", "sendrawtransaction", &sendrawtransaction, false, {"hexstring","allowhighfees"} },
{ "rawtransactions", "combinerawtransaction", &combinerawtransaction, true, {"txs"} },
{ "rawtransactions", "signrawtransaction", &signrawtransaction, false, {"hexstring","prevtxs","privkeys","sighashtype"} }, /* uses wallet if enabled */
@@ -71,18 +71,46 @@ ECCryptoClosure instance_of_eccryptoclosure;
} // namespace
/** Check that all specified flags are part of the libconsensus interface. */
-static bool verify_flags(unsigned int flags)
+static bool verify_flags(uint64_t flags)
{
return (flags & ~(bitcoinconsensus_SCRIPT_FLAGS_VERIFY_ALL)) == 0;
}
+class ScriptExecutionDebuggerForCAPI final : public ScriptExecutionDebugger {
+public:
+ const bitcoinconsensus_script_debugger_callbacks* const c_debugger;
+
+ ScriptExecutionDebuggerForCAPI(const bitcoinconsensus_script_debugger_callbacks* const c_debugger_in, void * const userdata_in) : c_debugger(c_debugger_in) {
+ userdata = userdata_in;
+ }
+
+ void ScriptBegin(ScriptExecution& ex) {
+ c_debugger->ScriptBegin(userdata, (struct bitcoinconsensus_script_execution*)&ex);
+ }
+
+ void ScriptPreStep(ScriptExecution& ex, const CScript::const_iterator& pos, opcodetype& opcode, ScriptExecution::StackElementType& pushdata) {
+ auto c_opcode = (uint8_t)opcode;
+ auto c_pushdata_sz = (size_t)pushdata.size();
+ c_debugger->ScriptPreStep(userdata, (struct bitcoinconsensus_script_execution*)&ex, (pos - ex.script.begin()), &c_opcode, pushdata.data(), &c_pushdata_sz);
+ }
+
+ void ScriptEOF(ScriptExecution& ex, const CScript::const_iterator& pos) {
+ c_debugger->ScriptEOF(userdata, (struct bitcoinconsensus_script_execution*)&ex, (pos - ex.script.begin()));
+ }
+};
+
static int verify_script(const unsigned char *scriptPubKey, unsigned int scriptPubKeyLen, CAmount amount,
const unsigned char *txTo , unsigned int txToLen,
- unsigned int nIn, unsigned int flags, bitcoinconsensus_error* err)
+ unsigned int nIn, uint64_t flags, bitcoinconsensus_error* err, const struct bitcoinconsensus_script_debugger_callbacks* const c_debugger = nullptr, void * const userdata = nullptr)
{
if (!verify_flags(flags)) {
return bitcoinconsensus_ERR_INVALID_FLAGS;
}
+ ScriptExecutionDebuggerForCAPI *cpp_debugger = nullptr;
+ if (c_debugger) {
+ cpp_debugger = new ScriptExecutionDebuggerForCAPI(c_debugger, userdata);
+ }
+ int rv;
try {
TxInputStream stream(SER_NETWORK, PROTOCOL_VERSION, txTo, txToLen);
CTransaction tx(deserialize, stream);
@@ -95,10 +123,24 @@ static int verify_script(const unsigned char *scriptPubKey, unsigned int scriptP
set_error(err, bitcoinconsensus_ERR_OK);
PrecomputedTransactionData txdata(tx);
- return VerifyScript(tx.vin[nIn].scriptSig, CScript(scriptPubKey, scriptPubKey + scriptPubKeyLen), &tx.vin[nIn].scriptWitness, flags, TransactionSignatureChecker(&tx, nIn, amount, txdata), nullptr);
+ rv = VerifyScript(tx.vin[nIn].scriptSig, CScript(scriptPubKey, scriptPubKey + scriptPubKeyLen), &tx.vin[nIn].scriptWitness, flags, TransactionSignatureChecker(&tx, nIn, amount, txdata), nullptr, cpp_debugger);
} catch (const std::exception&) {
- return set_error(err, bitcoinconsensus_ERR_TX_DESERIALIZE); // Error deserializing
+ rv = set_error(err, bitcoinconsensus_ERR_TX_DESERIALIZE); // Error deserializing
}
+ delete cpp_debugger;
+ return rv;
+}
+
+int bitcoinconsensus_trace_script(const unsigned char *scriptPubKey, unsigned int scriptPubKeyLen, int64_t amount, const unsigned char *txTo, unsigned int txToLen, unsigned int nIn, uint64_t flags, bitcoinconsensus_error* err, const struct bitcoinconsensus_script_debugger_callbacks* c_debugger, void *userdata)
+{
+ if (amount < 0) {
+ if (flags & bitcoinconsensus_SCRIPT_FLAGS_VERIFY_WITNESS) {
+ return set_error(err, bitcoinconsensus_ERR_AMOUNT_REQUIRED);
+ }
+ amount = 0;
+ }
+ CAmount am(amount);
+ return ::verify_script(scriptPubKey, scriptPubKeyLen, am, txTo, txToLen, nIn, flags, err, c_debugger, userdata);
}
int bitcoinconsensus_verify_script_with_amount(const unsigned char *scriptPubKey, unsigned int scriptPubKeyLen, int64_t amount,
@@ -6,6 +6,7 @@
#ifndef BITCOIN_BITCOINCONSENSUS_H
#define BITCOIN_BITCOINCONSENSUS_H
+#include <stddef.h>
#include <stdint.h>
#if defined(BUILD_BITCOIN_INTERNAL) && defined(HAVE_CONFIG_H)
@@ -72,6 +73,16 @@ EXPORT_SYMBOL int bitcoinconsensus_verify_script_with_amount(const unsigned char
const unsigned char *txTo , unsigned int txToLen,
unsigned int nIn, unsigned int flags, bitcoinconsensus_error* err);
+struct bitcoinconsensus_script_execution;
+
+struct bitcoinconsensus_script_debugger_callbacks {
+ void (*ScriptBegin)(void *userdata, struct bitcoinconsensus_script_execution*);
+ void (*ScriptPreStep)(void *userdata, struct bitcoinconsensus_script_execution*, size_t pos, const uint8_t* opcode, const void* pushdata, size_t* pushdata_sz);
+ void (*ScriptEOF)(void *userdata, struct bitcoinconsensus_script_execution*, size_t pos);
+};
+
+EXPORT_SYMBOL int bitcoinconsensus_trace_script(const unsigned char *scriptPubKey, unsigned int scriptPubKeyLen, int64_t amount, const unsigned char *txTo, unsigned int txToLen, unsigned int nIn, uint64_t flags, bitcoinconsensus_error* err, const struct bitcoinconsensus_script_debugger_callbacks*, void *userdata);
+
EXPORT_SYMBOL unsigned int bitcoinconsensus_version();
#ifdef __cplusplus
Oops, something went wrong.

0 comments on commit 0afed04

Please sign in to comment.