27.0 Release Candidate Testing Guide - bitcoin-core/bitcoin-devwiki GitHub Wiki

Testing Guide: Bitcoin Core 27.0 Release Candidate

For feedback on this guide, please visit #29685

This document outlines some of the upcoming Bitcoin Core 27.0 release changes and provides steps to help test them. This guide is meant to be the starting point for experimentation and further testing, but is in no way comprehensive! After running through the steps in this guide, you are encouraged to do your own testing.

This can be as simple as testing the same features in this guide but trying it a different way. Even better, think of features you use regularly and test that they still work as expected in the release candidate. You can also read the release notes to find something not covered in this guide. This is a great way to be involved with Bitcoin's development and helps keep Bitcoin running smoothly and bug-minimized! Your help in this endeavor is greatly appreciated.

Overview

Changes covered in this testing guide include:


For a comprehensive list of changes in Bitcoin Core 27.0, check out the release notes.

Preparation

Choose a directory to work from, for example:

export RC_TEST_DIR=~/rctesting
mkdir $RC_TEST_DIR
cd $RC_TEST_DIR

1. Obtain Latest Release Candidate

Current Release Candidate: Bitcoin Core 27.0rc1 (release-notes)

There are two ways to grab the latest release candidate: source code or pre-compiled binary. Choose one of the two.

From Source

The source code for the release candidate can be obtained from the releases page (and extracted), or by using git directly:

cd $RC_TEST_DIR
git clone https://github.com/bitcoin/bitcoin.git
cd bitcoin
git checkout tags/v27.0rc1

(ensure source is present in RC_TEST_DIR/bitcoin in either instance (run mv if necessary))

From Binary

If using a binary is preferred, make sure to obtain the correct one for your system. When working from binary, extract (example for x86_64):

cd $RC_TEST_DIR
wget https://bitcoincore.org/bin/bitcoin-core-27.0/test.rc1/bitcoin-27.0rc1-x86_64-linux-gnu.tar.gz
tar xf bitcoin-27.0rc1-x86_64-linux-gnu.tar.gz

(directory $RC_TEST_DIR/bitcoin-27.0rc1/ now exists)

2. Compile Release Candidate

If testing v27 from binary (rather than building from source), skip this step

Before compiling, make sure that your system has all the correct dependencies installed.

For the migratewallet test, BDB wallet support is required, please refer to the Bitcoin Core build guides for instructions on compiling with BDB support.

For more information on compiling from source, here are some guides to compile Bitcoin Core for UNIX/Linux, macOS, Windows, FreeBSD, NetBSD, and OpenBSD.

3. Obtain Previous Releases

Some testing in this guide involves using previous releases, v26.0 and v25.1.

v26.0

Obtain the v26.0 binary from here and extract within RC_TEST_DIR:

cd $RC_TEST_DIR
wget https://bitcoincore.org/bin/bitcoin-core-26.0/bitcoin-26.0-x86_64-linux-gnu.tar.gz
tar xf bitcoin-26.0-x86_64-linux-gnu.tar.gz

(directory $RC_TEST_DIR/bitcoin-26.0 now exists)

v25.1

Obtain the v25.1 binary from here and extract within RC_TEST_DIR:

cd $RC_TEST_DIR
wget https://bitcoincore.org/bin/bitcoin-core-25.1/bitcoin-25.1-x86_64-linux-gnu.tar.gz
tar xf bitcoin-25.1-x86_64-linux-gnu.tar.gz

(directory $RC_TEST_DIR/bitcoin-25.1 now exists)

4. Setting up command line environment

Create data directories

Create temporary data directories for each version.

export DATA_DIR_27=/tmp/27-rc-test
mkdir $DATA_DIR_27
export DATA_DIR_26=/tmp/26-test
mkdir $DATA_DIR_26
export DATA_DIR_25=/tmp/25-test
mkdir $DATA_DIR_25

Specify paths

Define paths containing binaries that will be executed.

v27 from Source

If testing with v27 compiled source, run:

export BINARY_PATH_27=$RC_TEST_DIR/bitcoin/src
v27 Pre-Compiled Binaries

If testing from v27 downloaded binaries, run:

export BINARY_PATH_27=$RC_TEST_DIR/bitcoin-27.0rc1/bin
Previous Releases

Specify the binary path for the v26.0 and v25.1 binaries.

export BINARY_PATH_26=$RC_TEST_DIR/bitcoin-26.0/bin
export BINARY_PATH_25=$RC_TEST_DIR/bitcoin-25.1/bin

Create Aliases

To avoid specifying the data directory (-datadir=$DATA_DIR) on each command, below are a few extra variables to set.

v27 Aliases
alias bitcoind-test="$BINARY_PATH_27/bitcoind -datadir=$DATA_DIR_27"
alias bcli="$BINARY_PATH_27/bitcoin-cli -datadir=$DATA_DIR_27"
Aliases for Previous Releases
alias bitcoind-test-26="$BINARY_PATH_26/bitcoind -datadir=$DATA_DIR_26"
alias bcli26="$BINARY_PATH_26/bitcoin-cli -datadir=$DATA_DIR_26"
alias bitcoind-test-25="$BINARY_PATH_25/bitcoind -datadir=$DATA_DIR_25"
alias bcli25="$BINARY_PATH_25/bitcoin-cli -datadir=$DATA_DIR_25"
Alias for Datadir cleanup

datadir-cleanup is created to make cleaning easier between tests.

alias datadir-cleanup='rm -r $DATA_DIR_27; mkdir $DATA_DIR_27; rm -r $DATA_DIR_26; mkdir $DATA_DIR_26; rm -r $DATA_DIR_25; mkdir $DATA_DIR_25'
MacOS
(Expand for special instructions for macOS Big Sur 11+).

In macOS 11 on Macs with Apple silicon, and starting in macOS Big Sur 11 beta 6, the operating system enforces that any executable must be signed before it’s allowed to run. This is a very simple process:

codesign --sign - $BINARY_PATH/*

Due to macOS security settings, you are likely to be unable to execute any of the downloaded binaries from the terminal before you have manually marked them as trusted. To do so, run the below command to remove quarantine flags or navigate to your $BINARY_DIR in Finder, and launch each of the 3 binaries (bitcoind, bitcoin-qt, bitcoin-cli) by right-clicking, and then selecting "Open" twice. Afterwards, you should be able to execute them from the terminal as usual.

xattr -dr com.apple.quarantine $BINARY_PATH
NixOS
(Expand for NixOS-only instructions).

Patch up binaries to use a valid ld-linux-x86-64.so.2.

file /mnt/tmp/rctesting/bitcoin-25.1/bin/bitcoin-cli 
/mnt/tmp/rctesting/bitcoin-25.1/bin/bitcoin-cli: ELF 64-bit LSB pie executable, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, stripped

We see above that the binary's "interpreter" is using a path that doesn't exist on NixOS. Use fzf or some other tool to find a valid ld-linux-x86-64.so.2 under /nix/store/. Then proceed to patch them:

patchelf --set-interpreter /nix/store/qn3ggz5sf3hkjs2c797xf7nan3amdxmp-glibc-2.38-27/lib/ld-linux-x86-64.so.2 $BINARY_PATH_25/bitcoin-cli
patchelf --set-interpreter /nix/store/qn3ggz5sf3hkjs2c797xf7nan3amdxmp-glibc-2.38-27/lib/ld-linux-x86-64.so.2 $BINARY_PATH_25/bitcoind
patchelf --set-interpreter /nix/store/qn3ggz5sf3hkjs2c797xf7nan3amdxmp-glibc-2.38-27/lib/ld-linux-x86-64.so.2 $BINARY_PATH_26/bitcoin-cli
patchelf --set-interpreter /nix/store/qn3ggz5sf3hkjs2c797xf7nan3amdxmp-glibc-2.38-27/lib/ld-linux-x86-64.so.2 $BINARY_PATH_26/bitcoind

(Do the same for v27 if using downloaded binaries).

Verify Aliases

Verify that all versions are correct (e.g. that there are no accidental duplicates):

bcli --version | grep version
bitcoind-test --version | grep version
bcli26 --version | grep version
bitcoind-test-26 --version | grep version
bcli25 --version | grep version
bitcoind-test-25 --version | grep version

Expected results:

Bitcoin Core RPC client version v27.0.0rc1
Bitcoin Core version v27.0.0rc1
Bitcoin Core RPC client version v26.0.0
Bitcoin Core version v26.0.0
Bitcoin Core RPC client version v25.1.0
Bitcoin Core version v25.1.0

Command Execution

Throughout the rest of the guide, command execution will look similar to the following:

When using RPC:

bcli [cli args]

Starting a node in the foreground:

bitcoind-test

Use CTRL-C to stop a node running in the foreground.

Note: When starting a node, allow a few seconds to pass (e.g. 10 seconds) for the node to listen for RPC before running subsequent bcli commands

For the rest of the guide, bitcoind will be started in daemon mode (-daemonwait) and as such CTRL-C will not stop the process. Instead, use bcli stop.


mempool.dat v1/v2 compatibility

The mempool.dat file uses a new format (v2, storing XOR'd data instead of raw data), which isn't understood by previous versions of Bitcoin Core. This new file format obfuscates the data stored by XORing with a key. This aids in preventing another program from mistakely misinterpreting the file (e.g. anti-virus software). A temporary setting -persistmempoolv1 allows fallback to the legacy format.

In general, the goal is to exercise a subset of functionality and basic migration from the legacy v1 to the new v2 format.

A wallet is created and funded in order to generate transactions for the mempool. Following this, the persistmempoolv1 parameter is exercised, then a migration from the legacy file format to the new file format is exercised. If the tester has the resources availalbe, a subset of these activities are performed in a mainnet environment.

This test will require us to set up both v26 and v27 regtest nodes. First, let's setup the bitcoin.conf for our v27 node:

echo "regtest=1" > $DATA_DIR_27/bitcoin.conf

Now we'll set up our v26 node, using ports different than those used by default in Bitcoin Core so that they don't conflict with our v27 node:

echo "regtest=1" > $DATA_DIR_26/bitcoin.conf
echo "[regtest]" >> $DATA_DIR_26/bitcoin.conf
echo "rpcallowip=127.0.0.1" >> $DATA_DIR_26/bitcoin.conf
echo "rpcbind=127.0.0.1:18343" >> $DATA_DIR_26/bitcoin.conf
echo "rpcport=18343" >> $DATA_DIR_26/bitcoin.conf
echo "bind=127.0.0.1:18344" >> $DATA_DIR_26/bitcoin.conf
echo "bind=127.0.0.1:18345=onion" >> $DATA_DIR_26/bitcoin.conf

Start the v26 node:

bitcoind-test-26 -daemonwait

Create a wallet, and fund it with 50 tBTC:

bcli26 createwallet test
bcli26 -rpcwallet=test -generate 101

Expected results:

{
  "name": "test"
}
//...
{
  "address": "bcrt1q...",
  "blocks": [
    "57d09495e62df6a4e51f5ebe517b11b1913d7e9ba6dccbbbf5f3989bb2b43b37",
    // ...
    "659b4ed5400f14c3c901fa5646b40da3be8b9d526a03d9b5b6684fd99ce82c6e",
    "79cef426a1e3a909ebc238c85498cd05c15a79442df46e2d3017f3f84f3a881e"
  ]
}

(your block hashes will differ)

Create two transactions (a child and parent transaction) and add them to the mempool.

address=$(bcli26 -rpcwallet=test getnewaddress)
bcli26 -rpcwallet=test -named send outputs="{\"$address\": 1}" fee_rate=10
bcli26 -rpcwallet=test -named send outputs="{\"$address\": 1}" fee_rate=10

Expected results: txids specified for each transaction.

{
  "txid": "2fdc4aa2755f29d4ba8104f31e488b3e5df814a360ddb0b97ebc273a125a5971",
  "complete": true
}
// ...
{
  "txid": "28b8215819e7bc428787fc5e71c3927e20ad13c1a60d79ee1a409acded6efda3",
  "complete": true
}

Stop the v26 node (saving the mempool).

bcli26 stop

Copy the v26 data into the v27 datadir and restore the v27 config.

mv $DATA_DIR_27/bitcoin.conf $DATA_DIR_27/bitcoin.conf.backup
cp -r $DATA_DIR_26/* $DATA_DIR_27/
mv $DATA_DIR_27/bitcoin.conf.backup $DATA_DIR_27/bitcoin.conf

Start the v27 node with the new persistmempoolv1 parameter.

bitcoind-test -daemonwait -persistmempoolv1=1

View the contents of the mempool. Verify that the mempool contains the same two transactions created earlier.

bcli getmempoolinfo
bcli getrawmempool
{
  "loaded": true,
  "size": 2,
  "bytes": 282,
  "usage": 2416,
  "total_fee": 0.00002820,
  "maxmempool": 300000000,
  "mempoolminfee": 0.00001000,
  "minrelaytxfee": 0.00001000,
  "incrementalrelayfee": 0.00001000,
  "unbroadcastcount": 2,
  "fullrbf": false
}
// ...
[
  "2fdc4aa2755f29d4ba8104f31e488b3e5df814a360ddb0b97ebc273a125a5971",
  "28b8215819e7bc428787fc5e71c3927e20ad13c1a60d79ee1a409acded6efda3"
]

Stop the node and verify use of the legacy v1 format.

bcli stop
ls --full-time $DATA_DIR_27/regtest/mempool.dat
hexdump -n8 $DATA_DIR_27/regtest/mempool.dat

ℹ️ Note: If ls --full-time fails, you may have an implementation of ls where ls -lT will give you the precise time information.

Take note of the modified date.

Expected results:

-rw------- 1 dev dev 558 2024-03-17 18:18:13.224229360 -0400 /tmp/27-rc-test/regtest/mempool.dat
...
0000000 0001 0000 0000 0000                    
0000008

Start the node using the new mempool.dat format (by omitting -persistmempoolv1=1). Verify that the mempool contains the same two transactions created earlier.

bitcoind-test -daemonwait
bcli getmempoolinfo
bcli getrawmempool

Expected results:

{
  "loaded": true,
  "size": 2,
  "bytes": 282,
  "usage": 2416,
  "total_fee": 0.00002820,
  "maxmempool": 300000000,
  "mempoolminfee": 0.00001000,
  "minrelaytxfee": 0.00001000,
  "incrementalrelayfee": 0.00001000,
  "unbroadcastcount": 2,
  "fullrbf": false
}
// ...
[
  "2fdc4aa2755f29d4ba8104f31e488b3e5df814a360ddb0b97ebc273a125a5971",
  "28b8215819e7bc428787fc5e71c3927e20ad13c1a60d79ee1a409acded6efda3"
]

Save the mempool to mempool.dat in the new v2 format. Verify that mempool.dat has been modified, and is in the new v2 format.

bcli savemempool
ls --full-time $DATA_DIR_27/regtest/mempool.dat
hexdump -n8 $DATA_DIR_27/regtest/mempool.dat

Expected results:

{
  "filename": "/tmp/27-rc-test/regtest/mempool.dat"
}
...
-rw------- 1 dev dev 567 2024-03-17 18:19:48.700777882 -0400 /tmp/27-rc-test/regtest/mempool.dat
...
0000000 0002 0000 0000 0000                    
0000008

Restart the node and verify successful loading of the new v2 format. Verify that the mempool contains the same two transactions created earlier.

bcli stop
bitcoind-test -daemonwait
bcli getmempoolinfo
bcli getrawmempool

Stop the node.

bcli stop

Cleanup the datadirs.

datadir-cleanup

Exercising Migration on Mainnet

The above tests using RegTest and a small mempool. If you have a fully synced v26.0 or older node (non-critical/non-production environment) that has been online for some time (e.g. at least several hours), the following outlines steps to test migration with a much larger mempool using Mainnet. Please do not run these steps if your Mainnet node's uptime is considered essential/critical. That being said, there is value to running this test on v27, as those running this Test Guide may find issues before the much larger adoption/migration to v27 after final release. This tests the most common migration expected.

If not already done, obtain the v27 RC on your Mainnet node (see Obtain Latest Release Candidate). For example, the directory /home/<user>/rctesting/bitcoin-27.0rc1/bin exists and contains bitcoind and bitcoin-cli.

Stop your Mainnet node (either using bitcoin-cli or using systemctl stop bitcoind.service if your node uses a systemd service (in this example named bitcoind.service)):

bitcoin-cli stop

Wait for your node to fully stop (e.g. monitor tail -f /path/to/your/existing/datadir/debug.log).

Make a backup copy of your existing mempool.dat file, so it can be restored later.

cp /path/to/your/existing/datadir/mempool.dat /path/to/your/existing/datadir/mempool.dat.backup

Add debug=mempool to bitcoin.conf to see mempool additional mempool focused logging messages in debug.log.

Start the v27 node using the datadir and config for your mainnet node. If your bitcoin.conf and datadir are not the default location (i.e. ~/.bitcoin), you may need to include the -conf and -datadir command line arguments when lauching /home/<user>/rctesting/bitcoin-27.0rc1/bin/bitcoind.

/home/<user>/rctesting/bitcoin-27.0rc1/bin/bitcoind

You should see a set of mempool related message, including:

2024-03-19T02:36:43Z Loading 53296 mempool transactions from disk...

and "progress" messages such as:

2024-03-19T02:38:48Z Progress loading mempool transactions from disk: 30% (tried 15989, 37307 remaining)

Additionally, many "accepted" messages such as the following may be observed:

2024-03-19T02:41:23Z [mempool] AcceptToMemoryPool: peer=9: accepted fa491be58fcbb12022d85a28a0d40fd6350ec91d2fac0fa648015408ad4da6a3 (wtxid=f0abf597a69ba7e06a896049cf9b8121435b0da29e7cbf01c8f2185c54bc3d67) (poolsz 17510 txn, 84518 kB)

It may take several minutes to complete the process, and the following message should be present:

2024-03-19T02:47:52Z Imported mempool transactions from disk: 52778 succeeded, 494 failed, 0 expired, 24 already there, 0 waiting for initial broadcast

If you see error related to mempool activity, this would be noteworthy.

Stop the node (with CTRL-C). This will save the mempool to mempool.dat (assuming bitcoin.conf does not prohibit mempool persist). Check that the mempool.dat file is the new v2 format:

hexdump -n8 /path/to/your/datadir/mempool.dat
0000000 0002 0000 0000 0000                    
0000008

Start the v27 node again to ensure that the node successfully loads the new mempool.dat:

/home/<user>/rctesting/bitcoin-27.0rc1/bin/bitcoind

Stop the node (with CTRL-C). Remove debug=mempool from bitcoin.conf. Overwrite the new mempool.dat with the backup mempool.dat.backup to restore the v1 version.

mv /path/to/your/existing/datadir/mempool.dat.backup /path/to/your/existing/datadir/mempool.dat

Start the existing (non-v27) bitcoind through usual methods (e.g. systemctl start bitcoind.service).

v2 Transport on by Default

Bitcoin Core 27.0 enables v2 P2P Transport by default for automatic connection and for bitcoind and bitcoin-cli command line flags and RPC commands.

Testing manual connections

For the following manual v2 connection tests, we will want to connect to signet nodes that support v2 transport. Two such nodes that we will use in the examples below are:

  • bitcoin.achow101.com:38333
  • 74.208.205.30:38333

Before the following tests, let's add signet=1 the bitcoin.conf to use the signet network for testing, and turn off DNS seeds and fixed seeds so that we have more control over who we connect to. We will also use debug=net to enable logging of BCLOG::NET messages.

{
echo "signet=1"
echo "dnsseed=0"
echo "fixedseeds=0"
echo "debug=net"
} > $DATA_DIR_27/bitcoin.conf

Seeding from a v2 connection

First, let's try using a v2 seednode to fill up our peer address book (addrman):

bitcoind-test -daemonwait -seednode=bitcoin.achow101.com:38333

After ~30 seconds, we can run bitcoin-cli with the -netinfo flag to make sure our node has succeeded in bootstrapping its connection from the seed node. -netinfo takes an optional integer between 0 and 4 that controls what information is output. We can use values higher than 0 to see the transport versions of our connections in the v column. (2 and 4 give you an address column).

☝ We can also use the getpeerinfo command to see the status of our node's connections.

bcli -netinfo 3
Bitcoin Core client v27.0rc1 - server 70016/Satoshi:27.0.0/

<->   type   net  v  mping   ping send recv  txn  blk  hb addrp addrl  age id version
out  block  ipv4  1     20    131    2    2    *    0         .          6 13 70016/Satoshi:25.0.0/
out   full  ipv4  1     43    669    0    0         0      1007          6  4 70016/Satoshi:25.0.0/
out   full  ipv4  1     51    649    0    0         0      1003          6  5 70016/Satoshi:25.0.0/
out   full  ipv4  1     58     58   35   46                1004          6  6 70016/Satoshi:22.0.0(FutureBit-Apollo-Node)/
out  block  ipv4  1    101    101    5    4    *    0         .          5 15 70016/Satoshi:24.0.1/
out   full  ipv4  1    103    420    0    0         0      1006          6  1 70015/Satoshi:0.20.1/
out   full  ipv4  1    110    634    0    0         0      1003          5 19 70016/Satoshi:26.0.0/
out   full  ipv4  1    161    164    0    0         0      1008          6 11 70016/Satoshi:23.0.0/
out   full  ipv4  1    200    382    0    0         0      1003          3 21 70016/Satoshi:25.1.0/
out   full  ipv4  1    248   6699    0    0         0      1005          6 12 70016/Satoshi:26.0.0/
                        ms     ms  sec  sec  min  min                  min

         ipv4    ipv6   total   block
in          0       0       0
out        10       0      10       2
total      10       0      10

Local addresses: n/a

To ensure that you have successfully made an addrfetch connection to the seed node using v2 Transport, you can view logs related to v2 and peer=0 with the following:

cat $DATA_DIR_27/signet/debug.log | grep "v2\|peer=0"
2024-03-16T17:15:05Z Bitcoin Core version v27.0rc1 (release build)
2024-03-16T17:15:05Z [net] trying v2 connection bitcoin.achow101.com:38333 lastseen=0.0hrs
2024-03-16T17:15:05Z [net] Added connection peer=0
2024-03-16T17:15:05Z [net] sending version (102 bytes) peer=0
2024-03-16T17:15:05Z [net] send version message: version 70016, blocks=0, txrelay=1, peer=0
2024-03-16T17:15:05Z [net] start sending v2 handshake to peer=0
2024-03-16T17:15:05Z [net] received: version (103 bytes) peer=0
2024-03-16T17:15:05Z [net] sending wtxidrelay (0 bytes) peer=0
2024-03-16T17:15:05Z [net] sending sendaddrv2 (0 bytes) peer=0
2024-03-16T17:15:05Z [net] sending verack (0 bytes) peer=0
2024-03-16T17:15:05Z [net] sending getaddr (0 bytes) peer=0
2024-03-16T17:15:05Z [net] receive version message: /Satoshi:27.99.0/: version 70016, blocks=187042, us=76.151.248.21:49556, txrelay=1, peer=0
2024-03-16T17:15:05Z [net] received: wtxidrelay (0 bytes) peer=0
2024-03-16T17:15:05Z [net] received: sendaddrv2 (0 bytes) peer=0
2024-03-16T17:15:05Z [net] received: verack (0 bytes) peer=0
2024-03-16T17:15:05Z New addr-fetch v2 peer connected: version: 70016, blocks=187042, peer=0
2024-03-16T17:15:05Z [net] sending sendcmpct (9 bytes) peer=0
2024-03-16T17:15:05Z [net] sending ping (8 bytes) peer=0
2024-03-16T17:15:05Z [net] sending feefilter (8 bytes) peer=0
2024-03-16T17:15:05Z [net] received: sendcmpct (9 bytes) peer=0
2024-03-16T17:15:05Z [net] received: ping (8 bytes) peer=0
2024-03-16T17:15:05Z [net] sending pong (8 bytes) peer=0
2024-03-16T17:15:05Z [net] received: getheaders (965 bytes) peer=0
2024-03-16T17:15:05Z [net] Ignoring getheaders from peer=0 because active chain has too little work; sending empty response
2024-03-16T17:15:05Z [net] sending headers (1 bytes) peer=0
2024-03-16T17:15:05Z [net] received: feefilter (8 bytes) peer=0
2024-03-16T17:15:05Z [net] received: feefilter of 0.00001000 BTC/kvB from peer=0
2024-03-16T17:15:05Z [net] received: addrv2 (19582 bytes) peer=0
2024-03-16T17:15:05Z [net] Received addr: 999 addresses (999 processed, 0 rate-limited) from peer=0
2024-03-16T17:15:05Z [net] addrfetch connection completed peer=0; disconnecting
2024-03-16T17:15:06Z [net] disconnecting peer=0
2024-03-16T17:15:06Z [net] Cleared nodestate for peer=0

Some lines of note in the output from debug.log:

  • trying v2 connection <peer host or ip>:38333: We attempted to initiate the connection over v2.

  • New addr-fetch v2 peer connected // [...] peer=0: The v2 peer handshake has succeeded and we have connected to the seed node as an addr-fetch peer.

  • Received addr: <number of addresses> from peer=0: Our addrfetch peer has sent us an ADDR or ADDRV2 message and we've processed it and are about to store in our addrman.

  • addrfetch connection complete peer=0: We've stored the addresses we've received in our addrman and are now disconnecting from the seednode

Adding a peer manually

Now let's try to add a manual connection to a v2 capable peer and make sure that Bitcoin Core successfully makes a v2 connection to them.

☝ We can probably reuse the address or hostname we used as our -seednode since addrfetch connections are terminated once they've given us enough peers. If you decide to reuse the same v2 peer that you seeded from, use getpeerinfo to make sure they are not one of our curent connections, and if they are, use disconnectnode to drop them.

bcli addnode 74.208.205.30:38333 add

Check that our manual addnode connection succeeded with getaddednodeinfo:

bcli getaddednodeinfo
[
  {
    "addednode": "74.208.205.30:38333",
    "connected": true,
    "addresses": [
      {
        "address": "74.208.205.30:38333",
        "connected": "outbound"
      }
    ]
  }
]

Now verify that your connection to the added node is a v2 connection using getpeerinfo:

bcli getpeerinfo
{
    "id": 18,
    "addr": "<hostname/ip>:38333",
    "servicesnames": [
      "NETWORK",
      "WITNESS",
      "COMPACT_FILTERS",
      "NETWORK_LIMITED",
      "P2P_V2"
    ],

    // [...]

    "connection_type": "manual",
    "transport_protocol_type": "v2",
    "session_id": "92e0c037b75edf14d0e85032f5aedc8ff450e1b708bd03ceee38eef065dd5e87"
  },

Once the test is successful, don't forget to stop your node, and reset your testing environment for the next test:

bcli stop
datadir-cleanup

If you are interested in learning more about v2 Transport, we encourage you to read BIP324 and the BIP324 tracking issue.

netinfo backward compatibility with pre-v26 nodes

-netinfo capability of a newer (e.g. v26) RPC client (bitcoin-cli) was broken when communicating with an older node (e.g. v25). This PR fixes this, and displays "v1" for the transport protocol used in the -netinfo output.

First, let's configure our v25 regtest node so that it can run simultaneously with our v27 node:

echo "regtest=1" > $DATA_DIR_25/bitcoin.conf
echo "[regtest]" >> $DATA_DIR_25/bitcoin.conf
echo "rpcallowip=127.0.0.1" >> $DATA_DIR_25/bitcoin.conf
echo "rpcbind=127.0.0.1:18243" >> $DATA_DIR_25/bitcoin.conf
echo "rpcport=18243" >> $DATA_DIR_25/bitcoin.conf
echo "bind=127.0.0.1:18244" >> $DATA_DIR_25/bitcoin.conf
echo "bind=127.0.0.1:18245=onion" >> $DATA_DIR_25/bitcoin.conf

Start up our v25 regtest node:

bitcoind-test-25 -daemonwait

Start up our v27 regtest node:

echo "regtest=1" > $DATA_DIR_27/bitcoin.conf
bitcoind-test -daemonwait

Connect the two nodes:

bcli addnode 127.0.0.1:18244 add

Run netinfo with the v27 RPC client calling the v27 node. Verify that the v column (transport version) is 1 (since this is a v1 connection).

bcli -netinfo 4

Run netinfo with the v27 RPC client calling the v25 node. Verify that the v column (transport version) is 1 (since this is a v1 connection).

bcli -conf=$DATA_DIR_25/bitcoin.conf -rpccookiefile=$DATA_DIR_25/regtest/.cookie -netinfo 4

Once the test is successful, don't forget to stop your node, and reset your testing environment for the next test:

bcli stop
bcli25 stop
datadir-cleanup

v3 Transaction Policy

This release includes part of v3 transaction policy support for testnets (not on Mainnet yet since they are still considered non-standard).

This policy is motivated by an effort to prevent pinning attacks. These attacks involve chaining child transactions onto a neutral parent to lower the average fee rate so as to make it unlikely to get accepted into a block, making it uneconomical or otherwise hard for the another party who depends on the transaction confirming soon (maybe to fulfill some L2 protocol constraints).

The new relay policy changes the rules for when transactions are allowed/blocked from entering the local mempool and relayed to peers. Previous rules allowed having up to 25 pending transaction ancestors/descendants in the mempool, and later added a special CPFP carve out rule. The new v3 policy only allows up to 2 related transactions - parent+child. This together with lower total transaction size limits should help make countering pinning-attempts more economically viable.


We will only be using the v27 node for this test.

Switch to regtest and start the node:

echo "regtest=1" > $DATA_DIR_27/bitcoin.conf
bitcoind-test -daemonwait

Create our wallet and generate 2 addresses:

bcli createwallet v3txwallet
address_a=$(bcli getnewaddress)
address_b=$(bcli getnewaddress)

Generate a regtest block paying the coinbase to our wallet:

bcli generatetoaddress 1 $address_a

Age our coinbase UTXO to make it spendable through generating 100 blocks.

bcli -generate 100

Confirm that we have a spendable transaction. (Your txid, address and scriptPubKey will differ).

bcli listunspent
[
  {
    "txid": "fff75e2f62aad3e859c5b66d2c047b87291ce79a77193ba510b65363d82421d8",
    "vout": 0,
    "address": "bcrt1qddzc7pg8h3u7s4m5hvkm06pq4k64062c84p8sm",
    "label": "",
    "scriptPubKey": "00146b458f0507bc79e85774bb2db7e820adb557e958",
    "amount": 50.00000000,
    "confirmations": 101,
    "spendable": true,
    // ...
  }
]

For the following tests we will be creating 3 chained transactions, to see how the mempool behaves when trying to submit them as v2 transactions vs v3 transactions.

v2 transactions

Create our first transaction from the unspent coinbase UTXO, leaving a few sats on the table for the fee:

coinbase_txid=$(bcli listunspent | jq -r ".[0].txid")
parent_raw_tx=$(bcli createrawtransaction "[{\"txid\": \"$coinbase_txid\", \"vout\": 0}]" "[{\"$address_b\": 1},{\"$address_a\": 48.999}]")
echo $parent_raw_tx
0200000001d82124d86353b610a53b19779ae71c29877b042c6db6c559e8d3aa622f5ef7ff0000000000fdffffff0200e1f50500000000160014623affcf6334d7e50a9757b80f702e4129aa02d0608a0e24010000001600146b458f0507bc79e85774bb2db7e820adb557e95800000000

Sign our first transaction:

signed_parent_tx_result=$(bcli signrawtransactionwithwallet $parent_raw_tx)
echo $signed_parent_tx_result
{
  "hex": "02000000000101d82124d86353b610a53b19779ae71c29877b042c6db6c559e8d3aa622f5ef7ff0000000000fdffffff0200e1f50500000000160014623affcf6334d7e50a9757b80f702e4129aa02d0608a0e24010000001600146b458f0507bc79e85774bb2db7e820adb557e9580247304402202ced690736814eb6b609105f53260e39026f28ec54f01865d457bfaa93487548022033a06d19ad715c035a62ad6c7b8760ec9f2dd8d79c6863219bdd05227d4fb96b012102fa562c1af027c39ee03a9892b388f854904c99fbf80b77f69aba3f48305f2aa900000000",
  "complete": true
}

Now decode the transaction to see txid, outputs, scriptPubKey, witness script (second hex blob in txinwitness). We will need these when signing the child:

signed_parent_tx=$(echo $signed_parent_tx_result | jq -r .hex)
decoded_parent_tx=$(bcli decoderawtransaction $signed_parent_tx)
echo "$decoded_parent_tx"
{
  "txid": "b6b81ebcdcc9e35e4d19ea03b66d24f9836483eb14ec9f0d16f1dc54b7c6ffbc",
  "hash": "2381e183a8766e0caa59b10626113851737a560a306daac6d930d9f1e7efa20b",
  "version": 2,
  // ...
  "vin": [
    {
      "txid": "fff75e2f62aad3e859c5b66d2c047b87291ce79a77193ba510b65363d82421d8",
      "vout": 0,
      // ...
      "txinwitness": [
        "304402202ced690736814eb6b609105f53260e39026f28ec54f01865d457bfaa93487548022033a06d19ad715c035a62ad6c7b8760ec9f2dd8d79c6863219bdd05227d4fb96b01",
        "02fa562c1af027c39ee03a9892b388f854904c99fbf80b77f69aba3f48305f2aa9"
      ]
    }
  ],
  "vout": [
    {
      "value": 1.00000000,
      "n": 0,
      // ...
    },
    {
      "value": 48.99900000,
      "n": 1,
      "scriptPubKey": {
        // ...
        "hex": "00146b458f0507bc79e85774bb2db7e820adb557e958"
      }
    }
  ]
}

Create a child transaction spending the second output ("n": 1) of 48.999 tBTC:

parent_txid=$(echo $decoded_parent_tx | jq -r .txid)
child_raw_tx=$(bcli createrawtransaction "[{\"txid\": \"$parent_txid\", \"vout\": 1}]" "[{\"$address_b\": 1},{\"$address_a\": 47.998}]")
echo $child_raw_tx
0200000001bcffc6b754dcf1160d9fec14eb836483f9246db603ea194d5ee3c9dcbc1eb8b60100000000fdffffff0200e1f50500000000160014623affcf6334d7e50a9757b80f702e4129aa02d0c022171e010000001600146b458f0507bc79e85774bb2db7e820adb557e95800000000

Sign the child:

parent_spk=$(echo $decoded_parent_tx | jq -r ".vout[1].scriptPubKey.hex")
parent_witness_script=$(echo $decoded_parent_tx | jq -r ".vin[0].txinwitness[1]")
signed_child_tx_result=$(bcli signrawtransactionwithwallet $child_raw_tx "[{\"txid\":\"$parent_txid\", \"vout\": 1, \"scriptPubKey\": \"$parent_spk\", \"witnessScript\": \"$parent_witness_script\", \"amount\": 48.999}]")
signed_child_tx=$(echo $signed_child_tx_result | jq -r .hex)
echo "$signed_child_tx_result"
{
  "hex": "02000000000101bcffc6b754dcf1160d9fec14eb836483f9246db603ea194d5ee3c9dcbc1eb8b60100000000fdffffff0200e1f50500000000160014623affcf6334d7e50a9757b80f702e4129aa02d0c022171e010000001600146b458f0507bc79e85774bb2db7e820adb557e958024730440220049298a8593e99b8d91b2e9c1777fcb0fbf6c09c74cde5c8487cacf12d04e61902200c0da87edf9f88665a845cea1eda25d33d1bce1651266f697c2b0f0fe026846f012102fa562c1af027c39ee03a9892b388f854904c99fbf80b77f69aba3f48305f2aa900000000",
  "complete": true
}

Decode the child to see txid, outputs, scriptPubKey, witness script:

decoded_child_tx=$(bcli decoderawtransaction $signed_child_tx)
echo "$decoded_child_tx"
{
  "txid": "cf177c1070c81b292cd94537c2a66b54cb4b63f3b1fe7d36bb9c24b4d33c4206",
  // ...
  "version": 2,
  // ...
  "vin": [
    {
      "txid": "b6b81ebcdcc9e35e4d19ea03b66d24f9836483eb14ec9f0d16f1dc54b7c6ffbc",
      "vout": 1,
      // ...
      "txinwitness": [
        "30440220049298a8593e99b8d91b2e9c1777fcb0fbf6c09c74cde5c8487cacf12d04e61902200c0da87edf9f88665a845cea1eda25d33d1bce1651266f697c2b0f0fe026846f01",
        "02fa562c1af027c39ee03a9892b388f854904c99fbf80b77f69aba3f48305f2aa9"
      ]
    }
  ],
  "vout": [
    {
      "value": 1.00000000,
      "n": 0,
      // ...
    },
    {
      "value": 47.99800000,
      "n": 1,
      "scriptPubKey": {
        // ...
        "hex": "00146b458f0507bc79e85774bb2db7e820adb557e958"
      }
    }
  ]
}

Create a grandchild transaction:

child_txid=$(echo $decoded_child_tx | jq -r .txid)
child_spk=$(echo $decoded_child_tx | jq -r ".vout[1].scriptPubKey.hex")
child_witness_script=$(echo $decoded_child_tx | jq -r ".vin[0].txinwitness[1]")
grandchild_raw_tx=$(bcli createrawtransaction "[{\"txid\": \"$child_txid\", \"vout\": 1}]" "[{\"$address_b\": 1},{\"$address_a\": 46.997}]")
echo $grandchild_raw_tx
020000000106423cd3b4249cbb367dfeb1f3634bcb546ba6c23745d92c291bc870107c17cf0100000000fdffffff0200e1f50500000000160014623affcf6334d7e50a9757b80f702e4129aa02d020bb1f18010000001600146b458f0507bc79e85774bb2db7e820adb557e95800000000

Sign the grandchild:

signed_grandchild_tx_result=$(bcli signrawtransactionwithwallet $grandchild_raw_tx "[{\"txid\":\"$child_txid\", \"vout\": 1, \"scriptPubKey\": \"$child_spk\", \"witnessScript\": \"$child_witness_script\", \"amount\": 47.998}]")
signed_grandchild_tx=$(echo $signed_grandchild_tx_result | jq -r .hex)
echo "$signed_grandchild_tx_result"
{
  "hex": "0200000000010106423cd3b4249cbb367dfeb1f3634bcb546ba6c23745d92c291bc870107c17cf0100000000fdffffff0200e1f50500000000160014623affcf6334d7e50a9757b80f702e4129aa02d020bb1f18010000001600146b458f0507bc79e85774bb2db7e820adb557e95802473044022071b22ccef6bf40eb04bc43271c272ed0bf75d3b5cdf61a7778d643f3629eebc702204edeb3eac993ee3973a174f8dda0537d5c37f76d67a0b3a394b70d92153697b8012102fa562c1af027c39ee03a9892b388f854904c99fbf80b77f69aba3f48305f2aa900000000",
  "complete": true
}

See if the mempool would accept all 3 together:

bcli testmempoolaccept "[\"$signed_parent_tx\",\"$signed_child_tx\",\"$signed_grandchild_tx\"]"
[
  {
    "txid": "b6b81ebcdcc9e35e4d19ea03b66d24f9836483eb14ec9f0d16f1dc54b7c6ffbc",
    "wtxid": "2381e183a8766e0caa59b10626113851737a560a306daac6d930d9f1e7efa20b",
    "allowed": true,
    "vsize": 141,
    "fees": {
      "base": 0.00100000,
      "effective-feerate": 0.00709219,
      "effective-includes": [
        "2381e183a8766e0caa59b10626113851737a560a306daac6d930d9f1e7efa20b"
      ]
    }
  },
  {
    "txid": "cf177c1070c81b292cd94537c2a66b54cb4b63f3b1fe7d36bb9c24b4d33c4206",
    "wtxid": "25f4049612ae54b71fa799dffe696bc0f86bafdecfed6efa6a7a366817d91ddb",
    "allowed": true,
    "vsize": 141,
    "fees": {
      "base": 0.00100000,
      "effective-feerate": 0.00709219,
      "effective-includes": [
        "25f4049612ae54b71fa799dffe696bc0f86bafdecfed6efa6a7a366817d91ddb"
      ]
    }
  },
  {
    "txid": "a6734c9721960da6fe538d65919b09a0bfbaa6c96c29c66cb634029c07336ede",
    "wtxid": "41af8505f072023992b62c3727d2142b76a67c3ca6de732c435cf41624b5789c",
    "allowed": true,
    "vsize": 141,
    "fees": {
      "base": 0.00100000,
      "effective-feerate": 0.00709219,
      "effective-includes": [
        "41af8505f072023992b62c3727d2142b76a67c3ca6de732c435cf41624b5789c"
      ]
    }
  }
]

This shows that 3 chained v2 transactions would be accepted into the mempool.

v3 transactions

Now we are going to do the same thing but for v3 transactions.

Take the first createrawtransaction result...

echo $parent_raw_tx
0200000001d82124d86353b610a53b19779ae71c29877b042c6db6c559e8d3aa622f5ef7ff0000000000fdffffff0200e1f50500000000160014623affcf6334d7e50a9757b80f702e4129aa02d0608a0e24010000001600146b458f0507bc79e85774bb2db7e820adb557e95800000000

...but edit the initial version from being little endian 2 - 02000000, to 3 - 03000000, and sign it:

v3_parent_signed_tx_result=$(bcli signrawtransactionwithwallet 0300000001d82124d86353b610a53b19779ae71c29877b042c6db6c559e8d3aa622f5ef7ff0000000000fdffffff0200e1f50500000000160014623affcf6334d7e50a9757b80f702e4129aa02d0608a0e24010000001600146b458f0507bc79e85774bb2db7e820adb557e95800000000)
v3_parent_signed_tx=$(echo $v3_parent_signed_tx_result | jq -r .hex)
echo "$v3_parent_signed_tx_result"
{
  "hex": "03000000000101d82124d86353b610a53b19779ae71c29877b042c6db6c559e8d3aa622f5ef7ff0000000000fdffffff0200e1f50500000000160014623affcf6334d7e50a9757b80f702e4129aa02d0608a0e24010000001600146b458f0507bc79e85774bb2db7e820adb557e958024730440220119e8a137e023990c5deeef0d64483c0832c9f7d84e78bac8f8d5706cc2c57d202206ab3b45535c12ae99b9f88cc8b6b217eaad37ab818a0f509e82ce723593ab2a4012102fa562c1af027c39ee03a9892b388f854904c99fbf80b77f69aba3f48305f2aa900000000",
  "complete": true
}

The modified version results in a different txid.

See if it will be accepted into the mempool:

bcli testmempoolaccept "[\"$v3_parent_signed_tx\"]"
[
  {
    "txid": "893803395981efb80af450a5958587e1c54d03e44149a8ff7ec1dd54955eee84",
    "wtxid": "d5ef8955a5b7d0d2a865690156354a98602b7aaca3133b4685b596bafdcf04aa",
    "allowed": false,
    "reject-reason": "version"
  }
]

What happened?!

v3 transactions are still treated as non-standard, but we are able to enable them on regtest. Let's do that:

bcli stop
bitcoind-test -daemonwait -acceptnonstdtxn

Reload the wallet:

bcli loadwallet v3txwallet

Retry:

bcli testmempoolaccept "[\"$v3_parent_signed_tx\"]"
[
  {
    "txid": "893803395981efb80af450a5958587e1c54d03e44149a8ff7ec1dd54955eee84",
    "wtxid": "d5ef8955a5b7d0d2a865690156354a98602b7aaca3133b4685b596bafdcf04aa",
    "allowed": true,
    "vsize": 141,
    "fees": {
      "base": 0.00100000,
      "effective-feerate": 0.00709219,
      "effective-includes": [
        "d5ef8955a5b7d0d2a865690156354a98602b7aaca3133b4685b596bafdcf04aa"
      ]
    }
  }
]

Woho, our regtest mempool now accepts v3!

Time to grab the tx details for our new child:

decoded_v3_parent_tx=$(bcli decoderawtransaction $v3_parent_signed_tx)
echo "$decoded_v3_parent_tx"
{
  "txid": "893803395981efb80af450a5958587e1c54d03e44149a8ff7ec1dd54955eee84",
  // ...
  "version": 3,
  // ...
  "vin": [
    {
      "txid": "fff75e2f62aad3e859c5b66d2c047b87291ce79a77193ba510b65363d82421d8",
      "vout": 0,
      // ...
      "txinwitness": [
        "30440220119e8a137e023990c5deeef0d64483c0832c9f7d84e78bac8f8d5706cc2c57d202206ab3b45535c12ae99b9f88cc8b6b217eaad37ab818a0f509e82ce723593ab2a401",
        "02fa562c1af027c39ee03a9892b388f854904c99fbf80b77f69aba3f48305f2aa9"
      ]
    }
  ],
  "vout": [
    {
      "value": 1.00000000,
      "n": 0,
      // ...
    },
    {
      "value": 48.99900000,
      "n": 1,
      "scriptPubKey": {
        // ...
        "hex": "00146b458f0507bc79e85774bb2db7e820adb557e958"
      }
    }
  ]
}

Create the v3 child transaction:

v3_parent_txid=$(echo $decoded_v3_parent_tx | jq -r .txid)
v3_parent_spk=$(echo $decoded_v3_parent_tx | jq -r ".vout[1].scriptPubKey.hex")
v3_parent_witness_script=$(echo $decoded_v3_parent_tx | jq -r ".vin[0].txinwitness[1]")
bcli createrawtransaction "[{\"txid\": \"$v3_parent_txid\", \"vout\": 1}]" "[{\"$address_b\": 1},{\"$address_a\": 47.998}]"
020000000184ee5e9554ddc17effa84941e4034dc5e1878595a550f40ab8ef8159390338890100000000fdffffff0200e1f50500000000160014623affcf6334d7e50a9757b80f702e4129aa02d0c022171e010000001600146b458f0507bc79e85774bb2db7e820adb557e95800000000

Tweak the version again and sign:

v3_child_signed_tx_result=$(bcli signrawtransactionwithwallet 030000000184ee5e9554ddc17effa84941e4034dc5e1878595a550f40ab8ef8159390338890100000000fdffffff0200e1f50500000000160014623affcf6334d7e50a9757b80f702e4129aa02d0c022171e010000001600146b458f0507bc79e85774bb2db7e820adb557e95800000000 "[{\"txid\":\"$v3_parent_txid\", \"vout\": 1, \"scriptPubKey\": \"$v3_parent_spk\", \"witnessScript\": \"$v3_parent_witness_script\", \"amount\": 48.999}]")
v3_child_signed_tx=$(echo $v3_child_signed_tx_result | jq -r .hex)
echo "$v3_child_signed_tx_result"
{
  "hex": "0300000000010184ee5e9554ddc17effa84941e4034dc5e1878595a550f40ab8ef8159390338890100000000fdffffff0200e1f50500000000160014623affcf6334d7e50a9757b80f702e4129aa02d0c022171e010000001600146b458f0507bc79e85774bb2db7e820adb557e9580247304402203db6610092c1f7bbc38b232ddd0bed0ad5f2cffc245517a53c3bb856cf9aea8e0220707f18fbfa6fcdb4860d6d92721c8e2d243cabbba0045dcf14955b634c0b652b012102fa562c1af027c39ee03a9892b388f854904c99fbf80b77f69aba3f48305f2aa900000000",
  "complete": true
}

Decode it for the grandchild:

decoded_v3_child_tx=$(bcli decoderawtransaction $v3_child_signed_tx)
echo "$decoded_v3_child_tx"
{
  "txid": "863889eecf327d63c164e5689bf77924ef8b573cfd699120e3a0eeb2da4463d8",
  // ...
  "version": 3,
  // ...
  "vin": [
    {
      "txid": "893803395981efb80af450a5958587e1c54d03e44149a8ff7ec1dd54955eee84",
      "vout": 1,
      // ...
      "txinwitness": [
        "304402203db6610092c1f7bbc38b232ddd0bed0ad5f2cffc245517a53c3bb856cf9aea8e0220707f18fbfa6fcdb4860d6d92721c8e2d243cabbba0045dcf14955b634c0b652b01",
        "02fa562c1af027c39ee03a9892b388f854904c99fbf80b77f69aba3f48305f2aa9"
      ],
      "sequence": 4294967293
    }
  ],
  "vout": [
    {
      "value": 1.00000000,
      "n": 0,
      // ...
    },
    {
      "value": 47.99800000,
      "n": 1,
      "scriptPubKey": {
        // ...
        "hex": "00146b458f0507bc79e85774bb2db7e820adb557e958"
      }
    }
  ]
}

Create v3 grandchild transaction:

v3_child_txid=$(echo $decoded_v3_child_tx | jq -r .txid)
v3_child_spk=$(echo $decoded_v3_child_tx | jq -r ".vout[1].scriptPubKey.hex")
v3_child_witness_script=$(echo $decoded_v3_child_tx | jq -r ".vin[0].txinwitness[1]")
bcli createrawtransaction "[{\"txid\": \"$v3_child_txid\", \"vout\": 1}]" "[{\"$address_b\": 1},{\"$address_a\": 46.997}]"
0200000001d86344dab2eea0e3209169fd3c578bef2479f79b68e564c1637d32cfee8938860100000000fdffffff0200e1f50500000000160014623affcf6334d7e50a9757b80f702e4129aa02d020bb1f18010000001600146b458f0507bc79e85774bb2db7e820adb557e95800000000

Tweak version and sign:

v3_grandchild_signed_tx_result=$(bcli signrawtransactionwithwallet 0300000001d86344dab2eea0e3209169fd3c578bef2479f79b68e564c1637d32cfee8938860100000000fdffffff0200e1f50500000000160014623affcf6334d7e50a9757b80f702e4129aa02d020bb1f18010000001600146b458f0507bc79e85774bb2db7e820adb557e95800000000 "[{\"txid\":\"$v3_child_txid\", \"vout\": 1, \"scriptPubKey\": \"$v3_child_spk\", \"witnessScript\": \"$v3_child_witness_script\", \"amount\": 47.998}]")
v3_grandchild_signed_tx=$(echo $v3_grandchild_signed_tx_result | jq -r .hex)
echo "$v3_grandchild_signed_tx_result"
{
  "hex": "03000000000101d86344dab2eea0e3209169fd3c578bef2479f79b68e564c1637d32cfee8938860100000000fdffffff0200e1f50500000000160014623affcf6334d7e50a9757b80f702e4129aa02d020bb1f18010000001600146b458f0507bc79e85774bb2db7e820adb557e9580247304402206b91fc223fe2ec13929bd39dea494997bf8bd85092c0119697f96d18070c6f1d0220157fe6e6885dc988fd6cee9ed13397da95d977ac017bdf5607c8d4f7b76bf338012102fa562c1af027c39ee03a9892b388f854904c99fbf80b77f69aba3f48305f2aa900000000",
  "complete": true
}

See whether our mempool will accept all 3 together:

bcli testmempoolaccept "[\"$v3_parent_signed_tx\",\"$v3_child_signed_tx\",\"$v3_grandchild_signed_tx\"]"
[
  {
    "txid": "893803395981efb80af450a5958587e1c54d03e44149a8ff7ec1dd54955eee84",
    "wtxid": "d5ef8955a5b7d0d2a865690156354a98602b7aaca3133b4685b596bafdcf04aa",
    "package-error": "v3-violation, tx 1a46bb68edb4808bc3122d59f0aa9f43d350fc5d311986ff630d2ad89504c74a (wtxid=db1c0c79bf895a2e7810819ce15a53d0d42e25832357590d83084d1004c104d4) would have too many ancestors"
  },
  {
    "txid": "863889eecf327d63c164e5689bf77924ef8b573cfd699120e3a0eeb2da4463d8",
    "wtxid": "9754824e40a51f617aa3b0e7611e1229b7094894619cd948d8597202491add81",
    "package-error": "v3-violation, tx 1a46bb68edb4808bc3122d59f0aa9f43d350fc5d311986ff630d2ad89504c74a (wtxid=db1c0c79bf895a2e7810819ce15a53d0d42e25832357590d83084d1004c104d4) would have too many ancestors"
  },
  {
    "txid": "1a46bb68edb4808bc3122d59f0aa9f43d350fc5d311986ff630d2ad89504c74a",
    "wtxid": "db1c0c79bf895a2e7810819ce15a53d0d42e25832357590d83084d1004c104d4",
    "package-error": "v3-violation, tx 1a46bb68edb4808bc3122d59f0aa9f43d350fc5d311986ff630d2ad89504c74a (wtxid=db1c0c79bf895a2e7810819ce15a53d0d42e25832357590d83084d1004c104d4) would have too many ancestors"
  }
]

No cigar. Let's try with just the first 2:

bcli testmempoolaccept "[\"$v3_parent_signed_tx\",\"$v3_child_signed_tx\"]"
[
  {
    "txid": "893803395981efb80af450a5958587e1c54d03e44149a8ff7ec1dd54955eee84",
    "wtxid": "d5ef8955a5b7d0d2a865690156354a98602b7aaca3133b4685b596bafdcf04aa",
    "allowed": true,
    "vsize": 141,
    "fees": {
      "base": 0.00100000,
      "effective-feerate": 0.00709219,
      "effective-includes": [
        "d5ef8955a5b7d0d2a865690156354a98602b7aaca3133b4685b596bafdcf04aa"
      ]
    }
  },
  {
    "txid": "863889eecf327d63c164e5689bf77924ef8b573cfd699120e3a0eeb2da4463d8",
    "wtxid": "9754824e40a51f617aa3b0e7611e1229b7094894619cd948d8597202491add81",
    "allowed": true,
    "vsize": 141,
    "fees": {
      "base": 0.00100000,
      "effective-feerate": 0.00709219,
      "effective-includes": [
        "9754824e40a51f617aa3b0e7611e1229b7094894619cd948d8597202491add81"
      ]
    }
  }
]

Good, the v3 transaction policy only allows for 2 connected pending transactions in the mempool.

Let's see if we are able to RBF the child. First, let us submit the transactions to our mempool:

bcli submitpackage "[\"$v3_parent_signed_tx\",\"$v3_child_signed_tx\"]"
{
  "package_msg": "success",
  "tx-results": {
    "d5ef8955a5b7d0d2a865690156354a98602b7aaca3133b4685b596bafdcf04aa": {
      "txid": "893803395981efb80af450a5958587e1c54d03e44149a8ff7ec1dd54955eee84",
      "vsize": 141,
      "fees": {
        "base": 0.00100000,
        "effective-feerate": 0.00709219,
        "effective-includes": [
          "d5ef8955a5b7d0d2a865690156354a98602b7aaca3133b4685b596bafdcf04aa"
        ]
      }
    },
    "9754824e40a51f617aa3b0e7611e1229b7094894619cd948d8597202491add81": {
      "txid": "863889eecf327d63c164e5689bf77924ef8b573cfd699120e3a0eeb2da4463d8",
      "vsize": 141,
      "fees": {
        "base": 0.00100000,
        "effective-feerate": 0.00709219,
        "effective-includes": [
          "9754824e40a51f617aa3b0e7611e1229b7094894619cd948d8597202491add81"
        ]
      }
    }
  },
  "replaced-transactions": [
  ]
}

Create a new version of the child paying higher fees:

bcli createrawtransaction "[{\"txid\": \"$v3_parent_txid\", \"vout\": 1}]" "[{\"$address_b\": 1},{\"$address_a\": 47.997}]"
020000000184ee5e9554ddc17effa84941e4034dc5e1878595a550f40ab8ef8159390338890100000000fdffffff0200e1f50500000000160014623affcf6334d7e50a9757b80f702e4129aa02d0209c151e010000001600146b458f0507bc79e85774bb2db7e820adb557e95800000000

Tweak to v3 and sign:

high_fee_child_signed_tx_result=$(bcli signrawtransactionwithwallet 030000000184ee5e9554ddc17effa84941e4034dc5e1878595a550f40ab8ef8159390338890100000000fdffffff0200e1f50500000000160014623affcf6334d7e50a9757b80f702e4129aa02d0209c151e010000001600146b458f0507bc79e85774bb2db7e820adb557e95800000000 "[{\"txid\":\"$v3_parent_txid\", \"vout\": 1, \"scriptPubKey\": \"$v3_parent_spk\", \"witnessScript\": \"$v3_parent_witness_script\", \"amount\": 48.999}]")
high_fee_child_signed_tx=$(echo $high_fee_child_signed_tx_result | jq -r .hex)
echo "$high_fee_child_signed_tx_result"
{
  "hex": "0300000000010184ee5e9554ddc17effa84941e4034dc5e1878595a550f40ab8ef8159390338890100000000fdffffff0200e1f50500000000160014623affcf6334d7e50a9757b80f702e4129aa02d0209c151e010000001600146b458f0507bc79e85774bb2db7e820adb557e958024730440220011d8df0b2615bcd6506b81f799544188538f7a43f9cb0f45fea9db6915258f102204b7d190d63b9d4e1bdb7bb3654215b9c74dfd8a2046421fb797279d325d27568012102fa562c1af027c39ee03a9892b388f854904c99fbf80b77f69aba3f48305f2aa900000000",
  "complete": true
}

Submit the replacement child together with the parent:

bcli submitpackage "[\"$v3_parent_signed_tx\",\"$high_fee_child_signed_tx\"]"
{
  "package_msg": "success",
  "tx-results": {
    "d5ef8955a5b7d0d2a865690156354a98602b7aaca3133b4685b596bafdcf04aa": {
      "txid": "893803395981efb80af450a5958587e1c54d03e44149a8ff7ec1dd54955eee84",
      "vsize": 141,
      "fees": {
        "base": 0.00100000
      }
    },
    "10b7cd4b26ef17eaaa8c6844334675a55ee7ebaa7eafe8feefc1eb4b640216c8": {
      "txid": "598fb5e9b7909cdf7c03910864ff396dffa1e2e8f03096c5f538267f13074598",
      "vsize": 141,
      "fees": {
        "base": 0.00200000,
        "effective-feerate": 0.01418439,
        "effective-includes": [
          "10b7cd4b26ef17eaaa8c6844334675a55ee7ebaa7eafe8feefc1eb4b640216c8"
        ]
      }
    }
  },
  "replaced-transactions": [
    "863889eecf327d63c164e5689bf77924ef8b573cfd699120e3a0eeb2da4463d8"
  ]
}

Once the test is successful, don't forget to stop your node, and reset your testing environment for the next test:

bcli stop
datadir-cleanup

Note: v3 sibling eviction (#29306) is currently not included as of v27.0rc1.

There are a bunch of cases that we haven't tested above. Like mixing v3 with confirmed and unconfirmed non-v3 transactions. See version3_transactions.md and mempool_accept_v3.py for further test ideas.

CoinGrinder coin selection algorithm

Bitcoin Core 27.0 includes a new coin selection algorithm called CoinGrinder. The goal of this algorithm is to minimize the total weight of the inputs when constructing a transaction with change in a high feerate environment. Previously, there have been issues where a ton of small inputs were used in a transaction even when there was a single UTXO that would have sufficed, resulting in an unnecessarily high transaction fee. Currently, CoinGrinder is only used when the feerate is greater than 3*long_term_feerate (3 x 10 sats/vb by default).

For this test, we will simulate the situation described in the link above, configuring a wallet to contain many small utxos and one larger utxo and then seeing whether input set weight is minimized when a transaction is constructed in a high fee environment.

First, make sure your data directories are clean. Then start up a v27 node on regtest.

echo "regtest=1" > $DATA_DIR_27/bitcoin.conf
bitcoind-test -daemonwait

Create two wallets. One will be used to receive block rewards and to fund the other one, which will be configured with a certain set of UTXOs (bunch of small, one big).

bcli createwallet coinbasewallet 
bcli createwallet testwallet

Mine blocks so that our coinbase UTXO is spendable.

bcli -rpcwallet=coinbasewallet generatetoaddress 101 $(bcli -rpcwallet=coinbasewallet getnewaddress)

Now, we'll send 200 UTXOs of 0.003 tBTC to our test wallet. We send them in 10 batches of 20 transactions, mining a block in between to confirm each batch.

num_batches=10
tx_per_batch=20

for batch in $(seq 1 $num_batches); do
    echo "Creating batch $batch of $tx_per_batch transactions..."
    for tx in $(seq 1 $tx_per_batch); do
        bcli -named -rpcwallet=coinbasewallet sendtoaddress address=$(bcli -rpcwallet=testwallet getnewaddress) amount=0.003 fee_rate=15
    done
    echo "Generating 1 block to confirm the batch..."
    bcli -rpcwallet=coinbasewallet generatetoaddress 1 $(bcli -rpcwallet=coinbasewallet getnewaddress)
done

Use this command to see the 0.003 UTXOs:

bcli -rpcwallet=testwallet listunspent

Send one UTXO of 0.5 tBTC to our test wallet. This will output a transaction hash, which will be important for later so be sure to save it. Your hash will be different than the one shown below.

bcli -rpcwallet=coinbasewallet -named sendtoaddress address=$(bcli -rpcwallet=testwallet getnewaddress) amount=0.5 fee_rate=15
2254455264eb3e6e02471594f4eebe9c140de9c09074e6a52485f4de4fa0936d

Mine 6 blocks to ensure CoinGrinder is used. Coin selection takes number of confirmations into account, so we need all UTXOs in our test wallet to have at least 6 confirmations.

bcli -rpcwallet=coinbasewallet generatetoaddress 6 $(bcli -rpcwallet=coinbasewallet getnewaddress)

Use this command to confirm that there is an unspent transaction worth 0.5 tBTC available in the test wallet. Make sure to have jq installed.

bcli -rpcwallet=testwallet listunspent | jq '.[] | select(.amount == 0.5)'

The output should look something like this:

{
  "txid": "2254455264eb3e6e02471594f4eebe9c140de9c09074e6a52485f4de4fa0936d",
  "vout": 1,
  "address": "bcrt1qy5u5fwdccrrzs4uxvattzqdqtaa4n0n4ys5a6s",
  "label": "",
  "scriptPubKey": "0014253944b9b8c0c62857866756b101a05f7b59be75",
  "amount": 0.50000000,
  "confirmations": 6,
  "spendable": true,
  "solvable": true,
  "desc": "wpkh([3e076eb4/84h/1h/0h/0/200]03ffe1a4c98b32e1bc92a6dc274ede70db57a8636d2d88e5160c71b855c47e3852)#2jf4twkm",
  "parent_descs": [
    "wpkh(tpubD6NzVbkrYhZ4Y5fXoTkdAoaUGD9SdbXu5wz9tfVf6LSF4x7pnBww3LSQRy2RzUhGvujYj12Sw8CVo1cRYM18oLdyzazc3DqwnUSBZzj4wMB/84h/1h/0h/0/*)#w6awyanv"
  ],
  "safe": true
}

Finally, we send our test transaction. We'll send 0.47 tBTC from our test wallet so that either the UTXO of 0.5 tBTC is used or multiple 0.003 UTXOs are used. Be sure that fee_rate is set to above 30 sats/vb in order for CoinGrinder to be used. This command should output a transaction hash, like the one shown below. Your hash will be different.

bcli -rpcwallet=testwallet -named sendtoaddress address=$(bcli -rpcwallet=coinbasewallet getnewaddress) amount=0.47 fee_rate=31
7bb3bfceadce62cbe5da379975bca01e26675e576883773333a2f1baaf0e3206

To check that only the 0.5 UTXO was used in the transaction, run

bcli getrawtransaction 7bb3bfceadce62cbe5da379975bca01e26675e576883773333a2f1baaf0e3206 true

using the hash you got from the test transaction. The output should look something like this:

{
  "txid": "7bb3bfceadce62cbe5da379975bca01e26675e576883773333a2f1baaf0e3206",
  "hash": "99dfd8043440914cdcf9d39dfd43309de692a2204dd1cf8f15afae5faebdf72d",
  "version": 2,
  "size": 222,
  "vsize": 141,
  "weight": 561,
  "locktime": 117,
  "vin": [
    {
      "txid": "2254455264eb3e6e02471594f4eebe9c140de9c09074e6a52485f4de4fa0936d",
      "vout": 1,
      "scriptSig": {
        "asm": "",
        "hex": ""
      },
      "txinwitness": [
        "30440220023fae967610f77c8088b70c71907b1a3c8bb391896b32b5511a614e5210676e02203ec5a6b383b8ca3faf0bfda4cdaaae461ed8ff3c38768e414a06958c371182fd01",
        "03ffe1a4c98b32e1bc92a6dc274ede70db57a8636d2d88e5160c71b855c47e3852"
      ],
      "sequence": 4294967293
    }
  ],
  "vout": [
    {
      "value": 0.47000000,
      "n": 0,
      "scriptPubKey": {
        "asm": "0 05607b74c06528cdb9a887a9ae67ce1ef6f65f26",
        "desc": "addr(bcrt1qq4s8kaxqv55vmwdgs756ue7wrmm0vhex567xe0)#sxeyrgqv",
        "hex": "001405607b74c06528cdb9a887a9ae67ce1ef6f65f26",
        "address": "bcrt1qq4s8kaxqv55vmwdgs756ue7wrmm0vhex567xe0",
        "type": "witness_v0_keyhash"
      }
    },
    {
      "value": 0.02995629,
      "n": 1,
      "scriptPubKey": {
        "asm": "0 60db6f5dc994ac3e40b6c916c6819e2354bbe21a",
        "desc": "addr(bcrt1qvrdk7hwfjjkrus9keytvdqv7yd2thcs6aa2eew)#jr9t8fwp",
        "hex": "001460db6f5dc994ac3e40b6c916c6819e2354bbe21a",
        "address": "bcrt1qvrdk7hwfjjkrus9keytvdqv7yd2thcs6aa2eew",
        "type": "witness_v0_keyhash"
      }
    }
  ],
  "hex": "020000000001016d93a04fdef48524a5e67490c0e90d149cbeeef4941547026e3eeb64524554220100000000fdffffff02c029cd020000000016001405607b74c06528cdb9a887a9ae67ce1ef6f65f26adb52d000000000016001460db6f5dc994ac3e40b6c916c6819e2354bbe21a024730440220023fae967610f77c8088b70c71907b1a3c8bb391896b32b5511a614e5210676e02203ec5a6b383b8ca3faf0bfda4cdaaae461ed8ff3c38768e414a06958c371182fd012103ffe1a4c98b32e1bc92a6dc274ede70db57a8636d2d88e5160c71b855c47e385275000000"
}

Notice in the vin section that there is only one input used and that the txid in that section (2254455264eb3e6e02471594f4eebe9c140de9c09074e6a52485f4de4fa0936d in this specific case) is the same hash as the hash we saved from the step above. If we run the getrawtransaction command with the saved hash, we'll see that one of its outpoint values is 0.5. This means that the 0.5 UTXO was the only one selected and that the weight of the inputs was minimized for the test transaction, confirming that the CoinGrinder algorithm was used.

If we were to run these same steps but set the test transaction fee rate to 30 sats/vb or less, we'd notice that a lot more inputs are selected from the test wallet to send 0.47 tBTC. The raw transaction will be a lot larger (therefore paying a lot higher of a transaction fee) than the one created above when CoinGrinder was activated in the high fee environment (>30 sats/vb).

This test is by no means comprehensive, but it should provide a baseline for further testing on regtest with the CoinGrinder algorithm.

Once finished with CoinGrinder testing, stop the node

bcli stop

and clean up the data directories.

datadir-cleanup

migratewallet RPC is no longer experimental

⚠ This test requires that Bitcoin Core be built with bdb support. If you are building the release candidate from source, refer to your platform's build instructions for details on building with bdb. Binary releases are built with bdb and legacy wallet support by default.

First added in Bitcoin Core v24.0, the migratewallet RPC is no longer experimental in Bitcoin Core v27. This change is part of the long road to deprecating legacy wallets and BDB in Bitcoin Core. At some point, maybe in v29.0, support for Legacy Wallets and BDB may be completely removed from Bitcoin Core, and the only path forward for legacy wallet users will be to use the migratewallet RPC or to migrate their wallets using the Bitcoin Core GUI. More information about the timeline for deprecating BDB and Legacy Wallets can be found in issue #20160.

Preparing a legacy wallet

First, let's start up a node on the regtest network with the deprecatedrpc=create_bdb flag set so that Bitcoin Core doesn't complain when we try to create a legacy wallet:

{
echo "regtest=1"
echo "deprecatedrpc=create_bdb"
} > $DATA_DIR_27/bitcoin.conf

bitcoind-test -daemonwait

Now let's create the legacy wallet we will use later in this test.

bcli -named createwallet wallet_name=legacy_wallet descriptors=false
{
  "name": "legacy",
  "warnings": [
    "Wallet created successfully. The legacy wallet type is being deprecated and support for creating and opening legacy wallets will be removed in the future."
  ]
}

Let's create an alias for using the wallet with future RPC commands:

alias wallet="bcli -rpcwallet=legacy_wallet"

Let's also create a 'helper' wallet and alias that will come in handy later on:

bcli -named createwallet wallet_name=helper_wallet descriptors=false
alias helper="bcli -rpcwallet=helper_wallet"

Legacy change and receive addresses

Let's make one of each kind of receive address supported by the legacy wallet and fund each with a coinbase reward.

legacy_receive=$(wallet getnewaddress "my-P2PKH" legacy)
bcli generatetoaddress 1 "$legacy_receive"
p2sh_segwit_receive=$(wallet getnewaddress "my-P2SH(P2WPKH)" p2sh-segwit)
bcli generatetoaddress 1 "$p2sh_segwit_receive"
bech32_receive=$(wallet getnewaddress "my-P2WPKH" bech32)
bcli generatetoaddress 1 "$bech32_receive"

Coinbase rewards cannot be spent until they are 100 blocks old.

helper -generate 100

Non-HD keys

For wallets containing non-HD keys, each key will have its own combo descriptor.

To test for this behavior, we will use the helper wallet to export a one-off key for our legacy wallet to import.

non_hd_address=$(helper getnewaddress)
non_hd_key=$(helper dumpprivkey $non_hd_address)
wallet importprivkey $non_hd_key "non-hd"

Let's send our one-off address some coin:

wallet -named send outputs="{\"$non_hd_address\": 1}" fee_rate=10
{
  "txid": "{32-byte-txid}",
  "complete": true
}

Watch-only scripts

Because descriptor wallets do not support having both private keys and watch-only scripts, there may be up to two additional wallets created after migration. The one that contains all of the watchonly scripts will be named _watchonly.

☝ Legacy wallets allow for addresses with private keys and watch-only addresses to mix together because it used to not be possible to have different wallet files for different purposes. Multiwallet is relatively recent. Discussion here: https://bitcoincore.reviews/19602#l-96

To test for this behavior, we will use the helper wallet.

watch_address=$(helper getnewaddress)
wallet importaddress $watch_address "watch_address"

Solvable scripts

Solvable scripts are any scripts for which we have the public key, and which the Bitcoin Core wallet has enough information to spend. For example, a 1-of-2 multisig P2(W)SH address, where we know the redeem script and one of the private keys. Because the user can spend the corresponding P2(W)SH scripts they are placed in an additional wallet named <wallet_name>_solvables.

☝ The solvables wallet can update a PSBT with the UTXO, scripts, and pubkeys, for inputs that spend any of the scripts it contains since it backs up the redeem script.

To test for this behavior, we will again use the helper-wallet to create a multisig with some keys that do not belong to this wallet.

not_my_address=$(helper getnewaddress)
not_my_pubkey=$(helper getaddressinfo $not_my_address | jq -r ".pubkey")
my_address=$(wallet getnewaddress)
multisig_address=$(wallet addmultisigaddress 1 '''["'$my_address'", "'$not_my_pubkey'"]''' "multisig" | jq -r ".address")

Migrating a Legacy wallet

Before migrating, let's use the getwalletinfo and getaddressinfo RPC's to observe how our wallet understands our newly created addresses.

Take note of the format (bdb), descriptors (false), and balance values in the output of getwalletinfo.

wallet getwalletinfo
{
  "walletname": "legacy_wallet",
  "walletversion": 169900,
  "format": "bdb",
  "balance": 149.99997810,
  "unconfirmed_balance": 0.00000000,
  "immature_balance": 0.00000000,
  "txcount": 4,
  "keypoololdest": 1711381628,
  "keypoolsize": 999,
  "hdseedid": "68070250fe5ded18d944a9e0baeb07989755f9aa",
  "keypoolsize_hd_internal": 1000,
  "paytxfee": 0.00000000,
  "private_keys_enabled": true,
  "avoid_reuse": false,
  "scanning": false,
  "descriptors": false,
  "external_signer": false,
  "blank": false,
  "birthtime": 1,
  "lastprocessedblock": {
    "hash": "63321203c9276d117a6b8bbc6da1170270a9e5543d320e60698c377f7a143488",
    "height": 103
  }
}

☝ Run the following commands one at a time and inspect the output, giving extra attention to the values of ismine, solvable, iswatchonly, and labels.

wallet getaddressinfo $legacy_receive
wallet getaddressinfo $p2sh_segwit_receive
wallet getaddressinfo $bech32_receive
wallet getaddressinfo $non_hd_address
wallet getaddressinfo $watch_address
wallet getaddressinfo $multisig_address

Now, let's run the migration (it may take a few moments):

bcli migratewallet legacy_wallet

If you followed all the steps, at the end of the migration you should see something like:

{
  "wallet_name": "legacy_wallet",
  "watchonly_name": "legacy_wallet_watchonly",
  "solvables_name": "legacy__wallet_solvables",
  "backup_path": "/tmp/27-rc-test/regtest/wallets/legacy_wallet/legacy_wallet-1710977436.legacy.bak"
}

Now let's restart our node.

bcli stop
bitcoind-test -daemonwait

And load our wallet:

bcli loadwallet legacy_wallet

And verify that our legacy wallets have been migrated:

bcli listwallets
[
  "legacy_wallet_watchonly",
  "legacy_wallet_solvables",
  "legacy_wallet"
]

Let's run getwalletinfo and compare it's output to what we got before the migration:

wallet getwalletinfo
{
  "walletname": "legacy_wallet",
  "walletversion": 169900,
  "format": "sqlite",
  "balance": 149.99997810,
  "unconfirmed_balance": 0.00000000,
  "immature_balance": 0.00000000,
  "txcount": 4,
  "keypoolsize": 4000,
  "keypoolsize_hd_internal": 4000,
  "paytxfee": 0.00000000,
  "private_keys_enabled": true,
  "avoid_reuse": false,
  "scanning": false,
  "descriptors": true,
  "external_signer": false,
  "blank": false,
  "birthtime": 0,
  "lastprocessedblock": {
    "hash": "63321203c9276d117a6b8bbc6da1170270a9e5543d320e60698c377f7a143488",
    "height": 103
  }
}

The 'format' value should now be 'sqlite', and 'descriptors' should be true.

Let's now see what getaddressinfo reports for each of our receive addresses:

wallet getaddressinfo $legacy_receive
wallet getaddressinfo $p2sh_segwit_receive
wallet getaddressinfo $bech32_receive
wallet getaddressinfo $non_hd_address
wallet getaddressinfo $watch_address
wallet getaddressinfo $multisig_address

Here's an example with the expected differences between wallet getaddressinfo $legacy_receive before and after migration highlighted :

 {                                                                                                                                                                                                            
   "address": "mhU5hTx7kEMN6msDvBJw4bk5fDkgEtMF4M",                                                                                                                                                           
   "scriptPubKey": "76a91415652f1654156e309d987ab3c4f7c6fc5040943488ac",                                                                                                                                      
   "ismine": true,                                                                                                                                                                                            
   "solvable": true,                                                                                                                                                                                          
   "desc": "pkh([8a98783b/0h/0h/0h]02b8f17f42dad2151b7304df808699813579c08dd6deda647a4153bb3af5ddb6b6)#ahcte5wq",                                                                                             
+  "parent_desc": "combo(tpubD6NzVbkrYhZ4WZoqNF6UHicVrFYT4Zb5ApP7oGCrA9utxDi1gxmhJncPCC7KFBUyZHA6LvSQSfAYsVCD2qXrr4TggNcgHnooTXSoq8gqCjL/0h/0h/*h)#lgzh23ul",                                                 
   "iswatchonly": false,                                                                                                                                                                                      
   "isscript": false,                                                                                                                                                                                         
   "iswitness": false,                                                                                                                                                                                        
   "pubkey": "02b8f17f42dad2151b7304df808699813579c08dd6deda647a4153bb3af5ddb6b6",                                                                                                                            
   "iscompressed": true,                                                                                                                                                                                      
   "ischange": false,
-  "timestamp": 1711381628,                                                                                                                                                                                                                                                                                                                                                                            
+  "timestamp": 0,
-  "hdkeypath": "m/0'/0'/0'",                                                                                                                                                                                             
+  "hdkeypath": "m/0h/0h/0h",
-  "hdseedid": "68070250fe5ded18d944a9e0baeb07989755f9aa"                                                                                                                                                                                 
+  "hdseedid": "0000000000000000000000000000000000000000",                                                                                                                                                    
   "hdmasterfingerprint": "8a98783b",                                                                                                                                                                         
   "labels": [                                                                                                                                                                                                
     "my-P2PKH"                                                                                                                                                                                               
   ]                                                                                                                                                                                                          
 } 

Now let's check the status of our migrated wallet's descriptors:

wallet listdescriptors

{
  "wallet_name": "legacy_wallet",
  "descriptors": [
    {
      "desc": "combo([92c1c7b0][...])#2k5hveg2",
      "timestamp": 1,
      "active": false
    },
    {
      "desc": "combo([aaf95597][...])#rku6wtl2",
      "timestamp": 1711381627,
      "active": false
    },
    {
      "desc": "combo(tpub[...]/0h/0h/*h)#lgzh23ul",
      "timestamp": 0,
      "active": false,
      // ...
    },
    {
      "desc": "combo(tpub[...]/0h/1h/*h)#0wgx6pfx",
      "timestamp": 0,
      "active": false,
      // ...
    },
    {
      "desc": "pkh([8a98783b/44h/1h/0h]tpub[...]/0/*)#3x0kynxu",
      "timestamp": 1711381968,
      "active": true,
      "internal": false,
      // ...
    },
    {
      "desc": "pkh([8a98783b/44h/1h/0h]tpub[...]/1/*)#qj2hexky",
      "timestamp": 1711381968,
      "active": true,
      "internal": true,
      // ...
    },
    {
      "desc": "sh(wpkh([8a98783b/49h/1h/0h]tpub[...]/0/*))#x84nrnlf",
      "timestamp": 1711381968,
      "active": true,
      "internal": false,
      // ...
    },
    {
      "desc": "sh(wpkh([8a98783b/49h/1h/0h]tpub[...]/1/*))#nxm9mv2k",
      "timestamp": 1711381968,
      "active": true,
      "internal": true,
      // ...
    },
    {
      "desc": "tr([8a98783b/86h/1h/0h]tpub[...]/0/*)#g9ul454z",
      "timestamp": 1711381968,
      "active": true,
      "internal": false,
      // ...
    },
    {
      "desc": "tr([8a98783b/86h/1h/0h]tpub[...]/1/*)#e3e7gp96",
      "timestamp": 1711381968,
      "active": true,
      "internal": true,
      // ...
    },
    {
      "desc": "wpkh([8a98783b/84h/1h/0h]tpub[...]/0/*)#vrwex429",
      "timestamp": 1711381968,
      "active": true,
      "internal": false,
      // ...
    },
    {
      "desc": "wpkh([8a98783b/84h/1h/0h]tpub[...]/1/*)#ahtcmq6a",
      "timestamp": 1711381968,
      "active": true,
      "internal": true,
      // ...
    }
  ]
}

We expect to have 12 descriptors in total here. All four address types should have one 'receive' address (internal: false), and one 'change' address (internal: true), there should be two combo descriptors, one internal (/0h/0h/1h/*h) and one external (`/0h/0h/0h/*h, for representing our legacy HD chains, and one combo descriptor to represent the HD master seed. A combo descriptor will also be used to represent each of the non-HD keys present in our legacy wallet. (Just one in our case).

If everything looks good, our migration was successful!

Once you've completed the test, don't forget to stop your node and reset your testing environment for the next test:

bcli stop
datadir-cleanup

Further testing

These steps only test a small fraction of the capabilities of the Bitcoin Core wallet, so we encourage you to use these instructions as a starting point for your own tests in use cases we haven't covered here that are interesting to you.


Kudos if you make it this far 👏🎉

Thanks for your contribution and for taking the time to make Bitcoin awesome. For feedback on this guide, please visit #29685


⚠️ **GitHub.com Fallback** ⚠️