2016-05-06 11:23:48 +02:00
#!/usr/bin/env python3
# Copyright (c) 2010 ArtForz -- public domain half-a-node
# Copyright (c) 2012 Jeff Garzik
2023-08-16 19:27:31 +02:00
# Copyright (c) 2010-2020 The Bitcoin Core developers
2016-05-06 11:23:48 +02:00
# Distributed under the MIT software license, see the accompanying
2015-04-28 18:36:15 +02:00
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
2024-01-15 20:35:29 +01:00
""" Test objects for interacting with a dashd node over the p2p protocol.
2019-01-07 10:55:35 +01:00
2024-01-15 20:35:29 +01:00
The P2PInterface objects interact with the dashd nodes under test using the
node ' s p2p interface. They can be used to send messages to the node, and
callbacks can be registered that execute when messages are received from the
node . Messages are sent to / received from the node on an asyncio event loop .
State held inside the objects must be guarded by the p2p_lock to avoid data
races between the main testing thread and the event loop .
2019-01-07 10:55:35 +01:00
2017-11-30 23:58:58 +01:00
P2PConnection : A low - level connection object to a node ' s P2P interface
P2PInterface : A high - level interface object for communicating to a node over P2P
2018-02-13 10:30:41 +01:00
P2PDataStore : A p2p interface class that keeps a store of transactions and blocks
and can respond correctly to getdata and getheaders messages
2022-05-06 06:04:50 +02:00
P2PTxInvStore : A p2p interface class that inherits from P2PDataStore , and keeps
a count of how many times each txid has been announced .
2019-01-07 10:55:35 +01:00
"""
2018-06-29 18:04:25 +02:00
import asyncio
2017-05-02 19:10:23 +02:00
from collections import defaultdict
from io import BytesIO
2015-04-28 18:36:15 +02:00
import logging
2017-05-02 19:10:23 +02:00
import struct
import sys
2017-12-12 12:52:33 +01:00
import threading
2017-05-02 19:10:23 +02:00
2018-10-27 13:17:59 +02:00
from test_framework . messages import (
CBlockHeader ,
2022-03-11 20:39:12 +01:00
CompressibleBlockHeader ,
2024-09-06 21:29:33 +02:00
MAX_HEADERS_COMPRESSED_RESULT ,
2024-09-06 23:14:20 +02:00
MAX_HEADERS_UNCOMPRESSED_RESULT ,
2022-03-11 20:39:12 +01:00
NODE_HEADERS_COMPRESSED ,
2018-10-27 13:17:59 +02:00
msg_addr ,
msg_addrv2 ,
msg_block ,
msg_blocktxn ,
2021-09-19 06:31:43 +02:00
msg_cfcheckpt ,
2021-09-16 15:58:52 +02:00
msg_cfheaders ,
2021-09-16 16:01:04 +02:00
msg_cfilter ,
2018-10-27 13:17:59 +02:00
msg_clsig ,
msg_cmpctblock ,
Merge #18515: test: add BIP37 remote crash bug [CVE-2013-5700] test to p2p_filter.py
0ed2d8e07d3806d78d03a77d2153f22f9d733a07 test: add BIP37 remote crash bug [CVE-2013-5700] test to p2p_filter.py (Sebastian Falbesoner)
Pull request description:
Integrates the missing message type `filteradd` to the test framework and checks that the BIP37 implementation is not vulnerable to the "remote crash bug" [CVE-2013-5700](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2013-5700) anymore. Prior to v.0.8.4, it was possible to trigger a division-by-zero error on the following line in the function `CBloomFilter::Hash()`:
https://github.com/bitcoin/bitcoin/blob/f0d6487e290761a4fb03798240a351b5fddfdb38/src/bloom.cpp#L45
By setting a zero-length filter via `filterload`, `vData.size()` is 0, so the modulo operation above, called on any .insert() or .contains() operation then crashed the node. The test uses the approach of just sending an arbitrary `filteradd` message after, which calls `CBloomFilter::insert()` (and in turn `CBloomFilter::Hash()`) on the node. The vulnerability was fixed by commit https://github.com/bitcoin/bitcoin/commit/37c6389c5a0ca63ae3573440ecdfe95d28ad8f07 (an intentional covert fix, [according to gmaxwell](https://github.com/bitcoin/bitcoin/issues/18483#issuecomment-608224095)), which introduced flags `isEmpty`/`isFull` that wouldn't call the `Hash()` member function if `isFull` is true (set to true by default constructor).
To validate that the test fails if the implementation is vulnerable, one can simply set the flags to false in the member function `UpdateEmptyFull()` (that is called after a filter received via `filterload` is constructed), which activates the vulnerable code path calling `Hash` in any case on adding or testing for data in the filter:
```diff
diff --git a/src/bloom.cpp b/src/bloom.cpp
index bd6069b..ef294a3 100644
--- a/src/bloom.cpp
+++ b/src/bloom.cpp
@@ -199,8 +199,8 @@ void CBloomFilter::UpdateEmptyFull()
full &= vData[i] == 0xff;
empty &= vData[i] == 0;
}
- isFull = full;
- isEmpty = empty;
+ isFull = false;
+ isEmpty = false;
}
```
Resulting in:
```
$ ./p2p_filter.py
[...]
2020-04-03T14:38:59.593000Z TestFramework (INFO): Check that division-by-zero remote crash bug [CVE-2013-5700] is fixed
2020-04-03T14:38:59.695000Z TestFramework (ERROR): Assertion failed
[...]
[... some exceptions following ...]
```
ACKs for top commit:
naumenkogs:
utACK 0ed2d8e07d3806d78d03a77d2153f22f9d733a07
Tree-SHA512: 02d0253d13eab70c4bd007b0750c56a5a92d05d419d53033523eeb3ed80318bc95196ab90f7745ea3ac9ebae7caee3adbf2a055a40a4124e0915226e49018fe8
2020-04-05 15:17:50 +02:00
msg_filteradd ,
2020-03-31 15:35:53 +02:00
msg_filterclear ,
2020-03-30 21:27:54 +02:00
msg_filterload ,
2018-10-27 13:17:59 +02:00
msg_getaddr ,
msg_getblocks ,
msg_getblocktxn ,
2022-05-18 19:08:44 +02:00
msg_getcfcheckpt ,
msg_getcfheaders ,
msg_getcfilters ,
2018-10-27 13:17:59 +02:00
msg_getdata ,
msg_getheaders ,
2022-03-11 20:39:12 +01:00
msg_getheaders2 ,
2018-10-27 13:17:59 +02:00
msg_getmnlistd ,
msg_headers ,
2022-03-11 20:39:12 +01:00
msg_headers2 ,
2018-10-27 13:17:59 +02:00
msg_inv ,
2021-10-05 19:42:34 +02:00
msg_isdlock ,
2018-10-27 13:17:59 +02:00
msg_mempool ,
msg_mnlistdiff ,
2020-03-30 21:27:54 +02:00
msg_merkleblock ,
2018-10-27 13:17:59 +02:00
msg_notfound ,
msg_ping ,
msg_pong ,
msg_qdata ,
msg_qgetdata ,
msg_sendaddrv2 ,
msg_sendcmpct ,
msg_sendheaders ,
2022-03-11 20:39:12 +01:00
msg_sendheaders2 ,
2018-10-27 13:17:59 +02:00
msg_tx ,
msg_verack ,
msg_version ,
2022-09-19 21:22:09 +02:00
MSG_BLOCK ,
MSG_TX ,
MSG_TYPE_MASK ,
2018-10-27 13:17:59 +02:00
NODE_NETWORK ,
2024-10-24 16:23:01 +02:00
MAGIC_BYTES ,
2018-10-27 13:17:59 +02:00
sha256 ,
)
2024-03-28 04:21:36 +01:00
from test_framework . util import (
MAX_NODES ,
p2p_port ,
wait_until_helper ,
)
2024-10-24 16:23:01 +02:00
from test_framework . v2_p2p import (
EncryptedP2PState ,
MSGTYPE_TO_SHORTID ,
SHORTID ,
)
2017-12-29 14:23:27 +01:00
2024-01-15 20:35:29 +01:00
logger = logging . getLogger ( " TestFramework.p2p " )
2017-03-09 21:16:20 +01:00
2024-08-27 20:53:33 +02:00
# The minimum P2P version that this test framework supports
MIN_P2P_VERSION_SUPPORTED = 60001
# The P2P version that this test framework implements and sends in its `version` message
2024-09-10 19:56:59 +02:00
# Version 70235 increased max header count for HEADERS2 message from 2000 to 8000
P2P_VERSION = 70235
2024-08-27 20:53:33 +02:00
# The services that this test framework offers in its `version` message
P2P_SERVICES = NODE_NETWORK | NODE_HEADERS_COMPRESSED
# The P2P user agent string that this test framework sends in its `version` message
P2P_SUBVERSION = " /python-p2p-tester:0.0.3 %s / "
# Value for relay that this test framework sends in its `version` message
P2P_VERSION_RELAY = 1
2024-09-04 18:29:28 +02:00
# Delay after receiving a tx inv before requesting transactions from non-preferred peers, in seconds
NONPREF_PEER_TX_DELAY = 2
2024-08-27 20:53:33 +02:00
2020-03-30 14:21:47 +02:00
MESSAGEMAP = {
b " addr " : msg_addr ,
2021-05-29 22:24:52 +02:00
b " addrv2 " : msg_addrv2 ,
2020-03-30 14:21:47 +02:00
b " block " : msg_block ,
b " blocktxn " : msg_blocktxn ,
2021-09-19 06:31:43 +02:00
b " cfcheckpt " : msg_cfcheckpt ,
2021-09-16 15:58:52 +02:00
b " cfheaders " : msg_cfheaders ,
2021-09-16 16:01:04 +02:00
b " cfilter " : msg_cfilter ,
2020-03-30 14:21:47 +02:00
b " cmpctblock " : msg_cmpctblock ,
Merge #18515: test: add BIP37 remote crash bug [CVE-2013-5700] test to p2p_filter.py
0ed2d8e07d3806d78d03a77d2153f22f9d733a07 test: add BIP37 remote crash bug [CVE-2013-5700] test to p2p_filter.py (Sebastian Falbesoner)
Pull request description:
Integrates the missing message type `filteradd` to the test framework and checks that the BIP37 implementation is not vulnerable to the "remote crash bug" [CVE-2013-5700](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2013-5700) anymore. Prior to v.0.8.4, it was possible to trigger a division-by-zero error on the following line in the function `CBloomFilter::Hash()`:
https://github.com/bitcoin/bitcoin/blob/f0d6487e290761a4fb03798240a351b5fddfdb38/src/bloom.cpp#L45
By setting a zero-length filter via `filterload`, `vData.size()` is 0, so the modulo operation above, called on any .insert() or .contains() operation then crashed the node. The test uses the approach of just sending an arbitrary `filteradd` message after, which calls `CBloomFilter::insert()` (and in turn `CBloomFilter::Hash()`) on the node. The vulnerability was fixed by commit https://github.com/bitcoin/bitcoin/commit/37c6389c5a0ca63ae3573440ecdfe95d28ad8f07 (an intentional covert fix, [according to gmaxwell](https://github.com/bitcoin/bitcoin/issues/18483#issuecomment-608224095)), which introduced flags `isEmpty`/`isFull` that wouldn't call the `Hash()` member function if `isFull` is true (set to true by default constructor).
To validate that the test fails if the implementation is vulnerable, one can simply set the flags to false in the member function `UpdateEmptyFull()` (that is called after a filter received via `filterload` is constructed), which activates the vulnerable code path calling `Hash` in any case on adding or testing for data in the filter:
```diff
diff --git a/src/bloom.cpp b/src/bloom.cpp
index bd6069b..ef294a3 100644
--- a/src/bloom.cpp
+++ b/src/bloom.cpp
@@ -199,8 +199,8 @@ void CBloomFilter::UpdateEmptyFull()
full &= vData[i] == 0xff;
empty &= vData[i] == 0;
}
- isFull = full;
- isEmpty = empty;
+ isFull = false;
+ isEmpty = false;
}
```
Resulting in:
```
$ ./p2p_filter.py
[...]
2020-04-03T14:38:59.593000Z TestFramework (INFO): Check that division-by-zero remote crash bug [CVE-2013-5700] is fixed
2020-04-03T14:38:59.695000Z TestFramework (ERROR): Assertion failed
[...]
[... some exceptions following ...]
```
ACKs for top commit:
naumenkogs:
utACK 0ed2d8e07d3806d78d03a77d2153f22f9d733a07
Tree-SHA512: 02d0253d13eab70c4bd007b0750c56a5a92d05d419d53033523eeb3ed80318bc95196ab90f7745ea3ac9ebae7caee3adbf2a055a40a4124e0915226e49018fe8
2020-04-05 15:17:50 +02:00
b " filteradd " : msg_filteradd ,
2020-03-31 15:35:53 +02:00
b " filterclear " : msg_filterclear ,
2020-03-30 21:27:54 +02:00
b " filterload " : msg_filterload ,
2020-03-30 14:21:47 +02:00
b " getaddr " : msg_getaddr ,
b " getblocks " : msg_getblocks ,
b " getblocktxn " : msg_getblocktxn ,
2022-05-18 19:08:44 +02:00
b " getcfcheckpt " : msg_getcfcheckpt ,
b " getcfheaders " : msg_getcfheaders ,
b " getcfilters " : msg_getcfilters ,
2020-03-30 14:21:47 +02:00
b " getdata " : msg_getdata ,
b " getheaders " : msg_getheaders ,
2022-03-11 20:39:12 +01:00
b " getheaders2 " : msg_getheaders2 ,
2020-03-30 14:21:47 +02:00
b " headers " : msg_headers ,
2022-03-11 20:39:12 +01:00
b " headers2 " : msg_headers2 ,
2020-03-30 14:21:47 +02:00
b " inv " : msg_inv ,
b " mempool " : msg_mempool ,
2020-03-30 21:27:54 +02:00
b " merkleblock " : msg_merkleblock ,
2020-03-30 14:21:47 +02:00
b " ping " : msg_ping ,
b " pong " : msg_pong ,
2021-05-29 22:24:52 +02:00
b " sendaddrv2 " : msg_sendaddrv2 ,
2020-03-30 14:21:47 +02:00
b " sendcmpct " : msg_sendcmpct ,
b " sendheaders " : msg_sendheaders ,
2022-03-11 20:39:12 +01:00
b " sendheaders2 " : msg_sendheaders2 ,
2020-03-30 14:21:47 +02:00
b " tx " : msg_tx ,
b " verack " : msg_verack ,
b " version " : msg_version ,
# Dash Specific
b " clsig " : msg_clsig ,
b " getmnlistd " : msg_getmnlistd ,
b " getsporks " : None ,
b " govsync " : None ,
2021-10-05 19:42:34 +02:00
b " isdlock " : msg_isdlock ,
2020-03-30 14:21:47 +02:00
b " mnlistdiff " : msg_mnlistdiff ,
2018-10-27 13:17:59 +02:00
b " notfound " : msg_notfound ,
2020-03-30 14:21:47 +02:00
b " qfcommit " : None ,
b " qsendrecsigs " : None ,
2021-01-28 23:33:18 +01:00
b " qgetdata " : msg_qgetdata ,
b " qdata " : msg_qdata ,
b " qwatch " : None ,
2020-03-30 14:21:47 +02:00
b " senddsq " : None ,
b " spork " : None ,
}
2018-06-29 18:04:25 +02:00
class P2PConnection ( asyncio . Protocol ) :
2020-04-05 13:12:45 +02:00
""" A low-level connection object to a node ' s P2P interface.
2017-05-02 19:10:23 +02:00
2020-04-05 13:12:45 +02:00
This class is responsible for :
2016-03-04 21:08:10 +01:00
2020-04-05 13:12:45 +02:00
- opening and closing the TCP connection to the node
- reading bytes from and writing bytes to the socket
- deserializing and serializing the P2P message header
- logging messages as they are sent and received
Backport compact blocks functionality from bitcoin (#1966)
* Merge #8068: Compact Blocks
48efec8 Fix some minor compact block issues that came up in review (Matt Corallo)
ccd06b9 Elaborate bucket size math (Pieter Wuille)
0d4cb48 Use vTxHashes to optimize InitData significantly (Matt Corallo)
8119026 Provide a flat list of txid/terators to txn in CTxMemPool (Matt Corallo)
678ee97 Add BIP 152 to implemented BIPs list (Matt Corallo)
56ba516 Add reconstruction debug logging (Matt Corallo)
2f34a2e Get our "best three" peers to announce blocks using cmpctblocks (Matt Corallo)
927f8ee Add ability to fetch CNode by NodeId (Matt Corallo)
d25cd3e Add receiver-side protocol implementation for CMPCTBLOCK stuff (Matt Corallo)
9c837d5 Add sender-side protocol implementation for CMPCTBLOCK stuff (Matt Corallo)
00c4078 Add protocol messages for short-ids blocks (Matt Corallo)
e3b2222 Add some blockencodings tests (Matt Corallo)
f4f8f14 Add TestMemPoolEntryHelper::FromTx version for CTransaction (Matt Corallo)
85ad31e Add partial-block block encodings API (Matt Corallo)
5249dac Add COMPACTSIZE wrapper similar to VARINT for serialization (Matt Corallo)
cbda71c Move context-required checks from CheckBlockHeader to Contextual... (Matt Corallo)
7c29ec9 If AcceptBlockHeader returns true, pindex will be set. (Matt Corallo)
96806c3 Stop trimming when mapTx is empty (Pieter Wuille)
* Merge #8408: Prevent fingerprinting, disk-DoS with compact blocks
1d06e49 Ignore CMPCTBLOCK messages for pruned blocks (Suhas Daftuar)
1de2a46 Ignore GETBLOCKTXN requests for unknown blocks (Suhas Daftuar)
* Merge #8418: Add tests for compact blocks
45c7ddd Add p2p test for BIP 152 (compact blocks) (Suhas Daftuar)
9a22a6c Add support for compactblocks to mininode (Suhas Daftuar)
a8689fd Tests: refactor compact size serialization in mininode (Suhas Daftuar)
9c8593d Implement SipHash in Python (Pieter Wuille)
56c87e9 Allow changing BIP9 parameters on regtest (Suhas Daftuar)
* Merge #8505: Trivial: Fix typos in various files
1aacfc2 various typos (leijurv)
* Merge #8449: [Trivial] Do not shadow local variable, cleanup
a159f25 Remove redundand (and shadowing) declaration (Pavel Janík)
cce3024 Do not shadow local variable, cleanup (Pavel Janík)
* Merge #8739: [qa] Fix broken sendcmpct test in p2p-compactblocks.py
157254a Fix broken sendcmpct test in p2p-compactblocks.py (Suhas Daftuar)
* Merge #8854: [qa] Fix race condition in p2p-compactblocks test
b5fd666 [qa] Fix race condition in p2p-compactblocks test (Suhas Daftuar)
* Merge #8393: Support for compact blocks together with segwit
27acfc1 [qa] Update p2p-compactblocks.py for compactblocks v2 (Suhas Daftuar)
422fac6 [qa] Add support for compactblocks v2 to mininode (Suhas Daftuar)
f5b9b8f [qa] Fix bug in mininode witness deserialization (Suhas Daftuar)
6aa28ab Use cmpctblock type 2 for segwit-enabled transfer (Pieter Wuille)
be7555f Fix overly-prescriptive p2p-segwit test for new fetch logic (Matt Corallo)
06128da Make GetFetchFlags always request witness objects from witness peers (Matt Corallo)
* Merge #8882: [qa] Fix race conditions in p2p-compactblocks.py and sendheaders.py
b55d941 [qa] Fix race condition in sendheaders.py (Suhas Daftuar)
6976db2 [qa] Another attempt to fix race condition in p2p-compactblocks.py (Suhas Daftuar)
* Merge #8904: [qa] Fix compact block shortids for a test case
4cdece4 [qa] Fix compact block shortids for a test case (Dagur Valberg Johannsson)
* Merge #8637: Compact Block Tweaks (rebase of #8235)
3ac6de0 Align constant names for maximum compact block / blocktxn depth (Pieter Wuille)
b2e93a3 Add cmpctblock to debug help list (instagibbs)
fe998e9 More agressively filter compact block requests (Matt Corallo)
02a337d Dont remove a "preferred" cmpctblock peer if they provide a block (Matt Corallo)
* Merge #8975: Chainparams: Trivial: In AppInit2(), s/Params()/chainparams/
6f2f639 Chainparams: Trivial: In AppInit2(), s/Params()/chainparams/ (Jorge Timón)
* Merge #8968: Don't hold cs_main when calling ProcessNewBlock from a cmpctblock
72ca7d9 Don't hold cs_main when calling ProcessNewBlock from a cmpctblock (Matt Corallo)
* Merge #8995: Add missing cs_main lock to ::GETBLOCKTXN processing
dfe7906 Add missing cs_main lock to ::GETBLOCKTXN processing (Matt Corallo)
* Merge #8515: A few mempool removal optimizations
0334430 Add some missing includes (Pieter Wuille)
4100499 Return shared_ptr<CTransaction> from mempool removes (Pieter Wuille)
51f2783 Make removed and conflicted arguments optional to remove (Pieter Wuille)
f48211b Bypass removeRecursive in removeForReorg (Pieter Wuille)
* Merge #9026: Fix handling of invalid compact blocks
d4833ff Bump the protocol version to distinguish new banning behavior. (Suhas Daftuar)
88c3549 Fix compact block handling to not ban if block is invalid (Suhas Daftuar)
c93beac [qa] Test that invalid compactblocks don't result in ban (Suhas Daftuar)
* Merge #9039: Various serialization simplifcations and optimizations
d59a518 Use fixed preallocation instead of costly GetSerializeSize (Pieter Wuille)
25a211a Add optimized CSizeComputer serializers (Pieter Wuille)
a2929a2 Make CSerAction's ForRead() constexpr (Pieter Wuille)
a603925 Avoid -Wshadow errors (Pieter Wuille)
5284721 Get rid of nType and nVersion (Pieter Wuille)
657e05a Make GetSerializeSize a wrapper on top of CSizeComputer (Pieter Wuille)
fad9b66 Make nType and nVersion private and sometimes const (Pieter Wuille)
c2c5d42 Make streams' read and write return void (Pieter Wuille)
50e8a9c Remove unused ReadVersion and WriteVersion (Pieter Wuille)
* Merge #9058: Fixes for p2p-compactblocks.py test timeouts on travis (#8842)
dac53b5 Modify getblocktxn handler not to drop requests for old blocks (Russell Yanofsky)
55bfddc [qa] Fix stale data bug in test_compactblocks_not_at_tip (Russell Yanofsky)
47e9659 [qa] Fix bug in compactblocks v2 merge (Russell Yanofsky)
* Merge #9160: [trivial] Fix hungarian variable name
ec34648 [trivial] Fix hungarian variable name (Russell Yanofsky)
* Merge #9159: [qa] Wait for specific block announcement in p2p-compactblocks
dfa44d1 [qa] Wait for specific block announcement in p2p-compactblocks (Russell Yanofsky)
* Merge #9125: Make CBlock a vector of shared_ptr of CTransactions
b4e4ba4 Introduce convenience type CTransactionRef (Pieter Wuille)
1662b43 Make CBlock::vtx a vector of shared_ptr<CTransaction> (Pieter Wuille)
da60506 Add deserializing constructors to CTransaction and CMutableTransaction (Pieter Wuille)
0e85204 Add serialization for unique_ptr and shared_ptr (Pieter Wuille)
* Merge #8872: Remove block-request logic from INV message processing
037159c Remove block-request logic from INV message processing (Matt Corallo)
3451203 [qa] Respond to getheaders and do not assume a getdata on inv (Matt Corallo)
d768f15 [qa] Make comptool push blocks instead of relying on inv-fetch (mrbandrews)
* Merge #9199: Always drop the least preferred HB peer when adding a new one.
ca8549d Always drop the least preferred HB peer when adding a new one. (Gregory Maxwell)
* Merge #9233: Fix some typos
15fa95d Fix some typos (fsb4000)
* Merge #9260: Mrs Peacock in The Library with The Candlestick (killed main.{h,cpp})
76faa3c Rename the remaining main.{h,cpp} to validation.{h,cpp} (Matt Corallo)
e736772 Move network-msg-processing code out of main to its own file (Matt Corallo)
87c35f5 Remove orphan state wipe from UnloadBlockIndex. (Matt Corallo)
* Merge #9014: Fix block-connection performance regression
dd0df81 Document ConnectBlock connectTrace postconditions (Matt Corallo)
2d6e561 Switch pblock in ProcessNewBlock to a shared_ptr (Matt Corallo)
2736c44 Make the optional pblock in ActivateBestChain a shared_ptr (Matt Corallo)
ae4db44 Create a shared_ptr for the block we're connecting in ActivateBCS (Matt Corallo)
fd9d890 Keep blocks as shared_ptrs, instead of copying txn in ConnectTip (Matt Corallo)
6fdd43b Add struct to track block-connect-time-generated info for callbacks (Matt Corallo)
* Merge #9240: Remove txConflicted
a874ab5 remove internal tracking of mempool conflicts for reporting to wallet (Alex Morcos)
bf663f8 remove external usage of mempool conflict tracking (Alex Morcos)
* Merge #9344: Do not run functions with necessary side-effects in assert()
da9cdd2 Do not run functions with necessary side-effects in assert() (Gregory Maxwell)
* Merge #9273: Remove unused CDiskBlockPos* argument from ProcessNewBlock
a13fa4c Remove unused CDiskBlockPos* argument from ProcessNewBlock (Matt Corallo)
* Merge #9352: Attempt reconstruction from all compact block announcements
813ede9 [qa] Update compactblocks test for multi-peer reconstruction (Suhas Daftuar)
7017298 Allow compactblock reconstruction when block is in flight (Suhas Daftuar)
* Merge #9252: Release cs_main before calling ProcessNewBlock, or processing headers (cmpctblock handling)
bd02bdd Release cs_main before processing cmpctblock as header (Suhas Daftuar)
680b0c0 Release cs_main before calling ProcessNewBlock (cmpctblock handling) (Suhas Daftuar)
* Merge #9283: A few more CTransactionRef optimizations
91335ba Remove unused MakeTransactionRef overloads (Pieter Wuille)
6713f0f Make FillBlock consume txn_available to avoid shared_ptr copies (Pieter Wuille)
62607d7 Convert COrphanTx to keep a CTransactionRef (Pieter Wuille)
c44e4c4 Make AcceptToMemoryPool take CTransactionRef (Pieter Wuille)
* Merge #9375: Relay compact block messages prior to full block connection
02ee4eb Make most_recent_compact_block a pointer to a const (Matt Corallo)
73666ad Add comment to describe callers to ActivateBestChain (Matt Corallo)
962f7f0 Call ActivateBestChain without cs_main/with most_recent_block (Matt Corallo)
0df777d Use a temp pindex to avoid a const_cast in ProcessNewBlockHeaders (Matt Corallo)
c1ae4fc Avoid holding cs_most_recent_block while calling ReadBlockFromDisk (Matt Corallo)
9eb67f5 Ensure we meet the BIP 152 old-relay-types response requirements (Matt Corallo)
5749a85 Cache most-recently-connected compact block (Matt Corallo)
9eaec08 Cache most-recently-announced block's shared_ptr (Matt Corallo)
c802092 Relay compact block messages prior to full block connection (Matt Corallo)
6987219 Add a CValidationInterface::NewPoWValidBlock callback (Matt Corallo)
180586f Call AcceptBlock with the block's shared_ptr instead of CBlock& (Matt Corallo)
8baaba6 [qa] Avoid race in preciousblock test. (Matt Corallo)
9a0b2f4 [qa] Make compact blocks test construction using fetch methods (Matt Corallo)
8017547 Make CBlockIndex*es in net_processing const (Matt Corallo)
* Merge #9486: Make peer=%d log prints consistent
e6111b2 Make peer id logging consistent ("peer=%d" instead of "peer %d") (Matt Corallo)
* Merge #9400: Set peers as HB peers upon full block validation
d4781ac Set peers as HB peers upon full block validation (Gregory Sanders)
* Merge #9499: Use recent-rejects, orphans, and recently-replaced txn for compact-block-reconstruction
c594580 Add braces around AddToCompactExtraTransactions (Matt Corallo)
1ccfe9b Clarify comment about mempool/extra conflicts (Matt Corallo)
fac4c78 Make PartiallyDownloadedBlock::InitData's second param const (Matt Corallo)
b55b416 Add extra_count lower bound to compact reconstruction debug print (Matt Corallo)
863edb4 Consider all (<100k memusage) txn for compact-block-extra-txn cache (Matt Corallo)
7f8c8ca Consider all orphan txn for compact-block-extra-txn cache (Matt Corallo)
93380c5 Use replaced transactions in compact block reconstruction (Matt Corallo)
1531652 Keep shared_ptrs to recently-replaced txn for compact blocks (Matt Corallo)
edded80 Make ATMP optionally return the CTransactionRefs it replaced (Matt Corallo)
c735540 Move ORPHAN constants from validation.h to net_processing.h (Matt Corallo)
* Merge #9587: Do not shadow local variable named `tx`.
44f2baa Do not shadow local variable named `tx`. (Pavel Janík)
* Merge #9510: [trivial] Fix typos in comments
cc16d99 [trivial] Fix typos in comments (practicalswift)
* Merge #9604: [Trivial] add comment about setting peer as HB peer.
dd5b011 [Trivial] add comment about setting peer as HB peer. (John Newbery)
* Fix using of AcceptToMemoryPool in PrivateSend code
* add `override`
* fSupportsDesiredCmpctVersion
* bring back tx ressurection in DisconnectTip
* Fix delayed headers
* Remove unused CConnman::FindNode overload
* Fix typos and comments
* Fix minor code differences
* Don't use rejection cache for corrupted transactions
Partly based on https://github.com/bitcoin/bitcoin/pull/8525
* Backport missed cs_main locking changes
Missed from https://github.com/bitcoin/bitcoin/commit/58a215ce8c13b900cf982c39f8ee4879290d1a95
* Backport missed comments and mapBlockSource.emplace call
Missed from two commits:
https://github.com/bitcoin/bitcoin/commit/88c35491ab19f9afdf9b3fa9356a072f70ef2f55
https://github.com/bitcoin/bitcoin/commit/7c98ce584ec23bcddcba8cdb33efa6547212f6ef
* Add CheckPeerHeaders() helper and check in (nCount == 0) too
2018-04-11 13:06:01 +02:00
2020-04-05 13:12:45 +02:00
This class contains no logic for handing the P2P message payloads . It must be
2017-11-30 23:58:58 +01:00
sub - classed and the on_message ( ) callback overridden . """
2017-11-09 19:48:07 +01:00
2020-04-05 13:12:45 +02:00
def __init__ ( self ) :
2018-06-29 18:04:25 +02:00
# The underlying transport of the connection.
# Should only call methods on this from the NetworkThread, c.f. call_soon_threadsafe
self . _transport = None
2024-10-24 16:23:01 +02:00
# This lock is acquired before sending messages over the socket. There's an implied lock order and
# p2p_lock must not be acquired after _send_lock as it could result in deadlocks.
self . _send_lock = threading . Lock ( )
self . v2_state = None # EncryptedP2PState object needed for v2 p2p connections
self . reconnect = False # set if reconnection needs to happen
2018-06-24 01:28:13 +02:00
@property
def is_connected ( self ) :
2018-06-29 18:04:25 +02:00
return self . _transport is not None
2018-06-24 01:28:13 +02:00
2024-10-24 16:23:01 +02:00
@property
def supports_v2_p2p ( self ) :
return self . v2_state is not None
2024-03-28 04:21:36 +01:00
def peer_connect_helper ( self , dstaddr , dstport , net , timeout_factor , uacomment ) :
2018-06-29 18:04:25 +02:00
assert not self . is_connected
2020-05-19 02:00:24 +02:00
self . timeout_factor = timeout_factor
2015-04-28 18:36:15 +02:00
self . dstaddr = dstaddr
self . dstport = dstport
2018-06-29 18:04:25 +02:00
# The initial message to send after the connection was made:
self . on_connection_send_msg = None
2016-04-10 16:54:28 +02:00
self . recvbuf = b " "
2021-11-03 06:14:59 +01:00
self . magic_bytes = MAGIC_BYTES [ net ]
2021-01-25 19:35:17 +01:00
self . uacomment = uacomment
2017-02-14 14:34:20 +01:00
2021-11-03 06:14:59 +01:00
if net == " devnet " :
2021-07-22 10:24:46 +02:00
devnet_name = " devnet1 " # see initialize_datadir()
2021-01-25 19:35:17 +01:00
if self . uacomment is None :
2024-08-27 20:53:33 +02:00
self . strSubVer = P2P_SUBVERSION % ( " (devnet.devnet- %s ) " % devnet_name )
2021-01-25 19:35:17 +01:00
else :
2024-08-27 20:53:33 +02:00
self . strSubVer = P2P_SUBVERSION % ( " (devnet.devnet- %s , %s ) " % ( devnet_name , self . uacomment ) )
2021-01-25 19:35:17 +01:00
elif self . uacomment is not None :
2024-08-27 20:53:33 +02:00
self . strSubVer = P2P_SUBVERSION % ( " ( %s ) " % self . uacomment )
2021-01-25 19:35:17 +01:00
else :
2024-08-27 20:53:33 +02:00
self . strSubVer = P2P_SUBVERSION % " "
2021-01-25 19:35:17 +01:00
2024-10-24 16:23:01 +02:00
def peer_connect ( self , dstaddr , dstport , * , net , timeout_factor , supports_v2_p2p , uacomment = None ) :
2024-03-28 04:21:36 +01:00
self . peer_connect_helper ( dstaddr , dstport , net , timeout_factor , uacomment )
2024-10-24 16:23:01 +02:00
if supports_v2_p2p :
self . v2_state = EncryptedP2PState ( initiating = True , net = net )
2015-04-28 18:36:15 +02:00
2018-06-29 18:04:25 +02:00
loop = NetworkThread . network_event_loop
2024-03-28 04:21:36 +01:00
logger . debug ( ' Connecting to Dash Node: %s : %d ' % ( self . dstaddr , self . dstport ) )
coroutine = loop . create_connection ( lambda : self , host = self . dstaddr , port = self . dstport )
return lambda : loop . call_soon_threadsafe ( loop . create_task , coroutine )
2024-10-24 16:23:01 +02:00
def peer_accept_connection ( self , connect_id , connect_cb = lambda : None , * , net , timeout_factor , supports_v2_p2p , reconnect , uacomment = None ) :
2024-03-28 04:21:36 +01:00
self . peer_connect_helper ( ' 0 ' , 0 , net , timeout_factor , uacomment )
2024-10-24 16:23:01 +02:00
self . reconnect = reconnect
if supports_v2_p2p :
self . v2_state = EncryptedP2PState ( initiating = False , net = net )
2024-03-28 04:21:36 +01:00
logger . debug ( ' Listening for Dash Node with id: {} ' . format ( connect_id ) )
return lambda : NetworkThread . listen ( self , connect_cb , idx = connect_id )
2015-04-28 18:36:15 +02:00
2020-04-05 13:12:45 +02:00
def peer_disconnect ( self ) :
# Connection could have already been closed by other end.
2018-06-29 18:04:25 +02:00
NetworkThread . network_event_loop . call_soon_threadsafe ( lambda : self . _transport and self . _transport . abort ( ) )
2020-04-05 13:12:45 +02:00
2020-03-30 14:21:47 +02:00
# Connection and disconnection methods
2018-06-29 18:04:25 +02:00
def connection_made ( self , transport ) :
""" asyncio callback when a connection is opened. """
assert not self . _transport
logger . debug ( " Connected & Listening: %s : %d " % ( self . dstaddr , self . dstport ) )
self . _transport = transport
2024-10-24 16:23:01 +02:00
# in an inbound connection to the TestNode with P2PConnection as the initiator, [TestNode <---- P2PConnection]
# send the initial handshake immediately
if self . supports_v2_p2p and self . v2_state . initiating and not self . v2_state . tried_v2_handshake :
send_handshake_bytes = self . v2_state . initiate_v2_handshake ( )
2024-01-30 14:57:21 +01:00
logger . debug ( f " sending { len ( self . v2_state . sent_garbage ) } bytes of garbage data " )
2024-10-24 16:23:01 +02:00
self . send_raw_message ( send_handshake_bytes )
# if v2 connection, send `on_connection_send_msg` after initial v2 handshake.
# if reconnection situation, send `on_connection_send_msg` after version message is received in `on_version()`.
if self . on_connection_send_msg and not self . supports_v2_p2p and not self . reconnect :
2018-06-29 18:04:25 +02:00
self . send_message ( self . on_connection_send_msg )
self . on_connection_send_msg = None # Never used again
self . on_open ( )
def connection_lost ( self , exc ) :
""" asyncio callback when a connection is closed. """
2024-10-24 16:23:01 +02:00
# don't display warning if reconnection needs to be attempted using v1 P2P
if exc and not self . reconnect :
2018-06-29 18:04:25 +02:00
logger . warning ( " Connection lost to {} : {} due to {} " . format ( self . dstaddr , self . dstport , exc ) )
else :
logger . debug ( " Closed connection to: %s : %d " % ( self . dstaddr , self . dstport ) )
self . _transport = None
2016-04-10 16:54:28 +02:00
self . recvbuf = b " "
2020-04-05 13:12:45 +02:00
self . on_close ( )
2015-04-28 18:36:15 +02:00
2024-10-24 16:23:01 +02:00
# v2 handshake method
def v2_handshake ( self ) :
""" v2 handshake performed before P2P messages are exchanged (see BIP324). P2PConnection is the initiator
( in inbound connections to TestNode ) and the responder ( in outbound connections from TestNode ) .
Performed by :
* initiator using ` initiate_v2_handshake ( ) ` , ` complete_handshake ( ) ` and ` authenticate_handshake ( ) `
* responder using ` respond_v2_handshake ( ) ` , ` complete_handshake ( ) ` and ` authenticate_handshake ( ) `
` initiate_v2_handshake ( ) ` is immediately done by the initiator when the connection is established in
` connection_made ( ) ` . The rest of the initial v2 handshake functions are handled here .
"""
if not self . v2_state . peer :
if not self . v2_state . initiating and not self . v2_state . sent_garbage :
# if the responder hasn't sent garbage yet, the responder is still reading ellswift bytes
# reads ellswift bytes till the first mismatch from 12 bytes V1_PREFIX
length , send_handshake_bytes = self . v2_state . respond_v2_handshake ( BytesIO ( self . recvbuf ) )
self . recvbuf = self . recvbuf [ length : ]
if send_handshake_bytes == - 1 :
self . v2_state = None
return
elif send_handshake_bytes :
2024-01-30 14:57:21 +01:00
logger . debug ( f " sending { len ( self . v2_state . sent_garbage ) } bytes of garbage data " )
2024-10-24 16:23:01 +02:00
self . send_raw_message ( send_handshake_bytes )
elif send_handshake_bytes == b " " :
return # only after send_handshake_bytes are sent can `complete_handshake()` be done
# `complete_handshake()` reads the remaining ellswift bytes from recvbuf
# and sends response after deriving shared ECDH secret using received ellswift bytes
length , response = self . v2_state . complete_handshake ( BytesIO ( self . recvbuf ) )
self . recvbuf = self . recvbuf [ length : ]
if response :
self . send_raw_message ( response )
else :
return # only after response is sent can `authenticate_handshake()` be done
# `self.v2_state.peer` is instantiated only after shared ECDH secret/BIP324 derived keys and ciphers
# is derived in `complete_handshake()`.
# so `authenticate_handshake()` which uses the BIP324 derived ciphers gets called after `complete_handshake()`.
assert self . v2_state . peer
length , is_mac_auth = self . v2_state . authenticate_handshake ( self . recvbuf )
if not is_mac_auth :
raise ValueError ( " invalid v2 mac tag in handshake authentication " )
self . recvbuf = self . recvbuf [ length : ]
if self . v2_state . tried_v2_handshake and self . on_connection_send_msg :
self . send_message ( self . on_connection_send_msg )
self . on_connection_send_msg = None
2020-03-30 14:21:47 +02:00
# Socket read methods
2018-06-29 18:04:25 +02:00
def data_received ( self , t ) :
""" asyncio callback when data is read from the socket. """
2017-09-13 15:17:15 +02:00
if len ( t ) > 0 :
self . recvbuf + = t
2024-10-24 16:23:01 +02:00
if self . supports_v2_p2p and not self . v2_state . tried_v2_handshake :
self . v2_handshake ( )
else :
self . _on_data ( )
2015-04-28 18:36:15 +02:00
2020-04-05 13:12:45 +02:00
def _on_data ( self ) :
""" Try to read P2P messages from the recv buffer.
This method reads data from the buffer in a loop . It deserializes ,
parses and verifies the P2P header , then passes the P2P payload to
the on_message callback for processing . """
2016-03-19 21:36:32 +01:00
try :
while True :
2024-10-24 16:23:01 +02:00
if self . supports_v2_p2p :
# v2 P2P messages are read
msglen , msg = self . v2_state . v2_receive_packet ( self . recvbuf )
if msglen == - 1 :
raise ValueError ( " invalid v2 mac tag " + repr ( self . recvbuf ) )
elif msglen == 0 : # need to receive more bytes in recvbuf
return
self . recvbuf = self . recvbuf [ msglen : ]
if msg is None : # ignore decoy messages
return
assert msg # application layer messages (which aren't decoy messages) are non-empty
shortid = msg [ 0 ] # 1-byte short message type ID
if shortid == 0 :
# next 12 bytes are interpreted as ASCII message type if shortid is b'\x00'
if len ( msg ) < 13 :
raise IndexError ( " msg needs minimum required length of 13 bytes " )
msgtype = msg [ 1 : 13 ] . rstrip ( b ' \x00 ' )
msg = msg [ 13 : ] # msg is set to be payload
else :
# a 1-byte short message type ID
msgtype = SHORTID . get ( shortid , f " unknown- { shortid } " )
msg = msg [ 1 : ]
else :
# v1 P2P messages are read
if len ( self . recvbuf ) < 4 :
return
if self . recvbuf [ : 4 ] != self . magic_bytes :
raise ValueError ( " magic bytes mismatch: {} != {} " . format ( repr ( self . magic_bytes ) , repr ( self . recvbuf ) ) )
if len ( self . recvbuf ) < 4 + 12 + 4 + 4 :
return
msgtype = self . recvbuf [ 4 : 4 + 12 ] . split ( b " \x00 " , 1 ) [ 0 ]
msglen = struct . unpack ( " <i " , self . recvbuf [ 4 + 12 : 4 + 12 + 4 ] ) [ 0 ]
checksum = self . recvbuf [ 4 + 12 + 4 : 4 + 12 + 4 + 4 ]
if len ( self . recvbuf ) < 4 + 12 + 4 + 4 + msglen :
return
msg = self . recvbuf [ 4 + 12 + 4 + 4 : 4 + 12 + 4 + 4 + msglen ]
th = sha256 ( msg )
h = sha256 ( th )
if checksum != h [ : 4 ] :
raise ValueError ( " got bad checksum " + repr ( self . recvbuf ) )
self . recvbuf = self . recvbuf [ 4 + 12 + 4 + 4 + msglen : ]
2023-08-17 19:32:05 +02:00
if msgtype not in MESSAGEMAP :
raise ValueError ( " Received unknown msgtype from %s : %d : ' %s ' %s " % ( self . dstaddr , self . dstport , msgtype , repr ( msg ) ) )
if MESSAGEMAP [ msgtype ] is None :
2017-11-09 19:48:07 +01:00
# Command is known but we don't want/need to handle it
continue
f = BytesIO ( msg )
2023-08-17 19:32:05 +02:00
t = MESSAGEMAP [ msgtype ] ( )
2017-11-09 19:48:07 +01:00
t . deserialize ( f )
2020-04-05 13:12:45 +02:00
self . _log_message ( " receive " , t )
self . on_message ( t )
2016-03-19 21:36:32 +01:00
except Exception as e :
2024-10-24 16:23:01 +02:00
if not self . reconnect :
logger . exception ( ' Error reading message: ' , repr ( e ) )
2017-09-13 15:17:15 +02:00
raise
2015-04-28 18:36:15 +02:00
2020-04-05 13:12:45 +02:00
def on_message ( self , message ) :
""" Callback for processing a P2P payload. Must be overridden by derived class. """
raise NotImplementedError
2020-03-30 14:21:47 +02:00
# Socket write methods
2024-10-24 16:23:01 +02:00
def send_message ( self , message , is_decoy = False ) :
2020-04-05 13:12:45 +02:00
""" Send a P2P message over the socket.
This method takes a P2P payload , builds the P2P header and adds
the message to the send buffer to be sent over the socket . """
2024-10-24 16:23:01 +02:00
with self . _send_lock :
tmsg = self . build_message ( message , is_decoy )
self . _log_message ( " send " , message )
return self . send_raw_message ( tmsg )
2018-11-06 11:08:40 +01:00
def send_raw_message ( self , raw_message_bytes ) :
2018-06-24 01:28:13 +02:00
if not self . is_connected :
raise IOError ( ' Not connected ' )
2021-06-29 11:58:08 +02:00
def maybe_write ( ) :
if not self . _transport :
return
2021-08-27 21:03:02 +02:00
if self . _transport . is_closing ( ) :
2021-06-29 11:58:08 +02:00
return
2018-11-06 11:08:40 +01:00
self . _transport . write ( raw_message_bytes )
2021-06-29 11:58:08 +02:00
NetworkThread . network_event_loop . call_soon_threadsafe ( maybe_write )
2015-04-28 18:36:15 +02:00
2020-03-30 14:21:47 +02:00
# Class utility methods
2015-04-28 18:36:15 +02:00
2024-10-24 16:23:01 +02:00
def build_message ( self , message , is_decoy = False ) :
2018-06-24 01:28:13 +02:00
""" Build a serialized P2P message """
2023-08-17 19:32:05 +02:00
msgtype = message . msgtype
2018-06-24 01:28:13 +02:00
data = message . serialize ( )
2024-10-24 16:23:01 +02:00
if self . supports_v2_p2p :
if msgtype in SHORTID . values ( ) :
tmsg = MSGTYPE_TO_SHORTID . get ( msgtype ) . to_bytes ( 1 , ' big ' )
else :
tmsg = b " \x00 "
tmsg + = msgtype
tmsg + = b " \x00 " * ( 12 - len ( msgtype ) )
tmsg + = data
return self . v2_state . v2_enc_packet ( tmsg , ignore = is_decoy )
else :
tmsg = self . magic_bytes
tmsg + = msgtype
tmsg + = b " \x00 " * ( 12 - len ( msgtype ) )
tmsg + = struct . pack ( " <I " , len ( data ) )
th = sha256 ( data )
h = sha256 ( th )
tmsg + = h [ : 4 ]
tmsg + = data
return tmsg
2018-06-24 01:28:13 +02:00
2017-04-08 12:33:14 +02:00
def _log_message ( self , direction , msg ) :
2020-04-05 13:12:45 +02:00
""" Logs a message being sent or received over the connection. """
2017-04-08 12:33:14 +02:00
if direction == " send " :
log_message = " Send message to "
elif direction == " receive " :
log_message = " Received message from "
log_message + = " %s : %d : %s " % ( self . dstaddr , self . dstport , repr ( msg ) [ : 500 ] )
if len ( log_message ) > 500 :
log_message + = " ... (msg truncated) "
logger . debug ( log_message )
2015-04-28 18:36:15 +02:00
2017-11-30 23:58:58 +01:00
class P2PInterface ( P2PConnection ) :
2024-01-15 20:35:29 +01:00
""" A high-level P2P interface class for communicating with a Dash node.
2020-04-05 13:12:45 +02:00
This class provides high - level callbacks for processing P2P message
payloads , as well as convenience methods for interacting with the
node over P2P .
Individual testcases should subclass this and override the on_ * methods
2017-11-30 23:58:58 +01:00
if they want to alter message handling behaviour . """
2021-05-29 22:24:52 +02:00
def __init__ ( self , support_addrv2 = False ) :
2020-04-05 13:12:45 +02:00
super ( ) . __init__ ( )
2020-07-30 09:15:46 +02:00
# Track number of messages of each type received.
# Should be read-only in a test.
2020-04-05 13:12:45 +02:00
self . message_count = defaultdict ( int )
2020-07-30 09:15:46 +02:00
# Track the most recent message of each type.
# To wait for a message to be received, pop that message from
2020-09-03 05:47:08 +02:00
# this and use self.wait_until.
2020-04-05 13:12:45 +02:00
self . last_message = { }
# A count of the number of ping messages we've sent to the node
self . ping_counter = 1
# The network services received from the peer
self . nServices = 0
2021-05-29 22:24:52 +02:00
self . support_addrv2 = support_addrv2
2024-03-28 04:21:36 +01:00
def peer_connect_send_version ( self , services ) :
# Send a version msg
vt = msg_version ( )
2024-08-27 20:53:33 +02:00
vt . nVersion = P2P_VERSION
vt . strSubVer = P2P_SUBVERSION
vt . relay = P2P_VERSION_RELAY
2024-03-28 04:21:36 +01:00
vt . nServices = services
vt . addrTo . ip = self . dstaddr
vt . addrTo . port = self . dstport
vt . addrFrom . ip = " 0.0.0.0 "
vt . addrFrom . port = 0
vt . strSubVer = self . strSubVer
self . on_connection_send_msg = vt # Will be sent soon after connection_made
2024-10-14 22:53:38 +02:00
def peer_connect ( self , * , services = P2P_SERVICES , send_version , * * kwargs ) :
create_conn = super ( ) . peer_connect ( * * kwargs )
2020-04-05 13:12:45 +02:00
if send_version :
2024-03-28 04:21:36 +01:00
self . peer_connect_send_version ( services )
return create_conn
def peer_accept_connection ( self , * args , services = NODE_NETWORK | NODE_HEADERS_COMPRESSED , * * kwargs ) :
create_conn = super ( ) . peer_accept_connection ( * args , * * kwargs )
self . peer_connect_send_version ( services )
2018-06-29 18:04:25 +02:00
return create_conn
2020-04-05 13:12:45 +02:00
# Message receiving methods
def on_message ( self , message ) :
""" Receive message and dispatch message to appropriate callback.
We keep a count of how many of each message type has been received
and the most recent message of each type . """
2024-01-15 20:35:29 +01:00
with p2p_lock :
2020-04-05 13:12:45 +02:00
try :
2023-08-17 19:32:05 +02:00
msgtype = message . msgtype . decode ( ' ascii ' )
self . message_count [ msgtype ] + = 1
self . last_message [ msgtype ] = message
getattr ( self , ' on_ ' + msgtype ) ( message )
2020-04-05 13:12:45 +02:00
except :
print ( " ERROR delivering %s ( %s ) " % ( repr ( message ) , sys . exc_info ( ) [ 0 ] ) )
raise
# Callback methods. Can be overridden by subclasses in individual test
# cases to provide custom message handling behaviour.
def on_open ( self ) :
pass
def on_close ( self ) :
pass
def on_addr ( self , message ) : pass
2021-05-29 22:24:52 +02:00
def on_addrv2 ( self , message ) : pass
2020-04-05 13:12:45 +02:00
def on_block ( self , message ) : pass
def on_blocktxn ( self , message ) : pass
2021-09-19 06:31:43 +02:00
def on_cfcheckpt ( self , message ) : pass
2021-09-16 15:58:52 +02:00
def on_cfheaders ( self , message ) : pass
2021-09-16 16:01:04 +02:00
def on_cfilter ( self , message ) : pass
2020-04-05 13:12:45 +02:00
def on_cmpctblock ( self , message ) : pass
def on_feefilter ( self , message ) : pass
Merge #18515: test: add BIP37 remote crash bug [CVE-2013-5700] test to p2p_filter.py
0ed2d8e07d3806d78d03a77d2153f22f9d733a07 test: add BIP37 remote crash bug [CVE-2013-5700] test to p2p_filter.py (Sebastian Falbesoner)
Pull request description:
Integrates the missing message type `filteradd` to the test framework and checks that the BIP37 implementation is not vulnerable to the "remote crash bug" [CVE-2013-5700](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2013-5700) anymore. Prior to v.0.8.4, it was possible to trigger a division-by-zero error on the following line in the function `CBloomFilter::Hash()`:
https://github.com/bitcoin/bitcoin/blob/f0d6487e290761a4fb03798240a351b5fddfdb38/src/bloom.cpp#L45
By setting a zero-length filter via `filterload`, `vData.size()` is 0, so the modulo operation above, called on any .insert() or .contains() operation then crashed the node. The test uses the approach of just sending an arbitrary `filteradd` message after, which calls `CBloomFilter::insert()` (and in turn `CBloomFilter::Hash()`) on the node. The vulnerability was fixed by commit https://github.com/bitcoin/bitcoin/commit/37c6389c5a0ca63ae3573440ecdfe95d28ad8f07 (an intentional covert fix, [according to gmaxwell](https://github.com/bitcoin/bitcoin/issues/18483#issuecomment-608224095)), which introduced flags `isEmpty`/`isFull` that wouldn't call the `Hash()` member function if `isFull` is true (set to true by default constructor).
To validate that the test fails if the implementation is vulnerable, one can simply set the flags to false in the member function `UpdateEmptyFull()` (that is called after a filter received via `filterload` is constructed), which activates the vulnerable code path calling `Hash` in any case on adding or testing for data in the filter:
```diff
diff --git a/src/bloom.cpp b/src/bloom.cpp
index bd6069b..ef294a3 100644
--- a/src/bloom.cpp
+++ b/src/bloom.cpp
@@ -199,8 +199,8 @@ void CBloomFilter::UpdateEmptyFull()
full &= vData[i] == 0xff;
empty &= vData[i] == 0;
}
- isFull = full;
- isEmpty = empty;
+ isFull = false;
+ isEmpty = false;
}
```
Resulting in:
```
$ ./p2p_filter.py
[...]
2020-04-03T14:38:59.593000Z TestFramework (INFO): Check that division-by-zero remote crash bug [CVE-2013-5700] is fixed
2020-04-03T14:38:59.695000Z TestFramework (ERROR): Assertion failed
[...]
[... some exceptions following ...]
```
ACKs for top commit:
naumenkogs:
utACK 0ed2d8e07d3806d78d03a77d2153f22f9d733a07
Tree-SHA512: 02d0253d13eab70c4bd007b0750c56a5a92d05d419d53033523eeb3ed80318bc95196ab90f7745ea3ac9ebae7caee3adbf2a055a40a4124e0915226e49018fe8
2020-04-05 15:17:50 +02:00
def on_filteradd ( self , message ) : pass
2020-03-31 15:35:53 +02:00
def on_filterclear ( self , message ) : pass
2020-03-30 21:27:54 +02:00
def on_filterload ( self , message ) : pass
2020-04-05 13:12:45 +02:00
def on_getaddr ( self , message ) : pass
def on_getblocks ( self , message ) : pass
def on_getblocktxn ( self , message ) : pass
def on_getdata ( self , message ) : pass
def on_getheaders ( self , message ) : pass
2022-03-11 20:39:12 +01:00
def on_getheaders2 ( self , message ) : pass
2020-04-05 13:12:45 +02:00
def on_headers ( self , message ) : pass
2022-03-11 20:39:12 +01:00
def on_headers2 ( self , message ) : pass
2020-04-05 13:12:45 +02:00
def on_mempool ( self , message ) : pass
2020-03-30 21:27:54 +02:00
def on_merkleblock ( self , message ) : pass
2018-10-27 13:17:59 +02:00
def on_notfound ( self , message ) : pass
2020-04-05 13:12:45 +02:00
def on_pong ( self , message ) : pass
2021-05-29 22:24:52 +02:00
def on_sendaddrv2 ( self , message ) : pass
2020-04-05 13:12:45 +02:00
def on_sendcmpct ( self , message ) : pass
def on_sendheaders ( self , message ) : pass
2022-03-11 20:39:12 +01:00
def on_sendheaders2 ( self , message ) : pass
2020-04-05 13:12:45 +02:00
def on_tx ( self , message ) : pass
def on_inv ( self , message ) :
want = msg_getdata ( )
for i in message . inv :
if i . type != 0 :
want . inv . append ( i )
if len ( want . inv ) :
self . send_message ( want )
def on_ping ( self , message ) :
self . send_message ( msg_pong ( message . nonce ) )
def on_mnlistdiff ( self , message ) : pass
def on_clsig ( self , message ) : pass
def on_islock ( self , message ) : pass
2021-10-05 19:42:34 +02:00
def on_isdlock ( self , message ) : pass
2020-04-05 13:12:45 +02:00
2021-01-28 23:33:18 +01:00
def on_qgetdata ( self , message ) : pass
def on_qdata ( self , message ) : pass
def on_qwatch ( self , message ) : pass
2018-11-07 18:20:01 +01:00
def on_verack ( self , message ) : pass
2020-04-05 13:12:45 +02:00
def on_version ( self , message ) :
2024-08-27 20:53:33 +02:00
assert message . nVersion > = MIN_P2P_VERSION_SUPPORTED , " Version {} received. Test framework only supports versions greater than {} " . format ( message . nVersion , MIN_P2P_VERSION_SUPPORTED )
2024-10-24 16:23:01 +02:00
# reconnection using v1 P2P has happened since version message can be processed, previously unsent version message is sent using v1 P2P here
if self . reconnect :
if self . on_connection_send_msg :
self . send_message ( self . on_connection_send_msg )
self . on_connection_send_msg = None
self . reconnect = False
2021-05-29 22:24:52 +02:00
if self . support_addrv2 :
self . send_message ( msg_sendaddrv2 ( ) )
2020-12-09 07:01:20 +01:00
self . send_message ( msg_verack ( ) )
2020-04-05 13:12:45 +02:00
self . nServices = message . nServices
2023-10-10 18:11:00 +02:00
if self . p2p_connected_to_node :
self . send_message ( msg_getaddr ( ) )
2020-04-05 13:12:45 +02:00
# Connection helper methods
2020-08-04 11:21:02 +02:00
def wait_until ( self , test_function_in , * , timeout = 60 , check_connected = True ) :
def test_function ( ) :
if check_connected :
assert self . is_connected
return test_function_in ( )
2020-09-03 05:47:08 +02:00
wait_until_helper ( test_function , timeout = timeout , lock = p2p_lock , timeout_factor = self . timeout_factor )
2020-05-03 14:58:48 +02:00
2024-03-28 04:21:36 +01:00
def wait_for_connect ( self , timeout = 60 ) :
test_function = lambda : self . is_connected
wait_until_helper ( test_function , timeout = timeout , lock = p2p_lock )
2020-04-05 13:12:45 +02:00
def wait_for_disconnect ( self , timeout = 60 ) :
2018-06-24 01:28:13 +02:00
test_function = lambda : not self . is_connected
2020-08-04 11:21:02 +02:00
self . wait_until ( test_function , timeout = timeout , check_connected = False )
2020-04-05 13:12:45 +02:00
2024-10-24 16:23:01 +02:00
def wait_for_reconnect ( self , timeout = 60 ) :
def test_function ( ) :
if not ( self . is_connected and self . last_message . get ( ' version ' ) and self . v2_state is None ) :
return False
return True
self . wait_until ( test_function , timeout = timeout , check_connected = False )
2020-04-05 13:12:45 +02:00
# Message receiving helper methods
2019-05-16 18:44:54 +02:00
def wait_for_tx ( self , txid , timeout = 60 ) :
def test_function ( ) :
if not self . last_message . get ( ' tx ' ) :
return False
return self . last_message [ ' tx ' ] . tx . rehash ( ) == txid
2020-05-03 14:58:48 +02:00
self . wait_until ( test_function , timeout = timeout )
2019-05-16 18:44:54 +02:00
2020-04-05 13:12:45 +02:00
def wait_for_block ( self , blockhash , timeout = 60 ) :
2019-08-01 15:13:05 +02:00
def test_function ( ) :
return self . last_message . get ( " block " ) and self . last_message [ " block " ] . block . rehash ( ) == blockhash
2020-05-03 14:58:48 +02:00
self . wait_until ( test_function , timeout = timeout )
2020-04-05 13:12:45 +02:00
2018-08-11 12:59:08 +02:00
def wait_for_header ( self , blockhash , timeout = 60 ) :
def test_function ( ) :
last_headers = self . last_message . get ( ' headers ' )
if not last_headers :
return False
2020-04-13 01:33:27 +02:00
return last_headers . headers [ 0 ] . rehash ( ) == int ( blockhash , 16 )
2018-08-11 12:59:08 +02:00
2020-05-03 14:58:48 +02:00
self . wait_until ( test_function , timeout = timeout )
2018-08-11 12:59:08 +02:00
2020-04-13 01:33:27 +02:00
def wait_for_merkleblock ( self , blockhash , timeout = 60 ) :
2020-03-30 21:27:54 +02:00
def test_function ( ) :
last_filtered_block = self . last_message . get ( ' merkleblock ' )
if not last_filtered_block :
return False
2020-04-13 01:33:27 +02:00
return last_filtered_block . merkleblock . header . rehash ( ) == int ( blockhash , 16 )
2020-03-30 21:27:54 +02:00
2020-09-03 05:47:08 +02:00
self . wait_until ( test_function , timeout = timeout )
2020-03-30 21:27:54 +02:00
2020-04-23 12:37:35 +02:00
def wait_for_getdata ( self , hash_list , timeout = 60 ) :
2020-04-05 13:12:45 +02:00
""" Waits for a getdata message.
2020-04-23 12:37:35 +02:00
The object hashes in the inventory vector must match the provided hash_list . """
2019-08-01 15:13:05 +02:00
def test_function ( ) :
2020-04-23 12:37:35 +02:00
last_data = self . last_message . get ( " getdata " )
if not last_data :
return False
return [ x . hash for x in last_data . inv ] == hash_list
2019-08-01 15:13:05 +02:00
2020-05-03 14:58:48 +02:00
self . wait_until ( test_function , timeout = timeout )
2020-04-05 13:12:45 +02:00
def wait_for_getheaders ( self , timeout = 60 ) :
""" Waits for a getheaders message.
Receiving any getheaders message will satisfy the predicate . the last_message [ " getheaders " ]
value must be explicitly cleared before calling this method , or this will return
immediately with success . TODO : change this method to take a hash value and only
return true if the correct block header has been requested . """
2019-08-01 15:13:05 +02:00
def test_function ( ) :
2022-03-11 20:39:12 +01:00
return self . last_message . get ( " getheaders2 " ) if self . nServices & NODE_HEADERS_COMPRESSED \
else self . last_message . get ( " getheaders " )
2019-08-01 15:13:05 +02:00
2020-05-03 14:58:48 +02:00
self . wait_until ( test_function , timeout = timeout )
2020-04-05 13:12:45 +02:00
Merge #18544: net: limit BIP37 filter lifespan (active between 'filterload'..'filterclear')
a9ecbdfcaa15499644d16e9c8ad2c63dfc45b37b test: add more inactive filter tests to p2p_filter.py (Sebastian Falbesoner)
5eae034996b340c19cebab9efb6c89d20fe051ef net: limit BIP37 filter lifespan (active between 'filterload' and 'filterclear') (Sebastian Falbesoner)
Pull request description:
This PR fixes https://github.com/bitcoin/bitcoin/issues/18483. On the master branch, there is currently _always_ a BIP37 filter set for every peer: if not a specific filter is set through a `filterload` message, a default match-everything filter is instanciated and pointed to via the `CBloomFilter` default constructor; that happens both initially, when the containing structure `TxRelay` is constructed:
https://github.com/bitcoin/bitcoin/blob/c0b389b33516fb3eaaad7c30bd11dba768882a7e/src/net.h#L812
and after a loaded filter is removed again through a `filterclear` message:
https://github.com/bitcoin/bitcoin/blob/c0b389b33516fb3eaaad7c30bd11dba768882a7e/src/net_processing.cpp#L3201
The behaviour was introduced by commit https://github.com/bitcoin/bitcoin/commit/37c6389c5a0ca63ae3573440ecdfe95d28ad8f07 (an intentional covert fix for [CVE-2013-5700](https://github.com/bitcoin/bitcoin/pull/18515), according to gmaxwell).
This default match-everything filter leads to some unintended side-effects:
1. `getdata` request for filtered blocks (i.e. type `MSG_FILTERED_BLOCK`) are always responded to with `merkleblock`s, even if no filter was set by the peer, see issue #18483 (strictly speaking, this is a violation of BIP37) https://github.com/bitcoin/bitcoin/blob/c0b389b33516fb3eaaad7c30bd11dba768882a7e/src/net_processing.cpp#L1504-L1507
2. if a peer sends a `filteradd` message without having loaded a filter via `filterload` before, the intended increasing of the banscore never happens (triggered if `bad` is set to true, a few lines below) https://github.com/bitcoin/bitcoin/blob/c0b389b33516fb3eaaad7c30bd11dba768882a7e/src/net_processing.cpp#L3182-L3186
This PR basically activates the `else`-branch code paths for all checks of `pfilter` again (on the master branch, they are dead code) by limiting the pointer's lifespan: instead of always having a filter set, the `pfilter` is only pointing to a `CBloomFilter`-instance after receiving a `filterload` message and the instance is destroyed again (and the pointer nullified) after receiving a `filterclear` message.
Here is a before/after comparison in behaviour:
| code part / scenario | master branch | PR branch |
| --------------------------------------------- | ---------------------------------- | ---------------------------------------------------- |
| `getdata` processing for `MSG_FILTERED_BLOCK` | always responds with `merkleblock` | only responds if filter was set via `filterload` |
| `filteradd` processing, no filter was loaded | nothing | peer's banscore increases by 100 (i.e. disconnect) |
On the other code parts where `pfilter` is checked there is no change in the logic behaviour (except that `CBloomFilter::IsRelevantAndUpdate()` is unnecessarily called and immediately returned in the master branch).
Note that the default constructor of `CBloomFilter` is only used for deserializing the received `filterload` message and nowhere else. The PR also contains a functional test checking that sending `getdata` for filtered blocks is ignored by the node if no bloom filter is set.
ACKs for top commit:
MarcoFalke:
re-ACK a9ecbdfcaa, only change is in test code 🕙
Tree-SHA512: 1a656a6d74ccaf628e7fdca063ba63fbab2089e0b6d0a11be9bbd387c2ee6d3230706ff8ffc1a55711481df3d4547137dd7c9d9184d89eaa43ade4927792d0b6
2020-04-20 12:56:56 +02:00
def wait_for_inv ( self , expected_inv , timeout = 60 ) :
""" Waits for an INV message and checks that the first inv object in the message was as expected. """
if len ( expected_inv ) > 1 :
raise NotImplementedError ( " wait_for_inv() will only verify the first inv object " )
def test_function ( ) :
return self . last_message . get ( " inv " ) and \
self . last_message [ " inv " ] . inv [ 0 ] . type == expected_inv [ 0 ] . type and \
self . last_message [ " inv " ] . inv [ 0 ] . hash == expected_inv [ 0 ] . hash
self . wait_until ( test_function , timeout = timeout )
2020-04-05 13:12:45 +02:00
def wait_for_verack ( self , timeout = 60 ) :
2019-08-01 15:13:05 +02:00
def test_function ( ) :
2020-07-30 09:15:46 +02:00
return " verack " in self . last_message
2019-08-01 15:13:05 +02:00
2020-08-09 18:32:59 +02:00
self . wait_until ( test_function , timeout = timeout )
2020-04-05 13:12:45 +02:00
# Message sending helper functions
2018-11-06 11:08:40 +01:00
def send_and_ping ( self , message , timeout = 60 ) :
2020-04-05 13:12:45 +02:00
self . send_message ( message )
2018-11-06 11:08:40 +01:00
self . sync_with_ping ( timeout = timeout )
2020-04-05 13:12:45 +02:00
2021-04-28 08:14:22 +02:00
def sync_send_with_ping ( self , timeout = 60 ) :
""" Ensure SendMessages is called on this connection """
# Calling sync_with_ping twice requires that the node calls
# `ProcessMessage` twice, and thus ensures `SendMessages` must have
# been called at least once
self . sync_with_ping ( )
self . sync_with_ping ( )
2020-04-05 13:12:45 +02:00
def sync_with_ping ( self , timeout = 60 ) :
2021-04-28 08:14:22 +02:00
""" Ensure ProcessMessages is called on this connection """
2020-04-05 13:12:45 +02:00
self . send_message ( msg_ping ( nonce = self . ping_counter ) )
2019-08-01 15:13:05 +02:00
def test_function ( ) :
return self . last_message . get ( " pong " ) and self . last_message [ " pong " ] . nonce == self . ping_counter
2020-05-03 14:58:48 +02:00
self . wait_until ( test_function , timeout = timeout )
2020-04-05 13:12:45 +02:00
self . ping_counter + = 1
2018-06-29 18:04:25 +02:00
# One lock for synchronizing all data access between the network event loop (see
2020-03-30 14:21:47 +02:00
# NetworkThread below) and the thread running the test logic. For simplicity,
2018-06-29 18:04:25 +02:00
# P2PConnection acquires this lock whenever delivering a message to a P2PInterface.
# This lock should be acquired in the thread running the test logic to synchronize
2017-11-30 23:58:58 +01:00
# access to any data shared with the P2PInterface or P2PConnection.
2024-01-15 20:35:29 +01:00
p2p_lock = threading . Lock ( )
2015-04-28 18:36:15 +02:00
2018-06-29 18:04:25 +02:00
2017-12-12 12:52:33 +01:00
class NetworkThread ( threading . Thread ) :
2018-06-29 18:04:25 +02:00
network_event_loop = None
2017-12-12 12:52:33 +01:00
def __init__ ( self ) :
super ( ) . __init__ ( name = " NetworkThread " )
2018-06-29 18:04:25 +02:00
# There is only one event loop and no more than one thread must be created
assert not self . network_event_loop
2024-03-28 04:21:36 +01:00
NetworkThread . listeners = { }
NetworkThread . protos = { }
2021-09-15 17:33:43 +02:00
if sys . platform == ' win32 ' :
asyncio . set_event_loop_policy ( asyncio . WindowsSelectorEventLoopPolicy ( ) )
2018-06-29 18:04:25 +02:00
NetworkThread . network_event_loop = asyncio . new_event_loop ( )
2017-12-12 12:52:33 +01:00
2015-04-28 18:36:15 +02:00
def run ( self ) :
2018-06-29 18:04:25 +02:00
""" Start the network thread. """
self . network_event_loop . run_forever ( )
def close ( self , timeout = 10 ) :
""" Close the connections and network event loop. """
self . network_event_loop . call_soon_threadsafe ( self . network_event_loop . stop )
2020-09-03 05:47:08 +02:00
wait_until_helper ( lambda : not self . network_event_loop . is_running ( ) , timeout = timeout )
2018-06-29 18:04:25 +02:00
self . network_event_loop . close ( )
self . join ( timeout )
2019-11-04 20:52:51 +01:00
# Safe to remove event loop.
NetworkThread . network_event_loop = None
2015-04-28 18:36:15 +02:00
2024-03-28 04:21:36 +01:00
@classmethod
def listen ( cls , p2p , callback , port = None , addr = None , idx = 1 ) :
""" Ensure a listening server is running on the given port, and run the
protocol specified by ` p2p ` on the next connection to it . Once ready
for connections , call ` callback ` . """
if port is None :
assert 0 < idx < = MAX_NODES
port = p2p_port ( MAX_NODES - idx )
if addr is None :
addr = ' 127.0.0.1 '
2024-10-24 16:23:01 +02:00
def exception_handler ( loop , context ) :
if not p2p . reconnect :
loop . default_exception_handler ( context )
cls . network_event_loop . set_exception_handler ( exception_handler )
2024-03-28 04:21:36 +01:00
coroutine = cls . create_listen_server ( addr , port , callback , p2p )
cls . network_event_loop . call_soon_threadsafe ( cls . network_event_loop . create_task , coroutine )
@classmethod
async def create_listen_server ( cls , addr , port , callback , proto ) :
def peer_protocol ( ) :
""" Returns a function that does the protocol handling for a new
connection . To allow different connections to have different
behaviors , the protocol function is first put in the cls . protos
dict . When the connection is made , the function removes the
protocol function from that dict , and returns it so the event loop
can start executing it . """
response = cls . protos . get ( ( addr , port ) )
2024-10-24 16:23:01 +02:00
# remove protocol function from dict only when reconnection doesn't need to happen/already happened
if not proto . reconnect :
cls . protos [ ( addr , port ) ] = None
2024-03-28 04:21:36 +01:00
return response
if ( addr , port ) not in cls . listeners :
# When creating a listener on a given (addr, port) we only need to
# do it once. If we want different behaviors for different
# connections, we can accomplish this by providing different
# `proto` functions
listener = await cls . network_event_loop . create_server ( peer_protocol , addr , port )
logger . debug ( " Listening server on %s : %d should be started " % ( addr , port ) )
cls . listeners [ ( addr , port ) ] = listener
cls . protos [ ( addr , port ) ] = proto
callback ( addr , port )
2020-04-07 01:15:22 +02:00
class P2PDataStore ( P2PInterface ) :
2018-02-13 10:30:41 +01:00
""" A P2P data store class.
Keeps a block and transaction store and responds correctly to getdata and getheaders requests . """
def __init__ ( self ) :
super ( ) . __init__ ( )
# store of blocks. key is block hash, value is a CBlock object
self . block_store = { }
self . last_block_hash = ' '
# store of txs. key is txid, value is a CTransaction object
self . tx_store = { }
self . getdata_requests = [ ]
2020-04-05 13:12:45 +02:00
def on_getdata ( self , message ) :
2018-02-13 10:30:41 +01:00
""" Check for the tx/block in our stores and if found, reply with an inv message. """
for inv in message . inv :
self . getdata_requests . append ( inv . hash )
if ( inv . type & MSG_TYPE_MASK ) == MSG_TX and inv . hash in self . tx_store . keys ( ) :
self . send_message ( msg_tx ( self . tx_store [ inv . hash ] ) )
elif ( inv . type & MSG_TYPE_MASK ) == MSG_BLOCK and inv . hash in self . block_store . keys ( ) :
self . send_message ( msg_block ( self . block_store [ inv . hash ] ) )
else :
logger . debug ( ' getdata message type {} received. ' . format ( hex ( inv . type ) ) )
2024-09-06 22:47:12 +02:00
def _compute_requested_block_headers ( self , locator , hash_stop , max_results ) :
2018-02-13 10:30:41 +01:00
# Assume that the most recent block added is the tip
if not self . block_store :
return
headers_list = [ self . block_store [ self . last_block_hash ] ]
while headers_list [ - 1 ] . sha256 not in locator . vHave :
# Walk back through the block store, adding headers to headers_list
# as we go.
prev_block_hash = headers_list [ - 1 ] . hashPrevBlock
if prev_block_hash in self . block_store :
2018-04-02 23:40:16 +02:00
prev_block_header = CBlockHeader ( self . block_store [ prev_block_hash ] )
2018-02-13 10:30:41 +01:00
headers_list . append ( prev_block_header )
if prev_block_header . sha256 == hash_stop :
# if this is the hashstop header, stop here
break
else :
logger . debug ( ' block hash {} not found in block store ' . format ( hex ( prev_block_hash ) ) )
break
# Truncate the list if there are too many headers
2024-09-04 20:56:55 +02:00
headers_list = headers_list [ : - max_results - 1 : - 1 ]
2022-03-11 20:39:12 +01:00
return headers_list
def on_getheaders2 ( self , message ) :
""" Search back through our block store for the locator, and reply with a compressed headers message if found. """
2024-09-06 22:47:12 +02:00
headers_list = self . _compute_requested_block_headers ( message . locator , message . hashstop , MAX_HEADERS_COMPRESSED_RESULT )
2022-03-11 20:39:12 +01:00
compressible_headers_list = [ CompressibleBlockHeader ( h ) for h in headers_list ] if headers_list else None
response = msg_headers2 ( compressible_headers_list )
if response is not None :
self . send_message ( response )
def on_getheaders ( self , message ) :
""" Search back through our block store for the locator, and reply with a headers message if found. """
2024-09-06 22:47:12 +02:00
headers_list = self . _compute_requested_block_headers ( message . locator , message . hashstop , MAX_HEADERS_UNCOMPRESSED_RESULT )
2018-02-13 10:30:41 +01:00
response = msg_headers ( headers_list )
if response is not None :
self . send_message ( response )
2024-10-24 16:23:01 +02:00
def send_blocks_and_test ( self , blocks , node , * , success = True , force_send = False , reject_reason = None , expect_disconnect = False , timeout = 60 , is_decoy = False ) :
2018-02-13 10:30:41 +01:00
""" Send blocks to test node and test whether the tip advances.
- add all blocks to our block_store
- send a headers message for the final block
- the on_getheaders handler will ensure that any getheaders are responded to
2021-12-08 06:41:56 +01:00
- if force_send is False : wait for getdata for each of the blocks . The on_getdata handler will
ensure that any getdata messages are responded to . Otherwise send the full block unsolicited .
2018-02-13 10:30:41 +01:00
- if success is True : assert that the node ' s tip advances to the most recent block
- if success is False : assert that the node ' s tip doesn ' t advance
2018-09-08 06:56:51 +02:00
- if reject_reason is set : assert that the correct reject message is logged """
2018-02-13 10:30:41 +01:00
2024-01-15 20:35:29 +01:00
with p2p_lock :
2018-02-13 10:30:41 +01:00
for block in blocks :
self . block_store [ block . sha256 ] = block
self . last_block_hash = block . sha256
2018-09-08 06:56:51 +02:00
reject_reason = [ reject_reason ] if reject_reason else [ ]
with node . assert_debug_log ( expected_msgs = reject_reason ) :
2024-10-24 16:23:01 +02:00
if is_decoy : # since decoy messages are ignored by the recipient - no need to wait for response
force_send = True
2021-12-08 06:41:56 +01:00
if force_send :
for b in blocks :
2024-10-24 16:23:01 +02:00
self . send_message ( msg_block ( block = b ) , is_decoy )
2021-12-08 06:41:56 +01:00
else :
self . send_message ( msg_headers ( [ CBlockHeader ( block ) for block in blocks ] ) )
2020-08-04 11:21:02 +02:00
self . wait_until (
lambda : blocks [ - 1 ] . sha256 in self . getdata_requests ,
timeout = timeout ,
check_connected = success ,
)
2018-02-13 10:30:41 +01:00
2018-09-08 06:56:51 +02:00
if expect_disconnect :
self . wait_for_disconnect ( )
else :
self . sync_with_ping ( )
2018-02-13 10:30:41 +01:00
2018-09-08 06:56:51 +02:00
if success :
2020-05-03 14:58:48 +02:00
self . wait_until ( lambda : node . getbestblockhash ( ) == blocks [ - 1 ] . hash , timeout = timeout )
2018-09-08 06:56:51 +02:00
else :
assert node . getbestblockhash ( ) != blocks [ - 1 ] . hash
2018-02-13 10:30:41 +01:00
2018-09-08 06:56:51 +02:00
def send_txs_and_test ( self , txs , node , * , success = True , expect_disconnect = False , reject_reason = None ) :
2018-02-13 10:30:41 +01:00
""" Send txs to test node and test whether they ' re accepted to the mempool.
- add all txs to our tx_store
- send tx messages for all txs
2018-04-26 19:32:17 +02:00
- if success is True / False : assert that the txs are / are not accepted to the mempool
- if expect_disconnect is True : Skip the sync with ping
2018-09-08 06:56:51 +02:00
- if reject_reason is set : assert that the correct reject message is logged . """
2018-02-13 10:30:41 +01:00
2024-01-15 20:35:29 +01:00
with p2p_lock :
2018-02-13 10:30:41 +01:00
for tx in txs :
self . tx_store [ tx . sha256 ] = tx
2018-09-08 06:56:51 +02:00
reject_reason = [ reject_reason ] if reject_reason else [ ]
with node . assert_debug_log ( expected_msgs = reject_reason ) :
2018-02-13 10:30:41 +01:00
for tx in txs :
2018-09-08 06:56:51 +02:00
self . send_message ( msg_tx ( tx ) )
2018-02-13 10:30:41 +01:00
2018-09-08 06:56:51 +02:00
if expect_disconnect :
self . wait_for_disconnect ( )
else :
self . sync_with_ping ( )
raw_mempool = node . getrawmempool ( )
if success :
# Check that all txs are now in the mempool
for tx in txs :
assert tx . hash in raw_mempool , " {} not found in mempool " . format ( tx . hash )
else :
# Check that none of the txs are now in the mempool
for tx in txs :
assert tx . hash not in raw_mempool , " {} tx found in mempool " . format ( tx . hash )
2022-05-06 06:04:50 +02:00
class P2PTxInvStore ( P2PInterface ) :
""" A P2PInterface which stores a count of how many times each txid has been announced. """
def __init__ ( self ) :
super ( ) . __init__ ( )
self . tx_invs_received = defaultdict ( int )
def on_inv ( self , message ) :
2020-05-22 01:27:28 +02:00
super ( ) . on_inv ( message ) # Send getdata in response.
2022-05-06 06:04:50 +02:00
# Store how many times invs have been received for each tx.
for i in message . inv :
if i . type == MSG_TX :
# save txid
self . tx_invs_received [ i . hash ] + = 1
def get_invs ( self ) :
2024-01-15 20:35:29 +01:00
with p2p_lock :
2022-05-06 06:04:50 +02:00
return list ( self . tx_invs_received . keys ( ) )
2020-05-22 01:27:28 +02:00
def wait_for_broadcast ( self , txns , timeout = 60 ) :
""" Waits for the txns (list of txids) to complete initial broadcast.
The mempool should mark unbroadcast = False for these transactions .
"""
# Wait until invs have been received (and getdatas sent) for each txid.
2020-08-04 11:21:02 +02:00
self . wait_until ( lambda : set ( self . tx_invs_received . keys ( ) ) == set ( [ int ( tx , 16 ) for tx in txns ] ) , timeout = timeout )
2020-05-22 01:27:28 +02:00
# Flush messages and wait for the getdatas to be processed
self . sync_with_ping ( )