Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
5ec9b3f
improve blockxor test, exact size in FileCopy
goatpig Sep 5, 2026
c39219b
txhints collision handler
goatpig Sep 5, 2026
4db8d93
Rotate xor key by file offset in FileCopy::xorMe
tomrobbo Sep 3, 2026
59ccf9f
Update comment
tomrobbo Sep 3, 2026
08a6acb
Don't throw in canLegacySerialize when the answer is false
tomrobbo Sep 4, 2026
36133a9
Merge pull request #786 from tomrobbo/patch-3
goatpig Sep 6, 2026
7649cc1
Merge pull request #789 from tomrobbo/patch-4
goatpig Sep 6, 2026
5ffd107
fix key share sequence when automating db with manual core
goatpig Sep 6, 2026
6432fd2
rescan test for DB_FULL, fix rescans for DB_FULL
goatpig Sep 7, 2026
667c1b6
cover & fix db rescan for DB_FULL, guard against mangled ZC in mempool
goatpig Sep 7, 2026
47ab5f5
hard check on tx hashes in zc parser pre resolver
goatpig Sep 13, 2026
526e334
improve build instructions
goatpig Sep 19, 2026
1fdc969
make windows build easier
goatpig Sep 20, 2026
a84578e
wstring path handling for WinAPI CreateProcessW
goatpig Sep 21, 2026
093b78a
implement windows side stdout hijacking
goatpig Sep 23, 2026
7a57dd8
fix BackupTests.BackupString_LegacyStatic on Windows
goatpig Sep 23, 2026
368594f
fix Windows autodb startup sequence
goatpig Sep 23, 2026
2256ef6
give armorydb time to start in automate operations
goatpig Sep 24, 2026
0dbfca1
fix socket write queue in Windows
goatpig Sep 25, 2026
f765f6a
fix missing accolade
goatpig Sep 25, 2026
9ae700c
improve test coverage around zeroconf loading
goatpig Sep 26, 2026
806765f
correctly evict mined zc during loadMempool, fix clear-mempool
goatpig Sep 27, 2026
6ac52cf
do not refeed drop ZCs to the parser
goatpig Sep 27, 2026
c32e065
fix autodb connection retry
goatpig Sep 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion ArmoryQt.py
Original file line number Diff line number Diff line change
Expand Up @@ -4797,7 +4797,6 @@ def completeCloseForReal(self):
else:
LOGINFO('BDM is safe for clean shutdown')

TheBDM.shutdown()
# Remove Temp Modules Directory if it exists:
if self.tempModulesDirName:
shutil.rmtree(self.tempModulesDirName)
Expand Down
17 changes: 0 additions & 17 deletions armoryengine/BDM.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,23 +181,6 @@ def setState(self, state):
def getState(self):
return self.bdmState

#############################################################################
@ActLikeASingletonBDM
def shutdown(self):
if self.bdmState == BDM_OFFLINE:
return

try:
if CLI_OPTIONS.bip150Used or CLI_OPTIONS.bip151Used:
Cpp.DisableBIP151()
self.bdv_.unregisterFromDB()
self.callback.shutdown()

cookie = self.getCookie()
self.bdv_.shutdown(cookie)
except:
pass

#############################################################################
@ActLikeASingletonBDM
def RegisterEventForSignal(self, func, signal):
Expand Down
23 changes: 11 additions & 12 deletions armoryengine/CppBridge.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
################################################################################
# #
# Copyright (C) 2019-2024, goatpig. #
# Copyright (C) 2019-2026, goatpig. #
# Distributed under the MIT license #
# See LICENSE-MIT or https://opensource.org/licenses/MIT #
# #
################################################################################

from __future__ import (absolute_import, division, annotations,
print_function, unicode_literals)
import os
import errno
import socket
from armoryengine.ArmoryUtils import LOGDEBUG, LOGERROR, LOGWARN, hash256, LOGINFO
from armoryengine.ArmoryUtils import LOGDEBUG, LOGERROR, LOGWARN, LOGINFO, \
OS_WINDOWS
from armoryengine.BinaryPacker import BinaryPacker, \
UINT32, UINT8, BINARY_CHUNK, VAR_INT
from struct import unpack
Expand Down Expand Up @@ -69,11 +68,12 @@ class BridgeSignerError(Exception):
################################################################################
def findCppBridgeBinary() -> str:
#search candidate locations in priority order, return the first that exists
bridgeBinName = "CppBridge.exe" if OS_WINDOWS else "CppBridge"
candidates = [
os.path.normpath(os.path.join(
os.path.dirname(os.path.abspath(__file__)), '..', 'CppBridge')),
os.path.join(os.getcwd(), 'CppBridge'),
os.path.join(os.getcwd(), 'build', 'CppBridge'),
os.path.dirname(os.path.abspath(__file__)), '..', bridgeBinName)),
os.path.join(os.getcwd(), bridgeBinName),
os.path.join(os.getcwd(), 'build', bridgeBinName),
]

for candidate in candidates:
Expand Down Expand Up @@ -228,9 +228,8 @@ def sendToBridgeBinary(self, payload, payloadId,
needsReply=True, callback: callable=None, cbArgs: list=[],
msgType = BRIDGE_CLIENT_HEADER):

#grab id from msg counter
if self.run == False:
return
return None

#serialize payload
bp = BinaryPacker()
Expand Down Expand Up @@ -426,7 +425,7 @@ def __init__(self, bridgeSocket):
#############################################################################
## commands ##
def connectToIp(self,
ip: str, port: str, callbackId: str,
ip: str, port: int, callbackId: str,
resultCallback: callable = None):
"""
Connect to remote DB by IP address (1-way auth).
Expand Down Expand Up @@ -1596,7 +1595,7 @@ def canLegacySerialize(self):
packet.signer.canLegacySerialize = None

fut = self.send(packet)
reply = fut.getVal()
reply = fut.getVal(nothrow=True)
return reply.success

################################################################################
Expand All @@ -1619,7 +1618,7 @@ def stop(self):
#############################################################################
def send(self, msg, needsReply=True, callback=None, cbArgs=[],
msgType=BRIDGE_CLIENT_HEADER):
self.bridgeSocket.sendToBridgeProto(msg,
return self.bridgeSocket.sendToBridgeProto(msg,
needsReply, callback, cbArgs, msgType)

#############################################################################
Expand Down
6 changes: 6 additions & 0 deletions armoryengine/WalletUtils.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,12 @@ def getWltForScrAddr(self, scrAddr):
return iterWlt
return None

def getWltForAddrStr(self, addrStr):
for _, iterWlt in self._walletMap.items():
if iterWlt.hasAddrString(addrStr):
return iterWlt
return None

def hasWallet(self, wltId: str):
return wltId in self._wltIdToDbId

Expand Down
6 changes: 4 additions & 2 deletions build_instructions/linux/linux.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,10 @@ The following dependencies you have to build from source.
```
git clone https://github.com/warmcat/libwebsockets
cd libwebsockets
mkdir build & cd build
cmake -DLWS_WITH_SSL=OFF ..
git checkout v4.5.8
mkdir build
cmake -DLWS_WITH_SSL=OFF -DLWS_WITHOUT_TESTAPPS=ON -B build
cd build
make
```

Expand Down
28 changes: 14 additions & 14 deletions build_instructions/windows/Windows_build_notes.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
# 1. Installing pre-requisites
* MSVC (Visual Studio Community): https://visualstudio.microsoft.com/downloads/
* MSYS2: ONLY DOWNLOAD the installer of MSYS2 from https://www.msys2.org/ and FOLLOW the setup instructions in the current document
* Python <=3.12 (3.13 breaks `pycapnp`)
* Git for Windows: https://gitforwindows.org/
* Python (tested on 3.14.7)

# 2. Installing build tools
As you will be exclusively using **MSYS2 MINGW64**, make sure you have opened that and not the UCRT/MSYS/CLANG terminal.
```
pacman -Syu
pacman -S autoconf automake libtoolize mingw-w64-x86_64-gcc mingw-w64-x86_64-libevent mingw-w64-x86_64-make mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja git
pacman -S autoconf automake libtool mingw-w64-x86_64-gcc mingw-w64-x86_64-libevent mingw-w64-x86_64-make mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja git
```
Next, create a symlink for the cmake binary in order to use the cmake command in MinGW64:

1. Open Windows Command Prompt and navigate to `<your MSYS2 installation path>\mingw64\bin`
2. Run `mklink make mingw32-make.exe`

# 3. Installing Python dependencies
```
Expand Down Expand Up @@ -45,14 +40,17 @@ It is strongly recommended to pick a single folder in which you will download al
git clone https://github.com/warmcat/libwebsockets.git
cd libwebsockets
git checkout v4.3.3
mkdir build & cd build
cmake -G Ninja -DLWS_WITH_SSL=OFF ..
mkdir build
cmake -G Ninja -DLWS_WITH_SSL=OFF -DLWS_WITHOUT_TESTAPPS=ON -DDISABLE_WERROR=ON -B build
cd build
ninja
```
3. [LMDB](https://github.com/LMDB/lmdb):
**Note**: Make sure you build off of the mdb.master branch, or else mmap will eat up all your free disk space!
```
git clone https://github.com/LMDB/lmdb.git
cb lmdb
git checkout mdb.master
cd libraries/liblmdb
make
```
Expand All @@ -61,17 +59,19 @@ It is strongly recommended to pick a single folder in which you will download al
```
git clone https://github.com/capnproto/capnproto.git
cd capnproto
git checkout v1.0.2
mkdir build & cd build
cmake -G Ninja ..
git checkout v1.5.0
mkdir build
cmake -G Ninja -B build
cd build
ninja
```
# 5. Building BitcoinArmory
```
git clone https://github.com/goatpig/BitcoinArmory
cd BitcoinArmory
mkdir build & cd build
cmake -G Ninja ..
mkdir build
cmake -G Ninja -B build
cd build
ninja
```
> [!WARNING]
Expand Down
2 changes: 1 addition & 1 deletion cppForSwig/AsyncClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ void BlockDataViewer::addPublicKey(const SecureBinaryData& pubkey, bool oneWay)

///////////////////////////////////////////////////////////////////////////////
std::shared_ptr<BlockDataViewer> BlockDataViewer::getNewBDV(
const std::string& addr, const std::string& port,
const std::string& addr, port_t port,
std::shared_ptr<NetworkPeers::ClientStore> peers, bool oneWayAuth,
std::shared_ptr<RemoteCallback> callbackPtr)
{
Expand Down
2 changes: 1 addition & 1 deletion cppForSwig/AsyncClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ namespace AsyncClient

//setup
static std::shared_ptr<BlockDataViewer> getNewBDV(
const std::string&, const std::string&,
const std::string&, Armory::Network::port_t,
std::shared_ptr<Armory::NetworkPeers::ClientStore>, bool,
std::shared_ptr<RemoteCallback>);

Expand Down
40 changes: 31 additions & 9 deletions cppForSwig/BDM_Server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -151,19 +151,26 @@ namespace {
case BdvRequest::Which::GET_TXS_BY_HASH:
{
auto db = bdv->getDB();
auto blockData = bdv->bdm()->blockchainData();
auto txHashList = request.getGetTxsByHash();
std::map<Types::TxKey, Tx> results;
std::set<Types::TxHash> possibleZcHashes;
for (auto txHash : txHashList) {
BinaryDataRef hashBdr(txHash.begin(), txHash.end());
try {
auto txKey = db->getDBKeyForHash(hashBdr);
if (!Types::isTxKeyValid(txKey)) {
possibleZcHashes.emplace(hashBdr);
continue;
}
auto tx = bdv->bdm()->blockchainData()->getTx(txKey);
results.emplace(txKey, std::move(tx));
} catch (const std::exception&) {
} catch (const TxHintCollision& collision) {
//could not get the tx, maybe it's a zc?
possibleZcHashes.emplace(hashBdr);
continue;
auto txKey = blockData->resolveTxHintCollision(collision);
if (!Types::isTxKeyValid(txKey)) {
possibleZcHashes.emplace(hashBdr);
}
}
}

Expand Down Expand Up @@ -375,7 +382,7 @@ namespace {
break;
}
clients->bdm()->signalStart(true);
break;
return nullptr;
}

case StaticRequest::Which::SHUTDOWN:
Expand Down Expand Up @@ -734,11 +741,11 @@ void BDV_Server_Object::init()
auto notifList = notifs.initNotifs(1);
auto notif = notifList[0];
if (ready) {
auto readyNotif = notif.initReady();
auto readyNotif = notif.initConnectionIsReady();
readyNotif.setHeight(blockchain().top()->getBlockHeight());
readyNotif.setBranchHeight(UINT32_MAX);
} else {
notif.setRegistered();
notif.setRegistrationDone();
}

//we expect this message to be smaller than our scratchpad
Expand Down Expand Up @@ -1838,9 +1845,24 @@ void Clients::p2pBroadcast(Types::BdvId bdvId, std::vector<BinaryDataRef>& rawZC
Tx tx(rawZcRef);
auto hash = tx.getThisHash();

auto dbKey = db->getDBKeyForHash(hash);
if (Types::isTxKeyValid(dbKey)) {
//notify the bdv of the error
Types::TxKey dbTxKey;
try {
//do we know this txhash?
dbTxKey = db->getDBKeyForHash(hash);
if (Types::isTxKeyValid(dbTxKey)) {
//we have a txkey for this hash, do a hard check against
//the actual tx data
std::set<Types::TxKey> hints{dbTxKey};
TxHintCollision collision{hash, hints};
dbTxKey = bdm_->blockchainData()->resolveTxHintCollision(collision);
}
} catch (const TxHintCollision& collision){
//multiple hints, do a hard check too
dbTxKey = bdm_->blockchainData()->resolveTxHintCollision(collision);
}

if (Types::isTxKeyValid(dbTxKey)) {
//this hash is mined, notify the bdv of the error
auto notifPacket = std::make_shared<BDV_Notification_Packet>();
notifPacket->bdvPtr = BDVs_.get(bdvId);

Expand Down
6 changes: 2 additions & 4 deletions cppForSwig/BlockchainDatabase/Blockchain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,13 +123,11 @@ HeaderPtr Blockchain::getHeaderById(Types::BlockId id) const
{
std::unique_lock<std::mutex> lock(mu_);
if (id > highestBlockID_.load(std::memory_order_relaxed)) {
LOGERR << "block id " << id << " is too big";
throw std::range_error("block id overflow");
throw std::range_error(std::format("block id overflow: {}", id));
}
auto header = headersById_[id];
if (header == nullptr) {
LOGERR << "cannot find block for id: " << id;
throw std::range_error("Cannot find block by id");
throw std::range_error(std::format("cannot find block {} by id", id));
}
return header;
}
Expand Down
33 changes: 30 additions & 3 deletions cppForSwig/BlockchainDatabase/BlockchainData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,14 +123,41 @@ Hash32 BlockchainData::getTxHashForTxKey(const Types::TxKey& txKey) const
return Hash32{tx.getThisHash()};
}

Types::TxKey BlockchainData::resolveTxHintCollision(
const TxHintCollision& collision)
{
for (const auto& txKey : collision.getCandidates()) {
if (!isTxKeyOnMainBranch(txKey)) {
continue;
}
try {
auto txHash = getTxHashForTxKey(txKey);
if (txHash == collision.getTxHash()) {
return txKey;
}
} catch (const BlockchainDataException& e) {
//ignore mangled data
LOGWARN << "[getTxHashForTxKey] " << e.what();
continue;
} catch (const std::range_error& e) {
LOGWARN << "[getTxHashForTxKey] " << e.what();
}
}
return Types::INVALID_TX_KEY;
}

bool BlockchainData::isTxKeyOnMainBranch(const Types::TxKey& txKey) const
{
auto blockID = Types::getBlockIDFromTxKey(txKey);
auto header = blockchain_->getHeaderById(blockID);
if (header == nullptr) {
try {
auto header = blockchain_->getHeaderById(blockID);
if (header == nullptr) {
return false;
}
return header->isMainBranch();
} catch (const std::range_error&) {
return false;
}
return header->isMainBranch();
}

////////
Expand Down
2 changes: 2 additions & 0 deletions cppForSwig/BlockchainDatabase/BlockchainData.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <Utils/Types.h>

class Tx;
class TxHintCollision;

namespace Armory
{
Expand Down Expand Up @@ -42,6 +43,7 @@ namespace Armory

Hash32 getTxHashForTxKey(const Types::TxKey&) const;
bool isTxKeyOnMainBranch(const Types::TxKey&) const;
Types::TxKey resolveTxHintCollision(const TxHintCollision&);

std::pair<std::vector<uint8_t>, size_t> getRawBlockForId(
Types::BlockId) const;
Expand Down
Loading