Skip to content
Open
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
5 changes: 4 additions & 1 deletion ci/test/00_setup_env_native_asan.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,14 @@ fi

export CONTAINER_NAME=ci_native_asan
export APT_LLVM_V="22"
export PACKAGES="systemtap-sdt-dev clang-${APT_LLVM_V} llvm-${APT_LLVM_V} libclang-rt-${APT_LLVM_V}-dev mold python3-zmq qt6-base-dev qt6-tools-dev qt6-l10n-tools libevent-dev libboost-dev libzmq3-dev libqrencode-dev libsqlite3-dev ${BPFCC_PACKAGE} libcapnp-dev capnproto python3-pip"
export PACKAGES="systemtap-sdt-dev clang-${APT_LLVM_V} llvm-${APT_LLVM_V} libclang-rt-${APT_LLVM_V}-dev mold python3-zmq qt6-base-dev qt6-tools-dev qt6-l10n-tools libevent-dev libboost-dev libzmq3-dev libqrencode-dev libsqlite3-dev ${BPFCC_PACKAGE} libcapnp-dev capnproto python3-pip curl ca-certificates"
export PIP_PACKAGES="--break-system-packages pycapnp"
export NO_DEPENDS=1
export GOAL="install"
export CI_LIMIT_STACK_SIZE=1
export RUN_ORD_TESTS=true
export ORD_VERSION=0.29.0
export ORD_LINUX_X86_64_SHA256=f65c758d71549954470aa7fe23b197478688fb4f910e84c2956cf9144078a94e
export BITCOIN_CONFIG="\
--preset=dev-mode \
-DSANITIZERS=address,float-divide-by-zero,integer,undefined \
Expand Down
18 changes: 18 additions & 0 deletions ci/test/03_test_script.sh
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,24 @@ if [ "$RUN_FUNCTIONAL_TESTS" = "true" ]; then
--failfast
fi

if [ "${RUN_ORD_TESTS:-false}" = "true" ]; then
ORD_TEST_DIR="${BASE_SCRATCH_DIR}/ord-${ORD_VERSION}"
ORD_ARCHIVE="${ORD_TEST_DIR}/ord.tar.gz"
mkdir -p "${ORD_TEST_DIR}"
curl --proto '=https' --tlsv1.2 --fail --location --retry 3 \
--output "${ORD_ARCHIVE}" \
"https://github.com/ordinals/ord/releases/download/${ORD_VERSION}/ord-${ORD_VERSION}-x86_64-unknown-linux-gnu.tar.gz"
echo "${ORD_LINUX_X86_64_SHA256} ${ORD_ARCHIVE}" | sha256sum --check
tar --extract --gzip --file "${ORD_ARCHIVE}" --directory "${ORD_TEST_DIR}"
ORD="${ORD_TEST_DIR}/ord-${ORD_VERSION}/ord" \
LD_LIBRARY_PATH="${DEPENDS_DIR}/${HOST}/lib" \
"${BASE_BUILD_DIR}/test/functional/test_runner.py" \
feature_ord_index.py \
--tmpdirprefix "${BASE_SCRATCH_DIR}/ord_test_runner/" \
--timeout-factor="${TEST_RUNNER_TIMEOUT_FACTOR}" \
--quiet
fi

if [ "${RUN_TIDY}" = "true" ]; then
cmake -B /tidy-build -DLLVM_DIR=/usr/lib/llvm-"${TIDY_LLVM_V}"/cmake -DCMAKE_BUILD_TYPE=Release -S "${BASE_ROOT_DIR}"/contrib/devtools/bitcoin-tidy
cmake --build /tidy-build "$MAKEJOBS"
Expand Down
1 change: 0 additions & 1 deletion depends/packages/qt.mk
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,6 @@ $(package)_config_opts_darwin += -no-feature-freetype
$(package)_config_opts_darwin += -no-pkg-config

$(package)_config_opts_linux := -fontconfig
$(package)_config_opts_linux += -no-feature-process
$(package)_config_opts_linux += -no-feature-xlib
$(package)_config_opts_linux += -no-xcb-xlib
$(package)_config_opts_linux += -pkg-config
Expand Down
60 changes: 60 additions & 0 deletions doc/ord.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Optional Ord integration

DogMode can install and initialize the [`ord`](https://github.com/ordinals/ord)
indexer as an explicit option during first-run setup. This integration does not
enable Ord by default and does not add inscription controls to the wallet.

## What the option changes

Selecting **Download and synchronize Ord** disables pruning and enables the
Bitcoin Core options required by Ord: `txindex`, `server`, and `rest`. These
values are saved in `settings.json`. An explicit conflicting command-line value,
such as `-prune=1` or `-txindex=0`, causes startup to stop with an explanation
instead of silently overriding the command line.

After Bitcoin Core and its transaction index are synchronized, DogMode:

1. Opens the pinned Ord 0.29.0 release in the system browser when requested and
asks you to select the downloaded archive.
2. Verifies the selected archive against a checksum compiled into DogMode.
3. Extracts it with the platform `tar` program, checks `ord --version`, and
installs it atomically.
4. Runs `ord index update` in the background with cookie authentication.

The Ord executable and index are stored below the network-specific Bitcoin data
directory:

```
ord/bin/0.29.0/ord
ord/data/
```

On Windows the executable is named `ord.exe`. Testnet, testnet4, signet, and
regtest each use their own Bitcoin network data directory and Ord index.

## Terminal use

The executable is deliberately not added to `PATH`. Invoke it by its full path.
For a custom Bitcoin data directory, also pass the matching Bitcoin data and
cookie paths. For example on mainnet:

```sh
<bitcoin-data-dir>/ord/bin/0.29.0/ord \
--bitcoin-data-dir <bitcoin-data-dir> \
--cookie-file <bitcoin-data-dir>/.cookie \
--data-dir <bitcoin-data-dir>/ord/data \
index info
```

Ord is third-party experimental software and is distributed separately under
its own license. DogMode itself does not make network requests to install Ord;
the system browser performs the download. Its index requires significant
additional disk space. Deleting the `ord/` directory removes the installed
executable and index but does not change DogMode's saved full-node settings.

## Updating the pinned release

Update the version, URLs, and all platform checksums together in
`src/qt/ordinstaller.cpp`. The extended CI job independently downloads the
Linux archive, verifies its pinned checksum, and runs
`test/functional/feature_ord_index.py` against a regtest node.
9 changes: 9 additions & 0 deletions doc/release-notes-ord.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Optional Ord full-node setup
============================

The first-run data-directory dialog now offers an opt-in option to install and
synchronize Ord. Enabling it configures an unpruned node with `txindex`, RPC
server, and REST support, directs the user to the pinned official Ord release,
verifies the selected archive's checksum and version, and builds the Ord index
after Bitcoin synchronization. Ord remains a command-line tool in this release.
See `doc/ord.md` for storage, security, and terminal-use details.
10 changes: 9 additions & 1 deletion src/qt/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,14 @@ add_library(bitcoinqt STATIC EXCLUDE_FROM_ALL
optionsdialog.h
optionsmodel.cpp
optionsmodel.h
ordconfiguration.cpp
ordconfiguration.h
ordinstaller.cpp
ordinstaller.h
ordmanager.cpp
ordmanager.h
ordprocess.cpp
ordprocess.h
peertablemodel.cpp
peertablemodel.h
peertablesortproxy.cpp
Expand Down Expand Up @@ -146,6 +154,7 @@ set_property(SOURCE macnotificationhandler.mm
target_link_libraries(bitcoinqt
PUBLIC
Qt6::Widgets
Qt6::Network
PRIVATE
core_interface
bitcoin_cli
Expand Down Expand Up @@ -223,7 +232,6 @@ if(ENABLE_WALLET)
target_link_libraries(bitcoinqt
PRIVATE
bitcoin_wallet
Qt6::Network
)
endif()

Expand Down
133 changes: 132 additions & 1 deletion src/qt/bitcoin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,14 @@
#include <qt/intro.h>
#include <qt/networkstyle.h>
#include <qt/optionsmodel.h>
#include <qt/ordconfiguration.h>
#include <qt/ordmanager.h>
#include <qt/platformstyle.h>
#include <qt/splashscreen.h>
#include <qt/utilitydialog.h>
#include <qt/winshutdownmonitor.h>
#include <uint256.h>
#include <univalue.h>
#include <util/exception.h>
#include <util/string.h>
#include <util/threadnames.h>
Expand All @@ -50,14 +53,18 @@

#include <QApplication>
#include <QDebug>
#include <QDesktopServices>
#include <QFileDialog>
#include <QLatin1String>
#include <QLibraryInfo>
#include <QLocale>
#include <QMessageBox>
#include <QPushButton>
#include <QSettings>
#include <QThread>
#include <QTimer>
#include <QTranslator>
#include <QUrl>
#include <QWindow>

// Declare meta types used for QMetaObject::invokeMethod
Expand Down Expand Up @@ -319,6 +326,123 @@ void BitcoinApplication::InitPruneSetting(int64_t prune_MiB)
optionsModel->SetPruneTargetGB(PruneMiBtoGB(prune_MiB));
}

bool BitcoinApplication::InitOrdSetting(bool enabled)
{
if (!enabled) return true;

const auto bool_setting = [this](const char* name, bool default_value) {
const auto value{node().getPersistentSetting(name)};
const bool command_line{node().isSettingIgnored(name)};
return OrdBoolSetting{
command_line ? gArgs.GetBoolArg(std::string{"-"} + name, default_value) : SettingToBool(value, default_value),
command_line ? OrdSettingSource::COMMAND_LINE : value.isNull() ? OrdSettingSource::DEFAULT : OrdSettingSource::PERSISTENT,
};
};
const auto prune_value{node().getPersistentSetting("prune")};
const bool prune_command_line{node().isSettingIgnored("prune")};
const OrdNodeOptions current{
.prune = {
prune_command_line ? gArgs.GetIntArg("-prune", 0) != 0 : SettingTo<int64_t>(prune_value, 0) != 0,
prune_command_line ? OrdSettingSource::COMMAND_LINE : prune_value.isNull() ? OrdSettingSource::DEFAULT : OrdSettingSource::PERSISTENT,
},
.txindex = bool_setting("txindex", false),
.server = bool_setting("server", false),
.rest = bool_setting("rest", false),
};
const auto resolved{ResolveOrdConfiguration(true, current)};
if (!resolved.ok()) {
QMessageBox::critical(nullptr, tr("Ord configuration error"), resolved.error);
return false;
}

// These settings must take effect before base initialization. Persisting the
// standard node options also makes the opt-in survive subsequent launches.
for (const auto& name : {"txindex", "server", "rest"}) {
node().forceSetting(name, true);
node().updateRwSetting(name, true);
}
node().forceSetting("prune", 0);
node().updateRwSetting("prune", 0);
m_ord_enabled = true;
return true;
}

void BitcoinApplication::startOrd()
{
if (!m_ord_enabled || m_ord_manager) return;

QString chain;
switch (gArgs.GetChainType()) {
case ChainType::MAIN: chain = QStringLiteral("mainnet"); break;
case ChainType::TESTNET: chain = QStringLiteral("testnet"); break;
case ChainType::TESTNET4: chain = QStringLiteral("testnet4"); break;
case ChainType::SIGNET: chain = QStringLiteral("signet"); break;
case ChainType::REGTEST: chain = QStringLiteral("regtest"); break;
}
m_ord_manager = std::make_unique<OrdManager>(
GUIUtil::PathToQString(gArgs.GetDataDirBase()),
GUIUtil::PathToQString(gArgs.GetDataDirNet()),
chain,
this);
connect(m_ord_manager.get(), &OrdManager::progress, window, [this](const QString& title, int percentage) {
window->showProgress(title, percentage);
});
connect(m_ord_manager.get(), &OrdManager::failed, window, [this](const QString& error) {
window->showProgress({}, 100);
window->message(tr("Ord error"), error, CClientUIInterface::MSG_ERROR);
});
connect(m_ord_manager.get(), &OrdManager::installationRequired, window, [this](const QString& version, const QString& url, const QString& sha256) {
QMessageBox prompt{window};
prompt.setWindowTitle(tr("Install Ord"));
prompt.setIcon(QMessageBox::Information);
prompt.setText(tr("DogMode requires the official Ord archive before it can build the Ord index."));
prompt.setInformativeText(tr("Download Ord %1 from its official release page, then select the downloaded archive. DogMode will verify this SHA-256 checksum before installing it:\n\n%2")
.arg(version, sha256));
auto* download{prompt.addButton(tr("Download Ord"), QMessageBox::ActionRole)};
auto* select{prompt.addButton(tr("Select Archive"), QMessageBox::AcceptRole)};
prompt.addButton(tr("Later"), QMessageBox::RejectRole);
prompt.exec();

if (prompt.clickedButton() == download) {
if (!QDesktopServices::openUrl(QUrl{url})) {
window->message(tr("Ord error"), tr("Could not open the Ord download page: %1").arg(url), CClientUIInterface::MSG_ERROR);
return;
}
} else if (prompt.clickedButton() != select) {
return;
}

const QString archive{QFileDialog::getOpenFileName(window, tr("Select Ord archive"), {}, tr("Ord archives (*.tar.gz *.zip);;All files (*)"))};
if (!archive.isEmpty()) m_ord_manager->installArchive(archive);
});
connect(m_ord_manager.get(), &OrdManager::ready, this, [this] {
window->message(tr("Ord installed"), tr("Ord is installed and will begin indexing after the Bitcoin blockchain and transaction index are synchronized."), CClientUIInterface::MSG_INFORMATION);
m_ord_sync_timer = new QTimer(this);
connect(m_ord_sync_timer, &QTimer::timeout, this, &BitcoinApplication::maybeStartOrdIndex);
m_ord_sync_timer->start(30000);
maybeStartOrdIndex();
});
connect(m_ord_manager.get(), &OrdManager::indexComplete, window, [this] {
window->message(tr("Ord synchronized"), tr("The Ord index is synchronized. Use %1 in a terminal for Ordinal operations.").arg(m_ord_manager->executablePath()), CClientUIInterface::MSG_INFORMATION);
});
m_ord_manager->start();
}

void BitcoinApplication::maybeStartOrdIndex()
{
if (!m_ord_manager || !m_ord_manager->isReady() || node().isInitialBlockDownload()) return;
try {
const UniValue indexes{node().executeRpc("getindexinfo", UniValue{UniValue::VARR}, {})};
if (!indexes.exists("txindex") || !indexes["txindex"].exists("synced") || !indexes["txindex"]["synced"].get_bool()) return;
} catch (const std::exception& e) {
qWarning() << "Unable to query txindex status for Ord:" << e.what();
return;
}
if (m_ord_sync_timer) m_ord_sync_timer->stop();
window->message(tr("Ord indexing"), tr("Bitcoin synchronization is complete. Ord is now building its index in the background."), CClientUIInterface::MSG_INFORMATION);
m_ord_manager->startIndex();
}

void BitcoinApplication::requestInitialize()
{
qDebug() << __func__ << ": Requesting initialize";
Expand All @@ -328,6 +452,7 @@ void BitcoinApplication::requestInitialize()

void BitcoinApplication::requestShutdown()
{
if (m_ord_manager) m_ord_manager->stop();
for (const auto w : QGuiApplication::topLevelWindows()) {
w->hide();
}
Expand Down Expand Up @@ -414,6 +539,7 @@ void BitcoinApplication::initializeResult(bool success, interfaces::BlockAndHead
window->showMinimized();
}
Q_EMIT windowShown(window);
startOrd();

#ifdef ENABLE_WALLET
// Now that initialization/startup is done, process any command-line
Expand Down Expand Up @@ -576,8 +702,9 @@ int GuiMain(int argc, char* argv[])
// User language is set up: pick a data directory
bool did_show_intro = false;
int64_t prune_MiB = 0; // Intro dialog prune configuration
bool ord_enabled = false;
// Gracefully exit if the user cancels
if (!Intro::showIfNeeded(did_show_intro, prune_MiB)) return EXIT_SUCCESS;
if (!Intro::showIfNeeded(did_show_intro, prune_MiB, ord_enabled)) return EXIT_SUCCESS;

/// 6-7. Parse bitcoin.conf, determine network, switch to network specific
/// options, and create datadir and settings.json.
Expand Down Expand Up @@ -653,6 +780,10 @@ int GuiMain(int argc, char* argv[])
return EXIT_FAILURE;
}

// Resolve Ord first so explicit command-line conflicts are still visible
// before the intro screen's prune choice is forced into memory.
if (!app.InitOrdSetting(ord_enabled)) return EXIT_FAILURE;

if (did_show_intro) {
// Store intro dialog settings other than datadir (network specific)
app.InitPruneSetting(prune_MiB);
Expand Down
8 changes: 8 additions & 0 deletions src/qt/bitcoin.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class BitcoinGUI;
class ClientModel;
class NetworkStyle;
class OptionsModel;
class OrdManager;
class PaymentServer;
class PlatformStyle;
class SplashScreen;
Expand Down Expand Up @@ -48,6 +49,7 @@ class BitcoinApplication: public QApplication
[[nodiscard]] bool createOptionsModel(bool resetSettings);
/// Initialize prune setting
void InitPruneSetting(int64_t prune_MiB);
bool InitOrdSetting(bool enabled);
/// Create main window
void createWindow(const NetworkStyle *networkStyle);
/// Create splash screen
Expand Down Expand Up @@ -90,11 +92,17 @@ public Q_SLOTS:
bool event(QEvent* e) override;

private:
void startOrd();
void maybeStartOrdIndex();

std::optional<InitExecutor> m_executor;
OptionsModel* optionsModel{nullptr};
ClientModel* clientModel{nullptr};
BitcoinGUI* window{nullptr};
QTimer* pollShutdownTimer{nullptr};
QTimer* m_ord_sync_timer{nullptr};
std::unique_ptr<OrdManager> m_ord_manager;
bool m_ord_enabled{false};
#ifdef ENABLE_WALLET
PaymentServer* paymentServer{nullptr};
WalletController* m_wallet_controller{nullptr};
Expand Down
Loading