Merge SAMI-PC hd-wallet + process-I2P into v6 master

Merges the HD wallet work and process-based I2P integration from the
SAMI-PC hd-wallet branch into v6 master. Conflict resolution keeps
v6 embedded I2P (CI2PEmbedded) as primary, includes process-I2P
files for reference, preserves FastImportBlockFile() from hd-wallet,
and keeps v6 version numbers (6.0.0) and wAddressStack Qt layout.
This commit is contained in:
Krystie
2026-06-29 14:52:53 -07:00
20 changed files with 2066 additions and 491 deletions
+12 -12
View File
@@ -182,9 +182,9 @@ jobs:
if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then
echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV
else
MAJOR=$(grep 'CLIENT_VERSION_MAJOR' src/clientversion.h | awk '{print $3}')
MINOR=$(grep 'CLIENT_VERSION_MINOR' src/clientversion.h | awk '{print $3}')
REV=$(grep 'CLIENT_VERSION_REVISION' src/clientversion.h | awk '{print $3}')
MAJOR=$(grep 'CLIENT_VERSION_MAJOR' src/clientversion.h | tr -d '\r' | awk '{print $3}')
MINOR=$(grep 'CLIENT_VERSION_MINOR' src/clientversion.h | tr -d '\r' | awk '{print $3}')
REV=$(grep 'CLIENT_VERSION_REVISION' src/clientversion.h | tr -d '\r' | awk '{print $3}')
echo "VERSION=${MAJOR}.${MINOR}.${REV}" >> $GITHUB_ENV
fi
@@ -413,9 +413,9 @@ jobs:
if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then
echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV
else
MAJOR=$(grep 'CLIENT_VERSION_MAJOR' src/clientversion.h | awk '{print $3}')
MINOR=$(grep 'CLIENT_VERSION_MINOR' src/clientversion.h | awk '{print $3}')
REV=$(grep 'CLIENT_VERSION_REVISION' src/clientversion.h | awk '{print $3}')
MAJOR=$(grep 'CLIENT_VERSION_MAJOR' src/clientversion.h | tr -d '\r' | awk '{print $3}')
MINOR=$(grep 'CLIENT_VERSION_MINOR' src/clientversion.h | tr -d '\r' | awk '{print $3}')
REV=$(grep 'CLIENT_VERSION_REVISION' src/clientversion.h | tr -d '\r' | awk '{print $3}')
echo "VERSION=${MAJOR}.${MINOR}.${REV}" >> $GITHUB_ENV
fi
@@ -549,9 +549,9 @@ jobs:
if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then
echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV
else
MAJOR=$(grep 'CLIENT_VERSION_MAJOR' src/clientversion.h | awk '{print $3}')
MINOR=$(grep 'CLIENT_VERSION_MINOR' src/clientversion.h | awk '{print $3}')
REV=$(grep 'CLIENT_VERSION_REVISION' src/clientversion.h | awk '{print $3}')
MAJOR=$(grep 'CLIENT_VERSION_MAJOR' src/clientversion.h | tr -d '\r' | awk '{print $3}')
MINOR=$(grep 'CLIENT_VERSION_MINOR' src/clientversion.h | tr -d '\r' | awk '{print $3}')
REV=$(grep 'CLIENT_VERSION_REVISION' src/clientversion.h | tr -d '\r' | awk '{print $3}')
echo "VERSION=${MAJOR}.${MINOR}.${REV}" >> $GITHUB_ENV
fi
@@ -620,9 +620,9 @@ jobs:
if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then
echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV
else
MAJOR=$(grep 'CLIENT_VERSION_MAJOR' src/clientversion.h | awk '{print $3}')
MINOR=$(grep 'CLIENT_VERSION_MINOR' src/clientversion.h | awk '{print $3}')
REV=$(grep 'CLIENT_VERSION_REVISION' src/clientversion.h | awk '{print $3}')
MAJOR=$(grep 'CLIENT_VERSION_MAJOR' src/clientversion.h | tr -d '\r' | awk '{print $3}')
MINOR=$(grep 'CLIENT_VERSION_MINOR' src/clientversion.h | tr -d '\r' | awk '{print $3}')
REV=$(grep 'CLIENT_VERSION_REVISION' src/clientversion.h | tr -d '\r' | awk '{print $3}')
echo "VERSION=${MAJOR}.${MINOR}.${REV}" >> $GITHUB_ENV
fi
+68
View File
@@ -0,0 +1,68 @@
# I2P support (SAM v3)
Triangles runs over I2P in addition to Tor, giving the wallet a second
anonymous network and a `.b32.i2p` address shown directly above the `.onion`
address in the status bar.
I2P is **on by default** and works the same way as the embedded Tor: the wallet
auto-launches a bundled **i2pd** router as a managed child process, enables its
SAM bridge, and connects to it. The user does not have to install or configure
anything — provided the i2pd binary ships with the wallet.
## Shipping the i2pd binary
Like `tor.exe`, the wallet looks for an `i2pd` executable in several places and
launches the first one it finds:
1. Next to the wallet executable (recommended): `i2pd.exe` (Windows) / `i2pd`
(Linux/macOS), or in an `i2pd/` subfolder beside it.
2. In the data directory (or its `i2pd/` subfolder).
3. Common system locations (`/usr/bin/i2pd`, Homebrew, `C:\i2pd\…`, etc.).
Get i2pd from https://i2pd.website/ (or your package manager) and place the
binary next to the wallet in your build/packaging step. That's the only manual
part, and it's a packaging concern, not something the end user does.
If no i2pd binary is found, the wallet logs a notice and continues with **Tor
only** — I2P is strictly additive and never blocks start-up.
## What happens at start-up
1. If a SAM bridge is already listening on `127.0.0.1:7656` (e.g. you run your
own router), the wallet uses it and does **not** launch its own.
2. Otherwise it writes `i2pd.conf` into `<datadir>/i2pd/` (SAM enabled, other
services off), launches i2pd, and waits for the SAM bridge to come up.
3. The SAM client then loads/creates a persistent destination
(`<datadir>/i2p_private_key`), opens a STREAM session, derives the
`.b32.i2p` address (`base32(SHA-256(destination))`), accepts inbound I2P
streams, and dials outbound `.b32.i2p` peers.
4. On wallet exit, the SAM session is closed and the i2pd child process is
terminated (an external router you started yourself is left running).
The first session takes a little longer while i2pd builds tunnels; the address
appears once the bridge is ready.
## Options
```
-i2p Enable I2P; auto-launches bundled i2pd (default: 1; -i2p=0 to disable)
-i2psam=<ip:port> SAM bridge address (default: 127.0.0.1:7656).
A non-loopback address disables the bundled router and
connects to that external bridge instead.
```
## Checking it
* GUI: the `.b32.i2p` address sits on top of the `.onion` in the status bar;
click either to copy.
* RPC: `getinfo` shows `toraddress` and `i2paddress`; `getnetworkinfo` shows
`toraddress` and an `i2p` object (`enabled`, `active`, `address`, `peers`).
## Notes / limitations
* The address serialization format carries a flag for I2P addresses, so **all
nodes must run this build** to exchange I2P peers; an old `peers.dat` is
discarded.
* `i2p_private_key` is your stable I2P identity — back it up, don't delete it.
* This was implemented without a build/CI environment here; build and test
against a real i2pd before relying on it.
+2
View File
@@ -54,6 +54,8 @@ set(CORE_SOURCES
net.cpp
net_bootstrap.cpp
netbase.cpp
i2p.cpp
i2p_process.cpp
protocol.cpp
script.cpp
sync.cpp
+453 -453
View File
@@ -1,453 +1,453 @@
// Copyright (c) 2009-2012 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 "checkpoints.h"
#include "txdb.h"
#include "main.h"
#include "uint256.h"
namespace Checkpoints
{
typedef std::map<int, uint256> MapCheckpoints;
//
// What makes a good checkpoint block?
// + Is surrounded by blocks with reasonable timestamps
// (no blocks before with a timestamp after, none after with
// timestamp before)
// + Contains no strange transactions
//
static MapCheckpoints mapCheckpoints = {
{ 0, hashGenesisBlockOfficial },
{ 2000, uint256("0x0000000000b5f20078bf46ebdf1500813bb6b2cb482065aa93b89e073b2c6467")},
{ 2101, uint256("0xd4ea1ac45b63c8162a7fc8033cec441db8d532ba988202849d7831e32fe2d059")},
{ 2847, uint256("0xb5015e2835f13fd3bb6135cff9b31ac33310c9b77d694bdb592b8680d98d018e")},
{ 3589, uint256("0xb12a2ca3db4e288cada98aa2139768532bd4474c49dd7b8158031032dac08d51")},
{ 3935, uint256("0xe16290c9757d1368b8d7c35de07d4f8f70c2c9f9c785b667df0c3bff85086ca6")},
{ 5703, uint256("0x587db07bb2172ad7db72c5fabc2518262a1b27f503f99417510b2c6fafa6557b")},
{ 9000, uint256("0x00000000019ef6b2f5e7c324c7d083ee94502305aabc7e9cd73a7fb2a57bb8db")},
{ 9001, uint256("0x6d5c6c5f201cc9e59659ee0da30d1430dc6bf3b12a8ff4c3864ab8d6286b0007")},
{ 9002, uint256("0xa1e20fb1d44688b763690cf74d6aefe859e4cc32981f9e3f2b2ae9702bbcf249")},
{ 10881, uint256("0x4b6554c45e1e6764a6f3c309c47baf53c9edd81f624e52b072518cd15da237e6")},
{ 17650, uint256("0x224940e1f986a202209b8e762728d1452ab45870c308abf84905674acf326a47")},
// Recent finality pin (PoS era). Closes the long unchecked span from
// 17650 to the live tip so stale-bootstrap / low-trust forks below
// this height are rejected outright. Hash from the canonical chain.
{ 2205000, uint256("0x6bdd3c5e5a32e1dd9a70e705f1a28d1dd84929f89579bd2696d41bc87f39446f")},
{ 2206004, uint256("0xb34e8e6a7bb7f52167d81aaad4d26f87a876898fdd0fce860916fc1aaf9a2a46")},
};
// Published UTXO snapshot file SHA256, keyed by snapshot height.
// Each entry binds height -> SHA256 of the canonical snapshot file produced by
// UtxoSnapshot::DumpSnapshot at that height. Used by SnapshotNet to verify
// P2P-delivered snapshots without trusting any peer.
//
// Maintainers: after producing a snapshot, sha256 the file and add an entry
// here. The corresponding (height, blockhash) must already exist in
// mapCheckpoints / mapCheckpointsTestnet.
static std::map<int, uint256> mapSnapshotHashes = {
{ 2206004, uint256("0x1419282dae817315ee1b955543f6248233fe5800f5e8488734a0ece5bd6781ea")},
};
static std::map<int, uint256> mapSnapshotHashesTestnet = {
};
static MapCheckpoints mapCheckpointsTestnet = {
{ 0, hashGenesisBlockTestNet },
{ 2000, uint256("0x0000000000b5f20078bf46ebdf1500813bb6b2cb482065aa93b89e073b2c6467")},
{ 2101, uint256("0xd4ea1ac45b63c8162a7fc8033cec441db8d532ba988202849d7831e32fe2d059")},
{ 2847, uint256("0xb5015e2835f13fd3bb6135cff9b31ac33310c9b77d694bdb592b8680d98d018e")},
{ 3589, uint256("0xb12a2ca3db4e288cada98aa2139768532bd4474c49dd7b8158031032dac08d51")},
{ 3935, uint256("0xe16290c9757d1368b8d7c35de07d4f8f70c2c9f9c785b667df0c3bff85086ca6")},
{ 5703, uint256("0x587db07bb2172ad7db72c5fabc2518262a1b27f503f99417510b2c6fafa6557b")},
{ 9000, uint256("0x00000000019ef6b2f5e7c324c7d083ee94502305aabc7e9cd73a7fb2a57bb8db")},
{ 9001, uint256("0x6d5c6c5f201cc9e59659ee0da30d1430dc6bf3b12a8ff4c3864ab8d6286b0007")},
{ 9002, uint256("0xa1e20fb1d44688b763690cf74d6aefe859e4cc32981f9e3f2b2ae9702bbcf249")},
{ 10881, uint256("0x4b6554c45e1e6764a6f3c309c47baf53c9edd81f624e52b072518cd15da237e6")},
{ 17650, uint256("0x224940e1f986a202209b8e762728d1452ab45870c308abf84905674acf326a47")},
};
bool CheckHardened(int nHeight, const uint256& hash)
{
MapCheckpoints& checkpoints = (fTestNet ? mapCheckpointsTestnet : mapCheckpoints);
MapCheckpoints::const_iterator i = checkpoints.find(nHeight);
if (i == checkpoints.end()) return true;
return hash == i->second;
}
bool IsKnownCheckpoint(int nHeight, const uint256& hash)
{
MapCheckpoints& checkpoints = (fTestNet ? mapCheckpointsTestnet : mapCheckpoints);
MapCheckpoints::const_iterator i = checkpoints.find(nHeight);
if (i == checkpoints.end()) return false;
return hash == i->second;
}
int GetTotalBlocksEstimate()
{
MapCheckpoints& checkpoints = (fTestNet ? mapCheckpointsTestnet : mapCheckpoints);
return checkpoints.rbegin()->first;
}
int GetBestSnapshotHeight()
{
std::map<int, uint256>& snaps = (fTestNet ? mapSnapshotHashesTestnet : mapSnapshotHashes);
if (snaps.empty()) return 0;
return snaps.rbegin()->first;
}
bool GetSnapshotHash(int nHeight, uint256& fileHashOut)
{
std::map<int, uint256>& snaps = (fTestNet ? mapSnapshotHashesTestnet : mapSnapshotHashes);
auto it = snaps.find(nHeight);
if (it == snaps.end()) return false;
fileHashOut = it->second;
return true;
}
CBlockIndex* GetLastCheckpoint(const std::map<uint256, CBlockIndex*>& mapBlockIndex)
{
MapCheckpoints& checkpoints = (fTestNet ? mapCheckpointsTestnet : mapCheckpoints);
for (auto it = checkpoints.rbegin(); it != checkpoints.rend(); ++it)
{
const uint256& hash = it->second;
std::map<uint256, CBlockIndex*>::const_iterator t = mapBlockIndex.find(hash);
if (t != mapBlockIndex.end())
return t->second;
}
return nullptr;
}
// triangles: synchronized checkpoint (centrally broadcasted)
uint256 hashSyncCheckpoint = uint256("0x7e7a6e4dd5fe895106fca912dfbacaeaf2a89e76c6a588df8ff96e0e18b96021");
uint256 hashPendingCheckpoint = uint256("0x7e7a6e4dd5fe895106fca912dfbacaeaf2a89e76c6a588df8ff96e0e18b96021");
CSyncCheckpoint checkpointMessage;
CSyncCheckpoint checkpointMessagePending;
uint256 hashInvalidCheckpoint = 0;
CCriticalSection cs_hashSyncCheckpoint;
// triangles: get last synchronized checkpoint
CBlockIndex* GetLastSyncCheckpoint()
{
LOCK(cs_hashSyncCheckpoint);
if (!mapBlockIndex.count(hashSyncCheckpoint))
error("GetSyncCheckpoint: block index missing for current sync-checkpoint %s", hashSyncCheckpoint.ToString().c_str());
else
return mapBlockIndex[hashSyncCheckpoint];
return nullptr;
}
// triangles: only descendant of current sync-checkpoint is allowed
bool ValidateSyncCheckpoint(uint256 hashCheckpoint)
{
if (!mapBlockIndex.count(hashSyncCheckpoint))
return error("ValidateSyncCheckpoint: block index missing for current sync-checkpoint %s", hashSyncCheckpoint.ToString().c_str());
if (!mapBlockIndex.count(hashCheckpoint))
return error("ValidateSyncCheckpoint: block index missing for received sync-checkpoint %s", hashCheckpoint.ToString().c_str());
CBlockIndex* pindexSyncCheckpoint = mapBlockIndex[hashSyncCheckpoint];
CBlockIndex* pindexCheckpointRecv = mapBlockIndex[hashCheckpoint];
if (pindexCheckpointRecv->nHeight <= pindexSyncCheckpoint->nHeight)
{
// Received an older checkpoint, trace back from current checkpoint
// to the same height of the received checkpoint to verify
// that current checkpoint should be a descendant block
CBlockIndex* pindex = pindexSyncCheckpoint;
while (pindex->nHeight > pindexCheckpointRecv->nHeight)
if (!(pindex = pindex->pprev))
return error("ValidateSyncCheckpoint: pprev null - block index structure failure");
if (pindex->GetBlockHash() != hashCheckpoint)
{
hashInvalidCheckpoint = hashCheckpoint;
return error("ValidateSyncCheckpoint: new sync-checkpoint %s is conflicting with current sync-checkpoint %s", hashCheckpoint.ToString().c_str(), hashSyncCheckpoint.ToString().c_str());
}
return false; // ignore older checkpoint
}
// Received checkpoint should be a descendant block of the current
// checkpoint. Trace back to the same height of current checkpoint
// to verify.
CBlockIndex* pindex = pindexCheckpointRecv;
while (pindex->nHeight > pindexSyncCheckpoint->nHeight)
if (!(pindex = pindex->pprev))
return error("ValidateSyncCheckpoint: pprev2 null - block index structure failure");
if (pindex->GetBlockHash() != hashSyncCheckpoint)
{
hashInvalidCheckpoint = hashCheckpoint;
return error("ValidateSyncCheckpoint: new sync-checkpoint %s is not a descendant of current sync-checkpoint %s", hashCheckpoint.ToString().c_str(), hashSyncCheckpoint.ToString().c_str());
}
return true;
}
bool WriteSyncCheckpoint(const uint256& hashCheckpoint)
{
auto txdb_holder = MakeChainDB(); CTxDBBase& txdb = *txdb_holder;
txdb.TxnBegin();
if (!txdb.WriteSyncCheckpoint(hashCheckpoint))
{
txdb.TxnAbort();
return error("WriteSyncCheckpoint(): failed to write to db sync checkpoint %s", hashCheckpoint.ToString().c_str());
}
if (!txdb.TxnCommit())
return error("WriteSyncCheckpoint(): failed to commit to db sync checkpoint %s", hashCheckpoint.ToString().c_str());
Checkpoints::hashSyncCheckpoint = hashCheckpoint;
return true;
}
bool AcceptPendingSyncCheckpoint()
{
LOCK(cs_hashSyncCheckpoint);
if (hashPendingCheckpoint != 0 && mapBlockIndex.count(hashPendingCheckpoint))
{
if (!ValidateSyncCheckpoint(hashPendingCheckpoint))
{
hashPendingCheckpoint = 0;
checkpointMessagePending.SetNull();
return false;
}
auto txdb_holder = MakeChainDB(); CTxDBBase& txdb = *txdb_holder;
CBlockIndex* pindexCheckpoint = mapBlockIndex[hashPendingCheckpoint];
if (!pindexCheckpoint->IsInMainChain())
{
CBlock block;
if (!block.ReadFromDisk(pindexCheckpoint))
return error("AcceptPendingSyncCheckpoint: ReadFromDisk failed for sync checkpoint %s", hashPendingCheckpoint.ToString().c_str());
if (!block.SetBestChain(txdb, pindexCheckpoint))
{
hashInvalidCheckpoint = hashPendingCheckpoint;
return error("AcceptPendingSyncCheckpoint: SetBestChain failed for sync checkpoint %s", hashPendingCheckpoint.ToString().c_str());
}
}
if (!WriteSyncCheckpoint(hashPendingCheckpoint))
return error("AcceptPendingSyncCheckpoint(): failed to write sync checkpoint %s", hashPendingCheckpoint.ToString().c_str());
hashPendingCheckpoint = 0;
checkpointMessage = checkpointMessagePending;
checkpointMessagePending.SetNull();
printf("AcceptPendingSyncCheckpoint : sync-checkpoint at %s\n", hashSyncCheckpoint.ToString().c_str());
// relay the checkpoint
if (!checkpointMessage.IsNull())
{
for (CNode* pnode : vNodes)
checkpointMessage.RelayTo(pnode);
}
return true;
}
return false;
}
// Automatically select a suitable sync-checkpoint
uint256 AutoSelectSyncCheckpoint()
{
const CBlockIndex *pindex = pindexBest;
// Search backward for a block within max span and maturity window
while (pindex->pprev && (pindex->GetBlockTime() + CHECKPOINT_MAX_SPAN > pindexBest->GetBlockTime() || pindex->nHeight + 8 > pindexBest->nHeight))
pindex = pindex->pprev;
return pindex->GetBlockHash();
}
// Check against synchronized checkpoint
// Disabled: master key removed in V5, no new sync checkpoints possible.
// Always returns true to prevent stale DB-persisted checkpoints from blocking IBD.
bool CheckSync(const uint256& hashBlock, const CBlockIndex* pindexPrev)
{
return true;
}
bool WantedByPendingSyncCheckpoint(uint256 hashBlock)
{
LOCK(cs_hashSyncCheckpoint);
if (hashPendingCheckpoint == 0)
return false;
if (hashBlock == hashPendingCheckpoint)
return true;
if (mapOrphanBlocks.count(hashPendingCheckpoint)
&& hashBlock == WantedByOrphan(mapOrphanBlocks[hashPendingCheckpoint].get()))
return true;
return false;
}
// triangles: reset synchronized checkpoint to last hardened checkpoint
bool ResetSyncCheckpoint()
{
LOCK(cs_hashSyncCheckpoint);
const uint256& hash = mapCheckpoints.rbegin()->second;
if (mapBlockIndex.count(hash) && !mapBlockIndex[hash]->IsInMainChain())
{
// checkpoint block accepted but not yet in main chain
printf("ResetSyncCheckpoint: SetBestChain to hardened checkpoint %s\n", hash.ToString().c_str());
auto txdb_holder = MakeChainDB(); CTxDBBase& txdb = *txdb_holder;
CBlock block;
if (!block.ReadFromDisk(mapBlockIndex[hash]))
return error("ResetSyncCheckpoint: ReadFromDisk failed for hardened checkpoint %s", hash.ToString().c_str());
if (!block.SetBestChain(txdb, mapBlockIndex[hash]))
{
return error("ResetSyncCheckpoint: SetBestChain failed for hardened checkpoint %s", hash.ToString().c_str());
}
}
else if(!mapBlockIndex.count(hash))
{
// checkpoint block not yet accepted
hashPendingCheckpoint = hash;
checkpointMessagePending.SetNull();
printf("ResetSyncCheckpoint: pending for sync-checkpoint %s\n", hashPendingCheckpoint.ToString().c_str());
}
for (auto it = mapCheckpoints.rbegin(); it != mapCheckpoints.rend(); ++it)
{
const uint256& hash = it->second;
if (mapBlockIndex.count(hash) && mapBlockIndex[hash]->IsInMainChain())
{
if (!WriteSyncCheckpoint(hash))
return error("ResetSyncCheckpoint: failed to write sync checkpoint %s", hash.ToString().c_str());
printf("ResetSyncCheckpoint: sync-checkpoint reset to %s\n", hashSyncCheckpoint.ToString().c_str());
return true;
}
}
return false;
}
void AskForPendingSyncCheckpoint(CNode* pfrom)
{
LOCK(cs_hashSyncCheckpoint);
if (pfrom && hashPendingCheckpoint != 0 && (!mapBlockIndex.count(hashPendingCheckpoint)) && (!mapOrphanBlocks.count(hashPendingCheckpoint)))
pfrom->AskFor(CInv(MSG_BLOCK, hashPendingCheckpoint));
}
bool SetCheckpointPrivKey(std::string strPrivKey)
{
// Test signing a sync-checkpoint with genesis block
CSyncCheckpoint checkpoint;
checkpoint.hashCheckpoint = !fTestNet ? hashGenesisBlockOfficial : hashGenesisBlockTestNet;
CDataStream sMsg(SER_NETWORK, PROTOCOL_VERSION);
sMsg << (CUnsignedSyncCheckpoint)checkpoint;
checkpoint.vchMsg = std::vector<unsigned char>(sMsg.begin(), sMsg.end());
std::vector<unsigned char> vchPrivKey = ParseHex(strPrivKey);
CKey key;
key.SetPrivKey(CPrivKey(vchPrivKey.begin(), vchPrivKey.end())); // if key is not correct openssl may crash
if (!key.Sign(Hash(checkpoint.vchMsg.begin(), checkpoint.vchMsg.end()), checkpoint.vchSig))
return false;
// Test signing successful, proceed
CSyncCheckpoint::strMasterPrivKey = strPrivKey;
return true;
}
bool SendSyncCheckpoint(uint256 hashCheckpoint)
{
CSyncCheckpoint checkpoint;
checkpoint.hashCheckpoint = hashCheckpoint;
CDataStream sMsg(SER_NETWORK, PROTOCOL_VERSION);
sMsg << (CUnsignedSyncCheckpoint)checkpoint;
checkpoint.vchMsg = std::vector<unsigned char>(sMsg.begin(), sMsg.end());
if (CSyncCheckpoint::strMasterPrivKey.empty())
return error("SendSyncCheckpoint: Checkpoint master key unavailable.");
std::vector<unsigned char> vchPrivKey = ParseHex(CSyncCheckpoint::strMasterPrivKey);
CKey key;
key.SetPrivKey(CPrivKey(vchPrivKey.begin(), vchPrivKey.end())); // if key is not correct openssl may crash
if (!key.Sign(Hash(checkpoint.vchMsg.begin(), checkpoint.vchMsg.end()), checkpoint.vchSig))
return error("SendSyncCheckpoint: Unable to sign checkpoint, check private key?");
if(!checkpoint.ProcessSyncCheckpoint(nullptr))
{
printf("WARNING: SendSyncCheckpoint: Failed to process checkpoint.\n");
return false;
}
// Relay checkpoint
{
LOCK(cs_vNodes);
for (CNode* pnode : vNodes)
checkpoint.RelayTo(pnode);
}
return true;
}
// Is the sync-checkpoint outside maturity window?
bool IsMatureSyncCheckpoint()
{
LOCK(cs_hashSyncCheckpoint);
if (!mapBlockIndex.count(hashSyncCheckpoint))
return true; // no valid sync checkpoint, treat as mature
const CBlockIndex* pindexSync = mapBlockIndex[hashSyncCheckpoint];
return (nBestHeight >= pindexSync->nHeight + nCoinbaseMaturity ||
pindexSync->GetBlockTime() + nStakeMinAge < GetAdjustedTime());
}
}
// triangles: sync-checkpoint master key (DISABLED for decentralization - v5 hard fork)
const std::string CSyncCheckpoint::strMasterPubKey = "";
std::string CSyncCheckpoint::strMasterPrivKey = "";
// triangles: verify signature of sync-checkpoint message
// Master key system disabled - checkpoint signatures are no longer required
bool CSyncCheckpoint::CheckSignature()
{
// Deserialize the checkpoint data without signature verification
CDataStream sMsg(vchMsg, SER_NETWORK, PROTOCOL_VERSION);
sMsg >> *(CUnsignedSyncCheckpoint*)this;
return true;
}
// triangles: process synchronized checkpoint
bool CSyncCheckpoint::ProcessSyncCheckpoint(CNode* pfrom)
{
if (!CheckSignature())
return false;
LOCK(Checkpoints::cs_hashSyncCheckpoint);
if (!mapBlockIndex.count(hashCheckpoint))
{
// We haven't received the checkpoint chain, keep the checkpoint as pending
Checkpoints::hashPendingCheckpoint = hashCheckpoint;
Checkpoints::checkpointMessagePending = *this;
printf("ProcessSyncCheckpoint: pending for sync-checkpoint %s\n", hashCheckpoint.ToString().c_str());
// Ask this guy to fill in what we're missing
if (pfrom)
{
pfrom->PushGetBlocks(pindexBest, hashCheckpoint);
// ask directly as well in case rejected earlier by duplicate
// proof-of-stake because getblocks may not get it this time
pfrom->AskFor(CInv(MSG_BLOCK, mapOrphanBlocks.count(hashCheckpoint)? WantedByOrphan(mapOrphanBlocks[hashCheckpoint].get()) : hashCheckpoint));
}
return false;
}
if (!Checkpoints::ValidateSyncCheckpoint(hashCheckpoint))
return false;
auto txdb_holder = MakeChainDB(); CTxDBBase& txdb = *txdb_holder;
CBlockIndex* pindexCheckpoint = mapBlockIndex[hashCheckpoint];
if (!pindexCheckpoint->IsInMainChain())
{
// checkpoint chain received but not yet main chain
CBlock block;
if (!block.ReadFromDisk(pindexCheckpoint))
return error("ProcessSyncCheckpoint: ReadFromDisk failed for sync checkpoint %s", hashCheckpoint.ToString().c_str());
if (!block.SetBestChain(txdb, pindexCheckpoint))
{
Checkpoints::hashInvalidCheckpoint = hashCheckpoint;
return error("ProcessSyncCheckpoint: SetBestChain failed for sync checkpoint %s", hashCheckpoint.ToString().c_str());
}
}
if (!Checkpoints::WriteSyncCheckpoint(hashCheckpoint))
return error("ProcessSyncCheckpoint(): failed to write sync checkpoint %s", hashCheckpoint.ToString().c_str());
Checkpoints::checkpointMessage = *this;
Checkpoints::hashPendingCheckpoint = 0;
Checkpoints::checkpointMessagePending.SetNull();
printf("ProcessSyncCheckpoint: sync-checkpoint at %s\n", hashCheckpoint.ToString().c_str());
return true;
}
// Copyright (c) 2009-2012 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 "checkpoints.h"
#include "txdb.h"
#include "main.h"
#include "uint256.h"
namespace Checkpoints
{
typedef std::map<int, uint256> MapCheckpoints;
//
// What makes a good checkpoint block?
// + Is surrounded by blocks with reasonable timestamps
// (no blocks before with a timestamp after, none after with
// timestamp before)
// + Contains no strange transactions
//
static MapCheckpoints mapCheckpoints = {
{ 0, hashGenesisBlockOfficial },
{ 2000, uint256("0x0000000000b5f20078bf46ebdf1500813bb6b2cb482065aa93b89e073b2c6467")},
{ 2101, uint256("0xd4ea1ac45b63c8162a7fc8033cec441db8d532ba988202849d7831e32fe2d059")},
{ 2847, uint256("0xb5015e2835f13fd3bb6135cff9b31ac33310c9b77d694bdb592b8680d98d018e")},
{ 3589, uint256("0xb12a2ca3db4e288cada98aa2139768532bd4474c49dd7b8158031032dac08d51")},
{ 3935, uint256("0xe16290c9757d1368b8d7c35de07d4f8f70c2c9f9c785b667df0c3bff85086ca6")},
{ 5703, uint256("0x587db07bb2172ad7db72c5fabc2518262a1b27f503f99417510b2c6fafa6557b")},
{ 9000, uint256("0x00000000019ef6b2f5e7c324c7d083ee94502305aabc7e9cd73a7fb2a57bb8db")},
{ 9001, uint256("0x6d5c6c5f201cc9e59659ee0da30d1430dc6bf3b12a8ff4c3864ab8d6286b0007")},
{ 9002, uint256("0xa1e20fb1d44688b763690cf74d6aefe859e4cc32981f9e3f2b2ae9702bbcf249")},
{ 10881, uint256("0x4b6554c45e1e6764a6f3c309c47baf53c9edd81f624e52b072518cd15da237e6")},
{ 17650, uint256("0x224940e1f986a202209b8e762728d1452ab45870c308abf84905674acf326a47")},
// Recent finality pin (PoS era). Closes the long unchecked span from
// 17650 to the live tip so stale-bootstrap / low-trust forks below
// this height are rejected outright. Hash from the canonical chain.
{ 2205000, uint256("0x6bdd3c5e5a32e1dd9a70e705f1a28d1dd84929f89579bd2696d41bc87f39446f")},
{ 2206004, uint256("0xb34e8e6a7bb7f52167d81aaad4d26f87a876898fdd0fce860916fc1aaf9a2a46")},
};
// Published UTXO snapshot file SHA256, keyed by snapshot height.
// Each entry binds height -> SHA256 of the canonical snapshot file produced by
// UtxoSnapshot::DumpSnapshot at that height. Used by SnapshotNet to verify
// P2P-delivered snapshots without trusting any peer.
//
// Maintainers: after producing a snapshot, sha256 the file and add an entry
// here. The corresponding (height, blockhash) must already exist in
// mapCheckpoints / mapCheckpointsTestnet.
static std::map<int, uint256> mapSnapshotHashes = {
{ 2206004, uint256("0x1419282dae817315ee1b955543f6248233fe5800f5e8488734a0ece5bd6781ea")},
};
static std::map<int, uint256> mapSnapshotHashesTestnet = {
};
static MapCheckpoints mapCheckpointsTestnet = {
{ 0, hashGenesisBlockTestNet },
{ 2000, uint256("0x0000000000b5f20078bf46ebdf1500813bb6b2cb482065aa93b89e073b2c6467")},
{ 2101, uint256("0xd4ea1ac45b63c8162a7fc8033cec441db8d532ba988202849d7831e32fe2d059")},
{ 2847, uint256("0xb5015e2835f13fd3bb6135cff9b31ac33310c9b77d694bdb592b8680d98d018e")},
{ 3589, uint256("0xb12a2ca3db4e288cada98aa2139768532bd4474c49dd7b8158031032dac08d51")},
{ 3935, uint256("0xe16290c9757d1368b8d7c35de07d4f8f70c2c9f9c785b667df0c3bff85086ca6")},
{ 5703, uint256("0x587db07bb2172ad7db72c5fabc2518262a1b27f503f99417510b2c6fafa6557b")},
{ 9000, uint256("0x00000000019ef6b2f5e7c324c7d083ee94502305aabc7e9cd73a7fb2a57bb8db")},
{ 9001, uint256("0x6d5c6c5f201cc9e59659ee0da30d1430dc6bf3b12a8ff4c3864ab8d6286b0007")},
{ 9002, uint256("0xa1e20fb1d44688b763690cf74d6aefe859e4cc32981f9e3f2b2ae9702bbcf249")},
{ 10881, uint256("0x4b6554c45e1e6764a6f3c309c47baf53c9edd81f624e52b072518cd15da237e6")},
{ 17650, uint256("0x224940e1f986a202209b8e762728d1452ab45870c308abf84905674acf326a47")},
};
bool CheckHardened(int nHeight, const uint256& hash)
{
MapCheckpoints& checkpoints = (fTestNet ? mapCheckpointsTestnet : mapCheckpoints);
MapCheckpoints::const_iterator i = checkpoints.find(nHeight);
if (i == checkpoints.end()) return true;
return hash == i->second;
}
bool IsKnownCheckpoint(int nHeight, const uint256& hash)
{
MapCheckpoints& checkpoints = (fTestNet ? mapCheckpointsTestnet : mapCheckpoints);
MapCheckpoints::const_iterator i = checkpoints.find(nHeight);
if (i == checkpoints.end()) return false;
return hash == i->second;
}
int GetTotalBlocksEstimate()
{
MapCheckpoints& checkpoints = (fTestNet ? mapCheckpointsTestnet : mapCheckpoints);
return checkpoints.rbegin()->first;
}
int GetBestSnapshotHeight()
{
std::map<int, uint256>& snaps = (fTestNet ? mapSnapshotHashesTestnet : mapSnapshotHashes);
if (snaps.empty()) return 0;
return snaps.rbegin()->first;
}
bool GetSnapshotHash(int nHeight, uint256& fileHashOut)
{
std::map<int, uint256>& snaps = (fTestNet ? mapSnapshotHashesTestnet : mapSnapshotHashes);
auto it = snaps.find(nHeight);
if (it == snaps.end()) return false;
fileHashOut = it->second;
return true;
}
CBlockIndex* GetLastCheckpoint(const std::map<uint256, CBlockIndex*>& mapBlockIndex)
{
MapCheckpoints& checkpoints = (fTestNet ? mapCheckpointsTestnet : mapCheckpoints);
for (auto it = checkpoints.rbegin(); it != checkpoints.rend(); ++it)
{
const uint256& hash = it->second;
std::map<uint256, CBlockIndex*>::const_iterator t = mapBlockIndex.find(hash);
if (t != mapBlockIndex.end())
return t->second;
}
return nullptr;
}
// triangles: synchronized checkpoint (centrally broadcasted)
uint256 hashSyncCheckpoint = uint256("0x7e7a6e4dd5fe895106fca912dfbacaeaf2a89e76c6a588df8ff96e0e18b96021");
uint256 hashPendingCheckpoint = uint256("0x7e7a6e4dd5fe895106fca912dfbacaeaf2a89e76c6a588df8ff96e0e18b96021");
CSyncCheckpoint checkpointMessage;
CSyncCheckpoint checkpointMessagePending;
uint256 hashInvalidCheckpoint = 0;
CCriticalSection cs_hashSyncCheckpoint;
// triangles: get last synchronized checkpoint
CBlockIndex* GetLastSyncCheckpoint()
{
LOCK(cs_hashSyncCheckpoint);
if (!mapBlockIndex.count(hashSyncCheckpoint))
error("GetSyncCheckpoint: block index missing for current sync-checkpoint %s", hashSyncCheckpoint.ToString().c_str());
else
return mapBlockIndex[hashSyncCheckpoint];
return nullptr;
}
// triangles: only descendant of current sync-checkpoint is allowed
bool ValidateSyncCheckpoint(uint256 hashCheckpoint)
{
if (!mapBlockIndex.count(hashSyncCheckpoint))
return error("ValidateSyncCheckpoint: block index missing for current sync-checkpoint %s", hashSyncCheckpoint.ToString().c_str());
if (!mapBlockIndex.count(hashCheckpoint))
return error("ValidateSyncCheckpoint: block index missing for received sync-checkpoint %s", hashCheckpoint.ToString().c_str());
CBlockIndex* pindexSyncCheckpoint = mapBlockIndex[hashSyncCheckpoint];
CBlockIndex* pindexCheckpointRecv = mapBlockIndex[hashCheckpoint];
if (pindexCheckpointRecv->nHeight <= pindexSyncCheckpoint->nHeight)
{
// Received an older checkpoint, trace back from current checkpoint
// to the same height of the received checkpoint to verify
// that current checkpoint should be a descendant block
CBlockIndex* pindex = pindexSyncCheckpoint;
while (pindex->nHeight > pindexCheckpointRecv->nHeight)
if (!(pindex = pindex->pprev))
return error("ValidateSyncCheckpoint: pprev null - block index structure failure");
if (pindex->GetBlockHash() != hashCheckpoint)
{
hashInvalidCheckpoint = hashCheckpoint;
return error("ValidateSyncCheckpoint: new sync-checkpoint %s is conflicting with current sync-checkpoint %s", hashCheckpoint.ToString().c_str(), hashSyncCheckpoint.ToString().c_str());
}
return false; // ignore older checkpoint
}
// Received checkpoint should be a descendant block of the current
// checkpoint. Trace back to the same height of current checkpoint
// to verify.
CBlockIndex* pindex = pindexCheckpointRecv;
while (pindex->nHeight > pindexSyncCheckpoint->nHeight)
if (!(pindex = pindex->pprev))
return error("ValidateSyncCheckpoint: pprev2 null - block index structure failure");
if (pindex->GetBlockHash() != hashSyncCheckpoint)
{
hashInvalidCheckpoint = hashCheckpoint;
return error("ValidateSyncCheckpoint: new sync-checkpoint %s is not a descendant of current sync-checkpoint %s", hashCheckpoint.ToString().c_str(), hashSyncCheckpoint.ToString().c_str());
}
return true;
}
bool WriteSyncCheckpoint(const uint256& hashCheckpoint)
{
auto txdb_holder = MakeChainDB(); CTxDBBase& txdb = *txdb_holder;
txdb.TxnBegin();
if (!txdb.WriteSyncCheckpoint(hashCheckpoint))
{
txdb.TxnAbort();
return error("WriteSyncCheckpoint(): failed to write to db sync checkpoint %s", hashCheckpoint.ToString().c_str());
}
if (!txdb.TxnCommit())
return error("WriteSyncCheckpoint(): failed to commit to db sync checkpoint %s", hashCheckpoint.ToString().c_str());
Checkpoints::hashSyncCheckpoint = hashCheckpoint;
return true;
}
bool AcceptPendingSyncCheckpoint()
{
LOCK(cs_hashSyncCheckpoint);
if (hashPendingCheckpoint != 0 && mapBlockIndex.count(hashPendingCheckpoint))
{
if (!ValidateSyncCheckpoint(hashPendingCheckpoint))
{
hashPendingCheckpoint = 0;
checkpointMessagePending.SetNull();
return false;
}
auto txdb_holder = MakeChainDB(); CTxDBBase& txdb = *txdb_holder;
CBlockIndex* pindexCheckpoint = mapBlockIndex[hashPendingCheckpoint];
if (!pindexCheckpoint->IsInMainChain())
{
CBlock block;
if (!block.ReadFromDisk(pindexCheckpoint))
return error("AcceptPendingSyncCheckpoint: ReadFromDisk failed for sync checkpoint %s", hashPendingCheckpoint.ToString().c_str());
if (!block.SetBestChain(txdb, pindexCheckpoint))
{
hashInvalidCheckpoint = hashPendingCheckpoint;
return error("AcceptPendingSyncCheckpoint: SetBestChain failed for sync checkpoint %s", hashPendingCheckpoint.ToString().c_str());
}
}
if (!WriteSyncCheckpoint(hashPendingCheckpoint))
return error("AcceptPendingSyncCheckpoint(): failed to write sync checkpoint %s", hashPendingCheckpoint.ToString().c_str());
hashPendingCheckpoint = 0;
checkpointMessage = checkpointMessagePending;
checkpointMessagePending.SetNull();
printf("AcceptPendingSyncCheckpoint : sync-checkpoint at %s\n", hashSyncCheckpoint.ToString().c_str());
// relay the checkpoint
if (!checkpointMessage.IsNull())
{
for (CNode* pnode : vNodes)
checkpointMessage.RelayTo(pnode);
}
return true;
}
return false;
}
// Automatically select a suitable sync-checkpoint
uint256 AutoSelectSyncCheckpoint()
{
const CBlockIndex *pindex = pindexBest;
// Search backward for a block within max span and maturity window
while (pindex->pprev && (pindex->GetBlockTime() + CHECKPOINT_MAX_SPAN > pindexBest->GetBlockTime() || pindex->nHeight + 8 > pindexBest->nHeight))
pindex = pindex->pprev;
return pindex->GetBlockHash();
}
// Check against synchronized checkpoint
// Disabled: master key removed in V5, no new sync checkpoints possible.
// Always returns true to prevent stale DB-persisted checkpoints from blocking IBD.
bool CheckSync(const uint256& hashBlock, const CBlockIndex* pindexPrev)
{
return true;
}
bool WantedByPendingSyncCheckpoint(uint256 hashBlock)
{
LOCK(cs_hashSyncCheckpoint);
if (hashPendingCheckpoint == 0)
return false;
if (hashBlock == hashPendingCheckpoint)
return true;
if (mapOrphanBlocks.count(hashPendingCheckpoint)
&& hashBlock == WantedByOrphan(mapOrphanBlocks[hashPendingCheckpoint].get()))
return true;
return false;
}
// triangles: reset synchronized checkpoint to last hardened checkpoint
bool ResetSyncCheckpoint()
{
LOCK(cs_hashSyncCheckpoint);
const uint256& hash = mapCheckpoints.rbegin()->second;
if (mapBlockIndex.count(hash) && !mapBlockIndex[hash]->IsInMainChain())
{
// checkpoint block accepted but not yet in main chain
printf("ResetSyncCheckpoint: SetBestChain to hardened checkpoint %s\n", hash.ToString().c_str());
auto txdb_holder = MakeChainDB(); CTxDBBase& txdb = *txdb_holder;
CBlock block;
if (!block.ReadFromDisk(mapBlockIndex[hash]))
return error("ResetSyncCheckpoint: ReadFromDisk failed for hardened checkpoint %s", hash.ToString().c_str());
if (!block.SetBestChain(txdb, mapBlockIndex[hash]))
{
return error("ResetSyncCheckpoint: SetBestChain failed for hardened checkpoint %s", hash.ToString().c_str());
}
}
else if(!mapBlockIndex.count(hash))
{
// checkpoint block not yet accepted
hashPendingCheckpoint = hash;
checkpointMessagePending.SetNull();
printf("ResetSyncCheckpoint: pending for sync-checkpoint %s\n", hashPendingCheckpoint.ToString().c_str());
}
for (auto it = mapCheckpoints.rbegin(); it != mapCheckpoints.rend(); ++it)
{
const uint256& hash = it->second;
if (mapBlockIndex.count(hash) && mapBlockIndex[hash]->IsInMainChain())
{
if (!WriteSyncCheckpoint(hash))
return error("ResetSyncCheckpoint: failed to write sync checkpoint %s", hash.ToString().c_str());
printf("ResetSyncCheckpoint: sync-checkpoint reset to %s\n", hashSyncCheckpoint.ToString().c_str());
return true;
}
}
return false;
}
void AskForPendingSyncCheckpoint(CNode* pfrom)
{
LOCK(cs_hashSyncCheckpoint);
if (pfrom && hashPendingCheckpoint != 0 && (!mapBlockIndex.count(hashPendingCheckpoint)) && (!mapOrphanBlocks.count(hashPendingCheckpoint)))
pfrom->AskFor(CInv(MSG_BLOCK, hashPendingCheckpoint));
}
bool SetCheckpointPrivKey(std::string strPrivKey)
{
// Test signing a sync-checkpoint with genesis block
CSyncCheckpoint checkpoint;
checkpoint.hashCheckpoint = !fTestNet ? hashGenesisBlockOfficial : hashGenesisBlockTestNet;
CDataStream sMsg(SER_NETWORK, PROTOCOL_VERSION);
sMsg << (CUnsignedSyncCheckpoint)checkpoint;
checkpoint.vchMsg = std::vector<unsigned char>(sMsg.begin(), sMsg.end());
std::vector<unsigned char> vchPrivKey = ParseHex(strPrivKey);
CKey key;
key.SetPrivKey(CPrivKey(vchPrivKey.begin(), vchPrivKey.end())); // if key is not correct openssl may crash
if (!key.Sign(Hash(checkpoint.vchMsg.begin(), checkpoint.vchMsg.end()), checkpoint.vchSig))
return false;
// Test signing successful, proceed
CSyncCheckpoint::strMasterPrivKey = strPrivKey;
return true;
}
bool SendSyncCheckpoint(uint256 hashCheckpoint)
{
CSyncCheckpoint checkpoint;
checkpoint.hashCheckpoint = hashCheckpoint;
CDataStream sMsg(SER_NETWORK, PROTOCOL_VERSION);
sMsg << (CUnsignedSyncCheckpoint)checkpoint;
checkpoint.vchMsg = std::vector<unsigned char>(sMsg.begin(), sMsg.end());
if (CSyncCheckpoint::strMasterPrivKey.empty())
return error("SendSyncCheckpoint: Checkpoint master key unavailable.");
std::vector<unsigned char> vchPrivKey = ParseHex(CSyncCheckpoint::strMasterPrivKey);
CKey key;
key.SetPrivKey(CPrivKey(vchPrivKey.begin(), vchPrivKey.end())); // if key is not correct openssl may crash
if (!key.Sign(Hash(checkpoint.vchMsg.begin(), checkpoint.vchMsg.end()), checkpoint.vchSig))
return error("SendSyncCheckpoint: Unable to sign checkpoint, check private key?");
if(!checkpoint.ProcessSyncCheckpoint(nullptr))
{
printf("WARNING: SendSyncCheckpoint: Failed to process checkpoint.\n");
return false;
}
// Relay checkpoint
{
LOCK(cs_vNodes);
for (CNode* pnode : vNodes)
checkpoint.RelayTo(pnode);
}
return true;
}
// Is the sync-checkpoint outside maturity window?
bool IsMatureSyncCheckpoint()
{
LOCK(cs_hashSyncCheckpoint);
if (!mapBlockIndex.count(hashSyncCheckpoint))
return true; // no valid sync checkpoint, treat as mature
const CBlockIndex* pindexSync = mapBlockIndex[hashSyncCheckpoint];
return (nBestHeight >= pindexSync->nHeight + nCoinbaseMaturity ||
pindexSync->GetBlockTime() + nStakeMinAge < GetAdjustedTime());
}
}
// triangles: sync-checkpoint master key (DISABLED for decentralization - v5 hard fork)
const std::string CSyncCheckpoint::strMasterPubKey = "";
std::string CSyncCheckpoint::strMasterPrivKey = "";
// triangles: verify signature of sync-checkpoint message
// Master key system disabled - checkpoint signatures are no longer required
bool CSyncCheckpoint::CheckSignature()
{
// Deserialize the checkpoint data without signature verification
CDataStream sMsg(vchMsg, SER_NETWORK, PROTOCOL_VERSION);
sMsg >> *(CUnsignedSyncCheckpoint*)this;
return true;
}
// triangles: process synchronized checkpoint
bool CSyncCheckpoint::ProcessSyncCheckpoint(CNode* pfrom)
{
if (!CheckSignature())
return false;
LOCK(Checkpoints::cs_hashSyncCheckpoint);
if (!mapBlockIndex.count(hashCheckpoint))
{
// We haven't received the checkpoint chain, keep the checkpoint as pending
Checkpoints::hashPendingCheckpoint = hashCheckpoint;
Checkpoints::checkpointMessagePending = *this;
printf("ProcessSyncCheckpoint: pending for sync-checkpoint %s\n", hashCheckpoint.ToString().c_str());
// Ask this guy to fill in what we're missing
if (pfrom)
{
pfrom->PushGetBlocks(pindexBest, hashCheckpoint);
// ask directly as well in case rejected earlier by duplicate
// proof-of-stake because getblocks may not get it this time
pfrom->AskFor(CInv(MSG_BLOCK, mapOrphanBlocks.count(hashCheckpoint)? WantedByOrphan(mapOrphanBlocks[hashCheckpoint].get()) : hashCheckpoint));
}
return false;
}
if (!Checkpoints::ValidateSyncCheckpoint(hashCheckpoint))
return false;
auto txdb_holder = MakeChainDB(); CTxDBBase& txdb = *txdb_holder;
CBlockIndex* pindexCheckpoint = mapBlockIndex[hashCheckpoint];
if (!pindexCheckpoint->IsInMainChain())
{
// checkpoint chain received but not yet main chain
CBlock block;
if (!block.ReadFromDisk(pindexCheckpoint))
return error("ProcessSyncCheckpoint: ReadFromDisk failed for sync checkpoint %s", hashCheckpoint.ToString().c_str());
if (!block.SetBestChain(txdb, pindexCheckpoint))
{
Checkpoints::hashInvalidCheckpoint = hashCheckpoint;
return error("ProcessSyncCheckpoint: SetBestChain failed for sync checkpoint %s", hashCheckpoint.ToString().c_str());
}
}
if (!Checkpoints::WriteSyncCheckpoint(hashCheckpoint))
return error("ProcessSyncCheckpoint(): failed to write sync checkpoint %s", hashCheckpoint.ToString().c_str());
Checkpoints::checkpointMessage = *this;
Checkpoints::hashPendingCheckpoint = 0;
Checkpoints::checkpointMessagePending.SetNull();
printf("ProcessSyncCheckpoint: sync-checkpoint at %s\n", hashCheckpoint.ToString().c_str());
return true;
}
+19 -19
View File
@@ -1,19 +1,19 @@
#ifndef CLIENTVERSION_H
#define CLIENTVERSION_H
//
// client versioning
//
// These need to be macros, as version.cpp's and triangles-qt.rc's voodoo requires it
#define CLIENT_VERSION_MAJOR 6
#define CLIENT_VERSION_MINOR 0
#define CLIENT_VERSION_REVISION 0
#define CLIENT_VERSION_BUILD 0
// Converts the parameter X to a string after macro replacement on X has been performed.
// Don't merge these into one macro!
#define STRINGIZE(X) DO_STRINGIZE(X)
#define DO_STRINGIZE(X) #X
#endif // CLIENTVERSION_H
#ifndef CLIENTVERSION_H
#define CLIENTVERSION_H
//
// client versioning
//
// These need to be macros, as version.cpp's and triangles-qt.rc's voodoo requires it
#define CLIENT_VERSION_MAJOR 6
#define CLIENT_VERSION_MINOR 0
#define CLIENT_VERSION_REVISION 0
#define CLIENT_VERSION_BUILD 0
// Converts the parameter X to a string after macro replacement on X has been performed.
// Don't merge these into one macro!
#define STRINGIZE(X) DO_STRINGIZE(X)
#define DO_STRINGIZE(X) #X
#endif // CLIENTVERSION_H
+466
View File
@@ -0,0 +1,466 @@
// Copyright (c) 2024 Triangles developers
// I2P (SAM v3) transport support
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "i2p.h"
#include "util.h"
#include "netbase.h"
#include "protocol.h" // CAddress
#include "net.h" // AddI2PInboundNode(), GetListenPort()
#include <openssl/sha.h>
#include <cstdio>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <sstream>
namespace fs = std::filesystem;
#ifdef WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <unistd.h>
#ifndef closesocket
#define closesocket close
#endif
#endif
// I2P uses a base64 variant where '+' -> '-' and '/' -> '~'.
static const char* pI2PBase64 =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-~";
static std::vector<unsigned char> DecodeI2PBase64(const std::string& str)
{
int table[256];
for (int i = 0; i < 256; i++) table[i] = -1;
for (int i = 0; i < 64; i++) table[(unsigned char)pI2PBase64[i]] = i;
std::vector<unsigned char> out;
int bits = 0; uint32_t buf = 0;
for (char c : str) {
if (c == '=' || c == '\r' || c == '\n') continue;
int v = table[(unsigned char)c];
if (v < 0) continue; // skip anything unexpected
buf = (buf << 6) | v;
bits += 6;
if (bits >= 8) {
bits -= 8;
out.push_back((unsigned char)((buf >> bits) & 0xFF));
}
}
return out;
}
CI2PSession* CI2PSession::GetInstance()
{
static CI2PSession instance;
return &instance;
}
CI2PSession::CI2PSession()
: samHost(I2P_DEFAULT_SAM_HOST), samPort(I2P_DEFAULT_SAM_PORT),
hSession(INVALID_SOCKET), fEnabled(false), fActive(false), fShutdown(false)
{
}
CI2PSession::~CI2PSession()
{
Stop();
}
std::string CI2PSession::GetB32Address()
{
std::lock_guard<std::mutex> lock(cs);
return b32Address;
}
// --- low level SAM helpers -------------------------------------------------
bool CI2PSession::SamConnect(SOCKET& hSocketRet)
{
SOCKET hSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (hSocket == INVALID_SOCKET)
return false;
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons((unsigned short)samPort);
addr.sin_addr.s_addr = inet_addr(samHost.c_str());
if (connect(hSocket, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) {
closesocket(hSocket);
return false;
}
hSocketRet = hSocket;
return true;
}
bool CI2PSession::SamSendLine(SOCKET hSocket, const std::string& strLine)
{
std::string out = strLine + "\n";
const char* p = out.c_str();
size_t left = out.size();
while (left > 0) {
int n = send(hSocket, p, (int)left, MSG_NOSIGNAL);
if (n <= 0)
return false;
p += n;
left -= n;
}
return true;
}
bool CI2PSession::SamRecvLine(SOCKET hSocket, std::string& strLineRet)
{
strLineRet.clear();
char c;
// SAM replies are newline terminated; read one byte at a time so we stop
// exactly at the boundary and leave any following stream data untouched.
for (int i = 0; i < 16384; i++) {
int n = recv(hSocket, &c, 1, 0);
if (n <= 0)
return false;
if (c == '\n')
return true;
if (c != '\r')
strLineRet += c;
}
return false;
}
std::string CI2PSession::SamGetValue(const std::string& strReply, const std::string& strKey)
{
// Tokens are space separated KEY=VALUE pairs. VALUE runs to the next space.
std::string needle = strKey + "=";
size_t pos = strReply.find(needle);
if (pos == std::string::npos)
return "";
pos += needle.size();
size_t end = strReply.find(' ', pos);
if (end == std::string::npos)
end = strReply.size();
return strReply.substr(pos, end - pos);
}
bool CI2PSession::SamHandshake(SOCKET hSocket)
{
if (!SamSendLine(hSocket, "HELLO VERSION MIN=3.1 MAX=3.3"))
return false;
std::string reply;
if (!SamRecvLine(hSocket, reply))
return false;
if (SamGetValue(reply, "RESULT") != "OK") {
printf("I2P: SAM handshake failed: %s\n", reply.c_str());
return false;
}
return true;
}
std::string CI2PSession::DestToB32(const std::string& strB64Dest)
{
std::vector<unsigned char> dest = DecodeI2PBase64(strB64Dest);
if (dest.empty())
return "";
unsigned char hash[SHA256_DIGEST_LENGTH];
SHA256(dest.data(), dest.size(), hash);
std::string b32 = EncodeBase32(hash, SHA256_DIGEST_LENGTH);
// I2P b32 addresses are unpadded.
while (!b32.empty() && b32[b32.size() - 1] == '=')
b32.erase(b32.size() - 1);
return b32 + ".b32.i2p";
}
// --- session bring-up ------------------------------------------------------
bool CI2PSession::LoadOrCreateDestination(std::string& strPrivKeyRet)
{
fs::path keyPath = GetDataDir() / "i2p_private_key";
// Reuse an existing persistent destination if we have one.
{
std::ifstream f(keyPath.string().c_str());
if (f.is_open()) {
std::string line;
std::getline(f, line);
while (!line.empty() &&
(line[line.size() - 1] == '\r' || line[line.size() - 1] == '\n'))
line.erase(line.size() - 1);
if (!line.empty()) {
strPrivKeyRet = line;
printf("I2P: loaded persistent destination from %s\n",
keyPath.string().c_str());
return true;
}
}
}
// Generate a fresh destination via the bridge (Ed25519, SIGNATURE_TYPE=7).
SOCKET hSocket = INVALID_SOCKET;
if (!SamConnect(hSocket) || !SamHandshake(hSocket)) {
if (hSocket != INVALID_SOCKET) closesocket(hSocket);
return false;
}
bool ok = false;
if (SamSendLine(hSocket, "DEST GENERATE SIGNATURE_TYPE=7")) {
std::string reply;
if (SamRecvLine(hSocket, reply)) {
std::string priv = SamGetValue(reply, "PRIV");
if (!priv.empty()) {
strPrivKeyRet = priv;
std::ofstream out(keyPath.string().c_str(), std::ios::trunc);
if (out.is_open()) {
out << priv << std::endl;
out.close();
printf("I2P: generated and saved new persistent destination\n");
ok = true;
} else {
printf("I2P: WARNING could not write %s\n", keyPath.string().c_str());
ok = true; // still usable for this run
}
}
}
}
closesocket(hSocket);
return ok;
}
bool CI2PSession::CreateSession()
{
if (!SamConnect(hSession))
return false;
if (!SamHandshake(hSession))
return false;
std::ostringstream id;
id << "triangles-" << (uint64_t)GetTime() << "-" << (uint64_t)(GetRand(1000000));
sessionId = id.str();
std::string cmd = "SESSION CREATE STYLE=STREAM ID=" + sessionId +
" DESTINATION=" + privateKey + " SIGNATURE_TYPE=7";
if (!SamSendLine(hSession, cmd))
return false;
std::string reply;
if (!SamRecvLine(hSession, reply))
return false;
if (SamGetValue(reply, "RESULT") != "OK") {
printf("I2P: SESSION CREATE failed: %s\n", reply.c_str());
return false;
}
// The bridge echoes the (possibly newly assigned) private key back.
std::string echoed = SamGetValue(reply, "DESTINATION");
if (!echoed.empty())
privateKey = echoed;
return true;
}
bool CI2PSession::ResolveMyB32()
{
SOCKET hSocket = INVALID_SOCKET;
if (!SamConnect(hSocket) || !SamHandshake(hSocket)) {
if (hSocket != INVALID_SOCKET) closesocket(hSocket);
return false;
}
bool ok = false;
if (SamSendLine(hSocket, "NAMING LOOKUP NAME=ME")) {
std::string reply;
if (SamRecvLine(hSocket, reply) && SamGetValue(reply, "RESULT") == "OK") {
std::string dest = SamGetValue(reply, "VALUE");
std::string b32 = DestToB32(dest);
if (!b32.empty()) {
std::lock_guard<std::mutex> lock(cs);
b32Address = b32;
ok = true;
}
}
}
closesocket(hSocket);
return ok;
}
bool CI2PSession::Start()
{
if (!GetBoolArg("-i2p", true)) {
printf("I2P: disabled (-i2p=0)\n");
return false;
}
fEnabled.store(true);
// -i2psam=host:port overrides the default SAM bridge endpoint.
std::string sam = GetArg("-i2psam", "");
if (!sam.empty()) {
int port = I2P_DEFAULT_SAM_PORT;
std::string host;
SplitHostPort(sam, port, host);
if (!host.empty()) samHost = host;
if (port > 0) samPort = port;
}
printf("I2P: connecting to SAM bridge at %s:%d\n", samHost.c_str(), samPort);
if (!LoadOrCreateDestination(privateKey)) {
printf("I2P: ERROR could not obtain a destination. Is an I2P router with "
"the SAM bridge enabled running at %s:%d?\n", samHost.c_str(), samPort);
return false;
}
if (!CreateSession()) {
printf("I2P: ERROR failed to create SAM STREAM session\n");
if (hSession != INVALID_SOCKET) { closesocket(hSession); hSession = INVALID_SOCKET; }
return false;
}
if (!ResolveMyB32())
printf("I2P: WARNING could not resolve our own .b32.i2p address yet\n");
fActive.store(true);
fShutdown.store(false);
printf("I2P: session active. Our address: %s\n", GetB32Address().c_str());
// Register our I2P address as a local address so peers can learn it.
CService meI2P;
if (!b32Address.empty() && meI2P.SetSpecial(b32Address)) {
meI2P.SetPort((unsigned short)GetListenPort());
AddLocal(meI2P, LOCAL_MANUAL);
}
acceptThread = std::thread(&CI2PSession::AcceptLoop, this);
return true;
}
void CI2PSession::Stop()
{
if (!fEnabled.load())
return;
fShutdown.store(true);
fActive.store(false);
if (hSession != INVALID_SOCKET) {
closesocket(hSession);
hSession = INVALID_SOCKET;
}
if (acceptThread.joinable())
acceptThread.join();
fEnabled.store(false);
printf("I2P: session stopped\n");
}
// --- inbound ---------------------------------------------------------------
void CI2PSession::AcceptLoop()
{
while (!fShutdown.load()) {
SOCKET hSocket = INVALID_SOCKET;
if (!SamConnect(hSocket) || !SamHandshake(hSocket)) {
if (hSocket != INVALID_SOCKET) closesocket(hSocket);
if (fShutdown.load()) break;
MilliSleep(2000);
continue;
}
// Block here until a peer dials us; the router then streams the remote
// destination on its own line, after which the socket carries data.
if (!SamSendLine(hSocket, "STREAM ACCEPT ID=" + sessionId + " SILENT=false")) {
closesocket(hSocket);
MilliSleep(1000);
continue;
}
std::string status;
if (!SamRecvLine(hSocket, status) || SamGetValue(status, "RESULT") != "OK") {
if (!fShutdown.load())
printf("I2P: STREAM ACCEPT rejected: %s\n", status.c_str());
closesocket(hSocket);
MilliSleep(1000);
continue;
}
std::string remoteDest;
if (!SamRecvLine(hSocket, remoteDest)) {
closesocket(hSocket);
continue;
}
if (fShutdown.load()) {
closesocket(hSocket);
break;
}
// The first token is the remote full destination (base64).
std::string destTok = remoteDest;
size_t sp = destTok.find(' ');
if (sp != std::string::npos)
destTok = destTok.substr(0, sp);
std::string b32 = DestToB32(destTok);
CAddress addr;
if (b32.empty() || !addr.SetSpecial(b32)) {
printf("I2P: could not parse inbound remote destination\n");
closesocket(hSocket);
continue;
}
addr.nServices = 0;
addr.nTime = GetTime();
// Hand the live data socket to the net layer as an inbound peer.
printf("I2P: inbound connection from %s\n", b32.c_str());
AddI2PInboundNode(hSocket, addr);
}
}
// --- outbound --------------------------------------------------------------
bool CI2PSession::Connect(const std::string& strDest, SOCKET& hSocketRet)
{
if (!fActive.load())
return false;
SOCKET hSocket = INVALID_SOCKET;
if (!SamConnect(hSocket) || !SamHandshake(hSocket)) {
if (hSocket != INVALID_SOCKET) closesocket(hSocket);
return false;
}
if (!SamSendLine(hSocket, "STREAM CONNECT ID=" + sessionId +
" DESTINATION=" + strDest + " SILENT=false")) {
closesocket(hSocket);
return false;
}
std::string status;
if (!SamRecvLine(hSocket, status) || SamGetValue(status, "RESULT") != "OK") {
printf("I2P: STREAM CONNECT to %s failed: %s\n", strDest.c_str(), status.c_str());
closesocket(hSocket);
return false;
}
// Socket is now a bidirectional stream to the peer.
hSocketRet = hSocket;
return true;
}
bool StartI2P()
{
return CI2PSession::GetInstance()->Start();
}
void StopI2P()
{
CI2PSession::GetInstance()->Stop();
}
+95
View File
@@ -0,0 +1,95 @@
// Copyright (c) 2024 Triangles developers
// I2P (SAM v3) transport support
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
//
// This module gives Triangles real I2P connectivity that mirrors the existing
// embedded-Tor design: instead of a SOCKS proxy it talks the SAM v3 protocol
// to a locally running I2P router (i2pd or Java I2P) and obtains a persistent
// I2P destination whose ".b32.i2p" address is shown alongside the .onion
// address. The wallet:
// * creates / loads a persistent destination (i2p_private_key in datadir),
// * runs a STREAM session so peers can dial us,
// * accepts inbound I2P streams and feeds them to the net layer,
// * dials outbound ".b32.i2p" peers through the same session.
//
// A running I2P router with its SAM bridge enabled (default 127.0.0.1:7656) is
// required; nothing is bundled. Enable with -i2p and optionally -i2psam=host:port.
#ifndef TRIANGLES_I2P_H
#define TRIANGLES_I2P_H
#include <atomic>
#include <mutex>
#include <string>
#include <thread>
#include "compat.h" // SOCKET / INVALID_SOCKET
// Default SAM bridge endpoint exposed by i2pd / Java I2P.
#define I2P_DEFAULT_SAM_HOST "127.0.0.1"
#define I2P_DEFAULT_SAM_PORT 7656
// Manages a single persistent I2P STREAM session over SAM v3.
class CI2PSession
{
public:
static CI2PSession* GetInstance();
// Bring the session up: connect to the SAM bridge, load/generate the
// persistent destination and start accepting inbound streams.
// Returns false (and logs) if no router/SAM bridge is reachable.
bool Start();
// Tear the session down and stop the accept loop.
void Stop();
bool IsEnabled() const { return fEnabled.load(); }
bool IsActive() const { return fActive.load(); }
// Our own ".b32.i2p" address (empty until the session is up).
std::string GetB32Address();
// Dial a remote ".b32.i2p" (or full base64 destination) through the
// session. On success hSocketRet is a connected, blocking data socket the
// caller can hand to a CNode. The caller takes ownership of the socket.
bool Connect(const std::string& strDest, SOCKET& hSocketRet);
private:
CI2PSession();
~CI2PSession();
// --- low level SAM helpers ---
bool SamConnect(SOCKET& hSocketRet); // raw TCP to the bridge
bool SamHandshake(SOCKET hSocket); // HELLO VERSION
bool SamSendLine(SOCKET hSocket, const std::string& strLine);
bool SamRecvLine(SOCKET hSocket, std::string& strLineRet);
static std::string SamGetValue(const std::string& strReply, const std::string& strKey);
bool LoadOrCreateDestination(std::string& strPrivKeyRet);
bool CreateSession(); // SESSION CREATE
bool ResolveMyB32(); // NAMING LOOKUP ME
void AcceptLoop(); // inbound STREAM ACCEPT
// Compute the ".b32.i2p" address from a base64 (I2P alphabet) destination.
static std::string DestToB32(const std::string& strB64Dest);
std::string samHost;
int samPort;
std::string sessionId;
std::string privateKey; // persistent destination private key (base64)
std::string b32Address; // our own .b32.i2p
SOCKET hSession; // long-lived control socket owning the session
std::atomic<bool> fEnabled;
std::atomic<bool> fActive;
std::atomic<bool> fShutdown;
std::thread acceptThread;
std::mutex cs;
};
// Convenience: start/stop from init.cpp.
bool StartI2P();
void StopI2P();
#endif // TRIANGLES_I2P_H
+368
View File
@@ -0,0 +1,368 @@
// Copyright (c) 2024 Triangles developers
// I2P Router Process Manager - launches and manages a bundled i2pd binary
// Distributed under the MIT/X11 software license
#ifdef WIN32
#define NOMINMAX
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0600
#endif
#endif
#include "i2p_process.h"
#include "util.h"
#include <filesystem>
#include <fstream>
#include <sstream>
#include <vector>
#ifdef WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#include <tlhelp32.h>
#include <windows.h>
#else
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <unistd.h>
#endif
namespace fs = std::filesystem;
static CI2PProcess* i2pProcessInstance = nullptr;
CI2PProcess* CI2PProcess::GetInstance()
{
if (!i2pProcessInstance)
i2pProcessInstance = new CI2PProcess();
return i2pProcessInstance;
}
CI2PProcess::CI2PProcess()
: samPort(7656)
, running(false)
, fExternal(false)
#ifdef WIN32
, hProcess(nullptr)
, hJob(nullptr)
, processId(0)
#else
, processId(0)
#endif
{
}
CI2PProcess::~CI2PProcess()
{
Stop();
}
// Try a quick TCP connect; success means something is already listening
// (e.g. the SAM bridge is up, or an external router is running).
bool CI2PProcess::CanConnect(const std::string& host, int port)
{
#ifdef WIN32
SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (s == INVALID_SOCKET) return false;
#else
int s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (s < 0) return false;
#endif
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons((unsigned short)port);
addr.sin_addr.s_addr = inet_addr(host.c_str());
bool ok = (connect(s, (struct sockaddr*)&addr, sizeof(addr)) == 0);
#ifdef WIN32
closesocket(s);
#else
close(s);
#endif
return ok;
}
std::string CI2PProcess::FindI2pdBinary()
{
std::vector<std::string> candidates;
#ifdef WIN32
const char* exeName = "i2pd.exe";
#else
const char* exeName = "i2pd";
#endif
// 1. Next to the wallet executable (this is how tor.exe is shipped).
try {
fs::path exeDir;
#ifdef WIN32
char buf[MAX_PATH];
if (GetModuleFileNameA(nullptr, buf, MAX_PATH) > 0)
exeDir = fs::path(buf).parent_path();
#else
exeDir = fs::current_path();
#endif
if (!exeDir.empty()) {
candidates.push_back((exeDir / exeName).string());
candidates.push_back((exeDir / "i2pd" / exeName).string());
candidates.push_back((exeDir / "I2P" / exeName).string());
}
} catch (...) {}
// 2. In / next to the data directory.
candidates.push_back((GetDataDir() / exeName).string());
candidates.push_back((GetDataDir() / "i2pd" / exeName).string());
// 3. Common system locations.
#ifdef WIN32
if (const char* pf = getenv("ProgramFiles"))
candidates.push_back(std::string(pf) + "\\i2pd\\" + exeName);
if (const char* pfx = getenv("ProgramFiles(x86)"))
candidates.push_back(std::string(pfx) + "\\i2pd\\" + exeName);
candidates.push_back(std::string("C:\\i2pd\\") + exeName);
#else
candidates.push_back("/usr/bin/i2pd");
candidates.push_back("/usr/local/bin/i2pd");
candidates.push_back("/opt/i2pd/bin/i2pd");
candidates.push_back("/opt/homebrew/bin/i2pd");
candidates.push_back("/usr/local/opt/i2pd/bin/i2pd");
#endif
for (const std::string& c : candidates) {
try {
if (fs::exists(c) && fs::is_regular_file(c)) {
printf("I2P: found i2pd binary at %s\n", c.c_str());
return c;
}
} catch (...) {}
}
return "";
}
bool CI2PProcess::WriteConfig()
{
fs::path dir(dataDir);
try {
fs::create_directories(dir);
} catch (const std::exception& e) {
lastError = std::string("Cannot create i2pd data directory: ") + e.what();
return false;
}
confPath = (dir / "i2pd.conf").string();
fs::path logPath = dir / "i2pd.log";
std::ofstream conf(confPath.c_str(), std::ios::trunc);
if (!conf.is_open()) {
lastError = "Cannot write i2pd.conf to " + confPath;
return false;
}
conf << "# Triangles Wallet I2P configuration (auto-generated)\n";
conf << "# Do not edit - this file is overwritten on startup\n\n";
conf << "daemon = false\n";
conf << "log = file\n";
conf << "logfile = " << logPath.string() << "\n";
conf << "datadir = " << dir.string() << "\n\n";
// The bridge our SAM client talks to.
conf << "[sam]\n";
conf << "enabled = true\n";
conf << "address = 127.0.0.1\n";
conf << "port = " << samPort << "\n\n";
// We only need SAM; keep everything else off to minimise footprint.
conf << "[httpproxy]\nenabled = false\n\n";
conf << "[socksproxy]\nenabled = false\n\n";
conf << "[http]\nenabled = false\n\n";
conf << "[i2pcontrol]\nenabled = false\n";
conf.close();
printf("I2P: wrote i2pd config to %s (SAM port %d)\n", confPath.c_str(), samPort);
return true;
}
bool CI2PProcess::Start(const std::string& dataDirIn, int samPortIn)
{
dataDir = dataDirIn;
samPort = samPortIn;
fExternal = false;
lastError.clear();
// If a SAM bridge is already up, use it instead of launching our own.
if (CanConnect("127.0.0.1", samPort)) {
printf("I2P: detected an I2P router already listening on SAM port %d; using it\n", samPort);
fExternal = true;
return true;
}
binaryPath = FindI2pdBinary();
if (binaryPath.empty()) {
lastError = "No i2pd binary found (ship i2pd alongside the wallet, like tor)";
printf("I2P: %s\n", lastError.c_str());
return false;
}
if (!WriteConfig())
return false;
printf("I2P: starting i2pd: %s --conf %s\n", binaryPath.c_str(), confPath.c_str());
#ifdef WIN32
STARTUPINFOA si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE;
ZeroMemory(&pi, sizeof(pi));
std::string cmdLine = "\"" + binaryPath + "\" --conf \"" + confPath + "\"";
if (!CreateProcessA(nullptr, (LPSTR)cmdLine.c_str(), nullptr, nullptr,
FALSE, CREATE_NO_WINDOW, nullptr, nullptr, &si, &pi)) {
DWORD err = ::GetLastError();
lastError = strprintf("CreateProcess failed for i2pd '%s' (Windows error %lu)", binaryPath.c_str(), err);
printf("I2P: ERROR %s\n", lastError.c_str());
return false;
}
hProcess = pi.hProcess;
processId = pi.dwProcessId;
CloseHandle(pi.hThread);
// Kill i2pd if the wallet dies (matches the embedded Tor behaviour).
hJob = CreateJobObject(nullptr, nullptr);
if (hJob) {
JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobInfo = {};
jobInfo.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
SetInformationJobObject(hJob, JobObjectExtendedLimitInformation, &jobInfo, sizeof(jobInfo));
if (!AssignProcessToJobObject(hJob, hProcess))
printf("I2P: WARNING could not assign i2pd to Job Object (error %lu)\n", GetLastError());
}
printf("I2P: i2pd started (PID %lu)\n", processId);
#else
pid_t pid = fork();
if (pid < 0) {
lastError = "Failed to fork for i2pd process";
printf("I2P: ERROR %s\n", lastError.c_str());
return false;
}
if (pid == 0) {
freopen("/dev/null", "w", stdout);
freopen("/dev/null", "w", stderr);
execl(binaryPath.c_str(), binaryPath.c_str(),
"--conf", confPath.c_str(), (char*)nullptr);
_exit(1);
}
processId = pid;
printf("I2P: i2pd started (PID %d)\n", processId);
#endif
running = true;
// Wait for the SAM bridge to come up. The bridge opens quickly; tunnel
// build (needed for actual connectivity) continues in the background.
printf("I2P: waiting for SAM bridge on port %d...\n", samPort);
for (int i = 0; i < 45; i++) {
MilliSleep(1000);
if (fShutdown) {
Stop();
return false;
}
if (CanConnect("127.0.0.1", samPort)) {
printf("I2P: SAM bridge ready on port %d (took %ds)\n", samPort, i + 1);
return true;
}
if (!IsRunning()) {
lastError = "i2pd exited during start-up before the SAM bridge became ready";
printf("I2P: ERROR %s\n", lastError.c_str());
running = false;
return false;
}
}
lastError = strprintf("i2pd started but SAM port %d not ready after 45s", samPort);
printf("I2P: WARNING %s (it may still be building tunnels)\n", lastError.c_str());
return true;
}
void CI2PProcess::Stop()
{
if (fExternal) {
// We never launched it; leave the user's router running.
running = false;
return;
}
if (!running) return;
#ifdef WIN32
if (hProcess != nullptr) {
printf("I2P: stopping i2pd (PID %lu)...\n", processId);
TerminateProcess(hProcess, 0);
WaitForSingleObject(hProcess, 5000);
CloseHandle(hProcess);
hProcess = nullptr;
}
if (hJob != nullptr) {
CloseHandle(hJob);
hJob = nullptr;
}
#else
if (processId > 0) {
printf("I2P: stopping i2pd (PID %d)...\n", processId);
kill(processId, SIGTERM);
for (int i = 0; i < 50; i++) {
int status;
pid_t result = waitpid(processId, &status, WNOHANG);
if (result != 0) break;
MilliSleep(100);
}
kill(processId, SIGKILL);
waitpid(processId, nullptr, 0);
}
#endif
processId = 0;
running = false;
printf("I2P: i2pd stopped\n");
}
bool CI2PProcess::IsRunning()
{
if (fExternal) return true;
if (!running) return false;
#ifdef WIN32
if (hProcess == nullptr) return false;
DWORD exitCode;
if (GetExitCodeProcess(hProcess, &exitCode))
return (exitCode == STILL_ACTIVE);
return false;
#else
if (processId <= 0) return false;
int status;
pid_t result = waitpid(processId, &status, WNOHANG);
return (result == 0); // 0 => still running
#endif
}
bool StartEmbeddedI2P(const std::string& dataDir, int samPort)
{
return CI2PProcess::GetInstance()->Start(dataDir, samPort);
}
void StopEmbeddedI2P()
{
CI2PProcess::GetInstance()->Stop();
}
+70
View File
@@ -0,0 +1,70 @@
// Copyright (c) 2024 Triangles developers
// I2P Router Process Manager - launches and manages a bundled i2pd binary
// Distributed under the MIT/X11 software license
//
// Mirrors tor_process.cpp: locate an i2pd executable shipped alongside the
// wallet (or installed on the system), write an auto-generated config that
// enables the SAM bridge, launch it as a managed child process, and shut it
// down when the wallet exits. The SAM session in i2p.cpp then connects to it,
// so the user does not have to install or run a separate I2P router.
#ifndef TRIANGLES_I2P_PROCESS_H
#define TRIANGLES_I2P_PROCESS_H
#include <string>
#ifdef WIN32
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#endif
class CI2PProcess
{
public:
static CI2PProcess* GetInstance();
CI2PProcess();
~CI2PProcess();
// Bring up the router. If something is already listening on the SAM port we
// assume an external router and do not launch our own (fExternal=true).
// Returns true if a SAM bridge is (or will shortly be) reachable.
bool Start(const std::string& dataDir, int samPort = 7656);
// Terminate the launched router (no-op for an external one).
void Stop();
bool IsRunning();
bool IsExternal() const { return fExternal; }
std::string GetLastError() const { return lastError; }
std::string GetBinaryPath() const { return binaryPath; }
private:
std::string FindI2pdBinary();
bool WriteConfig();
static bool CanConnect(const std::string& host, int port);
int samPort;
bool running;
bool fExternal;
std::string dataDir;
std::string binaryPath;
std::string confPath;
std::string lastError;
#ifdef WIN32
HANDLE hProcess;
HANDLE hJob;
DWORD processId;
#else
int processId;
#endif
};
// Convenience wrappers for init.cpp.
bool StartEmbeddedI2P(const std::string& dataDir, int samPort);
void StopEmbeddedI2P();
#endif // TRIANGLES_I2P_PROCESS_H
+43
View File
@@ -340,6 +340,10 @@ void Shutdown(void* parg)
pScriptCheckQueue.reset();
}
// Stop the I2P SAM session and its accept loop, then the i2pd router.
StopI2P();
StopEmbeddedI2P();
// NOW safe to destroy Tor state - all threads have stopped
ShutdownTorV3();
StopEmbeddedTor();
@@ -1670,6 +1674,45 @@ bool AppInit2()
if (!NewThread(ThreadTorMaintenance, nullptr))
printf("Warning: ThreadTorMaintenance could not be started\n");
}
// Bring up I2P (SAM) transport alongside Tor so the wallet has both a
// .onion and a .b32.i2p address. On by default; disable with -i2p=0.
// A bundled i2pd router is launched automatically (mirroring embedded
// Tor); if -i2psam points at a non-loopback bridge, or a router is
// already running, we use that instead.
if (GetBoolArg("-i2p", true)) {
int64_t nI2PStart = GetTimeMillis();
// Resolve the SAM endpoint (default 127.0.0.1:7656).
std::string sam = GetArg("-i2psam", "127.0.0.1:7656");
int samPort = I2P_DEFAULT_SAM_PORT;
std::string samHost = "127.0.0.1";
SplitHostPort(sam, samPort, samHost);
if (samPort <= 0) samPort = I2P_DEFAULT_SAM_PORT;
bool loopback = samHost.empty() || samHost == "127.0.0.1" || samHost == "localhost";
// Auto-launch our own i2pd only when the bridge is local.
if (loopback) {
uiInterface.InitMessage(_("Starting the I2P router..."));
if (!StartEmbeddedI2P((GetDataDir() / "i2pd").string(), samPort)) {
printf("NOTICE: bundled I2P router unavailable (%s).\n",
CI2PProcess::GetInstance()->GetLastError().c_str());
printf(" I2P will use an external router if one is running on %s.\n", sam.c_str());
}
}
uiInterface.InitMessage(_("Connecting to the I2P network..."));
bool i2pStarted = StartI2P();
StartupPerfLog("i2p_start", GetTimeMillis() - nI2PStart, strprintf("started=%d", i2pStarted));
if (i2pStarted) {
SetReachable(NET_I2P, true);
std::string i2pAddr = CI2PSession::GetInstance()->GetB32Address();
printf("I2P network enabled. Our address: %s\n", i2pAddr.c_str());
} else {
printf("NOTICE: I2P not available this session; continuing with Tor only\n");
StopEmbeddedI2P();
}
}
}
// ********************************************************* Step 9: import blocks
+284
View File
@@ -4010,6 +4010,290 @@ bool LoadExternalBlockFile(FILE* fileIn)
return nLoaded > 0;
}
bool FastImportBlockFile()
{
// Fast block import: reads blk0001.dat and builds the block index
// directly without re-writing block data. LevelDB writes are batched
// every 200K blocks for speed. Only used for trusted bootstrap data
// (blocks below the hardcoded checkpoint).
fs::path blkPath = GetDataDir() / "blk0001.dat";
if (!fs::exists(blkPath))
return false;
printf("FastImportBlockFile: starting from %s\n", blkPath.string().c_str());
int64_t nStart = GetTimeMillis();
FILE* fileIn = fopen(blkPath.string().c_str(), "rb");
if (!fileIn)
return false;
// Get file size for progress
fseek(fileIn, 0, SEEK_END);
int64_t nFileSize = ftell(fileIn);
fseek(fileIn, 0, SEEK_SET);
int nLoaded = 0;
int64_t nLastProgressReport = 0;
{
LOCK(cs_main);
CAutoFile blkdat(fileIn, SER_DISK, CLIENT_VERSION);
auto txdb_holder = MakeChainDB(); CTxDBBase& txdb = *txdb_holder;
txdb.TxnBegin();
unsigned int nPos = 0;
while (nPos != (unsigned int)-1 && blkdat.good() && !fRequestShutdown)
{
// Find message start bytes (same scan as LoadExternalBlockFile)
unsigned char pchData[65536];
do {
fseek(blkdat, nPos, SEEK_SET);
int nRead = fread(pchData, 1, sizeof(pchData), blkdat);
if (nRead <= 8)
{
nPos = (unsigned int)-1;
break;
}
void* nFind = memchr(pchData, pchMessageStart[0], nRead+1-sizeof(pchMessageStart));
if (nFind)
{
if (memcmp(nFind, pchMessageStart, sizeof(pchMessageStart))==0)
{
nPos += ((unsigned char*)nFind - pchData) + sizeof(pchMessageStart);
break;
}
nPos += ((unsigned char*)nFind - pchData) + 1;
}
else
nPos += sizeof(pchData) - sizeof(pchMessageStart) + 1;
} while(!fRequestShutdown);
if (nPos == (unsigned int)-1)
break;
fseek(blkdat, nPos, SEEK_SET);
unsigned int nSize;
blkdat >> nSize;
if (nSize == 0 || nSize > MAX_BLOCK_SIZE)
{
nPos += 4 + nSize;
continue;
}
// nBlockPos = file position where the block data starts
// (after 4-byte message start + 4-byte size)
unsigned int nBlockPos = nPos + 4;
CBlock block;
blkdat >> block;
uint256 hash = block.GetHash();
if (mapBlockIndex.count(hash))
{
nPos += 4 + nSize;
continue; // already indexed
}
// Create CBlockIndex
CBlockIndex* pindexNew = new CBlockIndex(1, nBlockPos, block);
if (!pindexNew)
break;
// Link to previous block
auto miPrev = mapBlockIndex.find(block.hashPrevBlock);
if (miPrev != mapBlockIndex.end())
{
pindexNew->pprev = miPrev->second;
pindexNew->nHeight = pindexNew->pprev->nHeight + 1;
}
// Chain trust
pindexNew->nChainTrust = (pindexNew->pprev ? pindexNew->pprev->nChainTrust : 0) + pindexNew->GetBlockTrust();
// Stake entropy bit
pindexNew->SetStakeEntropyBit(block.GetStakeEntropyBit());
// Stake modifier (minimal for blocks far below checkpoint)
int nCheckpointHeight = Checkpoints::GetTotalBlocksEstimate();
if (pindexNew->nHeight >= nCheckpointHeight - 1000)
{
uint64_t nStakeModifier = 0;
bool fGeneratedStakeModifier = false;
ComputeNextStakeModifier(pindexNew->pprev, nStakeModifier, fGeneratedStakeModifier);
pindexNew->SetStakeModifier(nStakeModifier, fGeneratedStakeModifier);
}
else
{
pindexNew->SetStakeModifier(0, pindexNew->nHeight == 0);
}
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;
// Link pnext for previous block
if (pindexNew->pprev)
pindexNew->pprev->pnext = pindexNew;
// NOTE: tx-index, UTXO-set and money-supply application are
// DEFERRED to a second pass over the active (best-trust) chain
// only — see the pass after this loop. Applying them here, for
// every block read from the file (which permanently retains
// ORPHANED side-chain blocks), wrote those orphans' outputs into
// the UTXO set as phantom coins and over-counted nMoneySupply.
// That was the root cause of UTXO-set / supply inflation on every
// reindex. Here we only build the block index for all blocks so
// best-chain selection by trust still works.
txdb.WriteBlockIndex(CDiskBlockIndex(pindexNew));
// Update best chain
if (pindexNew->nChainTrust > nBestChainTrust)
{
hashBestChain = hash;
pindexBest = pindexNew;
pblockindexFBBHLast = nullptr;
nBestHeight = pindexNew->nHeight;
nBestChainTrust = pindexNew->nChainTrust;
nTimeBestReceived = GetTime();
}
// Set genesis block
if (pindexGenesisBlock == nullptr && pindexNew->nHeight == 0)
pindexGenesisBlock = pindexNew;
nLoaded++;
nPos += 4 + nSize;
// Batch commit every 200K blocks for LevelDB efficiency
if (nLoaded % 200000 == 0)
{
txdb.WriteHashBestChain(hashBestChain);
txdb.TxnCommit();
txdb.TxnBegin();
}
// Report progress every 5000 blocks to keep GUI responsive.
// AppInit2 runs on the GUI thread, so uiInterface.InitMessage
// triggers processEvents() which prevents the window from freezing.
if (nLoaded % 5000 == 0)
{
int pct = (nFileSize > 0) ? (int)((int64_t)nPos * 100 / nFileSize) : 0;
printf("FastImport: %d blocks indexed (%d%%)\n", nLoaded, pct);
uiInterface.InitMessage(strprintf(_("Importing blocks... %d indexed (%d%%)"), nLoaded, pct));
}
}
// ---- Pass 2: apply tx-index, UTXO set and money supply along the
// ACTIVE (best-trust) chain ONLY. The file-order pass above indexed
// every block including orphaned side-chain blocks; replaying only
// the main chain here keeps the UTXO set and money supply exactly in
// consensus and prevents orphan outputs becoming phantom coins. ----
if (pindexBest)
{
std::vector<CBlockIndex*> vMain;
for (CBlockIndex* p = pindexBest; p; p = p->pprev)
vMain.push_back(p);
std::reverse(vMain.begin(), vMain.end());
printf("FastImportBlockFile: applying UTXO/supply along %d main-chain blocks...\n", (int)vMain.size());
uiInterface.InitMessage(_("Building UTXO set (main chain)..."));
int64_t nRunningSupply = 0;
int nApplied = 0;
for (CBlockIndex* pindex : vMain)
{
// Genesis (height 0) is a hardcoded special block that is not
// re-read from disk this way; it contributes nothing to supply
// and the genesis-walk audit skips it identically. Carry the
// running supply (0) forward and move on.
if (pindex->nHeight == 0)
{
pindex->nMint = 0;
pindex->nMoneySupply = nRunningSupply; // still 0 here
txdb.WriteBlockIndex(CDiskBlockIndex(pindex));
continue;
}
CBlock blockMain;
if (!blockMain.ReadFromDisk(pindex))
return error("FastImportBlockFile: ReadFromDisk failed at height %d", pindex->nHeight);
int64_t nBlockValueIn = 0;
int64_t nBlockValueOut = 0;
unsigned int nTxPos2 = pindex->nBlockPos + ::GetSerializeSize(CBlock(), SER_DISK, CLIENT_VERSION)
- (2 * GetSizeOfCompactSize(0)) + GetSizeOfCompactSize(blockMain.vtx.size());
for (const CTransaction& tx : blockMain.vtx)
{
uint256 hashTx = tx.GetHash();
CDiskTxPos posThisTx(1, pindex->nBlockPos, nTxPos2);
txdb.UpdateTxIndex(hashTx, CTxIndex(posThisTx, tx.vout.size()));
nTxPos2 += ::GetSerializeSize(tx, SER_DISK, CLIENT_VERSION);
nBlockValueOut += tx.GetValueOut();
if (!tx.IsCoinBase())
{
for (const CTxIn& txin : tx.vin)
{
CUtxoEntry uprev;
if (txdb.ReadUtxo(txin.prevout.hash, txin.prevout.n, uprev))
nBlockValueIn += uprev.nValue;
txdb.EraseUtxo(txin.prevout.hash, txin.prevout.n);
}
}
for (unsigned int k = 0; k < tx.vout.size(); k++)
{
if (tx.vout[k].IsEmpty())
continue;
CUtxoEntry utxo;
utxo.nValue = tx.vout[k].nValue;
utxo.nHeight = pindex->nHeight;
utxo.scriptPubKey = tx.vout[k].scriptPubKey;
utxo.fCoinBase = tx.IsCoinBase();
utxo.fCoinStake = tx.IsCoinStake();
utxo.nTxTime = tx.nTime;
txdb.WriteUtxo(hashTx, k, utxo);
}
}
pindex->nMint = nBlockValueOut - nBlockValueIn;
nRunningSupply += (nBlockValueOut - nBlockValueIn);
pindex->nMoneySupply = nRunningSupply;
txdb.WriteBlockIndex(CDiskBlockIndex(pindex));
if (++nApplied % 200000 == 0) { txdb.TxnCommit(); txdb.TxnBegin(); }
if (nApplied % 5000 == 0)
{
int pct2 = (int)((int64_t)nApplied * 100 / (vMain.empty() ? 1 : vMain.size()));
printf("FastImport UTXO apply: %d/%d main-chain blocks (%d%%)\n", nApplied, (int)vMain.size(), pct2);
uiInterface.InitMessage(strprintf(_("Building UTXO set... %d%%"), pct2));
}
}
}
// Final commit
if (pindexBest)
{
txdb.WriteHashBestChain(hashBestChain);
// Write sync checkpoint
Checkpoints::WriteSyncCheckpoint(hashBestChain);
}
txdb.TxnCommit();
}
nTransactionsUpdated++;
printf("FastImportBlockFile: indexed %d blocks in %" PRId64 "ms\n", nLoaded, GetTimeMillis() - nStart);
return nLoaded > 0;
}
string GetWarnings(string strFor)
{
string strStatusBar;
+72 -2
View File
@@ -591,6 +591,16 @@ CNode* ConnectNode(CAddress addrConnect, const char *pszDest)
return nullptr;
}
// For I2P make sure addrConnect carries the destination so the resulting
// CNode is labelled correctly even when we were given a bare pszDest.
if (fI2P && !addrConnect.IsI2P()) {
std::string i2pHost = addrStr;
size_t i2pEnd = i2pHost.find(".i2p");
if (i2pEnd != std::string::npos)
i2pHost = i2pHost.substr(0, i2pEnd + 4);
addrConnect.SetSpecial(i2pHost);
}
if (pszDest == nullptr) {
if (IsLocal(addrConnect))
return nullptr;
@@ -614,8 +624,20 @@ CNode* ConnectNode(CAddress addrConnect, const char *pszDest)
pszDest ? 0 : (double)(GetAdjustedTime() - addrConnect.nTime)/3600.0);
// Connect
SOCKET hSocket;
if (pszDest ? ConnectSocketByName(addrConnect, hSocket, pszDest, GetDefaultPort()) : ConnectSocket(addrConnect, hSocket))
SOCKET hSocket = INVALID_SOCKET;
bool fConnected;
if (fI2P) {
// Route through the I2P SAM session. Strip any :port suffix; I2P peers
// are reached purely by destination.
std::string i2pDest = addrStr;
size_t i2pEnd = i2pDest.find(".i2p");
if (i2pEnd != std::string::npos)
i2pDest = i2pDest.substr(0, i2pEnd + 4);
fConnected = CI2PSession::GetInstance()->Connect(i2pDest, hSocket);
} else {
fConnected = pszDest ? ConnectSocketByName(addrConnect, hSocket, pszDest, GetDefaultPort()) : ConnectSocket(addrConnect, hSocket);
}
if (fConnected)
{
addrman.Attempt(addrConnect);
@@ -650,6 +672,54 @@ CNode* ConnectNode(CAddress addrConnect, const char *pszDest)
}
}
// Adopt a connected I2P SAM data socket (from the accept loop in i2p.cpp) as an
// inbound peer. The socket arrives in blocking mode; switch it to non-blocking
// to match the rest of the socket handler, then register the node.
void AddI2PInboundNode(SOCKET hSocket, const CAddress& addr)
{
if (hSocket == INVALID_SOCKET)
return;
if (CNode::IsBanned(addr)) {
printf("I2P inbound from %s dropped (banned)\n", addr.ToString().c_str());
closesocket(hSocket);
return;
}
// Honour the inbound connection limit.
int nInbound = 0;
{
LOCK(cs_vNodes);
for (CNode* pnode : vNodes)
if (pnode->fInbound)
nInbound++;
}
int nMaxInbound = GetArg("-maxconnections", 125) - MAX_OUTBOUND_CONNECTIONS;
if (nInbound >= nMaxInbound) {
printf("I2P inbound from %s dropped (too many inbound)\n", addr.ToString().c_str());
closesocket(hSocket);
return;
}
#ifdef WIN32
u_long nOne = 1;
if (ioctlsocket(hSocket, FIONBIO, &nOne) == SOCKET_ERROR)
printf("AddI2PInboundNode() : ioctlsocket non-blocking setting failed, error %d\n", WSAGetLastError());
#else
if (fcntl(hSocket, F_SETFL, O_NONBLOCK) == SOCKET_ERROR)
printf("AddI2PInboundNode() : fcntl non-blocking setting failed, error %d\n", errno);
#endif
printf("accepted I2P connection %s\n", addr.ToString().c_str());
CNode* pnode = new CNode(hSocket, addr, "", true);
pnode->AddRef();
pnode->nTimeConnected = GetTime();
{
LOCK(cs_vNodes);
vNodes.push_back(pnode);
}
}
void CNode::CloseSocketDisconnect()
{
fDisconnect = true;
+2
View File
@@ -37,6 +37,8 @@ void AddressCurrentlyConnected(const CService& addr);
CNode* FindNode(const CNetAddr& ip);
CNode* FindNode(const CService& ip);
CNode* ConnectNode(CAddress addrConnect, const char *strDest = nullptr);
// Adopt a connected I2P SAM data socket as an inbound peer (called from i2p.cpp).
void AddI2PInboundNode(SOCKET hSocket, const CAddress& addr);
void MapPort();
unsigned short GetListenPort();
bool BindListenPort(const CService &bindAddr, std::string& strError=REF(std::string()));
+45 -3
View File
@@ -672,6 +672,7 @@ void CNetAddr::Init()
memset(ip, 0, sizeof(ip));
memset(tor_v3_pubkey, 0, sizeof(tor_v3_pubkey));
m_is_tor_v3 = false;
m_is_i2p = false;
}
void CNetAddr::SetIP(const CNetAddr& ipIn)
@@ -679,6 +680,7 @@ void CNetAddr::SetIP(const CNetAddr& ipIn)
memcpy(ip, ipIn.ip, sizeof(ip));
memcpy(tor_v3_pubkey, ipIn.tor_v3_pubkey, sizeof(tor_v3_pubkey));
m_is_tor_v3 = ipIn.m_is_tor_v3;
m_is_i2p = ipIn.m_is_i2p;
}
static const unsigned char pchOnionCat[] = {0xFD,0x87,0xD8,0x7E,0xEB,0x43};
@@ -734,6 +736,22 @@ bool CNetAddr::SetSpecial(const std::string &strName)
return true;
}
}
// Modern I2P base32 address: 52 base32 chars = SHA-256(destination) (32 bytes)
// rendered as "<b32>.b32.i2p". Store the hash and flag this as an I2P address.
if (strName.size()>8 && strName.substr(strName.size() - 8, 8) == ".b32.i2p") {
std::string addrPart = strName.substr(0, strName.size() - 8);
std::vector<unsigned char> vchAddr = DecodeBase32(addrPart.c_str());
if (vchAddr.size() != 32)
return false;
// Keep the GarliCat prefix in ip[] so legacy reachability checks that
// look for unique-local space still treat this as a routable overlay.
memcpy(ip, pchGarliCat, sizeof(pchGarliCat));
memset(ip + sizeof(pchGarliCat), 0, 16 - sizeof(pchGarliCat));
memcpy(tor_v3_pubkey, vchAddr.data(), 32);
m_is_i2p = true;
m_is_tor_v3 = false;
return true;
}
return false;
}
@@ -856,7 +874,7 @@ bool CNetAddr::IsTorV3() const
bool CNetAddr::IsI2P() const
{
return (memcmp(ip, pchGarliCat, sizeof(pchGarliCat)) == 0);
return m_is_i2p || (memcmp(ip, pchGarliCat, sizeof(pchGarliCat)) == 0);
}
bool CNetAddr::IsLocal() const
@@ -962,6 +980,13 @@ std::string CNetAddr::ToStringIP() const
}
if (IsTor())
return EncodeBase32(&ip[6], 10) + ".onion";
if (m_is_i2p) {
// Modern I2P: base32 of the 32-byte destination hash, unpadded.
std::string b32 = EncodeBase32(tor_v3_pubkey, 32);
while (!b32.empty() && b32[b32.size() - 1] == '=')
b32.erase(b32.size() - 1);
return b32 + ".b32.i2p";
}
if (IsI2P())
return EncodeBase32(&ip[6], 10) + ".b32.i2p";
CService serv(*this, 0);
@@ -995,12 +1020,14 @@ bool operator==(const CNetAddr& a, const CNetAddr& b)
{
if (a.m_is_tor_v3 || b.m_is_tor_v3)
return a.m_is_tor_v3 == b.m_is_tor_v3 && memcmp(a.tor_v3_pubkey, b.tor_v3_pubkey, 32) == 0;
if (a.m_is_i2p || b.m_is_i2p)
return a.m_is_i2p == b.m_is_i2p && memcmp(a.tor_v3_pubkey, b.tor_v3_pubkey, 32) == 0;
return (memcmp(a.ip, b.ip, 16) == 0);
}
bool operator!=(const CNetAddr& a, const CNetAddr& b)
{
return (memcmp(a.ip, b.ip, 16) != 0);
return !(a == b);
}
bool operator<(const CNetAddr& a, const CNetAddr& b)
@@ -1009,6 +1036,10 @@ bool operator<(const CNetAddr& a, const CNetAddr& b)
return !a.m_is_tor_v3; // non-v3 sorts before v3
if (a.m_is_tor_v3)
return memcmp(a.tor_v3_pubkey, b.tor_v3_pubkey, 32) < 0;
if (a.m_is_i2p != b.m_is_i2p)
return !a.m_is_i2p; // non-i2p sorts before i2p
if (a.m_is_i2p)
return memcmp(a.tor_v3_pubkey, b.tor_v3_pubkey, 32) < 0;
return (memcmp(a.ip, b.ip, 16) < 0);
}
@@ -1032,6 +1063,17 @@ bool CNetAddr::GetIn6Addr(struct in6_addr* pipv6Addr) const
// no two connections will be attempted to addresses with the same group
std::vector<unsigned char> CNetAddr::GetGroup() const
{
// Modern I2P addresses keep their identifying bytes in the 32-byte
// destination-hash field (ip[] only holds the overlay prefix), so derive
// the group from the hash to keep peers in distinct groups.
if (m_is_i2p) {
std::vector<unsigned char> vch;
vch.push_back(NET_I2P);
vch.push_back(tor_v3_pubkey[0]);
vch.push_back(tor_v3_pubkey[1]);
return vch;
}
std::vector<unsigned char> vchRet;
int nClass = NET_IPV6;
int nStartByte = 0;
@@ -1106,7 +1148,7 @@ std::vector<unsigned char> CNetAddr::GetGroup() const
uint64_t CNetAddr::GetHash() const
{
uint256 hash;
if (m_is_tor_v3)
if (m_is_tor_v3 || m_is_i2p)
hash = Hash(&tor_v3_pubkey[0], &tor_v3_pubkey[32]);
else
hash = Hash(&ip[0], &ip[16]);
+7 -1
View File
@@ -106,8 +106,12 @@ class CNetAddr
{
protected:
unsigned char ip[16]; // in network byte order
unsigned char tor_v3_pubkey[32]; // Ed25519 public key for Tor v3 onion addresses
// For Tor v3 this holds the 32-byte Ed25519 public key. When m_is_i2p is
// set it instead holds the 32-byte SHA-256 of the I2P destination (the
// value rendered as the ".b32.i2p" address). A CNetAddr is never both.
unsigned char tor_v3_pubkey[32];
bool m_is_tor_v3;
bool m_is_i2p;
public:
CNetAddr();
@@ -160,6 +164,7 @@ class CNetAddr
READWRITE(FLATDATA(ip));
READWRITE(FLATDATA(tor_v3_pubkey));
READWRITE(m_is_tor_v3);
READWRITE(m_is_i2p);
)
};
@@ -203,6 +208,7 @@ class CService : public CNetAddr
READWRITE(FLATDATA(ip));
READWRITE(FLATDATA(tor_v3_pubkey));
READWRITE(m_is_tor_v3);
READWRITE(m_is_i2p);
unsigned short portN = htons(port);
READWRITE(portN);
if (fRead)
+6 -1
View File
@@ -349,6 +349,12 @@ TrianglesGUI::TrianglesGUI(bool fIsTestnet, QWidget *parent):
labelOnionAddress->setCursor(Qt::PointingHandCursor);
labelOnionAddress->installEventFilter(this);
// I2P address, stacked directly above the .onion address (click to copy)
labelI2PAddress = ui->label_i2p;
labelI2PAddress->setVisible(false);
labelI2PAddress->setCursor(Qt::PointingHandCursor);
labelI2PAddress->installEventFilter(this);
// V3 indicator next to staking icon (hidden until onion is active)
labelV3Icon = ui->label_v3;
labelV3Icon->setVisible(false);
@@ -370,7 +376,6 @@ TrianglesGUI::TrianglesGUI(bool fIsTestnet, QWidget *parent):
labelI2PIcon = ui->label_i2p_icon;
labelI2PIcon->setVisible(false);
QTimer *timerI2P = new QTimer(this);
connect(timerI2P, SIGNAL(timeout()), this, SLOT(updateI2PAddress()));
timerI2P->start(5000);
+1
View File
@@ -110,6 +110,7 @@ private:
QLabel *labelConnectionsIcon;
QLabel *labelBlocksIcon;
QLabel *labelOnionAddress;
QLabel *labelI2PAddress;
QLabel *labelV3Icon;
QLabel *labelI2PAddress;
QLabel *labelI2PIcon;
+25
View File
@@ -10,6 +10,9 @@
#include "db.h"
#include "walletdb.h"
#include "net_bootstrap.h"
#include "i2p.h"
#include "tor/onion_v3.h"
#include "tor/tor_embedded.h"
using namespace json_spirit;
using namespace std;
@@ -34,12 +37,34 @@ Value getnetworkinfo(const Array& params, bool fHelp)
healthObj.push_back(Pair("lastblocktime", static_cast<int64_t>(health.lastBlockTime)));
healthObj.push_back(Pair("networkmode", "tor_native"));
// Tor .onion address (wallet hidden service).
std::string onionAddress = CTorV3Manager::GetInstance()->GetWalletOnionAddress();
if (onionAddress.empty())
onionAddress = CTorEmbedded::GetInstance()->GetOnionAddress();
// I2P session state and .b32.i2p address.
CI2PSession* i2p = CI2PSession::GetInstance();
int nI2PPeers = 0;
{
LOCK(cs_vNodes);
for (CNode* pnode : vNodes)
if (pnode->addr.IsI2P())
nI2PPeers++;
}
Object i2pObj;
i2pObj.push_back(Pair("enabled", i2p->IsEnabled()));
i2pObj.push_back(Pair("active", i2p->IsActive()));
i2pObj.push_back(Pair("address", i2p->GetB32Address()));
i2pObj.push_back(Pair("peers", nI2PPeers));
Object obj;
obj.push_back(Pair("version", FormatFullVersion()));
obj.push_back(Pair("protocolversion", (int)PROTOCOL_VERSION));
obj.push_back(Pair("connections", (int)vNodes.size()));
obj.push_back(Pair("proxy", (proxy.first.IsValid() ? proxy.first.ToStringIPPort() : string())));
obj.push_back(Pair("ip", addrSeenByPeer.ToStringIP()));
obj.push_back(Pair("toraddress", onionAddress));
obj.push_back(Pair("i2p", i2pObj));
obj.push_back(Pair("localservices", strprintf("%016"PRIx64, nLocalServices)));
obj.push_back(Pair("testnet", fTestNet));
obj.push_back(Pair("networkhealth", healthObj));
+10
View File
@@ -9,6 +9,9 @@
#include "init.h"
#include "base58.h"
#include "smessage.h"
#include "i2p.h"
#include "tor/onion_v3.h"
#include "tor/tor_embedded.h"
using namespace json_spirit;
using namespace std;
@@ -100,6 +103,13 @@ Value getinfo(const Array& params, bool fHelp)
obj.push_back(Pair("proxy", (proxy.first.IsValid() ? proxy.first.ToStringIPPort() : string())));
obj.push_back(Pair("ip", addrSeenByPeer.ToStringIP()));
// Anonymous network identities.
std::string onionAddress = CTorV3Manager::GetInstance()->GetWalletOnionAddress();
if (onionAddress.empty())
onionAddress = CTorEmbedded::GetInstance()->GetOnionAddress();
obj.push_back(Pair("toraddress", onionAddress));
obj.push_back(Pair("i2paddress", CI2PSession::GetInstance()->GetB32Address()));
diff.push_back(Pair("proof-of-work", GetDifficulty()));
diff.push_back(Pair("proof-of-stake", GetDifficulty(GetLastBlockIndex(pindexBest, true))));
obj.push_back(Pair("difficulty", diff));
+18
View File
@@ -311,6 +311,24 @@ bool CTorProcess::WriteTorrc()
torrc << "AvoidDiskWrites 1\n";
torrc << "Log notice stderr\n";
// Append user-supplied extra Tor configuration if present. This lets
// operators on censored / DPI-filtered networks add Bridge lines,
// ClientTransportPlugin (obfs4), or a Socks5Proxy/HTTPSProxy upstream so
// Tor can reach the network when direct connections are blocked. The file
// is never overwritten by the wallet; only the auto-generated torrc is.
{
fs::path extraPath = dataPath / "torrc.extra";
if (fs::exists(extraPath)) {
std::ifstream extra(extraPath.string().c_str());
if (extra.is_open()) {
torrc << "\n# ---- appended from torrc.extra (user-managed) ----\n";
torrc << extra.rdbuf();
torrc << "\n";
printf("Tor: appended user configuration from %s\n", extraPath.string().c_str());
}
}
}
torrc.close();
if (hiddenServiceEnabled) {