Compare commits

..

89 Commits

Author SHA1 Message Date
merge-script
4d7d5f6b79 Merge bitcoin/bitcoin#34229: [30.2] Backports & Final
04a996b1a7 doc: update manual pages for v30.2 (fanquake)
ed355b8f57 build: bump version to v30.2 (fanquake)
6c98d68be1 doc: update release notes for v30.2 (fanquake)
6d86b32e78 guix: Fix `osslsigncode` tests (Hennadii Stepanov)
1dae0027cd wallet: test: Failed migration cleanup (David Gumberg)
9e59047a7e test: migration, avoid backup name mismatch in default_wallet_failure (furszy)

Pull request description:

  Backports:
  * #34221
  * #34226
  * #34227

ACKs for top commit:
  hebasto:
    ACK 04a996b1a7, I have reviewed the code and it looks OK.
  willcl-ark:
    ACK 04a996b1a7
  marcofleon:
    ACK 04a996b1a7

Tree-SHA512: 3389b9b629dcb920186b383353fd386bb757967d224e0267501b5e2083dc1e6cba051df6ef646de05c0e58fd43c9f549b9175eefb77fed1fe9ab7d1648b2d9e7
2026-01-09 14:04:54 +00:00
fanquake
04a996b1a7 doc: update manual pages for v30.2 2026-01-09 11:23:01 +00:00
fanquake
ed355b8f57 build: bump version to v30.2 2026-01-09 11:17:00 +00:00
fanquake
6c98d68be1 doc: update release notes for v30.2 2026-01-09 11:16:32 +00:00
Hennadii Stepanov
6d86b32e78 guix: Fix osslsigncode tests
Github-Pull: #34227
Rebased-From: 194114daf3
2026-01-09 11:15:58 +00:00
David Gumberg
1dae0027cd wallet: test: Failed migration cleanup
Refactor a common way to perform the failed migration test that exists
for default wallets, and add relative-path wallets and absolute-path
wallets.

Github-Pull: #34226
Rebased-From: eeaf28dbe0
2026-01-09 10:11:19 +00:00
furszy
9e59047a7e test: migration, avoid backup name mismatch in default_wallet_failure
The test calls migrate_and_get_rpc(), which sets mock time internally.
The caller caches a mock time value and later relies on it to predict the
backup filename, so setting the mock time again could cause a naming
mismatch.

Fix this by calling the migration RPC directly. Since the test expects the
migration to fail, migrate_and_get_rpc() is unnecessary here.

Github-Pull: #34221
Rebased-From: cbf0bd35bb
2026-01-08 10:21:49 +00:00
merge-script
abb6ae2ec5 Merge bitcoin/bitcoin#34209: [30.x]: Final backports & 30.2rc1
483d158f53 doc: update manual pages for v30.2rc1 (fanquake)
747a863f5b build: bump version to v30.2rc1 (fanquake)
cc3cdbe921 doc: update release notes for v30.2rc1 (fanquake)
c4082a45e6 wallettool: do not use fs::remove_all in createfromdump cleanup (Ava Chow)
185ca0e391 test: coverage for migration failure when last sync is beyond prune height (furszy)
bc71372c0e wallet: migration, fix watch-only and solvables wallets names (furszy)
bef4b1fdee wallet: improve post-migration logging (furszy)
ac940ac2ca test: restorewallet, coverage for existing dirs, unnamed wallet and prune failure (furszy)
8e5c02a77f test: add coverage for unnamed wallet migration failure (furszy)
ac4d0956cc wallet: fix unnamed wallet migration failure (furszy)
454ac8e7db wallet: RestoreWallet failure, erase only what was created (furszy)

Pull request description:

  Backports:
  * #34156
  * #34215

ACKs for top commit:
  darosior:
    ACK 483d158f53
  willcl-ark:
    tACK 483d158f53
  janb84:
    ACK 483d158f53
  w0xlt:
    ACK 483d158f53
  marcofleon:
    lgtm ACK 483d158f53

Tree-SHA512: b43485a8745a663d3a031113a6a266af699b3e70b77f72a0e69ea1986adcaa0ee0d4ce51aacd0fde91611cb4bdba9b6be52e5dca59ff8b12135715b8b1ed5ef2
2026-01-07 15:44:56 +00:00
fanquake
483d158f53 doc: update manual pages for v30.2rc1 2026-01-07 14:34:29 +00:00
fanquake
747a863f5b build: bump version to v30.2rc1 2026-01-07 14:34:28 +00:00
fanquake
cc3cdbe921 doc: update release notes for v30.2rc1 2026-01-07 14:34:24 +00:00
Ava Chow
c4082a45e6 wallettool: do not use fs::remove_all in createfromdump cleanup
Github-Pull: #34215
Rebased-From: f78f6f1dc8
2026-01-07 14:33:39 +00:00
furszy
185ca0e391 test: coverage for migration failure when last sync is beyond prune height
Github-Pull: #34156
Rebased-From: b7c34d08dd
2026-01-07 11:20:07 +00:00
furszy
bc71372c0e wallet: migration, fix watch-only and solvables wallets names
Because the default wallet has no name, the watch-only and solvables
wallets created during migration end up having no name either.

This fixes it by applying the same prefix name we use for the backup
file for an unnamed default wallet.

Before: watch-only wallet named "_watchonly"
After:  watch-only wallet named "default_wallet_watchonly"

Github-Pull: #34156
Rebased-From: 82caa8193a
2026-01-07 11:20:04 +00:00
furszy
bef4b1fdee wallet: improve post-migration logging
Right now, after migration the last message users see is "migration completed",
but the migration isn't actually finished yet. We still need to load the new wallets
to ensure consistency, and if that fails, the migration will be rolled back. This
can be confusing for users.

This change logs the post-migration loading step and if a wallet fails to load and
the migration will be rolled back.

Github-Pull: #34156
Rebased-From: d70b159c42
2026-01-07 11:20:00 +00:00
furszy
ac940ac2ca test: restorewallet, coverage for existing dirs, unnamed wallet and prune failure
The first test verifies that restoring into an existing empty directory
or a directory with no .dat db files succeeds, while restoring into a
dir with a .dat file fails.

The second test covers restoring into the default unnamed wallet
(wallet.dat), which also implicitly exercises the recovery path used
after a failed migration.

The third test covers failure during restore on a prune node. When
the wallet last sync was beyond the pruning height.

Github-Pull: #34156
Rebased-From: f011e0f068
2026-01-07 11:19:57 +00:00
furszy
8e5c02a77f test: add coverage for unnamed wallet migration failure
Verifies that a failed migration of the unnamed (default) wallet
does not erase the main /wallets/ directory, and also that the
backup file exists.

Github-Pull: #34156
Rebased-From: 36093bde63
2026-01-07 11:19:39 +00:00
furszy
ac4d0956cc wallet: fix unnamed wallet migration failure
When migrating any legacy unnamed wallet, a failed migration would
cause the cleanup logic to remove its parent directory. Since this
type of legacy wallet lives directly in the main '/wallets/' folder,
this resulted in unintentionally erasing all wallets, including the
backup file.

To be fully safe, we will no longer call `fs::remove_all`. Instead,
we only erase the individual db files we have created, leaving
everything else intact. The created wallets parent directories are
erased only if they are empty.
As part of this last change, `RestoreWallet` was modified to allow
an existing directory as the destination, since we no longer remove
the original wallet directory (we only remove the files we created
inside it). This also fixes the restore of top-level default wallets
during failures, which were failing due to the directory existence
check that always returns true for the /wallets/ directory.

This bug started after:
f6ee59b6e2
Previously, the `fs::copy_file` call was failing for top-level wallets,
which prevented the `fs::remove_all` call from being reached.

Github-Pull: #34156
Rebased-From: f4c7e28e80
2026-01-07 11:19:35 +00:00
furszy
454ac8e7db wallet: RestoreWallet failure, erase only what was created
Track what RestoreWallet creates so only those files and directories
are removed during a failure and nothing else. Preexisting paths
must be left untouched.

Note:
Using fs::remove_all() instead of fs::remove() in RestoreWallet does
not cause any problems currently, but the change is necessary for the
next commit which extends RestoreWallet to work with existing directories,
which may contain files that must not be deleted.

Github-Pull: #34156
Rebased-From: 4ed0693a3f
2026-01-07 11:18:38 +00:00
merge-script
e9f73b8149 Merge bitcoin/bitcoin#34192: [30.x] Backports
f22122bc27 doc: update release notes for v30.x (fanquake)
7568bc3ab0 test: Avoid hard time.sleep(1) in feature_init.py (MarcoFalke)
c065bcd2d7 init: Signal m_tip_block_cv on Ctrl-C (Ryan Ofsky)
6983c7d769 test: Test SIGTERM handling during waitforblockheight call (Ryan Ofsky)
8769c718f4 doc: Update OpenBSD Build Guide (Hennadii Stepanov)
ed0774bd08 doc: update copyright year (fanquake)
f620dde411 fuzz: doc: remove any mention to address_deserialize_v2 (brunoerg)
b734c4026b guix: reduce allowed exported symbols (fanquake)
7ea855fd55 build: Update minimum required Boost version (Hennadii Stepanov)

Pull request description:

  Backports:
  * #33511
  * #33950
  * #34091
  * #34107
  * #34137
  * #34174
  * #34182

ACKs for top commit:
  willcl-ark:
    ACK f22122bc27
  marcofleon:
    ACK f22122bc27

Tree-SHA512: a4486d331ce8fef07e611188098a965348cdfb081b81e5a2c700e119b1cf3c28cddbeca883ab512a510f6aaa5fc14bf896f70823fa63afcdd0762d9fbda0118b
2026-01-05 17:27:19 +00:00
fanquake
f22122bc27 doc: update release notes for v30.x 2026-01-05 12:09:33 +00:00
MarcoFalke
7568bc3ab0 test: Avoid hard time.sleep(1) in feature_init.py
Github-Pull: #34137
Rebased-From: fa727e3ec9
2026-01-05 12:09:02 +00:00
Ryan Ofsky
c065bcd2d7 init: Signal m_tip_block_cv on Ctrl-C
Signal m_tip_block_cv when Ctrl-C is pressed or SIGTERM is received, the same
way it is currently signalled when the `stop` RPC is called. This lets RPC
calls like `waitforblockheight` and IPC calls like `waitTipChanged` be
interrupted, instead of waiting for their original timeouts and delaying
shutdown.

Historical notes:

- The behavior where `stop` RPC signals `m_tip_block_cv`, but CTRL-C does not,
  has been around since the condition variable was introduced in #30409
  (7eccdaf160).
- The signaling was later moved without changing behavior in #30967
  (5ca28ef28b). This commit moves it again to
  the Interrupt() function, which is probably the place it should have been
  added initially, so it works for Ctrl-C shutdowns as well as `stop`
  shutdowns.
- A Qt shutdown bug calling wait methods was fixed previously in #18452
  (da73f1513a), and this change updates that
  fix to avoid the hang happening again in Qt.

Github-Pull: #33511
Rebased-From: c25a5e670b
2026-01-05 12:07:02 +00:00
Ryan Ofsky
6983c7d769 test: Test SIGTERM handling during waitforblockheight call
Currently when CTRL-C is pressed and there is an active `waitforblockheight`,
or `waitforblock`, or `waitfornewblock` RPC call, or a mining interface
`waitTipChanged` IPC call with a long timeout, the node will not shut down
right away, and will wait for the timeout to be reached before exiting.

This behavior is not ideal and only happens when the node is stopped with
CTRL-C or SIGTERM. When the node is stopped with `bitcoin-cli stop`, the wait
calls are interrupted and the node does shut down right away.

The next commit improves node behavior. This commit just adds test coverage to
simplify the next commit and clarify the change in behavior there.

Github-Pull: #33511
Rebased-From: 6a29f79006
2026-01-05 12:06:57 +00:00
Hennadii Stepanov
8769c718f4 doc: Update OpenBSD Build Guide
Github-Pull: #34182
Rebased-From: 84d8c52662
2026-01-05 11:58:47 +00:00
fanquake
ed0774bd08 doc: update copyright year
Github-Pull: #34174
Rebased-From: b23b901363
2026-01-05 11:41:23 +00:00
brunoerg
f620dde411 fuzz: doc: remove any mention to address_deserialize_v2
Github-Pull: #34091
Rebased-From: caf4843a59
2025-12-31 12:30:48 +00:00
fanquake
b734c4026b guix: reduce allowed exported symbols
Github-Pull: #33950
Rebased-From: 7b90b4f5bb
2025-12-31 12:27:29 +00:00
Hennadii Stepanov
7ea855fd55 build: Update minimum required Boost version
Building with Boost 1.73.0 is broken.

Github-Pull: #34107
Rebased-From: f480c1e717
2025-12-31 12:22:49 +00:00
merge-script
dd47caee82 Merge bitcoin/bitcoin#34092: [30.x] Finalise v30.1
2a21824b11 doc: update manual pages for v30.1 (fanquake)
57264431ff build: bump version to v30.1 (fanquake)
26294d627e doc: update release notes for v30.1 (fanquake)

Pull request description:

  Finalise `v30.1`.

ACKs for top commit:
  waketraindev:
    ACK 2a21824b11
  hebasto:
    ACK 2a21824b11, I have reviewed the code and it looks OK.
  brunoerg:
    code review ACK 2a21824b11
  janb84:
    ACK 2a21824b11
  w0xlt:
    Code Review ACK 2a21824b11

Tree-SHA512: 622d289db49c793fcb7b415d5113495eb824dc87780b082ccd5c43e37d116dca5a606340979a77cad1bfdbfac2c5fd5f18262746ed0e492d9e87bb0d802386ee
2025-12-18 15:54:33 +00:00
fanquake
2a21824b11 doc: update manual pages for v30.1 2025-12-17 15:40:17 +00:00
fanquake
57264431ff build: bump version to v30.1 2025-12-17 15:29:05 +00:00
fanquake
26294d627e doc: update release notes for v30.1 2025-12-17 15:27:46 +00:00
merge-script
b1499ddf8b Merge bitcoin/bitcoin#33997: [30.x] Backports & 30.1rc1
d59ebac718 doc: update manual pages for v30.1rc1 (fanquake)
b83d4f7c57 build: bump version to v30.1rc1 (fanquake)
b26c93a9df doc: update release notes for v30.1rc1 (fanquake)
18f3ada037 Remove unreliable seed from chainparams.cpp, and the associated README (SatsAndSports)
187e3b89b5 [test] wallet send 3 generation TRUC (glozow)
ab58b2c0f8 [wallet] never try to spend from unconfirmed TRUC that already has ancestors (glozow)
d872277db5 contrib: fix manpage generation (fanquake)
2256f8965e contrib: rename gen-sdk to gen-sdk.py (fanquake)
5408e85145 macdeploy: disable compression in macOS gen-sdk script (fanquake)
338570de5c contrib: more selectively pick files for macOS SDK (fanquake)

Pull request description:

  Backports:
  * #32009
  * #33528
  * #33723
  * #33996

  Prior `30.x` backports in #33609.

ACKs for top commit:
  hebasto:
    re-ACK d59ebac718.
  marcofleon:
    ACK d59ebac718

Tree-SHA512: 41656dc7c2e7a023325b288ff38cf95877a3c223ed5c6defc1c0939b725fb5423455583881f283aff14821bc4fea5a4d4d0ac458cab6450eb9b1bc7730fc027c
2025-12-05 14:44:42 +00:00
fanquake
d59ebac718 doc: update manual pages for v30.1rc1 2025-12-05 09:46:37 +00:00
fanquake
b83d4f7c57 build: bump version to v30.1rc1 2025-12-05 09:46:37 +00:00
fanquake
b26c93a9df doc: update release notes for v30.1rc1 2025-12-05 09:46:37 +00:00
SatsAndSports
18f3ada037 Remove unreliable seed from chainparams.cpp, and the associated README
Github-Pull: #33723
Rebased-From: b0c706795c
2025-12-05 09:46:37 +00:00
glozow
187e3b89b5 [test] wallet send 3 generation TRUC
Github-Pull: #33528
Rebased-From: dcd42d6d8f
2025-12-05 09:46:37 +00:00
glozow
ab58b2c0f8 [wallet] never try to spend from unconfirmed TRUC that already has ancestors
Github-Pull: #33528
Rebased-From: e753fadfd0
2025-12-05 09:46:32 +00:00
fanquake
d872277db5 contrib: fix manpage generation
0972f55040 from #33229 broke manpage
generation, because the assumption that the last word in the line
containing the version number, was the version number, no-longer holds
for some binaries. i.e bitcoind.

Github-Pull: #33996
Rebased-From: e9536faaee
2025-12-03 11:20:36 +00:00
fanquake
2256f8965e contrib: rename gen-sdk to gen-sdk.py
This puts it in scope for the Python linters.

Github-Pull: #32009
Rebased-From: 3e01b5d0e7
2025-12-02 12:14:53 +00:00
fanquake
5408e85145 macdeploy: disable compression in macOS gen-sdk script
Starting with Python 3.11, Pythons gzip might delegate to zlib.
Depending on the OS, i.e Ubuntu vs Fedora, the underlying zlib
implementation might differ, resulting in different output.

For now, or until a better solution exists, disable compression. This
results in the SDK increasing in size to ~157mb. Which is not
unreasonable, to regain determinism (and would be significantly worse
without the previous commit).

See: https://docs.python.org/3/library/gzip.html#gzip.compress

Co-authored-by: stickies-v <stickies-v@protonmail.com>

Github-Pull: #32009
Rebased-From: c1213a35ab
2025-12-02 12:14:31 +00:00
fanquake
338570de5c contrib: more selectively pick files for macOS SDK
Only include what we really need. Skip 100s of mb of manpages,
swiftmodules, modulemaps.
Note that System/Library is only needed for the Qt build.

Github-Pull: #32009
Rebased-From: a33d034545
2025-12-02 12:13:59 +00:00
merge-script
72675b8f55 Merge bitcoin/bitcoin#33609: [30.x] Backports
b2cb203af0 doc: update release notes for 30.x (fanquake)
39d53dd8bf interfaces: add interruptWait method (ismaelsadeeq)
577ddf6f5d depends: Add patch for Windows11Style plugin (Hennadii Stepanov)
a0a2b07701 scripted-diff: Remove obsolete comment (Hennadii Stepanov)
e2c71c4fca doc: Correct `pkgin` command usage on NetBSD (Hennadii Stepanov)
c0d851e6c6 qt: Modernize custom filtering (Hennadii Stepanov)
34576c4574 ci: fix configure docker action inputs (will)
8f8c7cf7fa ci: fix lint docker caching (will)
049bf100f1 qt: add createwallet, createwalletdescriptor, and migratewallet to history filter (WakeTrainDev)
3afd5a9729 miner: fix empty mempool case for waitNext() (Sjors Provoost)
23ac752d09 bitcoin: Make wrapper not require -m (Ryan Ofsky)
cd7937ce2d test: add tool_bitcoin to test bitcoin wrapper behavior (Ryan Ofsky)
42d4847fdf init: add exe name to bitcoind, bitcoin-node -version output to be able to distinguish these in tests (Ryan Ofsky)
96110f8846 [doc] correct topology requirements in submitpackage helptext (glozow)
729e4c2abd multiprocess: align our logging with libmultiprocess's (Cory Fields)
f8db6f6ce7 multiprocess: update multiprocess EventLoop construction to use options (Cory Fields)
5e389959b9 test: change log rate limit version gate from 299900 to 290100 (Eugene Siegel)
c48d3a6ad8 ci: expose all ACTIONS_* vars (willcl-ark)
73ed57a35e depends: Use $(package)_file_name when downloading from the fallback (Ava Chow)
72d1141bd8 ci: Use native platform for win-cross task (MarcoFalke)
c9fa661131 ci: Properly include $FILE_ENV in DEPENDS_HASH (Ava Chow)

Pull request description:

  Backports:
  * #33229
  * #33508
  * #33517
  * #33558
  * #33566
  * #33580
  * #33581
  * #33612
  * #33630
  * #33676
  * #33744
  * #33826
  * #33827
  * #33906
  * https://github.com/bitcoin-core/gui/pull/899
  * https://github.com/bitcoin-core/gui/pull/901

ACKs for top commit:
  willcl-ark:
    ACK b2cb203af0
  marcofleon:
    Lgtm, ACK b2cb203af0

Tree-SHA512: cde3584c7ad9bc6034a54527c1f5e84f0c5e314520109342a5cbb3c6c703286e2bed26f2b24fbf0ba7bcc9cbc140d79921e9d63def6b5c4782fd2e208f51948a
2025-12-02 10:15:15 +00:00
fanquake
b2cb203af0 doc: update release notes for 30.x 2025-12-01 14:59:27 +00:00
ismaelsadeeq
39d53dd8bf interfaces: add interruptWait method
- This method can be used to cancel a running
  waitNext().

- This commit also adds a test case for interruptWait method

Github-Pull: #33676
Rebased-From: dcb56fd4cb
2025-11-26 16:49:06 +00:00
Hennadii Stepanov
577ddf6f5d depends: Add patch for Windows11Style plugin
Github-Pull: #33906
Rebased-From: 8558902e57
2025-11-25 17:12:56 +00:00
Hennadii Stepanov
a0a2b07701 scripted-diff: Remove obsolete comment
The removed comment become obsolete after bitcoin/bitcoin#32697 and
bitcoin/bitcoin#32881.

-BEGIN VERIFY SCRIPT-

sed -i "s/ Some tests are disabled if Python 3 is not available.//g" \
$( git grep -l " Some tests are disabled if Python 3 is not available." ./doc/ )

-END VERIFY SCRIPT-

Github-Pull: #33826
Rebased-From: 36724205fc
2025-11-10 11:00:58 +00:00
Hennadii Stepanov
e2c71c4fca doc: Correct pkgin command usage on NetBSD
Github-Pull: #33827
Rebased-From: 0698c6b494
2025-11-10 10:32:55 +00:00
Hennadii Stepanov
c0d851e6c6 qt: Modernize custom filtering
In `QSortFilterProxyModel`, `invalidateFilter()` is scheduled for
deprecation in Qt 6.13.
`beginFilterChange()` was introduced in Qt 6.9.
`endFilterChange()` was introduced in Qt 6.10.

Github-Pull: gui#899
Rebased-From: e15e8cbada
2025-11-05 09:52:41 +00:00
will
34576c4574 ci: fix configure docker action inputs
The options used were wrong in two ways: firstly they were not enforced
as a "choice" (i.e. invalid input valudes could be provided without
error) and one of the options was listed as `gh` when we passed it as
`gha` from ci.yml.

"Fix" this by removing the choice altogether but sanity-testing the
input value against an expected list using a GHA "warning" to notify of
unknown inputs.

Github-Pull: #33744
Rebased-From: 7632e0ba31
2025-11-04 11:42:20 +00:00
will
8f8c7cf7fa ci: fix lint docker caching
Fixes: 33735

Correct runner type selection for the lint job.

This was erroneously left-out during refactor of the runner selection
mechanism in #33302 causing the lint job to run on GH hosts (and
therefore not be able to acces local cirrus caches).

Github-Pull: #33744
Rebased-From: 0b3b8a3be1
2025-11-04 11:42:15 +00:00
WakeTrainDev
049bf100f1 qt: add createwallet, createwalletdescriptor, and migratewallet to history filter
Github-Pull: #gui901
Rebased-From: 4e352efa2c
2025-11-04 11:40:15 +00:00
Sjors Provoost
3afd5a9729 miner: fix empty mempool case for waitNext()
Block template fees are calculated by looping over new_tmpl->vTxFees
and return (early) once the fee_threshold is exceeded.

This left an edge case when the mempool is empty, which this commit
fixes and adds a test for. It does so by using std::accumulate instead
of manual loops.

Also update interface_ipc.py to account for the new behavior.

Co-authored-by: Raimo33 <claudio.raimondi@protonmail.com>

Github-Pull: #33566
Rebased-From: 8f7673257a
2025-10-23 15:36:56 +02:00
Ryan Ofsky
23ac752d09 bitcoin: Make wrapper not require -m
Choose the right binary by default if an IPC option is specified

Github-Pull: #33229
Rebased-From: 453b0fa286
2025-10-17 15:43:25 +01:00
Ryan Ofsky
cd7937ce2d test: add tool_bitcoin to test bitcoin wrapper behavior
Github-Pull: #33229
Rebased-From: 29e836fae6
2025-10-17 15:43:22 +01:00
Ryan Ofsky
42d4847fdf init: add exe name to bitcoind, bitcoin-node -version output to be able to distinguish these in tests
Github-Pull: #33229
Rebased-From: 0972f55040
2025-10-17 15:43:18 +01:00
glozow
96110f8846 [doc] correct topology requirements in submitpackage helptext
Github-Pull: #33630
Rebased-From: 3d22282564
2025-10-17 15:17:37 +01:00
Cory Fields
729e4c2abd multiprocess: align our logging with libmultiprocess's
Without this change, logging (even if unused) may account for a
substantial portion of bitcoin-node's and/or client's runtime cpu usage, due
to libmultiprocess's expensive message serialization.

This (along with some recent upstream changes) avoids the overhead by opting
out of log handling for messages that we're not interested in.

Info, Warning, and Error are logged unconditionally to match our behavior
elsewhere. See BCLog::Logger::GetCategoryLogLevel .

Github-Pull: #33517
Rebased-From: 0626b90f50
2025-10-16 09:55:12 +01:00
Cory Fields
f8db6f6ce7 multiprocess: update multiprocess EventLoop construction to use options
This uses the constructors recently added upstream.

Github-Pull: #33517
Rebased-From: 9d068225ee
2025-10-16 09:55:09 +01:00
Eugene Siegel
5e389959b9 test: change log rate limit version gate from 299900 to 290100
Github-Pull: #33612
Rebased-From: 7b544341c0
2025-10-16 09:53:44 +01:00
willcl-ark
c48d3a6ad8 ci: expose all ACTIONS_* vars
When using `docker buildx build` in conjunction with the `gha` backend
cache type, it's important to specify the URL and TOKEN needed to
authenticate.

On Cirrus runners this is working with only `ACTIONS_CACHE_URL` and
`ACTIONS_RUNTIME_TOKEN`, but this is not enough for the GitHub backend.

Fix this by exporting all `ACTIONS_*` variables.

This fixes cache restore/save on forks or where GH-hosted runners are
being used.

Github-Pull: #33508
Rebased-From: bc706955d7
2025-10-16 09:53:44 +01:00
Ava Chow
73ed57a35e depends: Use $(package)_file_name when downloading from the fallback
Github-Pull: #33580
Rebased-From: 671b774d1b
2025-10-16 09:53:44 +01:00
MarcoFalke
72d1141bd8 ci: Use native platform for win-cross task
Github-Pull: #33558
Rebased-From: fa6fd16f36
2025-10-16 09:53:44 +01:00
Ava Chow
c9fa661131 ci: Properly include $FILE_ENV in DEPENDS_HASH
$FILE_ENV has a full relative path already, prepending with ci/test/
results in a non-existent path which means that DEPENDS_HASH was not
actually committing to the test's environment file.

Github-Pull: #33581
Rebased-From: ceeb53adcd
2025-10-16 09:53:44 +01:00
merge-script
a14e7b9dee Merge bitcoin/bitcoin#33519: Update libmultiprocess subtree in 30.x branch
abcd4c4ff9 Squashed 'src/ipc/libmultiprocess/' changes from 13424cf2ecc1..a4f929696490 (Ryan Ofsky)

Pull request description:

  Includes:

  - https://github.com/bitcoin-core/libmultiprocess/pull/207
  - https://github.com/bitcoin-core/libmultiprocess/pull/208
  - https://github.com/bitcoin-core/libmultiprocess/pull/211
  - https://github.com/bitcoin-core/libmultiprocess/pull/201
  - https://github.com/bitcoin-core/libmultiprocess/pull/213
  - https://github.com/bitcoin-core/libmultiprocess/pull/214
  - https://github.com/bitcoin-core/libmultiprocess/pull/221
  - https://github.com/bitcoin-core/libmultiprocess/pull/220
  - https://github.com/bitcoin-core/libmultiprocess/pull/222
  - https://github.com/bitcoin-core/libmultiprocess/pull/224

  Corresponding to #32641 and #33518 in master.

  The changes can be verified by running `test/lint/git-subtree-check.sh src/ipc/libmultiprocess` as described in [developer notes](https://github.com/bitcoin/bitcoin/blob/master/doc/developer-notes.md#subtrees) and [lint instructions](https://github.com/bitcoin/bitcoin/tree/master/test/lint#git-subtree-checksh)

  They can also be verified by checking `src/ipc/libmultiprocess/` contents are the same in master. (See also #33439).

ACKs for top commit:
  Sjors:
    ACK ae63cc4bf2
  theuni:
    ACK ae63cc4bf2 . Verified that it's the same as what's in master.

Tree-SHA512: 6c9462d5fb9201ee8ace900d7d02bfb6d0c7aa3d2f22475dc55e55e0239e2d20ed69f572c0df233da7910375e9d8ccaf3e84bf949ae92df27b88f16adb26dd7a
2025-10-16 09:53:04 +01:00
merge-script
d0f6d9953a Merge bitcoin/bitcoin#33559: [30.x] Finalise v30.0
d615eb6998 doc: re-import release notes for v30.0 (fanquake)
f725754615 doc: update manual pages for v30.0 (fanquake)
d7c89ba74e build: bump version to v30.0 (fanquake)

Pull request description:

  Finalise `v30.0`.
  Imports the release notes from https://github.com/bitcoin-core/bitcoin-devwiki/wiki/v30.0-Release-Notes-Draft.

ACKs for top commit:
  instagibbs:
    ACK d615eb6998
  achow101:
    ACK d615eb6998
  janb84:
    ACK d615eb6998
  darosior:
    ACK d615eb6998

Tree-SHA512: 82c2879ad09a4f6ec02f63411bcb8fa4900b8af2cbc2bc588fc0f06fa622253c3ef7e7b4c642f5e7239b088acd17f97b2cca73d15513b75297c2872d3146c673
2025-10-09 16:04:52 -04:00
fanquake
d615eb6998 doc: re-import release notes for v30.0
Taken from
https://github.com/bitcoin-core/bitcoin-devwiki/wiki/v30.0-Release-Notes-Draft.
2025-10-08 10:22:50 +01:00
Ryan Ofsky
ae63cc4bf2 Merge commit 'abcd4c4ff928a7af280ca271f8a939367fc4e09a' into pr/subtree-6-v30 2025-10-07 10:12:08 -04:00
Ryan Ofsky
abcd4c4ff9 Squashed 'src/ipc/libmultiprocess/' changes from 13424cf2ecc1..a4f929696490
a4f929696490 Merge bitcoin-core/libmultiprocess#224: doc: fix typos
f4344ae87da0 Merge bitcoin-core/libmultiprocess#222: test, ci: Fix threadsanitizer errors in mptest
1434642b3804 doc: fix typos
73d22ba2e930 test: Fix tsan race in thread busy test
b74e1bba014d ci: Use tsan-instrumented cap'n proto in sanitizers job
c332774409ad test: Fix failing exception check in new thread busy test
ca3c05d56709 test: Use KJ_LOG instead of std::cout for logging
7eb1da120ab6 ci: Use tsan-instrumented libcxx in sanitizers job
ec86e4336e98 Merge bitcoin-core/libmultiprocess#220: Add log levels and advertise them to users via logging callback
515ce93ad349 Logging: Pass LogData struct to logging callback
213574ccc43d Logging: reclassify remaining log messages
e4de0412b430 Logging: Break out expensive log messages and classify them as Trace
408874a78fdc Logging: Use new logging macros
67b092d835cd Logging: Disable logging if messsage level is less than the requested level
d0a1ba7ebf21 Logging: add log levels to mirror Core's
463a8296d188 Logging: Disable moving or copying Logger
83a2e10c0b03 Logging: Add an EventLoop constructor to allow for user-specified log options
58cf47a7fc8c Merge bitcoin-core/libmultiprocess#221: test default PassField impl handles output parameters
db03a663f514 Merge bitcoin-core/libmultiprocess#214: Fix crash on simultaneous IPC calls using the same thread
afcc40b0f1e8 Merge bitcoin-core/libmultiprocess#213: util+doc: Clearer errors when attempting to run examples + polished docs
6db669628387 test In|Out parameter
29cf2ada75ea test default PassField impl handles output parameters
1238170f68e8 test: simultaneous IPC calls using same thread
eb069ab75d83 Fix crash on simultaneous IPC calls using the same thread
ec03a9639ab5 doc: Precision and typos
2b4348193551 doc: Where possible, remove links to ryanofsky/bitcoin/
286fe469c9c9 util: Add helpful error message when failing to execute file
47d79db8a552 Merge bitcoin-core/libmultiprocess#201: bug: fix mptest hang, ProxyClient<Thread> deadlock in disconnect handler
f15ae9c9b9fb Merge bitcoin-core/libmultiprocess#211: Add .gitignore
4a269b21b8c8 bug: fix ProxyClient<Thread> deadlock if disconnected as IPC call is returning
85df96482c49 Use try_emplace in SetThread instead of threads.find
ca9b380ea91a Use std::optional in ConnThreads to allow shortening locks
9b0799113557 doc: describe ThreadContext struct and synchronization requirements
d60db601ed9b proxy-io.h: add Waiter::m_mutex thread safety annotations
4e365b019a9f ci: Use -Wthread-safety not -Wthread-safety-analysis
15d7bafbb001 Add .gitignore
fe1cd8c76131 Merge bitcoin-core/libmultiprocess#208: ci: Test minimum cmake version in olddeps job
b713a0b7bfbc Merge bitcoin-core/libmultiprocess#207: ci: output CMake version in CI script
0f580397c913 ci: Test minimum cmake version in olddeps job
d603dcc0eef0 ci: output CMake version in CI script

git-subtree-dir: src/ipc/libmultiprocess
git-subtree-split: a4f92969649018ca70f949a09148bccfeaecd99a
2025-10-07 10:12:08 -04:00
fanquake
f725754615 doc: update manual pages for v30.0 2025-10-07 11:18:46 +01:00
fanquake
d7c89ba74e build: bump version to v30.0 2025-10-07 11:18:43 +01:00
merge-script
d5e0077bef Merge bitcoin/bitcoin#33473: [30.x] Backports & rc3
4e869a67aa doc: update example bitcoin conf for 30.0rc3 (fanquake)
a2ac6cce57 doc: update manual pages for v30.0rc3 (fanquake)
e4b568917c build: bump version to v30.0rc3 (fanquake)
f957c2171d contrib: fix using macdploy script without translations. (amisha)
1eb578045d depends: static libxcb_cursor (fanquake)
e4f9ec2f05 test: add more TRUC reorg coverge (Greg Sanders)
3485252584 Mempool: Do not enforce TRUC checks on reorg (Greg Sanders)
a3a1dcb589 fuzz: don't bypass_limits for most mempool harnesses (Greg Sanders)
fce1c60770 datacarrier: Undeprecate configuration option (Anthony Towns)
b75afaccb8 doc: rpc: fix case typo in `finalizepsbt` help (final_scriptwitness) (Sebastian Falbesoner)
45703931e5 miner: fix `addPackageTxs` unsigned integer overflow (ismaelsadeeq)
1e348bc55a rpc: fix getblock(header) returns target for tip (Sjors Provoost)
4ec30d53ec test: add block 2016 to mock mainnet (Sjors Provoost)

Pull request description:

  Backports:
  * #33434
  * #33446
  * #33453
  * #33475
  * #33482
  * #33484
  * #33504

  Includes changes for `v30.0rc3`:
  * Version bump
  * Regen manpages
  * Regen exmaple .conf

ACKs for top commit:
  marcofleon:
    lgtm ACK 4e869a67aa
  dergoegge:
    ACK 4e869a67aa
  hebasto:
    ACK 4e869a67aa, I agree on the backported PRs. I've reproduced locally all backports, the manpages update, and the example `bitcoin.conf` updated, and obtained zero diff with this PR.
  Zero-1729:
    LGTM ACK 4e869a67aa

Tree-SHA512: 90bffbb6dfe2b512167b5e08253ea163b714505ec3ef2247d798c40b30713a7db13cf0b5486b5f9e0e5b3ba53108dfaeea47276c40816eeb81065d42bd402379
2025-10-06 10:37:26 +01:00
merge-script
2869dae5ec Merge bitcoin/bitcoin#33541: Release: 30.0rc3 translations update
71ee0163de qt: 30.0rc3 translations update (Hennadii Stepanov)

Pull request description:

  This PR updates the Polish (pl) translation and addresses [this](https://github.com/bitcoin/bitcoin/pull/33275#issuecomment-3329617679) comment.

  Updates for other languages have been skipped, as I believe the review effort would not be worthwhile at this stage of the release process.

ACKs for top commit:
  maflcko:
    Though, this lgtm ACK 71ee0163de
  janb84:
    ACK 71ee0163de

Tree-SHA512: e9ee8146542d0928f5ddaac5b2cb0c0032cd7d236fc19b38fd4ebe91e13eccdfa7d25ff279376c3f27f0bc461729b2c0574fedbb670a2555bc7a534e5ec0cd76
2025-10-06 10:24:07 +01:00
Hennadii Stepanov
71ee0163de qt: 30.0rc3 translations update 2025-10-05 11:53:45 +01:00
fanquake
4e869a67aa doc: update example bitcoin conf for 30.0rc3 2025-10-03 16:33:13 +01:00
fanquake
a2ac6cce57 doc: update manual pages for v30.0rc3 2025-10-03 16:32:40 +01:00
fanquake
e4b568917c build: bump version to v30.0rc3 2025-10-03 16:28:31 +01:00
amisha
f957c2171d contrib: fix using macdploy script without translations.
QT translations are optional, but the script would error when
'translations_dir' falls back to its default value NULL.

This PR fixes it by moving the set-up of QT translations under
the check for 'translations_dir' presence.

Github-Pull: #33482
Rebased-From: 7b5261f7ef
2025-10-03 15:41:37 +01:00
fanquake
1eb578045d depends: static libxcb_cursor
Modern Ubuntu isn't shipping with this library installed by default.
Staticly link it to remove the need for end-users to install it.

Closes #33432.

Github-Pull: #33434
Rebased-From: eca50854e1
2025-10-03 15:27:48 +01:00
Greg Sanders
e4f9ec2f05 test: add more TRUC reorg coverge
Github-Pull: #33504
Rebased-From: 06df14ba75
2025-10-02 13:56:24 +01:00
Greg Sanders
3485252584 Mempool: Do not enforce TRUC checks on reorg
Not enforcing TRUC topology on reorg was the intended
behavior, but the appropriate bypass argument was not
checked.

This mistake means we could potentially invalidate a long
chain of perfectly incentive-compatible transactions that
were made historically, including subsequent non-TRUC
transactions, all of which may have been very high feerate.

Lastly, it wastes CPU cycles doing topology checks since
this behavior cannot actually enforce the topology in
general for the reorg setting.

Github-Pull: #33504
Rebased-From: 26e71c237d
2025-10-02 13:55:58 +01:00
Greg Sanders
a3a1dcb589 fuzz: don't bypass_limits for most mempool harnesses
Using bypass_limits=true is essentially fuzzing part of a
reorg only, and results in TRUC invariants unable to be
checked. Remove most instances of bypassing limits, leaving
one harness able to do so.

Github-Pull: #33504
Rebased-From: bbe8e9063c
2025-10-02 13:55:33 +01:00
Anthony Towns
fce1c60770 datacarrier: Undeprecate configuration option
Reverts commit 0b4048c733

Github-Pull: #33453
Rebased-From: 451ba9ada4
2025-09-30 18:37:37 -04:00
Sebastian Falbesoner
b75afaccb8 doc: rpc: fix case typo in finalizepsbt help (final_scriptwitness)
Github-Pull: #33484
Rebased-From: ff05bebcc4
2025-09-28 18:14:03 -04:00
ismaelsadeeq
45703931e5 miner: fix addPackageTxs unsigned integer overflow
Github-Pull: #33475
Rebased-From: b807dfcdc5
2025-09-25 08:19:33 -04:00
Sjors Provoost
1e348bc55a rpc: fix getblock(header) returns target for tip
A target field was added to the getblock and getblockheader RPC calls in bitcoin#31583, but it mistakingly always used the tip value.

Because regtest does not have difficulty adjustment, a test is added for mainnet instead.

Github-Pull: #33446
Rebased-From: bf7996cbc3
2025-09-24 10:09:25 -04:00
Sjors Provoost
4ec30d53ec test: add block 2016 to mock mainnet
The next commit requires an additional mainnet block which changes the difficulty.

Also fix a few minor mistakes in the test (suite):
- rename the create_coinbase retarger_period argument to halving_period. Before bitcoin#31583 this was hardcoded for regtest where these values are the same.
- drop unused fees argument from mine helper

Finally the CPU miner instructions for generating the alternative mainnet chain are expanded.

Github-Pull: #33446
Rebased-From: 4c3c1f42cf
2025-09-24 10:09:04 -04:00
107 changed files with 2027 additions and 847 deletions

View File

@@ -4,12 +4,21 @@ inputs:
cache-provider:
description: 'gha or cirrus cache provider'
required: true
options:
- gh
- cirrus
runs:
using: 'composite'
steps:
- name: Check inputs
shell: bash
run: |
# We expect only gha or cirrus as inputs to cache-provider
case "${{ inputs.cache-provider }}" in
gha|cirrus)
;;
*)
echo "::warning title=Unknown input to configure docker action::Provided value was ${{ inputs.cache-provider }}"
;;
esac
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
@@ -22,8 +31,12 @@ runs:
uses: actions/github-script@v6
with:
script: |
core.exportVariable('ACTIONS_CACHE_URL', process.env['ACTIONS_CACHE_URL'])
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env['ACTIONS_RUNTIME_TOKEN'])
Object.keys(process.env).forEach(function (key) {
if (key.startsWith('ACTIONS_')) {
core.info(`Exporting ${key}`);
core.exportVariable(key, process.env[key]);
}
});
- name: Construct docker build cache args
shell: bash

View File

@@ -17,7 +17,7 @@ runs:
- name: Set cache hashes
shell: bash
run: |
echo "DEPENDS_HASH=$(git ls-tree HEAD depends "ci/test/$FILE_ENV" | sha256sum | cut -d' ' -f1)" >> $GITHUB_ENV
echo "DEPENDS_HASH=$(git ls-tree HEAD depends "$FILE_ENV" | sha256sum | cut -d' ' -f1)" >> $GITHUB_ENV
echo "PREVIOUS_RELEASES_HASH=$(git ls-tree HEAD test/get_previous_releases.py | sha256sum | cut -d' ' -f1)" >> $GITHUB_ENV
- name: Get container name

View File

@@ -536,7 +536,7 @@ jobs:
lint:
name: 'lint'
needs: runners
runs-on: ${{ needs.runners.outputs.use-cirrus-runners == 'true' && 'ghcr.io/cirruslabs/ubuntu-runner-amd64:24.04-xs' || 'ubuntu-24.04' }}
runs-on: ${{ needs.runners.outputs.provider == 'cirrus' && 'ghcr.io/cirruslabs/ubuntu-runner-amd64:24.04-xs' || 'ubuntu-24.04' }}
if: ${{ vars.SKIP_BRANCH_PUSH != 'true' || github.event_name == 'pull_request' }}
timeout-minutes: 20
env:

View File

@@ -28,11 +28,11 @@ get_directory_property(precious_variables CACHE_VARIABLES)
#=============================
set(CLIENT_NAME "Bitcoin Core")
set(CLIENT_VERSION_MAJOR 30)
set(CLIENT_VERSION_MINOR 0)
set(CLIENT_VERSION_MINOR 2)
set(CLIENT_VERSION_BUILD 0)
set(CLIENT_VERSION_RC 2)
set(CLIENT_VERSION_RC 0)
set(CLIENT_VERSION_IS_RELEASE "true")
set(COPYRIGHT_YEAR "2025")
set(COPYRIGHT_YEAR "2026")
# During the enabling of the CXX and CXXOBJ languages, we modify
# CMake's compiler/linker invocation strings by appending the content

View File

@@ -1,7 +1,7 @@
The MIT License (MIT)
Copyright (c) 2009-2025 The Bitcoin Core developers
Copyright (c) 2009-2025 Bitcoin Developers
Copyright (c) 2009-2026 The Bitcoin Core developers
Copyright (c) 2009-2026 Bitcoin Developers
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -8,7 +8,6 @@ export LC_ALL=C.UTF-8
export CONTAINER_NAME=ci_win64
export CI_IMAGE_NAME_TAG="mirror.gcr.io/ubuntu:24.04" # Check that https://packages.ubuntu.com/noble/g++-mingw-w64-x86-64-posix (version 13.x, similar to guix) can cross-compile
export CI_IMAGE_PLATFORM="linux/amd64"
export HOST=x86_64-w64-mingw32
export PACKAGES="g++-mingw-w64-x86-64-posix nsis"
export RUN_UNIT_TESTS=false

View File

@@ -89,7 +89,7 @@ mkdir -p "${DEPENDS_DIR}/SDKs" "${DEPENDS_DIR}/sdk-sources"
OSX_SDK_BASENAME="Xcode-${XCODE_VERSION}-${XCODE_BUILD_ID}-extracted-SDK-with-libcxx-headers"
if [ -n "$XCODE_VERSION" ] && [ ! -d "${DEPENDS_DIR}/SDKs/${OSX_SDK_BASENAME}" ]; then
OSX_SDK_FILENAME="${OSX_SDK_BASENAME}.tar.gz"
OSX_SDK_FILENAME="${OSX_SDK_BASENAME}.tar"
OSX_SDK_PATH="${DEPENDS_DIR}/sdk-sources/${OSX_SDK_FILENAME}"
if [ ! -f "$OSX_SDK_PATH" ]; then
${CI_RETRY_EXE} curl --location --fail "${SDK_URL}/${OSX_SDK_FILENAME}" -o "$OSX_SDK_PATH"

View File

@@ -29,7 +29,7 @@ function(add_boost_if_needed)
endif()
endif()
find_package(Boost 1.73.0 REQUIRED CONFIG)
find_package(Boost 1.74.0 REQUIRED CONFIG)
mark_as_advanced(Boost_INCLUDE_DIR boost_headers_DIR)
# Workaround for a bug in NetBSD pkgsrc.
# See: https://github.com/NetBSD/pkgsrc/issues/167.

View File

@@ -5,7 +5,7 @@ Upstream-Contact: Satoshi Nakamoto <satoshin@gmx.com>
Source: https://github.com/bitcoin/bitcoin
Files: *
Copyright: 2009-2025, Bitcoin Core Developers
Copyright: 2009-2026, Bitcoin Core Developers
License: Expat
Comment: The Bitcoin Core Developers encompasses all contributors to the
project, listed in the release notes or the git log.

View File

@@ -3,6 +3,7 @@
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
import os
import re
import subprocess
import sys
import tempfile
@@ -59,10 +60,11 @@ for relpath in BINARIES:
print(f'{abspath} not found or not an executable', file=sys.stderr)
sys.exit(1)
# take first line (which must contain version)
verstr = r.stdout.splitlines()[0]
# last word of line is the actual version e.g. v22.99.0-5c6b3d5b3508
verstr = verstr.split()[-1]
assert verstr.startswith('v')
output = r.stdout.splitlines()[0]
# find the version e.g. v30.99.0-ce771726f3e7
search = re.search(r"v[0-9]\S+", output)
assert search
verstr = search.group(0)
# remaining lines are copyright
copyright = r.stdout.split('\n')[1:]
assert copyright[0].startswith('Copyright (C)')

View File

@@ -37,7 +37,7 @@ You can then either point to the SDK using the `SDK_PATH` environment variable:
```sh
# Extract the SDK tarball to /path/to/parent/dir/of/extracted/SDK/Xcode-<foo>-<bar>-extracted-SDK-with-libcxx-headers
tar -C /path/to/parent/dir/of/extracted/SDK -xaf /path/to/Xcode-<foo>-<bar>-extracted-SDK-with-libcxx-headers.tar.gz
tar -C /path/to/parent/dir/of/extracted/SDK -xaf /path/to/Xcode-<foo>-<bar>-extracted-SDK-with-libcxx-headers.tar
# Indicate where to locate the SDK tarball
export SDK_PATH=/path/to/parent/dir/of/extracted/SDK

View File

@@ -2,6 +2,7 @@
((gnu packages bash) #:select (bash-minimal))
(gnu packages bison)
((gnu packages certs) #:select (nss-certs))
((gnu packages check) #:select (libfaketime))
((gnu packages cmake) #:select (cmake-minimal))
(gnu packages commencement)
(gnu packages compression)
@@ -209,7 +210,17 @@ and abstract ELF, PE and MachO formats.")
(base32
"1j47vwq4caxfv0xw68kw5yh00qcpbd56d7rq6c483ma3y7s96yyz"))))
(build-system cmake-build-system)
(inputs (list openssl))
(arguments
(list
#:phases
#~(modify-phases %standard-phases
(replace 'check
(lambda* (#:key tests? #:allow-other-keys)
(if tests?
(invoke "faketime" "-f" "@2025-01-01 00:00:00" ;; Tests fail after 2025.
"ctest" "--output-on-failure" "--no-tests=error")
(format #t "test suite not run~%")))))))
(inputs (list libfaketime openssl))
(home-page "https://github.com/mtrojnar/osslsigncode")
(synopsis "Authenticode signing and timestamping tool")
(description "osslsigncode is a small tool that implements part of the

View File

@@ -46,8 +46,7 @@ MAX_VERSIONS = {
# Ignore symbols that are exported as part of every executable
IGNORE_EXPORTS = {
'environ', '_environ', '__environ', '_fini', '_init', 'stdin',
'stdout', 'stderr',
'stdin', 'stdout', 'stderr',
}
# Expected linker-loader names can be found here:
@@ -112,7 +111,6 @@ ELF_ALLOWED_LIBRARIES = {
'libfontconfig.so.1', # font support
'libfreetype.so.6', # font parsing
'libdl.so.2', # programming interface to dynamic linker
'libxcb-cursor.so.0',
'libxcb-icccm.so.4',
'libxcb-image.so.0',
'libxcb-shm.so.0',

View File

@@ -44,15 +44,15 @@ xip -x Xcode_15.xip
### Step 2: Generating the SDK tarball from `Xcode.app`
To generate the SDK, run the script [`gen-sdk`](./gen-sdk) with the
To generate the SDK, run the script [`gen-sdk.py`](./gen-sdk.py) with the
path to `Xcode.app` (extracted in the previous stage) as the first argument.
```bash
./contrib/macdeploy/gen-sdk '/path/to/Xcode.app'
./contrib/macdeploy/gen-sdk.py '/path/to/Xcode.app'
```
The generated archive should be: `Xcode-15.0-15A240d-extracted-SDK-with-libcxx-headers.tar.gz`.
The `sha256sum` should be `c0c2e7bb92c1fee0c4e9f3a485e4530786732d6c6dd9e9f418c282aa6892f55d`.
The generated archive should be: `Xcode-15.0-15A240d-extracted-SDK-with-libcxx-headers.tar`.
The `sha256sum` should be `95b00dc41fa090747dc0a7907a5031a2fcb2d7f95c9584ba6bccdb99b6e3d498`.
## Deterministic macOS App Notes

View File

@@ -2,9 +2,7 @@
import argparse
import plistlib
import pathlib
import sys
import tarfile
import gzip
import os
import contextlib
@@ -22,12 +20,12 @@ def run():
parser = argparse.ArgumentParser(
description=__doc__, formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument('xcode_app', metavar='XCODEAPP', nargs=1)
parser.add_argument("-o", metavar='OUTSDKTGZ', nargs=1, dest='out_sdktgz', required=False)
parser.add_argument('xcode_app', metavar='XCODEAPP', type=pathlib.Path)
parser.add_argument("-o", metavar='OUTSDKTAR', dest='out_sdkt', type=pathlib.Path, required=False)
args = parser.parse_args()
xcode_app = pathlib.Path(args.xcode_app[0]).resolve()
xcode_app = args.xcode_app.resolve()
assert xcode_app.is_dir(), "The supplied Xcode.app path '{}' either does not exist or is not a directory".format(xcode_app)
xcode_app_plist = xcode_app.joinpath("Contents/version.plist")
@@ -47,11 +45,7 @@ def run():
out_name = "Xcode-{xcode_version}-{xcode_build_id}-extracted-SDK-with-libcxx-headers".format(xcode_version=xcode_version, xcode_build_id=xcode_build_id)
if args.out_sdktgz:
out_sdktgz_path = pathlib.Path(args.out_sdktgz_path)
else:
# Construct our own out_sdktgz if not specified on the command line
out_sdktgz_path = pathlib.Path("./{}.tar.gz".format(out_name))
out_sdkt_path = args.out_sdkt or pathlib.Path("./{}.tar".format(out_name))
def tarfp_add_with_base_change(tarfp, dir_to_add, alt_base_dir):
"""Add all files in dir_to_add to tarfp, but prepend alt_base_dir to the files'
@@ -68,6 +62,8 @@ def run():
"""
def change_tarinfo_base(tarinfo):
if tarinfo.name and tarinfo.name.endswith((".swiftmodule", ".modulemap")):
return None
if tarinfo.name and tarinfo.name.startswith("./"):
tarinfo.name = str(pathlib.Path(alt_base_dir, tarinfo.name))
if tarinfo.linkname and tarinfo.linkname.startswith("./"):
@@ -81,16 +77,17 @@ def run():
return tarinfo
with cd(dir_to_add):
# recursion already adds entries in sorted order
tarfp.add(".", recursive=True, filter=change_tarinfo_base)
tarfp.add("./usr/include", recursive=True, filter=change_tarinfo_base)
tarfp.add("./usr/lib", recursive=True, filter=change_tarinfo_base)
tarfp.add("./System/Library/Frameworks", recursive=True, filter=change_tarinfo_base)
print("Creating output .tar.gz file...")
with out_sdktgz_path.open("wb") as fp:
with gzip.GzipFile(fileobj=fp, mode='wb', compresslevel=9, mtime=0) as gzf:
with tarfile.open(mode="w", fileobj=gzf, format=tarfile.GNU_FORMAT) as tarfp:
print("Adding MacOSX SDK {} files...".format(sdk_version))
tarfp_add_with_base_change(tarfp, sdk_dir, out_name)
print("Done! Find the resulting gzipped tarball at:")
print(out_sdktgz_path.resolve())
print("Creating output .tar file...")
with out_sdkt_path.open("wb") as fp:
with tarfile.open(mode="w", fileobj=fp, format=tarfile.PAX_FORMAT) as tarfp:
print("Adding MacOSX SDK {} files...".format(sdk_version))
tarfp_add_with_base_change(tarfp, sdk_dir, out_name)
print("Done! Find the resulting tarball at:")
print(out_sdkt_path.resolve())
if __name__ == '__main__':
run()

View File

@@ -466,18 +466,18 @@ if config.translations_dir:
sys.stderr.write(f"Error: Could not find translation dir \"{config.translations_dir[0]}\"\n")
sys.exit(1)
print("+ Adding Qt translations +")
print("+ Adding Qt translations +")
translations = Path(config.translations_dir[0])
translations = Path(config.translations_dir[0])
regex = re.compile('qt_[a-z]*(.qm|_[A-Z]*.qm)')
regex = re.compile('qt_[a-z]*(.qm|_[A-Z]*.qm)')
lang_files = [x for x in translations.iterdir() if regex.match(x.name)]
lang_files = [x for x in translations.iterdir() if regex.match(x.name)]
for file in lang_files:
if verbose:
print(file.as_posix(), "->", os.path.join(applicationBundle.resourcesPath, file.name))
shutil.copy2(file.as_posix(), os.path.join(applicationBundle.resourcesPath, file.name))
for file in lang_files:
if verbose:
print(file.as_posix(), "->", os.path.join(applicationBundle.resourcesPath, file.name))
shutil.copy2(file.as_posix(), os.path.join(applicationBundle.resourcesPath, file.name))
# ------------------------------------------------

View File

@@ -10,14 +10,13 @@ to addrman with).
Update `MIN_BLOCKS` in `makeseeds.py` and the `-m`/`--minblocks` arguments below, as needed.
The seeds compiled into the release are created from sipa's, achow101's and luke-jr's
The seeds compiled into the release are created from sipa's and achow101's
DNS seed, virtu's crawler, and asmap community AS map data. Run the following commands
from the `/contrib/seeds` directory:
```
curl https://bitcoin.sipa.be/seeds.txt.gz | gzip -dc > seeds_main.txt
curl https://21.ninja/seeds.txt.gz | gzip -dc >> seeds_main.txt
curl https://luke.dashjr.org/programs/bitcoin/files/charts/seeds.txt >> seeds_main.txt
curl https://mainnet.achownodes.xyz/seeds.txt.gz | gzip -dc >> seeds_main.txt
curl https://signet.achownodes.xyz/seeds.txt.gz | gzip -dc > seeds_signet.txt
curl https://testnet.achownodes.xyz/seeds.txt.gz | gzip -dc > seeds_test.txt

View File

@@ -38,7 +38,7 @@ endef
define fetch_file
( test -f $$($(1)_source_dir)/$(4) || \
( $(call fetch_file_inner,$(1),$(2),$(3),$(4),$(5)) || \
$(call fetch_file_inner,$(1),$(FALLBACK_DOWNLOAD_PATH),$(3),$(4),$(5))))
$(call fetch_file_inner,$(1),$(FALLBACK_DOWNLOAD_PATH),$(4),$(4),$(5))))
endef
# Shell script to create a source tarball in $(1)_source from local directory

View File

@@ -6,7 +6,7 @@ $(package)_sha256_hash=0e9c5446dc6f3beb8af6ebfcc9e27bcc6da6fe2860f7fc07b99144dfa
$(package)_dependencies=libxcb libxcb_util_render libxcb_util_image
define $(package)_set_vars
$(package)_config_opts = --disable-static
$(package)_config_opts = --disable-shared
$(package)_config_opts += --disable-dependency-tracking --enable-option-checking
endef

View File

@@ -16,6 +16,7 @@ $(package)_patches += qtbase_avoid_native_float16.patch
$(package)_patches += qtbase_avoid_qmain.patch
$(package)_patches += qtbase_platformsupport.patch
$(package)_patches += qtbase_plugins_cocoa.patch
$(package)_patches += qtbase_plugins_windows11style.patch
$(package)_patches += qtbase_skip_tools.patch
$(package)_patches += rcc_hardcode_timestamp.patch
$(package)_patches += qttools_skip_dependencies.patch
@@ -261,6 +262,7 @@ define $(package)_preprocess_cmds
patch -p1 -i $($(package)_patch_dir)/qtbase_avoid_qmain.patch && \
patch -p1 -i $($(package)_patch_dir)/qtbase_platformsupport.patch && \
patch -p1 -i $($(package)_patch_dir)/qtbase_plugins_cocoa.patch && \
patch -p1 -i $($(package)_patch_dir)/qtbase_plugins_windows11style.patch && \
patch -p1 -i $($(package)_patch_dir)/qtbase_skip_tools.patch && \
patch -p1 -i $($(package)_patch_dir)/rcc_hardcode_timestamp.patch
endef

View File

@@ -0,0 +1,113 @@
QWindows11Style: Calculate Spinbox size based on CommonStyle size
Use the calculation from Commonstyle and add the increased padding and
horizontally layouted buttons to the horizontal size hint.
Fixes: QTBUG-130288
Change-Id: I7932b782e7873a0178091a51379f17453eb585fd
Upstream commits:
- Qt 6.8.1: 9107817eaceaacc968dbc767c24594566d637b8c
- Qt 6.9.0: 96d46cad43517adefa2eb7cb8819a0b2cc9241e6
--- a/qtbase/src/plugins/styles/modernwindows/qwindows11style.cpp
+++ b/qtbase/src/plugins/styles/modernwindows/qwindows11style.cpp
@@ -2048,39 +2048,22 @@ QSize QWindows11Style::sizeFromContents(ContentsType type, const QStyleOption *o
}
break;
#endif
+#if QT_CONFIG(spinbox)
case QStyle::CT_SpinBox: {
if (const auto *spinBoxOpt = qstyleoption_cast<const QStyleOptionSpinBox *>(option)) {
// Add button + frame widths
- int width = 0;
-
- if (const QDateTimeEdit *spinBox = qobject_cast<const QDateTimeEdit *>(widget)) {
- const QSize textSizeMin = spinBoxOpt->fontMetrics.size(Qt::TextSingleLine, spinBox->minimumDateTime().toString(spinBox->displayFormat()));
- const QSize textSizeMax = spinBoxOpt->fontMetrics.size(Qt::TextSingleLine, spinBox->maximumDateTime().toString(spinBox->displayFormat()));
- width = qMax(textSizeMin.width(),textSizeMax.width());
- } else if (const QSpinBox *spinBox = qobject_cast<const QSpinBox *>(widget)) {
- const QSize textSizeMin = spinBoxOpt->fontMetrics.size(Qt::TextSingleLine, QString::number(spinBox->minimum()));
- const QSize textSizeMax = spinBoxOpt->fontMetrics.size(Qt::TextSingleLine, QString::number(spinBox->maximum()));
- width = qMax(textSizeMin.width(),textSizeMax.width());
- width += spinBoxOpt->fontMetrics.size(Qt::TextSingleLine, spinBox->prefix()).width();
- width += spinBoxOpt->fontMetrics.size(Qt::TextSingleLine, spinBox->suffix()).width();
-
- } else if (const QDoubleSpinBox *spinBox = qobject_cast<const QDoubleSpinBox *>(widget)) {
- const QSize textSizeMin = spinBoxOpt->fontMetrics.size(Qt::TextSingleLine, QString::number(spinBox->minimum()));
- const QSize textSizeMax = spinBoxOpt->fontMetrics.size(Qt::TextSingleLine, QString::number(spinBox->maximum()));
- width = qMax(textSizeMin.width(),textSizeMax.width());
- width += spinBoxOpt->fontMetrics.size(Qt::TextSingleLine, spinBox->prefix()).width();
- width += spinBoxOpt->fontMetrics.size(Qt::TextSingleLine, spinBox->suffix()).width();
- }
const qreal dpi = QStyleHelper::dpi(option);
const bool hasButtons = (spinBoxOpt->buttonSymbols != QAbstractSpinBox::NoButtons);
- const int buttonWidth = hasButtons ? 2 * qRound(QStyleHelper::dpiScaled(16, dpi)) : 0;
+ const int margins = 8;
+ const int buttonWidth = hasButtons ? qRound(QStyleHelper::dpiScaled(16, dpi)) : 0;
const int frameWidth = spinBoxOpt->frame ? proxy()->pixelMetric(PM_SpinBoxFrameWidth,
spinBoxOpt, widget) : 0;
- contentSize.setWidth(2 * 12 + width);
- contentSize += QSize(buttonWidth + 2 * frameWidth, 2 * frameWidth);
+
+ contentSize += QSize(2 * buttonWidth + 2 * frameWidth + 2 * margins, 2 * frameWidth);
}
break;
}
+#endif
default:
contentSize = QWindowsVistaStyle::sizeFromContents(type, option, size, widget);
break;
Windows11Style: don't set minimum width for QAbstractSpinBox
There is no need to set a minimum width for QAbstractSpinBox in
QWindows11Style::polish() as this might override the user preferences.
Also the minimum size handling is now properly done within
sizeFromContents().
Change-Id: Ibc1fd7a6f862fc85e3739025b9de581aa235d74c
Upstream commits:
- Qt 6.8.3: f86da3d3f853adb1a5b823c1cc7be6db4a0265f3
- Qt 6.9.0: b93a8dfdfe6900cb542fdc587dd2682007a6ac53
- Qt 6.10.0: 2ec4c28470de115c16944653a5d4f6209452d56c
--- a/qtbase/src/plugins/styles/modernwindows/qwindows11style.cpp
+++ b/qtbase/src/plugins/styles/modernwindows/qwindows11style.cpp
@@ -29,7 +29,6 @@ QT_BEGIN_NAMESPACE
const static int topLevelRoundingRadius = 8; //Radius for toplevel items like popups for round corners
const static int secondLevelRoundingRadius = 4; //Radius for second level items like hovered menu item round corners
-constexpr QLatin1StringView originalWidthProperty("_q_windows11_style_original_width");
enum WINUI3Color {
subtleHighlightColor, //Subtle highlight based on alpha used for hovered elements
@@ -2140,13 +2139,6 @@ void QWindows11Style::polish(QWidget* widget)
pal.setColor(QPalette::ButtonText, pal.text().color());
pal.setColor(QPalette::BrightText, pal.text().color());
widget->setPalette(pal);
- } else if (widget->inherits("QAbstractSpinBox")) {
- const int minWidth = 2 * 24 + 40;
- const int originalWidth = widget->size().width();
- if (originalWidth < minWidth) {
- widget->resize(minWidth, widget->size().height());
- widget->setProperty(originalWidthProperty.constData(), originalWidth);
- }
} else if (widget->inherits("QAbstractButton") || widget->inherits("QToolButton")) {
widget->setAutoFillBackground(false);
auto pal = widget->palette();
@@ -2191,13 +2183,6 @@ void QWindows11Style::unpolish(QWidget *widget)
scrollarea->viewport()->setPalette(pal);
scrollarea->viewport()->setProperty("_q_original_background_palette", QVariant());
}
- if (widget->inherits("QAbstractSpinBox")) {
- const QVariant originalWidth = widget->property(originalWidthProperty.constData());
- if (originalWidth.isValid()) {
- widget->resize(originalWidth.toInt(), widget->size().height());
- widget->setProperty(originalWidthProperty.constData(), QVariant());
- }
- }
}
/*

View File

@@ -101,5 +101,5 @@ cmake -B build -DENABLE_WALLET=OFF
```bash
cmake --build build # Append "-j N" for N parallel jobs.
ctest --test-dir build # Append "-j N" for N parallel tests. Some tests are disabled if Python 3 is not available.
ctest --test-dir build # Append "-j N" for N parallel tests.
```

View File

@@ -34,7 +34,7 @@ cmake -B build
SQLite is required for the wallet:
```bash
pkgin sqlite3
pkgin install sqlite3
```
To build Bitcoin Core without the wallet, use `-DENABLE_WALLET=OFF`.
@@ -42,7 +42,7 @@ To build Bitcoin Core without the wallet, use `-DENABLE_WALLET=OFF`.
Cap'n Proto is needed for IPC functionality (see [multiprocess.md](multiprocess.md)):
```bash
pkgin capnproto
pkgin install capnproto
```
Compile with `-DENABLE_IPC=OFF` if you do not need IPC functionality.
@@ -84,7 +84,7 @@ Otherwise, if you don't need QR encoding support, use the `-DWITH_QRENCODE=OFF`
Bitcoin Core can provide notifications via ZeroMQ. If the package is installed, support will be compiled in.
```bash
pkgin zeromq
pkgin install zeromq
```
#### Test Suite Dependencies
@@ -115,5 +115,5 @@ Build and run the tests:
```bash
cmake --build build # Append "-j N" for N parallel jobs.
ctest --test-dir build # Append "-j N" for N parallel tests. Some tests are disabled if Python 3 is not available.
ctest --test-dir build # Append "-j N" for N parallel tests.
```

View File

@@ -1,6 +1,6 @@
# OpenBSD Build Guide
**Updated for OpenBSD [7.6](https://www.openbsd.org/76.html)**
**Updated for OpenBSD [7.8](https://www.openbsd.org/78.html)**
This guide describes how to build bitcoind, command-line utilities, and GUI on OpenBSD.
@@ -21,8 +21,11 @@ pkg_add sqlite3
To build Bitcoin Core without the wallet, use `-DENABLE_WALLET=OFF`.
Cap'n Proto is needed for IPC functionality (see [multiprocess.md](multiprocess.md))
and can be built from source: https://capnproto.org/install.html
Cap'n Proto is needed for IPC functionality (see [multiprocess.md](multiprocess.md)):
```bash
pkg_add capnproto
```
Compile with `-DENABLE_IPC=OFF` if you do not need IPC functionality.
@@ -93,7 +96,7 @@ Run `cmake -B build -LH` to see the full list of available options.
```bash
cmake --build build # Append "-j N" for N parallel jobs.
ctest --test-dir build # Append "-j N" for N parallel tests. Some tests are disabled if Python 3 is not available.
ctest --test-dir build # Append "-j N" for N parallel tests.
```
## Resource limits

View File

@@ -170,7 +170,7 @@ Run the following in your terminal to compile Bitcoin Core:
``` bash
cmake --build build # Append "-j N" here for N parallel jobs.
ctest --test-dir build # Append "-j N" for N parallel tests. Some tests are disabled if Python 3 is not available.
ctest --test-dir build # Append "-j N" for N parallel tests.
```
### 3. Deploy (optional)

View File

@@ -81,8 +81,6 @@ the necessary parts of Qt, the libqrencode and pass `-DBUILD_GUI=ON`. Skip if yo
sudo apt-get install qt6-base-dev qt6-tools-dev qt6-l10n-tools qt6-tools-dev-tools libgl-dev
For Qt 6.5 and later, the `libxcb-cursor0` package must be installed at runtime.
Additionally, to support Wayland protocol for modern desktop environments:
sudo apt install qt6-wayland
@@ -133,8 +131,6 @@ the necessary parts of Qt, the libqrencode and pass `-DBUILD_GUI=ON`. Skip if yo
sudo dnf install qt6-qtbase-devel qt6-qttools-devel
For Qt 6.5 and later, the `xcb-util-cursor` package must be installed at runtime.
Additionally, to support Wayland protocol for modern desktop environments:
sudo dnf install qt6-qtwayland
@@ -182,8 +178,6 @@ the necessary parts of Qt, the libqrencode and pass `-DBUILD_GUI=ON`. Skip if yo
apk add qt6-qtbase-dev qt6-qttools-dev
For Qt 6.5 and later, the `xcb-util-cursor` package must be installed at runtime.
The GUI will be able to encode addresses in QR codes unless this feature is explicitly disabled. To install libqrencode, run:
apk add libqrencode-dev

View File

@@ -55,7 +55,7 @@ In the following instructions, the "Debug" configuration can be specified instea
```
cmake -B build --preset vs2022-static # It might take a while if the vcpkg binary cache is unpopulated or invalidated.
cmake --build build --config Release # Append "-j N" for N parallel jobs.
ctest --test-dir build --build-config Release # Append "-j N" for N parallel tests. Some tests are disabled if Python 3 is not available.
ctest --test-dir build --build-config Release # Append "-j N" for N parallel tests.
cmake --install build --config Release # Optional.
```
@@ -64,7 +64,7 @@ cmake --install build --config Release # Optional.
```
cmake -B build --preset vs2022 -DBUILD_GUI=OFF # It might take a while if the vcpkg binary cache is unpopulated or invalidated.
cmake --build build --config Release # Append "-j N" for N parallel jobs.
ctest --test-dir build --build-config Release # Append "-j N" for N parallel tests. Some tests are disabled if Python 3 is not available.
ctest --test-dir build --build-config Release # Append "-j N" for N parallel tests.
```
### 6. vcpkg-specific Issues and Workarounds

View File

@@ -19,7 +19,7 @@ Bitcoin Core requires one of the following compilers.
| Dependency | Releases | Minimum required |
| --- | --- | --- |
| [Boost](../depends/packages/boost.mk) | [link](https://www.boost.org/users/download/) | [1.73.0](https://github.com/bitcoin/bitcoin/pull/29066) |
| [Boost](../depends/packages/boost.mk) | [link](https://www.boost.org/users/download/) | [1.74.0](https://github.com/bitcoin/bitcoin/pull/34107) |
| CMake | [link](https://cmake.org/) | [3.22](https://github.com/bitcoin/bitcoin/pull/30454) |
| [libevent](../depends/packages/libevent.mk) | [link](https://github.com/libevent/libevent/releases) | [2.1.8](https://github.com/bitcoin/bitcoin/pull/24681) |

View File

@@ -79,7 +79,7 @@ of the test. Just make sure to use double-dash to distinguish them from the
fuzzer's own arguments:
```sh
$ FUZZ=address_deserialize_v2 build_fuzz/bin/fuzz -runs=1 fuzz_corpora/address_deserialize_v2 --checkaddrman=5 --printtoconsole=1
$ FUZZ=address_deserialize build_fuzz/bin/fuzz -runs=1 fuzz_corpora/address_deserialize --checkaddrman=5 --printtoconsole=1
```
## Fuzzing corpora

View File

@@ -1,7 +1,7 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH BITCOIN-CLI "1" "September 2025" "bitcoin-cli v30.0.0rc2" "User Commands"
.TH BITCOIN-CLI "1" "January 2026" "bitcoin-cli v30.2.0" "User Commands"
.SH NAME
bitcoin-cli \- manual page for bitcoin-cli v30.0.0rc2
bitcoin-cli \- manual page for bitcoin-cli v30.2.0
.SH SYNOPSIS
.B bitcoin-cli
[\fI\,options\/\fR] \fI\,<command> \/\fR[\fI\,params\/\fR]
@@ -15,7 +15,7 @@ bitcoin-cli \- manual page for bitcoin-cli v30.0.0rc2
.B bitcoin-cli
[\fI\,options\/\fR] \fI\,help <command>\/\fR
.SH DESCRIPTION
Bitcoin Core RPC client version v30.0.0rc2
Bitcoin Core RPC client version v30.2.0
.PP
The bitcoin\-cli utility provides a command line interface to interact with a Bitcoin Core RPC server.
.PP
@@ -188,7 +188,7 @@ additional "outonly" (or "o") argument can be passed to see
outbound peers only. Pass "help" (or "h") for detailed help
documentation.
.SH COPYRIGHT
Copyright (C) 2009-2025 The Bitcoin Core developers
Copyright (C) 2009-2026 The Bitcoin Core developers
Please contribute if you find Bitcoin Core useful. Visit
<https://bitcoincore.org/> for further information about the software.

View File

@@ -1,12 +1,12 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH BITCOIN-QT "1" "September 2025" "bitcoin-qt v30.0.0rc2" "User Commands"
.TH BITCOIN-QT "1" "January 2026" "bitcoin-qt v30.2.0" "User Commands"
.SH NAME
bitcoin-qt \- manual page for bitcoin-qt v30.0.0rc2
bitcoin-qt \- manual page for bitcoin-qt v30.2.0
.SH SYNOPSIS
.B bitcoin-qt
[\fI\,options\/\fR] [\fI\,URI\/\fR]
.SH DESCRIPTION
Bitcoin Core version v30.0.0rc2
Bitcoin Core version v30.2.0
.PP
The bitcoin\-qt application provides a graphical interface for interacting with Bitcoin Core.
.PP
@@ -695,13 +695,13 @@ Equivalent bytes per sigop in transactions for relay and mining
.HP
\fB\-datacarrier\fR
.IP
(DEPRECATED) Relay and mine data carrier transactions (default: 1)
Relay and mine data carrier transactions (default: 1)
.HP
\fB\-datacarriersize\fR
.IP
(DEPRECATED) Relay and mine transactions whose data\-carrying raw
scriptPubKeys in aggregate are of this size or less, allowing
multiple outputs (default: 100000)
Relay and mine transactions whose data\-carrying raw scriptPubKeys in
aggregate are of this size or less, allowing multiple outputs
(default: 100000)
.HP
\fB\-minrelaytxfee=\fR<amt>
.IP
@@ -839,7 +839,7 @@ Reset all settings changed in the GUI
.IP
Show splash screen on startup (default: 1)
.SH COPYRIGHT
Copyright (C) 2009-2025 The Bitcoin Core developers
Copyright (C) 2009-2026 The Bitcoin Core developers
Please contribute if you find Bitcoin Core useful. Visit
<https://bitcoincore.org/> for further information about the software.

View File

@@ -1,7 +1,7 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH BITCOIN-TX "1" "September 2025" "bitcoin-tx v30.0.0rc2" "User Commands"
.TH BITCOIN-TX "1" "January 2026" "bitcoin-tx v30.2.0" "User Commands"
.SH NAME
bitcoin-tx \- manual page for bitcoin-tx v30.0.0rc2
bitcoin-tx \- manual page for bitcoin-tx v30.2.0
.SH SYNOPSIS
.B bitcoin-tx
[\fI\,options\/\fR] \fI\,<hex-tx> \/\fR[\fI\,commands\/\fR]
@@ -9,7 +9,7 @@ bitcoin-tx \- manual page for bitcoin-tx v30.0.0rc2
.B bitcoin-tx
[\fI\,options\/\fR] \fI\,-create \/\fR[\fI\,commands\/\fR]
.SH DESCRIPTION
Bitcoin Core bitcoin\-tx utility version v30.0.0rc2
Bitcoin Core bitcoin\-tx utility version v30.2.0
.PP
The bitcoin\-tx tool is used for creating and modifying bitcoin transactions.
.PP
@@ -146,7 +146,7 @@ set=NAME:JSON\-STRING
.IP
Set register NAME to given JSON\-STRING
.SH COPYRIGHT
Copyright (C) 2009-2025 The Bitcoin Core developers
Copyright (C) 2009-2026 The Bitcoin Core developers
Please contribute if you find Bitcoin Core useful. Visit
<https://bitcoincore.org/> for further information about the software.

View File

@@ -1,7 +1,7 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH BITCOIN-UTIL "1" "September 2025" "bitcoin-util v30.0.0rc2" "User Commands"
.TH BITCOIN-UTIL "1" "January 2026" "bitcoin-util v30.2.0" "User Commands"
.SH NAME
bitcoin-util \- manual page for bitcoin-util v30.0.0rc2
bitcoin-util \- manual page for bitcoin-util v30.2.0
.SH SYNOPSIS
.B bitcoin-util
[\fI\,options\/\fR] [\fI\,command\/\fR]
@@ -9,7 +9,7 @@ bitcoin-util \- manual page for bitcoin-util v30.0.0rc2
.B bitcoin-util
[\fI\,options\/\fR] \fI\,grind <hex-block-header>\/\fR
.SH DESCRIPTION
Bitcoin Core bitcoin\-util utility version v30.0.0rc2
Bitcoin Core bitcoin\-util utility version v30.2.0
.PP
The bitcoin\-util tool provides bitcoin related functionality that does not rely on the ability to access a running node. Available [commands] are listed below.
.SH OPTIONS
@@ -65,7 +65,7 @@ grind
.IP
Perform proof of work on hex header string
.SH COPYRIGHT
Copyright (C) 2009-2025 The Bitcoin Core developers
Copyright (C) 2009-2026 The Bitcoin Core developers
Please contribute if you find Bitcoin Core useful. Visit
<https://bitcoincore.org/> for further information about the software.

View File

@@ -1,12 +1,12 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH BITCOIN-WALLET "1" "September 2025" "bitcoin-wallet v30.0.0rc2" "User Commands"
.TH BITCOIN-WALLET "1" "January 2026" "bitcoin-wallet v30.2.0" "User Commands"
.SH NAME
bitcoin-wallet \- manual page for bitcoin-wallet v30.0.0rc2
bitcoin-wallet \- manual page for bitcoin-wallet v30.2.0
.SH SYNOPSIS
.B bitcoin-wallet
[\fI\,options\/\fR] \fI\,<command>\/\fR
.SH DESCRIPTION
Bitcoin Core bitcoin\-wallet utility version v30.0.0rc2
Bitcoin Core bitcoin\-wallet utility version v30.2.0
.PP
bitcoin\-wallet is an offline tool for creating and interacting with Bitcoin Core wallet files.
.PP
@@ -100,7 +100,7 @@ info
.IP
Get wallet info
.SH COPYRIGHT
Copyright (C) 2009-2025 The Bitcoin Core developers
Copyright (C) 2009-2026 The Bitcoin Core developers
Please contribute if you find Bitcoin Core useful. Visit
<https://bitcoincore.org/> for further information about the software.

View File

@@ -1,7 +1,7 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH BITCOIN "1" "September 2025" "bitcoin v30.0.0rc2" "User Commands"
.TH BITCOIN "1" "January 2026" "bitcoin v30.2.0" "User Commands"
.SH NAME
bitcoin \- manual page for bitcoin v30.0.0rc2
bitcoin \- manual page for bitcoin v30.2.0
.SH SYNOPSIS
.B bitcoin
[\fI\,OPTIONS\/\fR] \fI\,COMMAND\/\fR...
@@ -46,7 +46,7 @@ chainstate [ARGS] Run bitcoin kernel chainstate util, equivalent to running 'bit
test [ARGS] Run unit tests, equivalent to running 'test_bitcoin [ARGS]'.
test\-gui [ARGS] Run GUI unit tests, equivalent to running 'test_bitcoin\-qt [ARGS]'.
.SH COPYRIGHT
Copyright (C) 2009-2025 The Bitcoin Core developers
Copyright (C) 2009-2026 The Bitcoin Core developers
Please contribute if you find Bitcoin Core useful. Visit
<https://bitcoincore.org/> for further information about the software.

View File

@@ -1,12 +1,12 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH BITCOIND "1" "September 2025" "bitcoind v30.0.0rc2" "User Commands"
.TH BITCOIND "1" "January 2026" "bitcoind v30.2.0" "User Commands"
.SH NAME
bitcoind \- manual page for bitcoind v30.0.0rc2
bitcoind \- manual page for bitcoind v30.2.0
.SH SYNOPSIS
.B bitcoind
[\fI\,options\/\fR]
.SH DESCRIPTION
Bitcoin Core daemon version v30.0.0rc2
Bitcoin Core daemon version v30.2.0 bitcoind
.PP
The Bitcoin Core daemon (bitcoind) is a headless program that connects to the Bitcoin network to validate and relay transactions and blocks, as well as relaying addresses.
.PP
@@ -695,13 +695,13 @@ Equivalent bytes per sigop in transactions for relay and mining
.HP
\fB\-datacarrier\fR
.IP
(DEPRECATED) Relay and mine data carrier transactions (default: 1)
Relay and mine data carrier transactions (default: 1)
.HP
\fB\-datacarriersize\fR
.IP
(DEPRECATED) Relay and mine transactions whose data\-carrying raw
scriptPubKeys in aggregate are of this size or less, allowing
multiple outputs (default: 100000)
Relay and mine transactions whose data\-carrying raw scriptPubKeys in
aggregate are of this size or less, allowing multiple outputs
(default: 100000)
.HP
\fB\-minrelaytxfee=\fR<amt>
.IP
@@ -817,7 +817,7 @@ subject to empty whitelists.
.IP
Accept command line and JSON\-RPC commands
.SH COPYRIGHT
Copyright (C) 2009-2025 The Bitcoin Core developers
Copyright (C) 2009-2026 The Bitcoin Core developers
Please contribute if you find Bitcoin Core useful. Visit
<https://bitcoincore.org/> for further information about the software.

View File

@@ -1 +1,91 @@
See https://github.com/bitcoin-core/bitcoin-devwiki/wiki/v30.0-Release-Notes-Draft.
v30.2 Release Notes
===================
Bitcoin Core version v30.2 is now available from:
<https://bitcoincore.org/bin/bitcoin-core-30.2/>
This release includes new features, various bug fixes and performance
improvements, as well as updated translations.
Please report bugs using the issue tracker at GitHub:
<https://github.com/bitcoin/bitcoin/issues>
To receive security and update notifications, please subscribe to:
<https://bitcoincore.org/en/list/announcements/join/>
How to Upgrade
==============
If you are running an older version, shut it down. Wait until it has completely
shut down (which might take a few minutes in some cases), then run the
installer (on Windows) or just copy over `/Applications/Bitcoin-Qt` (on macOS)
or `bitcoind`/`bitcoin-qt` (on Linux).
Upgrading directly from a version of Bitcoin Core that has reached its EOL is
possible, but it might take some time if the data directory needs to be migrated. Old
wallet versions of Bitcoin Core are generally supported.
Compatibility
==============
Bitcoin Core is supported and tested on operating systems using the
Linux Kernel 3.17+, macOS 13+, and Windows 10+. Bitcoin
Core should also work on most other Unix-like systems but is not as
frequently tested on them. It is not recommended to use Bitcoin Core on
unsupported systems.
Notable changes
===============
### Wallet
- #34156 wallet: fix unnamed legacy wallet migration failure
- #34215 wallettool: fix unnamed createfromdump failure walletsdir deletion
- #34221 test: migration, avoid backup name mismatch in default_wallet_failure
### IPC
- #33511 init: Fix Ctrl-C shutdown hangs during wait calls
### Build
- #33950 guix: reduce allowed exported symbols
- #34107 build: Update minimum required Boost version
- #34227 guix: Fix osslsigncode tests
### Test
- #34137 test: Avoid hard time.sleep(1) in feature_init.py
- #34226 wallet: test: Relative wallet failed migration cleanup
### Fuzz
- #34091 fuzz: doc: remove any mention to address_deserialize_v2
### Doc
- #34182 doc: Update OpenBSD Build Guide
### Misc
- #34174 doc: update copyright year to 2026
Credits
=======
Thanks to everyone who directly contributed to this release:
- Ava Chow
- brunoerg
- davidgumberg
- fanquake
- furszy
- Hennadii Stepanov
- MarcoFalke
- Ryan Ofsky
As well as to everyone that helped with translations on
[Transifex](https://explore.transifex.com/bitcoin/bitcoin/).

View File

@@ -111,7 +111,7 @@ For the period during which the notes are being edited on the wiki, the version
Generate list of authors:
git log --format='- %aN' v(current version, e.g. 25.0)..v(new version, e.g. 25.1) | grep -v 'merge-script' | sort -fiu
git log --format='- %aN' v(current version, e.g. 29.0)..v(new version, e.g. 30.0) | grep -v 'merge-script' | sort -fiu
### Setup and perform Guix builds

View File

@@ -576,12 +576,12 @@
# (default: 20)
#bytespersigop=1
# (DEPRECATED) Relay and mine data carrier transactions (default: 1)
# Relay and mine data carrier transactions (default: 1)
#datacarrier=1
# (DEPRECATED) Relay and mine transactions whose data-carrying raw
# scriptPubKeys in aggregate are of this size or less, allowing
# multiple outputs (default: 100000)
# Relay and mine transactions whose data-carrying raw scriptPubKeys in
# aggregate are of this size or less, allowing multiple outputs
# (default: 100000)
#datacarriersize=1
# Fees (in BTC/kvB) smaller than this are considered zero fee for

View File

@@ -292,7 +292,7 @@ if(BUILD_BITCOIN_BIN)
add_executable(bitcoin bitcoin.cpp)
add_windows_resources(bitcoin bitcoin-res.rc)
add_windows_application_manifest(bitcoin)
target_link_libraries(bitcoin core_interface bitcoin_util)
target_link_libraries(bitcoin core_interface bitcoin_common bitcoin_util)
install_binary_component(bitcoin HAS_MANPAGE)
endif()

View File

@@ -5,6 +5,7 @@
#include <bitcoin-build-config.h> // IWYU pragma: keep
#include <clientversion.h>
#include <common/args.h>
#include <util/fs.h>
#include <util/exec.h>
#include <util/strencodings.h>
@@ -47,7 +48,7 @@ Run '%s help' to see additional commands (e.g. for testing and debugging).
)";
struct CommandLine {
bool use_multiprocess{false};
std::optional<bool> use_multiprocess;
bool show_version{false};
bool show_help{false};
std::string_view command;
@@ -55,6 +56,7 @@ struct CommandLine {
};
CommandLine ParseCommandLine(int argc, char* argv[]);
bool UseMultiprocess(const CommandLine& cmd);
static void ExecCommand(const std::vector<const char*>& args, std::string_view argv0);
int main(int argc, char* argv[])
@@ -78,9 +80,9 @@ int main(int argc, char* argv[])
return EXIT_FAILURE;
}
} else if (cmd.command == "gui") {
args.emplace_back(cmd.use_multiprocess ? "bitcoin-gui" : "bitcoin-qt");
args.emplace_back(UseMultiprocess(cmd) ? "bitcoin-gui" : "bitcoin-qt");
} else if (cmd.command == "node") {
args.emplace_back(cmd.use_multiprocess ? "bitcoin-node" : "bitcoind");
args.emplace_back(UseMultiprocess(cmd) ? "bitcoin-node" : "bitcoind");
} else if (cmd.command == "rpc") {
args.emplace_back("bitcoin-cli");
// Since "bitcoin rpc" is a new interface that doesn't need to be
@@ -143,6 +145,30 @@ CommandLine ParseCommandLine(int argc, char* argv[])
return cmd;
}
bool UseMultiprocess(const CommandLine& cmd)
{
// If -m or -M options were explicitly specified, there is no need to
// further parse arguments to determine which to use.
if (cmd.use_multiprocess) return *cmd.use_multiprocess;
ArgsManager args;
args.SetDefaultFlags(ArgsManager::ALLOW_ANY);
std::string error_message;
auto argv{cmd.args};
argv.insert(argv.begin(), nullptr);
if (!args.ParseParameters(argv.size(), argv.data(), error_message)) {
tfm::format(std::cerr, "Warning: failed to parse subcommand command line options: %s\n", error_message);
}
if (!args.ReadConfigFiles(error_message, true)) {
tfm::format(std::cerr, "Warning: failed to parse subcommand config: %s\n", error_message);
}
args.SelectConfigNetwork(args.GetChainTypeString());
// If any -ipc* options are set these need to be processed by a
// multiprocess-capable binary.
return args.IsArgSet("-ipcbind") || args.IsArgSet("-ipcconnect") || args.IsArgSet("-ipcfd");
}
//! Execute the specified bitcoind, bitcoin-qt or other command line in `args`
//! using src, bin and libexec directory paths relative to this executable, where
//! the path to this executable is specified in `wrapper_argv0`.

View File

@@ -132,11 +132,16 @@ static bool ParseArgs(NodeContext& node, int argc, char* argv[])
return true;
}
static bool ProcessInitCommands(ArgsManager& args)
static bool ProcessInitCommands(interfaces::Init& init, ArgsManager& args)
{
// Process help and version before taking care about datadir
if (HelpRequested(args) || args.GetBoolArg("-version", false)) {
std::string strUsage = CLIENT_NAME " daemon version " + FormatFullVersion() + "\n";
std::string strUsage = CLIENT_NAME " daemon version " + FormatFullVersion();
if (const char* exe_name{init.exeName()}) {
strUsage += " ";
strUsage += exe_name;
}
strUsage += "\n";
if (args.GetBoolArg("-version", false)) {
strUsage += FormatParagraph(LicenseInfo());
@@ -277,7 +282,7 @@ MAIN_FUNCTION
ArgsManager& args = *Assert(node.args);
if (!ParseArgs(node, argc, argv)) return EXIT_FAILURE;
// Process early info return commands such as -help or -version
if (ProcessInitCommands(args)) return EXIT_SUCCESS;
if (ProcessInitCommands(*init, args)) return EXIT_SUCCESS;
// Start application
if (!AppInit(node) || !Assert(node.shutdown_signal)->wait()) {

View File

@@ -266,7 +266,13 @@ std::optional<unsigned int> ArgsManager::GetArgFlags(const std::string& name) co
return search->second.m_flags;
}
}
return std::nullopt;
return m_default_flags;
}
void ArgsManager::SetDefaultFlags(std::optional<unsigned int> flags)
{
LOCK(cs_args);
m_default_flags = flags;
}
fs::path ArgsManager::GetPathArg(std::string arg, const fs::path& default_value) const

View File

@@ -137,6 +137,7 @@ protected:
std::string m_network GUARDED_BY(cs_args);
std::set<std::string> m_network_only_args GUARDED_BY(cs_args);
std::map<OptionsCategory, std::map<std::string, Arg>> m_available_args GUARDED_BY(cs_args);
std::optional<unsigned int> m_default_flags GUARDED_BY(cs_args){};
bool m_accept_any_command GUARDED_BY(cs_args){true};
std::list<SectionInfo> m_config_sections GUARDED_BY(cs_args);
std::optional<fs::path> m_config_path GUARDED_BY(cs_args);
@@ -375,10 +376,15 @@ protected:
/**
* Return Flags for known arg.
* Return nullopt for unknown arg.
* Return default flags for unknown arg.
*/
std::optional<unsigned int> GetArgFlags(const std::string& name) const;
/**
* Set default flags to return for an unknown arg.
*/
void SetDefaultFlags(std::optional<unsigned int>);
/**
* Get settings file path, or return false if read-write settings were
* disabled with -nosettings.

View File

@@ -215,8 +215,6 @@ void InitContext(NodeContext& node)
node.shutdown_request = [&node] {
assert(node.shutdown_signal);
if (!(*node.shutdown_signal)()) return false;
// Wake any threads that may be waiting for the tip to change.
if (node.notifications) WITH_LOCK(node.notifications->m_tip_block_mutex, node.notifications->m_tip_block_cv.notify_all());
return true;
};
}
@@ -267,6 +265,8 @@ void Interrupt(NodeContext& node)
#if HAVE_SYSTEM
ShutdownNotify(*node.args);
#endif
// Wake any threads that may be waiting for the tip to change.
if (node.notifications) WITH_LOCK(node.notifications->m_tip_block_mutex, node.notifications->m_tip_block_cv.notify_all());
InterruptHTTPServer();
InterruptHTTPRPC();
InterruptRPC();
@@ -658,9 +658,9 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc)
argsman.AddArg("-dustrelayfee=<amt>", strprintf("Fee rate (in %s/kvB) used to define dust, the value of an output such that it will cost more than its value in fees at this fee rate to spend it. (default: %s)", CURRENCY_UNIT, FormatMoney(DUST_RELAY_TX_FEE)), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::NODE_RELAY);
argsman.AddArg("-acceptstalefeeestimates", strprintf("Read fee estimates even if they are stale (%sdefault: %u) fee estimates are considered stale if they are %s hours old", "regtest only; ", DEFAULT_ACCEPT_STALE_FEE_ESTIMATES, Ticks<std::chrono::hours>(MAX_FILE_AGE)), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-bytespersigop", strprintf("Equivalent bytes per sigop in transactions for relay and mining (default: %u)", DEFAULT_BYTES_PER_SIGOP), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
argsman.AddArg("-datacarrier", strprintf("(DEPRECATED) Relay and mine data carrier transactions (default: %u)", DEFAULT_ACCEPT_DATACARRIER), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
argsman.AddArg("-datacarrier", strprintf("Relay and mine data carrier transactions (default: %u)", DEFAULT_ACCEPT_DATACARRIER), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
argsman.AddArg("-datacarriersize",
strprintf("(DEPRECATED) Relay and mine transactions whose data-carrying raw scriptPubKeys in aggregate "
strprintf("Relay and mine transactions whose data-carrying raw scriptPubKeys in aggregate "
"are of this size or less, allowing multiple outputs (default: %u)",
MAX_OP_RETURN_RELAY),
ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
@@ -903,10 +903,6 @@ bool AppInitParameterInteraction(const ArgsManager& args)
InitWarning(_("Option '-checkpoints' is set but checkpoints were removed. This option has no effect."));
}
if (args.IsArgSet("-datacarriersize") || args.IsArgSet("-datacarrier")) {
InitWarning(_("Options '-datacarrier' or '-datacarriersize' are set but are marked as deprecated and are expected to be removed in a future version."));
}
// We no longer limit the orphanage based on number of transactions but keep the option to warn users who still have it in their config.
if (args.IsArgSet("-maxorphantx")) {
InitWarning(_("Option '-maxorphantx' is set but no longer has any effect (see release notes). Please remove it from your configuration."));

View File

@@ -39,6 +39,7 @@ public:
// bitcoin-node accepts the option, and bitcoin-gui accepts all bitcoin-node
// options and will start the node with those options.
bool canListenIpc() override { return true; }
const char* exeName() override { return EXE_NAME; }
node::NodeContext m_node;
std::unique_ptr<interfaces::Ipc> m_ipc;
};

View File

@@ -38,6 +38,7 @@ public:
std::unique_ptr<interfaces::Echo> makeEcho() override { return interfaces::MakeEcho(); }
interfaces::Ipc* ipc() override { return m_ipc.get(); }
bool canListenIpc() override { return true; }
const char* exeName() override { return EXE_NAME; }
node::NodeContext& m_node;
std::unique_ptr<interfaces::Ipc> m_ipc;
};

View File

@@ -16,6 +16,8 @@
namespace init {
namespace {
const char* EXE_NAME = "bitcoin-qt";
class BitcoinQtInit : public interfaces::Init
{
public:
@@ -32,6 +34,7 @@ public:
return MakeWalletLoader(chain, *Assert(m_node.args));
}
std::unique_ptr<interfaces::Echo> makeEcho() override { return interfaces::MakeEcho(); }
const char* exeName() override { return EXE_NAME; }
node::NodeContext m_node;
};
} // namespace

View File

@@ -18,6 +18,8 @@ using node::NodeContext;
namespace init {
namespace {
const char* EXE_NAME = "bitcoind";
class BitcoindInit : public interfaces::Init
{
public:
@@ -34,6 +36,7 @@ public:
return MakeWalletLoader(chain, *Assert(m_node.args));
}
std::unique_ptr<interfaces::Echo> makeEcho() override { return interfaces::MakeEcho(); }
const char* exeName() override { return EXE_NAME; }
NodeContext& m_node;
};
} // namespace

View File

@@ -38,6 +38,7 @@ public:
virtual std::unique_ptr<Echo> makeEcho() { return nullptr; }
virtual Ipc* ipc() { return nullptr; }
virtual bool canListenIpc() { return false; }
virtual const char* exeName() { return nullptr; }
};
//! Return implementation of Init interface for the node process. If the argv

View File

@@ -72,6 +72,11 @@ public:
* the tip is more than 20 minutes old.
*/
virtual std::unique_ptr<BlockTemplate> waitNext(const node::BlockWaitOptions options = {}) = 0;
/**
* Interrupts the current wait for the next block template.
*/
virtual void interruptWait() = 0;
};
//! Interface giving clients (RPC, Stratum v2 Template Provider in the future)

View File

@@ -33,6 +33,7 @@ interface BlockTemplate $Proxy.wrap("interfaces::BlockTemplate") {
getCoinbaseMerklePath @8 (context: Proxy.Context) -> (result: List(Data));
submitSolution @9 (context: Proxy.Context, version: UInt32, timestamp: UInt32, nonce: UInt32, coinbase :Data) -> (result: Bool);
waitNext @10 (context: Proxy.Context, options: BlockWaitOptions) -> (result: BlockTemplate);
interruptWait @11() -> ();
}
struct BlockCreateOptions $Proxy.wrap("node::BlockCreateOptions") {

View File

@@ -30,10 +30,36 @@
namespace ipc {
namespace capnp {
namespace {
void IpcLogFn(bool raise, std::string message)
BCLog::Level ConvertIPCLogLevel(mp::Log level)
{
LogDebug(BCLog::IPC, "%s\n", message);
if (raise) throw Exception(message);
switch (level) {
case mp::Log::Trace: return BCLog::Level::Trace;
case mp::Log::Debug: return BCLog::Level::Debug;
case mp::Log::Info: return BCLog::Level::Info;
case mp::Log::Warning: return BCLog::Level::Warning;
case mp::Log::Error: return BCLog::Level::Error;
case mp::Log::Raise: return BCLog::Level::Error;
} // no default case, so the compiler can warn about missing cases
// Be conservative and assume that if MP ever adds a new log level, it
// should only be shown at our most verbose level.
return BCLog::Level::Trace;
}
mp::Log GetRequestedIPCLogLevel()
{
if (LogAcceptCategory(BCLog::IPC, BCLog::Level::Trace)) return mp::Log::Trace;
if (LogAcceptCategory(BCLog::IPC, BCLog::Level::Debug)) return mp::Log::Debug;
// Info, Warning, and Error are logged unconditionally
return mp::Log::Info;
}
void IpcLogFn(mp::LogMessage message)
{
LogPrintLevel(BCLog::IPC, ConvertIPCLogLevel(message.level), "%s\n", message.message);
if (message.level == mp::Log::Raise) throw Exception(message.message);
}
class CapnpProtocol : public Protocol
@@ -62,7 +88,11 @@ public:
{
assert(!m_loop);
mp::g_thread_context.thread_name = mp::ThreadName(exe_name);
m_loop.emplace(exe_name, &IpcLogFn, &m_context);
mp::LogOptions opts = {
.log_fn = IpcLogFn,
.log_level = GetRequestedIPCLogLevel()
};
m_loop.emplace(exe_name, std::move(opts), &m_context);
if (ready_fn) ready_fn();
mp::ServeStream<messages::Init>(*m_loop, fd, init);
m_parent_connection = &m_loop->m_incoming_connections.back();
@@ -90,7 +120,11 @@ public:
std::promise<void> promise;
m_loop_thread = std::thread([&] {
util::ThreadRename("capnp-loop");
m_loop.emplace(exe_name, &IpcLogFn, &m_context);
mp::LogOptions opts = {
.log_fn = IpcLogFn,
.log_level = GetRequestedIPCLogLevel()
};
m_loop.emplace(exe_name, std::move(opts), &m_context);
m_loop_ref.emplace(*m_loop);
promise.set_value();
m_loop->loop();

5
src/ipc/libmultiprocess/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
# CMake artifacts
/*build*
# Git artifacts
*.patch

View File

@@ -2,7 +2,7 @@ CI_DESC="CI job using LLVM-based libraries and tools (clang, libc++, clang-tidy,
CI_DIR=build-llvm
NIX_ARGS=(--arg enableLibcxx true)
export CXX=clang++
export CXXFLAGS="-Werror -Wall -Wextra -Wpedantic -Wthread-safety-analysis -Wno-unused-parameter"
export CXXFLAGS="-Werror -Wall -Wextra -Wpedantic -Wthread-safety -Wno-unused-parameter"
CMAKE_ARGS=(
-G Ninja
-DMP_ENABLE_CLANG_TIDY=ON

View File

@@ -1,5 +1,5 @@
CI_DESC="CI job using old Cap'n Proto version"
CI_DESC="CI job using old Cap'n Proto and cmake versions"
CI_DIR=build-olddeps
export CXXFLAGS="-Werror -Wall -Wextra -Wpedantic -Wno-unused-parameter -Wno-error=array-bounds"
NIX_ARGS=(--argstr capnprotoVersion "0.7.1")
NIX_ARGS=(--argstr capnprotoVersion "0.7.1" --argstr cmakeVersion "3.12.4")
BUILD_ARGS=(-k)

View File

@@ -1,7 +1,8 @@
CI_DESC="CI job running ThreadSanitizer"
CI_DIR=build-sanitize
NIX_ARGS=(--arg enableLibcxx true --argstr libcxxSanitizers "Thread" --argstr capnprotoSanitizers "thread")
export CXX=clang++
export CXXFLAGS="-ggdb -Werror -Wall -Wextra -Wpedantic -Wthread-safety-analysis -Wno-unused-parameter -fsanitize=thread"
export CXXFLAGS="-ggdb -Werror -Wall -Wextra -Wpedantic -Wthread-safety -Wno-unused-parameter -fsanitize=thread"
CMAKE_ARGS=()
BUILD_ARGS=(-k -j4)
BUILD_TARGETS=(mptest)

View File

@@ -17,6 +17,21 @@ fi
[ -n "${CI_CLEAN-}" ] && rm -rf "${CI_DIR}"
cmake -B "$CI_DIR" "${CMAKE_ARGS[@]+"${CMAKE_ARGS[@]}"}"
cmake --build "$CI_DIR" -t "${BUILD_TARGETS[@]}" -- "${BUILD_ARGS[@]+"${BUILD_ARGS[@]}"}"
ctest --test-dir "$CI_DIR" --output-on-failure
cmake --version
cmake_ver=$(cmake --version | awk '/version/{print $3; exit}')
ver_ge() { [ "$(printf '%s\n' "$2" "$1" | sort -V | head -n1)" = "$2" ]; }
src_dir=$PWD
mkdir -p "$CI_DIR"
cd "$CI_DIR"
cmake "$src_dir" "${CMAKE_ARGS[@]+"${CMAKE_ARGS[@]}"}"
if ver_ge "$cmake_ver" "3.15"; then
cmake --build . -t "${BUILD_TARGETS[@]}" -- "${BUILD_ARGS[@]+"${BUILD_ARGS[@]}"}"
else
# Older versions of cmake can only build one target at a time with --target,
# and do not support -t shortcut
for t in "${BUILD_TARGETS[@]}"; do
cmake --build . --target "$t" -- "${BUILD_ARGS[@]+"${BUILD_ARGS[@]}"}"
done
fi
ctest --output-on-failure

View File

@@ -2,8 +2,8 @@
Given an interface description of an object with one or more methods, libmultiprocess generates:
* A C++ `ProxyClient` class with an implementation of each interface method that sends a request over a socket, waits for a response, and returns the result.
* A C++ `ProxyServer` class that listens for requests over a socket and calls a wrapped C++ object implementing the same interface to actually execute the requests.
* A C++ `ProxyClient` class template specialization with an implementation of each interface method that sends a request over a socket, waits for a response, and returns the result.
* A C++ `ProxyServer` class template specialization that listens for requests over a socket and calls a wrapped C++ object implementing the same interface to actually execute the requests.
The function call ⇆ request translation supports input and output arguments, standard types like `unique_ptr`, `vector`, `map`, and `optional`, and bidirectional calls between processes through interface pointer and `std::function` arguments.
@@ -15,7 +15,7 @@ Libmultiprocess acts as a pure wrapper or layer over the underlying protocol. Cl
### Internals
The `ProxyClient` and `ProxyServer` generated classes are not directly exposed to the user, as described in [usage.md](usage.md). Instead, they wrap c++ interfaces and appear to the user as pointers to an interface. They are first instantiated when calling `ConnectStream` and `ServeStream` respectively for creating the `InitInterface`. These methods establish connections through sockets, internally creating `Connection` objects wrapping a `capnp::RpcSystem` configured for client and server mode respectively.
The `ProxyClient` and `ProxyServer` generated classes are not directly exposed to the user, as described in [usage.md](usage.md). Instead, they wrap C++ interfaces and appear to the user as pointers to an interface. They are first instantiated when calling `ConnectStream` and `ServeStream` respectively for creating the `InitInterface`. These methods establish connections through sockets, internally creating `Connection` objects wrapping a `capnp::RpcSystem` configured for client and server mode respectively.
The `InitInterface` interface will typically have methods which return other interfaces, giving the connecting process the ability to call other functions in the serving process. Interfaces can also have methods accepting other interfaces as parameters, giving serving processes the ability to call back and invoke functions in connecting processes. Creating new interfaces does not create new connections, and typically many interface objects will share the same connection.
@@ -23,13 +23,13 @@ Both `ConnectStream` and `ServeStream` also require an instantiation of the `Eve
When a generated method on the `ProxyClient` is called, it calls `clientInvoke` with the capnp-translated types. `clientInvoke` creates a self-executing promise (`kj::TaskSet`) that drives the execution of the request and gives ownership of it to the `EventLoop`. `clientInvoke` blocks until a response is received, or until there is a call from the server that needs to run on the same client thread, using a `Waiter` object.
On the server side, the `capnp::RpcSystem` receives the capnp request and invokes the corresponding c++ method through the corresponding `ProxyServer` and the heavily templated `serverInvoke` triggering a `ServerCall`. Its return values from the actual c++ methods are copied into capnp responses by `ServerRet` and exceptions are caught and copied by `ServerExcept`. The two are connected through `ServerField`. The main method driving execution of a request is `PassField`, which is invoked through `ServerField`. Instantiated interfaces, or capabilities in capnp speak, are tracked and owned by the server's `capnp::RpcSystem`.
On the server side, the `capnp::RpcSystem` receives the capnp request and invokes the corresponding C++ method through the corresponding `ProxyServer` and the heavily templated `serverInvoke` triggering a `ServerCall`. The return values from the actual C++ methods are copied into capnp responses by `ServerRet` and exceptions are caught and copied by `ServerExcept`. The two are connected through `ServerField`. The main method driving execution of a request is `PassField`, which is invoked through `ServerField`. Instantiated interfaces, or capabilities in capnp speak, are tracked and owned by the server's `capnp::RpcSystem`.
## Interface descriptions
As explained in the [usage](usage.md) document, interface descriptions need to be consumed both by the _libmultiprocess_ code generator, and by C++ code that calls and implements the interfaces. The C++ code only needs to know about C++ arguments and return types, while the code generator only needs to know about capnp arguments and return types, but both need to know class and method names, so the corresponding `.h` and `.capnp` source files contain some of the same information, and have to be kept in sync manually when methods or parameters change. Despite the redundancy, reconciling the interface definitions is designed to be _straightforward_ and _safe_. _Straightforward_ because there is no need to write manual serialization code or use awkward intermediate types like [`UniValue`](https://github.com/bitcoin/bitcoin/blob/master/src/univalue/include/univalue.h) instead of native types. _Safe_ because if there are any inconsistencies between API and data definitions (even minor ones like using a narrow int data type for a wider int API input), there are errors at build time instead of errors or bugs at runtime.
In the future, it would be possible to combine API and data definitions together using [C++ attributes](https://en.cppreference.com/w/cpp/language/attributes). To do this we would add attributes to the API definition files, and then generate the data definitions from the API definitions and attributes. I didn't take this approach mostly because it would be extra work, but also because until c++ standardizes reflection, this would require either hooking into compiler APIs like https://github.com/RosettaCommons/binder, or parsing c++ code manually like http://www.swig.org/.
In the future, it would be possible to combine API and data definitions together using [C++ attributes](https://en.cppreference.com/w/cpp/language/attributes). To do this we would add attributes to the API definition files, and then generate the data definitions from the API definitions and attributes. I didn't take this approach mostly because it would be extra work, but also because until C++ standardizes reflection, this would require either hooking into compiler APIs like https://github.com/RosettaCommons/binder, or parsing C++ code manually like http://www.swig.org/.
## What is `kj`?
@@ -39,6 +39,6 @@ basis in this library to construct the event-loop necessary to service IPC reque
## Future directions
_libmultiprocess_ uses the [Cap'n Proto](https://capnproto.org) interface description language and protocol, but it could be extended or changed to use a different IDL/protocol like [gRPC](https://grpc.io). The nice thing about _Cap'n Proto_ compared to _gRPC_ and most other lower level protocols is that it allows interface pointers (_Services_ in gRPC parlance) to be passed as method arguments and return values, so object references and bidirectional requests work out of the box. Supporting a lower-level protocol would require writing adding maps and tracking code to proxy objects.
_libmultiprocess_ uses the [Cap'n Proto](https://capnproto.org) interface description language and protocol, but it could be extended or changed to use a different IDL/protocol like [gRPC](https://grpc.io). The nice thing about _Cap'n Proto_ compared to _gRPC_ and most other lower level protocols is that it allows interface pointers (_Services_ in gRPC parlance) to be passed as method arguments and return values, so object references and bidirectional requests work out of the box. Supporting a lower-level protocol would require adding maps and tracking code to proxy objects.
_libmultiprocess_ is currently compatible with sandboxing but could add platform-specific sandboxing support or integration with a sandboxing library like [SAPI](https://github.com/google/sandboxed-api).

View File

@@ -4,9 +4,9 @@
_libmultiprocess_ is a library and code generator that allows calling C++ class interfaces across different processes. For an interface to be available from other processes, it needs two definitions:
- An **API definition** declaring how the interface is called. Included examples: [calculator.h](https://github.com/bitcoin-core/libmultiprocess/blob/master/example/calculator.h), [printer.h](https://github.com/bitcoin-core/libmultiprocess/blob/master/example/printer.h), [init.h](https://github.com/bitcoin-core/libmultiprocess/blob/master/example/init.h). Bitcoin examples: [node.h](https://github.com/ryanofsky/bitcoin/blob/ipc-export/src/interfaces/node.h), [wallet.h](https://github.com/ryanofsky/bitcoin/blob/ipc-export/src/interfaces/wallet.h), [echo.h](https://github.com/ryanofsky/bitcoin/blob/ipc-export/src/interfaces/echo.h), [init.h](https://github.com/ryanofsky/bitcoin/blob/ipc-export/src/interfaces/init.h).
- An **API definition** declaring how the interface is called. Included examples: [calculator.h](../example/calculator.h), [printer.h](../example/printer.h), [init.h](../example/init.h). Bitcoin examples: [node.h](https://github.com/bitcoin/bitcoin/blob/master/src/interfaces/node.h), [wallet.h](https://github.com/bitcoin/bitcoin/blob/master/src/interfaces/wallet.h), [echo.h](https://github.com/bitcoin/bitcoin/blob/master/src/interfaces/echo.h), [init.h](https://github.com/bitcoin/bitcoin/blob/master/src/interfaces/init.h).
- A **data definition** declaring how interface calls get sent across the wire. Included examples: [calculator.capnp](https://github.com/bitcoin-core/libmultiprocess/blob/master/example/calculator.capnp), [printer.capnp](https://github.com/bitcoin-core/libmultiprocess/blob/master/example/printer.capnp), [init.capnp](https://github.com/bitcoin-core/libmultiprocess/blob/master/example/init.capnp). Bitcoin examples: [node.capnp](https://github.com/ryanofsky/bitcoin/blob/ipc-export/src/ipc/capnp/node.capnp), [wallet.capnp](https://github.com/ryanofsky/bitcoin/blob/ipc-export/src/ipc/capnp/wallet.capnp), [echo.capnp](https://github.com/ryanofsky/bitcoin/blob/ipc-export/src/ipc/capnp/echo.capnp), [init.capnp](https://github.com/ryanofsky/bitcoin/blob/ipc-export/src/ipc/capnp/init.capnp).
- A **data definition** declaring how interface calls get sent across the wire. Included examples: [calculator.capnp](../example/calculator.capnp), [printer.capnp](../example/printer.capnp), [init.capnp](../example/init.capnp). Bitcoin examples: [node.capnp](https://github.com/ryanofsky/bitcoin/blob/ipc-export/src/ipc/capnp/node.capnp), [wallet.capnp](https://github.com/ryanofsky/bitcoin/blob/ipc-export/src/ipc/capnp/wallet.capnp), [echo.capnp](https://github.com/bitcoin/bitcoin/blob/master/src/ipc/capnp/echo.capnp), [init.capnp](https://github.com/bitcoin/bitcoin/blob/master/src/ipc/capnp/init.capnp).
The `*.capnp` data definition files are consumed by the _libmultiprocess_ code generator and each `X.capnp` file generates `X.capnp.c++`, `X.capnp.h`, `X.capnp.proxy-client.c++`, `X.capnp.proxy-server.c++`, `X.capnp.proxy-types.c++`, `X.capnp.proxy-types.h`, and `X.capnp.proxy.h` output files. The generated files include `mp::ProxyClient<Interface>` and `mp::ProxyServer<Interface>` class specializations for all the interfaces in the `.capnp` files. These allow methods on C++ objects in one process to be called from other processes over IPC sockets.

View File

@@ -9,6 +9,7 @@
#include <charconv>
#include <cstring>
#include <fstream>
#include <functional>
#include <iostream>
#include <kj/async.h>
#include <kj/common.h>
@@ -37,6 +38,7 @@ public:
}
};
// Exercises deprecated log callback signature
static void LogPrint(bool raise, const std::string& message)
{
if (raise) throw std::runtime_error(message);

View File

@@ -35,10 +35,10 @@ static auto Spawn(mp::EventLoop& loop, const std::string& process_argv0, const s
return std::make_tuple(mp::ConnectStream<InitInterface>(loop, fd), pid);
}
static void LogPrint(bool raise, const std::string& message)
static void LogPrint(mp::LogMessage log_data)
{
if (raise) throw std::runtime_error(message);
std::ofstream("debug.log", std::ios_base::app) << message << std::endl;
if (log_data.level == mp::Log::Raise) throw std::runtime_error(log_data.message);
std::ofstream("debug.log", std::ios_base::app) << log_data.message << std::endl;
}
int main(int argc, char** argv)

View File

@@ -32,10 +32,10 @@ public:
std::unique_ptr<Printer> makePrinter() override { return std::make_unique<PrinterImpl>(); }
};
static void LogPrint(bool raise, const std::string& message)
static void LogPrint(mp::LogMessage log_data)
{
if (raise) throw std::runtime_error(message);
std::ofstream("debug.log", std::ios_base::app) << message << std::endl;
if (log_data.level == mp::Log::Raise) throw std::runtime_error(log_data.message);
std::ofstream("debug.log", std::ios_base::app) << log_data.message << std::endl;
}
int main(int argc, char** argv)

View File

@@ -66,8 +66,6 @@ struct ProxyClient<Thread> : public ProxyClientBase<Thread, ::capnp::Void>
ProxyClient(const ProxyClient&) = delete;
~ProxyClient();
void setDisconnectCallback(const std::function<void()>& fn);
//! Reference to callback function that is run if there is a sudden
//! disconnect and the Connection object is destroyed before this
//! ProxyClient<Thread> object. The callback will destroy this object and
@@ -100,36 +98,29 @@ public:
EventLoop& m_loop;
};
using LogFn = std::function<void(bool raise, std::string message)>;
class Logger
{
public:
Logger(bool raise, LogFn& fn) : m_raise(raise), m_fn(fn) {}
Logger(Logger&& logger) : m_raise(logger.m_raise), m_fn(logger.m_fn), m_buffer(std::move(logger.m_buffer)) {}
~Logger() noexcept(false)
{
if (m_fn) m_fn(m_raise, m_buffer.str());
}
template <typename T>
friend Logger& operator<<(Logger& logger, T&& value)
{
if (logger.m_fn) logger.m_buffer << std::forward<T>(value);
return logger;
}
template <typename T>
friend Logger& operator<<(Logger&& logger, T&& value)
{
return logger << std::forward<T>(value);
}
bool m_raise;
LogFn& m_fn;
std::ostringstream m_buffer;
//! Log flags. Update stringify function if changed!
enum class Log {
Trace = 0,
Debug,
Info,
Warning,
Error,
Raise,
};
kj::StringPtr KJ_STRINGIFY(Log flags);
struct LogMessage {
//! Message to be logged
std::string message;
//! The severity level of this message
Log level;
};
using LogFn = std::function<void(LogMessage)>;
struct LogOptions {
//! External logging callback.
@@ -138,8 +129,60 @@ struct LogOptions {
//! Maximum number of characters to use when representing
//! request and response structs as strings.
size_t max_chars{200};
//! Messages with a severity level less than log_level will not be
//! reported.
Log log_level{Log::Trace};
};
class Logger
{
public:
Logger(const LogOptions& options, Log log_level) : m_options(options), m_log_level(log_level) {}
Logger(Logger&&) = delete;
Logger& operator=(Logger&&) = delete;
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
~Logger() noexcept(false)
{
if (enabled()) m_options.log_fn({std::move(m_buffer).str(), m_log_level});
}
template <typename T>
friend Logger& operator<<(Logger& logger, T&& value)
{
if (logger.enabled()) logger.m_buffer << std::forward<T>(value);
return logger;
}
template <typename T>
friend Logger& operator<<(Logger&& logger, T&& value)
{
return logger << std::forward<T>(value);
}
explicit operator bool() const
{
return enabled();
}
private:
bool enabled() const
{
return m_options.log_fn && m_log_level >= m_options.log_level;
}
const LogOptions& m_options;
Log m_log_level;
std::ostringstream m_buffer;
};
#define MP_LOGPLAIN(loop, ...) if (mp::Logger logger{(loop).m_log_opts, __VA_ARGS__}; logger) logger
#define MP_LOG(loop, ...) MP_LOGPLAIN(loop, __VA_ARGS__) << "{" << LongThreadName((loop).m_exe_name) << "} "
std::string LongThreadName(const char* exe_name);
//! Event loop implementation.
@@ -170,8 +213,19 @@ std::string LongThreadName(const char* exe_name);
class EventLoop
{
public:
//! Construct event loop object.
EventLoop(const char* exe_name, LogFn log_fn, void* context = nullptr);
//! Construct event loop object with default logging options.
EventLoop(const char* exe_name, LogFn log_fn, void* context = nullptr)
: EventLoop(exe_name, LogOptions{std::move(log_fn)}, context){}
//! Construct event loop object with specified logging options.
EventLoop(const char* exe_name, LogOptions log_opts, void* context = nullptr);
//! Backwards-compatible constructor for previous (deprecated) logging callback signature
EventLoop(const char* exe_name, std::function<void(bool, std::string)> old_callback, void* context = nullptr)
: EventLoop(exe_name,
LogFn{[old_callback = std::move(old_callback)](LogMessage log_data) {old_callback(log_data.level == Log::Raise, std::move(log_data.message));}},
context){}
~EventLoop();
//! Run event loop. Does not return until shutdown. This should only be
@@ -212,15 +266,6 @@ public:
//! Check if loop should exit.
bool done() const MP_REQUIRES(m_mutex);
Logger log()
{
Logger logger(false, m_log_opts.log_fn);
logger << "{" << LongThreadName(m_exe_name) << "} ";
return logger;
}
Logger logPlain() { return {false, m_log_opts.log_fn}; }
Logger raise() { return {true, m_log_opts.log_fn}; }
//! Process name included in thread names so combined debug output from
//! multiple processes is easier to understand.
const char* m_exe_name;
@@ -283,18 +328,19 @@ struct Waiter
Waiter() = default;
template <typename Fn>
void post(Fn&& fn)
bool post(Fn&& fn)
{
const std::unique_lock<std::mutex> lock(m_mutex);
assert(!m_fn);
const Lock lock(m_mutex);
if (m_fn) return false;
m_fn = std::forward<Fn>(fn);
m_cv.notify_all();
return true;
}
template <class Predicate>
void wait(std::unique_lock<std::mutex>& lock, Predicate pred)
void wait(Lock& lock, Predicate pred)
{
m_cv.wait(lock, [&] {
m_cv.wait(lock.m_lock, [&]() MP_REQUIRES(m_mutex) {
// Important for this to be "while (m_fn)", not "if (m_fn)" to avoid
// a lost-wakeup bug. A new m_fn and m_cv notification might be sent
// after the fn() call and before the lock.lock() call in this loop
@@ -317,9 +363,9 @@ struct Waiter
//! mutexes than necessary. This mutex can be held at the same time as
//! EventLoop::m_mutex as long as Waiter::mutex is locked first and
//! EventLoop::m_mutex is locked second.
std::mutex m_mutex;
Mutex m_mutex;
std::condition_variable m_cv;
std::optional<kj::Function<void()>> m_fn;
std::optional<kj::Function<void()>> m_fn MP_GUARDED_BY(m_mutex);
};
//! Object holding network & rpc state associated with either an incoming server
@@ -544,29 +590,73 @@ void ProxyServerBase<Interface, Impl>::invokeDestroy()
CleanupRun(m_context.cleanup_fns);
}
using ConnThreads = std::map<Connection*, ProxyClient<Thread>>;
//! Map from Connection to local or remote thread handle which will be used over
//! that connection. This map will typically only contain one entry, but can
//! contain multiple if a single thread makes IPC calls over multiple
//! connections. A std::optional value type is used to avoid the map needing to
//! be locked while ProxyClient<Thread> objects are constructed, see
//! ThreadContext "Synchronization note" below.
using ConnThreads = std::map<Connection*, std::optional<ProxyClient<Thread>>>;
using ConnThread = ConnThreads::iterator;
// Retrieve ProxyClient<Thread> object associated with this connection from a
// map, or create a new one and insert it into the map. Return map iterator and
// inserted bool.
std::tuple<ConnThread, bool> SetThread(ConnThreads& threads, std::mutex& mutex, Connection* connection, const std::function<Thread::Client()>& make_thread);
std::tuple<ConnThread, bool> SetThread(GuardedRef<ConnThreads> threads, Connection* connection, const std::function<Thread::Client()>& make_thread);
//! The thread_local ThreadContext g_thread_context struct provides information
//! about individual threads and a way of communicating between them. Because
//! it's a thread local struct, each ThreadContext instance is initialized by
//! the thread that owns it.
//!
//! ThreadContext is used for any client threads created externally which make
//! IPC calls, and for server threads created by
//! ProxyServer<ThreadMap>::makeThread() which execute IPC calls for clients.
//!
//! In both cases, the struct holds information like the thread name, and a
//! Waiter object where the EventLoop can post incoming IPC requests to execute
//! on the thread. The struct also holds ConnThread maps associating the thread
//! with local and remote ProxyClient<Thread> objects.
struct ThreadContext
{
//! Identifying string for debug.
std::string thread_name;
//! Waiter object used to allow client threads blocked waiting for a server
//! response to execute callbacks made from the client's corresponding
//! server thread.
//! Waiter object used to allow remote clients to execute code on this
//! thread. For server threads created by
//! ProxyServer<ThreadMap>::makeThread(), this is initialized in that
//! function. Otherwise, for client threads created externally, this is
//! initialized the first time the thread tries to make an IPC call. Having
//! a waiter is necessary for threads making IPC calls in case a server they
//! are calling expects them to execute a callback during the call, before
//! it sends a response.
//!
//! For IPC client threads, the Waiter pointer is never cleared and the Waiter
//! just gets destroyed when the thread does. For server threads created by
//! makeThread(), this pointer is set to null in the ~ProxyServer<Thread> as
//! a signal for the thread to exit and destroy itself. In both cases, the
//! same Waiter object is used across different calls and only created and
//! destroyed once for the lifetime of the thread.
std::unique_ptr<Waiter> waiter = nullptr;
//! When client is making a request to a server, this is the
//! `callbackThread` argument it passes in the request, used by the server
//! in case it needs to make callbacks into the client that need to execute
//! while the client is waiting. This will be set to a local thread object.
ConnThreads callback_threads;
//!
//! Synchronization note: The callback_thread and request_thread maps are
//! only ever accessed internally by this thread's destructor and externally
//! by Cap'n Proto event loop threads. Since it's possible for IPC client
//! threads to make calls over different connections that could have
//! different event loops, these maps are guarded by Waiter::m_mutex in case
//! different event loop threads add or remove map entries simultaneously.
//! However, individual ProxyClient<Thread> objects in the maps will only be
//! associated with one event loop and guarded by EventLoop::m_mutex. So
//! Waiter::m_mutex does not need to be held while accessing individual
//! ProxyClient<Thread> instances, and may even need to be released to
//! respect lock order and avoid locking Waiter::m_mutex before
//! EventLoop::m_mutex.
ConnThreads callback_threads MP_GUARDED_BY(waiter->m_mutex);
//! When client is making a request to a server, this is the `thread`
//! argument it passes in the request, used to control which thread on
@@ -575,7 +665,9 @@ struct ThreadContext
//! by makeThread. If a client call is being made from a thread currently
//! handling a server request, this will be set to the `callbackThread`
//! request thread argument passed in that request.
ConnThreads request_threads;
//!
//! Synchronization note: \ref callback_threads note applies here as well.
ConnThreads request_threads MP_GUARDED_BY(waiter->m_mutex);
//! Whether this thread is a capnp event loop thread. Not really used except
//! to assert false if there's an attempt to execute a blocking operation
@@ -598,7 +690,7 @@ std::unique_ptr<ProxyClient<InitInterface>> ConnectStream(EventLoop& loop, int f
init_client = connection->m_rpc_system->bootstrap(ServerVatId().vat_id).castAs<InitInterface>();
Connection* connection_ptr = connection.get();
connection->onDisconnect([&loop, connection_ptr] {
loop.log() << "IPC client: unexpected network disconnect.";
MP_LOG(loop, Log::Warning) << "IPC client: unexpected network disconnect.";
delete connection_ptr;
});
});
@@ -621,7 +713,7 @@ void _Serve(EventLoop& loop, kj::Own<kj::AsyncIoStream>&& stream, InitImpl& init
});
auto it = loop.m_incoming_connections.begin();
it->onDisconnect([&loop, it] {
loop.log() << "IPC server: socket disconnected.";
MP_LOG(loop, Log::Info) << "IPC server: socket disconnected.";
loop.m_incoming_connections.erase(it);
});
}

View File

@@ -568,7 +568,7 @@ template <typename Client>
void clientDestroy(Client& client)
{
if (client.m_context.connection) {
client.m_context.loop->log() << "IPC client destroy " << typeid(client).name();
MP_LOG(*client.m_context.loop, Log::Info) << "IPC client destroy " << typeid(client).name();
} else {
KJ_LOG(INFO, "IPC interrupted client destroy", typeid(client).name());
}
@@ -577,7 +577,7 @@ void clientDestroy(Client& client)
template <typename Server>
void serverDestroy(Server& server)
{
server.m_context.loop->log() << "IPC server destroy " << typeid(server).name();
MP_LOG(*server.m_context.loop, Log::Info) << "IPC server destroy " << typeid(server).name();
}
//! Entry point called by generated client code that looks like:
@@ -605,7 +605,7 @@ void clientInvoke(ProxyClient& proxy_client, const GetRequest& get_request, Fiel
// declaration so the server method runs in a dedicated thread.
assert(!g_thread_context.loop_thread);
g_thread_context.waiter = std::make_unique<Waiter>();
proxy_client.m_context.loop->logPlain()
MP_LOGPLAIN(*proxy_client.m_context.loop, Log::Info)
<< "{" << g_thread_context.thread_name
<< "} IPC client first request from current thread, constructing waiter";
}
@@ -617,7 +617,7 @@ void clientInvoke(ProxyClient& proxy_client, const GetRequest& get_request, Fiel
const char* disconnected = nullptr;
proxy_client.m_context.loop->sync([&]() {
if (!proxy_client.m_context.connection) {
const std::unique_lock<std::mutex> lock(thread_context.waiter->m_mutex);
const Lock lock(thread_context.waiter->m_mutex);
done = true;
disconnected = "IPC client method called after disconnect.";
thread_context.waiter->m_cv.notify_all();
@@ -629,22 +629,26 @@ void clientInvoke(ProxyClient& proxy_client, const GetRequest& get_request, Fiel
using FieldList = typename ProxyClientMethodTraits<typename Request::Params>::Fields;
invoke_context.emplace(*proxy_client.m_context.connection, thread_context);
IterateFields().handleChain(*invoke_context, request, FieldList(), typename FieldObjs::BuildParams{&fields}...);
proxy_client.m_context.loop->logPlain()
MP_LOGPLAIN(*proxy_client.m_context.loop, Log::Debug)
<< "{" << thread_context.thread_name << "} IPC client send "
<< TypeName<typename Request::Params>() << " " << LogEscape(request.toString(), proxy_client.m_context.loop->m_log_opts.max_chars);
<< TypeName<typename Request::Params>();
MP_LOGPLAIN(*proxy_client.m_context.loop, Log::Trace)
<< "send data: " << LogEscape(request.toString(), proxy_client.m_context.loop->m_log_opts.max_chars);
proxy_client.m_context.loop->m_task_set->add(request.send().then(
[&](::capnp::Response<typename Request::Results>&& response) {
proxy_client.m_context.loop->logPlain()
MP_LOGPLAIN(*proxy_client.m_context.loop, Log::Debug)
<< "{" << thread_context.thread_name << "} IPC client recv "
<< TypeName<typename Request::Results>() << " " << LogEscape(response.toString(), proxy_client.m_context.loop->m_log_opts.max_chars);
<< TypeName<typename Request::Results>();
MP_LOGPLAIN(*proxy_client.m_context.loop, Log::Trace)
<< "recv data: " << LogEscape(response.toString(), proxy_client.m_context.loop->m_log_opts.max_chars);
try {
IterateFields().handleChain(
*invoke_context, response, FieldList(), typename FieldObjs::ReadResults{&fields}...);
} catch (...) {
exception = std::current_exception();
}
const std::unique_lock<std::mutex> lock(thread_context.waiter->m_mutex);
const Lock lock(thread_context.waiter->m_mutex);
done = true;
thread_context.waiter->m_cv.notify_all();
},
@@ -653,20 +657,20 @@ void clientInvoke(ProxyClient& proxy_client, const GetRequest& get_request, Fiel
disconnected = "IPC client method call interrupted by disconnect.";
} else {
kj_exception = kj::str("kj::Exception: ", e).cStr();
proxy_client.m_context.loop->logPlain()
MP_LOGPLAIN(*proxy_client.m_context.loop, Log::Info)
<< "{" << thread_context.thread_name << "} IPC client exception " << kj_exception;
}
const std::unique_lock<std::mutex> lock(thread_context.waiter->m_mutex);
const Lock lock(thread_context.waiter->m_mutex);
done = true;
thread_context.waiter->m_cv.notify_all();
}));
});
std::unique_lock<std::mutex> lock(thread_context.waiter->m_mutex);
Lock lock(thread_context.waiter->m_mutex);
thread_context.waiter->wait(lock, [&done]() { return done; });
if (exception) std::rethrow_exception(exception);
if (!kj_exception.empty()) proxy_client.m_context.loop->raise() << kj_exception;
if (disconnected) proxy_client.m_context.loop->raise() << disconnected;
if (!kj_exception.empty()) MP_LOGPLAIN(*proxy_client.m_context.loop, Log::Raise) << kj_exception;
if (disconnected) MP_LOGPLAIN(*proxy_client.m_context.loop, Log::Raise) << disconnected;
}
//! Invoke callable `fn()` that may return void. If it does return void, replace
@@ -700,8 +704,10 @@ kj::Promise<void> serverInvoke(Server& server, CallContext& call_context, Fn fn)
using Results = typename decltype(call_context.getResults())::Builds;
int req = ++server_reqs;
server.m_context.loop->log() << "IPC server recv request #" << req << " "
<< TypeName<typename Params::Reads>() << " " << LogEscape(params.toString(), server.m_context.loop->m_log_opts.max_chars);
MP_LOG(*server.m_context.loop, Log::Debug) << "IPC server recv request #" << req << " "
<< TypeName<typename Params::Reads>();
MP_LOG(*server.m_context.loop, Log::Trace) << "request data: "
<< LogEscape(params.toString(), server.m_context.loop->m_log_opts.max_chars);
try {
using ServerContext = ServerInvokeContext<Server, CallContext>;
@@ -717,14 +723,15 @@ kj::Promise<void> serverInvoke(Server& server, CallContext& call_context, Fn fn)
return ReplaceVoid([&]() { return fn.invoke(server_context, ArgList()); },
[&]() { return kj::Promise<CallContext>(kj::mv(call_context)); })
.then([&server, req](CallContext call_context) {
server.m_context.loop->log() << "IPC server send response #" << req << " " << TypeName<Results>()
<< " " << LogEscape(call_context.getResults().toString(), server.m_context.loop->m_log_opts.max_chars);
MP_LOG(*server.m_context.loop, Log::Debug) << "IPC server send response #" << req << " " << TypeName<Results>();
MP_LOG(*server.m_context.loop, Log::Trace) << "response data: "
<< LogEscape(call_context.getResults().toString(), server.m_context.loop->m_log_opts.max_chars);
});
} catch (const std::exception& e) {
server.m_context.loop->log() << "IPC server unhandled exception: " << e.what();
MP_LOG(*server.m_context.loop, Log::Error) << "IPC server unhandled exception: " << e.what();
throw;
} catch (...) {
server.m_context.loop->log() << "IPC server unhandled exception";
MP_LOG(*server.m_context.loop, Log::Error) << "IPC server unhandled exception";
throw;
}
}

View File

@@ -25,7 +25,7 @@ void CustomBuildField(TypeList<>,
// Also store the Thread::Client reference in the callback_threads map so
// future calls over this connection can reuse it.
auto [callback_thread, _]{SetThread(
thread_context.callback_threads, thread_context.waiter->m_mutex, &connection,
GuardedRef{thread_context.waiter->m_mutex, thread_context.callback_threads}, &connection,
[&] { return connection.m_threads.add(kj::heap<ProxyServer<Thread>>(thread_context, std::thread{})); })};
// Call remote ThreadMap.makeThread function so server will create a
@@ -43,12 +43,12 @@ void CustomBuildField(TypeList<>,
return request.send().getResult(); // Nonblocking due to capnp request pipelining.
}};
auto [request_thread, _1]{SetThread(
thread_context.request_threads, thread_context.waiter->m_mutex,
GuardedRef{thread_context.waiter->m_mutex, thread_context.request_threads},
&connection, make_request_thread)};
auto context = output.init();
context.setThread(request_thread->second.m_client);
context.setCallbackThread(callback_thread->second.m_client);
context.setThread(request_thread->second->m_client);
context.setCallbackThread(callback_thread->second->m_client);
}
//! PassField override for mp.Context arguments. Return asynchronously and call
@@ -89,29 +89,39 @@ auto PassField(Priority<1>, TypeList<>, ServerContext& server_context, const Fn&
// need to update the map.
auto& thread_context = g_thread_context;
auto& request_threads = thread_context.request_threads;
auto [request_thread, inserted]{SetThread(
request_threads, thread_context.waiter->m_mutex,
server.m_context.connection,
[&] { return context_arg.getCallbackThread(); })};
ConnThread request_thread;
bool inserted;
server.m_context.loop->sync([&] {
std::tie(request_thread, inserted) = SetThread(
GuardedRef{thread_context.waiter->m_mutex, request_threads}, server.m_context.connection,
[&] { return context_arg.getCallbackThread(); });
});
// If an entry was inserted into the requests_threads map,
// If an entry was inserted into the request_threads map,
// remove it after calling fn.invoke. If an entry was not
// inserted, one already existed, meaning this must be a
// recursive call (IPC call calling back to the caller which
// makes another IPC call), so avoid modifying the map.
const bool erase_thread{inserted};
KJ_DEFER(if (erase_thread) {
std::unique_lock<std::mutex> lock(thread_context.waiter->m_mutex);
// Call erase here with a Connection* argument instead
// of an iterator argument, because the `request_thread`
// iterator may be invalid if the connection is closed
// during this function call. More specifically, the
// iterator may be invalid because SetThread adds a
// cleanup callback to the Connection destructor that
// erases the thread from the map, and also because the
// ProxyServer<Thread> destructor calls
// request_threads.clear().
request_threads.erase(server.m_context.connection);
// Erase the request_threads entry on the event loop
// thread with loop->sync(), so if the connection is
// broken there is not a race between this thread and
// the disconnect handler trying to destroy the thread
// client object.
server.m_context.loop->sync([&] {
// Look up the thread again without using existing
// iterator since entry may no longer be there after
// a disconnect. Destroy node after releasing
// Waiter::m_mutex, so the ProxyClient<Thread>
// destructor is able to use EventLoop::mutex
// without violating lock order.
ConnThreads::node_type removed;
{
Lock lock(thread_context.waiter->m_mutex);
removed = request_threads.extract(server.m_context.connection);
}
});
});
fn.invoke(server_context, args...);
}
@@ -140,11 +150,16 @@ auto PassField(Priority<1>, TypeList<>, ServerContext& server_context, const Fn&
// thread.
KJ_IF_MAYBE (thread_server, perhaps) {
const auto& thread = static_cast<ProxyServer<Thread>&>(*thread_server);
server.m_context.loop->log()
MP_LOG(*server.m_context.loop, Log::Debug)
<< "IPC server post request #" << req << " {" << thread.m_thread_context.thread_name << "}";
thread.m_thread_context.waiter->post(std::move(invoke));
if (!thread.m_thread_context.waiter->post(std::move(invoke))) {
MP_LOG(*server.m_context.loop, Log::Error)
<< "IPC server error request #" << req
<< " {" << thread.m_thread_context.thread_name << "}" << ", thread busy";
throw std::runtime_error("thread busy");
}
} else {
server.m_context.loop->log()
MP_LOG(*server.m_context.loop, Log::Error)
<< "IPC server error request #" << req << ", missing thread to execute request";
throw std::runtime_error("invalid thread handle");
}

View File

@@ -182,6 +182,17 @@ public:
std::unique_lock<std::mutex> m_lock;
};
template<typename T>
struct GuardedRef
{
Mutex& mutex;
T& ref MP_GUARDED_BY(mutex);
};
// CTAD for Clang 16: GuardedRef{mutex, x} -> GuardedRef<decltype(x)>
template <class U>
GuardedRef(Mutex&, U&) -> GuardedRef<U>;
//! Analog to std::lock_guard that unlocks instead of locks.
template <typename Lock>
struct UnlockGuard

View File

@@ -3,11 +3,19 @@
, enableLibcxx ? false # Whether to use libc++ toolchain and libraries instead of libstdc++
, minimal ? false # Whether to create minimal shell without extra tools (faster when cross compiling)
, capnprotoVersion ? null
, capnprotoSanitizers ? null # Optional sanitizers to build cap'n proto with
, cmakeVersion ? null
, libcxxSanitizers ? null # Optional LLVM_USE_SANITIZER value to use for libc++, see https://llvm.org/docs/CMake.html
}:
let
lib = pkgs.lib;
llvm = crossPkgs.llvmPackages_20;
llvmBase = crossPkgs.llvmPackages_21;
llvm = llvmBase // lib.optionalAttrs (libcxxSanitizers != null) {
libcxx = llvmBase.libcxx.override {
devExtraCmakeFlags = [ "-DLLVM_USE_SANITIZER=${libcxxSanitizers}" ];
};
};
capnprotoHashes = {
"0.7.0" = "sha256-Y/7dUOQPDHjniuKNRw3j8dG1NI9f/aRWpf8V0WzV9k8=";
"0.7.1" = "sha256-3cBpVmpvCXyqPUXDp12vCFCk32ZXWpkdOliNH37UwWE=";
@@ -34,15 +42,36 @@ let
} // (lib.optionalAttrs (lib.versionOlder capnprotoVersion "0.10") {
env = { }; # Drop -std=c++20 flag forced by nixpkgs
}));
capnproto = capnprotoBase.override (lib.optionalAttrs enableLibcxx { clangStdenv = llvm.libcxxStdenv; });
capnproto = (capnprotoBase.overrideAttrs (old: lib.optionalAttrs (capnprotoSanitizers != null) {
env = (old.env or { }) // {
CXXFLAGS =
lib.concatStringsSep " " [
(old.env.CXXFLAGS or "")
"-fsanitize=${capnprotoSanitizers}"
"-fno-omit-frame-pointer"
"-g"
];
};
})).override (lib.optionalAttrs enableLibcxx { clangStdenv = llvm.libcxxStdenv; });
clang = if enableLibcxx then llvm.libcxxClang else llvm.clang;
clang-tools = llvm.clang-tools.override { inherit enableLibcxx; };
cmakeHashes = {
"3.12.4" = "sha256-UlVYS/0EPrcXViz/iULUcvHA5GecSUHYS6raqbKOMZQ=";
};
cmakeBuild = if cmakeVersion == null then pkgs.cmake else (pkgs.cmake.overrideAttrs (old: {
version = cmakeVersion;
src = pkgs.fetchurl {
url = "https://cmake.org/files/v${lib.versions.majorMinor cmakeVersion}/cmake-${cmakeVersion}.tar.gz";
hash = lib.attrByPath [cmakeVersion] "" cmakeHashes;
};
patches = [];
})).override { isMinimalBuild = true; };
in crossPkgs.mkShell {
buildInputs = [
capnproto
];
nativeBuildInputs = with pkgs; [
cmake
cmakeBuild
include-what-you-use
ninja
] ++ lib.optionals (!minimal) [

View File

@@ -12,6 +12,7 @@
#include <atomic>
#include <capnp/capability.h>
#include <capnp/common.h> // IWYU pragma: keep
#include <capnp/rpc.h>
#include <condition_variable>
#include <functional>
@@ -23,9 +24,9 @@
#include <kj/debug.h>
#include <kj/function.h>
#include <kj/memory.h>
#include <kj/string.h>
#include <map>
#include <memory>
#include <mutex>
#include <optional>
#include <stdexcept>
#include <string>
@@ -42,7 +43,7 @@ thread_local ThreadContext g_thread_context;
void LoggingErrorHandler::taskFailed(kj::Exception&& exception)
{
KJ_LOG(ERROR, "Uncaught exception in daemonized task.", exception);
m_loop.log() << "Uncaught exception in daemonized task.";
MP_LOG(m_loop, Log::Error) << "Uncaught exception in daemonized task.";
}
EventLoopRef::EventLoopRef(EventLoop& loop, Lock* lock) : m_loop(&loop), m_lock(lock)
@@ -81,6 +82,11 @@ ProxyContext::ProxyContext(Connection* connection) : connection(connection), loo
Connection::~Connection()
{
// Connection destructor is always called on the event loop thread. If this
// is a local disconnect, it will trigger I/O, so this needs to run on the
// event loop thread, and if there was a remote disconnect, this is called
// by an onDisconnect callback directly from the event loop thread.
assert(std::this_thread::get_id() == m_loop->m_thread_id);
// Shut down RPC system first, since this will garbage collect any
// ProxyServer objects that were not freed before the connection was closed.
// Typically all ProxyServer objects associated with this connection will be
@@ -156,6 +162,9 @@ CleanupIt Connection::addSyncCleanup(std::function<void()> fn)
void Connection::removeSyncCleanup(CleanupIt it)
{
// Require cleanup functions to be removed on the event loop thread to avoid
// needing to deal with them being removed in the middle of a disconnect.
assert(std::this_thread::get_id() == m_loop->m_thread_id);
const Lock lock(m_loop->m_mutex);
m_sync_cleanup_fns.erase(it);
}
@@ -183,13 +192,13 @@ void EventLoop::addAsyncCleanup(std::function<void()> fn)
startAsyncThread();
}
EventLoop::EventLoop(const char* exe_name, LogFn log_fn, void* context)
EventLoop::EventLoop(const char* exe_name, LogOptions log_opts, void* context)
: m_exe_name(exe_name),
m_io_context(kj::setupAsyncIo()),
m_task_set(new kj::TaskSet(m_error_handler)),
m_log_opts(std::move(log_opts)),
m_context(context)
{
m_log_opts.log_fn = log_fn;
int fds[2];
KJ_SYSCALL(socketpair(AF_UNIX, SOCK_STREAM, 0, fds));
m_wait_fd = fds[0];
@@ -243,9 +252,9 @@ void EventLoop::loop()
break;
}
}
log() << "EventLoop::loop done, cancelling event listeners.";
MP_LOG(*this, Log::Info) << "EventLoop::loop done, cancelling event listeners.";
m_task_set.reset();
log() << "EventLoop::loop bye.";
MP_LOG(*this, Log::Info) << "EventLoop::loop bye.";
wait_stream = nullptr;
KJ_SYSCALL(::close(post_fd));
const Lock lock(m_mutex);
@@ -305,29 +314,34 @@ bool EventLoop::done() const
return m_num_clients == 0 && m_async_fns->empty();
}
std::tuple<ConnThread, bool> SetThread(ConnThreads& threads, std::mutex& mutex, Connection* connection, const std::function<Thread::Client()>& make_thread)
std::tuple<ConnThread, bool> SetThread(GuardedRef<ConnThreads> threads, Connection* connection, const std::function<Thread::Client()>& make_thread)
{
const std::unique_lock<std::mutex> lock(mutex);
auto thread = threads.find(connection);
if (thread != threads.end()) return {thread, false};
thread = threads.emplace(
std::piecewise_construct, std::forward_as_tuple(connection),
std::forward_as_tuple(make_thread(), connection, /* destroy_connection= */ false)).first;
thread->second.setDisconnectCallback([&threads, &mutex, thread] {
// Note: it is safe to use the `thread` iterator in this cleanup
// function, because the iterator would only be invalid if the map entry
// was removed, and if the map entry is removed the ProxyClient<Thread>
// destructor unregisters the cleanup.
assert(std::this_thread::get_id() == connection->m_loop->m_thread_id);
ConnThread thread;
bool inserted;
{
const Lock lock(threads.mutex);
std::tie(thread, inserted) = threads.ref.try_emplace(connection);
}
if (inserted) {
thread->second.emplace(make_thread(), connection, /* destroy_connection= */ false);
thread->second->m_disconnect_cb = connection->addSyncCleanup([threads, thread] {
// Note: it is safe to use the `thread` iterator in this cleanup
// function, because the iterator would only be invalid if the map entry
// was removed, and if the map entry is removed the ProxyClient<Thread>
// destructor unregisters the cleanup.
// Connection is being destroyed before thread client is, so reset
// thread client m_disconnect_cb member so thread client destructor does not
// try to unregister this callback after connection is destroyed.
// Remove connection pointer about to be destroyed from the map
const std::unique_lock<std::mutex> lock(mutex);
thread->second.m_disconnect_cb.reset();
threads.erase(thread);
});
return {thread, true};
// Connection is being destroyed before thread client is, so reset
// thread client m_disconnect_cb member so thread client destructor does not
// try to unregister this callback after connection is destroyed.
thread->second->m_disconnect_cb.reset();
// Remove connection pointer about to be destroyed from the map
const Lock lock(threads.mutex);
threads.ref.erase(thread);
});
}
return {thread, inserted};
}
ProxyClient<Thread>::~ProxyClient()
@@ -336,17 +350,18 @@ ProxyClient<Thread>::~ProxyClient()
// cleanup callback that was registered to handle the connection being
// destroyed before the thread being destroyed.
if (m_disconnect_cb) {
m_context.connection->removeSyncCleanup(*m_disconnect_cb);
// Remove disconnect callback on the event loop thread with
// loop->sync(), so if the connection is broken there is not a race
// between this thread trying to remove the callback and the disconnect
// handler attempting to call it.
m_context.loop->sync([&]() {
if (m_disconnect_cb) {
m_context.connection->removeSyncCleanup(*m_disconnect_cb);
}
});
}
}
void ProxyClient<Thread>::setDisconnectCallback(const std::function<void()>& fn)
{
assert(fn);
assert(!m_disconnect_cb);
m_disconnect_cb = m_context.connection->addSyncCleanup(fn);
}
ProxyServer<Thread>::ProxyServer(ThreadContext& thread_context, std::thread&& thread)
: m_thread_context(thread_context), m_thread(std::move(thread))
{
@@ -364,7 +379,7 @@ ProxyServer<Thread>::~ProxyServer()
assert(m_thread_context.waiter.get());
std::unique_ptr<Waiter> waiter;
{
const std::unique_lock<std::mutex> lock(m_thread_context.waiter->m_mutex);
const Lock lock(m_thread_context.waiter->m_mutex);
//! Reset thread context waiter pointer, as shutdown signal for done
//! lambda passed as waiter->wait() argument in makeThread code below.
waiter = std::move(m_thread_context.waiter);
@@ -398,7 +413,7 @@ kj::Promise<void> ProxyServer<ThreadMap>::makeThread(MakeThreadContext context)
g_thread_context.thread_name = ThreadName(m_connection.m_loop->m_exe_name) + " (from " + from + ")";
g_thread_context.waiter = std::make_unique<Waiter>();
thread_context.set_value(&g_thread_context);
std::unique_lock<std::mutex> lock(g_thread_context.waiter->m_mutex);
Lock lock(g_thread_context.waiter->m_mutex);
// Wait for shutdown signal from ProxyServer<Thread> destructor (signal
// is just waiter getting set to null.)
g_thread_context.waiter->wait(lock, [] { return !g_thread_context.waiter; });
@@ -416,4 +431,16 @@ std::string LongThreadName(const char* exe_name)
return g_thread_context.thread_name.empty() ? ThreadName(exe_name) : g_thread_context.thread_name;
}
kj::StringPtr KJ_STRINGIFY(Log v)
{
switch (v) {
case Log::Trace: return "Trace";
case Log::Debug: return "Debug";
case Log::Info: return "Info";
case Log::Warning: return "Warning";
case Log::Error: return "Error";
case Log::Raise: return "Raise";
}
return "<Log?>";
}
} // namespace mp

View File

@@ -7,6 +7,8 @@
#include <cerrno>
#include <cstdio>
#include <filesystem>
#include <iostream>
#include <kj/common.h>
#include <kj/string-tree.h>
#include <pthread.h>
@@ -29,6 +31,8 @@
#include <pthread_np.h>
#endif // HAVE_PTHREAD_GETTHREADID_NP
namespace fs = std::filesystem;
namespace mp {
namespace {
@@ -138,6 +142,9 @@ void ExecProcess(const std::vector<std::string>& args)
argv.push_back(nullptr);
if (execvp(argv[0], argv.data()) != 0) {
perror("execvp failed");
if (errno == ENOENT && !args.empty()) {
std::cerr << "Missing executable: " << fs::weakly_canonical(args.front()) << '\n';
}
_exit(1);
}
}

View File

@@ -13,6 +13,8 @@ $Proxy.includeTypes("mp/test/foo-types.h");
interface FooInterface $Proxy.wrap("mp::test::FooImplementation") {
add @0 (a :Int32, b :Int32) -> (result :Int32);
addOut @19 (a :Int32, b :Int32) -> (ret :Int32);
addInOut @20 (x :Int32, sum :Int32) -> (sum :Int32);
mapSize @1 (map :List(Pair(Text, Text))) -> (result :Int32);
pass @2 (arg :FooStruct) -> (result :FooStruct);
raise @3 (arg :FooStruct) -> (error :FooStruct $Proxy.exception("mp::test::FooStruct"));

View File

@@ -62,6 +62,8 @@ class FooImplementation
{
public:
int add(int a, int b) { return a + b; }
void addOut(int a, int b, int& out) { out = a + b; }
void addInOut(int x, int& sum) { sum += x; }
int mapSize(const std::map<std::string, std::string>& map) { return map.size(); }
FooStruct pass(FooStruct foo) { return foo; }
void raise(FooStruct foo) { throw foo; }

View File

@@ -5,26 +5,32 @@
#include <mp/test/foo.capnp.h>
#include <mp/test/foo.capnp.proxy.h>
#include <atomic>
#include <capnp/capability.h>
#include <capnp/rpc.h>
#include <condition_variable>
#include <cstring>
#include <functional>
#include <future>
#include <iostream>
#include <kj/async.h>
#include <kj/async-io.h>
#include <kj/common.h>
#include <kj/debug.h>
#include <kj/exception.h>
#include <kj/memory.h>
#include <kj/string.h>
#include <kj/test.h>
#include <memory>
#include <mp/proxy.h>
#include <mp/proxy.capnp.h>
#include <mp/proxy-io.h>
#include <mp/util.h>
#include <optional>
#include <set>
#include <stdexcept>
#include <string>
#include <string_view>
#include <system_error>
#include <thread>
#include <utility>
#include <vector>
@@ -60,9 +66,10 @@ public:
TestSetup(bool client_owns_connection = true)
: thread{[&] {
EventLoop loop("mptest", [](bool raise, const std::string& log) {
std::cout << "LOG" << raise << ": " << log << "\n";
if (raise) throw std::runtime_error(log);
EventLoop loop("mptest", [](mp::LogMessage log) {
// Info logs are not printed by default, but will be shown with `mptest --verbose`
KJ_LOG(INFO, log.level, log.message);
if (log.level == mp::Log::Raise) throw std::runtime_error(log.message);
});
auto pipe = loop.m_io_context.provider->newTwoWayPipe();
@@ -113,6 +120,11 @@ KJ_TEST("Call FooInterface methods")
ProxyClient<messages::FooInterface>* foo = setup.client.get();
KJ_EXPECT(foo->add(1, 2) == 3);
int ret;
foo->addOut(3, 4, ret);
KJ_EXPECT(ret == 7);
foo->addInOut(3, ret);
KJ_EXPECT(ret == 10);
FooStruct in;
in.name = "name";
@@ -297,5 +309,71 @@ KJ_TEST("Calling IPC method, disconnecting and blocking during the call")
signal.set_value();
}
KJ_TEST("Make simultaneous IPC calls to trigger 'thread busy' error")
{
TestSetup setup;
ProxyClient<messages::FooInterface>* foo = setup.client.get();
std::promise<void> signal;
foo->initThreadMap();
// Use callFnAsync() to get the client to set up the request_thread
// that will be used for the test.
setup.server->m_impl->m_fn = [&] {};
foo->callFnAsync();
ThreadContext& tc{g_thread_context};
Thread::Client *callback_thread, *request_thread;
foo->m_context.loop->sync([&] {
Lock lock(tc.waiter->m_mutex);
callback_thread = &tc.callback_threads.at(foo->m_context.connection)->m_client;
request_thread = &tc.request_threads.at(foo->m_context.connection)->m_client;
});
setup.server->m_impl->m_fn = [&] {
try
{
signal.get_future().get();
}
catch (const std::future_error& e)
{
KJ_EXPECT(e.code() == std::make_error_code(std::future_errc::future_already_retrieved));
}
};
auto client{foo->m_client};
bool caught_thread_busy = false;
// NOTE: '3' was chosen because it was the lowest number
// of simultaneous calls required to reliably catch a "thread busy" error
std::atomic<size_t> running{3};
foo->m_context.loop->sync([&]
{
for (size_t i = 0; i < running; i++)
{
auto request{client.callFnAsyncRequest()};
auto context{request.initContext()};
context.setCallbackThread(*callback_thread);
context.setThread(*request_thread);
foo->m_context.loop->m_task_set->add(request.send().then(
[&](auto&& results) {
running -= 1;
tc.waiter->m_cv.notify_all();
},
[&](kj::Exception&& e) {
KJ_EXPECT(std::string_view{e.getDescription().cStr()} ==
"remote exception: std::exception: thread busy");
caught_thread_busy = true;
running -= 1;
signal.set_value();
tc.waiter->m_cv.notify_all();
}
));
}
});
{
Lock lock(tc.waiter->m_mutex);
tc.waiter->wait(lock, [&running] { return running == 0; });
}
KJ_EXPECT(caught_thread_busy);
}
} // namespace test
} // namespace mp

View File

@@ -147,7 +147,6 @@ public:
// release ASAP to avoid it where possible.
vSeeds.emplace_back("seed.bitcoin.sipa.be."); // Pieter Wuille, only supports x1, x5, x9, and xd
vSeeds.emplace_back("dnsseed.bluematt.me."); // Matt Corallo, only supports x9
vSeeds.emplace_back("dnsseed.bitcoin.dashjr-list-of-p2p-nodes.us."); // Luke Dashjr
vSeeds.emplace_back("seed.bitcoin.jonasschnelli.ch."); // Jonas Schnelli, only supports x1, x5, x9, and xd
vSeeds.emplace_back("seed.btc.petertodd.net."); // Peter Todd, only supports x1, x5, x9, and xd
vSeeds.emplace_back("seed.bitcoin.sprovoost.nl."); // Sjors Provoost

View File

@@ -132,7 +132,6 @@ public:
}
void appShutdown() override
{
Interrupt(*m_context);
Shutdown(*m_context);
}
void startShutdown() override
@@ -141,12 +140,7 @@ public:
if (!(Assert(ctx.shutdown_request))()) {
LogError("Failed to send shutdown signal\n");
}
// Stop RPC for clean shutdown if any of waitfor* commands is executed.
if (args().GetBoolArg("-server", false)) {
InterruptRPC();
StopRPC();
}
Interrupt(*m_context);
}
bool shutdownRequested() override { return ShutdownRequested(*Assert(m_context)); };
bool isSettingIgnored(const std::string& name) override
@@ -918,15 +912,21 @@ public:
std::unique_ptr<BlockTemplate> waitNext(BlockWaitOptions options) override
{
auto new_template = WaitAndCreateNewBlock(chainman(), notifications(), m_node.mempool.get(), m_block_template, options, m_assemble_options);
auto new_template = WaitAndCreateNewBlock(chainman(), notifications(), m_node.mempool.get(), m_block_template, options, m_assemble_options, m_interrupt_wait);
if (new_template) return std::make_unique<BlockTemplateImpl>(m_assemble_options, std::move(new_template), m_node);
return nullptr;
}
void interruptWait() override
{
InterruptWait(notifications(), m_interrupt_wait);
}
const BlockAssembler::Options m_assemble_options;
const std::unique_ptr<CBlockTemplate> m_block_template;
bool m_interrupt_wait{false};
ChainstateManager& chainman() { return *Assert(m_node.chainman); }
KernelNotifications& notifications() { return *Assert(m_node.notifications); }
NodeContext& m_node;

View File

@@ -29,6 +29,7 @@
#include <algorithm>
#include <utility>
#include <numeric>
namespace node {
@@ -397,8 +398,8 @@ void BlockAssembler::addPackageTxs(int& nPackagesSelected, int& nDescendantsUpda
++nConsecutiveFailed;
if (nConsecutiveFailed > MAX_CONSECUTIVE_FAILURES && nBlockWeight >
m_options.nBlockMaxWeight - BLOCK_FULL_ENOUGH_WEIGHT_DELTA) {
if (nConsecutiveFailed > MAX_CONSECUTIVE_FAILURES && nBlockWeight +
BLOCK_FULL_ENOUGH_WEIGHT_DELTA > m_options.nBlockMaxWeight) {
// Give up if we're close to full and haven't succeeded in a while
break;
}
@@ -453,12 +454,20 @@ void AddMerkleRootAndCoinbase(CBlock& block, CTransactionRef coinbase, uint32_t
block.hashMerkleRoot = BlockMerkleRoot(block);
}
void InterruptWait(KernelNotifications& kernel_notifications, bool& interrupt_wait)
{
LOCK(kernel_notifications.m_tip_block_mutex);
interrupt_wait = true;
kernel_notifications.m_tip_block_cv.notify_all();
}
std::unique_ptr<CBlockTemplate> WaitAndCreateNewBlock(ChainstateManager& chainman,
KernelNotifications& kernel_notifications,
CTxMemPool* mempool,
const std::unique_ptr<CBlockTemplate>& block_template,
const BlockWaitOptions& options,
const BlockAssembler::Options& assemble_options)
const BlockAssembler::Options& assemble_options,
bool& interrupt_wait)
{
// Delay calculating the current template fees, just in case a new block
// comes in before the next tick.
@@ -483,8 +492,12 @@ std::unique_ptr<CBlockTemplate> WaitAndCreateNewBlock(ChainstateManager& chainma
// method on BlockTemplate and no template could have been
// generated before a tip exists.
tip_changed = Assume(tip_block) && tip_block != block_template->block.hashPrevBlock;
return tip_changed || chainman.m_interrupt;
return tip_changed || chainman.m_interrupt || interrupt_wait;
});
if (interrupt_wait) {
interrupt_wait = false;
return nullptr;
}
}
if (chainman.m_interrupt) return nullptr;
@@ -522,18 +535,13 @@ std::unique_ptr<CBlockTemplate> WaitAndCreateNewBlock(ChainstateManager& chainma
// Calculate the original template total fees if we haven't already
if (current_fees == -1) {
current_fees = 0;
for (CAmount fee : block_template->vTxFees) {
current_fees += fee;
}
current_fees = std::accumulate(block_template->vTxFees.begin(), block_template->vTxFees.end(), CAmount{0});
}
CAmount new_fees = 0;
for (CAmount fee : new_tmpl->vTxFees) {
new_fees += fee;
Assume(options.fee_threshold != MAX_MONEY);
if (new_fees >= current_fees + options.fee_threshold) return new_tmpl;
}
// Check if fees increased enough to return the new template
const CAmount new_fees = std::accumulate(new_tmpl->vTxFees.begin(), new_tmpl->vTxFees.end(), CAmount{0});
Assume(options.fee_threshold != MAX_MONEY);
if (new_fees >= current_fees + options.fee_threshold) return new_tmpl;
}
now = NodeClock::now();

View File

@@ -238,6 +238,9 @@ void ApplyArgsManOptions(const ArgsManager& gArgs, BlockAssembler::Options& opti
/* Compute the block's merkle root, insert or replace the coinbase transaction and the merkle root into the block */
void AddMerkleRootAndCoinbase(CBlock& block, CTransactionRef coinbase, uint32_t version, uint32_t timestamp, uint32_t nonce);
/* Interrupt the current wait for the next block template. */
void InterruptWait(KernelNotifications& kernel_notifications, bool& interrupt_wait);
/**
* Return a new block template when fees rise to a certain threshold or after a
* new tip; return nullopt if timeout is reached.
@@ -247,7 +250,8 @@ std::unique_ptr<CBlockTemplate> WaitAndCreateNewBlock(ChainstateManager& chainma
CTxMemPool* mempool,
const std::unique_ptr<CBlockTemplate>& block_template,
const BlockWaitOptions& options,
const BlockAssembler::Options& assemble_options);
const BlockAssembler::Options& assemble_options,
bool& interrupt_wait);
/* Locks cs_main and returns the block hash and block height of the active chain if it exists; otherwise, returns nullopt.*/
std::optional<BlockRef> GetTip(ChainstateManager& chainman);

File diff suppressed because it is too large Load Diff

View File

@@ -72,6 +72,9 @@ namespace {
// don't add private key handling cmd's to the history
const QStringList historyFilter = QStringList()
<< "createwallet"
<< "createwalletdescriptor"
<< "migratewallet"
<< "signmessagewithprivkey"
<< "signrawtransactionwithkey"
<< "walletpassphrase"

View File

@@ -52,32 +52,78 @@ bool TransactionFilterProxy::filterAcceptsRow(int sourceRow, const QModelIndex &
void TransactionFilterProxy::setDateRange(const std::optional<QDateTime>& from, const std::optional<QDateTime>& to)
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0)
beginFilterChange();
#endif
dateFrom = from;
dateTo = to;
#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0)
endFilterChange(QSortFilterProxyModel::Direction::Rows);
#else
invalidateFilter();
#endif
}
void TransactionFilterProxy::setSearchString(const QString &search_string)
{
if (m_search_string == search_string) return;
#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0)
beginFilterChange();
#endif
m_search_string = search_string;
#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0)
endFilterChange(QSortFilterProxyModel::Direction::Rows);
#else
invalidateFilter();
#endif
}
void TransactionFilterProxy::setTypeFilter(quint32 modes)
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0)
beginFilterChange();
#endif
this->typeFilter = modes;
#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0)
endFilterChange(QSortFilterProxyModel::Direction::Rows);
#else
invalidateFilter();
#endif
}
void TransactionFilterProxy::setMinAmount(const CAmount& minimum)
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0)
beginFilterChange();
#endif
this->minAmount = minimum;
#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0)
endFilterChange(QSortFilterProxyModel::Direction::Rows);
#else
invalidateFilter();
#endif
}
void TransactionFilterProxy::setShowInactive(bool _showInactive)
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0)
beginFilterChange();
#endif
this->showInactive = _showInactive;
#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0)
endFilterChange(QSortFilterProxyModel::Direction::Rows);
#else
invalidateFilter();
#endif
}

View File

@@ -166,7 +166,7 @@ UniValue blockheaderToJSON(const CBlockIndex& tip, const CBlockIndex& blockindex
result.pushKV("mediantime", blockindex.GetMedianTimePast());
result.pushKV("nonce", blockindex.nNonce);
result.pushKV("bits", strprintf("%08x", blockindex.nBits));
result.pushKV("target", GetTarget(tip, pow_limit).GetHex());
result.pushKV("target", GetTarget(blockindex, pow_limit).GetHex());
result.pushKV("difficulty", GetDifficulty(blockindex));
result.pushKV("chainwork", blockindex.nChainWork.GetHex());
result.pushKV("nTx", blockindex.nTx);

View File

@@ -936,8 +936,9 @@ static RPCHelpMan submitpackage()
,
{
{"package", RPCArg::Type::ARR, RPCArg::Optional::NO, "An array of raw transactions.\n"
"The package must solely consist of a child transaction and all of its unconfirmed parents, if any. None of the parents may depend on each other.\n"
"The package must be topologically sorted, with the child being the last element in the array.",
"The package must consist of a transaction with (some, all, or none of) its unconfirmed parents. A single transaction is permitted.\n"
"None of the parents may depend on each other. Parents that are already in mempool do not need to be present in the package.\n"
"The package must be topologically sorted, with the child being the last element in the array if there are multiple elements.",
{
{"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""},
},

View File

@@ -1608,7 +1608,7 @@ static RPCHelpMan finalizepsbt()
return RPCHelpMan{"finalizepsbt",
"Finalize the inputs of a PSBT. If the transaction is fully signed, it will produce a\n"
"network serialized transaction which can be broadcast with sendrawtransaction. Otherwise a PSBT will be\n"
"created which has the final_scriptSig and final_scriptWitness fields filled for inputs that are complete.\n"
"created which has the final_scriptSig and final_scriptwitness fields filled for inputs that are complete.\n"
"Implements the Finalizer and Extractor roles.\n",
{
{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "A base64 string of a PSBT"},

View File

@@ -51,7 +51,7 @@ static std::vector<const char*> g_args;
static void SetArgs(int argc, char** argv) {
for (int i = 1; i < argc; ++i) {
// Only take into account arguments that start with `--`. The others are for the fuzz engine:
// `fuzz -runs=1 fuzz_corpora/address_deserialize_v2 --checkaddrman=5`
// `fuzz -runs=1 fuzz_corpora/address_deserialize --checkaddrman=5`
if (strlen(argv[i]) > 2 && argv[i][0] == '-' && argv[i][1] == '-') {
g_args.push_back(argv[i]);
}

View File

@@ -325,7 +325,7 @@ FUZZ_TARGET(ephemeral_package_eval, .init = initialize_tx_pool)
return ProcessNewPackage(chainstate, tx_pool, txs, /*test_accept=*/single_submit, /*client_maxfeerate=*/{}));
const auto res = WITH_LOCK(::cs_main, return AcceptToMemoryPool(chainstate, txs.back(), GetTime(),
/*bypass_limits=*/fuzzed_data_provider.ConsumeBool(), /*test_accept=*/!single_submit));
/*bypass_limits=*/false, /*test_accept=*/!single_submit));
if (!single_submit && result_package.m_state.GetResult() != PackageValidationResult::PCKG_POLICY) {
// We don't know anything about the validity since transactions were randomly generated, so

View File

@@ -296,7 +296,6 @@ FUZZ_TARGET(tx_pool_standard, .init = initialize_tx_pool)
std::set<CTransactionRef> added;
auto txr = std::make_shared<TransactionsDelta>(removed, added);
node.validation_signals->RegisterSharedValidationInterface(txr);
const bool bypass_limits = fuzzed_data_provider.ConsumeBool();
// Make sure ProcessNewPackage on one transaction works.
// The result is not guaranteed to be the same as what is returned by ATMP.
@@ -311,7 +310,7 @@ FUZZ_TARGET(tx_pool_standard, .init = initialize_tx_pool)
it->second.m_result_type == MempoolAcceptResult::ResultType::INVALID);
}
const auto res = WITH_LOCK(::cs_main, return AcceptToMemoryPool(chainstate, tx, GetTime(), bypass_limits, /*test_accept=*/false));
const auto res = WITH_LOCK(::cs_main, return AcceptToMemoryPool(chainstate, tx, GetTime(), /*bypass_limits=*/false, /*test_accept=*/false));
const bool accepted = res.m_result_type == MempoolAcceptResult::ResultType::VALID;
node.validation_signals->SyncWithValidationInterfaceQueue();
node.validation_signals->UnregisterSharedValidationInterface(txr);
@@ -394,6 +393,9 @@ FUZZ_TARGET(tx_pool, .init = initialize_tx_pool)
chainstate.SetMempool(&tx_pool);
// If we ever bypass limits, do not do TRUC invariants checks
bool ever_bypassed_limits{false};
LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 300)
{
const auto mut_tx = ConsumeTransaction(fuzzed_data_provider, txids);
@@ -412,13 +414,17 @@ FUZZ_TARGET(tx_pool, .init = initialize_tx_pool)
tx_pool.PrioritiseTransaction(txid, delta);
}
const bool bypass_limits{fuzzed_data_provider.ConsumeBool()};
ever_bypassed_limits |= bypass_limits;
const auto tx = MakeTransactionRef(mut_tx);
const bool bypass_limits = fuzzed_data_provider.ConsumeBool();
const auto res = WITH_LOCK(::cs_main, return AcceptToMemoryPool(chainstate, tx, GetTime(), bypass_limits, /*test_accept=*/false));
const bool accepted = res.m_result_type == MempoolAcceptResult::ResultType::VALID;
if (accepted) {
txids.push_back(tx->GetHash());
CheckMempoolTRUCInvariants(tx_pool);
if (!ever_bypassed_limits) {
CheckMempoolTRUCInvariants(tx_pool);
}
}
}
Finish(fuzzed_data_provider, tx_pool, chainstate);

View File

@@ -113,6 +113,22 @@ void MinerTestingSetup::TestPackageSelection(const CScript& scriptPubKey, const
options.coinbase_output_script = scriptPubKey;
LOCK(tx_mempool.cs);
BOOST_CHECK(tx_mempool.size() == 0);
// Block template should only have a coinbase when there's nothing in the mempool
std::unique_ptr<BlockTemplate> block_template = mining->createNewBlock(options);
BOOST_REQUIRE(block_template);
CBlock block{block_template->getBlock()};
BOOST_REQUIRE_EQUAL(block.vtx.size(), 1U);
// waitNext() on an empty mempool should return nullptr because there is no better template
auto should_be_nullptr = block_template->waitNext({.timeout = MillisecondsDouble{0}, .fee_threshold = 1});
BOOST_REQUIRE(should_be_nullptr == nullptr);
// Unless fee_threshold is 0
block_template = block_template->waitNext({.timeout = MillisecondsDouble{0}, .fee_threshold = 0});
BOOST_REQUIRE(block_template);
// Test the ancestor feerate transaction selection.
TestMemPoolEntryHelper entry;
@@ -144,9 +160,9 @@ void MinerTestingSetup::TestPackageSelection(const CScript& scriptPubKey, const
const auto high_fee_tx{entry.Fee(50000).Time(Now<NodeSeconds>()).SpendsCoinbase(false).FromTx(tx)};
AddToMempool(tx_mempool, high_fee_tx);
std::unique_ptr<BlockTemplate> block_template = mining->createNewBlock(options);
block_template = mining->createNewBlock(options);
BOOST_REQUIRE(block_template);
CBlock block{block_template->getBlock()};
block = block_template->getBlock();
BOOST_REQUIRE_EQUAL(block.vtx.size(), 4U);
BOOST_CHECK(block.vtx[1]->GetHash() == hashParentTx);
BOOST_CHECK(block.vtx[2]->GetHash() == hashHighFeeTx);
@@ -184,7 +200,7 @@ void MinerTestingSetup::TestPackageSelection(const CScript& scriptPubKey, const
AddToMempool(tx_mempool, entry.Fee(feeToUse).FromTx(tx));
// waitNext() should return nullptr because there is no better template
auto should_be_nullptr = block_template->waitNext({.timeout = MillisecondsDouble{0}, .fee_threshold = 1});
should_be_nullptr = block_template->waitNext({.timeout = MillisecondsDouble{0}, .fee_threshold = 1});
BOOST_REQUIRE(should_be_nullptr == nullptr);
block = block_template->getBlock();

View File

@@ -1044,26 +1044,28 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
// Even though just checking direct mempool parents for inheritance would be sufficient, we
// check using the full ancestor set here because it's more convenient to use what we have
// already calculated.
if (const auto err{SingleTRUCChecks(ws.m_ptx, ws.m_ancestors, ws.m_conflicts, ws.m_vsize)}) {
// Single transaction contexts only.
if (args.m_allow_sibling_eviction && err->second != nullptr) {
// We should only be considering where replacement is considered valid as well.
Assume(args.m_allow_replacement);
if (!args.m_bypass_limits) {
if (const auto err{SingleTRUCChecks(ws.m_ptx, ws.m_ancestors, ws.m_conflicts, ws.m_vsize)}) {
// Single transaction contexts only.
if (args.m_allow_sibling_eviction && err->second != nullptr) {
// We should only be considering where replacement is considered valid as well.
Assume(args.m_allow_replacement);
// Potential sibling eviction. Add the sibling to our list of mempool conflicts to be
// included in RBF checks.
ws.m_conflicts.insert(err->second->GetHash());
// Adding the sibling to m_iters_conflicting here means that it doesn't count towards
// RBF Carve Out above. This is correct, since removing to-be-replaced transactions from
// the descendant count is done separately in SingleTRUCChecks for TRUC transactions.
ws.m_iters_conflicting.insert(m_pool.GetIter(err->second->GetHash()).value());
ws.m_sibling_eviction = true;
// The sibling will be treated as part of the to-be-replaced set in ReplacementChecks.
// Note that we are not checking whether it opts in to replaceability via BIP125 or TRUC
// (which is normally done in PreChecks). However, the only way a TRUC transaction can
// have a non-TRUC and non-BIP125 descendant is due to a reorg.
} else {
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "TRUC-violation", err->first);
// Potential sibling eviction. Add the sibling to our list of mempool conflicts to be
// included in RBF checks.
ws.m_conflicts.insert(err->second->GetHash());
// Adding the sibling to m_iters_conflicting here means that it doesn't count towards
// RBF Carve Out above. This is correct, since removing to-be-replaced transactions from
// the descendant count is done separately in SingleTRUCChecks for TRUC transactions.
ws.m_iters_conflicting.insert(m_pool.GetIter(err->second->GetHash()).value());
ws.m_sibling_eviction = true;
// The sibling will be treated as part of the to-be-replaced set in ReplacementChecks.
// Note that we are not checking whether it opts in to replaceability via BIP125 or TRUC
// (which is normally done in PreChecks). However, the only way a TRUC transaction can
// have a non-TRUC and non-BIP125 descendant is due to a reorg.
} else {
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "TRUC-violation", err->first);
}
}
}

View File

@@ -276,11 +276,17 @@ bool CreateFromDump(const ArgsManager& args, const std::string& name, const fs::
dump_file.close();
}
// On failure, gather the paths to remove
std::vector<fs::path> paths_to_remove = wallet->GetDatabase().Files();
if (!name.empty()) paths_to_remove.push_back(wallet_path);
wallet.reset(); // The pointer deleter will close the wallet for us.
// Remove the wallet dir if we have a failure
if (!ret) {
fs::remove_all(wallet_path);
for (const auto& p : paths_to_remove) {
fs::remove(p);
}
}
return ret;

View File

@@ -403,6 +403,11 @@ CoinsResult AvailableCoins(const CWallet& wallet,
if (wtx.tx->version != TRUC_VERSION) continue;
// this unconfirmed v3 transaction already has a child
if (wtx.truc_child_in_mempool.has_value()) continue;
// this unconfirmed v3 transaction has a parent: spending would create a third generation
size_t ancestors, descendants;
wallet.chain().getTransactionAncestry(wtx.tx->GetHash(), ancestors, descendants);
if (ancestors > 1) continue;
} else {
if (wtx.tx->version == TRUC_VERSION) continue;
Assume(!wtx.truc_child_in_mempool.has_value());

View File

@@ -469,6 +469,8 @@ std::shared_ptr<CWallet> RestoreWallet(WalletContext& context, const fs::path& b
const fs::path wallet_path = fsbridge::AbsPathJoin(GetWalletDir(), fs::u8path(wallet_name));
auto wallet_file = wallet_path / "wallet.dat";
std::shared_ptr<CWallet> wallet;
bool wallet_file_copied = false;
bool created_parent_dir = false;
try {
if (!fs::exists(backup_file)) {
@@ -477,13 +479,34 @@ std::shared_ptr<CWallet> RestoreWallet(WalletContext& context, const fs::path& b
return nullptr;
}
if (fs::exists(wallet_path) || !TryCreateDirectories(wallet_path)) {
error = Untranslated(strprintf("Failed to create database path '%s'. Database already exists.", fs::PathToString(wallet_path)));
status = DatabaseStatus::FAILED_ALREADY_EXISTS;
return nullptr;
// Wallet directories are allowed to exist, but must not contain a .dat file.
// Any existing wallet database is treated as a hard failure to prevent overwriting.
if (fs::exists(wallet_path)) {
// If this is a file, it is the db and we don't want to overwrite it.
if (!fs::is_directory(wallet_path)) {
error = Untranslated(strprintf("Failed to restore wallet. Database file exists '%s'.", fs::PathToString(wallet_path)));
status = DatabaseStatus::FAILED_ALREADY_EXISTS;
return nullptr;
}
// Check we are not going to overwrite an existing db file
if (fs::exists(wallet_file)) {
error = Untranslated(strprintf("Failed to restore wallet. Database file exists in '%s'.", fs::PathToString(wallet_file)));
status = DatabaseStatus::FAILED_ALREADY_EXISTS;
return nullptr;
}
} else {
// The directory doesn't exist, create it
if (!TryCreateDirectories(wallet_path)) {
error = Untranslated(strprintf("Failed to restore database path '%s'.", fs::PathToString(wallet_path)));
status = DatabaseStatus::FAILED_ALREADY_EXISTS;
return nullptr;
}
created_parent_dir = true;
}
fs::copy_file(backup_file, wallet_file, fs::copy_options::none);
wallet_file_copied = true;
if (load_after_restore) {
wallet = LoadWallet(context, wallet_name, load_on_start, options, status, error, warnings);
@@ -496,7 +519,13 @@ std::shared_ptr<CWallet> RestoreWallet(WalletContext& context, const fs::path& b
// Remove created wallet path only when loading fails
if (load_after_restore && !wallet) {
fs::remove_all(wallet_path);
if (wallet_file_copied) fs::remove(wallet_file);
// Clean up the parent directory if we created it during restoration.
// As we have created it, it must be empty after deleting the wallet file.
if (created_parent_dir) {
Assume(fs::is_empty(wallet_path));
fs::remove(wallet_path);
}
}
return wallet;
@@ -4057,6 +4086,15 @@ bool CWallet::CanGrindR() const
return !IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
}
// Returns wallet prefix for migration.
// Used to name the backup file and newly created wallets.
// E.g. a watch-only wallet is named "<prefix>_watchonly".
static std::string MigrationPrefixName(CWallet& wallet)
{
const std::string& name{wallet.GetName()};
return name.empty() ? "default_wallet" : name;
}
bool DoMigration(CWallet& wallet, WalletContext& context, bilingual_str& error, MigrationResult& res) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
{
AssertLockHeld(wallet.cs_wallet);
@@ -4088,7 +4126,7 @@ bool DoMigration(CWallet& wallet, WalletContext& context, bilingual_str& error,
DatabaseStatus status;
std::vector<bilingual_str> warnings;
std::string wallet_name = wallet.GetName() + "_watchonly";
std::string wallet_name = MigrationPrefixName(wallet) + "_watchonly";
std::unique_ptr<WalletDatabase> database = MakeWalletDatabase(wallet_name, options, status, error);
if (!database) {
error = strprintf(_("Wallet file creation failed: %s"), error);
@@ -4127,7 +4165,7 @@ bool DoMigration(CWallet& wallet, WalletContext& context, bilingual_str& error,
DatabaseStatus status;
std::vector<bilingual_str> warnings;
std::string wallet_name = wallet.GetName() + "_solvables";
std::string wallet_name = MigrationPrefixName(wallet) + "_solvables";
std::unique_ptr<WalletDatabase> database = MakeWalletDatabase(wallet_name, options, status, error);
if (!database) {
error = strprintf(_("Wallet file creation failed: %s"), error);
@@ -4242,7 +4280,7 @@ util::Result<MigrationResult> MigrateLegacyToDescriptor(std::shared_ptr<CWallet>
// cases, but in the case where the wallet name is a path to a data file,
// the name of the data file is used, and in the case where the wallet name
// is blank, "default_wallet" is used.
const std::string backup_prefix = wallet_name.empty() ? "default_wallet" : [&] {
const std::string backup_prefix = wallet_name.empty() ? MigrationPrefixName(*local_wallet) : [&] {
// fs::weakly_canonical resolves relative specifiers and remove trailing slashes.
const auto legacy_wallet_path = fs::weakly_canonical(GetWalletDir() / fs::PathFromString(wallet_name));
return fs::PathToString(legacy_wallet_path.filename());
@@ -4295,11 +4333,28 @@ util::Result<MigrationResult> MigrateLegacyToDescriptor(std::shared_ptr<CWallet>
}
}
// In case of loading failure, we need to remember the wallet dirs to remove.
// In case of loading failure, we need to remember the wallet files we have created to remove.
// A `set` is used as it may be populated with the same wallet directory paths multiple times,
// both before and after loading. This ensures the set is complete even if one of the wallets
// fails to load.
std::set<fs::path> wallet_dirs;
std::set<fs::path> wallet_files_to_remove;
std::set<fs::path> wallet_empty_dirs_to_remove;
// Helper to track wallet files and directories for cleanup on failure.
// Only directories of wallets created during migration (not the main wallet) are tracked.
auto track_for_cleanup = [&](const CWallet& wallet) {
const auto files = wallet.GetDatabase().Files();
wallet_files_to_remove.insert(files.begin(), files.end());
if (wallet.GetName() != wallet_name) {
// If this isnt the main wallet, mark its directory for removal.
// This applies to the watch-only and solvable wallets.
// Wallets stored directly as files in the top-level directory
// (e.g. default unnamed wallets) dont have a removable parent directory.
wallet_empty_dirs_to_remove.insert(fs::PathFromString(wallet.GetDatabase().Filename()).parent_path());
}
};
if (success) {
Assume(!res.wallet); // We will set it here.
// Check if the local wallet is empty after migration
@@ -4307,23 +4362,30 @@ util::Result<MigrationResult> MigrateLegacyToDescriptor(std::shared_ptr<CWallet>
// This wallet has no records. We can safely remove it.
std::vector<fs::path> paths_to_remove = local_wallet->GetDatabase().Files();
local_wallet.reset();
for (const auto& path_to_remove : paths_to_remove) fs::remove_all(path_to_remove);
for (const auto& path_to_remove : paths_to_remove) fs::remove(path_to_remove);
}
LogInfo("Loading new wallets after migration...\n");
// Migration successful, load all the migrated wallets.
for (std::shared_ptr<CWallet>* wallet_ptr : {&local_wallet, &res.watchonly_wallet, &res.solvables_wallet}) {
if (success && *wallet_ptr) {
std::shared_ptr<CWallet>& wallet = *wallet_ptr;
// Save db path and load wallet
wallet_dirs.insert(fs::PathFromString(wallet->GetDatabase().Filename()).parent_path());
// Track db path and load wallet
track_for_cleanup(*wallet);
assert(wallet.use_count() == 1);
std::string wallet_name = wallet->GetName();
wallet.reset();
wallet = LoadWallet(context, wallet_name, /*load_on_start=*/std::nullopt, options, status, error, warnings);
success = (wallet != nullptr);
if (!wallet) {
LogError("Failed to load wallet '%s' after migration. Rolling back migration to preserve consistency. "
"Error cause: %s\n", wallet_name, error.original);
success = false;
break;
}
// When no wallet is set, set the main wallet.
if (success && !res.wallet) {
// Set the first successfully loaded wallet as the main one.
// The loop order is intentional and must always start with the local wallet.
if (!res.wallet) {
res.wallet_name = wallet->GetName();
res.wallet = std::move(wallet);
}
@@ -4338,8 +4400,8 @@ util::Result<MigrationResult> MigrateLegacyToDescriptor(std::shared_ptr<CWallet>
if (res.solvables_wallet) created_wallets.push_back(std::move(res.solvables_wallet));
// Get the directories to remove after unloading
for (std::shared_ptr<CWallet>& w : created_wallets) {
wallet_dirs.emplace(fs::PathFromString(w->GetDatabase().Filename()).parent_path());
for (std::shared_ptr<CWallet>& wallet : created_wallets) {
track_for_cleanup(*wallet);
}
// Unload the wallets
@@ -4358,9 +4420,15 @@ util::Result<MigrationResult> MigrateLegacyToDescriptor(std::shared_ptr<CWallet>
}
}
// Delete the wallet directories
for (const fs::path& dir : wallet_dirs) {
fs::remove_all(dir);
// First, delete the db files we have created throughout this process and nothing else
for (const fs::path& file : wallet_files_to_remove) {
fs::remove(file);
}
// Second, delete the created wallet directories and nothing else. They must be empty at this point.
for (const fs::path& dir : wallet_empty_dirs_to_remove) {
Assume(fs::is_empty(dir));
fs::remove(dir);
}
// Restore the backup

View File

@@ -11,9 +11,10 @@ The alternate mainnet chain was generated as follows:
- restart node with a faketime 2 minutes later
```sh
for i in {1..2015}
for i in {1..2016}
do
faketime "`date -d @"$(( 1231006505 + $i * 120 ))" +'%Y-%m-%d %H:%M:%S'`" \
t=$(( 1231006505 + $i * 120 ))
faketime "`date -d @$t +'%Y-%m-%d %H:%M:%S'`" \
bitcoind -connect=0 -nocheckpoints -stopatheight=$i
done
```
@@ -21,7 +22,9 @@ done
The CPU miner is kept running as follows:
```sh
./minerd --coinbase-addr 1NQpH6Nf8QtR2HphLRcvuVqfhXBXsiWn8r --no-stratum --algo sha256d --no-longpoll --scantime 3 --retry-pause 1
./minerd -u ... -p ... -o http://127.0.0.1:8332 --no-stratum \
--coinbase-addr 1NQpH6Nf8QtR2HphLRcvuVqfhXBXsiWn8r \
--algo sha256d --no-longpoll --scantime 3 --retry-pause 1
```
The payout address is derived from first BIP32 test vector master key:
@@ -40,3 +43,8 @@ The timestamp was not kept constant because at difficulty 1 it's not sufficient
to only grind the nonce. Grinding the extra_nonce or version field instead
would have required additional (stratum) software. It would also make it more
complicated to reconstruct the blocks in this test.
The `getblocktemplate` RPC code needs to be patched to ignore not being connected
to any peers, and to ignore the IBD status check.
On macOS use `faketime "@$t"` instead.

View File

@@ -2014,7 +2014,8 @@
1231247971,
1231248071,
1231248198,
1231248322
1231248322,
1231248621
],
"nonces": [
2345621585,
@@ -4031,6 +4032,7 @@
3658502865,
2519048297,
1915965760,
1183846025
1183846025,
2713372123
]
}

View File

@@ -3,6 +3,7 @@
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Tests related to node initialization."""
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
import os
import platform
@@ -240,9 +241,59 @@ class InitTest(BitcoinTestFramework):
self.stop_node(0)
assert not custom_pidfile_absolute.exists()
def break_wait_test(self):
"""Test what happens when a break signal is sent during a
waitforblockheight RPC call with a long timeout. Ctrl-Break is sent on
Windows and SIGTERM is sent on other platforms, to trigger the same node
shutdown sequence that would happen if Ctrl-C were pressed in a
terminal. (This can be different than the node shutdown sequence that
happens when the stop RPC is sent.)
The waitforblockheight call should be interrupted and return right away,
and not time out."""
self.log.info("Testing waitforblockheight RPC call followed by break signal")
node = self.nodes[0]
if platform.system() == 'Windows':
# CREATE_NEW_PROCESS_GROUP prevents python test from exiting
# with STATUS_CONTROL_C_EXIT (-1073741510) when break is sent.
self.start_node(node.index, creationflags=subprocess.CREATE_NEW_PROCESS_GROUP)
else:
self.start_node(node.index)
current_height = node.getblock(node.getbestblockhash())['height']
with ThreadPoolExecutor(max_workers=1) as ex:
# Call waitforblockheight with wait timeout longer than RPC timeout,
# so it is possible to distinguish whether it times out or returns
# early. If it times out it will throw an exception, and if it
# returns early it will return the current block height.
self.log.debug(f"Calling waitforblockheight with {self.rpc_timeout} sec RPC timeout")
fut = ex.submit(node.waitforblockheight, height=current_height+1, timeout=self.rpc_timeout*1000*2)
self.wait_until(lambda: any(c["method"] == "waitforblockheight" for c in node.cli.getrpcinfo()["active_commands"]))
self.log.debug(f"Sending break signal to pid {node.process.pid}")
if platform.system() == 'Windows':
# Note: CTRL_C_EVENT should not be sent here because unlike
# CTRL_BREAK_EVENT it can not be targeted at a specific process
# group and may behave unpredictably.
node.process.send_signal(signal.CTRL_BREAK_EVENT)
else:
# Note: signal.SIGINT would work here as well
node.process.send_signal(signal.SIGTERM)
node.process.wait()
result = fut.result()
self.log.debug(f"waitforblockheight returned {result!r}")
assert_equal(result["height"], current_height)
node.wait_until_stopped()
def run_test(self):
self.init_pid_test()
self.init_stress_test()
self.break_wait_test()
if __name__ == '__main__':

View File

@@ -153,6 +153,7 @@ class IPCInterfaceTest(BitcoinTestFramework):
self.log.debug("Wait for a new template")
waitoptions = self.capnp_modules['mining'].BlockWaitOptions()
waitoptions.timeout = timeout
waitoptions.feeThreshold = 1
waitnext = template.result.waitNext(ctx, waitoptions)
self.generate(self.nodes[0], 1)
template2 = await waitnext
@@ -168,6 +169,7 @@ class IPCInterfaceTest(BitcoinTestFramework):
block3 = await self.parse_and_deserialize_block(template4, ctx)
assert_equal(len(block3.vtx), 2)
self.log.debug("Wait again, this should return the same template, since the fee threshold is zero")
waitoptions.feeThreshold = 0
template5 = await template4.result.waitNext(ctx, waitoptions)
block4 = await self.parse_and_deserialize_block(template5, ctx)
assert_equal(len(block4.vtx), 2)
@@ -182,6 +184,28 @@ class IPCInterfaceTest(BitcoinTestFramework):
template7 = await template6.result.waitNext(ctx, waitoptions)
assert_equal(template7.to_dict(), {})
self.log.debug("interruptWait should abort the current wait")
wait_started = asyncio.Event()
async def wait_for_block():
new_waitoptions = self.capnp_modules['mining'].BlockWaitOptions()
new_waitoptions.timeout = waitoptions.timeout * 60 # 1 minute wait
new_waitoptions.feeThreshold = 1
wait_started.set()
return await template6.result.waitNext(ctx, new_waitoptions)
async def interrupt_wait():
await wait_started.wait() # Wait for confirmation wait started
await asyncio.sleep(0.1) # Minimal buffer
template6.result.interruptWait()
miniwallet.send_self_transfer(fee_rate=10, from_node=self.nodes[0])
wait_task = asyncio.create_task(wait_for_block())
interrupt_task = asyncio.create_task(interrupt_wait())
result = await wait_task
await interrupt_task
assert_equal(result.to_dict(), {})
current_block_height = self.nodes[0].getchaintips()[0]["height"]
check_opts = self.capnp_modules['mining'].BlockCheckOptions()
template = await mining.result.createNewBlock(opts)

View File

@@ -100,17 +100,5 @@ class DataCarrierTest(BitcoinTestFramework):
self.test_null_data_transaction(node=self.nodes[2], data=one_byte, success=True)
self.test_null_data_transaction(node=self.nodes[3], data=one_byte, success=False)
# Clean shutdown boilerplate due to deprecation
self.expected_stderr = [
"", # node 0 has no deprecated options
"Warning: Options '-datacarrier' or '-datacarriersize' are set but are marked as deprecated and are expected to be removed in a future version.",
"Warning: Options '-datacarrier' or '-datacarriersize' are set but are marked as deprecated and are expected to be removed in a future version.",
"Warning: Options '-datacarrier' or '-datacarriersize' are set but are marked as deprecated and are expected to be removed in a future version.",
]
for i in range(self.num_nodes):
self.stop_node(i, expected_stderr=self.expected_stderr[i])
if __name__ == '__main__':
DataCarrierTest(__file__).main()

View File

@@ -165,23 +165,36 @@ class MempoolTRUC(BitcoinTestFramework):
def test_truc_reorg(self):
node = self.nodes[0]
self.log.info("Test that, during a reorg, TRUC rules are not enforced")
tx_v2_block = self.wallet.send_self_transfer(from_node=node, version=2)
tx_v3_block = self.wallet.send_self_transfer(from_node=node, version=3)
tx_v3_block2 = self.wallet.send_self_transfer(from_node=node, version=3)
self.check_mempool([tx_v3_block["txid"], tx_v2_block["txid"], tx_v3_block2["txid"]])
self.check_mempool([])
# Testing 2<-3 versions allowed
tx_v2_block = self.wallet.create_self_transfer(version=2)
# Testing 3<-2 versions allowed
tx_v3_block = self.wallet.create_self_transfer(version=3)
# Testing overly-large child size
tx_v3_block2 = self.wallet.create_self_transfer(version=3)
# Also create a linear chain of 3 TRUC transactions that will be directly mined, followed by one v2 in-mempool after block is made
tx_chain_1 = self.wallet.create_self_transfer(version=3)
tx_chain_2 = self.wallet.create_self_transfer(utxo_to_spend=tx_chain_1["new_utxo"], version=3)
tx_chain_3 = self.wallet.create_self_transfer(utxo_to_spend=tx_chain_2["new_utxo"], version=3)
tx_to_mine = [tx_v3_block["hex"], tx_v2_block["hex"], tx_v3_block2["hex"], tx_chain_1["hex"], tx_chain_2["hex"], tx_chain_3["hex"]]
block = self.generateblock(node, output="raw(42)", transactions=tx_to_mine)
block = self.generate(node, 1)
self.check_mempool([])
tx_v2_from_v3 = self.wallet.send_self_transfer(from_node=node, utxo_to_spend=tx_v3_block["new_utxo"], version=2)
tx_v3_from_v2 = self.wallet.send_self_transfer(from_node=node, utxo_to_spend=tx_v2_block["new_utxo"], version=3)
tx_v3_child_large = self.wallet.send_self_transfer(from_node=node, utxo_to_spend=tx_v3_block2["new_utxo"], target_vsize=1250, version=3)
assert_greater_than(node.getmempoolentry(tx_v3_child_large["txid"])["vsize"], TRUC_CHILD_MAX_VSIZE)
self.check_mempool([tx_v2_from_v3["txid"], tx_v3_from_v2["txid"], tx_v3_child_large["txid"]])
node.invalidateblock(block[0])
self.check_mempool([tx_v3_block["txid"], tx_v2_block["txid"], tx_v3_block2["txid"], tx_v2_from_v3["txid"], tx_v3_from_v2["txid"], tx_v3_child_large["txid"]])
# This is needed because generate() will create the exact same block again.
node.reconsiderblock(block[0])
tx_chain_4 = self.wallet.send_self_transfer(from_node=node, utxo_to_spend=tx_chain_3["new_utxo"], version=2)
self.check_mempool([tx_v2_from_v3["txid"], tx_v3_from_v2["txid"], tx_v3_child_large["txid"], tx_chain_4["txid"]])
# Reorg should have all block transactions re-accepted, ignoring TRUC enforcement
node.invalidateblock(block["hash"])
self.check_mempool([tx_v3_block["txid"], tx_v2_block["txid"], tx_v3_block2["txid"], tx_v2_from_v3["txid"], tx_v3_from_v2["txid"], tx_v3_child_large["txid"], tx_chain_1["txid"], tx_chain_2["txid"], tx_chain_3["txid"], tx_chain_4["txid"]])
@cleanup(extra_args=["-limitdescendantsize=10"])
def test_nondefault_package_limits(self):

View File

@@ -53,15 +53,15 @@ class MiningMainnetTest(BitcoinTestFramework):
help='Block data file (default: %(default)s)',
)
def mine(self, height, prev_hash, blocks, node, fees=0):
def mine(self, height, prev_hash, blocks, node):
self.log.debug(f"height={height}")
block = CBlock()
block.nVersion = 0x20000000
block.hashPrevBlock = int(prev_hash, 16)
block.nTime = blocks['timestamps'][height - 1]
block.nBits = DIFF_1_N_BITS
block.nBits = DIFF_1_N_BITS if height < 2016 else DIFF_4_N_BITS
block.nNonce = blocks['nonces'][height - 1]
block.vtx = [create_coinbase(height=height, script_pubkey=bytes.fromhex(COINBASE_SCRIPT_PUBKEY), retarget_period=2016)]
block.vtx = [create_coinbase(height=height, script_pubkey=bytes.fromhex(COINBASE_SCRIPT_PUBKEY), halving_period=210000)]
# The alternate mainnet chain was mined with non-timelocked coinbase txs.
block.vtx[0].nLockTime = 0
block.vtx[0].vin[0].nSequence = SEQUENCE_FINAL
@@ -82,12 +82,15 @@ class MiningMainnetTest(BitcoinTestFramework):
self.log.info("Load alternative mainnet blocks")
path = os.path.join(os.path.dirname(os.path.realpath(__file__)), self.options.datafile)
prev_hash = node.getbestblockhash()
blocks = None
with open(path, encoding='utf-8') as f:
blocks = json.load(f)
n_blocks = len(blocks['timestamps'])
assert_equal(n_blocks, 2015)
for i in range(2015):
prev_hash = self.mine(i + 1, prev_hash, blocks, node)
assert_equal(n_blocks, 2016)
# Mine up to the last block of the first retarget period
for i in range(2015):
prev_hash = self.mine(i + 1, prev_hash, blocks, node)
assert_equal(node.getblockcount(), 2015)
@@ -102,5 +105,21 @@ class MiningMainnetTest(BitcoinTestFramework):
assert_equal(mining_info['next']['bits'], nbits_str(DIFF_4_N_BITS))
assert_equal(mining_info['next']['target'], target_str(DIFF_4_TARGET))
# Mine first block of the second retarget period
height = 2016
prev_hash = self.mine(height, prev_hash, blocks, node)
assert_equal(node.getblockcount(), height)
mining_info = node.getmininginfo()
assert_equal(mining_info['difficulty'], 4)
self.log.info("getblock RPC should show historical target")
block_info = node.getblock(node.getblockhash(1))
assert_equal(block_info['difficulty'], 1)
assert_equal(block_info['bits'], nbits_str(DIFF_1_N_BITS))
assert_equal(block_info['target'], target_str(DIFF_1_TARGET))
if __name__ == '__main__':
MiningMainnetTest(__file__).main()

View File

@@ -144,7 +144,7 @@ def script_BIP34_coinbase_height(height):
return CScript([CScriptNum(height)])
def create_coinbase(height, pubkey=None, *, script_pubkey=None, extra_output_script=None, fees=0, nValue=50, retarget_period=REGTEST_RETARGET_PERIOD):
def create_coinbase(height, pubkey=None, *, script_pubkey=None, extra_output_script=None, fees=0, nValue=50, halving_period=REGTEST_RETARGET_PERIOD):
"""Create a coinbase transaction.
If pubkey is passed in, the coinbase output will be a P2PK output;
@@ -158,7 +158,7 @@ def create_coinbase(height, pubkey=None, *, script_pubkey=None, extra_output_scr
coinbaseoutput = CTxOut()
coinbaseoutput.nValue = nValue * COIN
if nValue == 50:
halvings = int(height / retarget_period)
halvings = int(height / halving_period)
coinbaseoutput.nValue >>= halvings
coinbaseoutput.nValue += fees
if pubkey is not None:

View File

@@ -161,7 +161,7 @@ class TestNode():
self.args.append("-logsourcelocations")
if self.version_is_at_least(239000):
self.args.append("-loglevel=trace")
if self.version_is_at_least(299900):
if self.version_is_at_least(290100):
self.args.append("-nologratelimit")
# Default behavior from global -v2transport flag is added to args to persist it over restarts.

Some files were not shown because too many files have changed in this diff Show More