Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ci/dash/build_src.sh
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ if [ -n "$CONFIG_SHELL" ]; then
export CONFIG_SHELL="$CONFIG_SHELL"
fi

BITCOIN_CONFIG_ALL="--disable-dependency-tracking --prefix=$DEPENDS_DIR/$HOST --bindir=$BASE_OUTDIR/bin --libdir=$BASE_OUTDIR/lib"
BITCOIN_CONFIG_ALL="--enable-external-signer --disable-dependency-tracking --prefix=$DEPENDS_DIR/$HOST --bindir=$BASE_OUTDIR/bin --libdir=$BASE_OUTDIR/lib"
if [ -z "$NO_WERROR" ]; then
BITCOIN_CONFIG_ALL="${BITCOIN_CONFIG_ALL} --enable-werror"
fi
Expand Down
30 changes: 11 additions & 19 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -320,9 +320,9 @@ AC_ARG_ENABLE([werror],
[enable_werror=no])

AC_ARG_ENABLE([external-signer],
[AS_HELP_STRING([--enable-external-signer],[compile external signer support (default is auto, requires Boost::Process)])],
[AS_HELP_STRING([--enable-external-signer],[compile external signer support (default is yes)])],
[use_external_signer=$enableval],
[use_external_signer=auto])
[use_external_signer=yes])

AC_LANG_PUSH([C++])

Expand Down Expand Up @@ -354,7 +354,7 @@ if test "$enable_debug" = "yes"; then
dnl them, to prevent autoconfs "-g -O2" being added. Otherwise we'd end up
dnl with "-O0 -g3 -g -O2".
if test "$CXXFLAGS_overridden" = "no"; then
CXXFLAGS=""
CXXFLAGS=""
fi

dnl Disable all optimizations
Expand Down Expand Up @@ -1611,22 +1611,14 @@ if test "$use_boost" = "yes"; then
fi
fi

if test "$use_external_signer" != "no"; then
case $host in
*mingw*)
dnl Boost Process uses Boost Filesystem when targeting Windows. Also,
dnl since Boost 1.71.0, Process does not work with mingw-w64 without
dnl workarounds. See 67669ab425b52a2b6be3d2f3b3b7e3939b676a2c.
if test "$use_external_signer" = "yes"; then
AC_MSG_ERROR([External signing is not supported on Windows])
fi
use_external_signer="no";
;;
*)
use_external_signer="yes"
AC_DEFINE([ENABLE_EXTERNAL_SIGNER], [1], [Define if external signer support is enabled])
;;
esac
case $host in
dnl Re-enable it after enabling Windows support in cpp-subprocess.
*mingw*)
use_external_signer="no"
;;
esac
if test "$use_external_signer" = "yes"; then
AC_DEFINE([ENABLE_EXTERNAL_SIGNER], [1], [Define if external signer support is enabled])
fi
AM_CONDITIONAL([ENABLE_EXTERNAL_SIGNER], [test "$use_external_signer" = "yes"])

Expand Down
6 changes: 1 addition & 5 deletions doc/build-openbsd.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# OpenBSD Build Guide

**Updated for OpenBSD [7.1](https://www.openbsd.org/71.html)**
**Updated for OpenBSD [7.3](https://www.openbsd.org/73.html)**

This guide describes how to build dashd, command-line utilities, and GUI on OpenBSD.

Expand Down Expand Up @@ -82,10 +82,6 @@ export AUTOMAKE_VERSION=1.16

### 1. Configuration

Note that external signer support is currently not available on OpenBSD, since
the used header-only library Boost.Process fails to compile (certain system
calls and preprocessor defines like `waitid()` and `WEXITED` are missing).

There are many ways to configure Dash Core, here are a few common examples:

##### Descriptor Wallet and GUI:
Expand Down
3 changes: 3 additions & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ BITCOIN_CORE_H = \
coinjoin/walletman.h \
coins.h \
common/bloom.h \
common/run_command.h \
compat/assumptions.h \
compat/byteswap.h \
compat/compat.h \
Expand Down Expand Up @@ -404,6 +405,7 @@ BITCOIN_CORE_H = \
util/sock.h \
util/string.h \
util/spanparsing.h \
util/subprocess.hpp \
util/syserror.h \
util/system.h \
util/time.h \
Expand Down Expand Up @@ -899,6 +901,7 @@ libbitcoin_common_a_SOURCES = \
chainparams.cpp \
coins.cpp \
common/bloom.cpp \
common/run_command.cpp \
compressor.cpp \
core_read.cpp \
core_write.cpp \
Expand Down
50 changes: 50 additions & 0 deletions src/common/run_command.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright (c) 2022 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#if defined(HAVE_CONFIG_H)
#include <config/bitcoin-config.h>
#endif

#include <common/run_command.h>

#include <tinyformat.h>
#include <univalue.h>

#ifdef ENABLE_EXTERNAL_SIGNER
#include <util/subprocess.hpp>
#endif // ENABLE_EXTERNAL_SIGNER

UniValue RunCommandParseJSON(const std::string& str_command, const std::string& str_std_in)
{
#ifdef ENABLE_EXTERNAL_SIGNER
namespace sp = subprocess;

UniValue result_json;
std::istringstream stdout_stream;
std::istringstream stderr_stream;

if (str_command.empty()) return UniValue::VNULL;

auto c = sp::Popen(str_command, sp::input{sp::PIPE}, sp::output{sp::PIPE}, sp::error{sp::PIPE});
if (!str_std_in.empty()) {
c.send(str_std_in);
}
auto [out_res, err_res] = c.communicate();
stdout_stream.str(std::string{out_res.buf.begin(), out_res.buf.end()});
stderr_stream.str(std::string{err_res.buf.begin(), err_res.buf.end()});

std::string result;
std::string error;
std::getline(stdout_stream, result);
std::getline(stderr_stream, error);

const int n_error = c.retcode();
if (n_error) throw std::runtime_error(strprintf("RunCommandParseJSON error: process(%s) returned %d: %s\n", str_command, n_error, error));
if (!result_json.read(result)) throw std::runtime_error("Unable to parse JSON: " + result);

return result_json;
#else
throw std::runtime_error("Compiled without external signing support (required for external signing).");
#endif // ENABLE_EXTERNAL_SIGNER
}
21 changes: 21 additions & 0 deletions src/common/run_command.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) 2022 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#ifndef BITCOIN_COMMON_RUN_COMMAND_H
#define BITCOIN_COMMON_RUN_COMMAND_H

#include <string>

class UniValue;

/**
* Execute a command which returns JSON, and parse the result.
*
* @param str_command The command to execute, including any arguments
* @param str_std_in string to pass to stdin
* @return parsed JSON
*/
UniValue RunCommandParseJSON(const std::string& str_command, const std::string& str_std_in="");

#endif // BITCOIN_COMMON_RUN_COMMAND_H
10 changes: 5 additions & 5 deletions src/external_signer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <chainparams.h>
#include <common/run_command.h>
#include <core_io.h>
#include <psbt.h>
#include <util/strencodings.h>
#include <util/system.h>
#include <external_signer.h>

#include <algorithm>
Expand Down Expand Up @@ -61,12 +61,12 @@ bool ExternalSigner::Enumerate(const std::string& command, std::vector<ExternalS

UniValue ExternalSigner::DisplayAddress(const std::string& descriptor) const
{
return RunCommandParseJSON(m_command + " --fingerprint \"" + m_fingerprint + "\"" + NetworkArg() + " displayaddress --desc \"" + descriptor + "\"");
return RunCommandParseJSON(m_command + " --fingerprint " + m_fingerprint + NetworkArg() + " displayaddress --desc " + descriptor);
}

UniValue ExternalSigner::GetDescriptors(const int account)
{
return RunCommandParseJSON(m_command + " --fingerprint \"" + m_fingerprint + "\"" + NetworkArg() + " getdescriptors --account " + strprintf("%d", account));
return RunCommandParseJSON(m_command + " --fingerprint " + m_fingerprint + NetworkArg() + " getdescriptors --account " + strprintf("%d", account));
}

bool ExternalSigner::SignTransaction(PartiallySignedTransaction& psbtx, std::string& error)
Expand All @@ -89,8 +89,8 @@ bool ExternalSigner::SignTransaction(PartiallySignedTransaction& psbtx, std::str
return false;
}

const std::string command = m_command + " --stdin --fingerprint \"" + m_fingerprint + "\"" + NetworkArg();
const std::string stdinStr = "signtx \"" + EncodeBase64(ssTx.str()) + "\"";
const std::string command = m_command + " --stdin --fingerprint " + m_fingerprint + NetworkArg();
const std::string stdinStr = "signtx " + EncodeBase64(ssTx.str());

const UniValue signer_result = RunCommandParseJSON(command, stdinStr);

Expand Down
4 changes: 4 additions & 0 deletions src/qt/walletcontroller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,10 @@ void CreateWalletActivity::create()
} catch (const std::runtime_error& e) {
QMessageBox::critical(nullptr, tr("Can't list signers"), e.what());
}
if (signers.size() > 1) {
QMessageBox::critical(nullptr, tr("Too many external signers found"), QString::fromStdString("More than one external signer found. Please connect only one at a time."));
signers.clear();
}
m_create_wallet_dialog->setSigners(signers);

m_create_wallet_dialog->setWindowModality(Qt::ApplicationModal);
Expand Down
44 changes: 9 additions & 35 deletions src/test/system_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,11 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
//
#include <test/util/setup_common.h>
#include <util/system.h>
#include <common/run_command.h>
#include <univalue.h>

#ifdef ENABLE_EXTERNAL_SIGNER
#if defined(WIN32) && !defined(__kernel_entry)
// A workaround for boost 1.71 incompatibility with mingw-w64 compiler.
// For details see https://github.com/bitcoin/bitcoin/pull/22348.
#define __kernel_entry
#endif
#include <boost/process.hpp>
#include <util/subprocess.hpp>
#endif // ENABLE_EXTERNAL_SIGNER

#include <boost/test/unit_test.hpp>
Expand All @@ -35,45 +30,30 @@ BOOST_AUTO_TEST_CASE(run_command)
BOOST_CHECK(result.isNull());
}
{
#ifdef WIN32
const UniValue result = RunCommandParseJSON("cmd.exe /c echo {\"success\": true}");
#else
const UniValue result = RunCommandParseJSON("echo \"{\"success\": true}\"");
#endif
const UniValue result = RunCommandParseJSON("echo {\"success\": true}");
BOOST_CHECK(result.isObject());
const UniValue& success = result.find_value("success");
BOOST_CHECK(!success.isNull());
BOOST_CHECK_EQUAL(success.get_bool(), true);
}
{
// An invalid command is handled by Boost
BOOST_CHECK_EXCEPTION(RunCommandParseJSON("invalid_command"), boost::process::process_error, [&](const boost::process::process_error& e) {
BOOST_CHECK(std::string(e.what()).find("RunCommandParseJSON error:") == std::string::npos);
BOOST_CHECK_EQUAL(e.code().value(), 2);
return true;
});
// An invalid command is handled by cpp-subprocess
const std::string expected{"execve failed: "};
BOOST_CHECK_EXCEPTION(RunCommandParseJSON("invalid_command"), subprocess::CalledProcessError, HasReason(expected));
}
{
// Return non-zero exit code, no output to stderr
#ifdef WIN32
const std::string command{"cmd.exe /c call"};
#else
const std::string command{"false"};
#endif
BOOST_CHECK_EXCEPTION(RunCommandParseJSON(command), std::runtime_error, [&](const std::runtime_error& e) {
BOOST_CHECK(std::string(e.what()).find(strprintf("RunCommandParseJSON error: process(%s) returned 1: \n", command)) != std::string::npos);
const std::string what{e.what()};
BOOST_CHECK(what.find(strprintf("RunCommandParseJSON error: process(%s) returned 1: \n", command)) != std::string::npos);
return true;
});
}
{
// Return non-zero exit code, with error message for stderr
#ifdef WIN32
const std::string command{"cmd.exe /c dir nosuchfile"};
const std::string expected{"File Not Found"};
#else
const std::string command{"ls nosuchfile"};
const std::string expected{"No such file or directory"};
#endif
BOOST_CHECK_EXCEPTION(RunCommandParseJSON(command), std::runtime_error, [&](const std::runtime_error& e) {
const std::string what(e.what());
BOOST_CHECK(what.find(strprintf("RunCommandParseJSON error: process(%s) returned", command)) != std::string::npos);
Expand All @@ -83,23 +63,17 @@ BOOST_AUTO_TEST_CASE(run_command)
}
{
// Unable to parse JSON
#ifdef WIN32
const std::string command{"cmd.exe /c echo {"};
#else
const std::string command{"echo {"};
#endif
BOOST_CHECK_EXCEPTION(RunCommandParseJSON(command), std::runtime_error, HasReason("Unable to parse JSON: {"));
}
// Test std::in, except for Windows
#ifndef WIN32
// Test std::in
{
const UniValue result = RunCommandParseJSON("cat", "{\"success\": true}");
BOOST_CHECK(result.isObject());
const UniValue& success = result.find_value("success");
BOOST_CHECK(!success.isNull());
BOOST_CHECK_EQUAL(success.get_bool(), true);
}
#endif
}
#endif // ENABLE_EXTERNAL_SIGNER

Expand Down
Loading
Loading