Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6b2293ad1a | |||
| 0cadbba30c | |||
| fc4bba23b3 |
+424
-681
File diff suppressed because it is too large
Load Diff
+3
-35
@@ -23,7 +23,7 @@ namespace Bootstrap {
|
||||
// Check if data dir already has blockchain data
|
||||
bool NeedsBootstrap(const std::filesystem::path& dataDir);
|
||||
|
||||
// Download a single file via HTTP GET, write to destPath.
|
||||
// Download a file via HTTP GET, write to destPath.
|
||||
// If noProxy is true, bypass Tor SOCKS proxy and connect directly
|
||||
// (used for clearnet bootstrap downloads).
|
||||
// If portOverride is set (>0), uses that port instead of the default PORT.
|
||||
@@ -35,19 +35,6 @@ namespace Bootstrap {
|
||||
int portOverride = -1,
|
||||
int64_t maxDownloadBytes = 4LL * 1024 * 1024 * 1024);
|
||||
|
||||
// Fetch the file manifest (list of relative paths to download)
|
||||
bool FetchFileList(const std::string& host,
|
||||
std::vector<std::string>& files,
|
||||
std::string& strError,
|
||||
bool noProxy = false);
|
||||
|
||||
// Download bootstrap.tar.gz and extract to dataDir.
|
||||
// Falls back to filelist.txt + individual file download if tar.gz unavailable.
|
||||
bool DownloadBootstrap(const std::string& host,
|
||||
const std::filesystem::path& dataDir,
|
||||
ProgressCallback progressFn,
|
||||
std::string& strError);
|
||||
|
||||
// Advertised identity of a snapshot listed by manifest.json.
|
||||
// The advertised SHA256 is accepted only when it matches the hash compiled
|
||||
// into checkpoints.cpp for the same height.
|
||||
@@ -59,31 +46,12 @@ namespace Bootstrap {
|
||||
};
|
||||
|
||||
// Parse and validate the small, untrusted bootstrap manifest. This routine
|
||||
// performs no network I/O and is exposed so malformed-input behavior can be
|
||||
// covered by unit tests.
|
||||
// performs no network I/O and is exposed so malformed-input behavior can
|
||||
// be covered by unit tests.
|
||||
bool ParseRemoteSnapshotManifest(const std::string& manifestText,
|
||||
RemoteSnapshot& snapshot,
|
||||
std::string& strError);
|
||||
|
||||
// Snapshot manifest (parsed from snapshot.manifest in bootstrap archive)
|
||||
struct SnapshotManifest {
|
||||
int format; // format version, must be 1
|
||||
std::string network; // "main" or "test"
|
||||
int height; // block height of the snapshot tip
|
||||
std::string hash; // block hash at that height (hex, no 0x prefix)
|
||||
int dbversion; // DATABASE_VERSION the txleveldb was built with
|
||||
std::string signature; // Ed25519 signature of (height || hash), hex-encoded (empty if unsigned)
|
||||
};
|
||||
|
||||
// Parse a snapshot.manifest file into a SnapshotManifest struct.
|
||||
bool ParseManifest(const std::filesystem::path& manifestPath,
|
||||
SnapshotManifest& manifest,
|
||||
std::string& strError);
|
||||
|
||||
// Verify a parsed manifest against compiled-in checkpoints and config.
|
||||
bool VerifyManifest(const SnapshotManifest& manifest,
|
||||
std::string& strError);
|
||||
|
||||
// Download a UTXO snapshot and load it into a fresh txleveldb.
|
||||
// This is much faster than downloading the full bootstrap archive.
|
||||
// Returns true if snapshot was downloaded and loaded successfully.
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright (c) 2024-2026 Triangles developers
|
||||
// Distributed under the MIT/X11 software license
|
||||
//
|
||||
// Embedded trust anchors for HTTPS bootstrap. Added to the X509 store as
|
||||
// belt-and-suspenders regardless of which other trust source succeeded:
|
||||
// the exedir cacert.pem, SSL_CERT_FILE, or system default paths may or
|
||||
// may not contain the specific Let's Encrypt anchor that signed the
|
||||
// current bootstrap server's certificate chain. Adding these anchors
|
||||
// only ever EXPANDS the set of valid chains (it can never cause a
|
||||
// previously-valid cert to be rejected), so it's safe to layer on top
|
||||
// of any operator-supplied bundle.
|
||||
//
|
||||
// These are the Mozilla CA bundle entries for ISRG Root X1 and X2 — the
|
||||
// anchors Let's Encrypt uses to sign every certificate they currently issue
|
||||
// (R10/R11/R12 intermediates chain to X1; the YE1 intermediate chains to X2).
|
||||
// Sourced from https://curl.se/ca/cacert.pem and verified via SHA-256 against
|
||||
// the Mozilla NSS bundle.
|
||||
//
|
||||
// Last verified: 2026-08-06 (cacert.pem snapshot).
|
||||
#ifndef TRIANGLES_BOOTSTRAP_ROOTS_H
|
||||
#define TRIANGLES_BOOTSTRAP_ROOTS_H
|
||||
|
||||
const char* const EMBEDDED_ISRG_ROOT_X1_PEM =
|
||||
"-----BEGIN CERTIFICATE-----\n"
|
||||
"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\n"
|
||||
"m+kXQ99b21/+jh5Xos1AnX5iItreGCc=\n"
|
||||
"-----END CERTIFICATE-----";
|
||||
|
||||
const char* const EMBEDDED_ISRG_ROOT_X2_PEM =
|
||||
"-----BEGIN CERTIFICATE-----\n"
|
||||
"MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQswCQYDVQQGEwJV\n"
|
||||
"UzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElT\n"
|
||||
"UkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVT\n"
|
||||
"MSkwJwYDVQQKEyBJbnRlcm5ldCBTZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNS\n"
|
||||
"RyBSb290IFgyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0H\n"
|
||||
"ttwW+1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9ItgKbppb\n"
|
||||
"d9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV\n"
|
||||
"HQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZIzj0EAwMDaAAwZQIwe3lORlCEwkSHRhtF\n"
|
||||
"cP9Ymd70/aTSVaYgLXTWNLxBo1BfASdWtL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5\n"
|
||||
"U6VR5CmD1/iQMVtCnwr1/q4AaOeMSQ+2b1tbFfLn\n"
|
||||
"-----END CERTIFICATE-----";
|
||||
|
||||
|
||||
#endif // TRIANGLES_BOOTSTRAP_ROOTS_H
|
||||
@@ -911,10 +911,21 @@ void CI2PEmbedded::DiscoverServerTunnelDestination()
|
||||
// Step 2: only publish if a LIVE registered server tunnel matches
|
||||
// the keys-file hash. Registry membership confirms the tunnel is
|
||||
// active; LeaseSet publication is i2pd's responsibility after that.
|
||||
//
|
||||
// Thread-safety: GetServerTunnels() returns a const reference to
|
||||
// i2pd's internal m_ServerTunnels map, which has NO internal lock.
|
||||
// VisitTunnels(true) (called from ReloadConfig / Stop) can erase
|
||||
// entries concurrently. We make a VALUE COPY of the map (not a
|
||||
// reference) so that iterator invalidation during the copy is a
|
||||
// narrow read-only window, and all string comparisons run on the
|
||||
// local snapshot with no live-map access. The copy constructor of
|
||||
// std::map is exception-safe; if it throws (bad_alloc), the catch
|
||||
// below handles it.
|
||||
if (!keysFileIdentB32.empty()) {
|
||||
try {
|
||||
for (const auto& kv : i2p::client::context.GetServerTunnels()) {
|
||||
const i2p::data::IdentHash& dest = kv.first.first;
|
||||
auto tunnels = i2p::client::context.GetServerTunnels(); // value copy
|
||||
for (const auto& kv : tunnels) {
|
||||
const auto& dest = kv.first.first;
|
||||
if (dest.ToBase32() == keysFileIdentB32) {
|
||||
if (i2pHostname != keysFileIdentB32 + ".b32.i2p") {
|
||||
i2pHostname = keysFileIdentB32 + ".b32.i2p";
|
||||
|
||||
+8
-4
@@ -4424,16 +4424,20 @@ bool FastImportBlockFile()
|
||||
hash.ToString().c_str());
|
||||
}
|
||||
pindexNew->SetStakeModifier(nStakeModifier, fGeneratedStakeModifier);
|
||||
|
||||
// Insert into mapBlockIndex and set phashBlock BEFORE calling
|
||||
// GetStakeModifierChecksum, which calls GetBlockHash() which
|
||||
// dereferences phashBlock. Without this ordering, phashBlock is
|
||||
// null and the checksum call segfaults.
|
||||
auto mi = mapBlockIndex.insert(make_pair(hash, pindexNew)).first;
|
||||
pindexNew->phashBlock = &mi->first;
|
||||
|
||||
pindexNew->nStakeModifierChecksum = GetStakeModifierChecksum(pindexNew);
|
||||
|
||||
// PoS stake seen set
|
||||
if (pindexNew->IsProofOfStake())
|
||||
setStakeSeen.insert(make_pair(pindexNew->prevoutStake, pindexNew->nStakeTime));
|
||||
|
||||
// Insert into mapBlockIndex
|
||||
auto mi = mapBlockIndex.insert(make_pair(hash, pindexNew)).first;
|
||||
pindexNew->phashBlock = &mi->first;
|
||||
|
||||
// pnext is rebuilt after best-chain selection. File order also
|
||||
// contains side branches, so assigning it here would let the last
|
||||
// imported child hijack stake-modifier forward walks.
|
||||
|
||||
+44
-21
@@ -17,12 +17,28 @@
|
||||
#include <QCheckBox>
|
||||
#include <QApplication>
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
#include <cstdio>
|
||||
#include <ctime>
|
||||
#include <set>
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
// Convert QString to fs::path preserving non-ASCII characters on Windows.
|
||||
// On Windows, QString::toStdString() returns UTF-8 but std::filesystem::path
|
||||
// constructed from a narrow string then uses the ANSI code page, which
|
||||
// mangles UTF-8 paths. QString::toStdWString() + fs::path(std::wstring)
|
||||
// preserves them. On non-Windows platforms the UTF-8 path is correct.
|
||||
static fs::path qstringToPath(const QString& s)
|
||||
{
|
||||
#ifdef WIN32
|
||||
return fs::path(std::wstring(s.toStdWString()));
|
||||
#else
|
||||
return fs::path(s.toStdString());
|
||||
#endif
|
||||
}
|
||||
|
||||
IntroDialog::IntroDialog(QWidget *parent) :
|
||||
QDialog(parent)
|
||||
{
|
||||
@@ -157,7 +173,7 @@ void IntroDialog::on_defaultRadio_toggled(bool checked)
|
||||
void IntroDialog::updateFreeSpace()
|
||||
{
|
||||
QString path = getDataDirectory();
|
||||
std::filesystem::path fsPath(path.toStdString());
|
||||
std::filesystem::path fsPath = qstringToPath(path);
|
||||
|
||||
// Walk up to find an existing parent
|
||||
try {
|
||||
@@ -216,14 +232,18 @@ bool IntroDialog::pickDataDirectory()
|
||||
}
|
||||
|
||||
// If the saved path is the default, don't set -datadir (let normal defaults work)
|
||||
QString defaultDir = QString::fromStdString(GetDefaultDataDir().string());
|
||||
QString defaultDir = QString::fromStdString(
|
||||
std::string(GetDefaultDataDir().u8string()));
|
||||
if (dataDir != defaultDir) {
|
||||
mapArgs["-datadir"] = dataDir.toStdString();
|
||||
// Pass the data dir to the daemon as UTF-8 bytes so a non-ASCII path
|
||||
// on Windows isn't mangled by the ANSI code page (path::string() does
|
||||
// that). The daemon side uses fs::u8path() to convert back.
|
||||
mapArgs["-datadir"] = std::string(qstringToPath(dataDir).u8string());
|
||||
}
|
||||
|
||||
// Ensure the directory exists
|
||||
try {
|
||||
fs::create_directories(fs::path(dataDir.toStdString()));
|
||||
fs::create_directories(qstringToPath(dataDir));
|
||||
} catch (const fs::filesystem_error &) {
|
||||
QMessageBox::critical(0, "Triangles",
|
||||
QString("Error: Could not create data directory \"%1\".").arg(dataDir));
|
||||
@@ -232,7 +252,7 @@ bool IntroDialog::pickDataDirectory()
|
||||
|
||||
// Auto-bootstrap: if no blockchain data exists, download automatically.
|
||||
// If data exists, offer optional re-download (unless user checked "don't ask again").
|
||||
fs::path dataDirPath(dataDir.toStdString());
|
||||
fs::path dataDirPath = qstringToPath(dataDir);
|
||||
bool needsBootstrap = Bootstrap::NeedsBootstrap(dataDirPath);
|
||||
bool userWantsBootstrap = false;
|
||||
// Captured local-load error from the staged-snapshot probe below, surfaced
|
||||
@@ -429,21 +449,24 @@ bool IntroDialog::pickDataDirectory()
|
||||
QApplication::processEvents();
|
||||
};
|
||||
|
||||
// Try the fast UTXO snapshot path first (matches daemon behavior in init.cpp).
|
||||
// The legacy DownloadBootstrap() is hard-disabled in bootstrap.cpp — it always
|
||||
// returns false with "Legacy file-list bootstrap is disabled". Calling it here
|
||||
// would make the GUI wallet unable to bootstrap a fresh install.
|
||||
// Try the fast UTXO snapshot path. The GUI has already probed the data
|
||||
// dir for a staged utxo-snapshot.bin above; if that didn't find one,
|
||||
// DownloadUtxoSnapshot is the canonical HTTPS path to the bootstrap
|
||||
// server. TLS validation is now handled in bootstrap.cpp's StartTLS
|
||||
// via a layered trust store (exedir cacert.pem → SSL_CERT_FILE →
|
||||
// system default paths → embedded ISRG X1 + X2 as belt-and-suspenders),
|
||||
// so this should succeed on Windows GUI builds where the Qt-bundled
|
||||
// libssl-3-x64.dll ships without a default cert path. The system-path
|
||||
// call is trusted on its return value (OpenSSL's hashed-directory
|
||||
// lookups are lazy and would otherwise show 0 eagerly loaded store
|
||||
// objects even on a valid install); the embedded fallbacks are
|
||||
// always attempted as cross-sign resilience and become load-bearing
|
||||
// on a stripped Windows GUI with no cacert.pem and no usable system
|
||||
// CA directory.
|
||||
std::string utxoError;
|
||||
bool success = Bootstrap::DownloadUtxoSnapshot(host, dataDirPath, progressFn, utxoError);
|
||||
if (!success) {
|
||||
// Fall back to legacy bootstrap path (will fail with "disabled" error, but
|
||||
// surfaces the real error if the snapshot path had a different failure).
|
||||
std::string legacyError;
|
||||
if (Bootstrap::DownloadBootstrap(host, dataDirPath, progressFn, legacyError)) {
|
||||
success = true;
|
||||
} else {
|
||||
strError = "UTXO snapshot: " + utxoError + " | Legacy: " + legacyError;
|
||||
}
|
||||
strError = utxoError;
|
||||
}
|
||||
if (!success) {
|
||||
// The TLS-detection strings are matched against the standard error
|
||||
@@ -509,8 +532,8 @@ bool IntroDialog::migrateDataDirectory(const QString& oldPath, const QString& ne
|
||||
{
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
fs::path srcDir(oldPath.toStdString());
|
||||
fs::path dstDir(newPath.toStdString());
|
||||
fs::path srcDir = qstringToPath(oldPath);
|
||||
fs::path dstDir = qstringToPath(newPath);
|
||||
|
||||
if (!fs::exists(srcDir) || !fs::is_directory(srcDir))
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user