Initial Vexta Core v0.1.0 release

This commit is contained in:
Capri 2026-07-15 14:07:28 +00:00
commit 25c454329d
2481 changed files with 689289 additions and 0 deletions

2
test/functional/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
*.pyc
cache

192
test/functional/README.md Normal file
View file

@ -0,0 +1,192 @@
# Functional tests
### Writing Functional Tests
#### Example test
The file [test/functional/example_test.py](example_test.py) is a heavily commented example
of a test case that uses both the RPC and P2P interfaces. If you are writing your first test, copy
that file and modify to fit your needs.
#### Coverage
Running `test/functional/test_runner.py` with the `--coverage` argument tracks which RPCs are
called by the tests and prints a report of uncovered RPCs in the summary. This
can be used (along with the `--extended` argument) to find out which RPCs we
don't have test cases for.
#### Style guidelines
- Where possible, try to adhere to [PEP-8 guidelines](https://www.python.org/dev/peps/pep-0008/)
- Use a python linter like flake8 before submitting PRs to catch common style
nits (eg trailing whitespace, unused imports, etc)
- The oldest supported Python version is specified in [doc/dependencies.md](/doc/dependencies.md).
Consider using [pyenv](https://github.com/pyenv/pyenv), which checks [.python-version](/.python-version),
to prevent accidentally introducing modern syntax from an unsupported Python version.
The CI linter job also checks this, but [possibly not in all cases](https://github.com/digibyte/digibyte/pull/14884#discussion_r239585126).
- See [the python lint script](/test/lint/lint-python.sh) that checks for violations that
could lead to bugs and issues in the test code.
- Use [type hints](https://docs.python.org/3/library/typing.html) in your code to improve code readability
and to detect possible bugs earlier.
- Avoid wildcard imports
- Use a module-level docstring to describe what the test is testing, and how it
is testing it.
- When subclassing the DigiByteTestFramework, place overrides for the
`set_test_params()`, `add_options()` and `setup_xxxx()` methods at the top of
the subclass, then locally-defined helper methods, then the `run_test()` method.
- Use `f'{x}'` for string formatting in preference to `'{}'.format(x)` or `'%s' % x`.
#### Naming guidelines
- Name the test `<area>_test.py`, where area can be one of the following:
- `feature` for tests for full features that aren't wallet/mining/mempool, eg `feature_rbf.py`
- `interface` for tests for other interfaces (REST, ZMQ, etc), eg `interface_rest.py`
- `mempool` for tests for mempool behaviour, eg `mempool_reorg.py`
- `mining` for tests for mining features, eg `mining_prioritisetransaction.py`
- `p2p` for tests that explicitly test the p2p interface, eg `p2p_disconnect_ban.py`
- `rpc` for tests for individual RPC methods or features, eg `rpc_listtransactions.py`
- `tool` for tests for tools, eg `tool_wallet.py`
- `wallet` for tests for wallet features, eg `wallet_keypool.py`
- Use an underscore to separate words
- exception: for tests for specific RPCs or command line options which don't include underscores, name the test after the exact RPC or argument name, eg `rpc_decodescript.py`, not `rpc_decode_script.py`
- Don't use the redundant word `test` in the name, eg `interface_zmq.py`, not `interface_zmq_test.py`
#### General test-writing advice
- Instead of inline comments or no test documentation at all, log the comments to the test log, e.g.
`self.log.info('Create enough transactions to fill a block')`. Logs make the test code easier to read and the test
logic easier [to debug](/test/README.md#test-logging).
- Set `self.num_nodes` to the minimum number of nodes necessary for the test.
Having additional unrequired nodes adds to the execution time of the test as
well as memory/CPU/disk requirements (which is important when running tests in
parallel).
- Avoid stop-starting the nodes multiple times during the test if possible. A
stop-start takes several seconds, so doing it several times blows up the
runtime of the test.
- Set the `self.setup_clean_chain` variable in `set_test_params()` to `True` to
initialize an empty blockchain and start from the Genesis block, rather than
load a premined blockchain from cache with the default value of `False`. The
cached data directories contain a 200-block pre-mined blockchain with the
spendable mining rewards being split between four nodes. Each node has 25
mature block subsidies (25x50=1250 BTC) in its wallet. Using them is much more
efficient than mining blocks in your test.
- When calling RPCs with lots of arguments, consider using named keyword
arguments instead of positional arguments to make the intent of the call
clear to readers.
- Many of the core test framework classes such as `CBlock` and `CTransaction`
don't allow new attributes to be added to their objects at runtime like
typical Python objects allow. This helps prevent unpredictable side effects
from typographical errors or usage of the objects outside of their intended
purpose.
#### RPC and P2P definitions
Test writers may find it helpful to refer to the definitions for the RPC and
P2P messages. These can be found in the following source files:
- `/src/rpc/*` for RPCs
- `/src/wallet/rpc*` for wallet RPCs
- `ProcessMessage()` in `/src/net_processing.cpp` for parsing P2P messages
#### Using the P2P interface
- `P2P`s can be used to test specific P2P protocol behavior.
[p2p.py](test_framework/p2p.py) contains test framework p2p objects and
[messages.py](test_framework/messages.py) contains all the definitions for objects passed
over the network (`CBlock`, `CTransaction`, etc, along with the network-level
wrappers for them, `msg_block`, `msg_tx`, etc).
- P2P tests have two threads. One thread handles all network communication
with the digibyted(s) being tested in a callback-based event loop; the other
implements the test logic.
- `P2PConnection` is the class used to connect to a digibyted. `P2PInterface`
contains the higher level logic for processing P2P payloads and connecting to
the DigiByte Core node application logic. For custom behaviour, subclass the
P2PInterface object and override the callback methods.
`P2PConnection`s can be used as such:
```python
p2p_conn = node.add_p2p_connection(P2PInterface())
p2p_conn.send_and_ping(msg)
```
They can also be referenced by indexing into a `TestNode`'s `p2ps` list, which
contains the list of test framework `p2p` objects connected to itself
(it does not include any `TestNode`s):
```python
node.p2ps[0].sync_with_ping()
```
More examples can be found in [p2p_unrequested_blocks.py](p2p_unrequested_blocks.py),
[p2p_compactblocks.py](p2p_compactblocks.py).
#### Prototyping tests
The [`TestShell`](test-shell.md) class exposes the DigiByteTestFramework
functionality to interactive Python3 environments and can be used to prototype
tests. This may be especially useful in a REPL environment with session logging
utilities, such as
[IPython](https://ipython.readthedocs.io/en/stable/interactive/reference.html#session-logging-and-restoring).
The logs of such interactive sessions can later be adapted into permanent test
cases.
### Test framework modules
The following are useful modules for test developers. They are located in
[test/functional/test_framework/](test_framework).
#### [authproxy.py](test_framework/authproxy.py)
Taken from the [python-digibyterpc repository](https://github.com/jgarzik/python-digibyterpc).
#### [test_framework.py](test_framework/test_framework.py)
Base class for functional tests.
#### [util.py](test_framework/util.py)
Generally useful functions.
#### [p2p.py](test_framework/p2p.py)
Test objects for interacting with a digibyted node over the p2p interface.
#### [script.py](test_framework/script.py)
Utilities for manipulating transaction scripts (originally from python-digibytelib)
#### [key.py](test_framework/key.py)
Test-only secp256k1 elliptic curve implementation
#### [blocktools.py](test_framework/blocktools.py)
Helper functions for creating blocks and transactions.
### Benchmarking with perf
An easy way to profile node performance during functional tests is provided
for Linux platforms using `perf`.
Perf will sample the running node and will generate profile data in the node's
datadir. The profile data can then be presented using `perf report` or a graphical
tool like [hotspot](https://github.com/KDAB/hotspot).
There are two ways of invoking perf: one is to use the `--perf` flag when
running tests, which will profile each node during the entire test run: perf
begins to profile when the node starts and ends when it shuts down. The other
way is the use the `profile_with_perf` context manager, e.g.
```python
with node.profile_with_perf("send-big-msgs"):
# Perform activity on the node you're interested in profiling, e.g.:
for _ in range(10000):
node.p2ps[0].send_message(some_large_message)
```
To see useful textual output, run
```sh
perf report -i /path/to/datadir/send-big-msgs.perf.data.xxxx --stdio | c++filt | less
```
#### See also:
- [Installing perf](https://askubuntu.com/q/50145)
- [Perf examples](https://www.brendangregg.com/perf.html)
- [Hotspot](https://github.com/KDAB/hotspot): a GUI for perf output analysis

View file

@ -0,0 +1,25 @@
#!/usr/bin/env python3
# Copyright (c) 2017 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test digibyte-cli"""
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import assert_equal
class TestDigiByteCli(DigiByteTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
def run_test(self):
"""Main test logic"""
self.log.info("Compare responses from getinfo RPC and `digibyte-cli getinfo`")
cli_get_info = self.nodes[0].cli.getinfo()
rpc_get_info = self.nodes[0].getinfo()
assert_equal(cli_get_info, rpc_get_info)
if __name__ == '__main__':
TestDigiByteCli().main()

201
test/functional/combine_logs.py Executable file
View file

@ -0,0 +1,201 @@
#!/usr/bin/env python3
# Copyright (c) 2017-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Combine logs from multiple digibyte nodes as well as the test_framework log.
This streams the combined log output to stdout. Use combine_logs.py > outputfile
to write to an outputfile.
If no argument is provided, the most recent test directory will be used."""
import argparse
from collections import defaultdict, namedtuple
import heapq
import itertools
import os
import pathlib
import re
import sys
import tempfile
# N.B.: don't import any local modules here - this script must remain executable
# without the parent module installed.
# Should match same symbol in `test_framework.test_framework`.
TMPDIR_PREFIX = "digibyte_func_test_"
# Matches on the date format at the start of the log event
TIMESTAMP_PATTERN = re.compile(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{6})?Z")
LogEvent = namedtuple('LogEvent', ['timestamp', 'source', 'event'])
def main():
"""Main function. Parses args, reads the log files and renders them as text or html."""
parser = argparse.ArgumentParser(
description=__doc__, formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument(
'testdir', nargs='?', default='',
help=('temporary test directory to combine logs from. '
'Defaults to the most recent'))
parser.add_argument('-c', '--color', dest='color', action='store_true', help='outputs the combined log with events colored by source (requires posix terminal colors. Use less -r for viewing)')
parser.add_argument('--html', dest='html', action='store_true', help='outputs the combined log as html. Requires jinja2. pip install jinja2')
args = parser.parse_args()
if args.html and args.color:
print("Only one out of --color or --html should be specified")
sys.exit(1)
testdir = args.testdir or find_latest_test_dir()
if not testdir:
print("No test directories found")
sys.exit(1)
if not args.testdir:
print("Opening latest test directory: {}".format(testdir), file=sys.stderr)
colors = defaultdict(lambda: '')
if args.color:
colors["test"] = "\033[0;36m" # CYAN
colors["node0"] = "\033[0;34m" # BLUE
colors["node1"] = "\033[0;32m" # GREEN
colors["node2"] = "\033[0;31m" # RED
colors["node3"] = "\033[0;33m" # YELLOW
colors["reset"] = "\033[0m" # Reset font color
log_events = read_logs(testdir)
if args.html:
print_logs_html(log_events)
else:
print_logs_plain(log_events, colors)
print_node_warnings(testdir, colors)
def read_logs(tmp_dir):
"""Reads log files.
Delegates to generator function get_log_events() to provide individual log events
for each of the input log files."""
# Find out what the folder is called that holds the debug.log file
glob = pathlib.Path(tmp_dir).glob('node0/**/debug.log')
path = next(glob, None)
if path:
assert next(glob, None) is None # more than one debug.log, should never happen
chain = re.search(r'node0/(.+?)/debug\.log$', path.as_posix()).group(1) # extract the chain name
else:
chain = 'regtest' # fallback to regtest (should only happen when none exists)
files = [("test", "%s/test_framework.log" % tmp_dir)]
for i in itertools.count():
logfile = "{}/node{}/{}/debug.log".format(tmp_dir, i, chain)
if not os.path.isfile(logfile):
break
files.append(("node%d" % i, logfile))
return heapq.merge(*[get_log_events(source, f) for source, f in files])
def print_node_warnings(tmp_dir, colors):
"""Print nodes' errors and warnings"""
warnings = []
for stream in ['stdout', 'stderr']:
for i in itertools.count():
folder = "{}/node{}/{}".format(tmp_dir, i, stream)
if not os.path.isdir(folder):
break
for (_, _, fns) in os.walk(folder):
for fn in fns:
warning = pathlib.Path('{}/{}'.format(folder, fn)).read_text().strip()
if warning:
warnings.append(("node{} {}".format(i, stream), warning))
print()
for w in warnings:
print("{} {} {} {}".format(colors[w[0].split()[0]], w[0], w[1], colors["reset"]))
def find_latest_test_dir():
"""Returns the latest tmpfile test directory prefix."""
tmpdir = tempfile.gettempdir()
def join_tmp(basename):
return os.path.join(tmpdir, basename)
def is_valid_test_tmpdir(basename):
fullpath = join_tmp(basename)
return (
os.path.isdir(fullpath)
and basename.startswith(TMPDIR_PREFIX)
and os.access(fullpath, os.R_OK)
)
testdir_paths = [
join_tmp(name) for name in os.listdir(tmpdir) if is_valid_test_tmpdir(name)
]
return max(testdir_paths, key=os.path.getmtime) if testdir_paths else None
def get_log_events(source, logfile):
"""Generator function that returns individual log events.
Log events may be split over multiple lines. We use the timestamp
regex match as the marker for a new log event."""
try:
with open(logfile, 'r', encoding='utf-8') as infile:
event = ''
timestamp = ''
for line in infile:
# skip blank lines
if line == '\n':
continue
# if this line has a timestamp, it's the start of a new log event.
time_match = TIMESTAMP_PATTERN.match(line)
if time_match:
if event:
yield LogEvent(timestamp=timestamp, source=source, event=event.rstrip())
timestamp = time_match.group()
if time_match.group(1) is None:
# timestamp does not have microseconds. Add zeroes.
timestamp_micro = timestamp.replace("Z", ".000000Z")
line = line.replace(timestamp, timestamp_micro)
timestamp = timestamp_micro
event = line
# if it doesn't have a timestamp, it's a continuation line of the previous log.
else:
# Add the line. Prefix with space equivalent to the source + timestamp so log lines are aligned
event += " " + line
# Flush the final event
yield LogEvent(timestamp=timestamp, source=source, event=event.rstrip())
except FileNotFoundError:
print("File %s could not be opened. Continuing without it." % logfile, file=sys.stderr)
def print_logs_plain(log_events, colors):
"""Renders the iterator of log events into text."""
for event in log_events:
lines = event.event.splitlines()
print("{0} {1: <5} {2} {3}".format(colors[event.source.rstrip()], event.source, lines[0], colors["reset"]))
if len(lines) > 1:
for line in lines[1:]:
print("{0}{1}{2}".format(colors[event.source.rstrip()], line, colors["reset"]))
def print_logs_html(log_events):
"""Renders the iterator of log events into html."""
try:
import jinja2 #type:ignore
except ImportError:
print("jinja2 not found. Try `pip install jinja2`")
sys.exit(1)
print(jinja2.Environment(loader=jinja2.FileSystemLoader('./'))
.get_template('combined_log_template.html')
.render(title="Combined Logs from testcase", log_events=[event._asdict() for event in log_events]))
if __name__ == '__main__':
main()

View file

@ -0,0 +1,40 @@
<html lang="en">
<head>
<title> {{ title }} </title>
<style>
ul {
list-style-type: none;
font-family: monospace;
}
li {
border: 1px solid slategray;
margin-bottom: 1px;
}
li:hover {
filter: brightness(85%);
}
li.log-test {
background-color: cyan;
}
li.log-node0 {
background-color: lightblue;
}
li.log-node1 {
background-color: lightgreen;
}
li.log-node2 {
background-color: lightsalmon;
}
li.log-node3 {
background-color: lightyellow;
}
</style>
</head>
<body>
<ul>
{% for event in log_events %}
<li class="log-{{ event.source }}"> {{ event.source }} {{ event.timestamp }} {{event.event}}</li>
{% endfor %}
</ul>
</body>
</html>

27
test/functional/create_cache.py Executable file
View file

@ -0,0 +1,27 @@
#!/usr/bin/env python3
# Copyright (c) 2016-2019 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Create a blockchain cache.
Creating a cache of the blockchain speeds up test execution when running
multiple functional tests. This helper script is executed by test_runner when multiple
tests are being run in parallel.
"""
from test_framework.test_framework import DigiByteTestFramework
class CreateCache(DigiByteTestFramework):
# Test network and test nodes are not required:
def set_test_params(self):
self.num_nodes = 0
def setup_network(self):
pass
def run_test(self):
pass
if __name__ == '__main__':
CreateCache().main()

View file

View file

@ -0,0 +1,548 @@
fork:02000020522282aaac58be581dda845a286135757298cad90d679529be63571d71a08e30c32f6b2b737edb2adf1600b41b346cfdcec0d33211b6bfd0fcb720564e90fd8eee0e3e62ffff0f1e16b40500
fork:020000206718bee779497289c84e75323eaa4e5cb0c5e84d2bb78e9752f7c8eebec0cbc4b5c494825cfbb0905337d9174db358474302fb05eba76886e5ca0bf177fb872c25123e62ffff0f1ecac20300
02000020522282aaac58be581dda845a286135757298cad90d679529be63571d71a08e305bee833088be55ce7f6aeb7607593ebb0ba5c56d3ee6011f0e7c00d12b7c692858eea35cffff0f1e86060000
02000020748bca844417c14c295572f9a86fd6b573d3b525c7af7b15ea3a2a1331340c04e6ae953d4aa03e8705636d72a00077c4f1e00a485acd6e06000f7e4d9f9915c8baf3a35cffff0f1e6a0a0000
020000201593c39a58903c8cfeb3a8246fd53c86849971978378149a49d794f477c2812e7993c8da52a2978a54d16fd1b6c9efa0dbf9527ef50c8c4365bd07a05735572e6ef7a35cffff0f1e921c0000
020000209b88ba56b0d9fd7aad05aa5c83a2d85f2240b940ff2f18bd8b7697a45ebf4ab69803198482acbeeabfd07a07cd627f73d790958e9a12647808f2059328d32c04e2f7a35cffff0f1e51240000
02000020b010442641bda2e4465a31f371f66c22083abc3a9383dbb2c01a24a3bb8e1bcdc6b7d3dd47e91fd2ece4e72797135f4f4fa231a12b578f9b860e22c9e06c7f5644f8a35cffff0f1e701b0000
02000020e3082e9aa6c91f1cbabbc6f2332372033dc67dff34388d400c6c606453e9ae136fa85265ec3602dff1dab2870f806fdca7e53f0a1e4f241588b91bd566150a417af8a35cffff0f1e1f180000
020000201e4565238a1da591be11dc6d97d6c79aed97bd1adad1a0c29c2feecffe1bcd115c23b0c5991bc49dd6562b69ce3b500c8885a4a923851583aed1654b0196de6157fba35cffff0f1eb0130000
0200002036b79a6b20a60e50bcccb2625964626ae6be7b81d14203ecd0c92cdf774124ca76ff3a713c5b506df65b752edb77979035f0860b17a4f2dd3ab8a4f05d445e176400a45cffff0f1e01130000
020000205d101265ca652da5ac0e53b80314b4116783aea901bd6c64fac11c1a3e420da6a2bb8d6d196f6572c8aa8600296afae66b24fcec61944dfdda21160512d22c92a902a45cffff0f1e91240000
020000203147eb32991d2c4a8744dcbfbac31f70e48959f70059826adf22e0e27a911d74136e560fa441848c010a8f283114d5e0bf5044d5a28878f972603ed22b308e0aab08a45cffff0f1ee6240000
02000020955c181d3ec70f5fd03b62ed7003ced914a5158896fb3c010de190ffed9558a5febb33774f1e653b71dad64d453de2fa099d44fc1dfc656b6a9644cbedda6e8ad008a45cffff0f1ecc010000
0200002050fccbaa17e9a3519f66b0a8707ba37d204bfa69e6137834a3988b206d96ea40ebc87ccadad7462358e545af72860053bbd504feb80ad64c5663bba9e78e6045ef08a45cffff0f1e3f0b0000
0200002072128f9f49c6e69838cff1e96d182e26f3b77f5944bdf76fab203c021993b0b508269c4d318230763570c9ae8b3f38ba6a8752a9534122531ee2ea4cf2f82dc52809a45cffff0f1e390a0000
020000209913c28161321f23615b33ef805a016b8a6a3519591b918c7c4eb15c72966d864402815a85a71f270e60e2b8c780919cc7fc5f9f8dd19b5fb390bf3742d9fffef40ba45cffff0f1ec4160000
020000205e8b089cf5c597d51e18c7c88f3dfec1aab36b41ea1d6df2dd430dd877f9baa3aa738cba4ec0ef8122665c8825e5784e8256df7ff6d425e683e07fdc1d255d71f711a45cffff0f1e4e190000
020000204f1c85fbbae63935e474ca5fc937db8d24b7247efcc5bc78c189559224f5913410b1e88435dc286dfdca9d2b7816839a9804d03d53bbe70944904bb8e91719b4c517a45cffff0f1e0d0a0000
0200002025bcc7d4e43cda84433845c7785ef4685f1bc9fa17acb8bada9489d58d3c9a96302e1aa74c92472d170485e7a6552058b54b442dace52df54bcf5e051f45df480d1aa45cffff0f1ee2190000
02000020401bd4ff7833675208710618a3e6aad0af8b9e73aee72a52d8a475e24ff77f938a5c00b83b89f450250fc269ce2cba0632cacd5bc5c0ae666832d8a21e1c0e970f1ba45cffff0f1e66230000
02000020cf399a3b034f9fd4295a490ded341e9abf4e4670c4cd41e66852a4e10cb9ec4d0ab7fe27eb474328f7836de697d581e5e8a97c75e9ea81ec980c36053c7cf198ff1ba45cffff0f1eeb0e0000
02000020de14372ed6589d2b6cedad70e261199b85d88c712c72a2af699c5eeff05a37ddd03ee19ed4e755a60d4aa09e67f574a4f2cb6190c11ecdd36601d16c895819e8451ea45cffff0f1ec0250000
02000020dd5cd18fb0ce4a4dd2d1996d21506df4ec663186fb78e1bf2a22a5e3091bbfd8801eb9d7f67199b610e6efa5368e6dade6c731a0317dc3d7d66ba79fd1c993c1b21fa45cffff0f1ed2000000
02000020777099026a060a7b8d6ab0da8e5ea5f2bcdb093dc213921ca908dc92ac83f2615e0fd21f5172024ebc29d7b9beb5a36b6bfcff62a2ee9540a0de8df0fdd34dd97e22a45cffff0f1edc130000
020000209ab46573e2f994cbbf59a365f88548c180dd611553bb0fcd10ade9481a15125018f5a58a7c67563a6c7bae55dc77675ef2d2844ff65560b777a7e1bf0d1ebb23de24a45cffff0f1e1a060000
0200002054c8c73c20e36e85ad6dd876cb2c805a27258db999ee9670148b526f5987c847dbaf6a1978996d4fa588ec598d223f944a359f59bc456a049c3ab47402294770dc25a45cffff0f1e8f110000
020000206d7f904e95d286772984329c72908fe7892f35ee8030b2faaccfc8a37b180f0ee9d5674208107a2d003bf3392947ac1e20c77c687377b20ff4ed2369414a4f806329a45cffff0f1ede1c0000
020000201ac7b871bf5f9c177670e699dfd19175f24aa38254a5507622d2a9b510f49d8060bb3a8b966c108c8be234afd768023ca9c7fc1cda3c851e877366642cfb5a3c072da45cffff0f1e02190000
02000020ec57bafc7b0d2ae732425dcb58d27232f67e4b9e6335189c7b9e5eb2f416562e46e1ab463eca46d9755676c2816dd93161697db3257ce70a218afeb133e070600b2ea45cffff0f1e50240000
02000020b7a15b8a61a00a365d2a8d3700317cbe8d2856c0d7be20cc67260afd69b49aacf546fd3b6ac8f3c6f5d162cbd6f9048d37d7619bb583ae001619dda3b86f0586102ea45cffff0f1e71130000
02000020cd3564926511451ae910d101df87200d2a2761785d01a2fce2151f6ae701bad191653f8b1a6e2893a78618e2aee36d6b07a4a6d112b271c58f01422769d4269f4d33a45cffff0f1eb8030000
02000020fab3da6541b91986a4ffd5b2aa129ff398b768247f9770b8d6cd5c445dfc9b6cb5c267650b1fbaa355e56950dceaf98d97fe9734f5d260c78c341ce7f265edcf7933a45cffff0f1eb0060000
0200002026336009f8f8a0d630e96cda6256e91a777ca593dd96455a3e901db6616e75d504f1fab780031e55de21a32d25b2b4ad7f7bf51c1925b99160891458437191706b34a45cffff0f1e89090000
02000020263432f1e739e5062673e51aa2c0aa64a0b6928ffc77af0c191406c4fef03b622dc917b02bec0e0d934b9d0136186be9342221b60612f001a64862655a1d78f0c036a45cffff0f1e1a080000
02000020e6eaafd8a7226bd280ccebd08b60477eb368fb104895fb4e61c50b82a8ef288fcbafd538dba69959b4ee8a8204b33e85aeb5fdf7bded41ec826af442e6f975b40b37a45cffff0f1e75170000
02000020c025997d2ae6199144a2035471f069e7bb59a843e9ef2295893548293c1d0b722e810c139ba9910041098254ce626060de3875d370875df9871087ac23c120f90840a45cffff0f1ecf130000
020000203671cb0060a28046cee74cdfa5d1c4a30828ff51352a28d107dba84f0006ae5d47c8896f13ed02c0aee764b02669a4904893a21738c3a9efaf6f3bc2a2373a0c1043a45cffff0f1e0d0d0000
02000020404c44e52598c0dcee21e6c4f5b77fec7c13881290ec75636861789c6e512a42607d1252cf7bd67ea4b84e0222bcd82bf03c15e40570342495ec120804d3b13b1a50a45cffff0f1e4d220000
020000206c5c49da3851d9e7411294c61bb6aece6e8722181d352fc5ac9430b76953c99560a97dea66deb07b459d2b1b46e226c78a17707dd1d2d2600ab41b406152f6ed4f52a45cffff0f1e3f0c0000
020000200c85439cdf0928bde7c3e8ac625a6021f38b682c34e24fb1c9b268c077fd6436c5c8f8c14d54cce5f7a80a035a611a3aac2d8e672ab4ec43a3c270abd78de86ec052a45cffff0f1e05000000
0200002042c5a2a474beac0965e3465835ab331e6b7c3fa9e3a17572d53be31a228f7872cddb3a55fc96efbf592feeeded2927b8b32c69780f4d900d08f359177b17b20b5254a45cffff0f1ef0000000
02000020a49db2fd5aac019615628ce1c7a343ed242f632a23c74577e17df9bab0184f209e65421fff504902b5243957dc7f10c637a06c5cec72521b685ced3fb2f6048b1a55a45cffff0f1e3a0a0000
020000209249123a6b3c314f18e0f8f076d65322889fd69a5691446de631da00467be4140ebeb48087f6a813ce52729b0b3db464e5834c64de93dcfcb11c789746ecfb32e957a45cffff0f1e3a1a0000
02000020910832db107717faeee7eed971041d0446e2e36dba5e9a5a05864d248c6cdb6dd34441b0ceb559f021f79c5d48cddcc2b54ea7e184e10c782855ac4c7a19db3d045ca45cffff0f1e22150000
02000020c36eca404664d7f0c2f22ca194fc6d4dd30bc1553cec8864615661b11ec6fddf4ba5e5fd08eb8554f233b8dbb86f44763141c5ae532695680262361cba68f86d0d61a45cffff0f1ea50f0000
020000209b794290aba1b4385eaed2314a6ced04943bed81ea90b6829c57542ad620a222ac54234e4af64349317da4a7d152f30b7def983b54e6a6722b1644e37ea1036d5062a45cffff0f1e81160000
02000020ef5e10ce0f857c91027c902c1d43b30543900254a2b2c1e567a57784ab2726af5fbabbd577aba7756f9233e4e35cfff8477c445cd6d4b6ab892e4e16d39af2fe5d63a45cffff0f1e40190000
02000020aa4fd64b12365964f5890e8ea12a14a563630fef1648ead186785151610b22005c4f08f47a8cde014177555d0885648149d15250c976c731cab36bc4e7ca9800d163a45cffff0f1e6a0f0000
02000020c04947248e6b574ed67acdc57fbbaad7397353f75ea3e8df964ea8394ac1659e3b89a867d93df459d3f5d87798dfca0362bec94f11ffc081b5181741b2fd5e9fd568a45cffff0f1e631a0000
02000020d2cb9a2c525ae30401e98bcbe8009ae828a9b3f11debe421678ac3507bc5392f1902319a42227085e2441e246cb0121a29bfcb51779c247144beb2fda6fee9d94471a45cffff0f1ec1190000
020000205836f788d4b86e36257e6a7f949e0d0f7397d653551452402c7957b18879623ebb9348d19a795df8b51c07bcdd159af3055b6171e608276a24b147aeb72965f07873a45cffff0f1eb1050000
0200002009826ae4e280804fe4f96b4f4c3dd2ea13443aa38666811211d99e21d12150daf258d635a92f087ea0077bddd5c0f52f27b556ed20aeb741b28ab47d3e5e23e5a47ca45cffff0f1eac120000
0200002098c0c7427ab0aac8d36a2cdfc167b36b58cbc50c94aa90d15994cad762dfdeb386b55e6fcf2602f8e37d1c49bd48277ad10bf2cb14848b20f9c9c83f3b4f3c07c87ea45cffff0f1e21200000
02000020c4ee33880592f7918fba5f3395e236e15668d8ca93481db21fa1f13141558948b618a505b0a3c7295d8d28ddef9defabe5fc708a302ebdbf8cfe8c85f00d093a1186a45cffff0f1e16070000
020000201cf6e9fddd2832b978f06da0a38fc49ec7f9273319ed0ce0c6c1809be4349803d2827f16460f56712417e00423cd8c6bb26593e8f9f6aca773cb66a01342f7f0d487a45cffff0f1e65000000
020000209f0e24011e07f5e8a14e7ea848eab0fa85d5c7814f88fef4ba92bf70f10a8aa071408d5b7694ea89d6c2e3ad00520295909ee09f0d6a777457d30a8ce905e964a489a45cffff0f1eb3010000
020000209cb8370b875d2aecfd936c68493127b1ba1e806743ac573ce9c003d12fab3d3be5f9b4cb7c52732c7de544bf59f257a2cd87d3d7f75e7aad94fab6335d3aaa6dfa8aa45cffff0f1e98020000
02000020986ed048fa92c2effce974fb8f7ec03eb310c3763c79719182285d61d272ab76d84e265d925cca01ee69fb20f6a1a6f2cfd544da65c27615755a80c9d4a18178718ba45cffff0f1e2c020000
02000020d5acd92c93f62a2e59770e2f3dd8698153b42063d00bc6f2c05b172d1161063fa1bc607205016b0408abdc68f5ddf926dd97466ee6a5267f489626f835d490f66a8ca45cffff0f1e650c0000
020000206f94b3347fb30b3bfb96c67d39a7b8539928063aff823fac4b9b93eb13409259740e6a86eb080677cc7ed2685c72ade99498e010ec2be97d94a4ff9b352aee0ada8da45cffff0f1e79010000
020000200ebaf4b9b5b06e8b0c317fc2881e93310a29f102b2bc20370e025b593ecb9e1cce8bc62c5a1af38fc75456192fc67c662505d1a909011191fff69210268f9ee5e18da45cffff0f1e6a180000
02000020845dc551328dc92c4715d6c02760ec856eeb2b81852fcdae7ac6f6310f1af650ffc82cebee675f3d02e1ba39e5dc39f3175cbb972bd2b9fdb1311f6decf0a736c790a45cffff0f1ecc010000
02000020861d5fd609c98c021a1882b6b22838e3b5457772542bdaf1873fe5b1d75c17ee0d5fc268031a169076100cfd940f77b80ce4158db191a11adb91289890187058e095a45cffff0f1efd030000
02000020f26c4e2a0b7e96233a8a399193b96e4a3f9afdc0f290397d427c8230d24431b4698a8fb632580366dfb4cc715348cd775a35f7d7adc89c52e32a6cb6cbfd143e6798a45cffff0f1ec1230000
020000201ed55806c882681c3ed8b9128932159a22e74717066e1f0cf567d69c848c2432508f0cce216e1b118e339299b580ce964e17b8f667e18056420dc685885fe78b9ba4a45cffff0f1eb6010000
02000020d8c3db46636a6350d892ffe925a086108d88fca30eade35d214a3e9362c8a435df60f85e916ea1bd25dda7091cc439d7ebf48c4c6b083f62842d10831277c67646aba45cffff0f1efa170000
020000204a1958f54fb0da9a418a3aefaf850d8dd20f83f1ee8fbb19aeb584439cd0ea6192b87125e5b7dc9d1e73aa7338d81094e9a3de3c65c9ec9fd5fd9f8ff7662bc82daca45cffff0f1ee3160000
02000020bf7fca15b9b18df5adc14af30f555767d178596a5e57fc5f236d6b44aa7903047d10105ccd883c1fec2759758dacf4daf576869e8d168ca5f3080e00d05fcd34d5ada45cffff0f1ed9110000
02000020bda2196c5e49ad1b14d53d4f47be23c62581a3589da318243c1674fb7b0b41a0c71acee74946b3d347d94933effd75da4f59b3eeadb3c1ce4b9d68567bc4007d38afa45cffff0f1efe070000
02000020bc5fbc9f153c6c9c9ad564e28a9689829a1dd3a6d5fefffa521b0068dd3ee0ec2674504a5d2b6edc3f7fbd2e65dd3570772b1224daabbef1efe7517ca65ebb8c4ab0a45cffff0f1e4d0b0000
02000020f968b0dc93f7c9984d110e16bd2dfb1363d3ac80c73e41561408e1abb07261d39cdf9c29e2728a9fff95ffcc10937ffb3409782524aa3cce17abd05d6e2870f955b3a45cffff0f1e16050000
020000201fba3e2550e4f8013c7c3330afd37af3a301efe17c56375d9a6c95aa23c2e73fe7f83320f3d3f3cec50c48b20fc3937dbd95dc96ba83502fa97d2b3ae436eb7671b4a45cffff0f1e3e1d0000
02000020f88543fbdaafd20bb2454f8c9f04cb1cf21342c350f59973b93bab8ebb591e5b7297859d4fa0ae1aff7a173b30394ce35dd129c2b0d7f17ca35bfa89b118d9c4c7baa45cffff0f1e371c0000
020000204911120abe07c4461b0d0e7c450442c6b536559875f64eaca1642a82a30f4098451a36c75be749836b81429859efd412edf298c93e9af24db1f34a6e07d79bf1ecbaa45cffff0f1ec10e0000
02000020838051113229d1ebd9f3f21e3b4754809051a0a8acd4215e6ffdc7401fd97c5aa1db13e8719aa63ba13f8dc100372c75d4ba3d4286648e82335b50b41918d8e14fbda45cffff0f1e95070000
0200002009c3ac009d9b59cc5916cdf7e7889a395e310c0985373c748e29f76ff6093c0ac5c5cd368bbb04de6607462d3846e3098cc0a672f9924288c62d9fa047fb687adebda45cffff0f1e6f1d0000
02000020065582f26a22a1de1885327617315e7fab4d9b6471afe21947b2685d7e83309b68f1694b991692f09f1f934eb1d903094a3663f4233532f066edd87de7d37ba4c0c2a45cffff0f1efc250000
02000020e81e5f3bef069e51f39e8db95d9dbc8c40f7f747fc0044780e76996e6843e168d44bcda3de3834ee1fc353d474929334fa37ca9f69dc7bb43a2d61cbdd40b367e6c4a45cffff0f1ebc220000
02000020d102e8a324dae54865bae9f96d226e3fb68d3befe85ba0cfcf6f72d5805aa122aaf30e8c7acfc13197303d57fb7c399d4cec7b2897b756817071b0fe5b389c6a6acaa45cffff0f1e39130000
02000020cbf511509a6c61aca51b72e128ec52321d640f544be107d865671a069939465b6c7f6b5521d82837b9296e345ec518fca901db617b1d6e55be6a1f06132331bcf4cca45cffff0f1e740e0000
02000020c56f14481fdb59633e61a74fd558b8a0f368f2de0187faade1b2c4657b46ee11a3481495fa5522503117a31fbeb5f1aab1c2406bd049c39a233ab6bcfbb6d07db1cea45cffff0f1ef9010000
02000020cb0378bd53ee32deeed8d39918181be8edd4163c48697e6c69b184c27ef761d78c771e37b1e19723e717f5a65b2aa38af738765a1638479839ce4f83f3757d566acfa45cffff0f1e3b180000
02000020442f495dba318eccabdeb3cad61eafa64f0490bd4bca49aa1b885d5e6676717d6bba7a3d2766a83995b31e373232e9815daf7714fad943497aa9ec238149ce2c8ed1a45cffff0f1ecf150000
020000203d4f41d3dcd02386b950ca494aef0c7eed99de2399ca59aab47b98ac59ca82298c1545b449f2ff742c9da7dbd51882493cbd34eb877064c4a67fcebbf657ef8d87dfa45cffff0f1ea51b0000
020000209af00482e73b0a6104cba681a5163a40d5db3d9b4296a7b3f3621bbae980df213a91adb61ae7eeafe931ece9512da9280f8230c43cbb93c641ba0327f8951966f9e5a45cffff0f1e8b080000
02000020ae848d8886e78a68903ae7019f270562408044266b50274be11ad4d51d62e8fb1745cd36fcb2dc8d3c4b7afbb96627d137e1ae3fdeb92a3142c4b8e0186c0c6c87e7a45cffff0f1e3f160000
02000020d04fbc5e5213639f8db2eec8ad87f09cdf66110767fef9568138e5f7923988c006e88b88f845be0f21ff5d57a86668eb23ec8f0a7803b6bf96b302b0f078511b04eba45cffff0f1e8c240000
0200002076fe35f105cdb5fc30a164c948d921509f90474790bfa84ba7ae404c441b77e6d8a8b3302bea29558d1e25dd7801d112cc3d23cae2f8cd67179f8ed719cd4ad993eba45cffff0f1e3f110000
020000207a063a83eac1f554128bf415de59f1a1c8e537dc60b2dd2c9e98f3d4c0cf9f287e7a9bbd576916a1aed7ef14f77c9f17c8b6f0da44568101fbbd81d9008be67721f0a45cffff0f1ecf040000
02000020abcdb4c7d36904f56146ae0dd104fcf667a8c1313a731aa46f30ea3e3555ccfd8d27b2f25c1d3e1fd8514bb66fe438418c09be8464b9b5279bfd670a29d20cd2a9f0a45cffff0f1e7f0a0000
0200002090ba7dcb3aa679885e82a5ad2725e2185f7582a71a385b34b4fdbcc02e0e9f50455f2c0e5bb2ce9b2cec2ae6fe3bc26ba5b84975137718aeb1c58418f5ce88254bf5a45cffff0f1ee31b0000
02000020d0289e62b579ee07327d80f252964e2f5c3c9831dabebe8ab3f1fb9283d299c407cca0e82b44163bb2d9cc9d354ac0a70d2f866a9e2540c8d5d3664909df51ceeef6a45cffff0f1e9b110000
0200002094516bb159eda6b2a6e8e1d112081fd0a16b616ee5419192292cd6bc54e8df770719b264c3794d80bb5d5e6b635330f2f4411fba236446eada5ce9753cdd169244f8a45cffff0f1e44050000
02000020b71b0d46c3a7aed397c29493b012642497db618599049b2f2724fbeae8cf04b0d61aec5a863be0ccef132a151c7bf90a23a352ab93075f1b2359403f7017de7d5ef8a45cffff0f1e56090000
0200002076aeb545b49f44a5bf11579d8c6c4665fd6e687f87028447a1d68d49699f6f151fbd753cfa68fae9de113651322a8ef8542e7c89373992aebbb80d81baa961ededf8a45cffff0f1e6c070000
020000206677f3bce9d9e799a14d49c2ec49809047e953c840a67e12ef7c87ae15aa3a953ad10b3979ffef9e00c2aa38a16d080811874be3d59d617b9ebbf5983586a9129bf9a45cffff0f1eb5090000
02000020fc63b6fc233fe86bb63c827ea3a2fe9e41c1eb72695bca4111f40633f8ea63eb1f054127df65fea8ea55370e238fe983558625a7988bf1672292187b52262b7e71faa45cffff0f1e9b200000
0200002026f55bc7a26734cd3e491e8df8e2886d77780e2804683d1167679e1b138cefabd1409435099802ea580ffd5bf3c4cfbd5ee9382162e2d1cdfe3d089f35774f55a8faa45cffff0f1ed1000000
02000020f1596e029de521795ac3a199ed98883d577f212c89f27a2bfcd6dd7e6eb692571e8c3a60c74d4d710bdbeab2bf52fc3a888267f6e0aa6587b5aeaa404c49acaa9201a55cffff0f1efc190000
0200002006d3943b992b5255626f4ad24569dcf120d6e7f097997881f4ae7302826194572a6101860f7153ebf498d3cc1027b584279e2dfd703ec2c5f4015b97eb50e559ce01a55cffff0f1e5e190000
0200002000fb09ad3bae2df3b6a8dc1ef47b1f751c1f102d26e14cee579b8fb3fadc7509411037246e9ad0dc87c6aa4baed45a8fa6e9b9e5ab690f30bbf9d665a4ee43dd0b04a55cffff0f1e6a000000
02000020d41b9ae94fa59dbd26b4de65597b391c0dcb05cd3e0f8db15a2299d3d0f6d0e195f7d7e1b1897e57c8c8fe731651f8e0d47109b56e16a5b1307442ccd20a74354304a55cffff0f1e8d170000
02060020b3341b3128d6445550b8307789bff428a42f167ba93f61130069abc7286d629ae613b3df33540fd7350db07e2a3589537f01d4cae52786591a281e3282b803434904a55cffff0f1ea61a0000
0202002044a8484d90898e59dbd4200ee56ebbc7b4459038c5933046360ea4225dbb31876e66bc43ba814a6a248d8103a761dca62d8b422ac4fac5a30d9d1b234e0d47c25304a55cffff0f1e8ce60000
02040020b5e9a0cdf250da55f8e8695197c01a4c43859b25a253f520d60cea07c9050000930308eb00b802f895d55f3ad44643cf8c0ac23640c2da0ef0d11e816a8ebe645504a55cffff0f1ecba40000
020200205ac2ad87d475303cabafefd9f68d1a5936ca22f0ed64d5b1742785a8c9e1bf53e2dce3ef3be0e3d64c455bb21d33a4c330de9fe9f479569761ae291a69745f605e04a55cffff0f1e47560000
020200206d4cd1c3457ec58237d1e3e769e51263348b99d74cabfc8b520ab8da81030000539344c6c4633f5979d70b0502737fa381f9172e750859254be0320acdd3e4b16a04a55cffff0f1efa0c0000
020200203a42584cc3a835c1610ba2aeda50d1fe743fed8cf513fd0d20fec09f08060000edfa0ec3fb4f2832b5690ec06ddb0b4059b0ea79c64d7641377002059fea73087604a55cffff0f1e82210000
020200206cd0d7d4a853f60abb12a0f4e1ea2c349b13dfd3cccb0d973652e14ba60900006b15fb66c9fe76c40181d0ef236ef52e2e93859bad642c8108e09c35e05a20778204a55cffff0f1e376e0000
02060020959b03d23b9b02489188379db3934adaebae00b7991833f497adc85b67070000fa2c13e78b03c045fe7c527f4f041fd2ea8a296bfeaca84e2d77e240f3fcf3429004a55cffff0f1e19750000
020200200a1a382f6311d9405e0d10146bfac1f5e5228df6e0ee75bfbf10d91030c9f4412ae286e30f4b82d1f159f4554de8de503129b531cf6a497e72aecc9c000b8ad09a04a55cffff0f1e1cdb0000
02060020e5176b46db6ed63398bfc2f528a80ac5666b01f9cbfa47eca5b9d057e80d000072b6b756dccda6cf36c746592250aadbc57e9fe58e99e22a345ad7f75fdcf20b9b04a55cffff0f1e26200000
02020020b1454e2aad73bffb3be84dc8d511cb34261c2938e71b9eb8930b331f1d68f966f44b307ec1c87cfb5056d8ce4a429bcccdd0935cc70153f62383562e06a75fa8bf04a55cffff0f1e937a0000
02060020b3d98fbb1e2974c1764b9098ad4ede534d58c498b15b1ba7efb2e566d800000084d75e78f947525baf3c756282faaa9c75609b37e54f05acce4899a41fe60df2bf04a55cffff0f1e31870000
02020020dd0c0e7177e39a9827d4ee25036b131e7dc0be3095245f39a2240f907215b0d591b4b3dd16bc437d2af4e68513c0578d2e42e43cb1cf49f9921f0d5e63af402bca04a55cffff0f1e62ac0000
020600205b494c1283e683f5ff2acd16799f8fad226247715b41d87eac0f0378e2070000707a62aced33f9d9f3ab451715f12417c9f23afc359a608d8faf2021b0181f05cb04a55cffff0f1ed85e0000
02040020074c158a2e1f4278f50c6119fc90226638da39a9d4b5850b824b370afb03f2dfcdfcd21d0389df184dc6cc888bff3e16af9d9b20ef2ba894f14248710ec52c5acd04a55cffff0f1e64740000
020200207e04d62086efcd8f3594b315b59416397e6955eb3be4a3b5e2b15e7c44ee19b69cf9855f285ea37a10f0d1d092909d5417dfb616cf35420ddbb903ab22fd580bd504a55cffff0f1e10e10000
020200208d3c5665682549cafaf72cc75544d7b1ebed641670d48fa4a0485a6748070000b4d37239031b0e1e781dbb79a1d14977a2123b13e929deec182851b781d11a91e104a55cffff0f1e82d00000
020200203d4565121ad9339ac5d4921d07c3e89ac9ea1d40d8a82d4db10900d2fe050000aa1c85db5df120439a4177826fd373a1b173a09e135a2d523cbac79e3c637af3ed04a55ccccc0c1e5f550000
02060020d83f098881663c25deea94aedda3e96b76989799dbd1ab4c9097547fc70900009403b13871e1f742488468fba409a324f47a2ccba633f22203c6551aff2ba4aaee04a55cffff0f1ef6720000
02060020ab22c9d70febf9339072901783bc73e58bfc244c75deb773d8f45aa13939b885ce96b65197a9e7fa8cb9650fb2dbc0112c33eb9776ee4012e060235567eb1c81fa04a55cffff0f1ea87b0000
020400207543bc70e7024d235b1ac01ff9c9b995fa2afec739e9dd4ee6c7bca760b0a7e79ad95755fbc3e357e77f930d86237174ba1114249924961bb6313fb7213f5b37fb04a55cffff0f1e723c0000
020200207248c14c9d5ca8e7209961f102a751d20409f0e370b7f265db92961aa2e04614f37ab63ea8942f11defbe7c154df7453d9daa317fb0d042672e9b2f1fce9b1150405a55c703d0a1ef7f80000
0206002037ec6c03f114b1237f5a1d2147befa496616cf394772436ba87122d384050000dd0e6e107d587ed4f28aaaead3b4dc646f691c3a135d8fcb32e534c8769ba94a0505a55cffff0f1eac5e0000
02060020e5b3ef10252672eadd03d63a1715011979a3c9809bd52b2a5f0760990b26b8f0c7868accca19b161476e224843d95afe42f7fab5a2786ba50291ba85ee41fbf81e05a55cffff0f1e89b30000
02080020175785d201b323c50908d35c4a0c112256c3cd8785c60fcd466ed3987b5e6d6db0af5895513dddee1437dc9eb40c102ea84ebef9909636941b837a14abe80bab3b05a55cffff0f1e41350000
0206002040506ec87628e47a25f8de4e153b8d4ed0a6e9ab75b84a0cdd7938c88ec177ba81c80104918b7a23ce21bf27d0da36c21a011d35a80a24ee153b2390fd3d6a984305a55cffff0f1e0c510000
02020020d70ba22da9d5fe84dfcba790f23873afa4a1af7cc7d771ad13cd16609c43be6793af70d7c7ebb55abba4a2d1789a847ba4b69a832af030648a4b5a21dd2b518b5b05a55c2631081e34c20000
02060020be475345ea5c2e3b7d02af907c03adefe40c2d0ea2684df3a348e7198a0700008634a61ef25e6c3c391ce3b39f4b2444a4e82d740487f06f5ca67f8770bebe127505a55ccccc0c1eef520000
020600209264366aaaa8d023fdcce0236508642fb5b565e03e0b3404f7dd9f4c4376bea3b0308e7200c75bb22f1273d2f43fc98bf5902bfec595f9d3239d3ccec1b6f4bc8205a55c703d0a1eca470000
0208002018b0dd68bc2f9fbe9927b6d05d07272911c21c7dd3f0f20cbd7134a56a3775bf5f4ace8e044870426d013d2ee8b051d7a8d593af489a9d76469296cf95ff957c8305a55cffff0f1e583c0000
0206002040c1dd726ff008806e259e74777056bfe70ee112482c96823a601f5f2e662dec38370f2acad43eb77a08960012a1dee87c3f365c5746437a9f335d6adafb7d498c05a55c2631081ece520000
020600206edf52f30aea28c7a2c5628a34695c4e10bede24d193fc7b400f808a73f6ee5f8d094ac219c466af586e6ecc9a412a800df90c25f8139a272a963f63da7be18e9905a55cb88d061e0dad0000
02040020310002646465c14a5215885bc0c4a1faa35bc427eb756dbb2dcf0cd1ee9bde21a66ed79a2827d0f4de4661ed9feaa735724959ff30eba3744cd8790eb1cc1b9e9905a55cffff0f1e7c400000
02040020e14f30260988ed1c39b24eef8f06332a3458c858ba902c83b1ae248839bce39f7d335a7e3bbb3bcfa57e7b1cf98f1ba4e4b60394cbd5a9e7286ca79043f5f9e2a605a55cffff0f1ecdc70000
02020020336ee397dd7c1b22d728d1434f60798d3c7ac7c42a5eac812da6e2199f106d52f0ebd252f80c1bb80c2ef30d5f0e20bf882dcd77fb96cb03c7b1cce2abd51d64b005a55cb88d061ea6f00000
02020020bace5dc8f7c0366bac9a6f54a806a243f2b62ce5bc6d5e4d752528955200000061e0f3fe50ad8fd8fa4468c0d53010f86e4af0e3ab40a7734071b9ee19ae5e66bc05a55c2c3e051e785e0000
02080020de9182ef34a2797e4b9e3d7b06db0f918129875f919db4f4d8e2b573240400009883f2a94792600ce81c87fcb7b956f64aecb282ae7cab74dc6c792129a166d9cc05a55cffff0f1ebb980000
02040020e5ac68c0088d07198319cf548e47e4af4b7cbbd04367169e96fa470ee371ddb127b2dd323d25245a6d8b0b720fcf1e5b2502577d18bb09aa6b4d9fbc192ed9eee305a55cffff0f1ed8a40000
02060020adc2b4e0ec5a58fcdb4ee8934dbf0157523cc7d6f9d804c7d5bc50bab2de6b969f7855417ebbe8ac63541d665615a7d1c215f546b3f4ac6d2bd9355d13f3dab80806a55c2c3e051e2ac90000
0202002035af7981affda13f93ba3d5e39849a071098b134834b3f9359bcecfffe12984604837002c0da186ad216465e2032ca2e1261a4bf8e640798b6f966f6178842133a06a55cbc31041e71b90000
02060020f1bf6f6cf3d2ff0ee966d85dab6939a8cc1c339723e089c9b2b91d94de01000054f6f6ed2d2dad23fa7904790140c27da3aa352bda90f6b203c5786b2bf7ef454806a55cbc31041ecc2f0000
02040020510a7c9e4744a336f356213ef219cc31531f929d16e549c9be1c272e7700b288ff8cb2283f7242a4b1e293ce706676838289541d56e7a8b7751a62f8b75e152c5506a55cffff0f1eb6e40000
020200204e1566b963dcbb9b7f48ddeeddb5cd80888bd2a658683ae353a0f595bd29d75427011f0b80081047bbd719ddf5c1c5dc317559aab43a44830ec76c45b62350445e06a55cfc5a031e902c0000
0204002046eea19d3619d5f4a371b803660e55d15a8a25340e195b8bcf5d4b3dc20100005cacc1c742e4190b340e6fe6cd2e38b56ab83131347c0dcced8fba8b13df955b6e06a55cffff0f1e5ba50000
02080020cfc7197408fd9a4d1947b90a92d71cf2b3cc2514b03330b25437e5418cd809573cff98a2850105d2e89605fc46ff226c2f376a5be85709d7834b74e07fa4d0397b06a55cffff0f1e9d220000
02040020cf1c1cea6652b60fd34d50ec236158d317f637b8b1708407f0f109786abbe49808031fa39cbc5ddfef4107053b0e178b14458e316262c1ff3be0872149e272528506a55cffff0f1e371d0000
020200209b8a03f2bb8930bd665a8bf52247e7a5caf4a61a705735e54514ebbdb597f14ec3afaf3575bee30e1aa9a9377a9d52c9c1e6476d517ecf6b2e3868e969c0af57ce06a55c30af021e61a90000
020400208f1ce031d567a93b4eebb9833fe5a61997f24409f509385ecc9b7bd9230000008c03ef044963adde7c35d62aa9d301f7f890fa3f686954649dfe08b6c3f201c0d106a55cffff0f1e55100000
02080020ae5b1f93b942754831950afc684f44f0505def2daa91f0bde73a3dc76377be5bcd73bd5c46d7b37e2a15a5551b692c24587ef1c6872f3b5153fce758661a1c36de06a55cffff0f1ef1e00000
02060020dcf09d6d8ffc8d0916594afc1a30e193b8a8228a10b8e05941a327864abbb8c2e7bc0721529f2f1ee893b25fe8a650cf87cfa51ee0494218365db4b783dc912df506a55cfc5a031ede3c0000
02040020a46fe723faa12ea5fe0e09d7af9e52e6a374dfedbe6c2820330d72270f5888fd54ce403a30ef7b906858992e5dc3ce528f6903e21559d9c57a31ba5ad6aa9de04207a55ccccc0c1ee9720000
020200205d8b4c8a01000067f562195fe046c1d1a569016b6d75756f05b5fb92f4240eab8177038b088cbd2f921bf1661015a0d7793c4912317cd156994374d97accb8ef4c07a55cc025021ecc350000
02040020624e108b888cde8f22240e0c04e92f356b98021711632fe4f77e8371c500000045a3ead4912431a3d3846d06716839320504caa9c4373a9fa7fa1c9d49fc14404f07a55c703d0a1edfb40000
02040020ea143a312aa95d6d4c95c50fae3c93e5dacc58e1fba812c27f479449db4bb4774c5bf58cb0ea93c4cb680765aa2498b3f017966cb939aa4a18652b662f2d5f366707a55c2631081e9e2a0000
02060020bbd8bdd61b0fd4be1a79b34ee9f3bb4300e9da0137581670f5f90726682e11b36698148927828087db40af1d333dc1850a816bee4d1ff746e1ee43fe60a27d4e7f07a55c30af021e14de0000
020200201af5fd5df861e085e69490715c59e5f5e7adb6dbebc22cbdcc3d092e5b021b1beeafd2811fb43de96e81aa5bb6dadb785290ff47de70a561d959a068ea814eec9707a55cccb7011eeb770000
02080020c672e76d43c3c7f27a74ea1065f11b1633ce8640f09b5fc258a4eb10560100003ba7bb0501a078df9266b02ba23a9570340eff8971f7d15e8e09dc05dbe6b902b507a55cffff0f1ef6350000
020400201b67881bd5c7198ad226e58fddc6819363fd9f8bf9b390044f334105810ee37d450cb5bc5fe05f72fe80d06b5054db5f115ecd784333327a700199e6ceac37dad907a55cb88d061e11a90000
0202002002df24933e59fd1fa72c66b1fb9aea12aca6399d409f8174b24b6e8d578947872b1814ea450ee301a67181e5d9d0374ad704403bf6ffc312209ce66dc1a781e03c08a55cd65f011e8e580000
020600203afbd40db2c72f045855ede7839a57904bbb298675e1848fa4b9fb70f4000000ae4d011591368fb524bd89fb350f8cc61b739c158577071802e03532fc63a5559808a55cc025021e3a0c0000
0208002040b0e91eff5bf9d7d706c0e34b371f704dcfd907522803171a804c707414d8048e5507b643f43febd3b13c2d0295c85425cb2e2dfa1d5c8e87edb44b86c0c89c0109a55cffff0f1edc700000
020800202d2ff119fbc7c3c6da8897b84f66f4dcd0ee8a03143463d231500cc7a340842051e22c7471a97b5ccc0466c53685030849bbc3d572212dbfdc42a177d6c0e9043309a55cffff0f1eb0fa0000
02040020536bf674bdca15d2323ae4ba9ceca4779fcca6a9251bde8c7391eb4aaf0c454ba7827d9c52971c826db186909bea5b8c605d5e78230ea8b0e2e3003d1f6940685809a55c2c3e051e26780000
020800209ad81fd9b3c62659c5e78c18561777c572d5a3d0917f2b49a8dc636bd4252157e49aad6cfa7e15bb96788c8e57055de68aa3845916533e7a8b25539dc1c93a328009a55cffff0f1efc560000
02080020d2e5dea1b8d8810fc093ccf4d6017537c03e130a469e73fe920b2e8199126c96887a6e27cf721a86d6ebc6b2b6ccff416381790641d472a7200be63dc0b5136b8d09a55cffff0f1e305a0000
0202002042950899458631a557a3a14d571ba8bc71ddd1f926a6cb42905e892695a85639067af4b6d6e2f3383d364b6912c91d72d1cb1ab1cc58d980e6ad75fca24b17919409a55c7819011ebc610000
02000020d0eeaea1d4320e09c915198624114d4d671175b976feca072f0b6c5243000000bc993943df3478ca07c879b0aa9eb38e93086fa37aad9bbf4df963a4a382f4929a09a55cffff0f1e0c050000
02040020a7fc55e900a28873f9d0dc758f3120bb859d99f85196e5448812a9f15c3fa2a2b69b360e0ab08e28adafab3fa55a2fc3cb1223f1b585ea9ea298eef85c003f4dc509a55cbc31041e20670000
02020020160a4e24c7d365b833af5b40e5179537d35b7d3d3a1cfa074794fa8ba8f23141d42c8a356f77f73448598a7862998af4b553c1b37f7c2a28822a3b270f8da5e8ce09a55c2ce1001ec6280000
02020020789fd1c43f789cbb8683198a765a231948cf9d9b173bcd7d17f1fa213400000096681246c901d523992559fa8b6ff3564eedac70254f1cee10079b8d37fb86ebda09a55c23b4001e28a10000
02060020436aa5d2755c50c616f40e483a334d935d014f020bc3dd390bd0df496d0000001a7dd91972e73e6ec11a53a5ed3a4401c5bd3a38d0ce4e8e8e395eb50f4962f9f409a55cccb7011e623e0000
02060020e670957af16b3b9e1e84c2a3b59386afc6bb18cdcedf83dc21331637e11221b868c6dc38617ad72ab61bd5e9d963fd10e35e1c0888c1e44f7105ab1bff746fde010aa55cd65f011eed430000
02080020935bd2b77da63480e2f481abc6b13c0063803108a30bf0c948bca05192fe19d34e6029b121b4f6fb3abacba9a46ecd83c33cbaa7ad857942b0db8570bb606d232a0aa55ccccc0c1ee3210000
02060020635cb35a91c78fdf29d7cced9f063c90d9d008e60f78a7644c3f3400d08ba2bcb2b9a11f4c59fb78b9937a83ba52d33f61b0f805ab776d51113cb612ee8ab2a9a80aa55cffff0f1e5a110000
0206002098ac2dd4f32daf5579438da681e871a4954186a8c2bd30cf1d738a20615b1e509e7226d8cb9c3134b084ccf3d87ffe1c8789597af512f6219c211bcfec92ee83db0aa55c7819011ebe8f0000
02000020e8838c43c96167c3d810f66ad017691fdf37752580a19963b0a9b8fdd2502055d178a947f8bdc9b6d8e875aee96cce102ab57577b19adae0b6e1a6702b4e732dec0aa55cffff0f1e96180000
0208002056fa16f4bc0c99cdbe114d0a775ac1169d6d7de48a5cfb7811ee38f82b470bfb19a6520413ce89fa76551e283bc6702df5449b89017530517719a5358861ed0b0e0ba55c703d0a1eb71c0000
02060020bcbe4f1196fe7f6bede97405069ddc33030132aa93d0c287047407aad55ec23b5373d84a97337c596401030a27f681ca0eea77fb3ec5a05e2cb6fa33eee65c9d720ba55c02ff001eb2f00000
02060020a3269360ab215b0661d1a356ac7be463202c9b57d82caa59782e665dec7e4027978761fe5a16353271fed598b5e20d576daaae37f05dbc5141d5017d00499a67cc0ba55c7ffe001e8fed0000
02020020c4345fce9f3cc5cfff414434bb5124c6c4bc4d8dbb34fba7b9094a0ca82ecd109251e2cce4ab0fca22be9a32c369516353c9b5eeb22c52a12d90e5df490b0cc34b0ca55cffff0f1ef76f0000
02040020feeb74927c065c22520eb1e16dc850cf2a9f571befc5ee92dd320ea3aa05000084315661705491dae91cad0029c5b5e593e4ce3f32a69cacb6180d34fffec62b740ca55cfc5a031e38430000
02080020fe4b825aefd55c2b00dae933083320064fcdc761a756a4b5523ed9753a310a6840e3dcd4a7fbf38fa82334b5b1abf709051c782af0f5b44e348f84d43b1912a1810ca55c4d30091e55840000
02040020bc668f1905103ad4bf8b88c7b1a2b2f7efa9cb29a00b256e54dd84f15a5cef1718b70dcae09bdaa9ba51f1f0b047facb93a70fa1623d183dfe5251f4e43abbf5b20ca55c0973031ee56b0000
02060020aede77e088e225891c2a0c8aa3c10298287645b4ed62e476d40d16a6f29db86b6ffa4e97a424fd73eca5ce5b5d96026c241f9cb72c50d6287cdfddb679ac52dad60ca55c6cfa001ee4640000
02060020cf540022ea8abfeb99fbe66783da1518ccc6db3d434dc2b139c7f3f0fbf721c730a0be3f989dc1e5ab62edece50cf854d7b230f6a8e2e2823ca70e464906cd9ef00ca55c2318011e94fe0000
0200002025f0da2f3f3a8905436dc44efe94992fd97e78818e3ad890de0e9aaabdc119036bcb836ba8abdce7c57eb4fc1e72e615ffaf78072412316078bd14525d0de1c2280da55cffff0f1eb6160000
020600206c1304205ae7d62dab8ae0a1bcda3bbc4de7c5ca1a36b9e4a76d4fb4c9c45d1aa590eb010d196e152e64895f5b7dc7f789a183fc615050d43dd69aa6aec968b02d0da55ced1d011e47480000
020200204ac780d5c67d7ba5cd6c0cda78f5dfcc47fee1bd574a24dc8b69cdd96f0c57456e19e3d4688fc3682b45c79ba3d9604ca7e34e98bc7bb677e5c5cdf446a438ea450da55c1c90001e7b060000
02080020e0e56cd042498449e57e9180da5ca03b5e8e19be3f54a023bce20c5d5a0000009a5f603559c9edd15f2fd04d7b0b2c1f1e1af6bb0c9d97446f36d9c879b84240480da55c2a72091e80330000
02060020be66ee2d6a9b35465998169f5d6a9ba2c829df65df09bcb8a427869874d9dbe6ae3d8730913f782cbcf1cabe263c00056a086e3ca95cc07524b75b40ac90919c840da55c2815011e83b50000
02020020929b2798244f88d36fe9a221d7f97db048df89f4d96b9dccdad1ead225d4cf2a5e303be9dc8253d815dca32acea341d9e8fe57c12ce65bab115607f0969544fc030ea55cffff0f1e64950000
02060020cf23751daeeb727863d7833a1dc907507c8041442cba1157a8d5854cf0080000c497884f3ffb6a3fe5befa53c8ccd7246cc84fd59a44a34aaa688aa4c846303b840ea55cffff0f1efd3f0000
02080020628c6f08534e641bd60755ae8c638e2c0527f30003077bfb5ba96d5fd1afc12bc31dc2603cfa6033dc10fc4bf9916f22fb35000d99d65f5d61e818d1a14290d4d50ea55c15570a1e1c240000
0206002037a276cf52e665150efa49d69bc3483326f466070ca9926fb55f6fbb1e374f8544cf342fad9d28965fc1b52fbf4da348d86067650285f2b297449b0488629031f70ea55ccfe8001e04ad0000
0208002060713284d0dc940fc37f0aa719f365414ba35b92c0abc4d6ad8d36b78c6a7d6c9390a7dc899b5f2ff6133883363598f0a6f5b0322f8475446b28675d59be60f8060fa55cda920c1e71220000
0202002018572eedf90517989dcabe50e5275737b9a880f480157cd6dae9a2a119219f486f6a488f8bf084cb151aac9ed4fd4da38250e1ef5f0443d75a6709d2da85e356400fa55c38ad001e743f0000
020200208c8f5a8d01d8930056d5bf0d9d50bb25948d6d67e8d07abcdd6699133400000006b415fe8a4e878ec95db4edd91fa92eea48321b4be587ee24bd8b015d601f84c20fa55cffff0f1e5e650000
020200203bedf1e1255f46ca770c4b4d0e76bbaf91dcb3e95568d5b5b127c326fa0e00004139ed652f8fac743f25c1a99f72f6b5ab579c91a44aae07af0341867f3d5979dc0fa55c81f2001ef1dd0000
02040020f32012da64ab3efdf4eb532637a570129357a76ca1070fa62fe5a46321000000c93e6ea2b2a200cc3de6184d2ba537fa10ebfec5a67683ea5c230a54e0e8358fea0fa55cb8a2031e41750000
02080020fe0036de9999e819a0b25c5941d455eace5c1b8d736fda68f107ca26c7971eba1337090ab7ac263e3b7fce81bcd1be1fe2739d9c393c7f28cc682a21af2cbef02010a55cd5ea0c1e94470000
02040020f59af806a3e82323c07b573edf6a4ffa5ad6e4e7070a0efa4f43e9af9bef064fb81e12894c3489097cbc6997513e5de271b8f762ad11256bba3fd348993d2c4c4510a55c0117051ee2840000
020800201573babe0a745ac7ab58993abb1934530aa5a0a70b18fd8a10f068a6fc9be179ac14feff16ce1bf9048d97ab52d0171a15e2288afcae8494243e55c2b98540cc6d10a55cac440f1ee26e0000
02040020494de1eeb814e83c7cea9374224a53fb649c76c2d198f4c18ced44708cfe8df2cc634dc6f4ac9105d90c8409cea3d8ebe2989bceafd08af29e0720f2d8fcd74f9f10a55c3420071e66220000
02040020207c637cc2dcb6b0fde43d4de985fd45d07ed03db5ae8f127c6c2af07440f527f445c7cfca93d103dfb7dc754166fb7bd6fe0a53c18b3b89fba27a00ae123422ab10a55ce2f9091e9ea00000
020800202381814a019424766ce3d48528b272c01af0edb92fa6529252ba1ecd1f64ee64c1aab17fb3fb19f89da98b5da7427ea83b38c8990c60e61c56e37889a3aab6fcb810a55cffff0f1eccf00000
02040020b05bc33aaf19cb5d29adc295c7557c6c5f4bd6b92097b26fd0bebce313fbcfef0967457118491b3fc4000ea2857d377dce4e991f625de3001006f0af1b55c9f6f710a55c6ff70d1e5f620000
02020020516029f48d74d055d228c802d54ae83d2693968c44c8d0377ca56f00edb070935f4df39fba4a22e141b3a7de68c225cfd3667b8c209665812c8f036a6ec6845f2811a55c8153011e1d3a0000
0202002081a92beabfbf0e4e87cae01d33cb80d5de887c9ccf1b0da1ea4a579100000000a71ec2cba372b1d9e273c1804ee827d3c653b4465321e5ed1c4d6ad86068e3325b11a55c4edb011eab340000
02020020d2cf2e42237c4bf1d979ae70e71c880462adbd67829a380a8d876f408501000071fede729e8b5ff720be16ed20c916cc5ce50fd7caefbd8d68006131ddba2f8e8111a55c6d99021e37dd0000
0204002083ab0737752c2010eff50977261083bd0bd7c7eb364c511e03f40a8c7800000073bdba1372966e6f28c8cd74af1aa8c382d6bf8593e05b419db352f1ad4135fe8211a55cffff0f1e74a50000
02020020f4ada1292452a8be122b1a7335fcf3d3c1a185d72011f0afe1430384ac939822b63689211a85846c2113117eddc6ea9d9087ea0847451d4afd13f2e35857ac568c11a55c98a3031ed9da0000
020800205d71b9c2a9e833147413be389202d74a7c87efc04ffb7fd06a95fdd6f002000036dbb38ab9cbf6a82a944b271c9e8842d92226b29448f1aa4240ee3ba4e6b4b68f11a55cffff0f1e0ced0000
02080020c8e88b3a171222936a7ddafc204b2472cb2dccb38da93baf77c447b7f397d97777f71a56902513000682a837b80e14fdcfce6d8afc98122b6137645d5467326f9b11a55cffff0f1e457f0000
02020020e1ca4c1b5b0dc7817007e11c5a77c489651aed661196800b9d7b7ab7673948af16b73856e10161812d0d817d26aff670c49f44ce6b1ce7e2a6213b08c66053a8a311a55cf7f2041e63630000
02020020c6fcd327735a74c30a346fd5570700f089c74b9047831d32379dc46cfd030000a09db94faf4f42bee25def5c78eb91262386556b495361e02cbee85ab6d41df6af11a55c829d061e735f0000
02080020863805b12540cec456e88d08046df8c7ecfbdddd17080e89b7e173b42f01000064e5c66a016a827e662a9e8e89dc19b6bc8fd0563587b42502c0881dcda44e58b411a55cffff0f1e49430000
020200203c05392e27aabbcb93ab5d17a76a34684149a8f6c20bdf727c3e79ecfcd568b99b5d2c2699db2bfb12125eb70bab714e05ab7698f33b0d2a66fd4edaf91f956dbb11a55ca7d7081ee0bd0000
02080020f32e14e1aed3035486c83cf2d984444e1ce16b0d8557c3eee61d50efcd000000beed333300a4f095987f56f7b6f24b72bd175cc056c5df45cb09371b807abfdbd811a55c83320e1e455c0000
020400200bceff9320ebc8d0b2d67926e4b1e9aa906a1818f38b7cb65fed13bc1b2a08e2c23f35cfae6c26577a1c177af10e0874df081f97b37b2629285ef6ba73f70e34ef11a55cffff0f1e14240000
0204002037eb6d60c827f73dbdc2e30a69c12a389c6bcf8584bbfbd8bf3977e1e4af260848ee2a88ce678d476365fd49ed52aa34fccec9d029e95ea415683a1a0db3929a0912a55cffff0f1eec7b0000
02080020a81c425172ef22d3f943323859d5b6ce7e464ea077275cd419c3b902eea2dd7f84697cf5039eb1727839d902cb44b6908bbc3b25422f0add28df0ae4be5e5b092312a55c9c5b0b1e796c0000
02020020f178a1d447b5d39bbf79a6f8113d852901a1ea86284745ed5fea4509a334da99f4f65c661f37fc830137878081638aa25f4c282c6c55a6a8f6a6c139655b5f2a2a12a55cec12071eff4f0000
0202002053f6b1c3a00da6a4d95ffdbcd767a097587285c44c572b770a49c02898010000b6993604d93f498d780a5322e46a4cca7ed6cc4357bfcbe193a7061629c96cad7712a55cbca8051e393a0000
020400201e932f5a12c860e4912852a56b5a7f84b12489cbef9c5ffa92b6e43a180300008c44308f0279947a39ad3dcd85c5e3efbec5b8c9499f396dc21f7871103a16187912a55c1e3e0f1e4fe50000
02040020a25d77cd23cf265fbdff6d1a5b8ba33afeabfbfc656e092d6785cd6ac94d777e1cef234456abacfed3c401042c3d405429ad0eeb1cd0ee7ab091d855001714628612a55c7c070f1e30bf0000
02020020f4b750ed6ade19b587f2d168b9d1cc8facf4ab14628008eba174ab8d27b0c7d984ee918abb65ce654e4093b31bb9eca603622a49c2158ea7a20177a446315cc69012a55cfc86041e79c80000
020200207a6819f81a77d31b52a7682e80e56d5639ed322e1ea1e148489e4bf7a400000095cf291e70f1aeebf790e5791884e4d1680eb67237f31543da66346ded5a7b1d9c12a55c309f031e634f0000
02020020cc4f1e22b805a1f64ce5c6741669b56a5e5fc6737823f1d961da8c6f3d0000002d9336499822dfef066d8a982885b274d56edc0f7d59b77baca2985f149a7ba6a712a55cc0e5021ef63b0000
020400208e514dbff7fd112a2f96fd3657fdb0cdbb63e3b89e5f190077715d79c401000058673a0d3c99acadcfb13ce2b2ce0ce3c642af9e3af733ab9a210217c5148dfbc312a55cfc050c1ee5e50000
0202002030ad6bc372e110fb46e32aa80b418d957138840810cfbde8a1c1e8d3435396e16476bc1812e75673537e6bce1e6826d5a603c0673eacb80846f6ffbbe986d0570213a55c6651021e688c0000
020400209f7cd4ab7083b556f4df4222e66281d88aa6b4471f8606176d080a99610000009f369a68324c54b31427a31e2982e114bdb14aadada3bfd87865cdf494ddcde01013a55c639e091eac380000
0202002057443ee4f1320a496c7e086f3d13964e43e4c114ab764d5448af0a0b0f8a5c3f61077a425fe086e23d12f7c652823f0d3471ecdd3941e74fa1a1fdd8aca9e4793413a55cb8da011ee6c60000
02020020fd3c38c9e43910a4d8e850174e4f1e67c6262479081e11bbda759762250100008eacb98ea8fc4428e392358cf3953d206a3059326df584639362c837da7633f94013a55cc67b011e076f0000
020400206da3464be0dd5f639ead29aff341f5e56ef826121937fc3499372c666f000000eb12806b5927c99eedd5a432dd384bdd5fea896362c6c6ed84b8b3aa62b8183d5d13a55ce8b1071e51650000
02040020e14198ed09d0c7c1d0346174996d3bbca9815395611d178875cc3843d428a338aef8d7192153e1f1830a3f3984ed9bc3b110c023102f02e3d709fb001322579dc413a55cec27061ea6770000
02040020e5c3f12518858df786fca9ea5be8a13b58a09737126fd773f2100aecb31e2d50880edd62570f4ad57d1557eb41bb05c90105c9912b5fe39355df58b51f3fc980dd13a55cbcec041e468f0000
02040020d06f269abf0ab118270cebcd718b31da0eeca9e56f162cc77650b3c05b99caa70e0d2b2f42ff91e6f3752dc8f0e13c4769dfcd933982075b68f278b64ea3a6dd1d14a55c96f0031ea42f0000
02060020b4aeccee85b9c1b9a2f5db8e2c4d1b28cf9b7065278abbb47b619a501ab1f61a8066e3652e560bf56ff824c148fdff794db2ed4d4865177c38ebea5896dafa824114a55c21c7001e59280000
020200205c0c04bfa86467cb07954deebee0fbda826cc1aee0fbc7fb75b1466b6193cc3b016ff7b4136f73d3f017a31d7e780f64d1e2e7fe00591a3f9d18345f2f88e99d5814a55cd12f011eceda0000
020400204eba8c221246f5b634979cf69a305aabbb4f60b7825547b1d0864a509f000000a34d7bcbccba873a31a0f4834d6e370bae36fb2a702e6760e288f915db23fb828214a55cde26031ec1a40000
02020020c88b56a48506051105c9189b00c1cdb80a06b5c5135b4d345d9b67340b6d8efaed68fd3e8497247d163963d1f9b3594f494d4ad442c6d582fe77a3e4f51d37598b14a55c0df3001ed4d20000
0208002077c0eb09b27a8c887179afeccac67bd6c9d9bb46b274dfdb79a74cfce4000000aff18c033ad336231fb9f10f108283b51b234538551cc67a862fbdb428f70084dc14a55c1616091e0cec0000
0204002071a0b68d1feb92904c2ab4a39c3a9407b24b808c3c24dadb05bac6c69fda87a69991814930ab5a3c6da1780b0aa5770b41f9e2570dfbaf5e895503012355eba81c15a55c7e85021efd570000
02080020bd36b9b69349ce894c911dd13c6bbbfdeeaa507b42e6ddb3215249f4bd9b84d38269da774579192d62216a7555dae75db7263ce5f104649be39a93a4bb405cbb5d15a55cc80c091eca6d0000
020600207074c8b1dc0c259da722677f1e877751d82a12cf15c307c5efc3cc8be6e3bbec0bab00190ee3a399b79647b01017477aa1afeca295a7a691500cb3bef54491dc6615a55cc716011e72080000
02060020b14fc0ca1344ab586c3245ec1ac55dd71a0a14cbd1472c198a5494dabf5d14144ecbec6c03662ba581ca5b5aacb56d49b5e73c0cf62eb6a9225ba3eeb183ac5dcc15a55c4986011e22740000
0206002089b0d647d53480d262394338f863ecd86e397a88419de8f52dfdc74173870b5d0d730180862e805427b9f8c71279f3f947680905d2fbb2cc0e0cc18fd8e0d1122716a55c6622021ecc640000
02020020e0b3971783e7932b407eb76e65aac1351c47199d5f7b1f72fb7c512ea5bda49c3b59f5aa5519d9d50951e162d63f611e9b467f9c69e6d495fd5183d6895bd0683216a55c70c2001efc6f0000
020600201d16b99e76bfb0e144c1f33b8a6571073e2d5f81c3860d7bd7de9e4aa1000000983b5bf464405086a92a02b29dcdd262205530231e024404095e91b1a6cbb830a716a55cf5fc021e0b4c0000
020600200555986e725330df0e8468c3fa79556daf5b8aff0067cdda6d217b4f9b4505a08216052281317effba0caf91c67dc665ab94eeb6562a6188d344cf2efef0f5bbe716a55cf02e041ef41a0000
02060020b478737ae8cf2eb385eda258c7ec55cdb3be4f1ee35a7a8cf0ac04b4ff03503570c4fa8b5dd153abe9f06349de7738f86bedbd4ec3005b12c58089da58b9fd103517a55c50db051ea6080000
020400204ac27465e66a41c2e08640e81529e63cf7262fe4a562198b77dbc9b67838c0dedaa06aaf5f99563bdd81a4bb546d7272b7aba99bb752e23d6ab5d928332208c16917a55c6404021ee0e30000
02060020fd3e78a9c0b5b0ab1de4fdc17c98fbb9b780839fa7f5ab55b55c2b59cf1e5d7ba4b790fd68babb6f4b4dabde096a1048a5937cd4200040e0412da5975f8258ba8e17a55c0933081e859b0000
020600209d27e8a19235c6832d0d42e25b8559f0dc6249cdfae558f45aa69a1f07a56632b0399897591ea649bb298ae8be8470113e3fe8571b6fe69d45df45c04ff7cc309a17a55ca67a0b1ebc4e0000
02060020338c62c1caafeb06638a852a06b70251f0daedd8b5b44f84bfb7a6ef9af1b21163f7f74864ec7b8ec5c74e3e280249b8d605e028b9ac62b77fb5ca2320c9500ace17a55cffff0f1ebc570000
020600209f91b4953f26cbf30cf2f03dcff0eadb7ef14041bfa82a318826a09ef9a0bf0380ce05343080ba5d967c94818ee665dcb278eb2b5330aaaf7cbec95987cbf482da17a55ccccc0c1e34330000
0206002095a0ab8c08f865d27181dbc0031d557ebd7d671116bdab6f0193c58917459dada5b1b879c11a205e662c23e4528c515c2d448e146493e13b1639a43091df6f8e0d18a55c703d0a1e8da30000
0202002067d2a2735984f88bb827dd297c1ffd51a5178022502c43157cf7ce96bb428deb3fdf54c05b6632faf0938da48e1c5bfc7a0f9bafc85785f3ecf5ff26a6ab9c7d1918a55c8c9b001e3b000000
0206002056d18821e5b6771af74965fe1d0221a1021e6a6bdca68675a9b2063d37000000dbd3523cc41a53980a97485938625ac709e05cf7d4cbedcb4acaec5ef941aeea2618a55c2631081ebaf70000
020600201f077d8ee67f4beecb52c8f5a9a0c3ac9f84bedbb49bac28e8e9c2e2503c2de0190b68bb11ce0b39c5a9a356aa52145621f9ceebdbba86923229b338b691e9e85a18a55cb88d061e3d0e0000
02060020a0b18cb83b93b7d89ca840e46544a6d6fe94ac6c554c4a56f5eebf271f0c2eddb19a429751fb8b957bee287de99ba410bb123e05ab85b4b34388a7c2047c4b1f6618a55c2c3e051efcdb0000
020800208af392653cc246443bb1ddd3cc0c1fdb0fc5188dea2feed4d48be0aa3830f59dd6df6eb3a53900806207381ed9b3ca8255923a4e9e282bc0b3a6ff5e64e2213f6818a55c3317081e744e0000
020600208c9a6d3a0dfe45f6e10f316f2846c446503fd0857babf59f110fbb253719348b7b6e54823dc3912dadbc87083c6fc1db4ccd63e3d874acf4856f1c142b55ecb07e18a55cbc31041e44460000
020000204e8977ec3dfa0a96f628dcc89140326d57c956e297086fc0a367204e8b12663b813fd35a0e7b809403ac2dcab2cb355a1a6dbc49e5171ecebba020b0dc558574a918a55cffff0f1e53040000
02080020c3978e21db686282e7041fd7ed304d6a003a35a65d5f42423679f6a6574872109a84da1171592923f16538e6055f20db7e327087dbf04f7dc7eb4bbd0449ef67ae18a55cf9040b1e0c170000
020600200e9f57b83e47bdc6d2a9eab03848178aab937a483290e31775912df8b6aa0b624e48dcabe90b5f52317fa77254746184530261fc89a134a4c576dade41e34be8de18a55cfc5a031e2b240000
020800204e97e62f2671ce7def3e2992c656ebd83262f20facce784b703a9d29bdc1403c31161c3ce0b336b8e8b8618f6b5569c231b9158cf19fd48c764b16b12de20998e018a55cc2f80e1ef76a0000
020200207251d27d985017b619e7d1ac7fe71b067dacb98e2298c9fdc15db8b318e33750829b9596ef35248e868f2d9ef710f897c4ced5adf3ec6a64d5751ca3e885538e0119a55cf092001ef4eb0000
02020020c67af593aad7f0ac805c87571511155630e010becf68978cea83899e78000000411a31867829bdd9a394c13c700f52cf4e8550fa2f9e3c4478924b6ed1d24a714219a55c5ba0001e53e60000
02080020b42c33ec135130d834b6bae4079877968cfefbb4c1715adb17d80ef540000000bfe4269a91fe229532f1a38473b8fec13f83fc257a4052d7188bf95d0164fb6d7a19a55cffff0f1ef5610000
0204002055f339eb752b2f1b4810b526e7a44a0ef69f7103409ca8db1b16e16ccc973ee19429834dd11b02a16b9ba37d5f4598f7667f6fc073d6569af5be9f7a5199583cb919a55cabae011e8d570000
020200204ffabaec1b975eb5664870cc7b8bcb69b1ba9a8ba50755b51c22dab9b49323c02ce21b5f0be90d51b1cb04faa2d3402acc59d96adc2d6a2b7f7d5ebdd7db2dee361aa55cffff0f1ea4140000
02040020b55932cc9556229d793b9b785c8f221da8ac67e3890298abe8507b080a020000d856ca5ed088da852a3f60b9f2e6b68c9ddf35959da981e4bda677cdb903683f521aa55ca2ff011e46b20000
02080020d4ead9c65293530b8612fe664212d554e1055df5c67575a2d2e0e979a7738bf02a650b17c99677447c6bd63ac96437d5b8060d47e9e5bb010f46d8d4957d07475f1aa55cffff0f1e33870000
02060020296d1b0ab6e6042d72aad6ab729b410f2199d6cb0c1902ef8c88f03354aa4a2b74ac6df3d20b2cab5e56127e67e7aa25a8506301a4ae5300ccbb909c620301e3a91aa55c30af021ee5960000
020600204b0b10c037f7de06e2e233dafffbe93ead6b835a9df39afea63a75b590da4fd16ae452ac8791902b3d8f7ecbfdbff961baefebdf65bdc9a1c8fad3d583c7c6dbce1aa55cc025021ef7580000
020200207ea6a20bcee685b901644ae74ce3b3cacf5d2731302c52e703f95d47a6206d811a3cafd00cf9b75baa5500fcc60f4452ccd7cf6d80c94c3d4fca69219e7b38951a1ba55cc6b4001ec1660000
02060020868c9666b8fc632387376354c7e347e331a4760aea77774d8cfadf7f430000006bb9e75d9f6b83592c7f894746bb6d700281becaac9e219a9ed2293fcb9bf2358f1ba55cccb7011ec78b0000
02040020d8d67915f91dd6686076596a010789d0f90dcbb2aa185cd4a0890a16a9b706e99358d53e6ba5d0697187f622288078ea7b6a8d5d3198e3f1d1b1568acf8ffcf7de1ba55cbe79021e99a20000
02060020960bcd3bac53386aab7971f4fe051519011838455d8d2389ba120a48e1d3cd59342ae5d74f5d8b8e444220b91e74071b30552941d747f9301605092d9cd14714e91ba55cd65f011e59050000
020600205f0ea5a57ac7cd52cabab4ce35e0f4bb20b953908a01b7f1d61565379703fcec9efe2b396daeaa3810fda2f40090dbb81c7b54c7166f83ead5e7c1b394d0be8b291ca55c7819011e66430000
02020020513b2c6a10f188f04a09a70ff27bca37076d3f2cf1b6dc321986087a7d05f2e6cfa288b523e1972e151c0af22cdcbb2bd120f8a24f94a1cc8a2d6f8dcc09c918681ca55cb5f9001e938b0000
02040020a8e0a690d995073d751be388a9f5fc212fa23de412f66881c28fd184ad00000034fa3f7f9b947b0151bdb7e1b1d6913506fe76b80bd186eddbb0eaf81403b4406a1ca55c3d77031e9f420000
02040020121fe83e0c3ba49990e0d5b7d5e6fc041207282d8d8da518027aa106e4726a55b56e6ac5e4b2b3d99cb252e0d0bcc5164c1aa45a527770e3a3d85d0bb72ecaacb71ca55c22da041ecc6c0000
02080020957ba4dca5cf5f4e60b2680fb72dcbe18e078047be0116a1ed2b3eec1b7b5e66318e791e3c836d7b0563bb6699c5b53df655d032a18618622ae5d43b8c195e04c51ca55cffff0f1e68040000
020200209c77412678f2e18a6f1c5fa43bbfdf1331114fd743b57777adbebd2146973b99934564aba3f5634d2fd790e292fdc974b6d89f6a5d0430e81fdf7acf851c7aa0401da55cffff0f1ecb7c0000
02040020370d7cf1dcc983911859b90255f55d8782139e0d68c54d8ecb4c37ad310d00001acb90c94d821987f380fe19b7e841529ff8440c4ec5a12560e6868e25ed26384f1da55cfcca061e071a0000
0204002095edd77b2c1721243490278edc6e28ace6b6b90155bcb80f62924497568f0e2d63f6f5d5b677db883621577e6f1c29573b1c6008af66ecbe7406ea26153946fa9c1da55c9482091e93470000
020800203c06041852df928364d6cf96265b102c355783026ff451b48d09794d7b6e69a92652b8be287e0df5f693ea1073f4fd46270508659b0774b5e67d06cdb36e40c29c1da55cffff0f1ec3050000
02020020636301da2c6c10258bc051fba934c70f684649fc4f5a56800a7835e2017d0cff5a36bb2f8d1372b3caf13c3ce0385d18c00bce176bc7d32021f6f20b5d14243aa31da55c975d011eb8850000
020400204ad8b5bd9d76d02ea873fadf94468b067032dd530cdfc6800b2a2ff26c0000006ce47664c81f1b321eabf74349670f29d5c2dae09fc8906d762a9659c0a1cb2fa51da55c68500d1e25c50000
020400203918a6e1678d65a1597a242d295a66fa6a2160ab98451da41588ba88cd07b60301dd27c6d69bf8a997a0e48646554a2bae81c53b49374e50bf6af53fe3e400adb11da55cffff0f1e79200000
020400207ac3dc4777890c47b20dae146c5a1969407f4f9585f3d9cd29a44ab2a4954e0569f6390298e02688b49efd6b9cc37d5376355c5b525278179b2054f9c4b9f672ca1da55cffff0f1e6c130000
0202002092ec73b0da1b09df6d774db73d784fc05ac9abad765fc56584bdec36ec1a23b5e5f8e2a983431ab878729c47378ef6bc430c533ca5d2e4d77d5c7a7ba22b4d75e01da55c6de9011e76b50000
020400207324388e40ad213ebfc919fcc8bea428c2bf729e95c2188a57ec27683a0100002529dbd548545f7ec0245879b02e7b6d00728d53092b2f84be765dd568b52e54f01da55ccccc0c1eed6a0000
020200204f29a74b7049a638c587263199ae06dc3cace54e710f5fc1dd89ef3c85d23bb953638659463f11c2b10671475b8b687cda91bd53bd37112f8f14ef00e35a9852541ea55c32ad021eeaa80000
020200202b3e21d4e449e0f39474c86565dd535497a245138a8de673913e9810b30000008cffb12b67b0623ab2ea7d6abc3e3784dd83df9362a259a941be68a5aa684dc0601ea55c46bf031ec9920000
020400208b231c772a103de3b034d648bea089be33a1918b13eaacde5e0ef18e770000002203e7640723bb60c068c142c9275fcdf75004ba623f4cf37969ae1ee8f4eaa5621ea55c703d0a1e6e900000
02020020ccd682d2e4b4c1b736cda41ab4397f592ec8c227b34015e52a55b00ff9d0b2ce3bbec5ce515ca9e90310caed0ab330cab27dcfe908ad4af1f74bc92c9189dc0c781ea55c253b051efcd10000
02040020290dfa801a979c2bf4788149a733e9c41387e67eb3ba219d41adf127ab030000e84314bd83adf06e0b12fb3c9857034e9ad4faca407b9eaed938e7a0da93c82a7a1ea55c2631081ef8940000
02020020b8707c54e5cfdd7258390b0d8caeaa11c3d55875ae149b6f66398688d6eeeba5a2d5b8268ac0824bb4d973163f3005c205d1a3d46708580a8b10b0aafdb0f91f831ea55c18b0051ed2190000
02020020716f22c00d94f6a88e3055bb9059be54d942081a46c77601bb53548686050000df4915dddd27a1890b8a731f810aafda1c43ed6764869af4c0f4b5fea44163b8b51ea55cba58051e27e40000
02020020dde1c4d2bde64e0eaed5c3d9305c655edcc883faa82dba7a921949f232000000589968506da11f0db05a680958a0a48e9c84f2f8b636d1be09c8210b89a4b4fdc01ea55ceaf8041ed01a0000
020200205c8e33d3d900149813f28b9b11fa5346bd501030d933b581da003f141d040000415fcd7883af02b2d816b7056e4b05db3770719601ed4400abd6864a67e2368eda1ea55c54fa031efaf00000
0208002095dcb49c2d572220216d624ba10db09bd327402c0a79b4fa6a25ccafdc02000051061676daf48bf2c9c0270fc205fb2d96bb12c4833fd1a40cb7e82e5748c4d9f71ea55cffff0f1eabca0000
02020020fc2660c0788f0d06d4f8841c49e43db0cfcc1632f51abbf5988d8400ffbc9c2e53eb7a1670746068e57c23c89878d2dbc284faf9aa193ac26af7f0b068040613191fa55ca92e031e51720000
02040020313137cf4fefd9ea89b2ab875f708553db80c87c56547d6fa65807d5d50100001457f9d76b737fb8a57fbfa8d66ec96e4abf16c89f07d5a37059eda697776308361fa55cb88d061e49e70000
02020020abc9145bb19c686ca0735ac44a80a73b905aa864b4ed32cab175d8e1b98fb3f6bebe4735f9b55f0bf831980dbfe1b22d09d9c28e52f10c4f5ae1646ca173b036401fa55cba8b021ee37f0000
02040020e974994f9db2573f5a404cee22a94fce1a1d66d7053d175943e3f5f87a0000003e6801f57f9c9cb0b1dbbcc8a5ef55d603893a7e064234de447a47236821b771411fa55c2c3e051e924d0000
020400206eabbb8cca123d953bc7b782c1957bb6d337a5136a5fba11b718db22e6ec6dfa8e5f5bba3529ef75789ab193e7c031690d94310d3fafd15fd2131fb14c69f1d0671fa55cbc31041e82d70000
02020020be13b0ca6592b3028a72f40db2dbd63745b92f513f82e292ffd79ac3ad8bd5667982f6f38513cdd4176bc8b8098c0526087ad476cf5e92f35204220eac6e0b81a41fa55c6109021ec9c80000
02080020810a7074aa2fff1299b582d8bdc60db85c1533d352a83ee9333909dcaf0100005ce08e67444a1f481f6c877b3187d86f5825679835d2ee6a782511a03eec2517b41fa55cffff0f1e6bca0000
0206002016eadf59273a393642826fdc151005a152201e3b804b8e56276eb21a2dedd85abe2ce1d5ea0508d41a8aa3a872fdc66df79efb90e66e0a957d731cc01d600fc7d71fa55c2ce1001eb0030000
0204002007f4c2ccc759730916020113b0157861a653cb2797aa2751d2e36a69e93b845ded6e7700062065966f0f7aae3ff35653b46fd9d22fb8deab5dc73b67fe1d300cd71fa55cfc5a031edebb0000
02020020e0afc4cf353fd9c11103a38965de9ff77e18977908487ad3982f7f8f0c7154186a91e0571f9e46640e87e415242988e80ff7395c918108ce0787664f51aa0bc7e01fa55c1aa1011e175f0000
0206002005fcf606c8114ad5ed40552bd0ee7f2b6a0c1f8ff991ef5366d567f6050000007dd8dfe5aed5c21c7201bd3f318eb081e586a9c9896f86cff09e291a7e99b6fa6120a55cffff0f1e751b0000
0208002017a157f9054f81d81d98f920117fc20a611f266626ab70617c2be69fe6b789208abca3d5e4fec598c13490a43997b6f45332137fe0545e62aa4f8b98f32626869820a55cffff0f1e32ff0000
020800200ec797b11b1f11193e7ff4fa2f792fd7259d52f91daf733aa061aa334ee0de12809d39eb9860b2c325e1fb11199b9c1f9ddcd5ca748ced03c5dcfaafbc321339b220a55cffff0f1ec6620000
020200204e7eda71f6bb2bd29815daacf4fd7094f3afed2c540c2c766da9cd43c4f23bc2d3ca26f3c81f35e2c37277484f95e834c7d77cca54f3b7f9c1cc878259925f210721a55cae4d011ea3680000
0202002029745ce3ff3498920447b264330070a2e475c801fff9928af544bb15db000000922699e0f5b0fb49b756dc0153106432f36898790856a0d070f7e48d525f38c15321a55cf10a011e54b30000
02080020c4e9bf5a73de8550e1a42d0d209349cad5d0ef77b3db0ee39c916ebf6a000000603f0100a2c5b3b85256befce5904e87997cd04d114a0307d562ac6c6947c9f08a21a55cffff0f1eb7c70000
02080020afbed0ac70ae30a5720d2a49da1154f2e547ea15e9efa0c72a9e9cb880fbe6d5e990a190135f8d438ab654bf00955c4cae5a128688ee8f99001c884658565bb6bc21a55cffff0f1eedbb0000
02040020d0acc927f95ad8a8c9e8e40a94154e3a4b1858024b95f01798cd5221d4698610cc1e2d43ec1992bf4985cb290d95da18cf04a25ea46e4144a329e6fbac3f4ed7c721a55c30af021ec3850000
0206002050d92f432092101d54955035284d396f696b63928eb073fa509daf0c2d6e6ca18bb72409907c0dd9d2cec303593f6f566fdfafe1a376b7cfb0abcc222507cbb62c22a55cc51f011e8a950000
0208002017b02f586c33b3aeed28ffd34301b3b15da27e2d3b6d87b84453e0256687351cabea6e6de4df0b8c2886db44d837b39a47af3999f3308a1f026e205cd4245e154822a55cffff0f1eddfe0000
02080020abf5283f70b60953277dd2347102d288a300ddc03242a469451de1a6df1239c4e6de2af81ba5d461d5619a3b7a2fcb24de5ed3a933cc69ce7d27c09bc52b34528922a55cffff0f1ebf200000
0206002041ef5c8f4165408d7129b7e9ff6261ae6c9ec74ef9d886de93945edf425ee5de8db163135e1505fb57ca4c01a2ed7b580eeefffe3b62b9b11c73494fec7951f69e22a55ce092011e1cc00000
02060020506d9d7996fd179f6de4ae474eb1ac485532204d7ec64e93831eba3aa5f9e114eae372958f06cd592bf0b379d377a4f9c302f25a85658c0452f00de19bf9024eb722a55c0634021e5ab60000
02040020cf8bb74d973a51e4d3519d9169e3b762572b61fef3aed762871a3ce13aba65c9daf32fc11719db760a32ce0456ed3740cbe716eec267b655174cf637be4315b9b922a55cc025021e572a0000
02060020dec29b6af5f762d4ab5a411db6bc3736610e89fdf41edb4628942b84eb0ce6452f40f0b68fd4449bdcec249a8f4c21ba957e88381eff33fc571b790553f670e1dd22a55ca215031ecfbd0000
02040020d8eed709ed93f3fd322f492f04fa442f3c0ed874c9c532cbb4e29c555f9bccc70df746548ebabaa77fca8603f36442da3f511ac856c71b2d14c3d2b4b21d77c32d23a55ce3ce011ec2970000
02060020021602a91c939e181acf044cdddbfaf6558a5e9053fef8708b021972e5aa33dd4b2c8c213679d2fdad17b5df33c83204199122606857bfdf4798cdef7551eca13823a55c7c51041ebb0a0000
02080020d92c01a012f26d1320675944e5e43cd43d4cbdf0235bf2ba8cf25d1f6b00f4f879e5f18fe9c18a4cc5ee88bb868f538ced19ed6f26f39b1185b72820ee3303cf4623a55c75be0f1e722a0000
02060020b9c8999713e77427bee81ac661e2fc4139ebf0e945e9d67940f19675a5b962fc8a09da51532422656fe74bb8d454b56c853e67ed1a329491e00ea4338481405c5c23a55cad0b061ed1240000
02080020a1bb1a9e395f70d7e53966a3aacb3f2c41cda9c5c315d6c9c9f3df46355882bb5fee150dbe26fb1b44e4b003606d3fa88e8c658da6c1bff6b037eb81b15f94705d23a55c1b380f1ea7c90000
02060020ec000239d69b66cf478fb41fd570ef45d4d6445d8a7c9f7f4126b969601f1bbddb06bd171120480bbb44461d1d831aa35ded95f1ce301e923c760015eb50ea736723a55c780c081e14f20000
02080020cbbbdd85cf059a9a9bd8d9efecd1f6aed379fd4d83c16ddcf206fb857fe7f1dd3479b127d7068e3f74e263c89f8a95b9774093000b8a4e0c2debb4af52f9d1fc8223a55ce22c0c1e64230000
020600202066a6d5adf0a59703d3578cb4f2536d8924d42a1a1aa4df4de325c6e88cf07b195b98166f162e85b4f80c7fab186fe8ebfbe4d220ad1fbc7b724a0cfaea052f8b23a55ca74a0a1e29ef0000
02040020e3bf9fb039608c370ad41dd240002654187ca9d3c0094d8b96a646a37a26f6d5083314c312a2c6b7dfeb82509e3ca006eadd0f97f40398ab7723ce2ea79229449a23a55cd29d011ed16c0000
02060020b1c302b2f8921bdd9c8fce5c7c544224f01923b6271cf8f2f2e899ac5cfee11f6f9b12464a6469988a1fb649c0baad9b4d542c1d048177f3009a1f18a93bd07ea423a55ca9f70c1e2ea30000
0206002007db8992f8b7ce4982e79c2d30bf28a0a0d75e3c1c7121308456d7e4069687bd43b75d6230b08cabd111cd94a5929548d28b1a7b5c1b8edc18a1444c7e4016f8be23a55cba5f0a1eda780000
02040020bfa2384dbe54823d3996a8409c209a00e7f87686e7b897ed22aea01073c02c5053a069f1cade600d855cc10bf43919abcd0f7b3164f117c93f1fa9480cd132dad923a55c9370011e36450000
020600208e9456e9d530488dd7e5d27729c18c06ddafe85391007ab2de369744825535e0068a127758260952e612b59252655a2cbfe012a09ed90fd46827bc85d16ec820f123a55c944c081e29320000
02080020a617c58a306179d4c217b4b2c328cd74b21b648922ba8465c1b6751310dc240d1aa1fc8ce9edd9f4aa4847d6fd19b0d74acb6fb41418094cdf2339cacb4fd2570024a55c81bd091e5f5f0000
020600203e2c74ae55decce91dd06ce91f65495896a1b5b0cda597067311233c671a45e3baaf669ad03929d4606cf2178f24f44d5377cef2c220a2a164c3cb2c19aace401524a55ca9a3061e540d0000
02080020f1c1651f7c2bc98c1d9916e53798e61070a29bd94924415463d7da1941d52055855faadbda9c0617164deb44186cfcf03fe1e16b5e91aeb06b1fe90e242e98822424a55ccdca071ef0600000
020600202525203ac234bbe87df061fd2a2b73278b5e265bdd26902835d0a1f0db882da2f6d3f49603bb3f274e68937323fd398d4f314bd8c87c0918552a3d02e6ef89743a24a55cba4f051efba90000
02080020d1bb0a97a7698700f57b4eb4997411b07f353e9872d6eea3b395d9adef60baa40e2409421ecf95bc41934ce4cc813a4ff14580cc7c6aedd44b1cd43ce97097993c24a55cd73b061ebbb30000
0208002079bea02ff7802e8bf2fc2e8807cf2a9591405e8d2bd4d9d8156d70e48239fe8cab4f84e6505e0d5254f5d2771bac0f87ffa7a4d27e5a99abcdac8218bdbcb5605424a55cacfc041e45430000
02060020d7c17861c4da0a8c8952d9c9f23152a44247936bbbe15d315d85372f0d9abdd48a56033fdc7921d6e03a1f464a7c4925bdf1298b5611449d3f888abb964df1596b24a55cc83f041ef9420000
0208002097b5a1f06c401242ef39abe0eeb4f05bd59575c3f62d16eed9e2dd9997822d6c6319ef4554bbafa4bd3815c3d49c196a567e51ca0eae15d73f41fb9e4733ee329424a55c56fd031ed8400000
020600207add5f37df4729205247f024485ac5e36649a9fbefbb9f0eabf65d88d8781499412ec0d5b24c3b5729a97d14e5462f1e45bb433e76d9288e2d30a4326172075db724a55c3966031e5baf0000
020200205a30fb2dea53d3c05d9e8c3f8d21a92b7a9354e244bf31948fefa884bc6a69b427125191f92f30a8c9222b58344541de2ac81cd80f01d91f7671a18818a6fa18c124a55c8dd5001ec7da0000
02020020e152c145bf74f82c2ccd8a9fd658bc5022219c71e33d8445432fe9629800000084346cd55bf16194b6792b96c378355f2d570ac5ad0147d1ad2401dc9700b5604125a55cffff0f1e6bb20000
02020020e5f4cfc4a6e6d5b47d4d4a369df897a5554697dd4b1e8d74df9e8b58250d00009df58133512f5c4d50bedc9f1e4bd91b7acab1e93c49bcef6232e8e770def29ca725a55c62dc001eab580000
020600206daa2242395ba771cae14dccb700aed561bda94f0a9e3f2becf7ed3f8d000000aad607a8b4d258d2f00b7a202991fbbc6c68b310c5c208f7413689d5c27e399ee825a55c2db8021ee8610000
020600201788b49df0f68f8b14ba776475e14d5f1aa382fe939e366ab09f373fe8904fe425984a7112561fbcd228de725326273faa8daf177c8807ba9808fbd1dfa14be50e26a55cf02c021e258d0000
02020020852f6a92f390eb77214a23cf0e36fae65469d91614167bd86762779ef375d74a1baa284e4b4180f0d02e30b198a10de9db2d0742591d4d015a26fb3e8bd280628f26a55cffff0f1eb2490000
020600206db0b0547624481a31ede261ce37f87e36b10116cff2f125b49a631d83060000872e393559980c1d2d8f2ec52d3edc289d74627404e10ba0d275d50ea63686049d26a55c8cbd011ea48c0000
0202002086336001bb64d34268cbaf8fa5872bd099011765bb772534ed268504d41ff41b13c797baf0e439db072985fbd597f2455f32caa47c1f0273b6077c8b4c994d3bdc26a55c9c03011e1ac40000
0202002079e50afc8eb125abc2286df2b7d623117b54f6c5c2661ab855d1d3b8c7000000784acbdb5df8a3d5d89c1f97e35e312ea651d882411718405f865a57816b60f0f426a55ccc62011e24b10000
02060020fe4dc1aa4980e4b4d581ac69ebfe0f837364093f1469f000c7c78aeaa10000004f02954401c8442a0adeaf9bcedfd21d0fdeb4cd38668f798235920b625736161c27a55c7064011ebda80000
0204002036e882bada955703b9d64d3b9972c1baf03d975f8a949d7a238485dde686a9606f84afc4cb6652c4de2743cd1476c9e59077cc578ceb1cfaf560f60f646c5eae5e27a55cdc51011e80170000
020200205c2035c3c71d60779b4f2df3e65ca21481a5bd4488e58ee956d3601883dd4d862893bfa16c7596bd1db87546d5935836d45b532b8c9c6afb811ce52db79db54c8127a55caadb011e74860000
02000020b726749c6f95e9d23254ca7619b4dbe7bbc4e2e1976a6246a1ee638474000000b2bf928a5480fd51dd4409c1ba90e145318529150859910d245c419853f3b044c927a55cffff0f1e95000000
0204002019abdbc0a17791c368bf6427b28537cc54f49334c53c4d3cd42592a87a8e525a7ed931a0790350112c5ed08302a4fed791824a6b08498f7689dcac7f791963fbe627a55c4cd6011ec7480000
020800203df9a58cf159558efb1217d98aa2e160fd89fba632992c57b725ccdcff37300f5ed9df634810795b1e2ce618048db269730d41a63b825a7a12764df11d79ee330128a55c1131031ea38d0000
0204002038fa34a8f287eadc69fe642be14895ab8cd9a81855c7d01cbf99620c9d9428c95f5eb2d8e5f68172091d5be38f9ab7efd7d0798892ac1a4d1dfe9a0894fb21cf4028a55c6a92021e365e0000
02020020dba0271bcb6aa8aa7f4a0e282cbb3736f41ef4d484535c8502e4e3d9b8e4d74df4d2f96ed4e582088819e1f394f27f68e379b92227f8ccf91f9c567c5080c18f5528a55cee99021e4b780000
020600204eaa4213a38807bce0a3a7f2a0e0266690267de529724f976de470d53b0000004786d2bec80527ed207f9e2e67451cce246dec67fe606c944c456d2f003d1c255728a55c261d011ed2030000
02060020a30dc139ac60a5d91b9beb3f8297e7b6ede7172a1d18e55eb18dbc63506790fc8e2c8ee32fcd8232fb929a7652e45ac0ecab627c6bb456c6ff645ff1ff4c8c617028a55c1ee4001e2d7d0000
020400201939dd1d9de12788a86547f00cafe30ad6b989240ff6bbe4e9842a3db4be7fd6887fd96c532f7053d6e1fbdfc2628eca5e53e69e29bb6217efb886e87fd2ff287128a55cc799031e6ec00000
02020020453427d9ee623d26628ea3d8b632854c86e9acf0a6824fba22ca52db152f10610b0428e7165e3af41abfefd3ab564054e4e64d64735cce2d7870fe90f41a82908728a55c4da4031e88850000
020800202d530963182a557364e36358f706891087a057a1b245ad57519b332bb6020000925265ea7f1624739165d5b9269d5b8e615d116a62b97a62ea2f0249a6afbcdf9728a55c98fa021e115d0000
0202002047d87853048caab9ebcd5a715f36f04a7271425c7ddf61e99c98c9bb729465687891efdd5963b7570f611f30179d69ee06268a1c50baac2dad43ed1972936d4f9f28a55c3819051e95be0000
0202002099c75ac4b0ee2a7f1020aad54e307bb013fead9155ac33379a7112a253040000070f49b1653c551f003a647c10b764ff186ff8a61ce6c991c54f1e4f6a3c39f0ac28a55c909b061ecfcb0000
020800208e24f8bf28776a034bd9da09b324da205495bbbf389bbb868dea411f7502000020557585192090176147da8185ef6b70a36846b64c13a6d5edb3493c4c15ec7bf128a55cedb3021eed740000
02020020f346281f289573b2d566ec6cefb4499580a5c285cec4e8b648883e54c27c8d6d226020a9fd63f20b456c574779252445715b9c56a79e8370c855e088bc644272f928a55c3849081e96c40000
0202002007f41bf52e0e391c17d62d2379b21c826051d8e70496484b288ead09610000000c92d9ff53ae5fcec74f5b1fe1e4b61d4c4da7e5dea46df6250ad140d311a9b30529a55cf9a0061efbdd0000
020200209c750aed8c07fa522ae84337bc2ac25d098801932d6cfe7d8cc022f286000000c463d4acaa9db81c1bda47792deb7d9e4cc1431f952203b7f469f2504586c3632a29a55c944d051e42540000
0206002055b0c271be20ad11844120bfbb827facf3d29bdd380888987c4074a542020000ecd44fe11fea6386e0537f94eb2f62469fb2cca9ddf6ac70c4364b81a369b9d83929a55c7eb6001ebbd40000
0202002005bc884e1cc289f958cc7282d133d07e808357bc70f31130273d5f9b8416aafa6abee684bf7ce1a36793a8085fb6396deb2df07061babf3128ea4b50c54fc1bcab29a55c103e041e99890000
020600204f8504ddd53e09cc740da6d11b2c3f91be4a981ad2264fc1e1956381a0030000ffb5e781989128b36723fa8b720d84f2d770a64d04e231f357fd9edf5d4429d1c529a55c9a9b001e49ae0000
020200203b49cd5c9a08afeea19edf6f8de6cea89f8685ba68ffe5e9034e2517f964d7b3c747bcfd2eac922126accf6c485068547018e60e1beda9aadbf1b2b5f3ce0016ea29a55cd964031ee19a0000
0204002090e8b9a78442e6ebc3e23444323b55e809467c4d71a873ee811a14df8701000005645abe361c0cb7bfb287f56e418f6c1ad760970578305a223cf2302922f7dbeb29a55c7d0a051e32db0000
02040020d049bfaa308cc3649d972d15009b6112cca51aee63c7ac773ac6d764d6a1f4ee8a37b310752f617233cf053af9913169d0c30d2deb79b1653e6938f6c5d3a4502b2aa55ceb00071eda9e0000
02040020ff17fdec69546d2df98eb7d3dcc84c6a53134aefde7a89d831b9174fc083028cb0dee191c360501fbe64bda7b92f52277d320b331627b8f2394c7427f070e7fa362aa55c33e6081ea2480000
0204002030e9554bde29284dfde6af23328ab3b3f4433322aff87a6c80750a7bc16bb7eeb179b71157c017df3854b697ac74e1e446bbcd298ff929d2dddc7dfaf595567a8f2aa55c5baf0a1e67190000
02040020680ddc9c9f7f6ca2754de03931223871718e1a4ab3ef8205e51fe4ade5212ff30a7d9bd3c6ff9ecf063ee75537422660d757316d7b0f820389a8db330cea84f2b52aa55cc7af0c1e11d10000
0204002079f972287ef57790f4068da1d255fb3e4b415015476ccddc480aabffa3dcdf51a1ae2e59a6b7d6f8c4719acfcaa5ed4d4a7deb1b459389dcbb41d63456be952fcf2aa55c10da0e1e59b40000
020200207281d695fb826bb2233ff35329c04f0a19786d7eec1c713864f07fbc19b5af3bea43da8bc08ba5cf70064255f6a589e15d2420cdb390b78ede0bb2d77b4e1d3f192ba55c14b7021e840f0000
02000020ab8c6aa97b4a7ef600c71b67e113e3da876fc6a43599bd886517b9c2a0000000b34191b7717c05560177fb7747e8fb7b56e3920e3a78819d61d00b7398fd595c1e2ba55cffff0f1e840d0000
02080020dcae69c07de308f9037da11efada62e15ebf2de8647d5f2d2a4e7d6909ea344cdd65eb7898069491c2127713b9bf34bbb4d90ee15ff2d2e462f47e698f845900252ba55cb692021e08440000
020400205c9be5a8e0b3b2e7fa4e05ccaef0f05eefffa7811f75bf3560463c86bdef8a991cd1325e3ae429c246e00acbe93227a86b7258d0d0aea6ddd5d62ffb9da8071d302ba55ca6e10b1efb000000
02020020eb70833164cddff3157a2ca9f6651a6ce6ca24039de386cc40d8504370d2036be94f15a6421e2e4e039ed2d9409551993c32a5b52f671f5b74ce4ed0f53863aa6d2ba55c102c021ea49c0000
020400200ca58ab23bc30928ede8fbbb94e31210d8862d2e7157689516ff1a88350000004e964960926e6f56395361944e0f3ebf9eaa3bf702e1a6181a70931a6102b9016e2ba55c5181091eb82f0000
02020020ce5d84b705dd6b5567bb31e64f9d83dad08f28be5b5e70db4696b47f0b9780334fc6ae83b0e41a741eb404bc5baab7671b6a004bf0dc398522f9f8b3e360d21b942ba55cd9bc011e7b030000
02020020d95dff1187f1a6c5ff014d7dd8e4a34f8c6720d41d6f79984bf81f7bc1000000865aefa9bbee390a3981a12cd1fccff2136c683f2e75aa41b7d1fa26caf9e680142ca55cffff0f1edcf80000
02060020fff388358f4017236ebc068dc5b8ed5fe880244254466bff817de9642704000053dd3a9f901c95b4ad85d8461eebd08bbbc1fa8f24e729ad80ff2fd5d77087a9212ca55c1d8e001e5c6e0000
02040020a3853b027072eb90501adef364721a773d8471fc27cd069f7d75e9b79c876fe8d3bbce3677a4a6f2d4d24bc62917ac60a2f462fd04111275c62febbf55b306db892ca55ca79a071eb5880000
020400209bd8cb403c909dd6712fe6c677be329d8e517fc66de5f6e3aa670f1b29248df2a146678d79e2959f25695c0193d670fa7403d2cd9933d0d75263553bc82a7524962ca55ce5c5061e180e0000
020200207206549717b003bdfd1e080ca46d5c6784a575fda661fe8e3f9a2a128a72fb6003bab3cf695d7b1bd91bd7e18a35e68bfdfe8d5c1e935ed95db135fb04548d79132da55cffff0f1e0f5d0000
02080020fc0641f9a1d5424b0379cf76bf452545fec05a299b7fe0da8cef0622a10f000048e461036712472c4118eb0407f60ea00ead0d2b99ca5cb14c80f0ce21b31471242da55c1543031efd150000
02040020ce295ef559cb4d2350feb684ded66fb9e22b5a7f83acbfe134b731adf353fc61fe8a66c353b15f0b61de4a92fca0dceef62c5b0b180e4ba9eaad0bffdf4f3204482da55cdb9c061e3e8d0000
0202002049937f2eda2175b1362373faf120911f49472623c68a00ba2ab2013a14c0f8bdde7c2ed6c48e1924c7e41fc1bf71920caabb52d9b691d62f28d60db86c0b8aa5922da55cbf0f021e4a0b0000
02060020ed1e847bdf638c1b5f4138d3ea95db9c8dae8da552693599d23c2c986701000017ce88f009ad0ab33f204bea4941cb8db41d72999aed4039dffe67ed601a0e2e922da55c8ca3001e4c710000
02020020b68991b1de24bb60f89297f54c347cc032e779c8b1c73cfef36ad68bfd466546bf74baa14a4edeb7bd1737fb97d52264e3c4afdb591184b8dde631e5785cc22ab72da55ca0f1011e25c20000
0202002051109fdb40912013a789adf327916d060d755a900804c11490d046426a00000071858e7cbcd76c3b6ef3763e01aad41d2cbdeafd5f534f451db023ab3260565b1d2ea55c6ec7011e4df00000
02020020f0fb0c0afdba31c9939f26f8f62e9980e397f00c46ea2635219a78c028010000235d5d335d3fe1dab80fb78d3406babc796c9f8c461fc0355d9180ebd0504633502ea55c12a1011e87ba0000
02040020a57e87ff41ab7428c8b0437a31901f44bb402c252d538c352a32136df1000000ee89ece20cf234f4c44263651e97c7503ba50bcded269fd16fca854c4d36b670d52ea55cffff0f1ee3f30000
02040020871d7265d7bbfe3a8121b148b97d112490ac7ff1a4ef567e1c07f69c8c028bf0aadc08306a3a3652927e223d84bb2e227c5e9a8b51426b070b35e3ffaf8d87a3e22ea55c3091071e0eb50000
0204002011bf1f3c9d1d59d94123f63e0cc474524d5099cd4973e2e92d01989a411a6767577933cb7d5b0ede1205f2c948efb18e2f45bfac60ca041b3ac9f9e026239750482fa55cebcc061edd860000
02040020887b1eb67a4c850cb2402df0cfc00cee227ee553e0e5ff192e9b964783487a23046e7771186ddad54858a38976a095e99ea50b509e6eab7939b23f216d967e2f882fa55c8b1b061e104b0000
02040020e74384e31ce66d1ef9f31879660600a3bf86d3eb24ce7dc3ce14f858728eeb1c7b85842142e754f8ebb7ca4326ca2e6d194dfa6fc80b2229093e5af18b33b96d952fa55cad78051e2ed60000
0204002032bc29c74fd640095451596a87de10e933e443088c2c56d39a2fd03012c56495d3290af3928a853b8226dd2d1db91b1de12ea69fd684c049864712ef4b4d180aa12fa55c90eb041e7d900000
02040020b7b9fb4595f28decfeb3adeebcacae590e022a74da5b41ebb6c7e40867619e456cacf8001e05a2ad5b85701080083b503f454de0465a3f8baf4695aeeacb6818fa2fa55c4775041eeee00000
0204002012d3d08f7fb4e0e10f0bb07c17d10aee6bdc0a8a648b8c6705ab780181c1e6bb2a2ed95e0435f5116c597acd8e2d770dc53593b53e010027af82cef8cbab52c12130a55ccd08041ea9c40000
02020020e2fd9f3a55d8c47c7d531ee7131ac2e51aa8b381ad63f15d75b2c49779a22f0557a0b81f0fbe26014b08cf393657cf7e73670a565aa875e6a3d1426d0ef722914530a55cca07021e12580000
0202002000e527a523dc963f25f84a24f92341d9841cf39db38a3a585e29045856010000192ed61f92fa023e74f6ca6e1955f83e6e25939698e15f6aca8d1736eba53c387830a55c58d9011ee7630000
0202002011eb502bfa1129c4894a598c9931cda9c8c1a3c455422a2cd472356b16000000ed223465be6e59d7cc4538116e98211a6a11df54177ecc8a69134e57abaa43140531a55cffff0f1ec60f0000
02020020ce529cdb5624211318480c028bf5048c0035be250a63be77c89afadd300200000ffc734e451b2fa4c69abcced21b866d4fa0cf44a968754053db179d7d1307ff7831a55c5dbc011ec5440000
020600201c5b5d8804d773e4784e4d6e03c781fb252ba950a02f3382c7a76783160100006f04b1434b7f09981b59db35d7417c626e12cb5000fea2cc2b9c7686b443de00a031a55c8c0b011ee9670000
02040020abef241b1eaba0d85830ff6fe96f03215185a2f61562dc0c0085276b01742c0b368f0d0a2a27094d2a15b50e4e6e74372813ad68d4fc13ceabfd83f8b05307c0d531a55cdb76041e4dea0000
02020020adaa38fbba975492c2fd38557ae6bf9d91f3314e9a97d67bbde6256ce54f15c8d9bfe2e55a535e21f1c271be01152a6bbd1abef64d68a0893715687cd43b07b1eb31a55c09b4011eb32c0000
02020020c28a00ee9a2eee4f88f0ddf1978ac0e322c79fc056a2ee14dab7de85ca000000258802da740159fd81fb0867e3be9d5aaeb9389ca51fa2b898de3b1765203b951e32a55cd38c011ef1ac0000
02040020de059f217e3e041275848b5daaa1c9923229522db3d55acbd326bbe234000000db67edb6319eecbef4a5fb3ace4e3d3b967f8f25c50ed2408aeccc72bf185f543a32a55c2a75041e561e0000
02060020967558fac11f6a27c319bfed1d19c8b913c34193a4a8c00e6d6f9805529604c09c3d48c424bbc2091575afb495d2a484ce699998e8e752fc6722ab221f8afef85232a55ca023011eae6c0000
0204002081675528be1b034cc0d1e7468832496839082aa93bc13a6b9d5bad4e5d0a3124cce6befbc719db20bbf34b01f198bf17f3abda91378396d287c631264040d17f9332a55cda55041e23bc0000
020600208309a01d56251457076d3e77feb8541ce9718f77fda943eb430dbfe71a623fe384198bcc2ad12f0290a3b7981d2f37ac69f6ea77fbf816f73278d2dd17287687ab32a55ca91c011e19d70000
0208002052fdd693d61c49c7a33c9137b47774040918e59f5fb4ce6ad17b0e8f2977e3e24beca833ae5cbc2e871ae7bec104ffef21134e3324142b9fc8fedabc9b0d1805c832a55c587e081e7d7d0000
0206002075ae7081cd3b4b524463b51cef3448e2e66a3cd8fb078995cbadaf4c1659e99e32d77fbd8c89d5a943d23be12bea9137bb42daf6d4f313c7ddcb296e8deda44ad132a55c3617011e109c0000
0206002008eaffd03c0ed4185739a93f03618ff4817fae6e845f07b64e4401240e91a6bdbba99e2109532acc66eb6e90acd7700905d4776eedb7cc8f84b1f36c32ab9f69de32a55ca607011e16fb0000
02060020cd09b170c6e8c2d700bc19815c9dcfba8a804637683780a1d2f6e22633e14747931a91745eb66b893ae5edac8d596baa074eceb7fd3f9215b454b66b1d107724f732a55ce7f7001ec63e0000
020800206a17aea03d7302e12b022327a6dd410c52a3f8fa19d739eb6044ee56ad87013deb8921b82c9d2dd1dfa914646eea04383151ab8ac2d9cd02eb7eb499c59b4de10633a55c5408091ea9000000
02080020b22588da28666f1aad69b898c33836a07daa38159e73bc63b6e04410ec822dfcd745dc5294ee9b5bfbf8e0cb0d47eaab1c71e007314ecc52e10ebb8a4b71ce792b33a55cbd8c081e17310000
020600203ce55d790fc64db74e702883b6f7df18cd988c110f28f41ae9043e551669693f093d20e1ac7258e4c25895cc0a6f9bc295a647dc34a5588fbf0d376a76613cea4033a55ca5fd001ef67b0000
020200209a44d8b5751d8bb2c837a4be2e9da5e8760cf32f4e18498fcef59e8733f074a4b2a542aa7839f37096294d5a4607a9b8cbf4a882720c00c18e27dd3302a28d4b5833a55c8b41021e122b0000
02040020cba49588636755a8f07a0761c2312cad8ff647bb9b2e4293669bb0228e000000b36bd578819f18a7a198b305f386f08b2e3a28ff18384d63d9bdfabcf410254d7333a55c22c6051e23d30000
02020020d8392f3c2f1cad45a624f0268fbfbd0580b65a87c6c423389735af897d9b95a7f8b3eb492c966aa0126dd26c29b108e352a012098d8c9068dbc8e2ed905f9a1e8933a55c0932021e30090000
020600206006014a59f447ace26cb1aa5c897166bf1661f1061c73690c51d8a7b20000000633697fbf4ba07000c41fccc31f6540ed434075731ab2e3c7e6bf11ae4bde838a33a55c530a011e8fbd0000
020600207d58893e8b2d1bf52531a365e111a4cdae5e72016ef823257914cebbecfc5bcfb16dd1c034327e70b3592525fce81c5f198ade40a93af663bad27c61eb093e20f133a55c82f9001e0d620000
020400204a37a1e634b6c2bbf7859e21d52e1b06373708360fced534fdb494306a08edcefb43a9f0b87ac06769d7adc2995697c19df13216dfa6044ed0534feff9ab91fcf233a55c170f061e75150000
020200204c4812ce813823137350e19d478dadc348a6bd316aa83fae54357524b8d1138ae4bacdd2c973706e405a90a66d2518cca7a53656d88aa3f0ffa17bbc4c7247187034a55cffff0f1e78ad0000
0204002008dd60d6b24fe3cd70bada118507c8bb3ee9be13bba4fce49451f82d15080000c69de36585a8db5686e74f0a66860d9449d6e6d6d6e2fa7d4202db33124c901e9a34a55c7fd9051ef9ef0000
02080020461dc5972750e8f009594811c449787320c5a9193fa5bb2b8526ca25dfcadf1cefabdf85c878d903f40258e38735657f054f3a6cfdca01eb226c2a24d284617aa734a55c9f460b1e31090000
020200208ebb242aef932965cd56fc747362be04d8724c0c596b7c1b43105953e87db2f6150de83d18c1a059890f3cb3f47129d94ba9c6efbfb682f278168719e9d9f002f034a55c5691021e1c590000
02060020192b8b88d144d1d65ceb9bbee30ad42c1e0dab6c4b32dd36c61456b4300000006519a0d0c732f04d1ba3a244e3e3290d1cc6507ccbf6f269a1932b32d9c89dd0f034a55c7519011ecdb80000
02040020c0d6927ac517320b3764c93daa70e5dba95366965dd632ddb684b277e6caecb035ff22fa603d259902e70f5f39f9474536c37a7342a64f876318dd1623c8e14afe34a55cda18061eec300000
02060020b6ca848842d9d0251b2f07b69cbc8dc3ead66353b44afaad6334e5d64212d5eeb75c8720e6ca4f886bfb49e88bb8422f2b376e5ecc17dde1cb5dc0fff36b440d3035a55c9112011eed2e0000
02080020ccb9f51eb88ee55bf749f922f43df04a32e64910f2df5bba1b7a685226ea9eb59d6ffba67066a8cb07b4230d114b6e52516ff5d64a0eab80cc06202da41172fa3235a55c35690c1e35450000
020600203eb3b4d69cda0dae72544f4aff4e7308e982d44d2a640c7e626a6b9fd00149023d4f5075a1ce2e19292a84b455e58dd400ca2c6602eefa351a0cbe6469dac1546f35a55c5b0b011e63070000
02020020b0998b231afa4e6a7ee83fcf2b7fa2bcea6699e258ccbc9bcd81ed9943f135bc25902828562b1cc22b10d73f36a3128c67075630f3cbb8dfd94bcf6cce74b4b08835a55c45f2021e67080000
020800204f866062885a0285809f67bb4faf9173c7b7934c1192eccb443d7340b6000000455b1f82a196a3a73b295faf4896a004319fb96f13589d255a30328f5ef7ef518c35a55c4c9d0c1e5b4e0000
02080020f864ea2d699ea03a643c264aa1c4b81b2d4deb42f3859ba22e866da30229a2bda4b05cc352a7db3bb1637c834c5922a6a8b3e20633d08fc4c64c552d99b50cdac035a55ca7b70b1e81800000
02060020b129c956b25760723a8be11f160e650a5447e50ed0ac85f002a051d9c853866ed23d7276780ddfc0afa83fe94a311368cfc045cd1f335a29d9fcb4f69435598fc935a55c6118011e54a20000
02060020eece29025217e5499603dfb36ec8b5fc0552841bbe2b7a0307bef4904646b91ce5ec486e16804be9631fe40456b03a6766ade33b6e3ba869cfc88ae512d219c5d635a55cae02011e8c080000
020800207f87be88100957e40045789244d45fca5e88672047bfacc7467cfb3afd4e39957c9fef2b6a0413772d8db83082459ba01bcd284b4ab7efe40d3fa21128df1244f135a55c28c00b1eece40000
0208002007c0f52fa86b5169e12e69c478eff2c3472694092cdfd719fc22ccae8dcf89dd03c2b3fb261a63b80ba12f53828aaf67c9aed6adbefea0fca747a8f86990c02afd35a55cf3c70a1ecd5c0000
0204002071d34b67807db80f64fcd6836e3c5b292cb7b4ac9cb23f192ac3df085903184a535a6eaac7bfdb47b4a62880021cb9c8b71e7c5c6d10cc22f7b62db3672b64b21436a55c2c45081e37b00000
02040020e55c83314209118c6e814624f87fe672a97cbeaea2afd248545dd899b56a68f1770ecf67fc24c3ccb2ac403d6aa5904ee6264bb14ac85c6ebe0c0843a85b6f6d2c36a55cb097071e6c810000
02040020693284d2d9b1fef40e90f4c437631daa725ed7e8668cf499a30659adcaa16b60f81792293dab7fd8ab05c76ff2960aefc108d10e77cda52a70402b9f27dd2f515f36a55c3cec061efe0f0000
020800201d2258d84ceeea05c88e50becd3f14e1d6c09a72f59662311b5ad2fd75ae52f2cf600262022f2dc9be0e14abaf9ea0ac62993fcc7af5d67ff565b5d19ef090a06036a55c41120b1e947c0000
02020020fd37259dc9001c9fec835b1881aba196583be48f9934bdf93c94800d69167c5a97aabe06c8c68afdb844bfe0aea9e498fde7e79d582324a203ceadd3d128e4376836a55ca7fa031e72850000
02040020426cde05475a76b23d5e8cf2983366287316f9707f02069a99c1a34d1b010000fdeabea0dcc650ea8d5be8b5b11324cf53bbd9afa74bb1b0fcd1e81acd77d31c7736a55c79c4061e3f470000
020200207d8c48b81da7b083c8de3a9b2b00fc333df4c286d43a5a797e427639dba3ea9291561d3b21e739c9631d0e44c2bd1911d151f8fd888be235470dcb166b14ae718c36a55c7aba031e5cad0000
02060020caf982ec5813d8ccb27ea999bf2e46507ff1fe947b845fc06b49581ea200000080c829c14891f9007584ee7ee9b92eb8e7e2e762cb968a4ba547a10cf9ec22eada36a55cf147011e9c6f0000
0208002077f4163cacad5241850ec2b55986085678869dbefa4fbac06427452fdba89f8bda095689412f4a337cbd6fbfb40865946dd0f1b3222b30b5aa781d04abdc2395dc36a55ca1940b1eec780000
02040020436030ea19abe5abaa2fdf7cfc6e20f975af27273e537e29b55133a49a66e933bfe4e601a368e7ac93e7c2c58fdae10c6d20ad8d8cc034ac1df3e0c4a74b1dc2f436a55c96bb061e8d780000
020400206c7c7a79a3bf565d2a732a1beeefdac501ecd4157dadcee5138ab3c76ab780dcef162c0a23bdfeb3b37354e84598beaa0a0d8ef4452d4f98f895587882d199935a37a55c90eb051e3bff0000
020200207bf957245f6ed61751e932ac36ea9c48c90d11007b1aecae68c6a24519cf87b5143b6e2f17f9eac646ea4744af00bf093b6e0aa764ee237400f145209dfc7b347137a55cd6d5031e45f60000
02040020be5f89094dc89a976dfd9ae01aa56a4b4530ad842b832fcc1a4ef01f36010000793821da0b2ebff5de488ed71256f1574802d78ee6db5e5ce46fdd76c5619b128d37a55cbf6b051e46a80000
02060020deaac876961cac62ca9621b77a395355f4f15c38ab8cb968c774bd86f01297b5c5db01a36806c9dcfc516827ac9d91cd5016ec308a6ddb0b128306cfdfa374279737a55cd65e011e018e0000
02080020a64ce22a03b6f68bbd603abf6961303ec219cf4f525eac1028ee1504394b2bdd87a4c206ddae8c2467631a397d0f3b896636b263dc8b24851e02bafcd45980259937a55c44570c1e3e1a0000
020800208da936be24831baab6220f0c45765b0567568ccb265e3c99c48fe33b57c19eec6b01dd18b2fdeb57b1a47f70f82a97bdc577131d919630a4544125843d9f8e36b237a55c91cb0a1e2e4b0000
0206002093b67c567ad43754156b2e4fc2ae77ce5b56ba4b05cb16370ea8b229090db8385902434971c96e9321bd6ceaddd7056c0c1a51b44678ad8c98ebac82f1fd1955d537a55caa4d011ea2930000
020400209259997515a80acf3385831081f21be9dd56b86f6a09aa2825cbb58e42d4279dafc94da47b6aedff0cdf51dc02b4bd3db11823f13537afe096f23aa7108411c6d637a55cf381051e53880000
02040020a9dbf8edec4b7424c08664c4208f996a7b0b94a35d24ff5cbdc608e7626facbcbfc449b002be2ebaf27737b59e75f330df80358373399aa46c5096b7827646d8e237a55c76bc041ed7ac0000
020400208838a8305af112dfad4386208cd72070c0e4a8c314993ac26f8756ed13d932e9377d8a6bf255166092181d179d55b27d6f5a9154c3dbb1ac61ec3e38fedaee3fee37a55c2e0f041e034e0000
020600200f97954ceb3fc642c1ae8ae88e17ba5be7d54ecb495bbcf4974358ed03814deab0cef87cf6532ed577860839887ef28016157bef1bf2c46c82c63ca26c230dda0638a55cf93f011e31ba0000
02020020e7e04c53d2dc07ed5fe97b592fee234362bc8f1737049a5a9900bdd4c56c244a4c6f38251ab1869322cc6f2a0792ccd816f7ff8bb629c00f0764af4a6ecf53721e38a55c2aa8041e4c150000
020200200cc5919ed0bb3ec03a50d284016d0de91193107230cc1564e65d52742c030000188266dd931c6c8d005856c95a6f984ea22d46de32199d25a6a5edf2147971e43738a55cfdf6031ee3f50000
020800203c8ee6702bc2dd9fb74238b4e24c4a03711d11e9aefa7b2b0027232d7a020000ef72eb360eeabd005097a0852643636df0f67da75fab739fcf6508ac2719a3594738a55ce90f0c1e31310000
0204002079ac9d10e4a3f4ab1824a9155eddd335df3f36095c336bc4b9984fc9dacce74a94bb5ae397215183f061485a41203b48646771f614c1028ff898da6c1039536b5d38a55cdd06041e461f0000
02020020da097692a1aa37803feff32e5828ef98a003cdb3070c6c767ac3786276998abb5acc74ec02a7025df18349ffe94c410d9d79fb71f0645a86baf2e2ad75ad75356638a55c469d031ebef10000
020600209687b9715c9bf1b7fbd7d8f526aad811b3f0699694c186887b27e8ea7901000061348f56cb02618200ee4edf4f7b3ca889e8179bef721adbf6eb7c92cb8f769e8138a55c1c48011e24050000
02080020ca72894121312b7bb320969352a73c2d3d6189c88e07c2ce1e4a4c8609922488f7eeb9d5d0301bb262197e25bfccc8137a027cd65313ba0f389a2e37681d528e9138a55c986d0b1eba630000
020400202c34701be006b4de5786ad787ccd4ab06f1c74fa9f3547791c191d33d667728a41b7b7b93baeaf63dc21ddfacdb7ac8a63b7196463be7d0eed1b622330971b0ab538a55ca8d2031e01c40000
02060020c798d80fd96b433d8019efb59b5afab7eb05d73790007967239931c887091c62d6fd1c2c323588c7323a47afb7c4500e640f43c88a60155942d393fdfb64ca1cd938a55c832b011e1c760000
020200205fc4c51fa04f895121704c0a09ca19793fb54545acfb8ba627f28d4db323f0fbaacacc81eb6477c9dd3d7aba9a402bf8721e6fb72f582268c09b7a6afc078871f138a55c8d91031eb7e70000
02060020d1e8715a4cbbec9662e60f655efbb4bf3aebdbfa3d6e5e9d59987393bb020000176bb7e754a91817dadbad93717374ba52fee0f0c37283de4011c4f1dc6035fef238a55cb606011e8a3f0000
020200201fe47e692bedd392d9dda697893f14c8ed2b6b6f2be470fa381d054c4b8afd1029b14945eade6a1b5be6a1a39961183fc3f7cf3853ec399985b9e902e37fdd15fc38a55c3b20031e2f290000
02040020f353ba311ae8ffbee7d97bd6426b7bc8c0ed58d44ef096cfb00641f31303000071acab8e737d8843bb9cbdd8465616a99086802984d7f2b45bb2dd8852a1f3994c39a55ca6c3031e8b1d0000
0202002061f6dd876f792df79d8d1ea32e6ef848de7f4f33cef422109aad49aa9fb25f16b5adb387854d925d586689ca013ce55289fd66a93d144d3e85b81b77f16510166e39a55c71bd021e62370000
02060020ea653bd987f02a905b266eb0986561f5848cb187b5a340816a3ae82ea9020000e33b8d0104859594a405b410a509f7cf028d78c556ce2fe40164954fd4e05dcd8939a55c68f9001e44bf0000
0202002042c2b28cbfc723212d1da34a92a9467ea1752f8029216d1e250f4091f81f0408220b1105b5dfb225410536efde0aba11b0060e93a2326ed54806db7f8b90370ea139a55cae67021e42750000
0204002013451a82993eb0137a4d7f19a961e0b02f59b83b54cf15c1ac02ff2c7b0000007785ec13a004f172120e09ade1ca8f6d0b3a2a17ddd51cb535236823fa905684f139a55cd992031e40610000
020600208afb348466ace37d9413873bbcd8506e3eca743cf3346b513b1071c6e375a6bbdfe75b34848bd7fa3eb1cf2d3f0183286cf3b1fe7e3e93f4777048fb6bd0bba8633aa55c5de1001e46d80000
020000200a23dbfd567937cb690212a74b95d02dcf99417c9f6aa5f61e3f7cf06d2e59ac7705b68fa761f7bdbe3ca5dc6aed34ae09e1669819f775d66f54e5d18486ee08dc3aa55cffff0f1e131a0000
0202002090f7e2696f59bc662ab64a6562d02a59089874a66ad25c9e3275a6efb20afb4ffe212cb456edc1caeabd8a799503965ab007062074f26d1beafe7901388ba8dbe13aa55c6541021ecff00000
02000020e658b5747b2f8f75b407791124d557757c6e3777d47bacb05940f18459000000cd71f69a8c433edecd15a1c266074d7232cf6b05279f2f5e5ab3ec6ea8eef982e73aa55cffff0f1e7a0f0000
02020020c96508bea4d5d6a87885db29b2181014eb8faa8819d196c3de3dc015a05fa3550cc023ef4195cc5627e8acd9334891af0c8051e5f3e527567c6ec2c106ce0566113ba55ca3f4011e4eea0000
02080020380c9bfb86d965f270f2f9832aaaafb76999930d93a96fb84b82600a5b010000a14ad540ae33ad39a08d417e7a6591970fdffdf689423f7e6d8249bdbc5181c56f3ba55cffff0f1edec20000
020600205070a1dd2730decad847a799ab33300ec293be0fc99633b83bf088fe893d3aae7fb4bddf644bf5d3b2bb52c4fabb2221a0c6ea1d03d6dbb331238915837c387d863ba55cb8e7001efe020000
02020020661eca8d1e02a7f9e28a2ec7441475af078b5ad4fb93e5ff8db758001d02bd9cb4e7302acd741cf3c0913b66e9e43871f1f01f8e492448765e0c4d009b30bba6903ba55cddce011ecc310000
02080020a256445ce202a09185785ea1568d09cc02e5f4d40b4f9539e12bd7b49e000000146309565ea6c0b2aecbfee0cf0f2e5ce95d23da63686225c33e2ea1b204e674943ba55c4eb90e1ed45e0000
0204002035a62d451b978ef2061e81ce0e3e36410b5adbf5022bf5acb0beca055f1caa0d5d476e72004575e37ec915496853b4a1570f4946e9221dca88d1ca5d7ab8d987d33ba55c9954041ec2da0000
02060020e99b80a14e9ca64f4d35a6e80cdf4232c24139e62600ddc02bdaa4cbdb7bbbb283d961f86507bb38ffc3cd8394cdd86d4b1c4f10e7db1423bc59ec8838b7ae9d2c3ca55c78dd001e219b0000
0206002002f743d10be5232497361927ab703307adfd7ab49b458e5b05d9bd819726f9fa3b80ae510ded044341e32caf766084ee1d118b2e480c86bb7d569f2fb68b233d393ca55c70bd001e570c0000
02080020c5d38eefa966c8e5232990401737a6c719fed7b36df093719dc872b10ae5c3b7ebc6bbd6f3591150a34a9a38549dc10904c6406bb5122c1cf58cd793a8f4c493563ca55c69340e1e03420000
020400201840e9edcd48713959257a131ee5a7760c585d50db7dd22a2a22cc1b0398683e1267b9d36f3e28dc19e6d51eb4e9fef74822e18c8adaeb64e89932db805ae1e1943ca55c8928041e0bea0000
02020020e9b5ff1600b33912ee719f6ccb3d0822f5120278eee4593c719a782960bda5cfd8d3e91323a0f6772d948e749107214ff4fe554c708646e4c0957bdeb76986819e3ca55cf7f3011ebecf0000
02060020eb1d49cb5e71832fa008f8a8adc32d7918b70b152a7ee1f7db600bc5e10000002e0f91948bfcf8ed2379ebb8d476c3266eb892c3876bdd038aaf7388a81fc5ef2f3da55cffff0f1e3efe0000
020200205a892465c69153fa612fee5fb4e6392a81fe6bc90d371a68825d3fe84160ac12a53b2db11172db518ebc8666bc87faefd0d1517d8b4913499d6c9b5ca65879e73a3da55ce9c0011e99300000
02080020d71d804227df24b03a4ee5dd590e1fd6dddc20838cdc113ddacdd4a07301000063a8cef57cfcca4bb3223810cb2f8f0e5518a081c1861b383ec7e6b8f11aafcc663da55c325b0e1e5f4b0000
02020020e7435fb84593f3979e5e3732f1ea3dfeb25d0d235b7af40805407a3e91dab64b85628caa7414704244b70357be15b018c40aaa6529bf32eabb0d88bb3a357cce873da55c9a93011eed4a0000
02040020428005017c2780ca39d185e2d1f52a6da4d17bb445e7ccbb2b52521c85010000dd709339866e11f4b2d02c6eea5106934de743462cf781c2b674687a17d629b0be3da55c1764041e4ec60000
02040020e824494ae03c028870c4cd5197e52c68fcf451545e1ffaebcfa866e6a18f56aebd8b1455c9bfc941550eac31ca492c6f5a4bdc7a6d8dc14d188ed05a32c3507bd93da55c20cb031e9d8c0000
0202002071339df8974afc2f58e029e99d0a4ea28af9d1ccaec823e571c20ee99b15283b35a69869aaea7f0e7254e1098bbcc71c2e7dbfb646198c15fa7439b629836699ef3da55c1a7f011e7b230000
02080020706058565a342678f5e94b4f296d1761731f615e80cafaee990697345900000058ad36b7e202380984e00bb3330500d1e23e7a6c86c07c252736a13474bdaf5fff3da55ca6bf0e1e6da00000
02020020bb0aeb5aaacec59b53dcd4d4a67eda647d624fdb7a66bcee08863880a2c8abeab6d3505c77fafe5588754a6c1d53f0c25d984c394184b50d43c705b2c689f7977d3ea55cffff0f1e92980000
02020020dad3016cc994f03ab62cbcf1379bf466406cc15ebcc9d418ddd5d382210d0000b8664ba2150acfb61dbfc6505ef564df81d1846d7e25d70bfc78af2af253dc7ba33ea55c4c6d011e89cd0000
0202002017db238a73f874c0e2d2640252e227f4161049afe35db554004d95604c0100001c9ee5a727c564ad2effc6a0d2cd0500f5faff97cbf906d16102a202d8784017e33ea55c6a41011eb7530000
02020020cad418139cfc07afefdb397724be9d85c8adb07a500e42ffb2578377bc00000037b302e1759eb9cf671870058ef3dcffcb0a61a9d1bb0971bb64dbc36e000778163fa55c891b011ecf0e0000
02080020cbab27f4c77e683f15784d08f51c40956b42d141c7d65a4c18bda7724c0000005291991cfc682e71c6e28d3d0cc886b932782cb8e52bb1cbfd01c4fd99a1fef4743fa55cf3350f1ed8520000
020400209a9ae3f9f84335a9999c1ff2ba611ad2fa2cfd95bbdbeaaf6207f2e495a370cd619b2e5dc5972602fc5af55cd7a85642251c7273a1e69899bdde24a5266b1964cc3fa55c6a5c041e6c6c0000
02060020438a7f9e6f2ded759f673b1213e34d29600b50b57792d725b48d8a47782eb83be0b4f4f26fc85960b794f97a511146121f1fa343f4f2d407223874d25c8cfa7dd63fa55cf145011eb5660000
02000020dbdea3e20a346d3f225f3082e39320bb603c775b0df280e26c75e0260e5ee81142908817a2923b2ad6a97fe2984519c8781e017f87ea79b9540244357f768101da3fa55cffff0f1ee6150000
020600207a1d5d3faa963b18326e3bc8af5a1ddc6fd063c231adf608a60af3505653cb8d17bfcdd958f23362b77a1b594c96d9c4f346c3c796b46803120c5ce81c7fa4c2ec3fa55c8b2d011eac7f0000
02080020c5493f0aefef55e015064faa46a57a59090e574f446c1f3d49de747c13f77b5810cda1ff7ffefe4a34092f2f2ad62f63aa93abfe2b1aefc58874141c5298c402fc3fa55c12e90f1ef5510000
02020020bd22b7bbd349db1646c3d872259661e896dcfcfa9fa101e6003e199a3f62e5ad6caa132abd67b71e382ed46c61533dd72efff0345f9d2b33bf0b5d67fe5a317f1140a55c3b43011e69250000
020400205b4b433539721a5e8aabbfcf37464c805c0634de5bee1f74a173fec35900000094d5dcec2a23ed382c222731a69399a133fa7567d97510ecebc05faa24dec5952040a55cf5cd041e6e5c0000
02020020bd2f4646f22ba79ab0339d7d36cdb572a1ef5be2a0ea4e14abaf18246f9cc7be382fd566aeb0bf271251c502cec62e2728ee5f4c414ff5918fe2dac942db86244340a55cc830011ec6fc0000
02020020882945377744d38385820b966160a7058e8f4fa2850896290e7e92c9ac00000046c319cfa700e40925069233c1f507ecf1aec72425767a96c21c505f93a3b9377640a55c2814011ef1b10000
02040020e6b2b46764a9d98f81c082425cdda4dd0f140c986c8881bbe022f9fae400000014b381ed3bb7998ee14ba680402c380ac64d5707c6ad0eff16f267c3866745938540a55c27b6041e887a0000
02020020b5e6a2c5e8c6ad4493c5f53dbaa9538e2e6172b7bb98c333b4a4df21e2a336ff4fbcfb3c728507920f4372ada4ff1d739e07049b9b8da0fddbc245a9788c8bd2a840a55c1104011e1bd70000
02040020dcc750816c6dc056663a789c1f470e151b1282983e26be0d8c329f3f680000006c2d1648f993a0516cfd36bc06dbdfdcd9f1e303e7c331840548c4e9ad5a5a35aa40a55ce26f041e209c0000
02040020289ffe3bc92e0dcf18f5dec345ad2129645564b0c827a678a507490180c87f0dc23000865e875932dbf7a883337798b437c123806ab4594242906bf7dc231768b540a55c4503041ea69f0000
02080020e707bc8effe0a37bb61ea61bed02dab2c43202baeb05d61ff105b69fb11de0d4a5a862832dbe5957ce24709298ba9634ab24b16e3c697acb13386a87c19e364b1241a55cffff0f1e0f060000
020200200e1f28e627805636b1ffe20f52d2176207a8797a23eff58291b31d9bf11e819bd9d83d2ed369b670759bb58dd7d4c3e65922267e398023cd4641172331ba329d4041a55ce609011ef46b0000
020800202f65881e97d6aa4d9dc33fa04927b1614888e67f08cf8f4f1037395fdf0000002371088d43f5f0a2ab43763e5d19ac7cc0c556c27fe722ebc4c2efa3ebe8caf78441a55c5e220f1e1d9e0000
020400202723b10cbd0adb749bf15d7b00e55f13e8f7bf40939cefbc60b79ed05bd9107212f3d7d18913f0900cfc20e72e1cb63126e76e289d8f369e4245377c33f7893e8e41a55ca21b041ecee10000
020600200c5dcd37933047a9d546a2360ad0676bc7dabb2243c72c7666de40354689c218a39881f60362d1847e545919fe78a5d3dfae83130fd776ea79284151877e8c5f0142a55c5fc8011e89cd0000
020200204228058e25d5ce351d32471bbdeb2df54254e8617f34f68bb8c9da88c26b5be92a1f61a50ca5b535029fc2e8faea25f29296244695af397b3c259a0b364a7b530c42a55c2e0f011ef3e30000
0204002032b70e010877cd2761af16985d7e92ab4da21b78beee84f807cbfaf3540000006cc954f54da78604d54633c4f36e86d1c4c2f806d0fa2b37dc0f6903c8b4cadc1c42a55c1c0d041e3ff60000
0206002048372ff5a5a30eb70d2ca40c934ed627bcee884dc342a1e10c36bbf2bfed374067dec4f5232598b950f77d0cc8df2ccd7e1f8f2aaf6dfde79517b77a52b6e6733442a55c31c3011ec2ca0000
02060020f6ab4902f64fe3931ac6e5da6b8d274454d8bde72921bc6408da01edb0a24a36e66ea538bfd2725fee19bfdb7cf616b56a08e6f5a2a54c49a72eb7d22a1f49374e42a55c3f9f011e693c0000

View file

@ -0,0 +1,283 @@
#!/usr/bin/env python3
# Copyright (c) 2015-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""
Templates for constructing various sorts of invalid transactions.
These templates (or an iterator over all of them) can be reused in different
contexts to test using a number of invalid transaction types.
Hopefully this makes it easier to get coverage of a full variety of tx
validation checks through different interfaces (AcceptBlock, AcceptToMemPool,
etc.) without repeating ourselves.
Invalid tx cases not covered here can be found by running:
$ diff \
<(grep -IREho "bad-txns[a-zA-Z-]+" src | sort -u) \
<(grep -IEho "bad-txns[a-zA-Z-]+" test/functional/data/invalid_txs.py | sort -u)
"""
import abc
from typing import Optional
from test_framework.messages import (
COutPoint,
CTransaction,
CTxIn,
CTxOut,
MAX_MONEY,
)
from test_framework.blocktools import create_tx_with_script, MAX_BLOCK_SIGOPS
from test_framework.script import (
CScript,
OP_0,
OP_2DIV,
OP_2MUL,
OP_AND,
OP_CAT,
OP_CHECKSIG,
OP_DIV,
OP_INVERT,
OP_LEFT,
OP_LSHIFT,
OP_MOD,
OP_MUL,
OP_OR,
OP_RIGHT,
OP_RSHIFT,
OP_SUBSTR,
OP_TRUE,
OP_XOR,
)
from test_framework.script_util import (
script_to_p2sh_script,
)
basic_p2sh = script_to_p2sh_script(CScript([OP_0]))
class BadTxTemplate:
"""Allows simple construction of a certain kind of invalid tx. Base class to be subclassed."""
__metaclass__ = abc.ABCMeta
# The expected error code given by digibyted upon submission of the tx.
reject_reason: Optional[str] = ""
# Only specified if it differs from mempool acceptance error.
block_reject_reason = ""
# Do we expect to be disconnected after submitting this tx?
expect_disconnect = False
# Is this tx considered valid when included in a block, but not for acceptance into
# the mempool (i.e. does it violate policy but not consensus)?
valid_in_block = False
def __init__(self, *, spend_tx=None, spend_block=None):
self.spend_tx = spend_block.vtx[0] if spend_block else spend_tx
self.spend_avail = sum(o.nValue for o in self.spend_tx.vout)
self.valid_txin = CTxIn(COutPoint(self.spend_tx.sha256, 0), b"", 0xffffffff)
@abc.abstractmethod
def get_tx(self, *args, **kwargs):
"""Return a CTransaction that is invalid per the subclass."""
pass
class OutputMissing(BadTxTemplate):
reject_reason = "bad-txns-vout-empty"
expect_disconnect = True
def get_tx(self):
tx = CTransaction()
tx.vin.append(self.valid_txin)
tx.calc_sha256()
return tx
class InputMissing(BadTxTemplate):
reject_reason = "bad-txns-vin-empty"
expect_disconnect = True
# We use a blank transaction here to make sure
# it is interpreted as a non-witness transaction.
# Otherwise the transaction will fail the
# "surpufluous witness" check during deserialization
# rather than the input count check.
def get_tx(self):
tx = CTransaction()
tx.calc_sha256()
return tx
# The following check prevents exploit of lack of merkle
# tree depth commitment (CVE-2017-12842)
class SizeTooSmall(BadTxTemplate):
reject_reason = "tx-size-small"
expect_disconnect = False
valid_in_block = True
def get_tx(self):
tx = CTransaction()
tx.vin.append(self.valid_txin)
tx.vout.append(CTxOut(0, CScript([OP_TRUE])))
tx.calc_sha256()
return tx
class BadInputOutpointIndex(BadTxTemplate):
# Won't be rejected - nonexistent outpoint index is treated as an orphan since the coins
# database can't distinguish between spent outpoints and outpoints which never existed.
reject_reason = None
expect_disconnect = False
def get_tx(self):
num_indices = len(self.spend_tx.vin)
bad_idx = num_indices + 100
tx = CTransaction()
tx.vin.append(CTxIn(COutPoint(self.spend_tx.sha256, bad_idx), b"", 0xffffffff))
tx.vout.append(CTxOut(0, basic_p2sh))
tx.calc_sha256()
return tx
class DuplicateInput(BadTxTemplate):
reject_reason = 'bad-txns-inputs-duplicate'
expect_disconnect = True
def get_tx(self):
tx = CTransaction()
tx.vin.append(self.valid_txin)
tx.vin.append(self.valid_txin)
tx.vout.append(CTxOut(1, basic_p2sh))
tx.calc_sha256()
return tx
class PrevoutNullInput(BadTxTemplate):
reject_reason = 'bad-txns-prevout-null'
expect_disconnect = True
def get_tx(self):
tx = CTransaction()
tx.vin.append(self.valid_txin)
tx.vin.append(CTxIn(COutPoint(hash=0, n=0xffffffff)))
tx.vout.append(CTxOut(1, basic_p2sh))
tx.calc_sha256()
return tx
class NonexistentInput(BadTxTemplate):
reject_reason = None # Added as an orphan tx.
expect_disconnect = False
def get_tx(self):
tx = CTransaction()
tx.vin.append(CTxIn(COutPoint(self.spend_tx.sha256 + 1, 0), b"", 0xffffffff))
tx.vin.append(self.valid_txin)
tx.vout.append(CTxOut(1, basic_p2sh))
tx.calc_sha256()
return tx
class SpendTooMuch(BadTxTemplate):
reject_reason = 'bad-txns-in-belowout'
expect_disconnect = True
def get_tx(self):
return create_tx_with_script(
self.spend_tx, 0, script_pub_key=basic_p2sh, amount=(self.spend_avail + 1))
class CreateNegative(BadTxTemplate):
reject_reason = 'bad-txns-vout-negative'
expect_disconnect = True
def get_tx(self):
return create_tx_with_script(self.spend_tx, 0, amount=-1)
class CreateTooLarge(BadTxTemplate):
reject_reason = 'bad-txns-vout-toolarge'
expect_disconnect = True
def get_tx(self):
return create_tx_with_script(self.spend_tx, 0, amount=MAX_MONEY + 1)
class CreateSumTooLarge(BadTxTemplate):
reject_reason = 'bad-txns-txouttotal-toolarge'
expect_disconnect = True
def get_tx(self):
tx = create_tx_with_script(self.spend_tx, 0, amount=MAX_MONEY)
tx.vout = [tx.vout[0]] * 2
tx.calc_sha256()
return tx
class InvalidOPIFConstruction(BadTxTemplate):
reject_reason = "mandatory-script-verify-flag-failed (Invalid OP_IF construction)"
expect_disconnect = True
valid_in_block = True
def get_tx(self):
return create_tx_with_script(
self.spend_tx, 0, script_sig=b'\x64' * 35,
amount=(self.spend_avail // 2))
class TooManySigops(BadTxTemplate):
reject_reason = "bad-txns-too-many-sigops"
block_reject_reason = "bad-blk-sigops, out-of-bounds SigOpCount"
expect_disconnect = False
def get_tx(self):
lotsa_checksigs = CScript([OP_CHECKSIG] * (MAX_BLOCK_SIGOPS))
return create_tx_with_script(
self.spend_tx, 0,
script_pub_key=lotsa_checksigs,
amount=1)
def getDisabledOpcodeTemplate(opcode):
""" Creates disabled opcode tx template class"""
def get_tx(self):
tx = CTransaction()
vin = self.valid_txin
vin.scriptSig = CScript([opcode])
tx.vin.append(vin)
tx.vout.append(CTxOut(1, basic_p2sh))
tx.calc_sha256()
return tx
return type('DisabledOpcode_' + str(opcode), (BadTxTemplate,), {
'reject_reason': "disabled opcode",
'expect_disconnect': True,
'get_tx': get_tx,
'valid_in_block' : True
})
# Disabled opcode tx templates (CVE-2010-5137)
DisabledOpcodeTemplates = [getDisabledOpcodeTemplate(opcode) for opcode in [
OP_CAT,
OP_SUBSTR,
OP_LEFT,
OP_RIGHT,
OP_INVERT,
OP_AND,
OP_OR,
OP_XOR,
OP_2MUL,
OP_2DIV,
OP_MUL,
OP_DIV,
OP_MOD,
OP_LSHIFT,
OP_RSHIFT]]
def iter_all_templates():
"""Iterate through all bad transaction template types."""
return BadTxTemplate.__subclasses__()

View file

@ -0,0 +1,58 @@
[
{
"keys": [
"02ff12471208c14bd580709cb2358d98975247d8765f92bc25eab3b2763ed605f8",
"02fe6f0a5a297eb38c391581c4413e084773ea23954d93f7753db7dc0adc188b2f"
],
"sorted_keys": [
"02fe6f0a5a297eb38c391581c4413e084773ea23954d93f7753db7dc0adc188b2f",
"02ff12471208c14bd580709cb2358d98975247d8765f92bc25eab3b2763ed605f8"
],
"script": "522102fe6f0a5a297eb38c391581c4413e084773ea23954d93f7753db7dc0adc188b2f2102ff12471208c14bd580709cb2358d98975247d8765f92bc25eab3b2763ed605f852ae",
"address": "yUE7ErMK6xP5bwRaWiHG6gHXrUm3n6vWPp"
},
{
"keys": [
"02632b12f4ac5b1d1b72b2a3b508c19172de44f6f46bcee50ba33f3f9291e47ed0",
"027735a29bae7780a9755fae7a1c4374c656ac6a69ea9f3697fda61bb99a4f3e77",
"02e2cc6bd5f45edd43bebe7cb9b675f0ce9ed3efe613b177588290ad188d11b404"
],
"sorted_keys": [
"02632b12f4ac5b1d1b72b2a3b508c19172de44f6f46bcee50ba33f3f9291e47ed0",
"027735a29bae7780a9755fae7a1c4374c656ac6a69ea9f3697fda61bb99a4f3e77",
"02e2cc6bd5f45edd43bebe7cb9b675f0ce9ed3efe613b177588290ad188d11b404"
],
"script": "522102632b12f4ac5b1d1b72b2a3b508c19172de44f6f46bcee50ba33f3f9291e47ed021027735a29bae7780a9755fae7a1c4374c656ac6a69ea9f3697fda61bb99a4f3e772102e2cc6bd5f45edd43bebe7cb9b675f0ce9ed3efe613b177588290ad188d11b40453ae",
"address": "yWwiPPRDoYihkWkrUzsBS8sHj2g7B2raaK"
},
{
"keys": [
"030000000000000000000000000000000000004141414141414141414141414141",
"020000000000000000000000000000000000004141414141414141414141414141",
"020000000000000000000000000000000000004141414141414141414141414140",
"030000000000000000000000000000000000004141414141414141414141414140"
],
"sorted_keys": [
"020000000000000000000000000000000000004141414141414141414141414140",
"020000000000000000000000000000000000004141414141414141414141414141",
"030000000000000000000000000000000000004141414141414141414141414140",
"030000000000000000000000000000000000004141414141414141414141414141"
],
"script": "522102000000000000000000000000000000000000414141414141414141414141414021020000000000000000000000000000000000004141414141414141414141414141210300000000000000000000000000000000000041414141414141414141414141402103000000000000000000000000000000000000414141414141414141414141414154ae",
"address": "yM7Z1Nv5i4iH2B17oYnXPs4D6U3EKdqos9"
},
{
"keys": [
"022df8750480ad5b26950b25c7ba79d3e37d75f640f8e5d9bcd5b150a0f85014da",
"03e3818b65bcc73a7d64064106a859cc1a5a728c4345ff0b641209fba0d90de6e9",
"021f2f6e1e50cb6a953935c3601284925decd3fd21bc445712576873fb8c6ebc18"
],
"sorted_keys": [
"021f2f6e1e50cb6a953935c3601284925decd3fd21bc445712576873fb8c6ebc18",
"022df8750480ad5b26950b25c7ba79d3e37d75f640f8e5d9bcd5b150a0f85014da",
"03e3818b65bcc73a7d64064106a859cc1a5a728c4345ff0b641209fba0d90de6e9"
],
"script": "5221021f2f6e1e50cb6a953935c3601284925decd3fd21bc445712576873fb8c6ebc1821022df8750480ad5b26950b25c7ba79d3e37d75f640f8e5d9bcd5b150a0f85014da2103e3818b65bcc73a7d64064106a859cc1a5a728c4345ff0b641209fba0d90de6e953ae",
"address": "yihJAm8p1JRvFQr5BoQ1q8Zcf3W1hMNK8E"
}
]

View file

@ -0,0 +1,86 @@
[
[
"5120eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
{
"asm": "1 eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
"address": "dgbrt1pamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhqqgync2",
"type": "witness_v1_taproot",
"p2sh": "yM9u3hytiPNzAFFQ6v4wHAeeatFgaHgobi"
}
],
[
"5102eeee",
{
"asm": "1 -28398",
"address": "bcrt1pamhqk96edn",
"type": "witness_unknown"
}
],
[
"0020eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
{
"asm": "0 eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
"address": "bcrt1qamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhqgdn98t",
"type": "witness_v0_scripthash",
"p2sh": "2MwGk8mw1GBP6U9D5X8gTvgvXpuknmAK3fo"
}
],
[
"a914eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee87",
{
"asm": "OP_HASH160 eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee OP_EQUAL",
"address": "2NF2b3KS8xXb9XHvbRMXdZh8s5g92rUZHtp",
"type": "scripthash"
}
],
[
"6a00",
{
"asm": "OP_RETURN 0",
"type": "nulldata"
}
],
[
"6aee",
{
"asm": "OP_RETURN OP_UNKNOWN",
"type": "nonstandard"
}
],
[
"6a02ee",
{
"asm": "OP_RETURN [error]",
"type": "nonstandard"
}
],
[
"02eeee",
{
"asm": "-28398",
"type": "nonstandard",
"p2sh": "2N34iiGoUUkVSPiaaTFpJjB1FR9TXQu3PGM",
"segwit": {
"asm": "0 96c2368fc30514a438a8bd909f93c49a1549d77198ccbdb792043b666cb24f42",
"hex": "002096c2368fc30514a438a8bd909f93c49a1549d77198ccbdb792043b666cb24f42",
"address": "bcrt1qjmprdr7rq522gw9ghkgfly7yng25n4m3nrxtmdujqsakvm9jfapqk795l5",
"type": "witness_v0_scripthash",
"p2sh-segwit": "2MtoejEictTQ6XtmHYzoYttt35Ec6krqFKN"
}
}
],
[
"ba",
{
"asm": "OP_CHECKSIGADD",
"type": "nonstandard"
}
],
[
"50",
{
"asm": "OP_RESERVED",
"type": "nonstandard"
}
]
]

View file

@ -0,0 +1,222 @@
{
"blocks": [
"0100000000000000000000000000000000000000000000000000000000000000000000000675a59d4d085d0d9d951d90141dd81286fd64f2800671588df6e4147c22cfc33a22915affff7f20040000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff3c04ffff001d01043456657874612047656e6573697320426c6f636b2032392f4a756e2f32303236202d20565458206e6574776f726b20626567696e73ffffffff01401f0000000000000200ac00000000",
"0202002053394494d2083d59d62ef7366750ea412896617f48ffeb8496a0cda798821d4cdf59be124f792bef963bfb4df6fd56c2224e58c99b52d8cb2e75b07d54b438f8194ae75affff7f200400000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03510101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"020200207f30cd88aa74d3feb74012be411163debcea0467c365d6f85a58fdc39d554002091b6a7e58f3887d533176a029c339566f70807fd7ca7c92772742c97a0809381a4ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03520101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020c67b58a8bd4d9e44bc5f6eece1adc68ff431edc5aed9f8e0447debb131a00078b93f456b9b2b913c5542453f6b3e4e4d88899b140fa3f550fff4c99157ef97fc1a4ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03530101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"0202002057bd7fb6a77488a5617929f456974e9452a8b06c3f1cd9aeab14d2e8457fcc75f884f37b5aee95307de7734676cdfe4a97439145b67127b5ecf33ae2911035221b4ae75affff7f200300000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03540101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020c20ccbcf99b41a3d13e35522b205fe87b077cb738065c04dcd860a306d8c40356ea659c3caa000eb18ea85dff372a8331797ff5f2ef07dfdc690c10ce1fe88571b4ae75affff7f200300000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03550101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020e25cdb8aa1e0a5a5b48a0aed5c918cd494d1499f8cdce4e3e18a6542f9783d3095aa66657e2a01aa0fa2072ff440e62f96c02972bffb6c5f64409b5c9ffbdb781b4ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03560101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020fbef488801b5a9537fd32080bd0ad6fcd68ee62eaf24cbec5330aefd741637337c5eafede59338dec44c52efc5dd49bd9e3bb7a9b80dc97f7623721ccd7752621b4ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03570101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"020200209188441493c944d29a96ff345e9970b9df4a4c2f21e11b8f9ef5ae3bdf785100c6c2bcf7ca59d26c6e7fe294d65471dd1ba74a81ace1033489a405f4ed65a73b1c4ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03580101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"020200202c59883951c863bffa6419345d7d6d36e195975a73d46ca057a566f0d5985e7b501f64faff22f72a9141d58eab212341258b8e0ed1db717b1ab58530d5c90d0a1c4ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03590101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020a755fbdb6316c40fb65f2ee488ff90d2af900219e426d801beffdb8ea068e775670decdaeee47d78460ab8d09eef5dde72acaebddaf16ba45925d6da75d568cd1c4ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff035a0101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020442a46d4dc0acf9f226c49a6282647253ab63031e7dfe29649c2b5932cabc119d5a836d45a5f2a070ca9cb8bfc1aa7a586d4ec6038a4615c2e2e207f44b5b5d31c4ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff035b0101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"020200201e5b63d04c8e191ca049c2217f64e81b977b87a12f1591a37097d8fb46e4bb49d1833c50b68ea0cdc975b95c79b23e47be1ffa6f053ac047369d7d3fc37a4db71c4ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff035c0101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020191d2f9f842ce1464725f93211925c40c0b02c47fa986d32036b47043315596b7ba5f31d88d85aaf4135c3d2b01922970047c3289d45952cd9a955c7bc33cbbb1c4ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff035d0101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020c6e1b4122d463133e7da5bba4b8428665cc115416e47a94053d17e908fa0174116da1f6fdb97febb74e1cddc535db9506ac1433324de129f85ceef08af4fabec1d4ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff035e0101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020e6770212672fa5df31be927a8cbd6054769aa50243d4a74b187fa47a49467c758ea66b46b63443d0974c37e5e901ecec3f24b81de80e0024e21b390e188f59c91d4ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff035f0101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"0202002024f1fc0d8be87ec814aa1476a66ef9b87d37e8ddbf36568f766b1ba5564d5d576a80e249ac5564c789e76390916aff00fdb99db02e9fe11981809de8f02a914b1d4ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03600101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020436ca090e31624144a676abbcf465af1c675088d9eeeb3f04dcba419af9a176b952a3ff5df8c9184a6ea50f9e556f55962584f23429a134f314fb58788f8f8551d4ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401110101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020d322d3603c6621042382a1043e57b7acdeb1bb6dfd985adb923430832450490851876718d8678bdd165ef10b4a5efdc5e56bf834ea21fa7de90b4d25f3f6d49e1d4ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401120101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"020200206c4795aa50abda6a6ad2dd0a36a295298d6e14843420ce38e8e07a968f1db333161ad42e1263702fe88e538b2a84eaa348e3aa97cb83028a806f2b39a95f74cb1d4ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401130101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"0202002007692af23859214b89168171736ac0a7314e1b599a6d96fac2ea60194d587b39a59635655f03946416df7f5403c7de4fadc3b41c28c242e190f7663befff50361e4ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401140101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"020200201ac4f54c039e3ec34a354bfddf84e0348f40e5f4f47d2630d9f7cb1f456b7a67941bbe16fff2c3f999452bc8e75b1747843ed8c119f21ddf67785b4d2076491b1e4ae75affff7f200200000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401150101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020053b9e9b4eced2006b3016f1761c846f42e05757bc9f53ade66e2685728c5659f5b6d770ca49acf85f149cdad1dc857e1c4d1f7cfc064df71a5876015b2479a31e4ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401160101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020a7f523b2a8bf331afdc7274df93590b1257b489eb38980c8edf03518c437496e51c2f9ba55122283eb1b2687022913f1737515d861a65d5bba6ab68aff5da5191e4ae75affff7f200200000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401170101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"020200205ef4649a0d89ccacf53abab6b43930a32dd7e1c33a415e9702756b51bc52a028d7ce157046325cc79bfa6f64bdb5ec54a4a668bd8572c7d4115321dcf35f1d771e4ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401180101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020496c4bc3d079c852616640b44cf1d860103307017a94f6716ad068665de8bb16d4cd714a3dfcebeeec1b3eabbeef0188f2641285e1c0ccaa542634a06c380cb11e4ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401190101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"020200205ac44050d21c5b22e46471cc7285fee1049162eceed4491f805910272d9f606244f872e354a3be2034e2439544c159659a1056b852181aa915d2acb790a9223e1f4ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04011a0101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"020200200828a58e70efadbc8f162aaaff09cb9037cdf00e6d2a4848ac78b3ebac3e1f4d61cd5800bccc75335a629079b45e76bcb6ea616980a405a01b4db18bfc70ba841f4ae75affff7f200300000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04011b0101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"020200208bf9fe934200041028c444ee9135b8400bf96ec20d0d42cc5fcb6eb6dbb2dd4ea08440245250029ea653f9ad87ddd39e09515d7d2f4c8838267dff6ee0c6a5731f4ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04011c0101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020f64623d4d209a1c4eb2decc1dd63814d98da1e285fed9bf907b90222bff59d53d2c2ba30eb5b90fdffcc74a1d387b97664b489a94b3e3e7e2a12e4e9d5880cd31f4ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04011d0101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"0202002094a7148bf572fd90934d6c79c75c5c4362b5f36fe27fc10891df0a5038f68f30bcf127f13de37851a4fc6a8898ad60a53109a68db6a13bf21b9fac80abe608fa1f4ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04011e0101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020cbfcaf714c1ebda16fb70ac456cba41acd07f31f01bfcf2e1c9bb34cae1a6a0985b4b446c39b8e4fb4dccd43647d2c869ae2432af370c324a3594fb18920bbbd1f4ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04011f0101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"020200207fbfa70d3a5b675841fb9e9f3f70f61604d86eb2964093fa754e445ff5f58b0cf9e42e0a4344c15fa4d901d671c871be97e7c3031053bf52d71124d49b6ab523204ae75affff7f200700000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401200101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"0202002022b65939c3ae48e890fc049cfcf0243476ecf5296f256b1530d10a06d8935b08b3d4ebaf666f8a516c2952b5942f3066134cd3ae6acabd963831b757b834bdbe204ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401210101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"020200209858a091482be8fdf09b2baf18c7e7d37e8edcd3cab1cccb38a261ea2949b2176e0279999c4b571a125e41d564743f42ec113df2bf96ee8b7a35c9ad3029cb68204ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401220101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"020200201bfc3ff65fe099724e8692548f6b5620aaf66880a6f2f113869c5f7e6e5c6359a0f9b388b5356edc43fad7462ebb2de5c2c56223bd4203c381ef83f6a483d4b2204ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401230101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020dc27fb3e884c1c0cb9dde187cfd64c40e1fce05d67baf76c6d63bd57170e1902bda3e19ca56a72662b409e8f1739e03ee60b63f7812f142882e947567f2e99ce204ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401240101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020cec57c5fcc233d65e5977a2bb9e5f293836c1360293ee27943e52bf4f1c2be393ea0e6278cc65109a0f342f3ea3e0eb6fb610072bfd4911a6a0a3d9f1b34bd83204ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401250101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020e4c099441cb2c8375ae7dc5644855c361236ab9101deaecb0670d90fae934f4c11502bcea98442ef6cc45ef3c96c3f2eb69707f6beca11ea3cfcced3e385a236214ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401260101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"0202002090797cc7f5d93cae3a3a3d6f35e5dc7ae73982e2664c23fe246d9e38f08d3f7c9a00d878552ed528c5d011faa49e432ccc203ae17aa5ddc557daa7318003312b214ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401270101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020581157bd6b09abb4659ebc23ae11dcb0d2c5a6ee8d55c3d10260357defbd9252754a33d248904fdf0346138d0d85ab67e3a1eceddea6a6389139090b3fb18e57214ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401280101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020e0955c61742792b8bb5443fdf541673ed32a3d7e5b363558c886f91e4f2f1537da52cb3eb7e0d33ce0e98175438befded84dcd9a860a010131c1133b752dcdfd214ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401290101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"020200201659a7334b45031d27bb7e7642cf8e52664105d154dd0c81907baae17e3e5b027b53219467c156166cc538a7a98e359e8fa240ceab4c69d0edad09022665b4b6214ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04012a0101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020f345f57f572d6862e338a9a6bcf59c32fa1f350c8993dbee83a9ca5a4a27a24c386702b95ef1dbae04112d8f60596bfbdcb33dfc10b04e98cae7b94e9c6bc0bc214ae75affff7f200300000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04012b0101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"0202002049b7d83fa6a5d0b632dcd4ea2629570eb93374d59c0d4ef7c3c7ed2473d13b67de595de479e3bc22f89be1e150530f15efa8536e082193955f6e51931bf3f8e9224ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04012c0101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"020200207b2322465ddd8f0aa7a034b04fb431036e931b42cd392e93205da93ded59ef59d50ee857add519497cb279405fb94a39e81de6b93b94ae721e569286f981fd37224ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04012d0101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"020200207a823b6c24a45fecee87c458b393e881a0a652258403a3e33decc5c9ba084a516220eedd3066241025fd35ed8d233566cfdf56e8c22f017ce9bf1b45cafee631224ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04012e0101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020c8ac357c4abc6ed11d3aec115844b572e816d7d9691d39a63aa07d1d8929f139e8485b8cfab91b5e6cd119df914e49ae187d6217318be10351f23a6044352dc5224ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04012f0101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020a173a9710a783034eaa95d0477294035242707721ed38bf183e6c8758d9a786be6bf02869abf7bab0390f68eb8efce80a5d15358987c107e8996cbc3b5115d1d224ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401300101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"020200207054ea0b3d56523ae8c9f955ae7d3cc77e7c00b454e888e2e456b69ec6de915004d5e84fb1d97c6195d130ae15e9f2eaa220a0b9a0cf88212d351e401c911bcd224ae75affff7f200500000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401310101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020a478361b214589f03a8347797884e561f56b939df72cc8166c718f9c67e07a4031d989270443a16b8f1f897077053067e15a71c6e59c21a56919635af241b034234ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401320101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020bac3847f8985f42f5fdfc7ac6527401a8974d13434a6055d7f6477ae92273552db966f28f7d052ff71d46632f2f1ef9b9f420c706f18424e6a693c177cbb19ed234ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401330101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020a352ef9b9f6183b0547aa85d2cd8fa17d94fa5018a500aff9c14ca7366bebd03fc60aa83a61a0d4c91368ae96ad5da505e95565f81889f356b0edd65fb200e40234ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401340101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020d8a3ed41d36e9f850cdddf356dde4707137b714e00fbcc345d69b31eb799d207a14915656db13d85965afbca5a77ca76e87a96bf389c6db62f37caa64751d90a234ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401350101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"020200207fae559293ef0dcc385ffb47482b7fe2abee93b4d33ab62b6f1b97bd4851d849914d35c5e682ae6fc7583d92703bd7f2a2d8ca2688f557a98b4c6dc778025f78234ae75affff7f200300000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401360101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"0202002029dbacca8e93627c2c6483588a936b2358fb2550bd192cb3b3c84a41e7fa8c41dfee0864062738658c70893a60c285cec3f51a574976dfd7435965df995c9491234ae75affff7f200400000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401370101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"020200207ffb6e660812b98740d088f0904c1c91e87231c5f35dd4d3d51641bf22d6f56f9d491cbdc9153407d69d214e6371e3d3f709390b430aaef6a33c347f426e80bb244ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401380101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020f145ab1bb1795b4e89b65f5919ffe2d65387c7df101e4687ef80f59b57c02e5b0c9eb2b22593152f8e1ef9065f0d27a2e05623636b6534e66ddf67d2d4ab3296244ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401390101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020e5fb00c9603626882147fc3c1d7feb78e4ada9f4a4865a43aeb9da02d210af4cc2b46a121a6590fca97f55e925e6cb901d93b193ac528ff3fb9b78040d8627ed244ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04013a0101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"020200207535a974ecca5aae0c064a403eb474f9f320e1b7fa36d16f4a85c366af334c715040260537235bac9ca9373da20555e3e7dd6b7a38a446ca01680d4d4cce0ffc244ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04013b0101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020b9b063c03859a49763f1c1fda52366ba1442c4b8d1ff3950276472a28490e21f14fb6d0113cbb9c5079da626aa9d99ed94be9506912cd5bc480f491627f1eda7244ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04013c0101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020a59a0b062b1f3b5d49faf72cf3553e07696a30350f10c301efd65cd179708d549736e8ce060848f7f65c37198cff588fdd0af066eba85379f9ed881b92c3451d244ae75affff7f200300000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04013d0101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020f29c2e33eb56a1ca89a3b5c1661c5804bf7f812315ccd11442c1475cde2b1451b05a79c1c0c8ab1bf4c5c63ed4e8759a5b2766892ffdb97ee9a345b4d822aab8254ae75affff7f200200000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04013e0101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"020200208450feb47323512c90b51a0548796ef82ae99c913d54497eb96e2f79cb3b64316f61738ae548bdcaccfb24ff2d00bd077af7cc40991b017ece4bab7da8abf4a3254ae75affff7f200200000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04013f0101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020548900f4c1f104877ba311fe9c1472c30f505b2dcbb146220e6a9d8516efd176940f2b24f53d68d296308f1c97900e4a28994587d6f93653ed63e32e7ffe86d1254ae75affff7f200300000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401400101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020add1e937cded764334fd742e17e47d6256973d2f1da26b7dcb52ccd635b56206584b870ad75b900414204ec40c1adc278b3ab5ccbd3ebf374f3a0501d5a7493f254ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401410101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"020200201d0f862dbf27d37ad78cdb10e9195e2bb60fbfbee8faaa4bfb0d9c64b72a582e0e51fe7dc74ae25643d7e907a53b6b349cb32e07d4984f29fe729e51c21e6a17254ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401420101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"020200208cc758b8545db3a2da70cbdafc10744abe9622b70da1c039b5a7301e8f62184f0bcda2ec4a7866f2116a4f22935448d3a5e13a524eecf942193eaa492891d65c254ae75affff7f200400000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401430101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020857a4045c721bab1cee3bf4acd1088a4b7682004a01f3f02a590ef6b7f7efd0866d9e9fcb7c5e07947365b60369495bba5304e058d8cf51abfa7bae34f67a754264ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401440101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020db426757ae7dfa7a33b29e78e4f050ed7741efbbed3d200777b19935b03aba53a8e8f428f01ae68fe665586f3ca9c13518aed9e513bb0649f20c2fab11deb096264ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401450101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020c48c35e1aea5ea471130cb01bd9b7fa6ac01882e51d04cda1c226293d812a4739339e9c7aac33003452515f8275e5fb9d638eb19c801d34a2bf14f6a62677822264ae75affff7f200200000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401460101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020eabf80bbae545bcc1a2b2c2b75e5797a42512797107143c941139b195b0f7334f1416f6d47304ba74b1d4891d6bbe87727700fe50ece00c4f0bef9fbf5a15d40264ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401470101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"0202002001e232828d2ec272aae38fa154be396eb64352bd5209d2347783a38b64722622bb2e95cfbc57d3de586fdb2fc0ef85f0b0cbd48529066928ada2f5b225a964f9264ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401480101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"020200207100cda2a51693795f63b79089c15024a1df3e15d7c7e2f4740bd6e5b353b117a109f82343a4e6a2f218c9fe09331e088dcbd3cb2205bf38c62d8a20081bddfe264ae75affff7f200200000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401490101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020e29ebd9a8107e028521eb3d0d190e50b10616e440c69472b02079bebb97d3d4fceef205d47472064ff305215cd2f6cc0870043a2cb5a7aea31ec61253f6dcbea274ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04014a0101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020653089a559b8e51cf275ce18e458fcb7d90f11299975d97a963532bf44ddab1b36097e0567e812ab62fe2f398d2cf239b3e682f42640732c383fdab127bd8fbb274ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04014b0101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020b089da1d699271a53a3da1e378d33b47706d4de4f1e1b82612b0921f6609d26daec3730d4bd9ea5a8c28b6998c743e353a184e994b603d1f9e31022ff30159a6274ae75affff7f200300000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04014c0101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020479bae7cb9a3f8d1b64eefd5aaab3c543acbc39d10c4d2999fee1683d1fb1a0996eb4fb24140352356b5e5ef4c33b314dfc09aea6c9bcc88adec124f98a5f018274ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04014d0101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"0202002090b203a44e4cbbab5f481762abe0f9423d9ff782ea420c13a3b10d86fef9162e89e0e0b1ec515a4482f936d1e6ad38b5c9ae3b4831f0c0b504d82d61c27f1a4a274ae75affff7f200300000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04014e0101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"0202002031fa43344a08d2a4f1d5c438d9ec2685c854c594c15c60da203761803b12f935c51dbb0d9e51d2c5478d3c83dd25df9f8e054c9168426e73ec734e6aef98ce9f274ae75affff7f200500000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04014f0101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"020200208c9cb2cac5da6c05059b42a8516543a7b1d29ab314fe66f651639d9533501c6ad23e9488c5d2571d3382c0b4d43ff4964f90d503346ff21f1dbb97cde53f18d2284ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401500101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"0202002037d6aa2d323ff418571d9135de0533bd07c7d239a1e9c551eea70861741f1238f6cf20923f412b933d3c2c96734f08a611d29e69167b870eb5a0abd2d2025e75284ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401510101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020170f7c8e48bd420d77cd538b75bb07ade45b261ecbc5ac228bfa6c29662bcc54a802884f998c755d2ea0df0ee75f8ed7241e094bb730c085196c3c048a30095a284ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401520101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020ebd12a3c873200f27d732ae125da90e9b325c9535e2d5185dbc60c119b6c9d206d8247e1a54b759341e4abd002cfc7d13c6f8afedc21c8de1a175d6ecd5c59b6284ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401530101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"020200207c8e13c97075967aa80ec58075268f36c6bf52727b783f52a4cd87bdd3e6522f83786c02e8ed99a8b1c838de0a5c2ef0891e9eae4335155d85892280cf11fa03284ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401540101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"0202002014b06bcb9382c137cd9e2e3468bea09a774eac870af4b6b8a8b33540b6e7c248738e64a18812a8f86fb0cd43ac090d830d11aafad89edcc9e77454d83d7ba705284ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401550101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020b5f1b5727982a7fe084fc8fcf6037cf4eedcee73eb79824fe749922523ee756d432267bf6beb0cac630856f8601fd6b30a5f0d5027a76a67583c66f2ab36ff18294ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401560101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"020200200d2fc890c63ead1d7e3da33949f2296b0053557ac0ffb666587a8abc144da95d5351cb7ef74fc715ab17eb72281b79d93c2770762df64a1b20c05ec9fd2811f8294ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401570101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"020200209f1a920e1215bc7b99b3aa2cbf2b0e407e28b847b99dbea42866cc394f4ed838212ef7eacbc376594f867ceb28d45794bb1df509b9a17ec7352945a535165725294ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401580101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020072821ff6f1e57f2d84defc939e7384dc722d415d81559c03078ceb432c5ce52218b525a7f069a06f8dc9b56bb187f981840880effc13b28fbc766b7915f15b8294ae75affff7f200200000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401590101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"020200206835e1dbed008da256976143731e37fb8a7bf3f5ad57d8134eaac4da6553a11122f7a0ae384781b84c0a6b85d649a7e4a7f1b2714d54d21505b674e58f43fa0f294ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04015a0101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"0202002011077a04ff0dec4fa6114dd3b8f204a44f3b3dd2055230bad3ab71d823672767baeb408582c01cefcaf53d5b27d78648a02dab3594434c52a12600d37fdbbc19294ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04015b0101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020b92d965331f583fd3996cb49cfc896e1c50efaf6703e0a6d2f097002cb3ce845a80531318aefa24ac31152a0f618128d87def05b712779a47cf04a4ebe3feb652a4ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04015c0101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020384a306855a1c3cfd6590e684e6e5bfcbf7918380cd9b83f79c13154421dfc63a1fe8146a0ec9f0a86d29c7af3890373b64a846fba8c1b436117f35bd36853412a4ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04015d0101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020d948a64f9f89aed30ac4226af615f18b07ac3dc9687a1c6fae2da009fed9ad02d79a7da94afaae83d7e81dd6af2a56b2b064599b82b1cd34205006576a4a8e012a4ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04015e0101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020ed834d3da5524a57a64c6f6b6c9c580b373f11a25ffdbfbc1ea47f092f43ec593a3237ffc345e577e3c29de56968d41acd660a4d29223c945962a032f821c6592a4ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04015f0101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"0202002090b28e294a799149e41d1630f3bc2b779d644d2908200becc4b347aac311ca106d2900fccba59d929e5697eed01f0b12cf84f66a9244df8f10db1323043aa23b2a4ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401600101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"0202002064f72190cc39dcfbaadf76c5e881a4118ce5a5683645cd40cae18fda7ed3d20f6f5f1ae0375b5590f7951501abd21491722764935fdc1839e8caff280a757c0c2a4ae75affff7f200200000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401610101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"020200205e5da19f772d65addbb90f381c25ab267b7f5dddef53c4cd936abbfe5f11782af992aa4925a49a9612ce087c62e7b444fcf6e1d09f3b4cd2bda70229cd1837882b4ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401620101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020ef296bcdebd8025e96ed2c88b223fa86cd387d7ca4413e8ff1e8c6be6c6d31512e8ffa8a3440a292354aba8c44cf24e71d9518d3e3375d4c6aaf6e056ddf5dd42b4ae75affff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401630101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"0202002076b0fc0d4610f01a9476b9df04340830988f8ce67a33fd115dbc5defe26c7434ce7ad181c96fb7949bc0aee66dd0424e587e37bb69b442a8ee469bb32d1169802b4ae75affff7f200200000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401640101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020a28da4d963bf74274136a34ae67adb6f38082e9ca62b8dcb4b9aecba939bcb06080d9ab73f9a8547fef148efd8e5b91c9ce91567008b7aa62bfbb6ee192a5a932b4ae75affff7f200100000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401650101ffffffff0200f2052a01000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000",
"02020020d865a04564d73bddfc6b173b7c0104c878fc7927b05a1a23164ac588fea116231a7bdb4dbdb101fbf9f3a36a2828351110c5b808c44558083218d34d232d30662b4ae75affff7f200000000002020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401660101ffffffff02d0751b2a010000001976a9149e0fba4896c9771390bf1c931e368cf3494a24da88ac0000000000000000266a24aa21a9edcf507b1d9f6f32cb06877244a46281c1ac92505e1a663805dec805a44cf518f7012000000000000000000000000000000000000000000000000000000000000000000000000002000000000101df59be124f792bef963bfb4df6fd56c2224e58c99b52d8cb2e75b07d54b438f80000000000feffffff023046853b00000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d77480500286bee00000000160014c676a02eb2c0ba8711f2757c3e3d0fa5bb491a9702473044022041469e4418397b6b6d48461d7e0953db9f7c87d1792878250328c31bfda2e9bc0220178bd0228a2bd6afc19fdcaba6d5058fe4eb569e8aa1370ffec906d7ddc3f4a70121035930317c0509a1fa2ad873cb084953123c6e47c39aa1a79f93c79e225239a7f665000000",
"02020020aab6e370a298205c3a73baa075bc5a7ff77695ddb26fa317225210a0a913c56e1ee52d4b101df0c5bec461822fa84e0a689262e722b97415bc7f78c3034cc2752b4ae75affff7f200000000004020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401670101ffffffff02707d462a010000001976a9149e0fba4896c9771390bf1c931e368cf3494a24da88ac0000000000000000266a24aa21a9ed38eb48c35300206ba7e252b8c9cd7911d7696e89cd5653796d66ab0e8044e692012000000000000000000000000000000000000000000000000000000000000000000000000002000000000101b736f2201907c088de7a5bc26b3e3b63c8972ca6773e917878e9277bca6795120100000000feffffff02005ed0b2000000001600143b61cde74460e85df98644005876bb625b829be23046853b00000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d7748050247304402201b4cb8c288815e7513ad654cfdfb8ad1de24162ddee31c21b9ef70434c82b3ae02203b80fcb1e98e3c31be4e93b986f4d25b0064ee54f927bb169ea71a3aa5cb265c012103e2d651938a8a1d64c2b979bef439cdbac31175a5f6cba89f8960f4014a3020d66600000002000000000101a2192716c4bb284b1614faf6e3cf7e093b269d87cac3bbd7d205e5b6b4d74a470000000000feffffff023010207700000000160014b4cfd5106d6d9264a4769b3588ec678d1a8e2eec00ca9a3b00000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d774805024730440220464b99f396799f2709c3661f46e4ca8b43fa0bdae2594fc9f7d2a552b8d7bb53022037042293f5b61fb7eb6824b6d36344652fd4a8d42d2e07b898d1eb27acd01ada0121020cd2d43ef532f80916a38dbdc1a1dbe8af06e99155b00a1b3ea49e2ad53472591700000002000000000101d70029992cf94f55b797c85ccb99daf4b4537bbbc66e2e5358daec1c7baff99b0000000000feffffff02305de00500000000160014f7bb255ec8507c698c3a0a45ddb97e7d0d774805302f2a710000000016001419b4d49a7e17176f303364d645627f3132cc4ba20247304402202dd893af4977681da2b05c42a3b02b6f214b49556fe556c66599fdf0dd56886b02201a376fd6974ee2146590dbc4b21658f9737c37d6771a74231f2e63bfe71967300121024e9b7603d6fdf59c28a6ebf8c4020f2e0389543e7daca20c3ab4e7ec7229bea666000000"
],
"mocktime": 1525107225,
"stats": [
{
"avgfee": 0,
"avgfeerate": 0,
"avgtxsize": 0,
"blockhash": "2316a1fe88c54a16231a5ab02779fc78c804017c3b176bfcdd3bd76445a065d8",
"feerate_percentiles": [
0,
0,
0,
0,
0
],
"height": 101,
"ins": 0,
"maxfee": 0,
"maxfeerate": 0,
"maxtxsize": 0,
"medianfee": 0,
"mediantime": 1525107242,
"mediantxsize": 0,
"minfee": 0,
"minfeerate": 0,
"mintxsize": 0,
"outs": 2,
"subsidy": 5000000000,
"swtotal_size": 0,
"swtotal_weight": 0,
"swtxs": 0,
"time": 1525107243,
"total_out": 0,
"total_size": 0,
"total_weight": 0,
"totalfee": 0,
"txs": 1,
"utxo_increase": 2,
"utxo_size_inc": 160
},
{
"avgfee": 1410000,
"avgfeerate": 10053,
"avgtxsize": 222,
"blockhash": "6ec513a9a010522217a36fb2dd9576f77f5abc75a0ba733a5c2098a270e3b6aa",
"feerate_percentiles": [
10053,
10053,
10053,
10053,
10053
],
"height": 102,
"ins": 1,
"maxfee": 1410000,
"maxfeerate": 10053,
"maxtxsize": 222,
"medianfee": 1410000,
"mediantime": 1525107242,
"mediantxsize": 222,
"minfee": 1410000,
"minfeerate": 10053,
"mintxsize": 222,
"outs": 4,
"subsidy": 5000000000,
"swtotal_size": 222,
"swtotal_weight": 561,
"swtxs": 1,
"time": 1525107243,
"total_out": 4998590000,
"total_size": 222,
"total_weight": 561,
"totalfee": 1410000,
"txs": 2,
"utxo_increase": 3,
"utxo_size_inc": 235
},
{
"avgfee": 1410000,
"avgfeerate": 10053,
"avgtxsize": 222,
"blockhash": "1ab6c5024f61dc83a127a2f42ef1778155e1034a924a4b68f0febd4f367b616f",
"feerate_percentiles": [
10053,
10053,
10053,
10053,
10053
],
"height": 103,
"ins": 3,
"maxfee": 1410000,
"maxfeerate": 10053,
"maxtxsize": 222,
"medianfee": 1410000,
"mediantime": 1525107243,
"mediantxsize": 222,
"minfee": 1410000,
"minfeerate": 10053,
"mintxsize": 222,
"outs": 8,
"subsidy": 5000000000,
"swtotal_size": 666,
"swtotal_weight": 1683,
"swtxs": 3,
"time": 1525107243,
"total_out": 8994360000,
"total_size": 666,
"total_weight": 1683,
"totalfee": 4230000,
"txs": 4,
"utxo_increase": 5,
"utxo_size_inc": 379
}
]
}

View file

@ -0,0 +1,139 @@
{
"invalid" : [
"AgAAAAEmgXE3Ht/yhek3re6ks3t4AAwFZsuzrWRkFxPKQhcb9gAAAABqRzBEAiBwsiRRI+a/R01gxbUMBD1MaRpdJDXwmjSnZiqdwlF5CgIgATKcqdrPKAvfMHQOwDkEIkIsgctFg5RXrrdvwS7dlbMBIQJlfRGNM1e44PTCzUbbezn22cONmnCry5st5dyNv+TOMf7///8C09/1BQAAAAAZdqkU0MWZA8W6woaHYOkP1SGkZlqnZSCIrADh9QUAAAAAF6kUNUXm4zuDLEcFDyTT7rk8nAOUi8eHsy4TAA==",
"cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAA==",
"cHNidP8BAP0KAQIAAAACqwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QAAAAAakcwRAIgR1lmF5fAGwNrJZKJSGhiGDR9iYZLcZ4ff89X0eURZYcCIFMJ6r9Wqk2Ikf/REf3xM286KdqGbX+EhtdVRs7tr5MZASEDXNxh/HupccC1AaZGoqg7ECy0OIEhfKaC3Ibi1z+ogpL+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAABASAA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHhwEEFgAUhdE1N/LiZUBaNNuvqePdoB+4IwgAAAA=",
"cHNidP8AAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAA==",
"cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAQA/AgAAAAH//////////////////////////////////////////wAAAAAA/////wEAAAAAAAAAAANqAQAAAAAAAAAA",
"cHNidP8CAAFVAgAAAAEnmiMjpd+1H8RfIg+liw/BPh4zQnkqhdfjbNYzO1y8OQAAAAAA/////wGgWuoLAAAAABl2qRT/6cAGEJfMO2NvLLBGD6T8Qn0rRYisAAAAAAABASCVXuoLAAAAABepFGNFIA9o0YnhrcDfHE0W6o8UwNvrhyICA7E0HMunaDtq9PEjjNbpfnFn1Wn6xH8eSNR1QYRDVb1GRjBDAiAEJLWO/6qmlOFVnqXJO7/UqJBkIkBVzfBwtncUaUQtBwIfXI6w/qZRbWC4rLM61k7eYOh4W/s6qUuZvfhhUduamgEBBCIAIHcf0YrUWWZt1J89Vk49vEL0yEd042CtoWgWqO1IjVaBAQVHUiEDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUYhA95V0eHayAXj+KWMH7+blMAvPbqv4Sf+/KSZXyb4IIO9Uq4iBgOxNBzLp2g7avTxI4zW6X5xZ9Vp+sR/HkjUdUGEQ1W9RhC0prpnAAAAgAAAAIAEAACAIgYD3lXR4drIBeP4pYwfv5uUwC89uq/hJ/78pJlfJvggg70QtKa6ZwAAAIAAAACABQAAgAAA",
"cHNidP8BAFUCAAAAASeaIyOl37UfxF8iD6WLD8E+HjNCeSqF1+Ns1jM7XLw5AAAAAAD/////AaBa6gsAAAAAGXapFP/pwAYQl8w7Y28ssEYPpPxCfStFiKwAAAAAAAIBACCVXuoLAAAAABepFGNFIA9o0YnhrcDfHE0W6o8UwNvrhyICA7E0HMunaDtq9PEjjNbpfnFn1Wn6xH8eSNR1QYRDVb1GRjBDAiAEJLWO/6qmlOFVnqXJO7/UqJBkIkBVzfBwtncUaUQtBwIfXI6w/qZRbWC4rLM61k7eYOh4W/s6qUuZvfhhUduamgEBBCIAIHcf0YrUWWZt1J89Vk49vEL0yEd042CtoWgWqO1IjVaBAQVHUiEDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUYhA95V0eHayAXj+KWMH7+blMAvPbqv4Sf+/KSZXyb4IIO9Uq4iBgOxNBzLp2g7avTxI4zW6X5xZ9Vp+sR/HkjUdUGEQ1W9RhC0prpnAAAAgAAAAIAEAACAIgYD3lXR4drIBeP4pYwfv5uUwC89uq/hJ/78pJlfJvggg70QtKa6ZwAAAIAAAACABQAAgAAA",
"cHNidP8BAFUCAAAAASeaIyOl37UfxF8iD6WLD8E+HjNCeSqF1+Ns1jM7XLw5AAAAAAD/////AaBa6gsAAAAAGXapFP/pwAYQl8w7Y28ssEYPpPxCfStFiKwAAAAAAAEBIJVe6gsAAAAAF6kUY0UgD2jRieGtwN8cTRbqjxTA2+uHIQIDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUYwQwIgBCS1jv+qppThVZ6lyTu/1KiQZCJAVc3wcLZ3FGlELQcCH1yOsP6mUW1guKyzOtZO3mDoeFv7OqlLmb34YVHbmpoBAQQiACB3H9GK1FlmbdSfPVZOPbxC9MhHdONgraFoFqjtSI1WgQEFR1IhA7E0HMunaDtq9PEjjNbpfnFn1Wn6xH8eSNR1QYRDVb1GIQPeVdHh2sgF4/iljB+/m5TALz26r+En/vykmV8m+CCDvVKuIgYDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUYQtKa6ZwAAAIAAAACABAAAgCIGA95V0eHayAXj+KWMH7+blMAvPbqv4Sf+/KSZXyb4IIO9ELSmumcAAACAAAAAgAUAAIAAAA==",
"cHNidP8BAFUCAAAAASeaIyOl37UfxF8iD6WLD8E+HjNCeSqF1+Ns1jM7XLw5AAAAAAD/////AaBa6gsAAAAAGXapFP/pwAYQl8w7Y28ssEYPpPxCfStFiKwAAAAAAAEBIJVe6gsAAAAAF6kUY0UgD2jRieGtwN8cTRbqjxTA2+uHIgIDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUZGMEMCIAQktY7/qqaU4VWepck7v9SokGQiQFXN8HC2dxRpRC0HAh9cjrD+plFtYLisszrWTt5g6Hhb+zqpS5m9+GFR25qaAQIEACIAIHcf0YrUWWZt1J89Vk49vEL0yEd042CtoWgWqO1IjVaBAQVHUiEDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUYhA95V0eHayAXj+KWMH7+blMAvPbqv4Sf+/KSZXyb4IIO9Uq4iBgOxNBzLp2g7avTxI4zW6X5xZ9Vp+sR/HkjUdUGEQ1W9RhC0prpnAAAAgAAAAIAEAACAIgYD3lXR4drIBeP4pYwfv5uUwC89uq/hJ/78pJlfJvggg70QtKa6ZwAAAIAAAACABQAAgAAA",
"cHNidP8BAFUCAAAAASeaIyOl37UfxF8iD6WLD8E+HjNCeSqF1+Ns1jM7XLw5AAAAAAD/////AaBa6gsAAAAAGXapFP/pwAYQl8w7Y28ssEYPpPxCfStFiKwAAAAAAAEBIJVe6gsAAAAAF6kUY0UgD2jRieGtwN8cTRbqjxTA2+uHIgIDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUZGMEMCIAQktY7/qqaU4VWepck7v9SokGQiQFXN8HC2dxRpRC0HAh9cjrD+plFtYLisszrWTt5g6Hhb+zqpS5m9+GFR25qaAQEEIgAgdx/RitRZZm3Unz1WTj28QvTIR3TjYK2haBao7UiNVoECBQBHUiEDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUYhA95V0eHayAXj+KWMH7+blMAvPbqv4Sf+/KSZXyb4IIO9Uq4iBgOxNBzLp2g7avTxI4zW6X5xZ9Vp+sR/HkjUdUGEQ1W9RhC0prpnAAAAgAAAAIAEAACAIgYD3lXR4drIBeP4pYwfv5uUwC89uq/hJ/78pJlfJvggg70QtKa6ZwAAAIAAAACABQAAgAAA",
"cHNidP8BAFUCAAAAASeaIyOl37UfxF8iD6WLD8E+HjNCeSqF1+Ns1jM7XLw5AAAAAAD/////AaBa6gsAAAAAGXapFP/pwAYQl8w7Y28ssEYPpPxCfStFiKwAAAAAAAEBIJVe6gsAAAAAF6kUY0UgD2jRieGtwN8cTRbqjxTA2+uHIgIDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUZGMEMCIAQktY7/qqaU4VWepck7v9SokGQiQFXN8HC2dxRpRC0HAh9cjrD+plFtYLisszrWTt5g6Hhb+zqpS5m9+GFR25qaAQEEIgAgdx/RitRZZm3Unz1WTj28QvTIR3TjYK2haBao7UiNVoEBBUdSIQOxNBzLp2g7avTxI4zW6X5xZ9Vp+sR/HkjUdUGEQ1W9RiED3lXR4drIBeP4pYwfv5uUwC89uq/hJ/78pJlfJvggg71SriEGA7E0HMunaDtq9PEjjNbpfnFn1Wn6xH8eSNR1QYRDVb0QtKa6ZwAAAIAAAACABAAAgCIGA95V0eHayAXj+KWMH7+blMAvPbqv4Sf+/KSZXyb4IIO9ELSmumcAAACAAAAAgAUAAIAAAA==",
"cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAIAALsCAAAAAarXOTEBi9JfhK5AC2iEi+CdtwbqwqwYKYur7nGrZW+LAAAAAEhHMEQCIFj2/HxqM+GzFUjUgcgmwBW9MBNarULNZ3kNq2bSrSQ7AiBKHO0mBMZzW2OT5bQWkd14sA8MWUL7n3UYVvqpOBV9ugH+////AoDw+gIAAAAAF6kUD7lGNCFpa4LIM68kHHjBfdveSTSH0PIKJwEAAAAXqRQpynT4oI+BmZQoGFyXtdhS5AY/YYdlAAAAAQfaAEcwRAIgdAGK1BgAl7hzMjwAFXILNoTMgSOJEEjn282bVa1nnJkCIHPTabdA4+tT3O+jOCPIBwUUylWn3ZVE8VfBZ5EyYRGMAUgwRQIhAPYQOLMI3B2oZaNIUnRvAVdyk0IIxtJEVDk82ZvfIhd3AiAFbmdaZ1ptCgK4WxTl4pB02KJam1dgvqKBb2YZEKAG6gFHUiEClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8hAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXUq4AAQEgAMLrCwAAAAAXqRS39fr0Dj1ApaRZsds1NfK3L6kh6IcBByMiACCMI1MXN0O1ld+0oHtyuo5C43l9p06H/n2ddJfjsgKJAwEI2gQARzBEAiBi63pVYQenxz9FrEq1od3fb3B1+xJ1lpp/OD7/94S8sgIgDAXbt0cNvy8IVX3TVscyXB7TCRPpls04QJRdsSIo2l8BRzBEAiBl9FulmYtZon/+GnvtAWrx8fkNVLOqj3RQql9WolEDvQIgf3JHA60e25ZoCyhLVtT/y4j3+3Weq74IqjDym4UTg9IBR1IhAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcIQI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc1KuACICA6mkw39ZltOqJdusa1cK8GUDlEkpQkYLNUdT7Z7spYdxENkMak8AAACAAAAAgAQAAIAAIgICf2OZdX0u/1WhNq0CxoSxg4tlVuXxtrNCgqlLa1AFEJYQ2QxqTwAAAIAAAACABQAAgAA=",
"cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAACBwDaAEcwRAIgdAGK1BgAl7hzMjwAFXILNoTMgSOJEEjn282bVa1nnJkCIHPTabdA4+tT3O+jOCPIBwUUylWn3ZVE8VfBZ5EyYRGMAUgwRQIhAPYQOLMI3B2oZaNIUnRvAVdyk0IIxtJEVDk82ZvfIhd3AiAFbmdaZ1ptCgK4WxTl4pB02KJam1dgvqKBb2YZEKAG6gFHUiEClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8hAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXUq4AAQEgAMLrCwAAAAAXqRS39fr0Dj1ApaRZsds1NfK3L6kh6IcBByMiACCMI1MXN0O1ld+0oHtyuo5C43l9p06H/n2ddJfjsgKJAwEI2gQARzBEAiBi63pVYQenxz9FrEq1od3fb3B1+xJ1lpp/OD7/94S8sgIgDAXbt0cNvy8IVX3TVscyXB7TCRPpls04QJRdsSIo2l8BRzBEAiBl9FulmYtZon/+GnvtAWrx8fkNVLOqj3RQql9WolEDvQIgf3JHA60e25ZoCyhLVtT/y4j3+3Weq74IqjDym4UTg9IBR1IhAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcIQI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc1KuACICA6mkw39ZltOqJdusa1cK8GUDlEkpQkYLNUdT7Z7spYdxENkMak8AAACAAAAAgAQAAIAAIgICf2OZdX0u/1WhNq0CxoSxg4tlVuXxtrNCgqlLa1AFEJYQ2QxqTwAAAIAAAACABQAAgAA=",
"cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAABB9oARzBEAiB0AYrUGACXuHMyPAAVcgs2hMyBI4kQSOfbzZtVrWecmQIgc9Npt0Dj61Pc76M4I8gHBRTKVafdlUTxV8FnkTJhEYwBSDBFAiEA9hA4swjcHahlo0hSdG8BV3KTQgjG0kRUOTzZm98iF3cCIAVuZ1pnWm0KArhbFOXikHTYolqbV2C+ooFvZhkQoAbqAUdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSrgABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohwEHIyIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAggA2gQARzBEAiBi63pVYQenxz9FrEq1od3fb3B1+xJ1lpp/OD7/94S8sgIgDAXbt0cNvy8IVX3TVscyXB7TCRPpls04QJRdsSIo2l8BRzBEAiBl9FulmYtZon/+GnvtAWrx8fkNVLOqj3RQql9WolEDvQIgf3JHA60e25ZoCyhLVtT/y4j3+3Weq74IqjDym4UTg9IBR1IhAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcIQI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc1KuACICA6mkw39ZltOqJdusa1cK8GUDlEkpQkYLNUdT7Z7spYdxENkMak8AAACAAAAAgAQAAIAAIgICf2OZdX0u/1WhNq0CxoSxg4tlVuXxtrNCgqlLa1AFEJYQ2QxqTwAAAIAAAACABQAAgAA=",
"cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAABB9oARzBEAiB0AYrUGACXuHMyPAAVcgs2hMyBI4kQSOfbzZtVrWecmQIgc9Npt0Dj61Pc76M4I8gHBRTKVafdlUTxV8FnkTJhEYwBSDBFAiEA9hA4swjcHahlo0hSdG8BV3KTQgjG0kRUOTzZm98iF3cCIAVuZ1pnWm0KArhbFOXikHTYolqbV2C+ooFvZhkQoAbqAUdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSrgABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohwEHIyIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQjaBABHMEQCIGLrelVhB6fHP0WsSrWh3d9vcHX7EnWWmn84Pv/3hLyyAiAMBdu3Rw2/LwhVfdNWxzJcHtMJE+mWzThAlF2xIijaXwFHMEQCIGX0W6WZi1mif/4ae+0BavHx+Q1Us6qPdFCqX1aiUQO9AiB/ckcDrR7blmgLKEtW1P/LiPf7dZ6rvgiqMPKbhROD0gFHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4AIQIDqaTDf1mW06ol26xrVwrwZQOUSSlCRgs1R1PtnuylhxDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA",
"cHNidP8BAHMCAAAAATAa6YblFqHsisW0vGVz0y+DtGXiOtdhZ9aLOOcwtNvbAAAAAAD/////AnR7AQAAAAAAF6kUA6oXrogrXQ1Usl1jEE5P/s57nqKHYEOZOwAAAAAXqRS5IbG6b3IuS/qDtlV6MTmYakLsg4cAAAAAAAEBHwDKmjsAAAAAFgAU0tlLZK4IWH7vyO6xh8YB6Tn5A3wCAwABAAAAAAEAFgAUYunpgv/zTdgjlhAxawkM0qO3R8sAAQAiACCHa62DLx0WgBXtQSMqnqZaGBXZ7xPA74dZ9ktbKyeKZQEBJVEhA7fOI6AcW0vwCmQlN836uzFbZoMyhnR471EwnSvVf4qHUa4A",
"cHNidP8BAHMCAAAAATAa6YblFqHsisW0vGVz0y+DtGXiOtdhZ9aLOOcwtNvbAAAAAAD/////AnR7AQAAAAAAF6kUA6oXrogrXQ1Usl1jEE5P/s57nqKHYEOZOwAAAAAXqRS5IbG6b3IuS/qDtlV6MTmYakLsg4cAAAAAAAEBHwDKmjsAAAAAFgAU0tlLZK4IWH7vyO6xh8YB6Tn5A3wAAgAAFgAUYunpgv/zTdgjlhAxawkM0qO3R8sAAQAiACCHa62DLx0WgBXtQSMqnqZaGBXZ7xPA74dZ9ktbKyeKZQEBJVEhA7fOI6AcW0vwCmQlN836uzFbZoMyhnR471EwnSvVf4qHUa4A",
"cHNidP8BAHMCAAAAATAa6YblFqHsisW0vGVz0y+DtGXiOtdhZ9aLOOcwtNvbAAAAAAD/////AnR7AQAAAAAAF6kUA6oXrogrXQ1Usl1jEE5P/s57nqKHYEOZOwAAAAAXqRS5IbG6b3IuS/qDtlV6MTmYakLsg4cAAAAAAAEBHwDKmjsAAAAAFgAU0tlLZK4IWH7vyO6xh8YB6Tn5A3wAAQAWABRi6emC//NN2COWEDFrCQzSo7dHywABACIAIIdrrYMvHRaAFe1BIyqeploYFdnvE8Dvh1n2S1srJ4plIQEAJVEhA7fOI6AcW0vwCmQlN836uzFbZoMyhnR471EwnSvVf4qHUa4A",
"cHNidP8BAHMCAAAAAbiWoY6pOQepFsEGhUPXaulX9rvye2NH+NrdlAHg+WgpAQAAAAD/////AkBLTAAAAAAAF6kUqWwXCcLM5BN2zoNqMNT5qMlIi7+HQEtMAAAAAAAXqRSVF/in2XNxAlN1OSxkyp0z+Wtg2YcAAAAAAAEBIBNssgAAAAAAF6kUamsvautR8hRlMRY6OKNTx03DK96HAQcXFgAUo8u1LWpHprjt/uENAwBpGZD0UH0BCGsCRzBEAiAONfH3DYiw67ZbylrsxCF/XXpVwyWBRgofyRbPslzvwgIgIKCsWw5sHSIPh1icNvcVLZLHWj6NA7Dk+4Os2pOnMbQBIQPGStfYHPtyhpV7zIWtn0Q4GXv5gK1zy/tnJ+cBXu4iiwABABYAFMwmJQEz+HDpBEEabxJ5PogPsqZRAAEAFgAUyCrGc3h3FYCmiIspbv2pSTKZ5jU",
"cHNidP8BACoCAAAAAAFAQg8AAAAAABepFG6Rty1Vk+fUOR4v9E6R6YXDFkHwhwAAAAAAAQEAAQEBagA=",
"cHNidP8BACoCAAAAAAFAQg8AAAAAABepFG6Rty1Vk+fUOR4v9E6R6YXDFkHwhwAAAAAAAQAAAQABagA=",
"cHNidP8BADMBAAAAAREREREREREREREREREREREREfrK3hERERERERERERERfwAAAAD/////AAAAAAAAAQEJ//////////8AAQEJAADK/gAAAAAAAA==",
"cHNidP8BADMBAAAAAREREREREREREREREREREREREfrK3hERERERERERERERfwAAAAD/////AAAAAAAAAQMErd7f7gEDBAEAAAAA",
"cHNidP8BADMBAAAAAREREREREREREREREREREREREfrK3hERERERERERERERfwAAAAD/////AAAAAAAAAQQAAQQBagA=",
"cHNidP8BADMBAAAAAREREREREREREREREREREREREfrK3hERERERERERERERfwAAAAD/////AAAAAAAAAQEJAOH1BQAAAAAAAQUAAQUBUQA=",
"cHNidP8BADMBAAAAAREREREREREREREREREREREREfrK3hERERERERERERERfwAAAAD/////AAAAAAAAAQcAAQcBUQA=",
"cHNidP8BADMBAAAAAREREREREREREREREREREREREfrK3hERERERERERERERfwAAAAD/////AAAAAAAAAQEJAOH1BQAAAAAAAQgBAAEIAwEBUQA="
],
"valid" : [
"cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAAAA",
"cHNidP8BAKACAAAAAqsJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6W3vkAAAAAAD+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAEHakcwRAIgR1lmF5fAGwNrJZKJSGhiGDR9iYZLcZ4ff89X0eURZYcCIFMJ6r9Wqk2Ikf/REf3xM286KdqGbX+EhtdVRs7tr5MZASEDXNxh/HupccC1AaZGoqg7ECy0OIEhfKaC3Ibi1z+ogpIAAQEgAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4cBBBYAFIXRNTfy4mVAWjTbr6nj3aAfuCMIAAAA",
"cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAQMEAQAAAAAAAA==",
"cHNidP8BAKACAAAAAqsJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6W3vkAAAAAAD+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAEA3wIAAAABJoFxNx7f8oXpN63upLN7eAAMBWbLs61kZBcTykIXG/YAAAAAakcwRAIgcLIkUSPmv0dNYMW1DAQ9TGkaXSQ18Jo0p2YqncJReQoCIAEynKnazygL3zB0DsA5BCJCLIHLRYOUV663b8Eu3ZWzASECZX0RjTNXuOD0ws1G23s59tnDjZpwq8ubLeXcjb/kzjH+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQEgAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4cBBBYAFIXRNTfy4mVAWjTbr6nj3aAfuCMIACICAurVlmh8qAYEPtw94RbN8p1eklfBls0FXPaYyNAr8k6ZELSmumcAAACAAAAAgAIAAIAAIgIDlPYr6d8ZlSxVh3aK63aYBhrSxKJciU9H2MFitNchPQUQtKa6ZwAAAIABAACAAgAAgAA=",
"cHNidP8BAFUCAAAAASeaIyOl37UfxF8iD6WLD8E+HjNCeSqF1+Ns1jM7XLw5AAAAAAD/////AaBa6gsAAAAAGXapFP/pwAYQl8w7Y28ssEYPpPxCfStFiKwAAAAAAAEBIJVe6gsAAAAAF6kUY0UgD2jRieGtwN8cTRbqjxTA2+uHIgIDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUZGMEMCIAQktY7/qqaU4VWepck7v9SokGQiQFXN8HC2dxRpRC0HAh9cjrD+plFtYLisszrWTt5g6Hhb+zqpS5m9+GFR25qaAQEEIgAgdx/RitRZZm3Unz1WTj28QvTIR3TjYK2haBao7UiNVoEBBUdSIQOxNBzLp2g7avTxI4zW6X5xZ9Vp+sR/HkjUdUGEQ1W9RiED3lXR4drIBeP4pYwfv5uUwC89uq/hJ/78pJlfJvggg71SriIGA7E0HMunaDtq9PEjjNbpfnFn1Wn6xH8eSNR1QYRDVb1GELSmumcAAACAAAAAgAQAAIAiBgPeVdHh2sgF4/iljB+/m5TALz26r+En/vykmV8m+CCDvRC0prpnAAAAgAAAAIAFAACAAAA=",
"cHNidP8BACoCAAAAAAFAQg8AAAAAABepFG6Rty1Vk+fUOR4v9E6R6YXDFkHwhwAAAAAAAA=="
],
"creator" : [
{
"inputs" : [
{
"txid":"75ddabb27b8845f5247975c8a5ba7c6f336c4570708ebe230caf6db5217ae858",
"vout":0
},
{
"txid":"1dea7cd05979072a3578cab271c02244ea8a090bbb46aa680a65ecd027048d83",
"vout":1
}
],
"outputs" : [
{
"vtxrt1qmpwzkuwsqc9snjvgdt4czhjsnywa5yjdudruy5":1.49990000
},
{
"vtxrt1qqzh2ngh97ru8dfvgma25d6r595wcwqy0y4ue2q": 1
}
],
"result" : "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAAAAAA="
}
],
"signer" : [
{
"privkeys" : [
"ebmWPdzuhpdDptq4Te56e5ADfSUH6WnJdGCwYLbH3WVKeDhdn5rS",
"ednu6eDUHibgRq2EsrCd5irBRQawHemAuH86vU1CrsFYv2hbhXfu"
],
"psbt" : "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAABBEdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSriIGApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/ENkMak8AAACAAAAAgAAAAIAiBgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU21xDZDGpPAAAAgAAAAIABAACAAQMEAQAAAAABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohwEEIgAgjCNTFzdDtZXftKB7crqOQuN5fadOh/59nXSX47ICiQMBBUdSIQMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3CECOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnNSriIGAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zENkMak8AAACAAAAAgAMAAIAiBgMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3BDZDGpPAAAAgAAAAIACAACAAQMEAQAAAAAiAgOppMN/WZbTqiXbrGtXCvBlA5RJKUJGCzVHU+2e7KWHcRDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA",
"result" : "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAAiAgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgf0cwRAIgdAGK1BgAl7hzMjwAFXILNoTMgSOJEEjn282bVa1nnJkCIHPTabdA4+tT3O+jOCPIBwUUylWn3ZVE8VfBZ5EyYRGMAQEDBAEAAAABBEdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSriIGApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/ENkMak8AAACAAAAAgAAAAIAiBgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU21xDZDGpPAAAAgAAAAIABAACAAAEBIADC6wsAAAAAF6kUt/X69A49QKWkWbHbNTXyty+pIeiHIgIDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtxHMEQCIGLrelVhB6fHP0WsSrWh3d9vcHX7EnWWmn84Pv/3hLyyAiAMBdu3Rw2/LwhVfdNWxzJcHtMJE+mWzThAlF2xIijaXwEBAwQBAAAAAQQiACCMI1MXN0O1ld+0oHtyuo5C43l9p06H/n2ddJfjsgKJAwEFR1IhAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcIQI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc1KuIgYCOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnMQ2QxqTwAAAIAAAACAAwAAgCIGAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcENkMak8AAACAAAAAgAIAAIAAIgIDqaTDf1mW06ol26xrVwrwZQOUSSlCRgs1R1Ptnuylh3EQ2QxqTwAAAIAAAACABAAAgAAiAgJ/Y5l1fS7/VaE2rQLGhLGDi2VW5fG2s0KCqUtrUAUQlhDZDGpPAAAAgAAAAIAFAACAAA=="
},
{
"privkeys" : [
"efokiyDhZqnE7BSy2W2JhFexF47ZE6fCqamXcPaVmdweuNR1HuEp",
"eat4cruyM7tT1XCo2ZijAusQBa2VuGGTKqToFcKUcY62JxGC94eR"
],
"psbt" : "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAABBEdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSriIGApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/ENkMak8AAACAAAAAgAAAAIAiBgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU21xDZDGpPAAAAgAAAAIABAACAAQMEAQAAAAABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohwEEIgAgjCNTFzdDtZXftKB7crqOQuN5fadOh/59nXSX47ICiQMBBUdSIQMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3CECOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnNSriIGAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zENkMak8AAACAAAAAgAMAAIAiBgMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3BDZDGpPAAAAgAAAAIACAACAAQMEAQAAAAAiAgOppMN/WZbTqiXbrGtXCvBlA5RJKUJGCzVHU+2e7KWHcRDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA",
"result" : "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAAiAgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU210cwRAIgYxqYn+c4qSrQGYYCMxLBkhT+KAKznly8GsNniAbGksMCIDnbbDh70mdxbf2z1NjaULjoXSEzJrp8faqkwM5B65IjAQEDBAEAAAABBEdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSriIGApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/ENkMak8AAACAAAAAgAAAAIAiBgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU21xDZDGpPAAAAgAAAAIABAACAAAEBIADC6wsAAAAAF6kUt/X69A49QKWkWbHbNTXyty+pIeiHIgICOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnNHMEQCIGX0W6WZi1mif/4ae+0BavHx+Q1Us6qPdFCqX1aiUQO9AiB/ckcDrR7blmgLKEtW1P/LiPf7dZ6rvgiqMPKbhROD0gEBAwQBAAAAAQQiACCMI1MXN0O1ld+0oHtyuo5C43l9p06H/n2ddJfjsgKJAwEFR1IhAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcIQI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc1KuIgYCOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnMQ2QxqTwAAAIAAAACAAwAAgCIGAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcENkMak8AAACAAAAAgAIAAIAAIgIDqaTDf1mW06ol26xrVwrwZQOUSSlCRgs1R1Ptnuylh3EQ2QxqTwAAAIAAAACABAAAgAAiAgJ/Y5l1fS7/VaE2rQLGhLGDi2VW5fG2s0KCqUtrUAUQlhDZDGpPAAAAgAAAAIAFAACAAA=="
},
{
"privkeys" : [
"eat4cruyM7tT1XCo2ZijAusQBa2VuGGTKqToFcKUcY62JxGC94eR"
],
"psbt" : "cHNidP8BAKACAAAAAqsJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6W3vkAAAAAAD+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAEBItPf9QUAAAAAGXapFNSO0xELlAFMsRS9Mtb00GbcdCVriKwAAQEgAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4cBBBYAFIXRNTfy4mVAWjTbr6nj3aAfuCMIACICAurVlmh8qAYEPtw94RbN8p1eklfBls0FXPaYyNAr8k6ZELSmumcAAACAAAAAgAIAAIAAIgIDlPYr6d8ZlSxVh3aK63aYBhrSxKJciU9H2MFitNchPQUQtKa6ZwAAAIABAACAAgAAgAA=",
"result" : "cHNidP8BAKACAAAAAqsJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6W3vkAAAAAAD+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAEBItPf9QUAAAAAGXapFNSO0xELlAFMsRS9Mtb00GbcdCVriKwAAQEgAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4cBBBYAFIXRNTfy4mVAWjTbr6nj3aAfuCMIACICAurVlmh8qAYEPtw94RbN8p1eklfBls0FXPaYyNAr8k6ZELSmumcAAACAAAAAgAIAAIAAIgIDlPYr6d8ZlSxVh3aK63aYBhrSxKJciU9H2MFitNchPQUQtKa6ZwAAAIABAACAAgAAgAA="
},
{
"privkeys" : [
"efokiyDhZqnE7BSy2W2JhFexF47ZE6fCqamXcPaVmdweuNR1HuEp",
"eat4cruyM7tT1XCo2ZijAusQBa2VuGGTKqToFcKUcY62JxGC94eR"
],
"psbt" : "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAAiAgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU210gwRQIhAPYQOLMI3B2oZaNIUnRvAVdyk0IIxtJEVDk82ZvfIhd3AiAFbmdaZ1ptCgK4WxTl4pB02KJam1dgvqKBb2YZEKAG6gEBAwQBAAAAAQRHUiEClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8hAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXUq8iBgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfxDZDGpPAAAAgAAAAIAAAACAIgYC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtcQ2QxqTwAAAIAAAACAAQAAgAABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohyICAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zRzBEAiBl9FulmYtZon/+GnvtAWrx8fkNVLOqj3RQql9WolEDvQIgf3JHA60e25ZoCyhLVtT/y4j3+3Weq74IqjDym4UTg9IBAQMEAQAAAAEEIgAgjCNTFzdDtZXftKB7crqOQuN5fadOh/59nXSX47ICiQMBBUdSIQMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3CECOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnNSriIGAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zENkMak8AAACAAAAAgAMAAIAiBgMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3BDZDGpPAAAAgAAAAIACAACAACICA6mkw39ZltOqJdusa1cK8GUDlEkpQkYLNUdT7Z7spYdxENkMak8AAACAAAAAgAQAAIAAIgICf2OZdX0u/1WhNq0CxoSxg4tlVuXxtrNCgqlLa1AFEJYQ2QxqTwAAAIAAAACABQAAgAA=",
"result" : "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAAiAgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU210gwRQIhAPYQOLMI3B2oZaNIUnRvAVdyk0IIxtJEVDk82ZvfIhd3AiAFbmdaZ1ptCgK4WxTl4pB02KJam1dgvqKBb2YZEKAG6gEBAwQBAAAAAQRHUiEClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8hAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXUq8iBgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfxDZDGpPAAAAgAAAAIAAAACAIgYC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtcQ2QxqTwAAAIAAAACAAQAAgAABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohyICAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zRzBEAiBl9FulmYtZon/+GnvtAWrx8fkNVLOqj3RQql9WolEDvQIgf3JHA60e25ZoCyhLVtT/y4j3+3Weq74IqjDym4UTg9IBAQMEAQAAAAEEIgAgjCNTFzdDtZXftKB7crqOQuN5fadOh/59nXSX47ICiQMBBUdSIQMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3CECOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnNSriIGAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zENkMak8AAACAAAAAgAMAAIAiBgMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3BDZDGpPAAAAgAAAAIACAACAACICA6mkw39ZltOqJdusa1cK8GUDlEkpQkYLNUdT7Z7spYdxENkMak8AAACAAAAAgAQAAIAAIgICf2OZdX0u/1WhNq0CxoSxg4tlVuXxtrNCgqlLa1AFEJYQ2QxqTwAAAIAAAACABQAAgAA="
},
{
"privkeys" : [
"efokiyDhZqnE7BSy2W2JhFexF47ZE6fCqamXcPaVmdweuNR1HuEp",
"eat4cruyM7tT1XCo2ZijAusQBa2VuGGTKqToFcKUcY62JxGC94eR"
],
"psbt" : "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAAiAgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU210gwRQIhAPYQOLMI3B2oZaNIUnRvAVdyk0IIxtJEVDk82ZvfIhd3AiAFbmdaZ1ptCgK4WxTl4pB02KJam1dgvqKBb2YZEKAG6gEBAwQBAAAAAQRHUiEClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8hAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXUq4iBgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfxDZDGpPAAAAgAAAAIAAAACAIgYC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtcQ2QxqTwAAAIAAAACAAQAAgAABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohyICAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zRzBEAiBl9FulmYtZon/+GnvtAWrx8fkNVLOqj3RQql9WolEDvQIgf3JHA60e25ZoCyhLVtT/y4j3+3Weq74IqjDym4UTg9IBAQMEAQAAAAEEIgAgjCNTFzdDtZXftKB7crqOQuN5fadOh/59nXSX47ICiQABBUdSIQMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3CECOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnNSriIGAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zENkMak8AAACAAAAAgAMAAIAiBgMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3BDZDGpPAAAAgAAAAIACAACAACICA6mkw39ZltOqJdusa1cK8GUDlEkpQkYLNUdT7Z7spYdxENkMak8AAACAAAAAgAQAAIAAIgICf2OZdX0u/1WhNq0CxoSxg4tlVuXxtrNCgqlLa1AFEJYQ2QxqTwAAAIAAAACABQAAgAA=",
"result" : "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAAiAgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU210gwRQIhAPYQOLMI3B2oZaNIUnRvAVdyk0IIxtJEVDk82ZvfIhd3AiAFbmdaZ1ptCgK4WxTl4pB02KJam1dgvqKBb2YZEKAG6gEBAwQBAAAAAQRHUiEClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8hAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXUq4iBgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfxDZDGpPAAAAgAAAAIAAAACAIgYC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtcQ2QxqTwAAAIAAAACAAQAAgAABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohyICAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zRzBEAiBl9FulmYtZon/+GnvtAWrx8fkNVLOqj3RQql9WolEDvQIgf3JHA60e25ZoCyhLVtT/y4j3+3Weq74IqjDym4UTg9IBAQMEAQAAAAEEIgAgjCNTFzdDtZXftKB7crqOQuN5fadOh/59nXSX47ICiQABBUdSIQMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3CECOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnNSriIGAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zENkMak8AAACAAAAAgAMAAIAiBgMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3BDZDGpPAAAAgAAAAIACAACAACICA6mkw39ZltOqJdusa1cK8GUDlEkpQkYLNUdT7Z7spYdxENkMak8AAACAAAAAgAQAAIAAIgICf2OZdX0u/1WhNq0CxoSxg4tlVuXxtrNCgqlLa1AFEJYQ2QxqTwAAAIAAAACABQAAgAA="
},
{
"privkeys" : [
"efokiyDhZqnE7BSy2W2JhFexF47ZE6fCqamXcPaVmdweuNR1HuEp",
"eat4cruyM7tT1XCo2ZijAusQBa2VuGGTKqToFcKUcY62JxGC94eR"
],
"psbt" : "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAAiAgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU210gwRQIhAPYQOLMI3B2oZaNIUnRvAVdyk0IIxtJEVDk82ZvfIhd3AiAFbmdaZ1ptCgK4WxTl4pB02KJam1dgvqKBb2YZEKAG6gEBAwQBAAAAAQRHUiEClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8hAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXUq4iBgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfxDZDGpPAAAAgAAAAIAAAACAIgYC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtcQ2QxqTwAAAIAAAACAAQAAgAABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohyICAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zRzBEAiBl9FulmYtZon/+GnvtAWrx8fkNVLOqj3RQql9WolEDvQIgf3JHA60e25ZoCyhLVtT/y4j3+3Weq74IqjDym4UTg9IBAQMEAQAAAAEEIgAgjCNTFzdDtZXftKB7crqOQuN5fadOh/59nXSX47ICiQMBBUdSIQMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3CECOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnNSrSIGAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zENkMak8AAACAAAAAgAMAAIAiBgMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3BDZDGpPAAAAgAAAAIACAACAACICA6mkw39ZltOqJdusa1cK8GUDlEkpQkYLNUdT7Z7spYdxENkMak8AAACAAAAAgAQAAIAAIgICf2OZdX0u/1WhNq0CxoSxg4tlVuXxtrNCgqlLa1AFEJYQ2QxqTwAAAIAAAACABQAAgAA=",
"result" : "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAAiAgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU210gwRQIhAPYQOLMI3B2oZaNIUnRvAVdyk0IIxtJEVDk82ZvfIhd3AiAFbmdaZ1ptCgK4WxTl4pB02KJam1dgvqKBb2YZEKAG6gEBAwQBAAAAAQRHUiEClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8hAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXUq4iBgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfxDZDGpPAAAAgAAAAIAAAACAIgYC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtcQ2QxqTwAAAIAAAACAAQAAgAABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohyICAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zRzBEAiBl9FulmYtZon/+GnvtAWrx8fkNVLOqj3RQql9WolEDvQIgf3JHA60e25ZoCyhLVtT/y4j3+3Weq74IqjDym4UTg9IBAQMEAQAAAAEEIgAgjCNTFzdDtZXftKB7crqOQuN5fadOh/59nXSX47ICiQMBBUdSIQMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3CECOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnNSrSIGAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zENkMak8AAACAAAAAgAMAAIAiBgMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3BDZDGpPAAAAgAAAAIACAACAACICA6mkw39ZltOqJdusa1cK8GUDlEkpQkYLNUdT7Z7spYdxENkMak8AAACAAAAAgAQAAIAAIgICf2OZdX0u/1WhNq0CxoSxg4tlVuXxtrNCgqlLa1AFEJYQ2QxqTwAAAIAAAACABQAAgAA="
}
],
"combiner" : [
{
"combine" : [
"cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAAiAgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgf0cwRAIgdAGK1BgAl7hzMjwAFXILNoTMgSOJEEjn282bVa1nnJkCIHPTabdA4+tT3O+jOCPIBwUUylWn3ZVE8VfBZ5EyYRGMAQEDBAEAAAABBEdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSriIGApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/ENkMak8AAACAAAAAgAAAAIAiBgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU21xDZDGpPAAAAgAAAAIABAACAAAEBIADC6wsAAAAAF6kUt/X69A49QKWkWbHbNTXyty+pIeiHIgIDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtxHMEQCIGLrelVhB6fHP0WsSrWh3d9vcHX7EnWWmn84Pv/3hLyyAiAMBdu3Rw2/LwhVfdNWxzJcHtMJE+mWzThAlF2xIijaXwEBAwQBAAAAAQQiACCMI1MXN0O1ld+0oHtyuo5C43l9p06H/n2ddJfjsgKJAwEFR1IhAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcIQI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc1KuIgYCOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnMQ2QxqTwAAAIAAAACAAwAAgCIGAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcENkMak8AAACAAAAAgAIAAIAAIgIDqaTDf1mW06ol26xrVwrwZQOUSSlCRgs1R1Ptnuylh3EQ2QxqTwAAAIAAAACABAAAgAAiAgJ/Y5l1fS7/VaE2rQLGhLGDi2VW5fG2s0KCqUtrUAUQlhDZDGpPAAAAgAAAAIAFAACAAA==",
"cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAAiAgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU210cwRAIgYxqYn+c4qSrQGYYCMxLBkhT+KAKznly8GsNniAbGksMCIDnbbDh70mdxbf2z1NjaULjoXSEzJrp8faqkwM5B65IjAQEDBAEAAAABBEdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSriIGApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/ENkMak8AAACAAAAAgAAAAIAiBgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU21xDZDGpPAAAAgAAAAIABAACAAAEBIADC6wsAAAAAF6kUt/X69A49QKWkWbHbNTXyty+pIeiHIgICOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnNHMEQCIGX0W6WZi1mif/4ae+0BavHx+Q1Us6qPdFCqX1aiUQO9AiB/ckcDrR7blmgLKEtW1P/LiPf7dZ6rvgiqMPKbhROD0gEBAwQBAAAAAQQiACCMI1MXN0O1ld+0oHtyuo5C43l9p06H/n2ddJfjsgKJAwEFR1IhAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcIQI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc1KuIgYCOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnMQ2QxqTwAAAIAAAACAAwAAgCIGAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcENkMak8AAACAAAAAgAIAAIAAIgIDqaTDf1mW06ol26xrVwrwZQOUSSlCRgs1R1Ptnuylh3EQ2QxqTwAAAIAAAACABAAAgAAiAgJ/Y5l1fS7/VaE2rQLGhLGDi2VW5fG2s0KCqUtrUAUQlhDZDGpPAAAAgAAAAIAFAACAAA=="
],
"result" : "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAAiAgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgf0cwRAIgdAGK1BgAl7hzMjwAFXILNoTMgSOJEEjn282bVa1nnJkCIHPTabdA4+tT3O+jOCPIBwUUylWn3ZVE8VfBZ5EyYRGMASICAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXRzBEAiBjGpif5zipKtAZhgIzEsGSFP4oArOeXLwaw2eIBsaSwwIgOdtsOHvSZ3Ft/bPU2NpQuOhdITMmunx9qqTAzkHrkiMBAQMEAQAAAAEER1IhApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/IQLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU211KuIgYClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8Q2QxqTwAAAIAAAACAAAAAgCIGAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXENkMak8AAACAAAAAgAEAAIAAAQEgAMLrCwAAAAAXqRS39fr0Dj1ApaRZsds1NfK3L6kh6IciAgMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3EcwRAIgYut6VWEHp8c/RaxKtaHd329wdfsSdZaafzg+//eEvLICIAwF27dHDb8vCFV901bHMlwe0wkT6ZbNOECUXbEiKNpfASICAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zRzBEAiBl9FulmYtZon/+GnvtAWrx8fkNVLOqj3RQql9WolEDvQIgf3JHA60e25ZoCyhLVtT/y4j3+3Weq74IqjDym4UTg9IBAQMEAQAAAAEEIgAgjCNTFzdDtZXftKB7crqOQuN5fadOh/59nXSX47ICiQMBBUdSIQMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3CECOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnNSriIGAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zENkMak8AAACAAAAAgAMAAIAiBgMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3BDZDGpPAAAAgAAAAIACAACAACICA6mkw39ZltOqJdusa1cK8GUDlEkpQkYLNUdT7Z7spYdxENkMak8AAACAAAAAgAQAAIAAIgICf2OZdX0u/1WhNq0CxoSxg4tlVuXxtrNCgqlLa1AFEJYQ2QxqTwAAAIAAAACABQAAgAA="
},
{
"combine" : [
"cHNidP8BAD8CAAAAAf//////////////////////////////////////////AAAAAAD/////AQAAAAAAAAAAA2oBAAAAAAAKDwECAwQFBgcICQ8BAgMEBQYHCAkKCwwNDg8ACg8BAgMEBQYHCAkPAQIDBAUGBwgJCgsMDQ4PAAoPAQIDBAUGBwgJDwECAwQFBgcICQoLDA0ODwA=",
"cHNidP8BAD8CAAAAAf//////////////////////////////////////////AAAAAAD/////AQAAAAAAAAAAA2oBAAAAAAAKDwECAwQFBgcIEA8BAgMEBQYHCAkKCwwNDg8ACg8BAgMEBQYHCBAPAQIDBAUGBwgJCgsMDQ4PAAoPAQIDBAUGBwgQDwECAwQFBgcICQoLDA0ODwA="
],
"result" : "cHNidP8BAD8CAAAAAf//////////////////////////////////////////AAAAAAD/////AQAAAAAAAAAAA2oBAAAAAAAKDwECAwQFBgcICQ8BAgMEBQYHCAkKCwwNDg8KDwECAwQFBgcIEA8BAgMEBQYHCAkKCwwNDg8ACg8BAgMEBQYHCAkPAQIDBAUGBwgJCgsMDQ4PCg8BAgMEBQYHCBAPAQIDBAUGBwgJCgsMDQ4PAAoPAQIDBAUGBwgJDwECAwQFBgcICQoLDA0ODwoPAQIDBAUGBwgQDwECAwQFBgcICQoLDA0ODwA="
}
],
"finalizer" : [
{
"finalize" : "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAAiAgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgf0cwRAIgdAGK1BgAl7hzMjwAFXILNoTMgSOJEEjn282bVa1nnJkCIHPTabdA4+tT3O+jOCPIBwUUylWn3ZVE8VfBZ5EyYRGMASICAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXSDBFAiEA9hA4swjcHahlo0hSdG8BV3KTQgjG0kRUOTzZm98iF3cCIAVuZ1pnWm0KArhbFOXikHTYolqbV2C+ooFvZhkQoAbqAQEDBAEAAAABBEdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSriIGApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/ENkMak8AAACAAAAAgAAAAIAiBgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU21xDZDGpPAAAAgAAAAIABAACAAAEBIADC6wsAAAAAF6kUt/X69A49QKWkWbHbNTXyty+pIeiHIgIDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtxHMEQCIGLrelVhB6fHP0WsSrWh3d9vcHX7EnWWmn84Pv/3hLyyAiAMBdu3Rw2/LwhVfdNWxzJcHtMJE+mWzThAlF2xIijaXwEiAgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc0cwRAIgZfRbpZmLWaJ//hp77QFq8fH5DVSzqo90UKpfVqJRA70CIH9yRwOtHtuWaAsoS1bU/8uI9/t1nqu+CKow8puFE4PSAQEDBAEAAAABBCIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQVHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4iBgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8OcxDZDGpPAAAAgAAAAIADAACAIgYDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwQ2QxqTwAAAIAAAACAAgAAgAAiAgOppMN/WZbTqiXbrGtXCvBlA5RJKUJGCzVHU+2e7KWHcRDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA",
"result" : "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAABB9oARzBEAiB0AYrUGACXuHMyPAAVcgs2hMyBI4kQSOfbzZtVrWecmQIgc9Npt0Dj61Pc76M4I8gHBRTKVafdlUTxV8FnkTJhEYwBSDBFAiEA9hA4swjcHahlo0hSdG8BV3KTQgjG0kRUOTzZm98iF3cCIAVuZ1pnWm0KArhbFOXikHTYolqbV2C+ooFvZhkQoAbqAUdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSrgABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohwEHIyIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQjaBABHMEQCIGLrelVhB6fHP0WsSrWh3d9vcHX7EnWWmn84Pv/3hLyyAiAMBdu3Rw2/LwhVfdNWxzJcHtMJE+mWzThAlF2xIijaXwFHMEQCIGX0W6WZi1mif/4ae+0BavHx+Q1Us6qPdFCqX1aiUQO9AiB/ckcDrR7blmgLKEtW1P/LiPf7dZ6rvgiqMPKbhROD0gFHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4AIgIDqaTDf1mW06ol26xrVwrwZQOUSSlCRgs1R1Ptnuylh3EQ2QxqTwAAAIAAAACABAAAgAAiAgJ/Y5l1fS7/VaE2rQLGhLGDi2VW5fG2s0KCqUtrUAUQlhDZDGpPAAAAgAAAAIAFAACAAA=="
}
],
"extractor" : [
{
"extract" : "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAABB9oARzBEAiB0AYrUGACXuHMyPAAVcgs2hMyBI4kQSOfbzZtVrWecmQIgc9Npt0Dj61Pc76M4I8gHBRTKVafdlUTxV8FnkTJhEYwBSDBFAiEA9hA4swjcHahlo0hSdG8BV3KTQgjG0kRUOTzZm98iF3cCIAVuZ1pnWm0KArhbFOXikHTYolqbV2C+ooFvZhkQoAbqAUdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSrgABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohwEHIyIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQjaBABHMEQCIGLrelVhB6fHP0WsSrWh3d9vcHX7EnWWmn84Pv/3hLyyAiAMBdu3Rw2/LwhVfdNWxzJcHtMJE+mWzThAlF2xIijaXwFHMEQCIGX0W6WZi1mif/4ae+0BavHx+Q1Us6qPdFCqX1aiUQO9AiB/ckcDrR7blmgLKEtW1P/LiPf7dZ6rvgiqMPKbhROD0gFHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4AIgIDqaTDf1mW06ol26xrVwrwZQOUSSlCRgs1R1Ptnuylh3EQ2QxqTwAAAIAAAACABAAAgAAiAgJ/Y5l1fS7/VaE2rQLGhLGDi2VW5fG2s0KCqUtrUAUQlhDZDGpPAAAAgAAAAIAFAACAAA==",
"result" : "0200000000010258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7500000000da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752aeffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d01000000232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f000400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00000000"
}
]
}

25
test/functional/digibyte_cli.py Executable file
View file

@ -0,0 +1,25 @@
#!/usr/bin/env python3
# Copyright (c) 2017 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test digibyte-cli"""
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import assert_equal
class TestDigiByteCli(DigiByteTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
def run_test(self):
"""Main test logic"""
self.log.info("Compare responses from getinfo RPC and `digibyte-cli getinfo`")
cli_get_info = self.nodes[0].cli.getinfo()
rpc_get_info = self.nodes[0].getinfo()
assert_equal(cli_get_info, rpc_get_info)
if __name__ == '__main__':
TestDigiByteCli().main()

218
test/functional/example_test.py Executable file
View file

@ -0,0 +1,218 @@
#!/usr/bin/env python3
# Copyright (c) 2017-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""An example functional test
The module-level docstring should include a high-level description of
what the test is doing. It's the first thing people see when they open
the file and should give the reader information about *what* the test
is testing and *how* it's being tested
"""
# Imports should be in PEP8 ordering (std library first, then third party
# libraries then local imports).
from collections import defaultdict
# Avoid wildcard * imports
from test_framework.blocktools import (create_block, create_coinbase)
from test_framework.messages import CInv, MSG_BLOCK
from test_framework.p2p import (
P2PInterface,
msg_block,
msg_getdata,
p2p_lock,
)
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import (
assert_equal,
)
# P2PInterface is a class containing callbacks to be executed when a P2P
# message is received from the node-under-test. Subclass P2PInterface and
# override the on_*() methods if you need custom behaviour.
class BaseNode(P2PInterface):
def __init__(self):
"""Initialize the P2PInterface
Used to initialize custom properties for the Node that aren't
included by default in the base class. Be aware that the P2PInterface
base class already stores a counter for each P2P message type and the
last received message of each type, which should be sufficient for the
needs of most tests.
Call super().__init__() first for standard initialization and then
initialize custom properties."""
super().__init__()
# Stores a dictionary of all blocks received
self.block_receive_map = defaultdict(int)
def on_block(self, message):
"""Override the standard on_block callback
Store the hash of a received block in the dictionary."""
message.block.calc_sha256()
self.block_receive_map[message.block.sha256] += 1
def on_inv(self, message):
"""Override the standard on_inv callback"""
pass
def custom_function():
"""Do some custom behaviour
If this function is more generally useful for other tests, consider
moving it to a module in test_framework."""
# self.log.info("running custom_function") # Oops! Can't run self.log outside the DigiByteTestFramework
pass
class ExampleTest(DigiByteTestFramework):
# Each functional test is a subclass of the DigiByteTestFramework class.
# Override the set_test_params(), skip_test_if_missing_module(), add_options(), setup_chain(), setup_network()
# and setup_nodes() methods to customize the test setup as required.
def set_test_params(self):
"""Override test parameters for your individual test.
This method must be overridden and num_nodes must be explicitly set."""
# By default every test loads a pre-mined chain of 200 blocks from cache.
# Set setup_clean_chain to True to skip this and start from the Genesis
# block.
self.setup_clean_chain = True
self.num_nodes = 3
# Use self.extra_args to change command-line arguments for the nodes
self.extra_args = [[], ["-logips"], []]
# self.log.info("I've finished set_test_params") # Oops! Can't run self.log before run_test()
# Use skip_test_if_missing_module() to skip the test if your test requires certain modules to be present.
# This test uses generate which requires wallet to be compiled
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
# Use add_options() to add specific command-line options for your test.
# In practice this is not used very much, since the tests are mostly written
# to be run in automated environments without command-line options.
# def add_options()
# pass
# Use setup_chain() to customize the node data directories. In practice
# this is not used very much since the default behaviour is almost always
# fine
# def setup_chain():
# pass
def setup_network(self):
"""Setup the test network topology
Often you won't need to override this, since the standard network topology
(linear: node0 <-> node1 <-> node2 <-> ...) is fine for most tests.
If you do override this method, remember to start the nodes, assign
them to self.nodes, connect them and then sync."""
self.setup_nodes()
# In this test, we're not connecting node2 to node0 or node1. Calls to
# sync_all() should not include node2, since we're not expecting it to
# sync.
self.connect_nodes(0, 1)
self.sync_all(self.nodes[0:2])
# Use setup_nodes() to customize the node start behaviour (for example if
# you don't want to start all nodes at the start of the test).
# def setup_nodes():
# pass
def custom_method(self):
"""Do some custom behaviour for this test
Define it in a method here because you're going to use it repeatedly.
If you think it's useful in general, consider moving it to the base
DigiByteTestFramework class so other tests can use it."""
self.log.info("Running custom_method")
def run_test(self):
"""Main test logic"""
# Create P2P connections will wait for a verack to make sure the connection is fully up
peer_messaging = self.nodes[0].add_p2p_connection(BaseNode())
# Generating a block on one of the nodes will get us out of IBD
blocks = [int(self.generate(self.nodes[0], sync_fun=lambda: self.sync_all(self.nodes[0:2]), nblocks=1)[0], 16)]
# Notice above how we called an RPC by calling a method with the same
# name on the node object. Notice also how we used a keyword argument
# to specify a named RPC argument. Neither of those are defined on the
# node object. Instead there's some __getattr__() magic going on under
# the covers to dispatch unrecognised attribute calls to the RPC
# interface.
# Logs are nice. Do plenty of them. They can be used in place of comments for
# breaking the test into sub-sections.
self.log.info("Starting test!")
self.log.info("Calling a custom function")
custom_function()
self.log.info("Calling a custom method")
self.custom_method()
self.log.info("Create some blocks")
self.tip = int(self.nodes[0].getbestblockhash(), 16)
self.block_time = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['time'] + 1
height = self.nodes[0].getblockcount()
for _ in range(10):
# Use the blocktools functionality to manually build a block.
# Calling the generate() rpc is easier, but this allows us to exactly
# control the blocks and transactions.
block = create_block(self.tip, create_coinbase(height+1), self.block_time)
block.solve()
block_message = msg_block(block)
# Send message is used to send a P2P message to the node over our P2PInterface
peer_messaging.send_message(block_message)
self.tip = block.sha256
blocks.append(self.tip)
self.block_time += 1
height += 1
self.log.info("Wait for node1 to reach current tip (height 11) using RPC")
self.nodes[1].waitforblockheight(11)
self.log.info("Connect node2 and node1")
self.connect_nodes(1, 2)
self.log.info("Wait for node2 to receive all the blocks from node1")
self.sync_all()
self.log.info("Add P2P connection to node2")
self.nodes[0].disconnect_p2ps()
peer_receiving = self.nodes[2].add_p2p_connection(BaseNode())
self.log.info("Test that node2 propagates all the blocks to us")
getdata_request = msg_getdata()
for block in blocks:
getdata_request.inv.append(CInv(MSG_BLOCK, block))
peer_receiving.send_message(getdata_request)
# wait_until() will loop until a predicate condition is met. Use it to test properties of the
# P2PInterface objects.
peer_receiving.wait_until(lambda: sorted(blocks) == sorted(list(peer_receiving.block_receive_map.keys())), timeout=5)
self.log.info("Check that each block was received only once")
# The network thread uses a global lock on data access to the P2PConnection objects when sending and receiving
# messages. The test thread should acquire the global lock before accessing any P2PConnection data to avoid locking
# and synchronization issues. Note p2p.wait_until() acquires this global lock internally when testing the predicate.
with p2p_lock:
for block in peer_receiving.block_receive_map.values():
assert_equal(block, 1)
if __name__ == '__main__':
ExampleTest().main()

View file

@ -0,0 +1,50 @@
#!/usr/bin/env python3
# Copyright (c) 2019-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test digibyted aborts if can't disconnect a block.
- Start a single node and generate 3 blocks.
- Delete the undo data.
- Mine a fork that requires disconnecting the tip.
- Verify that digibyted AbortNode's.
"""
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import get_datadir_path
import os
class AbortNodeTest(DigiByteTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 2
self.rpc_timeout = 240
def setup_network(self):
self.setup_nodes()
# We'll connect the nodes later
def run_test(self):
self.generate(self.nodes[0], 3, sync_fun=self.no_op)
datadir = get_datadir_path(self.options.tmpdir, 0)
# Deleting the undo file will result in reorg failure
os.unlink(os.path.join(datadir, self.chain, 'blocks', 'rev00000.dat'))
# Connecting to a node with a more work chain will trigger a reorg
# attempt.
self.generate(self.nodes[1], 3, sync_fun=self.no_op)
with self.nodes[0].assert_debug_log(["Failed to disconnect block"]):
self.connect_nodes(0, 1)
self.generate(self.nodes[1], 1, sync_fun=self.no_op)
# Check that node0 aborted
self.log.info("Waiting for crash")
self.nodes[0].wait_until_stopped(timeout=200)
self.log.info("Node crashed - now verifying restart fails")
self.nodes[0].assert_start_raises_init_error()
if __name__ == '__main__':
AbortNodeTest().main()

View file

@ -0,0 +1,83 @@
#!/usr/bin/env python3
# Copyright (c) 2020-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test block-relay-only anchors functionality"""
import os
from test_framework.p2p import P2PInterface
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import check_node_connections
INBOUND_CONNECTIONS = 5
BLOCK_RELAY_CONNECTIONS = 2
class AnchorsTest(DigiByteTestFramework):
def set_test_params(self):
self.num_nodes = 1
self.disable_autoconnect = False
def run_test(self):
node_anchors_path = os.path.join(
self.nodes[0].datadir, "regtest", "anchors.dat"
)
self.log.info("When node starts, check if anchors.dat doesn't exist")
assert not os.path.exists(node_anchors_path)
self.log.info(f"Add {BLOCK_RELAY_CONNECTIONS} block-relay-only connections to node")
for i in range(BLOCK_RELAY_CONNECTIONS):
self.log.debug(f"block-relay-only: {i}")
self.nodes[0].add_outbound_p2p_connection(
P2PInterface(), p2p_idx=i, connection_type="block-relay-only"
)
self.log.info(f"Add {INBOUND_CONNECTIONS} inbound connections to node")
for i in range(INBOUND_CONNECTIONS):
self.log.debug(f"inbound: {i}")
self.nodes[0].add_p2p_connection(P2PInterface())
self.log.info("Check node connections")
check_node_connections(node=self.nodes[0], num_in=5, num_out=2)
# 127.0.0.1
ip = "7f000001"
# Since the ip is always 127.0.0.1 for this case,
# we store only the port to identify the peers
block_relay_nodes_port = []
inbound_nodes_port = []
for p in self.nodes[0].getpeerinfo():
addr_split = p["addr"].split(":")
if p["connection_type"] == "block-relay-only":
block_relay_nodes_port.append(hex(int(addr_split[1]))[2:])
else:
inbound_nodes_port.append(hex(int(addr_split[1]))[2:])
self.log.info("Stop node 0")
self.stop_node(0)
# It should contain only the block-relay-only addresses
self.log.info("Check the addresses in anchors.dat")
with open(node_anchors_path, "rb") as file_handler:
anchors = file_handler.read().hex()
for port in block_relay_nodes_port:
ip_port = ip + port
assert ip_port in anchors
for port in inbound_nodes_port:
ip_port = ip + port
assert ip_port not in anchors
self.log.info("Start node")
self.start_node(0)
self.log.info("When node starts, check if anchors.dat doesn't exist anymore")
assert not os.path.exists(node_anchors_path)
if __name__ == "__main__":
AnchorsTest().main()

113
test/functional/feature_asmap.py Executable file
View file

@ -0,0 +1,113 @@
#!/usr/bin/env python3
# Copyright (c) 2020-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test asmap config argument for ASN-based IP bucketing.
Verify node behaviour and debug log when launching digibyted in these cases:
1. `digibyted` with no -asmap arg, using /16 prefix for IP bucketing
2. `digibyted -asmap=<absolute path>`, using the unit test skeleton asmap
3. `digibyted -asmap=<relative path>`, using the unit test skeleton asmap
4. `digibyted -asmap/-asmap=` with no file specified, using the default asmap
5. `digibyted -asmap` restart with an addrman containing new and tried entries
6. `digibyted -asmap` with no file specified and a missing default asmap file
7. `digibyted -asmap` with an empty (unparsable) default asmap file
The tests are order-independent.
"""
import os
import shutil
from test_framework.test_framework import DigiByteTestFramework
DEFAULT_ASMAP_FILENAME = 'ip_asn.map' # defined in src/init.cpp
ASMAP = '../../src/test/data/asmap.raw' # path to unit test skeleton asmap
VERSION = 'fec61fa21a9f46f3b17bdcd660d7f4cd90b966aad3aec593c99b35f0aca15853'
def expected_messages(filename):
return [f'Opened asmap file "{filename}" (59 bytes) from disk',
f'Using asmap version {VERSION} for IP bucketing']
class AsmapTest(DigiByteTestFramework):
def set_test_params(self):
self.num_nodes = 1
self.extra_args = [[]] # Do addrman checks on all operations.
def fill_addrman(self, node_id):
"""Add 1 tried address to the addrman, followed by 1 new address."""
for addr, tried in [[0, True], [1, False]]:
self.nodes[node_id].addpeeraddress(address=f"101.{addr}.0.0", tried=tried, port=8333)
def test_without_asmap_arg(self):
self.log.info('Test digibyted with no -asmap arg passed')
self.stop_node(0)
with self.node.assert_debug_log(['Using /16 prefix for IP bucketing']):
self.start_node(0)
def test_asmap_with_absolute_path(self):
self.log.info('Test digibyted -asmap=<absolute path>')
self.stop_node(0)
filename = os.path.join(self.datadir, 'my-map-file.map')
shutil.copyfile(self.asmap_raw, filename)
with self.node.assert_debug_log(expected_messages(filename)):
self.start_node(0, [f'-asmap={filename}'])
os.remove(filename)
def test_asmap_with_relative_path(self):
self.log.info('Test digibyted -asmap=<relative path>')
self.stop_node(0)
name = 'ASN_map'
filename = os.path.join(self.datadir, name)
shutil.copyfile(self.asmap_raw, filename)
with self.node.assert_debug_log(expected_messages(filename)):
self.start_node(0, [f'-asmap={name}'])
os.remove(filename)
def test_default_asmap(self):
shutil.copyfile(self.asmap_raw, self.default_asmap)
for arg in ['-asmap', '-asmap=']:
self.log.info(f'Test digibyted {arg} (using default map file)')
self.stop_node(0)
with self.node.assert_debug_log(expected_messages(self.default_asmap)):
self.start_node(0, [arg])
os.remove(self.default_asmap)
def test_default_asmap_with_missing_file(self):
self.log.info('Test digibyted -asmap with missing default map file')
self.stop_node(0)
msg = f"Error: Could not find asmap file \"{self.default_asmap}\""
self.node.assert_start_raises_init_error(extra_args=['-asmap'], expected_msg=msg)
def test_empty_asmap(self):
self.log.info('Test digibyted -asmap with empty map file')
self.stop_node(0)
with open(self.default_asmap, "w", encoding="utf-8") as f:
f.write("")
msg = f"Error: Could not parse asmap file \"{self.default_asmap}\""
self.node.assert_start_raises_init_error(extra_args=['-asmap'], expected_msg=msg)
os.remove(self.default_asmap)
def run_test(self):
self.node = self.nodes[0]
self.datadir = os.path.join(self.node.datadir, self.chain)
self.default_asmap = os.path.join(self.datadir, DEFAULT_ASMAP_FILENAME)
self.asmap_raw = os.path.join(os.path.dirname(os.path.realpath(__file__)), ASMAP)
self.test_without_asmap_arg()
self.test_asmap_with_absolute_path()
self.test_asmap_with_relative_path()
self.test_default_asmap()
self.test_default_asmap_with_missing_file()
self.test_empty_asmap()
if __name__ == '__main__':
AsmapTest().main()

View file

@ -0,0 +1,185 @@
#!/usr/bin/env python3
# Copyright (c) 2014-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test logic for skipping signature validation on old blocks.
Test logic for skipping signature validation on blocks which we've assumed
valid (https://github.com/digibyte/digibyte/pull/9484)
We build a chain that includes and invalid signature for one of the
transactions:
0: genesis block
1: block 1 with coinbase transaction output.
2-101: bury that block with 100 blocks so the coinbase transaction
output can be spent
102: a block containing a transaction spending the coinbase
transaction output. The transaction has an invalid signature.
103-2202: bury the bad block with just over two weeks' worth of blocks
(2100 blocks)
Start three nodes:
- node0 has no -assumevalid parameter. Try to sync to block 2202. It will
reject block 102 and only sync as far as block 101
- node1 has -assumevalid set to the hash of block 102. Try to sync to
block 2202. node1 will sync all the way to block 2202.
- node2 has -assumevalid set to the hash of block 102. Try to sync to
block 200. node2 will reject block 102 since it's assumed valid, but it
isn't buried by at least two weeks' work.
"""
from test_framework.blocktools import (
COINBASE_MATURITY,
create_block,
create_coinbase,
get_coinbase_value,
)
from test_framework.key import ECKey
from test_framework.messages import (
CBlockHeader,
COutPoint,
CTransaction,
CTxIn,
CTxOut,
msg_block,
msg_headers,
)
from test_framework.p2p import P2PInterface
from test_framework.script import (CScript, OP_TRUE)
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import assert_equal
from time import sleep
COINBASE_MATURITY_2 = 100
class BaseNode(P2PInterface):
def send_header_for_blocks(self, new_blocks):
headers_message = msg_headers()
headers_message.headers = [CBlockHeader(b) for b in new_blocks]
self.send_message(headers_message)
class AssumeValidTest(DigiByteTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 3
self.rpc_timeout = 120
def setup_network(self):
self.add_nodes(3)
# Start node0. We don't start the other nodes yet since
# we need to pre-mine a block with an invalid transaction
# signature so we can pass in the block hash as assumevalid.
self.start_node(0, extra_args=['-easypow'])
def send_blocks_until_disconnected(self, p2p_conn):
"""Keep sending blocks to the node until we're disconnected."""
for i in range(len(self.blocks)):
if not p2p_conn.is_connected:
break
try:
p2p_conn.send_message(msg_block(self.blocks[i]))
except IOError:
assert not p2p_conn.is_connected
break
def run_test(self):
p2p0 = self.nodes[0].add_p2p_connection(BaseNode())
# Build the blockchain
self.tip = int(self.nodes[0].getbestblockhash(), 16)
self.block_time = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['time'] + 1
self.blocks = []
# Get a pubkey for the coinbase TXO
coinbase_key = ECKey()
coinbase_key.generate()
coinbase_pubkey = coinbase_key.get_pubkey().get_bytes()
# Create the first block with a coinbase output to our key
height = 1
block = create_block(self.tip, create_coinbase(height, coinbase_pubkey), self.block_time)
self.blocks.append(block)
self.block_time += 1
block.solve()
# Save the coinbase for later
self.block1 = block
self.tip = block.sha256
height += 1
# Bury the block 100 deep so the coinbase output is spendable
for _ in range(COINBASE_MATURITY_2):
block = create_block(self.tip, create_coinbase(height), self.block_time)
block.solve()
self.blocks.append(block)
self.tip = block.sha256
self.block_time += 1
height += 1
# Create a transaction spending the coinbase output with an invalid (null) signature
tx = CTransaction()
tx.vin.append(CTxIn(COutPoint(self.block1.vtx[0].sha256, 0), scriptSig=b""))
tx.vout.append(CTxOut(49 * 100000000, CScript([OP_TRUE])))
tx.calc_sha256()
block102 = create_block(self.tip, create_coinbase(height), self.block_time, txlist=[tx])
self.block_time += 1
block102.solve()
self.blocks.append(block102)
self.tip = block102.sha256
self.block_time += 1
height += 1
# Bury the assumed valid block 2100 deep
for _ in range(2100):
block = create_block(self.tip, create_coinbase(height, nValue=get_coinbase_value(height)), self.block_time)
block.solve()
self.blocks.append(block)
self.tip = block.sha256
self.block_time += 1
height += 1
self.nodes[0].disconnect_p2ps()
# Start node1 and node2 with assumevalid so they accept a block with a bad signature.
self.start_node(1, extra_args=["-easypow", "-assumevalid=" + hex(block102.sha256)])
self.start_node(2, extra_args=["-easypow"]) #, "-assumevalid=" + hex(block102.sha256)])
p2p0 = self.nodes[0].add_p2p_connection(BaseNode())
p2p1 = self.nodes[1].add_p2p_connection(BaseNode())
p2p2 = self.nodes[2].add_p2p_connection(BaseNode())
# send header lists to all three nodes
p2p0.send_header_for_blocks(self.blocks[0:2000])
p2p0.send_header_for_blocks(self.blocks[2000:])
p2p1.send_header_for_blocks(self.blocks[0:2000])
p2p1.send_header_for_blocks(self.blocks[2000:])
p2p2.send_header_for_blocks(self.blocks[0:200])
# Send blocks to node0. Block 102 will be rejected.
self.send_blocks_until_disconnected(p2p0)
self.wait_until(lambda: self.nodes[0].getblockcount() >= COINBASE_MATURITY_2 + 1)
assert_equal(self.nodes[0].getblockcount(), COINBASE_MATURITY_2 + 1)
# Send all blocks to node1. All blocks will be accepted.
for i in range(2202):
p2p1.send_message(msg_block(self.blocks[i]))
# Syncing 2200 blocks can take a while on slow systems. Give it plenty of time to sync.
p2p1.sync_with_ping(960)
assert_equal(self.nodes[1].getblock(self.nodes[1].getbestblockhash())['height'], 2202)
# Send blocks to node2. Block 102 will be rejected.
# Yoshi: Bitcoin developers must have made a mistake here. Assumevalid was given as an argument
# in start_node(2, ...) so why should it reject block 102.
# Commented out the parameters.
self.send_blocks_until_disconnected(p2p2)
self.wait_until(lambda: self.nodes[2].getblockcount() >= COINBASE_MATURITY_2 + 1)
assert_equal(self.nodes[2].getblockcount(), COINBASE_MATURITY_2 + 1)
if __name__ == '__main__':
AssumeValidTest().main()

View file

@ -0,0 +1,424 @@
#!/usr/bin/env python3
# Copyright (c) 2018-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Backwards compatibility functional test
Test various backwards compatibility scenarios. Requires previous releases binaries,
see test/README.md.
v0.15.2 is not required by this test, but it is used in wallet_upgradewallet.py.
Due to a hardfork in regtest, it can't be used to sync nodes.
Due to RPC changes introduced in various versions the below tests
won't work for older versions without some patches or workarounds.
Use only the latest patch version of each release, unless a test specifically
needs an older patch version.
"""
import os
import shutil
from test_framework.blocktools import COINBASE_MATURITY
from test_framework.test_framework import DigiByteTestFramework
from test_framework.descriptors import descsum_create
from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
)
class BackwardsCompatibilityTest(DigiByteTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 6
# Add new version after each release:
self.extra_args = [
["-addresstype=bech32"], # Pre-release: use to mine blocks
["-nowallet", "-walletrbf=1", "-addresstype=bech32"], # Pre-release: use to receive coins, swap wallets, etc
["-nowallet", "-walletrbf=1", "-addresstype=bech32"], # v0.19.1
["-nowallet", "-walletrbf=1", "-addresstype=bech32"], # v0.18.1
["-nowallet", "-walletrbf=1", "-addresstype=bech32"], # v0.17.2
["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-wallet=wallet.dat"], # v0.16.3
]
self.wallet_names = [self.default_wallet_name]
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
self.skip_if_no_previous_releases()
def setup_nodes(self):
self.add_nodes(self.num_nodes, extra_args=self.extra_args, versions=[
None,
None,
190100,
180100,
170200,
160300,
])
self.start_nodes()
self.import_deterministic_coinbase_privkeys()
def run_test(self):
self.generatetoaddress(self.nodes[0], COINBASE_MATURITY + 1, self.nodes[0].getnewaddress())
# Sanity check the test framework:
res = self.nodes[self.num_nodes - 1].getblockchaininfo()
assert_equal(res['blocks'], COINBASE_MATURITY + 1)
node_master = self.nodes[self.num_nodes - 5]
node_v19 = self.nodes[self.num_nodes - 4]
node_v18 = self.nodes[self.num_nodes - 3]
node_v17 = self.nodes[self.num_nodes - 2]
node_v16 = self.nodes[self.num_nodes - 1]
self.log.info("Test wallet backwards compatibility...")
# Create a number of wallets and open them in older versions:
# w1: regular wallet, created on master: update this test when default
# wallets can no longer be opened by older versions.
node_master.createwallet(wallet_name="w1")
wallet = node_master.get_wallet_rpc("w1")
info = wallet.getwalletinfo()
assert info['private_keys_enabled']
assert info['keypoolsize'] > 0
# Create a confirmed transaction, receiving coins
address = wallet.getnewaddress()
self.nodes[0].sendtoaddress(address, 10)
self.sync_mempools()
self.generate(self.nodes[0], 1)
# Create a conflicting transaction using RBF
return_address = self.nodes[0].getnewaddress()
tx1_id = self.nodes[1].sendtoaddress(return_address, 1)
tx2_id = self.nodes[1].bumpfee(tx1_id)["txid"]
# Confirm the transaction
self.sync_mempools()
self.generate(self.nodes[0], 1)
# Create another conflicting transaction using RBF
tx3_id = self.nodes[1].sendtoaddress(return_address, 1)
tx4_id = self.nodes[1].bumpfee(tx3_id)["txid"]
# Abandon transaction, but don't confirm
self.nodes[1].abandontransaction(tx3_id)
# w1_v19: regular wallet, created with v0.19
node_v19.rpc.createwallet(wallet_name="w1_v19")
wallet = node_v19.get_wallet_rpc("w1_v19")
info = wallet.getwalletinfo()
assert info['private_keys_enabled']
assert info['keypoolsize'] > 0
# Use addmultisigaddress (see #18075)
address_18075 = wallet.rpc.addmultisigaddress(1, ["0296b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52", "037211a824f55b505228e4c3d5194c1fcfaa15a456abdf37f9b9d97a4040afc073"], "", "legacy")["address"]
assert wallet.getaddressinfo(address_18075)["solvable"]
# w1_v18: regular wallet, created with v0.18
node_v18.rpc.createwallet(wallet_name="w1_v18")
wallet = node_v18.get_wallet_rpc("w1_v18")
info = wallet.getwalletinfo()
assert info['private_keys_enabled']
assert info['keypoolsize'] > 0
# w2: wallet with private keys disabled, created on master: update this
# test when default wallets private keys disabled can no longer be
# opened by older versions.
node_master.createwallet(wallet_name="w2", disable_private_keys=True)
wallet = node_master.get_wallet_rpc("w2")
info = wallet.getwalletinfo()
assert info['private_keys_enabled'] == False
assert info['keypoolsize'] == 0
# w2_v19: wallet with private keys disabled, created with v0.19
node_v19.rpc.createwallet(wallet_name="w2_v19", disable_private_keys=True)
wallet = node_v19.get_wallet_rpc("w2_v19")
info = wallet.getwalletinfo()
assert info['private_keys_enabled'] == False
assert info['keypoolsize'] == 0
# w2_v18: wallet with private keys disabled, created with v0.18
node_v18.rpc.createwallet(wallet_name="w2_v18", disable_private_keys=True)
wallet = node_v18.get_wallet_rpc("w2_v18")
info = wallet.getwalletinfo()
assert info['private_keys_enabled'] == False
assert info['keypoolsize'] == 0
# w3: blank wallet, created on master: update this
# test when default blank wallets can no longer be opened by older versions.
node_master.createwallet(wallet_name="w3", blank=True)
wallet = node_master.get_wallet_rpc("w3")
info = wallet.getwalletinfo()
assert info['private_keys_enabled']
assert info['keypoolsize'] == 0
# w3_v19: blank wallet, created with v0.19
node_v19.rpc.createwallet(wallet_name="w3_v19", blank=True)
wallet = node_v19.get_wallet_rpc("w3_v19")
info = wallet.getwalletinfo()
assert info['private_keys_enabled']
assert info['keypoolsize'] == 0
# w3_v18: blank wallet, created with v0.18
node_v18.rpc.createwallet(wallet_name="w3_v18", blank=True)
wallet = node_v18.get_wallet_rpc("w3_v18")
info = wallet.getwalletinfo()
assert info['private_keys_enabled']
assert info['keypoolsize'] == 0
# Copy the wallets to older nodes:
node_master_wallets_dir = os.path.join(node_master.datadir, "regtest/wallets")
node_v19_wallets_dir = os.path.join(node_v19.datadir, "regtest/wallets")
node_v18_wallets_dir = os.path.join(node_v18.datadir, "regtest/wallets")
node_v17_wallets_dir = os.path.join(node_v17.datadir, "regtest/wallets")
node_v16_wallets_dir = os.path.join(node_v16.datadir, "regtest")
node_master.unloadwallet("w1")
node_master.unloadwallet("w2")
node_v19.unloadwallet("w1_v19")
node_v19.unloadwallet("w2_v19")
node_v18.unloadwallet("w1_v18")
node_v18.unloadwallet("w2_v18")
# Copy wallets to v0.16
for wallet in os.listdir(node_master_wallets_dir):
shutil.copytree(
os.path.join(node_master_wallets_dir, wallet),
os.path.join(node_v16_wallets_dir, wallet)
)
# Copy wallets to v0.17
for wallet in os.listdir(node_master_wallets_dir):
shutil.copytree(
os.path.join(node_master_wallets_dir, wallet),
os.path.join(node_v17_wallets_dir, wallet)
)
for wallet in os.listdir(node_v18_wallets_dir):
shutil.copytree(
os.path.join(node_v18_wallets_dir, wallet),
os.path.join(node_v17_wallets_dir, wallet)
)
# Copy wallets to v0.18
for wallet in os.listdir(node_master_wallets_dir):
shutil.copytree(
os.path.join(node_master_wallets_dir, wallet),
os.path.join(node_v18_wallets_dir, wallet)
)
# Copy wallets to v0.19
for wallet in os.listdir(node_master_wallets_dir):
shutil.copytree(
os.path.join(node_master_wallets_dir, wallet),
os.path.join(node_v19_wallets_dir, wallet)
)
if not self.options.descriptors:
# Descriptor wallets break compatibility, only run this test for legacy wallet
# Open the wallets in v0.19
node_v19.loadwallet("w1")
wallet = node_v19.get_wallet_rpc("w1")
info = wallet.getwalletinfo()
assert info['private_keys_enabled']
assert info['keypoolsize'] > 0
txs = wallet.listtransactions()
assert_equal(len(txs), 5)
assert_equal(txs[1]["txid"], tx1_id)
assert_equal(txs[2]["walletconflicts"], [tx1_id])
assert_equal(txs[1]["replaced_by_txid"], tx2_id)
assert not(txs[1]["abandoned"])
assert_equal(txs[1]["confirmations"], -1)
assert_equal(txs[2]["blockindex"], 1)
assert txs[3]["abandoned"]
assert_equal(txs[4]["walletconflicts"], [tx3_id])
assert_equal(txs[3]["replaced_by_txid"], tx4_id)
assert not(hasattr(txs[3], "blockindex"))
node_v19.loadwallet("w2")
wallet = node_v19.get_wallet_rpc("w2")
info = wallet.getwalletinfo()
assert info['private_keys_enabled'] == False
assert info['keypoolsize'] == 0
node_v19.loadwallet("w3")
wallet = node_v19.get_wallet_rpc("w3")
info = wallet.getwalletinfo()
assert info['private_keys_enabled']
assert info['keypoolsize'] == 0
# Open the wallets in v0.18
node_v18.loadwallet("w1")
wallet = node_v18.get_wallet_rpc("w1")
info = wallet.getwalletinfo()
assert info['private_keys_enabled']
assert info['keypoolsize'] > 0
txs = wallet.listtransactions()
assert_equal(len(txs), 5)
assert_equal(txs[1]["txid"], tx1_id)
assert_equal(txs[2]["walletconflicts"], [tx1_id])
assert_equal(txs[1]["replaced_by_txid"], tx2_id)
assert not(txs[1]["abandoned"])
assert_equal(txs[1]["confirmations"], -1)
assert_equal(txs[2]["blockindex"], 1)
assert txs[3]["abandoned"]
assert_equal(txs[4]["walletconflicts"], [tx3_id])
assert_equal(txs[3]["replaced_by_txid"], tx4_id)
assert not(hasattr(txs[3], "blockindex"))
node_v18.loadwallet("w2")
wallet = node_v18.get_wallet_rpc("w2")
info = wallet.getwalletinfo()
assert info['private_keys_enabled'] == False
assert info['keypoolsize'] == 0
node_v18.loadwallet("w3")
wallet = node_v18.get_wallet_rpc("w3")
info = wallet.getwalletinfo()
assert info['private_keys_enabled']
assert info['keypoolsize'] == 0
node_v17.loadwallet("w1")
wallet = node_v17.get_wallet_rpc("w1")
info = wallet.getwalletinfo()
assert info['private_keys_enabled']
assert info['keypoolsize'] > 0
node_v17.loadwallet("w2")
wallet = node_v17.get_wallet_rpc("w2")
info = wallet.getwalletinfo()
assert info['private_keys_enabled'] == False
assert info['keypoolsize'] == 0
else:
# Descriptor wallets appear to be corrupted wallets to old software
assert_raises_rpc_error(-4, "Wallet file verification failed: wallet.dat corrupt, salvage failed", node_v19.loadwallet, "w1")
assert_raises_rpc_error(-4, "Wallet file verification failed: wallet.dat corrupt, salvage failed", node_v19.loadwallet, "w2")
assert_raises_rpc_error(-4, "Wallet file verification failed: wallet.dat corrupt, salvage failed", node_v19.loadwallet, "w3")
assert_raises_rpc_error(-4, "Wallet file verification failed: wallet.dat corrupt, salvage failed", node_v18.loadwallet, "w1")
assert_raises_rpc_error(-4, "Wallet file verification failed: wallet.dat corrupt, salvage failed", node_v18.loadwallet, "w2")
assert_raises_rpc_error(-4, "Wallet file verification failed: wallet.dat corrupt, salvage failed", node_v18.loadwallet, "w3")
# Open the wallets in v0.17
node_v17.loadwallet("w1_v18")
wallet = node_v17.get_wallet_rpc("w1_v18")
info = wallet.getwalletinfo()
assert info['private_keys_enabled']
assert info['keypoolsize'] > 0
node_v17.loadwallet("w2_v18")
wallet = node_v17.get_wallet_rpc("w2_v18")
info = wallet.getwalletinfo()
assert info['private_keys_enabled'] == False
assert info['keypoolsize'] == 0
# RPC loadwallet failure causes digibyted to exit, in addition to the RPC
# call failure, so the following test won't work:
# assert_raises_rpc_error(-4, "Wallet loading failed.", node_v17.loadwallet, 'w3_v18')
# Instead, we stop node and try to launch it with the wallet:
self.stop_node(4)
node_v17.assert_start_raises_init_error(["-wallet=w3_v18"], "Error: Error loading w3_v18: Wallet requires newer version of DigiByte Core")
if self.options.descriptors:
# Descriptor wallets appear to be corrupted wallets to old software
node_v17.assert_start_raises_init_error(["-wallet=w1"], "Error: wallet.dat corrupt, salvage failed")
node_v17.assert_start_raises_init_error(["-wallet=w2"], "Error: wallet.dat corrupt, salvage failed")
node_v17.assert_start_raises_init_error(["-wallet=w3"], "Error: wallet.dat corrupt, salvage failed")
else:
node_v17.assert_start_raises_init_error(["-wallet=w3"], "Error: Error loading w3: Wallet requires newer version of DigiByte Core")
self.start_node(4)
if not self.options.descriptors:
# Descriptor wallets break compatibility, only run this test for legacy wallets
# Open most recent wallet in v0.16 (no loadwallet RPC)
self.restart_node(5, extra_args=["-wallet=w2"])
wallet = node_v16.get_wallet_rpc("w2")
info = wallet.getwalletinfo()
assert info['keypoolsize'] == 1
# Create upgrade wallet in v0.16
self.restart_node(-1, extra_args=["-wallet=u1_v16"])
wallet = node_v16.get_wallet_rpc("u1_v16")
v16_addr = wallet.getnewaddress('', "bech32")
v16_info = wallet.validateaddress(v16_addr)
v16_pubkey = v16_info['pubkey']
self.stop_node(-1)
self.log.info("Test wallet upgrade path...")
# u1: regular wallet, created with v0.17
node_v17.rpc.createwallet(wallet_name="u1_v17")
wallet = node_v17.get_wallet_rpc("u1_v17")
address = wallet.getnewaddress("bech32")
v17_info = wallet.getaddressinfo(address)
hdkeypath = v17_info["hdkeypath"]
pubkey = v17_info["pubkey"]
if self.is_bdb_compiled():
# Old wallets are BDB and will only work if BDB is compiled
# Copy the 0.16 wallet to the last DigiByte Core version and open it:
shutil.copyfile(
os.path.join(node_v16_wallets_dir, "wallets/u1_v16"),
os.path.join(node_master_wallets_dir, "u1_v16")
)
load_res = node_master.loadwallet("u1_v16")
# Make sure this wallet opens without warnings. See https://github.com/digibyte/digibyte/pull/19054
assert_equal(load_res['warning'], '')
wallet = node_master.get_wallet_rpc("u1_v16")
info = wallet.getaddressinfo(v16_addr)
descriptor = f"wpkh([{info['hdmasterfingerprint']}{hdkeypath[1:]}]{v16_pubkey})"
assert_equal(info["desc"], descsum_create(descriptor))
# Now copy that same wallet back to 0.16 to make sure no automatic upgrade breaks it
os.remove(os.path.join(node_v16_wallets_dir, "wallets/u1_v16"))
shutil.copyfile(
os.path.join(node_master_wallets_dir, "u1_v16"),
os.path.join(node_v16_wallets_dir, "wallets/u1_v16")
)
self.start_node(-1, extra_args=["-wallet=u1_v16"])
wallet = node_v16.get_wallet_rpc("u1_v16")
info = wallet.validateaddress(v16_addr)
assert_equal(info, v16_info)
# Copy the 0.17 wallet to the last DigiByte Core version and open it:
node_v17.unloadwallet("u1_v17")
shutil.copytree(
os.path.join(node_v17_wallets_dir, "u1_v17"),
os.path.join(node_master_wallets_dir, "u1_v17")
)
node_master.loadwallet("u1_v17")
wallet = node_master.get_wallet_rpc("u1_v17")
info = wallet.getaddressinfo(address)
descriptor = f"wpkh([{info['hdmasterfingerprint']}{hdkeypath[1:]}]{pubkey})"
assert_equal(info["desc"], descsum_create(descriptor))
# Now copy that same wallet back to 0.17 to make sure no automatic upgrade breaks it
node_master.unloadwallet("u1_v17")
shutil.rmtree(os.path.join(node_v17_wallets_dir, "u1_v17"))
shutil.copytree(
os.path.join(node_master_wallets_dir, "u1_v17"),
os.path.join(node_v17_wallets_dir, "u1_v17")
)
node_v17.loadwallet("u1_v17")
wallet = node_v17.get_wallet_rpc("u1_v17")
info = wallet.getaddressinfo(address)
assert_equal(info, v17_info)
# Copy the 0.19 wallet to the last DigiByte Core version and open it:
shutil.copytree(
os.path.join(node_v19_wallets_dir, "w1_v19"),
os.path.join(node_master_wallets_dir, "w1_v19")
)
node_master.loadwallet("w1_v19")
wallet = node_master.get_wallet_rpc("w1_v19")
assert wallet.getaddressinfo(address_18075)["solvable"]
# Now copy that same wallet back to 0.19 to make sure no automatic upgrade breaks it
node_master.unloadwallet("w1_v19")
shutil.rmtree(os.path.join(node_v19_wallets_dir, "w1_v19"))
shutil.copytree(
os.path.join(node_master_wallets_dir, "w1_v19"),
os.path.join(node_v19_wallets_dir, "w1_v19")
)
node_v19.loadwallet("w1_v19")
wallet = node_v19.get_wallet_rpc("w1_v19")
assert wallet.getaddressinfo(address_18075)["solvable"]
if __name__ == '__main__':
BackwardsCompatibilityTest().main()

View file

@ -0,0 +1,95 @@
#!/usr/bin/env python3
# Copyright (c) 2014-2020 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""
Test starting digibyted with -bind and/or -bind=...=onion and confirm
that bind happens on the expected ports.
"""
import sys
from test_framework.netutil import (
addr_to_hex,
get_bind_addrs,
)
from test_framework.test_framework import (
DigiByteTestFramework,
SkipTest,
)
from test_framework.util import (
PORT_MIN,
PORT_RANGE,
assert_equal,
rpc_port,
)
class BindExtraTest(DigiByteTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
# Avoid any -bind= on the command line. Force the framework to avoid
# adding -bind=127.0.0.1.
self.bind_to_localhost_only = False
self.num_nodes = 2
def setup_network(self):
# Override setup_network() because we want to put the result of
# p2p_port() in self.extra_args[], before the nodes are started.
# p2p_port() is not usable in set_test_params() because PortSeed.n is
# not set at that time.
# Due to OS-specific network stats queries, we only run on Linux.
self.log.info("Checking for Linux")
if not sys.platform.startswith('linux'):
raise SkipTest("This test can only be run on Linux.")
loopback_ipv4 = addr_to_hex("127.0.0.1")
# Start custom ports after p2p and rpc ports.
port = PORT_MIN + 2 * PORT_RANGE
# Array of tuples [command line arguments, expected bind addresses].
self.expected = []
# Node0, no normal -bind=... with -bind=...=onion, thus only the tor target.
self.expected.append(
[
[f"-bind=127.0.0.1:{port}=onion"],
[(loopback_ipv4, port)]
],
)
port += 1
# Node1, both -bind=... and -bind=...=onion.
self.expected.append(
[
[f"-bind=127.0.0.1:{port}", f"-bind=127.0.0.1:{port + 1}=onion"],
[(loopback_ipv4, port), (loopback_ipv4, port + 1)]
],
)
port += 2
self.extra_args = list(map(lambda e: e[0], self.expected))
self.add_nodes(self.num_nodes, self.extra_args)
# Don't start the nodes, as some of them would collide trying to bind on the same port.
def run_test(self):
for i in range(len(self.expected)):
self.log.info(f"Starting node {i} with {self.expected[i][0]}")
self.start_node(i)
pid = self.nodes[i].process.pid
binds = set(get_bind_addrs(pid))
# Remove IPv6 addresses because on some CI environments "::1" is not configured
# on the system (so our test_ipv6_local() would return False), but it is
# possible to bind on "::". This makes it unpredictable whether to expect
# that digibyted has bound on "::1" (for RPC) and "::" (for P2P).
ipv6_addr_len_bytes = 32
binds = set(filter(lambda e: len(e[0]) != ipv6_addr_len_bytes, binds))
# Remove RPC ports. They are not relevant for this test.
binds = set(filter(lambda e: e[1] != rpc_port(i), binds))
assert_equal(binds, set(self.expected[i][1]))
self.stop_node(i)
self.log.info(f"Stopped node {i}")
if __name__ == '__main__':
BindExtraTest().main()

View file

@ -0,0 +1,428 @@
#!/usr/bin/env python3
# Copyright (c) 2014-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test BIP68 implementation."""
import time
from test_framework.blocktools import (
NORMAL_GBT_REQUEST_PARAMS,
add_witness_commitment,
create_block,
)
from test_framework.messages import (
COIN,
COutPoint,
CTransaction,
CTxIn,
CTxOut,
tx_from_hex,
)
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import (
assert_equal,
assert_greater_than,
assert_raises_rpc_error,
softfork_active,
)
from test_framework.script_util import DUMMY_P2WPKH_SCRIPT
SEQUENCE_LOCKTIME_DISABLE_FLAG = (1<<31)
SEQUENCE_LOCKTIME_TYPE_FLAG = (1<<22) # this means use time (0 means height)
SEQUENCE_LOCKTIME_GRANULARITY = 9 # this is a bit-shift
SEQUENCE_LOCKTIME_MASK = 0x0000ffff
# RPC error for non-BIP68 final transactions
NOT_FINAL_ERROR = "non-BIP68-final"
class BIP68Test(DigiByteTestFramework):
def set_test_params(self):
self.num_nodes = 2
self.extra_args = [
[
'-testactivationheight=csv@432',
"-acceptnonstdtxn=1",
'-easypow',
],
[
'-testactivationheight=csv@432',
"-acceptnonstdtxn=0",
'-easypow',
],
]
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
def run_test(self):
self.relayfee = self.nodes[0].getnetworkinfo()["relayfee"]
# Generate some coins
self.generate(self.nodes[0], 110)
self.log.info("Running test disable flag")
self.test_disable_flag()
self.log.info("Running test sequence-lock-confirmed-inputs")
self.test_sequence_lock_confirmed_inputs()
self.log.info("Running test sequence-lock-unconfirmed-inputs")
self.test_sequence_lock_unconfirmed_inputs()
self.log.info("Running test BIP68 not consensus before activation")
self.test_bip68_not_consensus()
self.log.info("Activating BIP68 (and 112/113)")
self.activateCSV()
self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 5)
self.generate(self.nodes[0], 1, sync_fun=self.no_op)
self.sync_blocks()
self.log.info("Verifying nVersion=2 transactions are standard.")
self.log.info("Note that nVersion=2 transactions are always standard (independent of BIP68 activation status).")
self.test_version2_relay()
self.log.info("Passed")
# Test that BIP68 is not in effect if tx version is 1, or if
# the first sequence bit is set.
def test_disable_flag(self):
# Create some unconfirmed inputs
new_addr = self.nodes[0].getnewaddress()
self.nodes[0].sendtoaddress(new_addr, 2) # send 2 BTC
utxos = self.nodes[0].listunspent(0, 0)
assert len(utxos) > 0
utxo = utxos[0]
tx1 = CTransaction()
value = int((utxo["amount"] - self.relayfee) * COIN)
# Check that the disable flag disables relative locktime.
# If sequence locks were used, this would require 1 block for the
# input to mature.
sequence_value = SEQUENCE_LOCKTIME_DISABLE_FLAG | 1
tx1.vin = [CTxIn(COutPoint(int(utxo["txid"], 16), utxo["vout"]), nSequence=sequence_value)]
tx1.vout = [CTxOut(value, DUMMY_P2WPKH_SCRIPT)]
tx1_signed = self.nodes[0].signrawtransactionwithwallet(tx1.serialize().hex())["hex"]
tx1_id = self.nodes[0].sendrawtransaction(tx1_signed)
tx1_id = int(tx1_id, 16)
# This transaction will enable sequence-locks, so this transaction should
# fail
tx2 = CTransaction()
tx2.nVersion = 2
sequence_value = sequence_value & 0x7fffffff
tx2.vin = [CTxIn(COutPoint(tx1_id, 0), nSequence=sequence_value)]
tx2.vout = [CTxOut(int(value - self.relayfee * COIN), DUMMY_P2WPKH_SCRIPT)]
tx2.rehash()
assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.nodes[0].sendrawtransaction, tx2.serialize().hex())
# Setting the version back down to 1 should disable the sequence lock,
# so this should be accepted.
tx2.nVersion = 1
self.nodes[0].sendrawtransaction(tx2.serialize().hex())
# Calculate the median time past of a prior block ("confirmations" before
# the current tip).
def get_median_time_past(self, confirmations):
block_hash = self.nodes[0].getblockhash(self.nodes[0].getblockcount()-confirmations)
return self.nodes[0].getblockheader(block_hash)["mediantime"]
# Test that sequence locks are respected for transactions spending confirmed inputs.
def test_sequence_lock_confirmed_inputs(self):
# Create lots of confirmed utxos, and use them to generate lots of random
# transactions.
max_outputs = 50
addresses = []
while len(addresses) < max_outputs:
addresses.append(self.nodes[0].getnewaddress())
while len(self.nodes[0].listunspent()) < 200:
import random
random.shuffle(addresses)
num_outputs = random.randint(1, max_outputs)
outputs = {}
for i in range(num_outputs):
outputs[addresses[i]] = random.randint(1, 20)*0.01
self.nodes[0].sendmany("", outputs)
self.generate(self.nodes[0], 1)
utxos = self.nodes[0].listunspent()
# Try creating a lot of random transactions.
# Each time, choose a random number of inputs, and randomly set
# some of those inputs to be sequence locked (and randomly choose
# between height/time locking). Small random chance of making the locks
# all pass.
for _ in range(400):
# Randomly choose up to 10 inputs
num_inputs = random.randint(1, 10)
random.shuffle(utxos)
# Track whether any sequence locks used should fail
should_pass = True
# Track whether this transaction was built with sequence locks
using_sequence_locks = False
tx = CTransaction()
tx.nVersion = 2
value = 0
for j in range(num_inputs):
sequence_value = 0xfffffffe # this disables sequence locks
# 50% chance we enable sequence locks
if random.randint(0,1):
using_sequence_locks = True
# 10% of the time, make the input sequence value pass
input_will_pass = (random.randint(1,10) == 1)
sequence_value = utxos[j]["confirmations"]
if not input_will_pass:
sequence_value += 1
should_pass = False
# Figure out what the median-time-past was for the confirmed input
# Note that if an input has N confirmations, we're going back N blocks
# from the tip so that we're looking up MTP of the block
# PRIOR to the one the input appears in, as per the BIP68 spec.
orig_time = self.get_median_time_past(utxos[j]["confirmations"])
cur_time = self.get_median_time_past(0) # MTP of the tip
# can only timelock this input if it's not too old -- otherwise use height
can_time_lock = True
if ((cur_time - orig_time) >> SEQUENCE_LOCKTIME_GRANULARITY) >= SEQUENCE_LOCKTIME_MASK:
can_time_lock = False
# if time-lockable, then 50% chance we make this a time lock
if random.randint(0,1) and can_time_lock:
# Find first time-lock value that fails, or latest one that succeeds
time_delta = sequence_value << SEQUENCE_LOCKTIME_GRANULARITY
if input_will_pass and time_delta > cur_time - orig_time:
sequence_value = ((cur_time - orig_time) >> SEQUENCE_LOCKTIME_GRANULARITY)
elif (not input_will_pass and time_delta <= cur_time - orig_time):
sequence_value = ((cur_time - orig_time) >> SEQUENCE_LOCKTIME_GRANULARITY)+1
sequence_value |= SEQUENCE_LOCKTIME_TYPE_FLAG
tx.vin.append(CTxIn(COutPoint(int(utxos[j]["txid"], 16), utxos[j]["vout"]), nSequence=sequence_value))
value += utxos[j]["amount"]*COIN
# Overestimate the size of the tx - signatures should be less than 120 bytes, and leave 50 for the output
tx_size = len(tx.serialize().hex())//2 + 120*num_inputs + 50
tx.vout.append(CTxOut(int(value-self.relayfee*tx_size*COIN/1000), DUMMY_P2WPKH_SCRIPT))
rawtx = self.nodes[0].signrawtransactionwithwallet(tx.serialize().hex())["hex"]
if (using_sequence_locks and not should_pass):
# This transaction should be rejected
assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.nodes[0].sendrawtransaction, rawtx)
else:
# This raw transaction should be accepted
self.nodes[0].sendrawtransaction(rawtx)
utxos = self.nodes[0].listunspent()
# Test that sequence locks on unconfirmed inputs must have nSequence
# height or time of 0 to be accepted.
# Then test that BIP68-invalid transactions are removed from the mempool
# after a reorg.
def test_sequence_lock_unconfirmed_inputs(self):
# Store height so we can easily reset the chain at the end of the test
cur_height = self.nodes[0].getblockcount()
# Create a mempool tx.
txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 2)
tx1 = tx_from_hex(self.nodes[0].getrawtransaction(txid))
tx1.rehash()
# Anyone-can-spend mempool tx.
# Sequence lock of 0 should pass.
tx2 = CTransaction()
tx2.nVersion = 2
tx2.vin = [CTxIn(COutPoint(tx1.sha256, 0), nSequence=0)]
tx2.vout = [CTxOut(int(tx1.vout[0].nValue - self.relayfee*COIN), DUMMY_P2WPKH_SCRIPT)]
tx2_raw = self.nodes[0].signrawtransactionwithwallet(tx2.serialize().hex())["hex"]
tx2 = tx_from_hex(tx2_raw)
tx2.rehash()
self.nodes[0].sendrawtransaction(tx2_raw)
# Create a spend of the 0th output of orig_tx with a sequence lock
# of 1, and test what happens when submitting.
# orig_tx.vout[0] must be an anyone-can-spend output
def test_nonzero_locks(orig_tx, node, relayfee, use_height_lock):
sequence_value = 1
if not use_height_lock:
sequence_value |= SEQUENCE_LOCKTIME_TYPE_FLAG
tx = CTransaction()
tx.nVersion = 2
tx.vin = [CTxIn(COutPoint(orig_tx.sha256, 0), nSequence=sequence_value)]
tx.vout = [CTxOut(int(orig_tx.vout[0].nValue - relayfee * COIN), DUMMY_P2WPKH_SCRIPT)]
tx.rehash()
if (orig_tx.hash in node.getrawmempool()):
# sendrawtransaction should fail if the tx is in the mempool
assert_raises_rpc_error(-26, NOT_FINAL_ERROR, node.sendrawtransaction, tx.serialize().hex())
else:
# sendrawtransaction should succeed if the tx is not in the mempool
node.sendrawtransaction(tx.serialize().hex())
return tx
test_nonzero_locks(tx2, self.nodes[0], self.relayfee, use_height_lock=True)
test_nonzero_locks(tx2, self.nodes[0], self.relayfee, use_height_lock=False)
# Now mine some blocks, but make sure tx2 doesn't get mined.
# Use prioritisetransaction to lower the effective feerate to 0
self.nodes[0].prioritisetransaction(txid=tx2.hash, fee_delta=int(-self.relayfee*COIN))
cur_time = int(time.time())
for _ in range(10):
self.nodes[0].setmocktime(cur_time + 600)
self.generate(self.nodes[0], 1, sync_fun=self.no_op)
cur_time += 600
assert tx2.hash in self.nodes[0].getrawmempool()
test_nonzero_locks(tx2, self.nodes[0], self.relayfee, use_height_lock=True)
test_nonzero_locks(tx2, self.nodes[0], self.relayfee, use_height_lock=False)
# Mine tx2, and then try again
self.nodes[0].prioritisetransaction(txid=tx2.hash, fee_delta=int(self.relayfee*COIN))
# Advance the time on the node so that we can test timelocks
self.nodes[0].setmocktime(cur_time+600)
# Save block template now to use for the reorg later
tmpl = self.nodes[0].getblocktemplate(NORMAL_GBT_REQUEST_PARAMS)
self.generate(self.nodes[0], 1)
assert tx2.hash not in self.nodes[0].getrawmempool()
# Now that tx2 is not in the mempool, a sequence locked spend should
# succeed
tx3 = test_nonzero_locks(tx2, self.nodes[0], self.relayfee, use_height_lock=False)
assert tx3.hash in self.nodes[0].getrawmempool()
self.generate(self.nodes[0], 1)
assert tx3.hash not in self.nodes[0].getrawmempool()
# One more test, this time using height locks
tx4 = test_nonzero_locks(tx3, self.nodes[0], self.relayfee, use_height_lock=True)
assert tx4.hash in self.nodes[0].getrawmempool()
# Now try combining confirmed and unconfirmed inputs
tx5 = test_nonzero_locks(tx4, self.nodes[0], self.relayfee, use_height_lock=True)
assert tx5.hash not in self.nodes[0].getrawmempool()
utxos = self.nodes[0].listunspent()
tx5.vin.append(CTxIn(COutPoint(int(utxos[0]["txid"], 16), utxos[0]["vout"]), nSequence=1))
tx5.vout[0].nValue += int(utxos[0]["amount"]*COIN)
raw_tx5 = self.nodes[0].signrawtransactionwithwallet(tx5.serialize().hex())["hex"]
assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.nodes[0].sendrawtransaction, raw_tx5)
# Test mempool-BIP68 consistency after reorg
#
# State of the transactions in the last blocks:
# ... -> [ tx2 ] -> [ tx3 ]
# tip-1 tip
# And currently tx4 is in the mempool.
#
# If we invalidate the tip, tx3 should get added to the mempool, causing
# tx4 to be removed (fails sequence-lock).
self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
assert tx4.hash not in self.nodes[0].getrawmempool()
assert tx3.hash in self.nodes[0].getrawmempool()
# Now mine 2 empty blocks to reorg out the current tip (labeled tip-1 in
# diagram above).
# This would cause tx2 to be added back to the mempool, which in turn causes
# tx3 to be removed.
for i in range(2):
block = create_block(tmpl=tmpl, ntime=cur_time)
block.solve()
tip = block.sha256
assert_equal(None if i == 1 else 'inconclusive', self.nodes[0].submitblock(block.serialize().hex()))
tmpl = self.nodes[0].getblocktemplate(NORMAL_GBT_REQUEST_PARAMS)
tmpl['previousblockhash'] = '%x' % tip
tmpl['transactions'] = []
cur_time += 1
mempool = self.nodes[0].getrawmempool()
assert tx3.hash not in mempool
assert tx2.hash in mempool
# Reset the chain and get rid of the mocktimed-blocks
self.nodes[0].setmocktime(0)
self.nodes[0].invalidateblock(self.nodes[0].getblockhash(cur_height+1))
self.generate(self.nodes[0], 10, sync_fun=self.no_op)
# Make sure that BIP68 isn't being used to validate blocks prior to
# activation height. If more blocks are mined prior to this test
# being run, then it's possible the test has activated the soft fork, and
# this test should be moved to run earlier, or deleted.
def test_bip68_not_consensus(self):
assert not softfork_active(self.nodes[0], 'csv')
txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 2)
tx1 = tx_from_hex(self.nodes[0].getrawtransaction(txid))
tx1.rehash()
# Make an anyone-can-spend transaction
tx2 = CTransaction()
tx2.nVersion = 1
tx2.vin = [CTxIn(COutPoint(tx1.sha256, 0), nSequence=0)]
tx2.vout = [CTxOut(int(tx1.vout[0].nValue - self.relayfee*COIN), DUMMY_P2WPKH_SCRIPT)]
# sign tx2
tx2_raw = self.nodes[0].signrawtransactionwithwallet(tx2.serialize().hex())["hex"]
tx2 = tx_from_hex(tx2_raw)
tx2.rehash()
self.nodes[0].sendrawtransaction(tx2.serialize().hex())
# Now make an invalid spend of tx2 according to BIP68
sequence_value = 100 # 100 block relative locktime
tx3 = CTransaction()
tx3.nVersion = 2
tx3.vin = [CTxIn(COutPoint(tx2.sha256, 0), nSequence=sequence_value)]
tx3.vout = [CTxOut(int(tx2.vout[0].nValue - self.relayfee * COIN), DUMMY_P2WPKH_SCRIPT)]
tx3.rehash()
assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.nodes[0].sendrawtransaction, tx3.serialize().hex())
# make a block that violates bip68; ensure that the tip updates
block = create_block(tmpl=self.nodes[0].getblocktemplate(NORMAL_GBT_REQUEST_PARAMS), txlist=[tx1, tx2, tx3])
add_witness_commitment(block)
block.solve()
assert_equal(None, self.nodes[0].submitblock(block.serialize().hex()))
assert_equal(self.nodes[0].getbestblockhash(), block.hash)
def activateCSV(self):
# activation should happen at block height 432 (3 periods)
# getblockchaininfo will show CSV as active at block 431 (144 * 3 -1) since it's returning whether CSV is active for the next block.
min_activation_height = 432
height = self.nodes[0].getblockcount()
assert_greater_than(min_activation_height - height, 2)
self.generate(self.nodes[0], min_activation_height - height - 2, sync_fun=self.no_op)
assert not softfork_active(self.nodes[0], 'csv')
self.generate(self.nodes[0], 1, sync_fun=self.no_op)
assert softfork_active(self.nodes[0], 'csv')
self.sync_blocks()
# Use self.nodes[1] to test that version 2 transactions are standard.
def test_version2_relay(self):
inputs = [ ]
outputs = { self.nodes[1].getnewaddress() : 1.0 }
rawtx = self.nodes[1].createrawtransaction(inputs, outputs)
rawtxfund = self.nodes[1].fundrawtransaction(rawtx)['hex']
tx = tx_from_hex(rawtxfund)
tx.nVersion = 2
tx_signed = self.nodes[1].signrawtransactionwithwallet(tx.serialize().hex())["hex"]
self.nodes[1].sendrawtransaction(tx_signed)
if __name__ == '__main__':
BIP68Test().main()

View file

@ -0,0 +1,283 @@
#!/usr/bin/env python3
# Copyright (c) 2015-2017 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test BIP 9 soft forks.
Connect to a single node.
regtest lock-in with 108/144 block signalling
activation after a further 144 blocks
mine 2 block and save coinbases for later use
mine 141 blocks to transition from DEFINED to STARTED
mine 100 blocks signalling readiness and 44 not in order to fail to change state this period
mine 108 blocks signalling readiness and 36 blocks not signalling readiness (STARTED->LOCKED_IN)
mine a further 143 blocks (LOCKED_IN)
test that enforcement has not triggered (which triggers ACTIVE)
test that enforcement has triggered
"""
from io import BytesIO
import shutil
import time
import itertools
from test_framework.test_framework import ComparisonTestFramework
from test_framework.util import *
from test_framework.mininode import CTransaction, network_thread_start
from test_framework.blocktools import create_coinbase, create_block
from test_framework.comptool import TestInstance, TestManager
from test_framework.script import CScript, OP_1NEGATE, OP_CHECKSEQUENCEVERIFY, OP_DROP
class BIP9SoftForksTest(ComparisonTestFramework):
def set_test_params(self):
self.num_nodes = 1
self.extra_args = [['-whitelist=127.0.0.1']]
self.setup_clean_chain = True
def run_test(self):
self.test = TestManager(self, self.options.tmpdir)
self.test.add_all_connections(self.nodes)
network_thread_start()
self.test.run()
def create_transaction(self, node, coinbase, to_address, amount):
from_txid = node.getblock(coinbase)['tx'][0]
inputs = [{ "txid" : from_txid, "vout" : 0}]
outputs = { to_address : amount }
rawtx = node.createrawtransaction(inputs, outputs)
tx = CTransaction()
f = BytesIO(hex_str_to_bytes(rawtx))
tx.deserialize(f)
tx.nVersion = 2
return tx
def sign_transaction(self, node, tx):
signresult = node.signrawtransactionwithwallet(bytes_to_hex_str(tx.serialize()))
tx = CTransaction()
f = BytesIO(hex_str_to_bytes(signresult['hex']))
tx.deserialize(f)
return tx
def generate_blocks(self, number, version, test_blocks = []):
for i in range(number):
block = create_block(self.tip, create_coinbase(self.height), self.last_block_time + 1)
block.nVersion = version
block.rehash()
block.solve()
test_blocks.append([block, True])
self.last_block_time += 1
self.tip = block.sha256
self.height += 1
return test_blocks
def get_bip9_status(self, key):
info = self.nodes[0].getblockchaininfo()
return info['bip9_softforks'][key]
def test_BIP(self, bipName, activated_version, invalidate, invalidatePostSignature, bitno):
assert_equal(self.get_bip9_status(bipName)['status'], 'defined')
assert_equal(self.get_bip9_status(bipName)['since'], 0)
# generate some coins for later
self.coinbase_blocks = self.nodes[0].generate(2)
self.height = 3 # height of the next block to build
self.tip = int("0x" + self.nodes[0].getbestblockhash(), 0)
self.nodeaddress = self.nodes[0].getnewaddress()
self.last_block_time = int(time.time())
assert_equal(self.get_bip9_status(bipName)['status'], 'defined')
assert_equal(self.get_bip9_status(bipName)['since'], 0)
tmpl = self.nodes[0].getblocktemplate({})
assert(bipName not in tmpl['rules'])
assert(bipName not in tmpl['vbavailable'])
assert_equal(tmpl['vbrequired'], 0)
assert_equal(tmpl['version'], 0x20000000)
# Test 1
# Advance from DEFINED to STARTED
test_blocks = self.generate_blocks(141, 4)
yield TestInstance(test_blocks, sync_every_block=False)
assert_equal(self.get_bip9_status(bipName)['status'], 'started')
assert_equal(self.get_bip9_status(bipName)['since'], 144)
assert_equal(self.get_bip9_status(bipName)['statistics']['elapsed'], 0)
assert_equal(self.get_bip9_status(bipName)['statistics']['count'], 0)
tmpl = self.nodes[0].getblocktemplate({})
assert(bipName not in tmpl['rules'])
assert_equal(tmpl['vbavailable'][bipName], bitno)
assert_equal(tmpl['vbrequired'], 0)
assert(tmpl['version'] & activated_version)
# Test 1-A
# check stats after max number of "signalling not" blocks such that LOCKED_IN still possible this period
test_blocks = self.generate_blocks(36, 4, test_blocks) # 0x00000004 (signalling not)
test_blocks = self.generate_blocks(10, activated_version) # 0x20000001 (signalling ready)
yield TestInstance(test_blocks, sync_every_block=False)
assert_equal(self.get_bip9_status(bipName)['statistics']['elapsed'], 46)
assert_equal(self.get_bip9_status(bipName)['statistics']['count'], 10)
assert_equal(self.get_bip9_status(bipName)['statistics']['possible'], True)
# Test 1-B
# check stats after one additional "signalling not" block -- LOCKED_IN no longer possible this period
test_blocks = self.generate_blocks(1, 4, test_blocks) # 0x00000004 (signalling not)
yield TestInstance(test_blocks, sync_every_block=False)
assert_equal(self.get_bip9_status(bipName)['statistics']['elapsed'], 47)
assert_equal(self.get_bip9_status(bipName)['statistics']['count'], 10)
assert_equal(self.get_bip9_status(bipName)['statistics']['possible'], False)
# Test 1-C
# finish period with "ready" blocks, but soft fork will still fail to advance to LOCKED_IN
test_blocks = self.generate_blocks(97, activated_version) # 0x20000001 (signalling ready)
yield TestInstance(test_blocks, sync_every_block=False)
assert_equal(self.get_bip9_status(bipName)['statistics']['elapsed'], 0)
assert_equal(self.get_bip9_status(bipName)['statistics']['count'], 0)
assert_equal(self.get_bip9_status(bipName)['statistics']['possible'], True)
assert_equal(self.get_bip9_status(bipName)['status'], 'started')
# Test 2
# Fail to achieve LOCKED_IN 100 out of 144 signal bit 1
# using a variety of bits to simulate multiple parallel softforks
test_blocks = self.generate_blocks(50, activated_version) # 0x20000001 (signalling ready)
test_blocks = self.generate_blocks(20, 4, test_blocks) # 0x00000004 (signalling not)
test_blocks = self.generate_blocks(50, activated_version, test_blocks) # 0x20000101 (signalling ready)
test_blocks = self.generate_blocks(24, 4, test_blocks) # 0x20010000 (signalling not)
yield TestInstance(test_blocks, sync_every_block=False)
assert_equal(self.get_bip9_status(bipName)['status'], 'started')
assert_equal(self.get_bip9_status(bipName)['since'], 144)
assert_equal(self.get_bip9_status(bipName)['statistics']['elapsed'], 0)
assert_equal(self.get_bip9_status(bipName)['statistics']['count'], 0)
tmpl = self.nodes[0].getblocktemplate({})
assert(bipName not in tmpl['rules'])
assert_equal(tmpl['vbavailable'][bipName], bitno)
assert_equal(tmpl['vbrequired'], 0)
assert(tmpl['version'] & activated_version)
# Test 3
# 108 out of 144 signal bit 1 to achieve LOCKED_IN
# using a variety of bits to simulate multiple parallel softforks
test_blocks = self.generate_blocks(57, activated_version) # 0x20000001 (signalling ready)
test_blocks = self.generate_blocks(26, 4, test_blocks) # 0x00000004 (signalling not)
test_blocks = self.generate_blocks(50, activated_version, test_blocks) # 0x20000101 (signalling ready)
test_blocks = self.generate_blocks(10, 4, test_blocks) # 0x20010000 (signalling not)
yield TestInstance(test_blocks, sync_every_block=False)
# check counting stats and "possible" flag before last block of this period achieves LOCKED_IN...
assert_equal(self.get_bip9_status(bipName)['statistics']['elapsed'], 143)
assert_equal(self.get_bip9_status(bipName)['statistics']['count'], 107)
assert_equal(self.get_bip9_status(bipName)['statistics']['possible'], True)
assert_equal(self.get_bip9_status(bipName)['status'], 'started')
# ...continue with Test 3
test_blocks = self.generate_blocks(1, activated_version) # 0x20000001 (signalling ready)
yield TestInstance(test_blocks, sync_every_block=False)
assert_equal(self.get_bip9_status(bipName)['status'], 'locked_in')
assert_equal(self.get_bip9_status(bipName)['since'], 576)
tmpl = self.nodes[0].getblocktemplate({})
assert(bipName not in tmpl['rules'])
# Test 4
# 143 more version 536870913 blocks (waiting period-1)
test_blocks = self.generate_blocks(143, 4)
yield TestInstance(test_blocks, sync_every_block=False)
assert_equal(self.get_bip9_status(bipName)['status'], 'locked_in')
assert_equal(self.get_bip9_status(bipName)['since'], 576)
tmpl = self.nodes[0].getblocktemplate({})
assert(bipName not in tmpl['rules'])
# Test 5
# Check that the new rule is enforced
spendtx = self.create_transaction(self.nodes[0],
self.coinbase_blocks[0], self.nodeaddress, 1.0)
invalidate(spendtx)
spendtx = self.sign_transaction(self.nodes[0], spendtx)
spendtx.rehash()
invalidatePostSignature(spendtx)
spendtx.rehash()
block = create_block(self.tip, create_coinbase(self.height), self.last_block_time + 1)
block.nVersion = activated_version
block.vtx.append(spendtx)
block.hashMerkleRoot = block.calc_merkle_root()
block.rehash()
block.solve()
self.last_block_time += 1
self.tip = block.sha256
self.height += 1
yield TestInstance([[block, True]])
assert_equal(self.get_bip9_status(bipName)['status'], 'active')
assert_equal(self.get_bip9_status(bipName)['since'], 720)
tmpl = self.nodes[0].getblocktemplate({})
assert(bipName in tmpl['rules'])
assert(bipName not in tmpl['vbavailable'])
assert_equal(tmpl['vbrequired'], 0)
assert(not (tmpl['version'] & (1 << bitno)))
# Test 6
# Check that the new sequence lock rules are enforced
spendtx = self.create_transaction(self.nodes[0],
self.coinbase_blocks[1], self.nodeaddress, 1.0)
invalidate(spendtx)
spendtx = self.sign_transaction(self.nodes[0], spendtx)
spendtx.rehash()
invalidatePostSignature(spendtx)
spendtx.rehash()
block = create_block(self.tip, create_coinbase(self.height), self.last_block_time + 1)
block.nVersion = 5
block.vtx.append(spendtx)
block.hashMerkleRoot = block.calc_merkle_root()
block.rehash()
block.solve()
self.last_block_time += 1
yield TestInstance([[block, False]])
# Restart all
self.test.clear_all_connections()
self.stop_nodes()
self.nodes = []
shutil.rmtree(self.options.tmpdir + "/node0")
self.setup_chain()
self.setup_network()
self.test.add_all_connections(self.nodes)
network_thread_start()
self.test.p2p_connections[0].wait_for_verack()
def get_tests(self):
for test in itertools.chain(
self.test_BIP('csv', 0x20000001, self.sequence_lock_invalidate, self.donothing, 0),
self.test_BIP('csv', 0x20000001, self.mtp_invalidate, self.donothing, 0),
self.test_BIP('csv', 0x20000001, self.donothing, self.csv_invalidate, 0)
):
yield test
def donothing(self, tx):
return
def csv_invalidate(self, tx):
"""Modify the signature in vin 0 of the tx to fail CSV
Prepends -1 CSV DROP in the scriptSig itself.
"""
tx.vin[0].scriptSig = CScript([OP_1NEGATE, OP_CHECKSEQUENCEVERIFY, OP_DROP] +
list(CScript(tx.vin[0].scriptSig)))
def sequence_lock_invalidate(self, tx):
"""Modify the nSequence to make it fails once sequence lock rule is
activated (high timespan).
"""
tx.vin[0].nSequence = 0x00FFFFFF
tx.nLockTime = 0
def mtp_invalidate(self, tx):
"""Modify the nLockTime to make it fails once MTP rule is activated."""
# Disable Sequence lock, Activate nLockTime
tx.vin[0].nSequence = 0x90FFFFFF
tx.nLockTime = self.last_block_time
if __name__ == '__main__':
BIP9SoftForksTest().main()

1459
test/functional/feature_block.py Executable file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,84 @@
#!/usr/bin/env python3
# Copyright (c) 2020-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test blockfilterindex in conjunction with prune."""
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import (
assert_equal,
assert_greater_than,
assert_raises_rpc_error,
)
import time
class FeatureBlockfilterindexPruneTest(DigiByteTestFramework):
def set_test_params(self):
self.num_nodes = 1
self.extra_args = [["-fastprune", "-prune=1", "-blockfilterindex=1"]]
def sync_index(self, height):
expected = {'basic block filter index': {'synced': True, 'best_block_height': height}}
self.wait_until(lambda: self.nodes[0].getindexinfo() == expected)
def run_test(self):
self.log.info("check if we can access a blockfilter when pruning is enabled but no blocks are actually pruned")
self.sync_index(height=200)
assert_greater_than(len(self.nodes[0].getblockfilter(self.nodes[0].getbestblockhash())['filter']), 0)
self.generate(self.nodes[0], 500)
self.sync_index(height=700)
self.log.info("prune some blocks")
pruneheight = self.nodes[0].pruneblockchain(400)
# the prune heights used here and below are magic numbers that are determined by the
# thresholds at which block files wrap, so they depend on disk serialization and default block file size.
assert_equal(pruneheight, 253)
self.log.info("check if we can access the tips blockfilter when we have pruned some blocks")
assert_greater_than(len(self.nodes[0].getblockfilter(self.nodes[0].getbestblockhash())['filter']), 0)
self.log.info("check if we can access the blockfilter of a pruned block")
assert_greater_than(len(self.nodes[0].getblockfilter(self.nodes[0].getblockhash(2))['filter']), 0)
# mine and sync index up to a height that will later be the pruneheight
self.generate(self.nodes[0], 298)
self.sync_index(height=998)
self.log.info("start node without blockfilterindex")
self.restart_node(0, extra_args=["-fastprune", "-prune=1"])
self.log.info("make sure accessing the blockfilters throws an error")
assert_raises_rpc_error(-1, "Index is not enabled for filtertype basic", self.nodes[0].getblockfilter, self.nodes[0].getblockhash(2))
# Generate 502 blocks
self.generate(self.nodes[0], 287)
self.log.info("prune exactly up to the blockfilterindexes best block while blockfilters are disabled")
pruneheight_2 = self.nodes[0].pruneblockchain(1000)
assert_equal(pruneheight_2, 753)
self.restart_node(0, extra_args=["-fastprune", "-prune=1", "-blockfilterindex=1"])
self.log.info("make sure that we can continue with the partially synced index after having pruned up to the index height")
self.sync_index(height=1285)
self.log.info("prune below the blockfilterindexes best block while blockfilters are disabled")
self.restart_node(0, extra_args=["-fastprune", "-prune=1"])
self.generate(self.nodes[0], 1000)
pruneheight_3 = self.nodes[0].pruneblockchain(2000)
assert_greater_than(pruneheight_3, pruneheight_2)
self.stop_node(0)
self.log.info("make sure we get an init error when starting the node again with block filters")
self.nodes[0].assert_start_raises_init_error(
extra_args=["-fastprune", "-prune=1", "-blockfilterindex=1"],
expected_msg="Error: basic block filter index best block of the index goes beyond pruned data. Please disable the index or reindex (which will download the whole blockchain again)",
)
self.log.info("make sure the node starts again with the -reindex arg")
self.start_node(0, extra_args=["-fastprune", "-prune=1", "-blockfilterindex", "-reindex"])
if __name__ == '__main__':
FeatureBlockfilterindexPruneTest().main()

View file

@ -0,0 +1,38 @@
#!/usr/bin/env python3
# Copyright (c) 2018-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test the blocksdir option.
"""
import os
import shutil
from test_framework.test_framework import DigiByteTestFramework, initialize_datadir
class BlocksdirTest(DigiByteTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
def run_test(self):
self.stop_node(0)
assert os.path.isdir(os.path.join(self.nodes[0].datadir, self.chain, "blocks"))
assert not os.path.isdir(os.path.join(self.nodes[0].datadir, "blocks"))
shutil.rmtree(self.nodes[0].datadir)
initialize_datadir(self.options.tmpdir, 0, self.chain)
self.log.info("Starting with nonexistent blocksdir ...")
blocksdir_path = os.path.join(self.options.tmpdir, 'blocksdir')
self.nodes[0].assert_start_raises_init_error([f"-blocksdir={blocksdir_path}"], f'Error: Specified blocks directory "{blocksdir_path}" does not exist.')
os.mkdir(blocksdir_path)
self.log.info("Starting with existing blocksdir ...")
self.start_node(0, [f"-blocksdir={blocksdir_path}"])
self.log.info("mining blocks..")
self.generatetoaddress(self.nodes[0], 10, self.nodes[0].get_deterministic_priv_key().address)
assert os.path.isfile(os.path.join(blocksdir_path, self.chain, "blocks", "blk00000.dat"))
assert os.path.isdir(os.path.join(self.nodes[0].datadir, self.chain, "blocks", "index"))
if __name__ == '__main__':
BlocksdirTest().main()

199
test/functional/feature_cltv.py Executable file
View file

@ -0,0 +1,199 @@
#!/usr/bin/env python3
# Copyright (c) 2015-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test BIP65 (CHECKLOCKTIMEVERIFY).
Test that the CHECKLOCKTIMEVERIFY soft-fork activates.
"""
from test_framework.blocktools import (
create_block,
create_coinbase,
)
from test_framework.messages import (
CTransaction,
msg_block,
)
from test_framework.p2p import P2PInterface
from test_framework.script import (
CScript,
CScriptNum,
OP_1NEGATE,
OP_CHECKLOCKTIMEVERIFY,
OP_DROP,
)
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import assert_equal
from test_framework.wallet import (
MiniWallet,
MiniWalletMode,
)
from test_framework.blockversion import (
calc_block_version,
)
# Helper function to modify a transaction by
# 1) prepending a given script to the scriptSig of vin 0 and
# 2) (optionally) modify the nSequence of vin 0 and the tx's nLockTime
def cltv_modify_tx(tx, prepend_scriptsig, nsequence=None, nlocktime=None):
assert_equal(len(tx.vin), 1)
if nsequence is not None:
tx.vin[0].nSequence = nsequence
tx.nLockTime = nlocktime
tx.vin[0].scriptSig = CScript(prepend_scriptsig + list(CScript(tx.vin[0].scriptSig)))
tx.rehash()
def cltv_invalidate(tx, failure_reason):
# Modify the signature in vin 0 and nSequence/nLockTime of the tx to fail CLTV
#
# According to BIP65, OP_CHECKLOCKTIMEVERIFY can fail due the following reasons:
# 1) the stack is empty
# 2) the top item on the stack is less than 0
# 3) the lock-time type (height vs. timestamp) of the top stack item and the
# nLockTime field are not the same
# 4) the top stack item is greater than the transaction's nLockTime field
# 5) the nSequence field of the txin is 0xffffffff
assert failure_reason in range(5)
scheme = [
# | Script to prepend to scriptSig | nSequence | nLockTime |
# +-------------------------------------------------+------------+--------------+
[[OP_CHECKLOCKTIMEVERIFY], None, None],
[[OP_1NEGATE, OP_CHECKLOCKTIMEVERIFY, OP_DROP], None, None],
[[CScriptNum(100), OP_CHECKLOCKTIMEVERIFY, OP_DROP], 0, 1519460922], # timestamp of genesis block
[[CScriptNum(100), OP_CHECKLOCKTIMEVERIFY, OP_DROP], 0, 50],
[[CScriptNum(50), OP_CHECKLOCKTIMEVERIFY, OP_DROP], 0xffffffff, 50],
][failure_reason]
cltv_modify_tx(tx, prepend_scriptsig=scheme[0], nsequence=scheme[1], nlocktime=scheme[2])
def cltv_validate(tx, height):
# Modify the signature in vin 0 and nSequence/nLockTime of the tx to pass CLTV
scheme = [[CScriptNum(height), OP_CHECKLOCKTIMEVERIFY, OP_DROP], 0, height]
cltv_modify_tx(tx, prepend_scriptsig=scheme[0], nsequence=scheme[1], nlocktime=scheme[2])
CLTV_HEIGHT = 111
class BIP65Test(DigiByteTestFramework):
def set_test_params(self):
self.num_nodes = 1
self.extra_args = [[
f'-testactivationheight=cltv@{CLTV_HEIGHT}',
'-whitelist=noban@127.0.0.1',
'-par=1', # Use only one script thread to get the exact reject reason for testing
'-acceptnonstdtxn=1', # cltv_invalidate is nonstandard
]]
self.setup_clean_chain = True
self.rpc_timeout = 480
def test_cltv_info(self, *, is_active):
assert_equal(self.nodes[0].getblockchaininfo()['softforks']['bip65'], {
"active": is_active,
"height": CLTV_HEIGHT,
"type": "buried",
},
)
def run_test(self):
peer = self.nodes[0].add_p2p_connection(P2PInterface())
wallet = MiniWallet(self.nodes[0], mode=MiniWalletMode.RAW_OP_TRUE)
self.test_cltv_info(is_active=False)
self.log.info("Mining %d blocks", CLTV_HEIGHT - 2)
self.generate(wallet, 10)
self.generate(self.nodes[0], CLTV_HEIGHT - 2 - 10)
assert_equal(self.nodes[0].getblockcount(), CLTV_HEIGHT - 2)
self.log.info("Test that invalid-according-to-CLTV transactions can still appear in a block")
# create one invalid tx per CLTV failure reason (5 in total) and collect them
invalid_cltv_txs = []
for i in range(5):
spendtx = wallet.create_self_transfer(from_node=self.nodes[0])['tx']
cltv_invalidate(spendtx, i)
invalid_cltv_txs.append(spendtx)
tip = self.nodes[0].getbestblockhash()
block_time = self.nodes[0].getblockheader(tip)['mediantime'] + 1
block = create_block(int(tip, 16), create_coinbase(CLTV_HEIGHT - 1), block_time, txlist=invalid_cltv_txs)
block.solve()
self.test_cltv_info(is_active=False) # Not active as of current tip and next block does not need to obey rules
peer.send_and_ping(msg_block(block))
self.test_cltv_info(is_active=True) # Not active as of current tip, but next block must obey rules
assert_equal(self.nodes[0].getbestblockhash(), block.hash)
self.log.info("Test that blocks must now be at least version 4")
tip = block.sha256
block_time += 1
block = create_block(tip, create_coinbase(CLTV_HEIGHT), block_time, version=3)
block.solve()
with self.nodes[0].assert_debug_log(expected_msgs=[f'{block.hash}, bad-version(0x00000003)']):
peer.send_and_ping(msg_block(block))
assert_equal(int(self.nodes[0].getbestblockhash(), 16), tip)
peer.sync_with_ping()
self.log.info("Test that invalid-according-to-CLTV transactions cannot appear in a block")
block.nVersion = calc_block_version(4)
block.vtx.append(CTransaction()) # dummy tx after coinbase that will be replaced later
# create and test one invalid tx per CLTV failure reason (5 in total)
for i in range(5):
spendtx = wallet.create_self_transfer(from_node=self.nodes[0])['tx']
cltv_invalidate(spendtx, i)
expected_cltv_reject_reason = [
"non-mandatory-script-verify-flag (Operation not valid with the current stack size)",
"non-mandatory-script-verify-flag (Negative locktime)",
"non-mandatory-script-verify-flag (Locktime requirement not satisfied)",
"non-mandatory-script-verify-flag (Locktime requirement not satisfied)",
"non-mandatory-script-verify-flag (Locktime requirement not satisfied)",
][i]
# First we show that this tx is valid except for CLTV by getting it
# rejected from the mempool for exactly that reason.
assert_equal(
[{
'txid': spendtx.hash,
'wtxid': spendtx.getwtxid(),
'allowed': False,
'reject-reason': expected_cltv_reject_reason,
}],
self.nodes[0].testmempoolaccept(rawtxs=[spendtx.serialize().hex()], maxfeerate=0),
)
# Now we verify that a block with this transaction is also invalid.
block.vtx[1] = spendtx
block.hashMerkleRoot = block.calc_merkle_root()
block.solve()
with self.nodes[0].assert_debug_log(expected_msgs=[f'CheckInputScripts on {block.vtx[-1].hash} failed with {expected_cltv_reject_reason}']):
peer.send_and_ping(msg_block(block))
assert_equal(int(self.nodes[0].getbestblockhash(), 16), tip)
peer.sync_with_ping()
self.log.info("Test that a version 4 block with a valid-according-to-CLTV transaction is accepted")
cltv_validate(spendtx, CLTV_HEIGHT - 1)
block.vtx.pop(1)
block.vtx.append(spendtx)
block.hashMerkleRoot = block.calc_merkle_root()
block.solve()
self.test_cltv_info(is_active=True) # Not active as of current tip, but next block must obey rules
peer.send_and_ping(msg_block(block))
self.test_cltv_info(is_active=True) # Active as of current tip
assert_equal(int(self.nodes[0].getbestblockhash(), 16), block.sha256)
if __name__ == '__main__':
BIP65Test().main()

View file

@ -0,0 +1,316 @@
#!/usr/bin/env python3
# Copyright (c) 2020 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test coinstatsindex across nodes.
Test that the values returned by gettxoutsetinfo are consistent
between a node running the coinstatsindex and a node without
the index.
"""
from decimal import Decimal
from test_framework.blocktools import (
COINBASE_MATURITY,
COINBASE_MATURITY_2,
create_block,
create_coinbase,
)
from test_framework.messages import (
COIN,
COutPoint,
CTransaction,
CTxIn,
CTxOut,
)
from test_framework.script import (
CScript,
OP_FALSE,
OP_RETURN,
)
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
try_rpc,
)
class CoinStatsIndexTest(DigiByteTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 2
self.supports_cli = False
self.extra_args = [
[],
["-coinstatsindex"]
]
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
def run_test(self):
self._test_coin_stats_index()
self._test_use_index_option()
self._test_reorg_index()
self._test_index_rejects_hash_serialized()
def block_sanity_check(self, block_info):
block_subsidy = 50
assert_equal(
block_info['prevout_spent'] + block_subsidy,
block_info['new_outputs_ex_coinbase'] + block_info['coinbase'] + block_info['unspendable']
)
def _test_coin_stats_index(self):
node = self.nodes[0]
index_node = self.nodes[1]
# Both none and muhash options allow the usage of the index
index_hash_options = ['none', 'muhash']
# Generate a normal transaction and mine it
self.generate(node, COINBASE_MATURITY_2 + 1)
address = self.nodes[0].get_deterministic_priv_key().address
node.sendtoaddress(address=address, amount=10, subtractfeefromamount=True)
self.generate(node, 1)
self.sync_blocks(timeout=120)
self.log.info("Test that gettxoutsetinfo() output is consistent with or without coinstatsindex option")
self.wait_until(lambda: not try_rpc(-32603, "Unable to read UTXO set", node.gettxoutsetinfo))
res0 = node.gettxoutsetinfo('none')
# The fields 'disk_size' and 'transactions' do not exist on the index
del res0['disk_size'], res0['transactions']
self.wait_until(lambda: not try_rpc(-32603, "Unable to read UTXO set", index_node.gettxoutsetinfo, 'muhash'))
for hash_option in index_hash_options:
res1 = index_node.gettxoutsetinfo(hash_option)
# The fields 'block_info' and 'total_unspendable_amount' only exist on the index
del res1['block_info'], res1['total_unspendable_amount']
res1.pop('muhash', None)
# Everything left should be the same
assert_equal(res1, res0)
self.log.info("Test that gettxoutsetinfo() can get fetch data on specific heights with index")
# Generate a new tip
self.generate(node, 5)
self.wait_until(lambda: not try_rpc(-32603, "Unable to read UTXO set", index_node.gettxoutsetinfo, 'muhash'))
for hash_option in index_hash_options:
# Fetch old stats by height
res2 = index_node.gettxoutsetinfo(hash_option, 102)
del res2['block_info'], res2['total_unspendable_amount']
res2.pop('muhash', None)
assert_equal(res0, res2)
# Fetch old stats by hash
res3 = index_node.gettxoutsetinfo(hash_option, res0['bestblock'])
del res3['block_info'], res3['total_unspendable_amount']
res3.pop('muhash', None)
assert_equal(res0, res3)
# It does not work without coinstatsindex
assert_raises_rpc_error(-8, "Querying specific block heights requires coinstatsindex", node.gettxoutsetinfo, hash_option, 102)
self.log.info("Test gettxoutsetinfo() with index and verbose flag")
for hash_option in index_hash_options:
# Genesis block is unspendable
res4 = index_node.gettxoutsetinfo(hash_option, 0)
assert_equal(res4['total_unspendable_amount'], 50)
assert_equal(res4['block_info'], {
'unspendable': 50,
'prevout_spent': 0,
'new_outputs_ex_coinbase': 0,
'coinbase': 0,
'unspendables': {
'genesis_block': 50,
'bip30': 0,
'scripts': 0,
'unclaimed_rewards': 0
}
})
self.block_sanity_check(res4['block_info'])
# Test an older block height that included a normal tx
res5 = index_node.gettxoutsetinfo(hash_option, 102)
assert_equal(res5['total_unspendable_amount'], 50)
assert_equal(res5['block_info'], {
'unspendable': 0,
'prevout_spent': 50,
'new_outputs_ex_coinbase': Decimal('49.977800'),
'coinbase': Decimal('50.022200'),
'unspendables': {
'genesis_block': 0,
'bip30': 0,
'scripts': 0,
'unclaimed_rewards': 0
}
})
self.block_sanity_check(res5['block_info'])
# Generate and send a normal tx with two outputs
tx1_inputs = []
tx1_outputs = {self.nodes[0].getnewaddress(): 21, self.nodes[0].getnewaddress(): 42}
raw_tx1 = self.nodes[0].createrawtransaction(tx1_inputs, tx1_outputs)
funded_tx1 = self.nodes[0].fundrawtransaction(raw_tx1)
signed_tx1 = self.nodes[0].signrawtransactionwithwallet(funded_tx1['hex'])
tx1_txid = self.nodes[0].sendrawtransaction(signed_tx1['hex'])
# Find the right position of the 21 BTC output
tx1_final = self.nodes[0].gettransaction(tx1_txid)
for output in tx1_final['details']:
if output['amount'] == Decimal('21.00000000') and output['category'] == 'receive':
n = output['vout']
# Generate and send another tx with an OP_RETURN output (which is unspendable)
tx2 = CTransaction()
tx2.vin.append(CTxIn(COutPoint(int(tx1_txid, 16), n), b''))
tx2.vout.append(CTxOut(int(20.99 * COIN), CScript([OP_RETURN] + [OP_FALSE]*30)))
tx2_hex = self.nodes[0].signrawtransactionwithwallet(tx2.serialize().hex())['hex']
self.nodes[0].sendrawtransaction(tx2_hex)
# Include both txs in a block
self.generate(self.nodes[0], 1)
self.sync_all()
self.wait_until(lambda: not try_rpc(-32603, "Unable to read UTXO set", index_node.gettxoutsetinfo, 'muhash'))
for hash_option in index_hash_options:
# Check all amounts were registered correctly
res6 = index_node.gettxoutsetinfo(hash_option, 108)
assert_equal(res6['total_unspendable_amount'], Decimal('70.98999999'))
assert_equal(res6['block_info'], {
'unspendable': Decimal('20.98999999'),
'prevout_spent': Decimal('111.00000000'),
'new_outputs_ex_coinbase': Decimal('89.96810000'),
'coinbase': Decimal('50.04190001'),
'unspendables': {
'genesis_block': 0,
'bip30': 0,
'scripts': Decimal('20.98999999'),
'unclaimed_rewards': 0
}
})
self.block_sanity_check(res6['block_info'])
# Create a coinbase that does not claim full subsidy and also
# has two outputs
cb = create_coinbase(109, nValue=35)
cb.vout.append(CTxOut(5 * COIN, CScript([OP_FALSE])))
cb.rehash()
# Generate a block that includes previous coinbase
tip = self.nodes[0].getbestblockhash()
block_time = self.nodes[0].getblock(tip)['time'] + 1
block = create_block(int(tip, 16), cb, block_time)
block.solve()
self.nodes[0].submitblock(block.serialize().hex())
self.sync_all()
self.wait_until(lambda: not try_rpc(-32603, "Unable to read UTXO set", index_node.gettxoutsetinfo, 'muhash'))
for hash_option in index_hash_options:
res7 = index_node.gettxoutsetinfo(hash_option, 109)
assert_equal(res7['total_unspendable_amount'], Decimal('80.98999999'))
assert_equal(res7['block_info'], {
'unspendable': 50 - 40,
'prevout_spent': 0,
'new_outputs_ex_coinbase': 0,
'coinbase': 40,
'unspendables': {
'genesis_block': 0,
'bip30': 0,
'scripts': 0,
'unclaimed_rewards': 50 - 40
}
})
self.block_sanity_check(res7['block_info'])
self.log.info("Test that the index is robust across restarts")
res8 = index_node.gettxoutsetinfo('muhash')
self.restart_node(1, extra_args=self.extra_args[1])
res9 = index_node.gettxoutsetinfo('muhash')
assert_equal(res8, res9)
self.generate(index_node, 1, sync_fun=self.no_op)
self.wait_until(lambda: not try_rpc(-32603, "Unable to read UTXO set", index_node.gettxoutsetinfo, 'muhash'))
res10 = index_node.gettxoutsetinfo('muhash')
assert(res8['txouts'] < res10['txouts'])
def _test_use_index_option(self):
self.log.info("Test use_index option for nodes running the index")
self.connect_nodes(0, 1)
self.nodes[0].waitforblockheight(110)
res = self.nodes[0].gettxoutsetinfo('muhash')
option_res = self.nodes[1].gettxoutsetinfo(hash_type='muhash', hash_or_height=None, use_index=False)
del res['disk_size'], option_res['disk_size']
assert_equal(res, option_res)
def _test_reorg_index(self):
self.log.info("Test that index can handle reorgs")
# Generate two block, let the index catch up, then invalidate the blocks
index_node = self.nodes[1]
reorg_blocks = self.generatetoaddress(index_node, 2, index_node.getnewaddress())
reorg_block = reorg_blocks[1]
self.wait_until(lambda: not try_rpc(-32603, "Unable to read UTXO set", index_node.gettxoutsetinfo, 'muhash'))
res_invalid = index_node.gettxoutsetinfo('muhash')
index_node.invalidateblock(reorg_blocks[0])
assert_equal(index_node.gettxoutsetinfo('muhash')['height'], 110)
# Add two new blocks
block = self.generate(index_node, 2, sync_fun=self.no_op)[1]
self.wait_until(lambda: not try_rpc(-32603, "Unable to read UTXO set", index_node.gettxoutsetinfo, 'muhash'))
res = index_node.gettxoutsetinfo(hash_type='muhash', hash_or_height=None, use_index=False)
# Test that the result of the reorged block is not returned for its old block height
res2 = index_node.gettxoutsetinfo(hash_type='muhash', hash_or_height=112)
assert_equal(res["bestblock"], block)
assert_equal(res["muhash"], res2["muhash"])
assert(res["muhash"] != res_invalid["muhash"])
# Test that requesting reorged out block by hash is still returning correct results
res_invalid2 = index_node.gettxoutsetinfo(hash_type='muhash', hash_or_height=reorg_block)
assert_equal(res_invalid2["muhash"], res_invalid["muhash"])
assert(res["muhash"] != res_invalid2["muhash"])
# Add another block, so we don't depend on reconsiderblock remembering which
# blocks were touched by invalidateblock
self.generate(index_node, 1)
self.sync_all()
# Ensure that removing and re-adding blocks yields consistent results
block = index_node.getblockhash(99)
index_node.invalidateblock(block)
self.wait_until(lambda: not try_rpc(-32603, "Unable to read UTXO set", index_node.gettxoutsetinfo, 'muhash'))
index_node.reconsiderblock(block)
self.wait_until(lambda: not try_rpc(-32603, "Unable to read UTXO set", index_node.gettxoutsetinfo, 'muhash'))
res3 = index_node.gettxoutsetinfo(hash_type='muhash', hash_or_height=112)
assert_equal(res2, res3)
self.log.info("Test that a node aware of stale blocks syncs them as well")
node = self.nodes[0]
# Ensure the node is aware of a stale block prior to restart
node.getblock(reorg_block)
self.restart_node(0, ["-coinstatsindex"])
self.wait_until(lambda: not try_rpc(-32603, "Unable to read UTXO set", node.gettxoutsetinfo, 'muhash'))
assert_raises_rpc_error(-32603, "Unable to read UTXO set", node.gettxoutsetinfo, 'muhash', reorg_block)
def _test_index_rejects_hash_serialized(self):
self.log.info("Test that the rpc raises if the legacy hash is passed with the index")
msg = "hash_serialized_2 hash type cannot be queried for a specific block"
assert_raises_rpc_error(-8, msg, self.nodes[1].gettxoutsetinfo, hash_type='hash_serialized_2', hash_or_height=111)
for use_index in {True, False, None}:
assert_raises_rpc_error(-8, msg, self.nodes[1].gettxoutsetinfo, hash_type='hash_serialized_2', hash_or_height=111, use_index=use_index)
if __name__ == '__main__':
CoinStatsIndexTest().main()

View file

@ -0,0 +1,282 @@
#!/usr/bin/env python3
# Copyright (c) 2017-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test various command line arguments and configuration file parameters."""
import os
import time
from test_framework.test_framework import DigiByteTestFramework
from test_framework import util
class ConfArgsTest(DigiByteTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
self.supports_cli = False
self.wallet_names = []
self.disable_autoconnect = False
def test_config_file_parser(self):
self.stop_node(0)
inc_conf_file_path = os.path.join(self.nodes[0].datadir, 'include.conf')
with open(os.path.join(self.nodes[0].datadir, 'digibyte.conf'), 'a', encoding='utf-8') as conf:
conf.write(f'includeconf={inc_conf_file_path}\n')
self.nodes[0].assert_start_raises_init_error(
expected_msg='Error: Error parsing command line arguments: Invalid parameter -dash_cli=1',
extra_args=['-dash_cli=1'],
)
with open(inc_conf_file_path, 'w', encoding='utf-8') as conf:
conf.write('vexta_unknown_conf=1\n')
self.start_node(0)
self.stop_node(0)
with open(inc_conf_file_path, 'w', encoding='utf-8') as conf:
conf.write('-dash=1\n')
self.start_node(0)
self.stop_node(0)
if self.is_wallet_compiled():
with open(inc_conf_file_path, 'w', encoding='utf8') as conf:
conf.write("wallet=foo\n")
self.start_node(0)
self.stop_node(0)
main_conf_file_path = os.path.join(self.options.tmpdir, 'node0', 'digibyte_main.conf')
util.write_config(main_conf_file_path, n=0, chain='', extra_config=f'includeconf={inc_conf_file_path}\n')
with open(inc_conf_file_path, 'w', encoding='utf-8') as conf:
conf.write('acceptnonstdtxn=1\n')
self.nodes[0].assert_start_raises_init_error(extra_args=[f"-conf={main_conf_file_path}"], expected_msg='Error: acceptnonstdtxn is not currently supported for main chain')
with open(inc_conf_file_path, 'w', encoding='utf-8') as conf:
conf.write('nono\n')
self.start_node(0)
self.stop_node(0)
with open(inc_conf_file_path, 'w', encoding='utf-8') as conf:
conf.write('server=1\nrpcuser=someuser\nrpcpassword=some#pass')
self.start_node(0)
self.stop_node(0)
with open(inc_conf_file_path, 'w', encoding='utf-8') as conf:
conf.write('server=1\nrpcuser=someuser\nmain.rpcpassword=some#pass')
self.start_node(0)
self.stop_node(0)
with open(inc_conf_file_path, 'w', encoding='utf-8') as conf:
conf.write('server=1\nrpcuser=someuser\n[main]\nrpcpassword=some#pass')
self.start_node(0)
self.stop_node(0)
inc_conf_file2_path = os.path.join(self.nodes[0].datadir, 'include2.conf')
with open(os.path.join(self.nodes[0].datadir, 'digibyte.conf'), 'a', encoding='utf-8') as conf:
conf.write(f'includeconf={inc_conf_file2_path}\n')
with open(inc_conf_file_path, 'w', encoding='utf-8') as conf:
conf.write('testnot.datadir=1\n')
with open(inc_conf_file2_path, 'w', encoding='utf-8') as conf:
conf.write('[testnet]\n')
self.restart_node(0)
self.nodes[0].stop_node()
with open(inc_conf_file_path, 'w', encoding='utf-8') as conf:
conf.write('') # clear
with open(inc_conf_file2_path, 'w', encoding='utf-8') as conf:
conf.write('') # clear
def test_invalid_command_line_options(self):
self.nodes[0].assert_start_raises_init_error(
expected_msg='Error: No proxy server specified. Use -proxy=<ip> or -proxy=<ip:port>.',
extra_args=['-proxy'],
)
def test_log_buffer(self):
self.stop_node(0)
with self.nodes[0].assert_debug_log(expected_msgs=['Warning: parsed potentially confusing double-negative -connect=0\n']):
self.start_node(0, extra_args=['-noconnect=0'])
def test_args_log(self):
self.stop_node(0)
self.log.info('Test config args logging')
with self.nodes[0].assert_debug_log(
expected_msgs=[
'Command-line arg: addnode="some.node"',
'Command-line arg: rpcauth=****',
'Command-line arg: rpcbind=****',
'Command-line arg: rpcpassword=****',
'Command-line arg: rpcuser=****',
'Command-line arg: torpassword=****',
f'Config file arg: {self.chain}="1"',
f'Config file arg: [{self.chain}] server="1"',
],
unexpected_msgs=[
'alice:f7efda5c189b999524f151318c0c86$d5b51b3beffbc0',
'127.1.1.1',
'secret-rpcuser',
'secret-torpassword',
]):
self.start_node(0, extra_args=[
'-addnode=some.node',
'-rpcauth=alice:f7efda5c189b999524f151318c0c86$d5b51b3beffbc0',
'-rpcbind=127.1.1.1',
'-rpcpassword=',
'-rpcuser=secret-rpcuser',
'-torpassword=secret-torpassword',
])
def test_networkactive(self):
self.log.info('Test -networkactive option')
self.stop_node(0)
with self.nodes[0].assert_debug_log(expected_msgs=['SetNetworkActive: true\n']):
self.start_node(0)
self.stop_node(0)
with self.nodes[0].assert_debug_log(expected_msgs=['SetNetworkActive: true\n']):
self.start_node(0, extra_args=['-networkactive'])
self.stop_node(0)
with self.nodes[0].assert_debug_log(expected_msgs=['SetNetworkActive: true\n']):
self.start_node(0, extra_args=['-networkactive=1'])
self.stop_node(0)
with self.nodes[0].assert_debug_log(expected_msgs=['SetNetworkActive: false\n']):
self.start_node(0, extra_args=['-networkactive=0'])
self.stop_node(0)
with self.nodes[0].assert_debug_log(expected_msgs=['SetNetworkActive: false\n']):
self.start_node(0, extra_args=['-nonetworkactive'])
self.stop_node(0)
with self.nodes[0].assert_debug_log(expected_msgs=['SetNetworkActive: false\n']):
self.start_node(0, extra_args=['-nonetworkactive=1'])
def test_seed_peers(self):
self.log.info('Test seed peers')
default_data_dir = self.nodes[0].datadir
# Only regtest has no fixed seeds. To avoid connections to random
# nodes, regtest is the only network where it is safe to enable
# -fixedseeds in tests
util.assert_equal(self.nodes[0].getblockchaininfo()['chain'],'regtest')
self.stop_node(0)
# No peers.dat exists and -dnsseed=1
# We expect the node will use DNS Seeds, but Regtest mode does not have
# any valid DNS seeds. So after 60 seconds, the node should fallback to
# fixed seeds
assert not os.path.exists(os.path.join(default_data_dir, "peers.dat"))
start = int(time.time())
with self.nodes[0].assert_debug_log(
expected_msgs=[
"Loaded 0 addresses from peers.dat",
"0 addresses found from DNS seeds",
"opencon thread start", # Ensure ThreadOpenConnections::start time is properly set
],
timeout=10,
):
self.start_node(0, extra_args=['-dnsseed=1', '-fixedseeds=1', f'-mocktime={start}'])
with self.nodes[0].assert_debug_log(expected_msgs=[
"Adding fixed seeds as 60 seconds have passed and addrman is empty",
]):
self.nodes[0].setmocktime(start + 65)
self.stop_node(0)
# No peers.dat exists and -dnsseed=0
# We expect the node will fallback immediately to fixed seeds
assert not os.path.exists(os.path.join(default_data_dir, "peers.dat"))
start = time.time()
with self.nodes[0].assert_debug_log(expected_msgs=[
"Loaded 0 addresses from peers.dat",
"DNS seeding disabled",
"Adding fixed seeds as -dnsseed=0, -addnode is not provided and all -seednode(s) attempted\n",
]):
self.start_node(0, extra_args=['-dnsseed=0', '-fixedseeds=1'])
assert time.time() - start < 60
self.stop_node(0)
# No peers.dat exists and dns seeds are disabled.
# We expect the node will not add fixed seeds when explicitly disabled.
assert not os.path.exists(os.path.join(default_data_dir, "peers.dat"))
start = time.time()
with self.nodes[0].assert_debug_log(expected_msgs=[
"Loaded 0 addresses from peers.dat",
"DNS seeding disabled",
"Fixed seeds are disabled",
]):
self.start_node(0, extra_args=['-dnsseed=0', '-fixedseeds=0'])
assert time.time() - start < 60
self.stop_node(0)
# No peers.dat exists and -dnsseed=0, but a -addnode is provided
# We expect the node will allow 60 seconds prior to using fixed seeds
assert not os.path.exists(os.path.join(default_data_dir, "peers.dat"))
start = int(time.time())
with self.nodes[0].assert_debug_log(
expected_msgs=[
"Loaded 0 addresses from peers.dat",
"DNS seeding disabled",
"opencon thread start", # Ensure ThreadOpenConnections::start time is properly set
],
timeout=10,
):
self.start_node(0, extra_args=['-dnsseed=0', '-fixedseeds=1', '-addnode=fakenodeaddr', f'-mocktime={start}'])
with self.nodes[0].assert_debug_log(expected_msgs=[
"Adding fixed seeds as 60 seconds have passed and addrman is empty",
]):
self.nodes[0].setmocktime(start + 65)
def run_test(self):
self.test_log_buffer()
self.test_args_log()
self.test_seed_peers()
self.test_networkactive()
self.test_config_file_parser()
self.test_invalid_command_line_options()
# Remove the -datadir argument so it doesn't override the config file
self.nodes[0].args = [arg for arg in self.nodes[0].args if not arg.startswith("-datadir")]
default_data_dir = self.nodes[0].datadir
new_data_dir = os.path.join(default_data_dir, 'newdatadir')
new_data_dir_2 = os.path.join(default_data_dir, 'newdatadir2')
# Check that using -datadir argument on non-existent directory fails
self.nodes[0].datadir = new_data_dir
self.nodes[0].assert_start_raises_init_error([f'-datadir={new_data_dir}'], f'Error: Specified data directory "{new_data_dir}" does not exist.')
return
# Check that using non-existent datadir in conf file fails
conf_file = os.path.join(default_data_dir, "digibyte.conf")
# datadir needs to be set before [chain] section
conf_file_contents = open(conf_file, encoding='utf8').read()
with open(conf_file, 'w', encoding='utf8') as f:
f.write(f"datadir={new_data_dir}\n")
f.write(conf_file_contents)
self.nodes[0].assert_start_raises_init_error([f'-conf={conf_file}'], f'Error: Error reading configuration file: specified data directory "{new_data_dir}" does not exist.')
# Check that an explicitly specified config file that cannot be opened fails
none_existent_conf_file = os.path.join(default_data_dir, "none_existent_digibyte.conf")
self.nodes[0].assert_start_raises_init_error(['-conf=' + none_existent_conf_file], 'Error: Error reading configuration file: specified config file "' + none_existent_conf_file + '" could not be opened.')
# Create the directory and ensure the config file now works
os.mkdir(new_data_dir)
self.start_node(0, [f'-conf={conf_file}'])
self.stop_node(0)
assert os.path.exists(os.path.join(new_data_dir, self.chain, 'blocks'))
# Ensure command line argument overrides datadir in conf
os.mkdir(new_data_dir_2)
self.nodes[0].datadir = new_data_dir_2
self.start_node(0, [f'-datadir={new_data_dir_2}', f'-conf={conf_file}'])
assert os.path.exists(os.path.join(new_data_dir_2, self.chain, 'blocks'))
if __name__ == '__main__':
ConfArgsTest().main()

View file

@ -0,0 +1,488 @@
#!/usr/bin/env python3
# Copyright (c) 2015-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test CSV soft fork activation.
This soft fork will activate the following BIPS:
BIP 68 - nSequence relative lock times
BIP 112 - CHECKSEQUENCEVERIFY
BIP 113 - MedianTimePast semantics for nLockTime
mine 83 blocks whose coinbases will be used to generate inputs for our tests
mine 344 blocks and seed block chain with the 83 inputs used for our tests at height 427
mine 2 blocks and verify soft fork not yet activated
mine 1 block and test that soft fork is activated (rules enforced for next block)
Test BIP 113 is enforced
Mine 4 blocks so next height is 580 and test BIP 68 is enforced for time and height
Mine 1 block so next height is 581 and test BIP 68 now passes time but not height
Mine 1 block so next height is 582 and test BIP 68 now passes time and height
Test that BIP 112 is enforced
Various transactions will be used to test that the BIPs rules are not enforced before the soft fork activates
And that after the soft fork activates transactions pass and fail as they should according to the rules.
For each BIP, transactions of versions 1 and 2 will be tested.
----------------
BIP 113:
bip113tx - modify the nLocktime variable
BIP 68:
bip68txs - 16 txs with nSequence relative locktime of 10 with various bits set as per the relative_locktimes below
BIP 112:
bip112txs_vary_nSequence - 16 txs with nSequence relative_locktimes of 10 evaluated against 10 OP_CSV OP_DROP
bip112txs_vary_nSequence_9 - 16 txs with nSequence relative_locktimes of 9 evaluated against 10 OP_CSV OP_DROP
bip112txs_vary_OP_CSV - 16 txs with nSequence = 10 evaluated against varying {relative_locktimes of 10} OP_CSV OP_DROP
bip112txs_vary_OP_CSV_9 - 16 txs with nSequence = 9 evaluated against varying {relative_locktimes of 10} OP_CSV OP_DROP
bip112tx_special - test negative argument to OP_CSV
bip112tx_emptystack - test empty stack (= no argument) OP_CSV
"""
from itertools import product
import time
from test_framework.blocktools import (
create_block,
create_coinbase,
)
from test_framework.p2p import P2PDataStore
from test_framework.script import (
CScript,
OP_CHECKSEQUENCEVERIFY,
OP_DROP,
)
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import (
assert_equal,
softfork_active,
)
from test_framework.wallet import (
MiniWallet,
MiniWalletMode,
)
TESTING_TX_COUNT = 83 # Number of testing transactions: 1 BIP113 tx, 16 BIP68 txs, 66 BIP112 txs (see comments above)
COINBASE_BLOCK_COUNT = TESTING_TX_COUNT # Number of coinbase blocks we need to generate as inputs for our txs
BASE_RELATIVE_LOCKTIME = 10
SEQ_DISABLE_FLAG = 1 << 31
SEQ_RANDOM_HIGH_BIT = 1 << 25
SEQ_TYPE_FLAG = 1 << 22
SEQ_RANDOM_LOW_BIT = 1 << 18
def relative_locktime(sdf, srhb, stf, srlb):
"""Returns a locktime with certain bits set."""
locktime = BASE_RELATIVE_LOCKTIME
if sdf:
locktime |= SEQ_DISABLE_FLAG
if srhb:
locktime |= SEQ_RANDOM_HIGH_BIT
if stf:
locktime |= SEQ_TYPE_FLAG
if srlb:
locktime |= SEQ_RANDOM_LOW_BIT
return locktime
def all_rlt_txs(txs):
return [tx['tx'] for tx in txs]
CSV_ACTIVATION_HEIGHT = 432
class BIP68_112_113Test(DigiByteTestFramework):
def set_test_params(self):
self.num_nodes = 1
self.setup_clean_chain = True
self.extra_args = [[
'-whitelist=noban@127.0.0.1',
f'-testactivationheight=csv@{CSV_ACTIVATION_HEIGHT}',
'-par=1', # Use only one script thread to get the exact reject reason for testing
]]
self.supports_cli = False
def create_self_transfer_from_utxo(self, input_tx):
utxo = self.miniwallet.get_utxo(txid=input_tx.rehash(), mark_as_spent=False)
tx = self.miniwallet.create_self_transfer(from_node=self.nodes[0], utxo_to_spend=utxo)['tx']
return tx
def create_bip112special(self, input, txversion):
tx = self.create_self_transfer_from_utxo(input)
tx.nVersion = txversion
self.miniwallet.sign_tx(tx)
tx.vin[0].scriptSig = CScript([-1, OP_CHECKSEQUENCEVERIFY, OP_DROP] + list(CScript(tx.vin[0].scriptSig)))
return tx
def create_bip112emptystack(self, input, txversion):
tx = self.create_self_transfer_from_utxo(input)
tx.nVersion = txversion
self.miniwallet.sign_tx(tx)
tx.vin[0].scriptSig = CScript([OP_CHECKSEQUENCEVERIFY] + list(CScript(tx.vin[0].scriptSig)))
return tx
def send_generic_input_tx(self, coinbases):
input_txid = self.nodes[0].getblock(coinbases.pop(), 2)['tx'][0]['txid']
utxo_to_spend = self.miniwallet.get_utxo(txid=input_txid)
return self.miniwallet.send_self_transfer(from_node=self.nodes[0], utxo_to_spend=utxo_to_spend)['tx']
def create_bip68txs(self, bip68inputs, txversion, locktime_delta=0):
"""Returns a list of bip68 transactions with different bits set."""
txs = []
assert len(bip68inputs) >= 16
for i, (sdf, srhb, stf, srlb) in enumerate(product(*[[True, False]] * 4)):
locktime = relative_locktime(sdf, srhb, stf, srlb)
tx = self.create_self_transfer_from_utxo(bip68inputs[i])
tx.nVersion = txversion
tx.vin[0].nSequence = locktime + locktime_delta
self.miniwallet.sign_tx(tx)
tx.rehash()
txs.append({'tx': tx, 'sdf': sdf, 'stf': stf})
return txs
def create_bip112txs(self, bip112inputs, varyOP_CSV, txversion, locktime_delta=0):
"""Returns a list of bip68 transactions with different bits set."""
txs = []
assert len(bip112inputs) >= 16
for i, (sdf, srhb, stf, srlb) in enumerate(product(*[[True, False]] * 4)):
locktime = relative_locktime(sdf, srhb, stf, srlb)
tx = self.create_self_transfer_from_utxo(bip112inputs[i])
if varyOP_CSV: # if varying OP_CSV, nSequence is fixed
tx.vin[0].nSequence = BASE_RELATIVE_LOCKTIME + locktime_delta
else: # vary nSequence instead, OP_CSV is fixed
tx.vin[0].nSequence = locktime + locktime_delta
tx.nVersion = txversion
self.miniwallet.sign_tx(tx)
if varyOP_CSV:
tx.vin[0].scriptSig = CScript([locktime, OP_CHECKSEQUENCEVERIFY, OP_DROP] + list(CScript(tx.vin[0].scriptSig)))
else:
tx.vin[0].scriptSig = CScript([BASE_RELATIVE_LOCKTIME, OP_CHECKSEQUENCEVERIFY, OP_DROP] + list(CScript(tx.vin[0].scriptSig)))
tx.rehash()
txs.append({'tx': tx, 'sdf': sdf, 'stf': stf})
return txs
def generate_blocks(self, number):
test_blocks = []
for _ in range(number):
block = self.create_test_block([])
test_blocks.append(block)
self.last_block_time += 600
self.tip = block.sha256
self.tipheight += 1
return test_blocks
def create_test_block(self, txs):
block = create_block(self.tip, create_coinbase(self.tipheight + 1), self.last_block_time + 600, txlist=txs)
block.solve()
return block
def send_blocks(self, blocks, success=True, reject_reason=None):
"""Sends blocks to test node. Syncs and verifies that tip has advanced to most recent block.
Call with success = False if the tip shouldn't advance to the most recent block."""
self.helper_peer.send_blocks_and_test(blocks, self.nodes[0], success=success, reject_reason=reject_reason)
def run_test(self):
self.helper_peer = self.nodes[0].add_p2p_connection(P2PDataStore())
self.miniwallet = MiniWallet(self.nodes[0], mode=MiniWalletMode.RAW_P2PK)
self.log.info("Generate blocks in the past for coinbase outputs.")
long_past_time = int(time.time()) - 600 * 1000 # enough to build up to 1000 blocks 10 minutes apart without worrying about getting into the future
self.nodes[0].setmocktime(long_past_time - 100) # enough so that the generated blocks will still all be before long_past_time
self.coinbase_blocks = self.generate(self.miniwallet, COINBASE_BLOCK_COUNT) # blocks generated for inputs
self.nodes[0].setmocktime(0) # set time back to present so yielded blocks aren't in the future as we advance last_block_time
self.tipheight = COINBASE_BLOCK_COUNT # height of the next block to build
self.last_block_time = long_past_time
self.tip = int(self.nodes[0].getbestblockhash(), 16)
# Activation height is hardcoded
# We advance to block height five below BIP112 activation for the following tests
test_blocks = self.generate_blocks(CSV_ACTIVATION_HEIGHT - 5 - COINBASE_BLOCK_COUNT)
self.send_blocks(test_blocks)
assert not softfork_active(self.nodes[0], 'csv')
# Inputs at height = 431
#
# Put inputs for all tests in the chain at height 431 (tip now = 430) (time increases by 600s per block)
# Note we reuse inputs for v1 and v2 txs so must test these separately
# 16 normal inputs
bip68inputs = []
for _ in range(16):
bip68inputs.append(self.send_generic_input_tx(self.coinbase_blocks))
# 2 sets of 16 inputs with 10 OP_CSV OP_DROP (actually will be prepended to spending scriptSig)
bip112basicinputs = []
for _ in range(2):
inputs = []
for _ in range(16):
inputs.append(self.send_generic_input_tx(self.coinbase_blocks))
bip112basicinputs.append(inputs)
# 2 sets of 16 varied inputs with (relative_lock_time) OP_CSV OP_DROP (actually will be prepended to spending scriptSig)
bip112diverseinputs = []
for _ in range(2):
inputs = []
for _ in range(16):
inputs.append(self.send_generic_input_tx(self.coinbase_blocks))
bip112diverseinputs.append(inputs)
# 1 special input with -1 OP_CSV OP_DROP (actually will be prepended to spending scriptSig)
bip112specialinput = self.send_generic_input_tx(self.coinbase_blocks)
# 1 special input with (empty stack) OP_CSV (actually will be prepended to spending scriptSig)
bip112emptystackinput = self.send_generic_input_tx(self.coinbase_blocks)
# 1 normal input
bip113input = self.send_generic_input_tx(self.coinbase_blocks)
self.nodes[0].setmocktime(self.last_block_time + 600)
inputblockhash = self.generate(self.nodes[0], 1)[0] # 1 block generated for inputs to be in chain at height 431
self.nodes[0].setmocktime(0)
self.tip = int(inputblockhash, 16)
self.tipheight += 1
self.last_block_time += 600
assert_equal(len(self.nodes[0].getblock(inputblockhash, True)["tx"]), TESTING_TX_COUNT + 1)
# 2 more version 4 blocks
test_blocks = self.generate_blocks(2)
self.send_blocks(test_blocks)
assert_equal(self.tipheight, CSV_ACTIVATION_HEIGHT - 2)
self.log.info(f"Height = {self.tipheight}, CSV not yet active (will activate for block {CSV_ACTIVATION_HEIGHT}, not {CSV_ACTIVATION_HEIGHT - 1})")
assert not softfork_active(self.nodes[0], 'csv')
# Test both version 1 and version 2 transactions for all tests
# BIP113 test transaction will be modified before each use to put in appropriate block time
bip113tx_v1 = self.create_self_transfer_from_utxo(bip113input)
bip113tx_v1.vin[0].nSequence = 0xFFFFFFFE
bip113tx_v1.nVersion = 1
bip113tx_v2 = self.create_self_transfer_from_utxo(bip113input)
bip113tx_v2.vin[0].nSequence = 0xFFFFFFFE
bip113tx_v2.nVersion = 2
# For BIP68 test all 16 relative sequence locktimes
bip68txs_v1 = self.create_bip68txs(bip68inputs, 1)
bip68txs_v2 = self.create_bip68txs(bip68inputs, 2)
# For BIP112 test:
# 16 relative sequence locktimes of 10 against 10 OP_CSV OP_DROP inputs
bip112txs_vary_nSequence_v1 = self.create_bip112txs(bip112basicinputs[0], False, 1)
bip112txs_vary_nSequence_v2 = self.create_bip112txs(bip112basicinputs[0], False, 2)
# 16 relative sequence locktimes of 9 against 10 OP_CSV OP_DROP inputs
bip112txs_vary_nSequence_9_v1 = self.create_bip112txs(bip112basicinputs[1], False, 1, -1)
bip112txs_vary_nSequence_9_v2 = self.create_bip112txs(bip112basicinputs[1], False, 2, -1)
# sequence lock time of 10 against 16 (relative_lock_time) OP_CSV OP_DROP inputs
bip112txs_vary_OP_CSV_v1 = self.create_bip112txs(bip112diverseinputs[0], True, 1)
bip112txs_vary_OP_CSV_v2 = self.create_bip112txs(bip112diverseinputs[0], True, 2)
# sequence lock time of 9 against 16 (relative_lock_time) OP_CSV OP_DROP inputs
bip112txs_vary_OP_CSV_9_v1 = self.create_bip112txs(bip112diverseinputs[1], True, 1, -1)
bip112txs_vary_OP_CSV_9_v2 = self.create_bip112txs(bip112diverseinputs[1], True, 2, -1)
# -1 OP_CSV OP_DROP input
bip112tx_special_v1 = self.create_bip112special(bip112specialinput, 1)
bip112tx_special_v2 = self.create_bip112special(bip112specialinput, 2)
# (empty stack) OP_CSV input
bip112tx_emptystack_v1 = self.create_bip112emptystack(bip112emptystackinput, 1)
bip112tx_emptystack_v2 = self.create_bip112emptystack(bip112emptystackinput, 2)
self.log.info("TESTING")
self.log.info("Pre-Soft Fork Tests. All txs should pass.")
self.log.info("Test version 1 txs")
success_txs = []
# BIP113 tx, -1 CSV tx and empty stack CSV tx should succeed
bip113tx_v1.nLockTime = self.last_block_time - 600 * 5 # = MTP of prior block (not <) but < time put on current block
self.miniwallet.sign_tx(bip113tx_v1)
success_txs.append(bip113tx_v1)
success_txs.append(bip112tx_special_v1)
success_txs.append(bip112tx_emptystack_v1)
# add BIP 68 txs
success_txs.extend(all_rlt_txs(bip68txs_v1))
# add BIP 112 with seq=10 txs
success_txs.extend(all_rlt_txs(bip112txs_vary_nSequence_v1))
success_txs.extend(all_rlt_txs(bip112txs_vary_OP_CSV_v1))
# try BIP 112 with seq=9 txs
success_txs.extend(all_rlt_txs(bip112txs_vary_nSequence_9_v1))
success_txs.extend(all_rlt_txs(bip112txs_vary_OP_CSV_9_v1))
self.send_blocks([self.create_test_block(success_txs)])
self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
self.log.info("Test version 2 txs")
success_txs = []
# BIP113 tx, -1 CSV tx and empty stack CSV tx should succeed
bip113tx_v2.nLockTime = self.last_block_time - 600 * 5 # = MTP of prior block (not <) but < time put on current block
self.miniwallet.sign_tx(bip113tx_v2)
success_txs.append(bip113tx_v2)
success_txs.append(bip112tx_special_v2)
success_txs.append(bip112tx_emptystack_v2)
# add BIP 68 txs
success_txs.extend(all_rlt_txs(bip68txs_v2))
# add BIP 112 with seq=10 txs
success_txs.extend(all_rlt_txs(bip112txs_vary_nSequence_v2))
success_txs.extend(all_rlt_txs(bip112txs_vary_OP_CSV_v2))
# try BIP 112 with seq=9 txs
success_txs.extend(all_rlt_txs(bip112txs_vary_nSequence_9_v2))
success_txs.extend(all_rlt_txs(bip112txs_vary_OP_CSV_9_v2))
self.send_blocks([self.create_test_block(success_txs)])
self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
# 1 more version 4 block to get us to height 432 so the fork should now be active for the next block
assert not softfork_active(self.nodes[0], 'csv')
test_blocks = self.generate_blocks(1)
self.send_blocks(test_blocks)
assert softfork_active(self.nodes[0], 'csv')
self.log.info("Post-Soft Fork Tests.")
self.log.info("BIP 113 tests")
# BIP 113 tests should now fail regardless of version number if nLockTime isn't satisfied by new rules
bip113tx_v1.nLockTime = self.last_block_time - 600 * 5 # = MTP of prior block (not <) but < time put on current block
self.miniwallet.sign_tx(bip113tx_v1)
bip113tx_v1.rehash()
bip113tx_v2.nLockTime = self.last_block_time - 600 * 5 # = MTP of prior block (not <) but < time put on current block
self.miniwallet.sign_tx(bip113tx_v2)
bip113tx_v2.rehash()
for bip113tx in [bip113tx_v1, bip113tx_v2]:
self.send_blocks([self.create_test_block([bip113tx])], success=False, reject_reason='bad-txns-nonfinal')
# BIP 113 tests should now pass if the locktime is < MTP
bip113tx_v1.nLockTime = self.last_block_time - 600 * 5 - 1 # < MTP of prior block
self.miniwallet.sign_tx(bip113tx_v1)
bip113tx_v1.rehash()
bip113tx_v2.nLockTime = self.last_block_time - 600 * 5 - 1 # < MTP of prior block
self.miniwallet.sign_tx(bip113tx_v2)
bip113tx_v2.rehash()
for bip113tx in [bip113tx_v1, bip113tx_v2]:
self.send_blocks([self.create_test_block([bip113tx])])
self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
# Next block height = 437 after 4 blocks of random version
test_blocks = self.generate_blocks(4)
self.send_blocks(test_blocks)
self.log.info("BIP 68 tests")
self.log.info("Test version 1 txs - all should still pass")
success_txs = []
success_txs.extend(all_rlt_txs(bip68txs_v1))
self.send_blocks([self.create_test_block(success_txs)])
self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
self.log.info("Test version 2 txs")
# All txs with SEQUENCE_LOCKTIME_DISABLE_FLAG set pass
bip68success_txs = [tx['tx'] for tx in bip68txs_v2 if tx['sdf']]
self.send_blocks([self.create_test_block(bip68success_txs)])
self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
# All txs without flag fail as we are at delta height = 8 < 10 and delta time = 8 * 600 < 10 * 512
bip68timetxs = [tx['tx'] for tx in bip68txs_v2 if not tx['sdf'] and tx['stf']]
for tx in bip68timetxs:
self.send_blocks([self.create_test_block([tx])], success=False, reject_reason='bad-txns-nonfinal')
bip68heighttxs = [tx['tx'] for tx in bip68txs_v2 if not tx['sdf'] and not tx['stf']]
for tx in bip68heighttxs:
self.send_blocks([self.create_test_block([tx])], success=False, reject_reason='bad-txns-nonfinal')
# Advance one block to 438
test_blocks = self.generate_blocks(1)
self.send_blocks(test_blocks)
# Height txs should fail and time txs should now pass 9 * 600 > 10 * 512
bip68success_txs.extend(bip68timetxs)
self.send_blocks([self.create_test_block(bip68success_txs)])
self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
for tx in bip68heighttxs:
self.send_blocks([self.create_test_block([tx])], success=False, reject_reason='bad-txns-nonfinal')
# Advance one block to 439
test_blocks = self.generate_blocks(1)
self.send_blocks(test_blocks)
# All BIP 68 txs should pass
bip68success_txs.extend(bip68heighttxs)
self.send_blocks([self.create_test_block(bip68success_txs)])
self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
self.log.info("BIP 112 tests")
self.log.info("Test version 1 txs")
# -1 OP_CSV tx and (empty stack) OP_CSV tx should fail
self.send_blocks([self.create_test_block([bip112tx_special_v1])], success=False,
reject_reason='non-mandatory-script-verify-flag (Negative locktime)')
self.send_blocks([self.create_test_block([bip112tx_emptystack_v1])], success=False,
reject_reason='non-mandatory-script-verify-flag (Operation not valid with the current stack size)')
# If SEQUENCE_LOCKTIME_DISABLE_FLAG is set in argument to OP_CSV, version 1 txs should still pass
success_txs = [tx['tx'] for tx in bip112txs_vary_OP_CSV_v1 if tx['sdf']]
success_txs += [tx['tx'] for tx in bip112txs_vary_OP_CSV_9_v1 if tx['sdf']]
self.send_blocks([self.create_test_block(success_txs)])
self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
# If SEQUENCE_LOCKTIME_DISABLE_FLAG is unset in argument to OP_CSV, version 1 txs should now fail
fail_txs = all_rlt_txs(bip112txs_vary_nSequence_v1)
fail_txs += all_rlt_txs(bip112txs_vary_nSequence_9_v1)
fail_txs += [tx['tx'] for tx in bip112txs_vary_OP_CSV_v1 if not tx['sdf']]
fail_txs += [tx['tx'] for tx in bip112txs_vary_OP_CSV_9_v1 if not tx['sdf']]
for tx in fail_txs:
self.send_blocks([self.create_test_block([tx])], success=False,
reject_reason='non-mandatory-script-verify-flag (Locktime requirement not satisfied)')
self.log.info("Test version 2 txs")
# -1 OP_CSV tx and (empty stack) OP_CSV tx should fail
self.send_blocks([self.create_test_block([bip112tx_special_v2])], success=False,
reject_reason='non-mandatory-script-verify-flag (Negative locktime)')
self.send_blocks([self.create_test_block([bip112tx_emptystack_v2])], success=False,
reject_reason='non-mandatory-script-verify-flag (Operation not valid with the current stack size)')
# If SEQUENCE_LOCKTIME_DISABLE_FLAG is set in argument to OP_CSV, version 2 txs should pass (all sequence locks are met)
success_txs = [tx['tx'] for tx in bip112txs_vary_OP_CSV_v2 if tx['sdf']]
success_txs += [tx['tx'] for tx in bip112txs_vary_OP_CSV_9_v2 if tx['sdf']]
self.send_blocks([self.create_test_block(success_txs)])
self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
# SEQUENCE_LOCKTIME_DISABLE_FLAG is unset in argument to OP_CSV for all remaining txs ##
# All txs with nSequence 9 should fail either due to earlier mismatch or failing the CSV check
fail_txs = all_rlt_txs(bip112txs_vary_nSequence_9_v2)
fail_txs += [tx['tx'] for tx in bip112txs_vary_OP_CSV_9_v2 if not tx['sdf']]
for tx in fail_txs:
self.send_blocks([self.create_test_block([tx])], success=False,
reject_reason='non-mandatory-script-verify-flag (Locktime requirement not satisfied)')
# If SEQUENCE_LOCKTIME_DISABLE_FLAG is set in nSequence, tx should fail
fail_txs = [tx['tx'] for tx in bip112txs_vary_nSequence_v2 if tx['sdf']]
for tx in fail_txs:
self.send_blocks([self.create_test_block([tx])], success=False,
reject_reason='non-mandatory-script-verify-flag (Locktime requirement not satisfied)')
# If sequencelock types mismatch, tx should fail
fail_txs = [tx['tx'] for tx in bip112txs_vary_nSequence_v2 if not tx['sdf'] and tx['stf']]
fail_txs += [tx['tx'] for tx in bip112txs_vary_OP_CSV_v2 if not tx['sdf'] and tx['stf']]
for tx in fail_txs:
self.send_blocks([self.create_test_block([tx])], success=False,
reject_reason='non-mandatory-script-verify-flag (Locktime requirement not satisfied)')
# Remaining txs should pass, just test masking works properly
success_txs = [tx['tx'] for tx in bip112txs_vary_nSequence_v2 if not tx['sdf'] and not tx['stf']]
success_txs += [tx['tx'] for tx in bip112txs_vary_OP_CSV_v2 if not tx['sdf'] and not tx['stf']]
self.send_blocks([self.create_test_block(success_txs)])
self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
# Additional test, of checking that comparison of two time types works properly
time_txs = []
for tx in [tx['tx'] for tx in bip112txs_vary_OP_CSV_v2 if not tx['sdf'] and tx['stf']]:
tx.vin[0].nSequence = BASE_RELATIVE_LOCKTIME | SEQ_TYPE_FLAG
self.miniwallet.sign_tx(tx)
tx.rehash()
time_txs.append(tx)
self.send_blocks([self.create_test_block(time_txs)])
self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
if __name__ == '__main__':
BIP68_112_113Test().main()

View file

@ -0,0 +1,289 @@
#!/usr/bin/env python3
# Copyright (c) 2017-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test recovery from a crash during chainstate writing.
- 4 nodes
* node0, node1, and node2 will have different dbcrash ratios, and different
dbcache sizes
* node3 will be a regular node, with no crashing.
* The nodes will not connect to each other.
- use default test framework starting chain. initialize starting_tip_height to
tip height.
- Main loop:
* generate lots of transactions on node3, enough to fill up a block.
* uniformly randomly pick a tip height from starting_tip_height to
tip_height; with probability 1/(height_difference+4), invalidate this block.
* mine enough blocks to overtake tip_height at start of loop.
* for each node in [node0,node1,node2]:
- for each mined block:
* submit block to node
* if node crashed on/after submitting:
- restart until recovery succeeds
- check that utxo matches node3 using gettxoutsetinfo"""
import errno
import http.client
import random
import time
from test_framework.messages import (
COIN,
COutPoint,
CTransaction,
CTxIn,
CTxOut,
)
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import (
assert_equal,
create_confirmed_utxos,
)
class ChainstateWriteCrashTest(DigiByteTestFramework):
def set_test_params(self):
self.num_nodes = 4
self.rpc_timeout = 480
self.supports_cli = False
# Set -maxmempool=0 to turn off mempool memory sharing with dbcache
# Set -rpcservertimeout=900 to reduce socket disconnects in this
# long-running test
self.base_args = ["-limitdescendantsize=0", "-maxmempool=0", "-rpcservertimeout=900", "-dbbatchsize=200000"]
# Set different crash ratios and cache sizes. Note that not all of
# -dbcache goes to the in-memory coins cache.
self.node0_args = ["-dbcrashratio=8", "-dbcache=4"] + self.base_args
self.node1_args = ["-dbcrashratio=16", "-dbcache=8"] + self.base_args
self.node2_args = ["-dbcrashratio=24", "-dbcache=16"] + self.base_args
# Node3 is a normal node with default args, except will mine full blocks
# and non-standard txs (e.g. txs with "dust" outputs)
self.node3_args = ["-blockmaxweight=4000000", "-acceptnonstdtxn"]
self.extra_args = [self.node0_args, self.node1_args, self.node2_args, self.node3_args]
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
def setup_network(self):
self.add_nodes(self.num_nodes, extra_args=self.extra_args)
self.start_nodes()
self.import_deterministic_coinbase_privkeys()
# Leave them unconnected, we'll use submitblock directly in this test
def restart_node(self, node_index, expected_tip):
"""Start up a given node id, wait for the tip to reach the given block hash, and calculate the utxo hash.
Exceptions on startup should indicate node crash (due to -dbcrashratio), in which case we try again. Give up
after 60 seconds. Returns the utxo hash of the given node."""
time_start = time.time()
while time.time() - time_start < 120:
try:
# Any of these RPC calls could throw due to node crash
self.start_node(node_index)
self.nodes[node_index].waitforblock(expected_tip)
utxo_hash = self.nodes[node_index].gettxoutsetinfo()['hash_serialized_2']
return utxo_hash
except:
# An exception here should mean the node is about to crash.
# If digibyted exits, then try again. wait_for_node_exit()
# should raise an exception if digibyted doesn't exit.
self.wait_for_node_exit(node_index, timeout=10)
self.crashed_on_restart += 1
time.sleep(1)
# If we got here, digibyted isn't coming back up on restart. Could be a
# bug in digibyted, or we've gotten unlucky with our dbcrash ratio --
# perhaps we generated a test case that blew up our cache?
# TODO: If this happens a lot, we should try to restart without -dbcrashratio
# and make sure that recovery happens.
raise AssertionError(f"Unable to successfully restart node {node_index} in allotted time")
def submit_block_catch_error(self, node_index, block):
"""Try submitting a block to the given node.
Catch any exceptions that indicate the node has crashed.
Returns true if the block was submitted successfully; false otherwise."""
try:
self.nodes[node_index].submitblock(block)
return True
except (http.client.CannotSendRequest, http.client.RemoteDisconnected) as e:
self.log.debug(f"node {node_index} submitblock raised exception: {e}")
return False
except OSError as e:
self.log.debug(f"node {node_index} submitblock raised OSError exception: errno={e.errno}")
if e.errno in [errno.EPIPE, errno.ECONNREFUSED, errno.ECONNRESET]:
# The node has likely crashed
return False
else:
# Unexpected exception, raise
raise
def sync_node3blocks(self, block_hashes):
"""Use submitblock to sync node3's chain with the other nodes
If submitblock fails, restart the node and get the new utxo hash.
If any nodes crash while updating, we'll compare utxo hashes to
ensure recovery was successful."""
node3_utxo_hash = self.nodes[3].gettxoutsetinfo()['hash_serialized_2']
# Retrieve all the blocks from node3
blocks = []
for block_hash in block_hashes:
blocks.append([block_hash, self.nodes[3].getblock(block_hash, 0)])
# Deliver each block to each other node
for i in range(3):
nodei_utxo_hash = None
self.log.debug(f"Syncing blocks to node {i}")
for (block_hash, block) in blocks:
# Get the block from node3, and submit to node_i
self.log.debug(f"submitting block {block_hash}")
if not self.submit_block_catch_error(i, block):
# TODO: more carefully check that the crash is due to -dbcrashratio
# (change the exit code perhaps, and check that here?)
self.wait_for_node_exit(i, timeout=30)
self.log.debug(f"Restarting node {i} after block hash {block_hash}")
nodei_utxo_hash = self.restart_node(i, block_hash)
assert nodei_utxo_hash is not None
self.restart_counts[i] += 1
else:
# Clear it out after successful submitblock calls -- the cached
# utxo hash will no longer be correct
nodei_utxo_hash = None
# Check that the utxo hash matches node3's utxo set
# NOTE: we only check the utxo set if we had to restart the node
# after the last block submitted:
# - checking the utxo hash causes a cache flush, which we don't
# want to do every time; so
# - we only update the utxo cache after a node restart, since flushing
# the cache is a no-op at that point
if nodei_utxo_hash is not None:
self.log.debug(f"Checking txoutsetinfo matches for node {i}")
assert_equal(nodei_utxo_hash, node3_utxo_hash)
def verify_utxo_hash(self):
"""Verify that the utxo hash of each node matches node3.
Restart any nodes that crash while querying."""
node3_utxo_hash = self.nodes[3].gettxoutsetinfo()['hash_serialized_2']
self.log.info("Verifying utxo hash matches for all nodes")
for i in range(3):
try:
nodei_utxo_hash = self.nodes[i].gettxoutsetinfo()['hash_serialized_2']
except OSError:
# probably a crash on db flushing
nodei_utxo_hash = self.restart_node(i, self.nodes[3].getbestblockhash())
assert_equal(nodei_utxo_hash, node3_utxo_hash)
def generate_small_transactions(self, node, count, utxo_list):
FEE = 100000 # TODO: replace this with node relay fee based calculation
num_transactions = 0
random.shuffle(utxo_list)
while len(utxo_list) >= 2 and num_transactions < count:
tx = CTransaction()
input_amount = 0
for _ in range(2):
utxo = utxo_list.pop()
tx.vin.append(CTxIn(COutPoint(int(utxo['txid'], 16), utxo['vout'])))
input_amount += int(utxo['amount'] * COIN)
output_amount = (input_amount - FEE) // 3
if output_amount <= 0:
# Sanity check -- if we chose inputs that are too small, skip
continue
for _ in range(3):
tx.vout.append(CTxOut(output_amount, bytes.fromhex(utxo['scriptPubKey'])))
# Sign and send the transaction to get into the mempool
tx_signed_hex = node.signrawtransactionwithwallet(tx.serialize().hex())['hex']
node.sendrawtransaction(tx_signed_hex)
num_transactions += 1
def run_test(self):
# Track test coverage statistics
self.restart_counts = [0, 0, 0] # Track the restarts for nodes 0-2
self.crashed_on_restart = 0 # Track count of crashes during recovery
# Start by creating a lot of utxos on node3
initial_height = self.nodes[3].getblockcount()
utxo_list = create_confirmed_utxos(self, self.nodes[3].getnetworkinfo()['relayfee'], self.nodes[3], 5000, sync_fun=self.no_op)
self.log.info(f"Prepped {len(utxo_list)} utxo entries")
# Sync these blocks with the other nodes
block_hashes_to_sync = []
for height in range(initial_height + 1, self.nodes[3].getblockcount() + 1):
block_hashes_to_sync.append(self.nodes[3].getblockhash(height))
self.log.debug(f"Syncing {len(block_hashes_to_sync)} blocks with other nodes")
# Syncing the blocks could cause nodes to crash, so the test begins here.
self.sync_node3blocks(block_hashes_to_sync)
starting_tip_height = self.nodes[3].getblockcount()
# Main test loop:
# each time through the loop, generate a bunch of transactions,
# and then either mine a single new block on the tip, or some-sized reorg.
for i in range(40):
self.log.info(f"Iteration {i}, generating 2500 transactions {self.restart_counts}")
# Generate a bunch of small-ish transactions
self.generate_small_transactions(self.nodes[3], 2500, utxo_list)
# Pick a random block between current tip, and starting tip
current_height = self.nodes[3].getblockcount()
random_height = random.randint(starting_tip_height, current_height)
self.log.debug(f"At height {current_height}, considering height {random_height}")
if random_height > starting_tip_height:
# Randomly reorg from this point with some probability (1/4 for
# tip, 1/5 for tip-1, ...)
if random.random() < 1.0 / (current_height + 4 - random_height):
self.log.debug(f"Invalidating block at height {random_height}")
self.nodes[3].invalidateblock(self.nodes[3].getblockhash(random_height))
# Now generate new blocks until we pass the old tip height
self.log.debug("Mining longer tip")
block_hashes = []
while current_height + 1 > self.nodes[3].getblockcount():
block_hashes.extend(self.generatetoaddress(
self.nodes[3],
nblocks=min(10, current_height + 1 - self.nodes[3].getblockcount()),
# new address to avoid mining a block that has just been invalidated
address=self.nodes[3].getnewaddress(),
sync_fun=self.no_op,
))
self.log.debug(f"Syncing {len(block_hashes)} new blocks...")
self.sync_node3blocks(block_hashes)
utxo_list = self.nodes[3].listunspent()
self.log.debug(f"Node3 utxo count: {len(utxo_list)}")
# Check that the utxo hashes agree with node3
# Useful side effect: each utxo cache gets flushed here, so that we
# won't get crashes on shutdown at the end of the test.
self.verify_utxo_hash()
# Check the test coverage
self.log.info(f"Restarted nodes: {self.restart_counts}; crashes on restart: {self.crashed_on_restart}")
# If no nodes were restarted, we didn't test anything.
assert self.restart_counts != [0, 0, 0]
# Make sure we tested the case of crash-during-recovery.
assert self.crashed_on_restart > 0
# Warn if any of the nodes escaped restart.
for i in range(3):
if self.restart_counts[i] == 0:
self.log.warning(f"Node {i} never crashed during utxo flush!")
if __name__ == "__main__":
ChainstateWriteCrashTest().main()

152
test/functional/feature_dersig.py Executable file
View file

@ -0,0 +1,152 @@
#!/usr/bin/env python3
# Copyright (c) 2015-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test BIP66 (DER SIG).
Test the DERSIG soft-fork activation on regtest.
"""
from test_framework.blocktools import (
create_block,
create_coinbase,
VERSIONBITS_TOP_BITS,
)
from test_framework.messages import msg_block
from test_framework.p2p import P2PInterface
from test_framework.script import CScript
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import (
assert_equal,
)
from test_framework.wallet import (
MiniWallet,
MiniWalletMode,
)
# A canonical signature consists of:
# <30> <total len> <02> <len R> <R> <02> <len S> <S> <hashtype>
def unDERify(tx):
"""
Make the signature in vin 0 of a tx non-DER-compliant,
by adding padding after the S-value.
"""
scriptSig = CScript(tx.vin[0].scriptSig)
newscript = []
for i in scriptSig:
if (len(newscript) == 0):
newscript.append(i[0:-1] + b'\0' + i[-1:])
else:
newscript.append(i)
tx.vin[0].scriptSig = CScript(newscript)
DERSIG_HEIGHT = 102
class BIP66Test(DigiByteTestFramework):
def set_test_params(self):
self.num_nodes = 1
self.extra_args = [[
f'-testactivationheight=dersig@{DERSIG_HEIGHT}',
'-whitelist=noban@127.0.0.1',
'-par=1', # Use only one script thread to get the exact log msg for testing
]]
self.setup_clean_chain = True
self.rpc_timeout = 240
def create_tx(self, input_txid):
utxo_to_spend = self.miniwallet.get_utxo(txid=input_txid, mark_as_spent=False)
return self.miniwallet.create_self_transfer(from_node=self.nodes[0], utxo_to_spend=utxo_to_spend)['tx']
def test_dersig_info(self, *, is_active):
assert_equal(self.nodes[0].getblockchaininfo()['softforks']['bip66'],
{
"active": is_active,
"height": DERSIG_HEIGHT,
"type": "buried",
},
)
def run_test(self):
peer = self.nodes[0].add_p2p_connection(P2PInterface())
self.miniwallet = MiniWallet(self.nodes[0], mode=MiniWalletMode.RAW_P2PK)
self.test_dersig_info(is_active=False)
self.log.info("Mining %d blocks", DERSIG_HEIGHT - 2)
self.coinbase_txids = [self.nodes[0].getblock(b)['tx'][0] for b in self.generate(self.miniwallet, DERSIG_HEIGHT - 2)]
self.log.info("Test that a transaction with non-DER signature can still appear in a block")
spendtx = self.create_tx(self.coinbase_txids[0])
unDERify(spendtx)
spendtx.rehash()
tip = self.nodes[0].getbestblockhash()
block_time = self.nodes[0].getblockheader(tip)['mediantime'] + 1
block = create_block(int(tip, 16), create_coinbase(DERSIG_HEIGHT - 1), block_time, txlist=[spendtx])
block.solve()
assert_equal(self.nodes[0].getblockcount(), DERSIG_HEIGHT - 2)
self.test_dersig_info(is_active=False) # Not active as of current tip and next block does not need to obey rules
peer.send_and_ping(msg_block(block))
assert_equal(self.nodes[0].getblockcount(), DERSIG_HEIGHT - 1)
self.test_dersig_info(is_active=True) # Not active as of current tip, but next block must obey rules
assert_equal(self.nodes[0].getbestblockhash(), block.hash)
self.log.info("Test that blocks must now be at least version 3")
tip = block.sha256
block_time += 1
block = create_block(tip, create_coinbase(DERSIG_HEIGHT), block_time, version=0x02)
block.solve()
with self.nodes[0].assert_debug_log(expected_msgs=[f'{block.hash}, bad-version(0x00000002)']):
peer.send_and_ping(msg_block(block))
assert_equal(int(self.nodes[0].getbestblockhash(), 16), tip)
peer.sync_with_ping()
self.log.info("Test that transactions with non-DER signatures cannot appear in a block")
block.nVersion = VERSIONBITS_TOP_BITS | 0x03
spendtx = self.create_tx(self.coinbase_txids[1])
unDERify(spendtx)
spendtx.rehash()
# First we show that this tx is valid except for DERSIG by getting it
# rejected from the mempool for exactly that reason.
assert_equal(
[{
'txid': spendtx.hash,
'wtxid': spendtx.getwtxid(),
'allowed': False,
'reject-reason': 'non-mandatory-script-verify-flag (Non-canonical DER signature)',
}],
self.nodes[0].testmempoolaccept(rawtxs=[spendtx.serialize().hex()], maxfeerate=0),
)
# Now we verify that a block with this transaction is also invalid.
block.vtx.append(spendtx)
block.hashMerkleRoot = block.calc_merkle_root()
block.solve()
with self.nodes[0].assert_debug_log(expected_msgs=[f'CheckInputScripts on {block.vtx[-1].hash} failed with non-mandatory-script-verify-flag (Non-canonical DER signature)']):
peer.send_and_ping(msg_block(block))
assert_equal(int(self.nodes[0].getbestblockhash(), 16), tip)
peer.sync_with_ping()
self.log.info("Test that a block with a DERSIG-compliant transaction is accepted")
block.vtx[1] = self.create_tx(self.coinbase_txids[1])
block.hashMerkleRoot = block.calc_merkle_root()
block.solve()
self.test_dersig_info(is_active=True) # Not active as of current tip, but next block must obey rules
peer.send_and_ping(msg_block(block))
self.test_dersig_info(is_active=True) # Active as of current tip
assert_equal(int(self.nodes[0].getbestblockhash(), 16), block.sha256)
if __name__ == '__main__':
BIP66Test().main()

View file

@ -0,0 +1,290 @@
#!/usr/bin/env python3
# Copyright (c) 2014-2020 The Bitcoin Core developers
# Copyright (c) 2020-2022 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test fee estimation code."""
from decimal import Decimal
import random
from test_framework.messages import (
COIN,
COutPoint,
CTransaction,
CTxIn,
CTxOut,
)
from test_framework.script import (
CScript,
OP_1,
OP_2,
OP_DROP,
OP_TRUE,
)
from test_framework.script_util import (
script_to_p2sh_script,
)
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import (
assert_equal,
assert_greater_than,
assert_greater_than_or_equal,
assert_raises_rpc_error,
satoshi_round,
)
# Construct 2 trivial P2SH's and the ScriptSigs that spend them
# So we can create many transactions without needing to spend
# time signing.
REDEEM_SCRIPT_1 = CScript([OP_1, OP_DROP])
REDEEM_SCRIPT_2 = CScript([OP_2, OP_DROP])
P2SH_1 = script_to_p2sh_script(REDEEM_SCRIPT_1)
P2SH_2 = script_to_p2sh_script(REDEEM_SCRIPT_2)
# Associated ScriptSig's to spend satisfy P2SH_1 and P2SH_2
SCRIPT_SIG = [CScript([OP_TRUE, REDEEM_SCRIPT_1]), CScript([OP_TRUE, REDEEM_SCRIPT_2])]
def small_txpuzzle_randfee(from_node, conflist, unconflist, amount, min_fee, fee_increment):
"""Create and send a transaction with a random fee.
The transaction pays to a trivial P2SH script, and assumes that its inputs
are of the same form.
The function takes a list of confirmed outputs and unconfirmed outputs
and attempts to use the confirmed list first for its inputs.
It adds the newly created outputs to the unconfirmed list.
Returns (raw transaction, fee)."""
# It's best to exponentially distribute our random fees
# because the buckets are exponentially spaced.
# Exponentially distributed from 1-128 * fee_increment
rand_fee = float(fee_increment) * (1.1892 ** random.randint(0, 28))
# Total fee ranges from min_fee to min_fee + 127*fee_increment
fee = min_fee - fee_increment + satoshi_round(rand_fee)
tx = CTransaction()
total_in = Decimal("0.00000000")
while total_in <= (amount + fee) and len(conflist) > 0:
t = conflist.pop(0)
total_in += t["amount"]
tx.vin.append(CTxIn(COutPoint(int(t["txid"], 16), t["vout"]), b""))
if total_in <= amount + fee:
while total_in <= (amount + fee) and len(unconflist) > 0:
t = unconflist.pop(0)
total_in += t["amount"]
tx.vin.append(CTxIn(COutPoint(int(t["txid"], 16), t["vout"]), b""))
if total_in <= amount + fee:
raise RuntimeError("Insufficient funds: need %d, have %d" % (amount + fee, total_in))
tx.vout.append(CTxOut(int((total_in - amount - fee) * COIN), P2SH_1))
tx.vout.append(CTxOut(int(amount * COIN), P2SH_2))
# These transactions don't need to be signed, but we still have to insert
# the ScriptSig that will satisfy the ScriptPubKey.
for inp in tx.vin:
inp.scriptSig = SCRIPT_SIG[inp.prevout.n]
txid = from_node.sendrawtransaction(hexstring=tx.serialize().hex(), maxfeerate=0)
unconflist.append({"txid": txid, "vout": 0, "amount": total_in - amount - fee})
unconflist.append({"txid": txid, "vout": 1, "amount": amount})
return (tx.serialize().hex(), fee)
def split_inputs(from_node, txins, txouts, initial_split=False):
"""Generate a lot of inputs so we can generate a ton of transactions.
This function takes an input from txins, and creates and sends a transaction
which splits the value into 2 outputs which are appended to txouts.
Previously this was designed to be small inputs so they wouldn't have
a high coin age when the notion of priority still existed."""
prevtxout = txins.pop()
tx = CTransaction()
tx.vin.append(CTxIn(COutPoint(int(prevtxout["txid"], 16), prevtxout["vout"]), b""))
half_change = satoshi_round(prevtxout["amount"] / 2)
rem_change = prevtxout["amount"] - half_change - Decimal("0.00100000")
tx.vout.append(CTxOut(int(half_change * COIN), P2SH_1))
tx.vout.append(CTxOut(int(rem_change * COIN), P2SH_2))
# If this is the initial split we actually need to sign the transaction
# Otherwise we just need to insert the proper ScriptSig
if (initial_split):
completetx = from_node.signrawtransactionwithwallet(tx.serialize().hex())["hex"]
else:
tx.vin[0].scriptSig = SCRIPT_SIG[prevtxout["vout"]]
completetx = tx.serialize().hex()
txid = from_node.sendrawtransaction(hexstring=completetx, maxfeerate=0)
txouts.append({"txid": txid, "vout": 0, "amount": half_change})
txouts.append({"txid": txid, "vout": 1, "amount": rem_change})
def check_raw_estimates(node, fees_seen):
"""Call estimaterawfee and verify that the estimates meet certain invariants."""
delta = 1.0e-6 # account for rounding error
for i in range(1, 26):
for _, e in node.estimaterawfee(i).items():
feerate = float(e["feerate"])
assert_greater_than(feerate, 0)
if feerate + delta < min(fees_seen) or feerate - delta > max(fees_seen):
raise AssertionError("Estimated fee (%f) out of range (%f,%f)"
% (feerate, min(fees_seen), max(fees_seen)))
def check_smart_estimates(node, fees_seen):
"""Call estimatesmartfee and verify that the estimates meet certain invariants."""
delta = 1.0e-6 # account for rounding error
last_feerate = float(max(fees_seen))
all_smart_estimates = [node.estimatesmartfee(i) for i in range(1, 26)]
for i, e in enumerate(all_smart_estimates): # estimate is for i+1
feerate = float(e["feerate"])
assert_greater_than(feerate, 0)
if feerate + delta < min(fees_seen) or feerate - delta > max(fees_seen):
raise AssertionError("Estimated fee (%f) out of range (%f,%f)"
% (feerate, min(fees_seen), max(fees_seen)))
if feerate - delta > last_feerate:
raise AssertionError("Estimated fee (%f) larger than last fee (%f) for lower number of confirms"
% (feerate, last_feerate))
last_feerate = feerate
if i == 0:
assert_equal(e["blocks"], 2)
else:
assert_greater_than_or_equal(i + 1, e["blocks"])
def check_estimates(node, fees_seen):
check_raw_estimates(node, fees_seen)
check_smart_estimates(node, fees_seen)
class EstimateFeeTest(DigiByteTestFramework):
def set_test_params(self):
self.num_nodes = 3
# mine non-standard txs (e.g. txs with "dust" outputs)
# Force fSendTrickle to true (via whitelist.noban)
self.extra_args = [
["-acceptnonstdtxn", "-whitelist=noban@127.0.0.1"],
["-acceptnonstdtxn", "-whitelist=noban@127.0.0.1", "-blockmaxweight=68000"],
["-acceptnonstdtxn", "-whitelist=noban@127.0.0.1", "-blockmaxweight=32000"],
]
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
def setup_network(self):
"""
We'll setup the network to have 3 nodes that all mine with different parameters.
But first we need to use one node to create a lot of outputs
which we will use to generate our transactions.
"""
self.add_nodes(3, extra_args=self.extra_args)
# Use node0 to mine blocks for input splitting
# Node1 mines small blocks but that are bigger than the expected transaction rate.
# NOTE: the CreateNewBlock code starts counting block weight at 4,000 weight,
# (68k weight is room enough for 120 or so transactions)
# Node2 is a stingy miner, that
# produces too small blocks (room for only 55 or so transactions)
self.start_nodes()
self.import_deterministic_coinbase_privkeys()
self.stop_nodes()
def transact_and_mine(self, numblocks, mining_node):
min_fee = Decimal("0.0003")
# We will now mine numblocks blocks generating on average 100 transactions between each block
# We shuffle our confirmed txout set before each set of transactions
# small_txpuzzle_randfee will use the transactions that have inputs already in the chain when possible
# resorting to tx's that depend on the mempool when those run out
for _ in range(numblocks):
random.shuffle(self.confutxo)
for _ in range(random.randrange(100 - 50, 100 + 50)):
from_index = random.randint(1, 2)
(txhex, fee) = small_txpuzzle_randfee(self.nodes[from_index], self.confutxo,
self.memutxo, Decimal("0.007"), min_fee, min_fee)
tx_kbytes = (len(txhex) // 2) / 1000.0
self.fees_per_kb.append(float(fee) / tx_kbytes)
self.sync_mempools(wait=.1)
mined = mining_node.getblock(self.generate(mining_node, 1)[0], True)["tx"]
self.sync_blocks(wait=.1)
# update which txouts are confirmed
newmem = []
for utx in self.memutxo:
if utx["txid"] in mined:
self.confutxo.append(utx)
else:
newmem.append(utx)
self.memutxo = newmem
def run_test(self):
self.log.info("This test is time consuming, please be patient")
self.log.info("Splitting inputs so we can generate tx's")
# Start node0
self.start_node(0)
self.txouts = []
self.txouts2 = []
# Split a coinbase into two transaction puzzle outputs
self.generatetoaddress(self.nodes[0], 101, self.nodes[0].getnewaddress(), sync_fun=self.no_op)
split_inputs(self.nodes[0], self.nodes[0].listunspent(0), self.txouts, True)
# Mine
while len(self.nodes[0].getrawmempool()) > 0:
self.generate(self.nodes[0], 1, sync_fun=self.no_op)
# Repeatedly split those 2 outputs, doubling twice for each rep
# Use txouts to monitor the available utxo, since these won't be tracked in wallet
reps = 0
while reps < 5:
# Double txouts to txouts2
while len(self.txouts) > 0:
split_inputs(self.nodes[0], self.txouts, self.txouts2)
while len(self.nodes[0].getrawmempool()) > 0:
self.generate(self.nodes[0], 1, sync_fun=self.no_op)
# Double txouts2 to txouts
while len(self.txouts2) > 0:
split_inputs(self.nodes[0], self.txouts2, self.txouts)
while len(self.nodes[0].getrawmempool()) > 0:
self.generate(self.nodes[0], 1, sync_fun=self.no_op)
reps += 1
self.log.info("Finished splitting")
# Now we can connect the other nodes, didn't want to connect them earlier
# so the estimates would not be affected by the splitting transactions
self.start_node(1)
self.start_node(2)
self.connect_nodes(1, 0)
self.connect_nodes(0, 2)
self.connect_nodes(2, 1)
self.sync_all()
self.fees_per_kb = []
self.memutxo = []
self.confutxo = self.txouts # Start with the set of confirmed txouts after splitting
self.log.info("Will output estimates for 1/2/3/6/15/25 blocks")
for _ in range(2):
self.log.info("Creating transactions and mining them with a block size that can't keep up")
# Create transactions and mine 10 small blocks with node 2, but create txs faster than we can mine
self.transact_and_mine(10, self.nodes[2])
check_estimates(self.nodes[1], self.fees_per_kb)
self.log.info("Creating transactions and mining them at a block size that is just big enough")
# Generate transactions while mining 10 more blocks, this time with node1
# which mines blocks with capacity just above the rate that transactions are being created
self.transact_and_mine(10, self.nodes[1])
check_estimates(self.nodes[1], self.fees_per_kb)
# Finish by mining a normal-sized block:
while len(self.nodes[1].getrawmempool()) > 0:
self.generate(self.nodes[1], 1)
self.sync_blocks(self.nodes[0:3], wait=.1)
self.log.info("Final estimates after emptying mempools")
check_estimates(self.nodes[1], self.fees_per_kb)
self.log.info("Testing that fee estimation is disabled in blocksonly.")
self.restart_node(0, ["-blocksonly"])
assert_raises_rpc_error(-32603, "Fee estimation disabled",
self.nodes[0].estimatesmartfee, 2)
if __name__ == '__main__':
EstimateFeeTest().main()

View file

@ -0,0 +1,49 @@
#!/usr/bin/env python3
# Copyright (c) 2018-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Check that it's not possible to start a second digibyted instance using the same datadir or wallet."""
import os
import random
import string
from test_framework.test_framework import DigiByteTestFramework
from test_framework.test_node import ErrorMatch
class FilelockTest(DigiByteTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 2
def setup_network(self):
self.add_nodes(self.num_nodes, extra_args=None)
self.nodes[0].start()
self.nodes[0].wait_for_rpc_connection()
def run_test(self):
datadir = os.path.join(self.nodes[0].datadir, self.chain)
self.log.info(f"Using datadir {datadir}")
self.log.info("Check that we can't start a second digibyted instance using the same datadir")
expected_msg = f"Error: Cannot obtain a lock on data directory {datadir}. {self.config['environment']['PACKAGE_NAME']} is probably already running."
self.nodes[1].assert_start_raises_init_error(extra_args=[f'-datadir={self.nodes[0].datadir}', '-noserver'], expected_msg=expected_msg)
if self.is_wallet_compiled():
def check_wallet_filelock(descriptors):
wallet_name = ''.join([random.choice(string.ascii_lowercase) for _ in range(6)])
self.nodes[0].createwallet(wallet_name=wallet_name, descriptors=descriptors)
wallet_dir = os.path.join(datadir, 'wallets')
self.log.info("Check that we can't start a second digibyted instance using the same wallet")
if descriptors:
expected_msg = f"Error: SQLiteDatabase: Unable to obtain an exclusive lock on the database, is it being used by another instance of {self.config['environment']['PACKAGE_NAME']}?"
else:
expected_msg = "Error: Error initializing wallet database environment"
self.nodes[1].assert_start_raises_init_error(extra_args=[f'-walletdir={wallet_dir}', f'-wallet={wallet_name}', '-noserver'], expected_msg=expected_msg, match=ErrorMatch.PARTIAL_REGEX)
if self.is_bdb_compiled():
check_wallet_filelock(False)
if self.is_sqlite_compiled():
check_wallet_filelock(True)
if __name__ == '__main__':
FilelockTest().main()

62
test/functional/feature_help.py Executable file
View file

@ -0,0 +1,62 @@
#!/usr/bin/env python3
# Copyright (c) 2018-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Verify that starting digibyte with -h works as expected."""
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import assert_equal
class HelpTest(DigiByteTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
def setup_network(self):
self.add_nodes(self.num_nodes)
# Don't start the node
def get_node_output(self, *, ret_code_expected):
ret_code = self.nodes[0].process.wait(timeout=60)
assert_equal(ret_code, ret_code_expected)
self.nodes[0].stdout.seek(0)
self.nodes[0].stderr.seek(0)
out = self.nodes[0].stdout.read()
err = self.nodes[0].stderr.read()
self.nodes[0].stdout.close()
self.nodes[0].stderr.close()
# Clean up TestNode state
self.nodes[0].running = False
self.nodes[0].process = None
self.nodes[0].rpc_connected = False
self.nodes[0].rpc = None
return out, err
def run_test(self):
self.log.info("Start digibyte with -h for help text")
self.nodes[0].start(extra_args=['-h'])
# Node should exit immediately and output help to stdout.
output, _ = self.get_node_output(ret_code_expected=0)
assert b'Options' in output
self.log.info(f"Help text received: {output[0:60]} (...)")
self.log.info("Start digibyte with -version for version information")
self.nodes[0].start(extra_args=['-version'])
# Node should exit immediately and output version to stdout.
output, _ = self.get_node_output(ret_code_expected=0)
assert b'version' in output
self.log.info(f"Version text received: {output[0:60]} (...)")
# Test that arguments not in the help results in an error
self.log.info("Start digibyted with -fakearg to make sure it does not start")
self.nodes[0].start(extra_args=['-fakearg'])
# Node should exit immediately and output an error to stderr
_, output = self.get_node_output(ret_code_expected=1)
assert b'Error parsing command line arguments' in output
self.log.info(f"Error message received: {output[0:60]} (...)")
if __name__ == '__main__':
HelpTest().main()

View file

@ -0,0 +1,90 @@
#!/usr/bin/env python3
# Copyright (c) 2018-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Tests the includeconf argument
Verify that:
1. adding includeconf to the configuration file causes the includeconf
file to be loaded in the correct order.
2. includeconf cannot be used as a command line argument.
3. includeconf cannot be used recursively (ie includeconf can only
be used from the base config file).
4. multiple includeconf arguments can be specified in the main config
file.
"""
import os
from test_framework.test_framework import DigiByteTestFramework
class IncludeConfTest(DigiByteTestFramework):
def set_test_params(self):
self.num_nodes = 1
def setup_chain(self):
super().setup_chain()
# Create additional config files
# - tmpdir/node0/relative.conf
with open(os.path.join(self.options.tmpdir, "node0", "relative.conf"), "w", encoding="utf8") as f:
f.write("uacomment=relative\n")
# - tmpdir/node0/relative2.conf
with open(os.path.join(self.options.tmpdir, "node0", "relative2.conf"), "w", encoding="utf8") as f:
f.write("uacomment=relative2\n")
with open(os.path.join(self.options.tmpdir, "node0", "digibyte.conf"), "a", encoding='utf8') as f:
f.write("uacomment=main\nincludeconf=relative.conf\n")
def run_test(self):
self.log.info("-includeconf works from config file. subversion should end with 'main; relative)/'")
subversion = self.nodes[0].getnetworkinfo()["subversion"]
self.log.info(f"subversion={subversion}")
assert subversion.startswith("/Vexta:")
self.log.info("-includeconf cannot be used as command-line arg")
self.stop_node(0)
self.nodes[0].assert_start_raises_init_error(
extra_args=['-noincludeconf=0'],
expected_msg='Error: Error parsing command line arguments: -includeconf cannot be used from commandline; -includeconf=true',
)
self.nodes[0].assert_start_raises_init_error(
extra_args=['-includeconf=relative2.conf', '-includeconf=no_warn.conf'],
expected_msg='Error: Error parsing command line arguments: -includeconf cannot be used from commandline; -includeconf="relative2.conf"',
)
self.log.info("-includeconf cannot be used recursively. subversion should end with 'main; relative)/'")
with open(os.path.join(self.options.tmpdir, "node0", "relative.conf"), "a", encoding="utf8") as f:
f.write("includeconf=relative2.conf\n")
self.start_node(0)
subversion = self.nodes[0].getnetworkinfo()["subversion"]
assert subversion.startswith("/Vexta:")
self.stop_node(0)
self.log.info("-includeconf cannot contain invalid arg")
# Commented out as long as we ignore invalid arguments in configuration files
#with open(os.path.join(self.options.tmpdir, "node0", "relative.conf"), "w", encoding="utf8") as f:
# f.write("foo=bar\n")
#self.nodes[0].assert_start_raises_init_error(expected_msg="Error: Error reading configuration file: Invalid configuration value foo")
self.log.info("-includeconf cannot be invalid path")
os.remove(os.path.join(self.options.tmpdir, "node0", "relative.conf"))
self.start_node(0)
self.stop_node(0)
self.log.info("multiple -includeconf args can be used from the base config file. subversion should end with 'main; relative; relative2)/'")
with open(os.path.join(self.options.tmpdir, "node0", "relative.conf"), "w", encoding="utf8") as f:
# Restore initial file contents
f.write("uacomment=relative\n")
with open(os.path.join(self.options.tmpdir, "node0", "digibyte.conf"), "a", encoding='utf8') as f:
f.write("includeconf=relative2.conf\n")
self.start_node(0)
subversion = self.nodes[0].getnetworkinfo()["subversion"]
assert subversion.startswith("/Vexta:")
if __name__ == '__main__':
IncludeConfTest().main()

View file

@ -0,0 +1,84 @@
#!/usr/bin/env python3
# Copyright (c) 2017-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test loadblock option
Test the option to start a node with the option loadblock which loads
a serialized blockchain from a file (usually called bootstrap.dat).
To generate that file this test uses the helper scripts available
in contrib/linearize.
"""
import os
import subprocess
import sys
import tempfile
import urllib
from test_framework.blocktools import COINBASE_MATURITY_2
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import assert_equal
class LoadblockTest(DigiByteTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 2
self.supports_cli = False
def run_test(self):
self.nodes[1].setnetworkactive(state=False)
self.generate(self.nodes[0], COINBASE_MATURITY_2, sync_fun=self.no_op)
# Parsing the url of our node to get settings for config file
data_dir = self.nodes[0].datadir
node_url = urllib.parse.urlparse(self.nodes[0].url)
cfg_file = os.path.join(data_dir, "linearize.cfg")
bootstrap_file = os.path.join(self.options.tmpdir, "bootstrap.dat")
genesis_block = self.nodes[0].getblockhash(0)
blocks_dir = os.path.join(data_dir, self.chain, "blocks")
hash_list = tempfile.NamedTemporaryFile(dir=data_dir,
mode='w',
delete=False,
encoding="utf-8")
self.log.info("Create linearization config file")
with open(cfg_file, "a", encoding="utf-8") as cfg:
cfg.write(f"datadir={data_dir}\n")
cfg.write(f"rpcuser={node_url.username}\n")
cfg.write(f"rpcpassword={node_url.password}\n")
cfg.write(f"port={node_url.port}\n")
cfg.write(f"host={node_url.hostname}\n")
cfg.write(f"output_file={bootstrap_file}\n")
cfg.write(f"max_height=100\n")
cfg.write(f"netmagic=56545852\n")
cfg.write(f"input={blocks_dir}\n")
cfg.write(f"genesis={genesis_block}\n")
cfg.write(f"hashlist={hash_list.name}\n")
base_dir = self.config["environment"]["SRCDIR"]
linearize_dir = os.path.join(base_dir, "contrib", "linearize")
self.log.info("Run linearization of block hashes")
linearize_hashes_file = os.path.join(linearize_dir, "linearize-hashes.py")
subprocess.run([sys.executable, linearize_hashes_file, cfg_file],
stdout=hash_list,
check=True)
self.log.info("Run linearization of block data")
linearize_data_file = os.path.join(linearize_dir, "linearize-data.py")
subprocess.run([sys.executable, linearize_data_file, cfg_file],
check=True)
self.log.info("Restart second, unsynced node with bootstrap file")
self.restart_node(1, extra_args=[f"-loadblock={bootstrap_file}"])
assert_equal(self.nodes[1].getblockcount(), 100) # start_node is blocking on all block files being imported
assert_equal(self.nodes[1].getblockchaininfo()['blocks'], 100)
assert_equal(self.nodes[0].getbestblockhash(), self.nodes[1].getbestblockhash())
if __name__ == '__main__':
LoadblockTest().main()

View file

@ -0,0 +1,74 @@
#!/usr/bin/env python3
# Copyright (c) 2017-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test debug logging."""
import os
from test_framework.test_framework import DigiByteTestFramework
from test_framework.test_node import ErrorMatch
class LoggingTest(DigiByteTestFramework):
def set_test_params(self):
self.num_nodes = 1
self.setup_clean_chain = True
def relative_log_path(self, name):
return os.path.join(self.nodes[0].datadir, self.chain, name)
def run_test(self):
# test default log file name
default_log_path = self.relative_log_path("debug.log")
assert os.path.isfile(default_log_path)
# test alternative log file name in datadir
self.restart_node(0, ["-debuglogfile=foo.log"])
assert os.path.isfile(self.relative_log_path("foo.log"))
# test alternative log file name outside datadir
tempname = os.path.join(self.options.tmpdir, "foo.log")
self.restart_node(0, [f"-debuglogfile={tempname}"])
assert os.path.isfile(tempname)
# check that invalid log (relative) will cause error
invdir = self.relative_log_path("foo")
invalidname = os.path.join("foo", "foo.log")
self.stop_node(0)
exp_stderr = r"Error: Could not open debug log file \S+$"
self.nodes[0].assert_start_raises_init_error([f"-debuglogfile={invalidname}"], exp_stderr, match=ErrorMatch.FULL_REGEX)
assert not os.path.isfile(os.path.join(invdir, "foo.log"))
# check that invalid log (relative) works after path exists
self.stop_node(0)
os.mkdir(invdir)
self.start_node(0, [f"-debuglogfile={invalidname}"])
assert os.path.isfile(os.path.join(invdir, "foo.log"))
# check that invalid log (absolute) will cause error
self.stop_node(0)
invdir = os.path.join(self.options.tmpdir, "foo")
invalidname = os.path.join(invdir, "foo.log")
self.nodes[0].assert_start_raises_init_error([f"-debuglogfile={invalidname}"], exp_stderr, match=ErrorMatch.FULL_REGEX)
assert not os.path.isfile(os.path.join(invdir, "foo.log"))
# check that invalid log (absolute) works after path exists
self.stop_node(0)
os.mkdir(invdir)
self.start_node(0, [f"-debuglogfile={invalidname}"])
assert os.path.isfile(os.path.join(invdir, "foo.log"))
# check that -nodebuglogfile disables logging
self.stop_node(0)
os.unlink(default_log_path)
assert not os.path.isfile(default_log_path)
self.start_node(0, ["-nodebuglogfile"])
assert not os.path.isfile(default_log_path)
# just sanity check no crash here
self.restart_node(0, [f"-debuglogfile={os.devnull}"])
if __name__ == '__main__':
LoggingTest().main()

View file

@ -0,0 +1,165 @@
#!/usr/bin/env python3
# Copyright (c) 2015-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test behavior of -maxuploadtarget.
* Verify that getdata requests for old blocks (>1week) are dropped
if uploadtarget has been reached.
* Verify that getdata requests for recent blocks are respected even
if uploadtarget has been reached.
* Verify that the upload counters are reset after 24 hours.
"""
from collections import defaultdict
import time
from test_framework.messages import CInv, MSG_BLOCK, msg_getdata
from test_framework.p2p import P2PInterface
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import assert_equal, mine_large_block
class TestP2PConn(P2PInterface):
def __init__(self):
super().__init__()
self.block_receive_map = defaultdict(int)
def on_inv(self, message):
pass
def on_block(self, message):
message.block.calc_sha256()
self.block_receive_map[message.block.sha256] += 1
class MaxUploadTest(DigiByteTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
self.extra_args = [[
"-maxuploadtarget=800M",
"-acceptnonstdtxn=1",
]]
self.supports_cli = False
# Cache for utxos, as the listunspent may take a long time later in the test
self.utxo_cache = []
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
def run_test(self):
# Before we connect anything, we first set the time on the node
# to be in the past, otherwise things break because the CNode
# time counters can't be reset backward after initialization
old_time = int(time.time() - 2*60*60*24*7)
self.nodes[0].setmocktime(old_time)
# Generate some old blocks
self.generate(self.nodes[0], 130)
# p2p_conns[0] will only request old blocks
# p2p_conns[1] will only request new blocks
# p2p_conns[2] will test resetting the counters
p2p_conns = []
for _ in range(3):
p2p_conns.append(self.nodes[0].add_p2p_connection(TestP2PConn()))
# Now mine a big block
mine_large_block(self, self.nodes[0], self.utxo_cache)
# Store the hash; we'll request this later
big_old_block = self.nodes[0].getbestblockhash()
old_block_size = self.nodes[0].getblock(big_old_block, True)['size']
big_old_block = int(big_old_block, 16)
# Advance to two days ago
self.nodes[0].setmocktime(int(time.time()) - 2*60*60*24)
# Mine one more block, so that the prior block looks old
mine_large_block(self, self.nodes[0], self.utxo_cache)
# We'll be requesting this new block too
big_new_block = self.nodes[0].getbestblockhash()
big_new_block = int(big_new_block, 16)
# p2p_conns[0] will test what happens if we just keep requesting the
# the same big old block too many times (expect: disconnect)
getdata_request = msg_getdata()
getdata_request.inv.append(CInv(MSG_BLOCK, big_old_block))
max_bytes_per_day = 800*1024*1024
daily_buffer = 144 * 4000000
max_bytes_available = max_bytes_per_day - daily_buffer
success_count = max_bytes_available // old_block_size
# 576MB will be reserved for relaying new blocks, so expect this to
# succeed for ~235 tries.
for i in range(success_count):
p2p_conns[0].send_and_ping(getdata_request)
assert_equal(p2p_conns[0].block_receive_map[big_old_block], i+1)
assert_equal(len(self.nodes[0].getpeerinfo()), 3)
# At most a couple more tries should succeed (depending on how long
# the test has been running so far).
for _ in range(3):
p2p_conns[0].send_message(getdata_request)
p2p_conns[0].wait_for_disconnect()
assert_equal(len(self.nodes[0].getpeerinfo()), 2)
self.log.info("Peer 0 disconnected after downloading old block too many times")
# Requesting the current block on p2p_conns[1] should succeed indefinitely,
# even when over the max upload target.
# We'll try 800 times
getdata_request.inv = [CInv(MSG_BLOCK, big_new_block)]
for i in range(800):
p2p_conns[1].send_and_ping(getdata_request)
assert_equal(p2p_conns[1].block_receive_map[big_new_block], i+1)
self.log.info("Peer 1 able to repeatedly download new block")
# But if p2p_conns[1] tries for an old block, it gets disconnected too.
getdata_request.inv = [CInv(MSG_BLOCK, big_old_block)]
p2p_conns[1].send_message(getdata_request)
p2p_conns[1].wait_for_disconnect()
assert_equal(len(self.nodes[0].getpeerinfo()), 1)
self.log.info("Peer 1 disconnected after trying to download old block")
self.log.info("Advancing system time on node to clear counters...")
# If we advance the time by 24 hours, then the counters should reset,
# and p2p_conns[2] should be able to retrieve the old block.
self.nodes[0].setmocktime(int(time.time()))
p2p_conns[2].sync_with_ping()
p2p_conns[2].send_and_ping(getdata_request)
assert_equal(p2p_conns[2].block_receive_map[big_old_block], 1)
self.log.info("Peer 2 able to download old block")
self.nodes[0].disconnect_p2ps()
self.log.info("Restarting node 0 with download permission and 1MB maxuploadtarget")
self.restart_node(0, ["-whitelist=download@127.0.0.1", "-maxuploadtarget=1"])
# Reconnect to self.nodes[0]
peer = self.nodes[0].add_p2p_connection(TestP2PConn())
#retrieve 20 blocks which should be enough to break the 1MB limit
getdata_request.inv = [CInv(MSG_BLOCK, big_new_block)]
for i in range(20):
peer.send_and_ping(getdata_request)
assert_equal(peer.block_receive_map[big_new_block], i+1)
getdata_request.inv = [CInv(MSG_BLOCK, big_old_block)]
peer.send_and_ping(getdata_request)
self.log.info("Peer still connected after trying to download old block (download permission)")
peer_info = self.nodes[0].getpeerinfo()
assert_equal(len(peer_info), 1) # node is still connected
assert_equal(peer_info[0]['permissions'], ['download'])
if __name__ == '__main__':
MaxUploadTest().main()

View file

@ -0,0 +1,89 @@
#!/usr/bin/env python3
# Copyright (c) 2017-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test logic for setting nMinimumChainWork on command line.
Nodes don't consider themselves out of "initial block download" until
their active chain has more work than nMinimumChainWork.
Nodes don't download blocks from a peer unless the peer's best known block
has more work than nMinimumChainWork.
While in initial block download, nodes won't relay blocks to their peers, so
test that this parameter functions as intended by verifying that block relay
only succeeds past a given node once its nMinimumChainWork has been exceeded.
"""
import time
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import assert_equal
# 2 hashes required per regtest block (with no difficulty adjustment)
REGTEST_WORK_PER_BLOCK = 2
class MinimumChainWorkTest(DigiByteTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 3
self.extra_args = [[], ["-minimumchainwork=0x65"], ["-minimumchainwork=0x65"]]
self.node_min_work = [0, 101, 101]
def setup_network(self):
# This test relies on the chain setup being:
# node0 <- node1 <- node2
# Before leaving IBD, nodes prefer to download blocks from outbound
# peers, so ensure that we're mining on an outbound peer and testing
# block relay to inbound peers.
self.setup_nodes()
for i in range(self.num_nodes-1):
self.connect_nodes(i+1, i)
def run_test(self):
# Start building a chain on node0. node2 shouldn't be able to sync until node1's
# minchainwork is exceeded
starting_chain_work = REGTEST_WORK_PER_BLOCK # Genesis block's work
self.log.info(f"Testing relay across node 1 (minChainWork = {self.node_min_work[1]})")
starting_blockcount = self.nodes[2].getblockcount()
num_blocks_to_generate = int((self.node_min_work[1] - starting_chain_work) / REGTEST_WORK_PER_BLOCK)
self.log.info(f"Generating {num_blocks_to_generate} blocks on node0")
hashes = self.generate(self.nodes[0], num_blocks_to_generate, sync_fun=self.no_op)
self.log.info(f"Node0 current chain work: {self.nodes[0].getblockheader(hashes[-1])['chainwork']}")
# Sleep a few seconds and verify that node2 didn't get any new blocks
# or headers. We sleep, rather than sync_blocks(node0, node1) because
# it's reasonable either way for node1 to get the blocks, or not get
# them (since they're below node1's minchainwork).
time.sleep(3)
self.log.info("Verifying node 2 has no more blocks than before")
self.log.info(f"Blockcounts: {[n.getblockcount() for n in self.nodes]}")
# Node2 shouldn't have any new headers yet, because node1 should not
# have relayed anything.
assert_equal(len(self.nodes[2].getchaintips()), 1)
assert_equal(self.nodes[2].getchaintips()[0]['height'], 0)
assert self.nodes[1].getbestblockhash() != self.nodes[0].getbestblockhash()
assert_equal(self.nodes[2].getblockcount(), starting_blockcount)
self.log.info("Generating one more block")
self.generate(self.nodes[0], 1)
self.log.info("Verifying nodes are all synced")
# Because nodes in regtest are all manual connections (eg using
# addnode), node1 should not have disconnected node0. If not for that,
# we'd expect node1 to have disconnected node0 for serving an
# insufficient work chain, in which case we'd need to reconnect them to
# continue the test.
self.sync_all()
self.log.info(f"Blockcounts: {[n.getblockcount() for n in self.nodes]}")
if __name__ == '__main__':
MinimumChainWorkTest().main()

View file

@ -0,0 +1,188 @@
#!/usr/bin/env python3
# Copyright (c) 2014-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test the -alertnotify, -blocknotify and -walletnotify options."""
import os
from test_framework.address import ADDRESS_BCRT1_UNSPENDABLE
from test_framework.descriptors import descsum_create
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import (
assert_equal,
)
# Linux allow all characters other than \x00
# Windows disallow control characters (0-31) and /\?%:|"<>
FILE_CHAR_START = 32 if os.name == 'nt' else 1
FILE_CHAR_END = 128
FILE_CHARS_DISALLOWED = '/\\?%*:|"<>' if os.name == 'nt' else '/'
UNCONFIRMED_HASH_STRING = 'unconfirmed'
def notify_outputname(walletname, txid):
return txid if os.name == 'nt' else f'{walletname}_{txid}'
class NotificationsTest(DigiByteTestFramework):
def set_test_params(self):
self.num_nodes = 2
self.setup_clean_chain = True
# The experimental syscall sandbox feature (-sandbox) is not compatible with -alertnotify,
# -blocknotify or -walletnotify (which all invoke execve).
self.disable_syscall_sandbox = True
def setup_network(self):
self.wallet = ''.join(chr(i) for i in range(FILE_CHAR_START, FILE_CHAR_END) if chr(i) not in FILE_CHARS_DISALLOWED)
self.alertnotify_dir = os.path.join(self.options.tmpdir, "alertnotify")
self.blocknotify_dir = os.path.join(self.options.tmpdir, "blocknotify")
self.walletnotify_dir = os.path.join(self.options.tmpdir, "walletnotify")
os.mkdir(self.alertnotify_dir)
os.mkdir(self.blocknotify_dir)
os.mkdir(self.walletnotify_dir)
# -alertnotify and -blocknotify on node0, walletnotify on node1
self.extra_args = [[
f"-alertnotify=echo > {os.path.join(self.alertnotify_dir, '%s')}",
f"-blocknotify=echo > {os.path.join(self.blocknotify_dir, '%s')}",
], [
f"-walletnotify=echo %h_%b > {os.path.join(self.walletnotify_dir, notify_outputname('%w', '%s'))}",
]]
self.wallet_names = [self.default_wallet_name, self.wallet]
super().setup_network()
def run_test(self):
if self.is_wallet_compiled():
# Setup the descriptors to be imported to the wallet
seed = "egMFpCzuYYAjwDZcfmzUszqctahU9EuNwmoYDxqXRdHnbyEgFFLr"
xpriv = "tprv8ZgxMBicQKsPfHCsTwkiM1KT56RXbGGTqvc2hgqzycpwbHqqpcajQeMRZoBD35kW4RtyCemu6j34Ku5DEspmgjKdt2qe4SvRch5Kk8B8A2v"
desc_imports = [{
"desc": descsum_create(f"wpkh({xpriv}/0/*)"),
"timestamp": 0,
"active": True,
"keypool": True,
},{
"desc": descsum_create(f"wpkh({xpriv}/1/*)"),
"timestamp": 0,
"active": True,
"keypool": True,
"internal": True,
}]
# Make the wallets and import the descriptors
# Ensures that node 0 and node 1 share the same wallet for the conflicting transaction tests below.
for i, name in enumerate(self.wallet_names):
self.nodes[i].createwallet(wallet_name=name, descriptors=self.options.descriptors, blank=True, load_on_startup=True)
if self.options.descriptors:
self.nodes[i].importdescriptors(desc_imports)
else:
self.nodes[i].sethdseed(True, seed)
self.log.info("test -blocknotify")
block_count = 10
blocks = self.generatetoaddress(self.nodes[1], block_count, self.nodes[1].getnewaddress() if self.is_wallet_compiled() else ADDRESS_BCRT1_UNSPENDABLE)
# wait at most 10 seconds for expected number of files before reading the content
self.wait_until(lambda: len(os.listdir(self.blocknotify_dir)) == block_count, timeout=10)
# directory content should equal the generated blocks hashes
assert_equal(sorted(blocks), sorted(os.listdir(self.blocknotify_dir)))
if self.is_wallet_compiled():
self.log.info("test -walletnotify")
# wait at most 10 seconds for expected number of files before reading the content
self.wait_until(lambda: len(os.listdir(self.walletnotify_dir)) == block_count, timeout=10)
# directory content should equal the generated transaction hashes
tx_details = list(map(lambda t: (t['txid'], t['blockheight'], t['blockhash']), self.nodes[1].listtransactions("*", block_count)))
self.expect_wallet_notify(tx_details)
self.log.info("test -walletnotify after rescan")
# rescan to force wallet notifications
self.nodes[1].rescanblockchain()
self.wait_until(lambda: len(os.listdir(self.walletnotify_dir)) == block_count, timeout=10)
self.connect_nodes(0, 1)
# directory content should equal the generated transaction hashes
tx_details = list(map(lambda t: (t['txid'], t['blockheight'], t['blockhash']), self.nodes[1].listtransactions("*", block_count)))
self.expect_wallet_notify(tx_details)
# Conflicting transactions tests.
# Generate spends from node 0, and check notifications
# triggered by node 1
self.log.info("test -walletnotify with conflicting transactions")
self.nodes[0].rescanblockchain()
self.generatetoaddress(self.nodes[0], 100, ADDRESS_BCRT1_UNSPENDABLE)
# Generate transaction on node 0, sync mempools, and check for
# notification on node 1.
tx1 = self.nodes[0].sendtoaddress(address=ADDRESS_BCRT1_UNSPENDABLE, amount=1, replaceable=True)
assert_equal(tx1 in self.nodes[0].getrawmempool(), True)
self.sync_mempools()
self.expect_wallet_notify([(tx1, -1, UNCONFIRMED_HASH_STRING)])
# Generate bump transaction, sync mempools, and check for bump1
# notification. In the future, per
# https://github.com/digibyte/digibyte/pull/9371, it might be better
# to have notifications for both tx1 and bump1.
bump1 = self.nodes[0].bumpfee(tx1)["txid"]
assert_equal(bump1 in self.nodes[0].getrawmempool(), True)
self.sync_mempools()
self.expect_wallet_notify([(bump1, -1, UNCONFIRMED_HASH_STRING)])
# Add bump1 transaction to new block, checking for a notification
# and the correct number of confirmations.
blockhash1 = self.generatetoaddress(self.nodes[0], 1, ADDRESS_BCRT1_UNSPENDABLE)[0]
blockheight1 = self.nodes[0].getblockcount()
self.sync_blocks()
self.expect_wallet_notify([(bump1, blockheight1, blockhash1)])
assert_equal(self.nodes[1].gettransaction(bump1)["confirmations"], 1)
# Generate a second transaction to be bumped.
tx2 = self.nodes[0].sendtoaddress(address=ADDRESS_BCRT1_UNSPENDABLE, amount=1, replaceable=True)
assert_equal(tx2 in self.nodes[0].getrawmempool(), True)
self.sync_mempools()
self.expect_wallet_notify([(tx2, -1, UNCONFIRMED_HASH_STRING)])
# Bump tx2 as bump2 and generate a block on node 0 while
# disconnected, then reconnect and check for notifications on node 1
# about newly confirmed bump2 and newly conflicted tx2.
self.disconnect_nodes(0, 1)
bump2 = self.nodes[0].bumpfee(tx2)["txid"]
blockhash2 = self.generatetoaddress(self.nodes[0], 1, ADDRESS_BCRT1_UNSPENDABLE, sync_fun=self.no_op)[0]
blockheight2 = self.nodes[0].getblockcount()
assert_equal(self.nodes[0].gettransaction(bump2)["confirmations"], 1)
assert_equal(tx2 in self.nodes[1].getrawmempool(), True)
self.connect_nodes(0, 1)
self.sync_blocks()
self.expect_wallet_notify([(bump2, blockheight2, blockhash2), (tx2, -1, UNCONFIRMED_HASH_STRING)])
assert_equal(self.nodes[1].gettransaction(bump2)["confirmations"], 1)
# TODO: add test for `-alertnotify` large fork notifications
def expect_wallet_notify(self, tx_details):
self.wait_until(lambda: len(os.listdir(self.walletnotify_dir)) >= len(tx_details), timeout=10)
# Should have no more and no less files than expected
assert_equal(sorted(notify_outputname(self.wallet, tx_id) for tx_id, _, _ in tx_details), sorted(os.listdir(self.walletnotify_dir)))
# Should now verify contents of each file
for tx_id, blockheight, blockhash in tx_details:
fname = os.path.join(self.walletnotify_dir, notify_outputname(self.wallet, tx_id))
# Wait for the cached writes to hit storage
self.wait_until(lambda: os.path.getsize(fname) > 0, timeout=10)
with open(fname, 'rt', encoding='utf-8') as f:
text = f.read()
# Universal newline ensures '\n' on 'nt'
assert_equal(text[-1], '\n')
text = text[:-1]
if os.name == 'nt':
# On Windows, echo as above will append a whitespace
assert_equal(text[-1], ' ')
text = text[:-1]
expected = str(blockheight) + '_' + blockhash
assert_equal(text, expected)
for tx_file in os.listdir(self.walletnotify_dir):
os.remove(os.path.join(self.walletnotify_dir, tx_file))
if __name__ == '__main__':
NotificationsTest().main()

View file

@ -0,0 +1,139 @@
#!/usr/bin/env python3
# Copyright (c) 2016-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test NULLDUMMY softfork.
Connect to a single node.
Generate 2 blocks (save the coinbases for later).
Generate COINBASE_MATURITY (CB) more blocks to ensure the coinbases are mature.
[Policy/Consensus] Check that NULLDUMMY compliant transactions are accepted in block CB + 3.
[Policy] Check that non-NULLDUMMY transactions are rejected before activation.
[Consensus] Check that the new NULLDUMMY rules are not enforced on block CB + 4.
[Policy/Consensus] Check that the new NULLDUMMY rules are enforced on block CB + 5.
"""
import time
from test_framework.blocktools import (
COINBASE_MATURITY,
NORMAL_GBT_REQUEST_PARAMS,
add_witness_commitment,
create_block,
create_transaction,
)
from test_framework.messages import CTransaction
from test_framework.script import (
OP_0,
OP_TRUE,
)
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
)
NULLDUMMY_ERROR = "non-mandatory-script-verify-flag (Dummy CHECKMULTISIG argument must be zero)"
def invalidate_nulldummy_tx(tx):
"""Transform a NULLDUMMY compliant tx (i.e. scriptSig starts with OP_0)
to be non-NULLDUMMY compliant by replacing the dummy with OP_TRUE"""
assert_equal(tx.vin[0].scriptSig[0], OP_0)
tx.vin[0].scriptSig = bytes([OP_TRUE]) + tx.vin[0].scriptSig[1:]
tx.rehash()
class NULLDUMMYTest(DigiByteTestFramework):
def set_test_params(self):
self.num_nodes = 1
self.setup_clean_chain = True
# This script tests NULLDUMMY activation, which is part of the 'segwit' deployment, so we go through
# normal segwit activation here (and don't use the default always-on behaviour).
self.extra_args = [[
f'-testactivationheight=segwit@{COINBASE_MATURITY + 5}',
'-addresstype=legacy',
'-par=1', # Use only one script thread to get the exact reject reason for testing
]]
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
def run_test(self):
self.nodes[0].createwallet(wallet_name='wmulti', disable_private_keys=True)
wmulti = self.nodes[0].get_wallet_rpc('wmulti')
w0 = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
self.address = w0.getnewaddress()
self.pubkey = w0.getaddressinfo(self.address)['pubkey']
self.ms_address = wmulti.addmultisigaddress(1, [self.pubkey])['address']
self.wit_address = w0.getnewaddress(address_type='p2sh-segwit')
self.wit_ms_address = wmulti.addmultisigaddress(1, [self.pubkey], '', 'p2sh-segwit')['address']
if not self.options.descriptors:
# Legacy wallets need to import these so that they are watched by the wallet. This is unnecessary (and does not need to be tested) for descriptor wallets
wmulti.importaddress(self.ms_address)
wmulti.importaddress(self.wit_ms_address)
self.coinbase_blocks = self.generate(self.nodes[0], 2) # block height = 2
coinbase_txid = []
for i in self.coinbase_blocks:
coinbase_txid.append(self.nodes[0].getblock(i)['tx'][0])
self.generate(self.nodes[0], COINBASE_MATURITY) # block height = COINBASE_MATURITY + 2
self.lastblockhash = self.nodes[0].getbestblockhash()
self.lastblockheight = COINBASE_MATURITY + 2
self.lastblocktime = int(time.time()) + self.lastblockheight
self.log.info(f"Test 1: NULLDUMMY compliant base transactions should be accepted to mempool and mined before activation [{COINBASE_MATURITY + 3}]")
test1txs = [create_transaction(self.nodes[0], coinbase_txid[0], self.ms_address, amount=49)]
txid1 = self.nodes[0].sendrawtransaction(test1txs[0].serialize_with_witness().hex(), 0)
test1txs.append(create_transaction(self.nodes[0], txid1, self.ms_address, amount=48))
txid2 = self.nodes[0].sendrawtransaction(test1txs[1].serialize_with_witness().hex(), 0)
test1txs.append(create_transaction(self.nodes[0], coinbase_txid[1], self.wit_ms_address, amount=49))
txid3 = self.nodes[0].sendrawtransaction(test1txs[2].serialize_with_witness().hex(), 0)
self.block_submit(self.nodes[0], test1txs, accept=True)
self.log.info("Test 2: Non-NULLDUMMY base multisig transaction should not be accepted to mempool before activation")
test2tx = create_transaction(self.nodes[0], txid2, self.ms_address, amount=47)
invalidate_nulldummy_tx(test2tx)
assert_raises_rpc_error(-26, NULLDUMMY_ERROR, self.nodes[0].sendrawtransaction, test2tx.serialize_with_witness().hex(), 0)
self.log.info(f"Test 3: Non-NULLDUMMY base transactions should be accepted in a block before activation [{COINBASE_MATURITY + 4}]")
self.block_submit(self.nodes[0], [test2tx], accept=True)
self.log.info("Test 4: Non-NULLDUMMY base multisig transaction is invalid after activation")
test4tx = create_transaction(self.nodes[0], test2tx.hash, self.address, amount=46)
test6txs = [CTransaction(test4tx)]
invalidate_nulldummy_tx(test4tx)
assert_raises_rpc_error(-26, NULLDUMMY_ERROR, self.nodes[0].sendrawtransaction, test4tx.serialize_with_witness().hex(), 0)
self.block_submit(self.nodes[0], [test4tx], accept=False)
self.log.info("Test 5: Non-NULLDUMMY P2WSH multisig transaction invalid after activation")
test5tx = create_transaction(self.nodes[0], txid3, self.wit_address, amount=48)
test6txs.append(CTransaction(test5tx))
test5tx.wit.vtxinwit[0].scriptWitness.stack[0] = b'\x01'
assert_raises_rpc_error(-26, NULLDUMMY_ERROR, self.nodes[0].sendrawtransaction, test5tx.serialize_with_witness().hex(), 0)
self.block_submit(self.nodes[0], [test5tx], with_witness=True, accept=False)
self.log.info(f"Test 6: NULLDUMMY compliant base/witness transactions should be accepted to mempool and in block after activation [{COINBASE_MATURITY + 5}]")
for i in test6txs:
self.nodes[0].sendrawtransaction(i.serialize_with_witness().hex(), 0)
self.block_submit(self.nodes[0], test6txs, with_witness=True, accept=True)
def block_submit(self, node, txs, *, with_witness=False, accept):
tmpl = node.getblocktemplate(NORMAL_GBT_REQUEST_PARAMS)
assert_equal(tmpl['previousblockhash'], self.lastblockhash)
assert_equal(tmpl['height'], self.lastblockheight + 1)
block = create_block(tmpl=tmpl, ntime=self.lastblocktime + 1, txlist=txs)
if with_witness:
add_witness_commitment(block)
block.solve()
assert_equal(None if accept else NULLDUMMY_ERROR, node.submitblock(block.serialize().hex()))
if accept:
assert_equal(node.getbestblockhash(), block.hash)
self.lastblockhash = block.hash
self.lastblocktime += 1
self.lastblockheight += 1
else:
assert_equal(node.getbestblockhash(), self.lastblockhash)
if __name__ == '__main__':
NULLDUMMYTest().main()

259
test/functional/feature_proxy.py Executable file
View file

@ -0,0 +1,259 @@
#!/usr/bin/env python3
# Copyright (c) 2015-2020 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test bitcoind with different proxy configuration.
Test plan:
- Start bitcoind's with different proxy configurations
- Use addnode to initiate connections
- Verify that proxies are connected to, and the right connection command is given
- Proxy configurations to test on bitcoind side:
- `-proxy` (proxy everything)
- `-onion` (proxy just onions)
- `-proxyrandomize` Circuit randomization
- Proxy configurations to test on proxy side,
- support no authentication (other proxy)
- support no authentication + user/pass authentication (Tor)
- proxy on IPv6
- Create various proxies (as threads)
- Create nodes that connect to them
- Manipulate the peer connections using addnode (onetry) and observe effects
- Test the getpeerinfo `network` field for the peer
addnode connect to IPv4
addnode connect to IPv6
addnode connect to onion
addnode connect to generic DNS name
- Test getnetworkinfo for each node
"""
import socket
import os
from test_framework.socks5 import Socks5Configuration, Socks5Command, Socks5Server, AddressType
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import (
PORT_MIN,
PORT_RANGE,
assert_equal,
)
from test_framework.netutil import test_ipv6_local
RANGE_BEGIN = PORT_MIN + 2 * PORT_RANGE # Start after p2p and rpc ports
# Networks returned by RPC getpeerinfo.
NET_UNROUTABLE = "not_publicly_routable"
NET_IPV4 = "ipv4"
NET_IPV6 = "ipv6"
NET_ONION = "onion"
NET_I2P = "i2p"
# Networks returned by RPC getnetworkinfo, defined in src/rpc/net.cpp::GetNetworksInfo()
NETWORKS = frozenset({NET_IPV4, NET_IPV6, NET_ONION, NET_I2P})
class ProxyTest(DigiByteTestFramework):
def set_test_params(self):
self.num_nodes = 4
self.setup_clean_chain = True
def setup_nodes(self):
self.have_ipv6 = test_ipv6_local()
# Create two proxies on different ports
# ... one unauthenticated
self.conf1 = Socks5Configuration()
self.conf1.addr = ('127.0.0.1', RANGE_BEGIN + (os.getpid() % 1000))
self.conf1.unauth = True
self.conf1.auth = False
# ... one supporting authenticated and unauthenticated (Tor)
self.conf2 = Socks5Configuration()
self.conf2.addr = ('127.0.0.1', RANGE_BEGIN + 1000 + (os.getpid() % 1000))
self.conf2.unauth = True
self.conf2.auth = True
if self.have_ipv6:
# ... one on IPv6 with similar configuration
self.conf3 = Socks5Configuration()
self.conf3.af = socket.AF_INET6
self.conf3.addr = ('::1', RANGE_BEGIN + 2000 + (os.getpid() % 1000))
self.conf3.unauth = True
self.conf3.auth = True
else:
self.log.warning("Testing without local IPv6 support")
self.serv1 = Socks5Server(self.conf1)
self.serv1.start()
self.serv2 = Socks5Server(self.conf2)
self.serv2.start()
if self.have_ipv6:
self.serv3 = Socks5Server(self.conf3)
self.serv3.start()
# We will not try to connect to this.
self.i2p_sam = ('127.0.0.1', 7656)
# Note: proxies are not used to connect to local nodes. This is because the proxy to
# use is based on CService.GetNetwork(), which returns NET_UNROUTABLE for localhost.
args = [
['-listen', '-proxy=%s:%i' % (self.conf1.addr),'-proxyrandomize=1'],
['-listen', '-proxy=%s:%i' % (self.conf1.addr),'-onion=%s:%i' % (self.conf2.addr),
'-i2psam=%s:%i' % (self.i2p_sam), '-i2pacceptincoming=0', '-proxyrandomize=0'],
['-listen', '-proxy=%s:%i' % (self.conf2.addr),'-proxyrandomize=1'],
[]
]
if self.have_ipv6:
args[3] = ['-listen', '-proxy=[%s]:%i' % (self.conf3.addr),'-proxyrandomize=0', '-noonion']
self.add_nodes(self.num_nodes, extra_args=args)
self.start_nodes()
def network_test(self, node, addr, network):
for peer in node.getpeerinfo():
if peer["addr"] == addr:
assert_equal(peer["network"], network)
def node_test(self, node, proxies, auth, test_onion=True):
rv = []
addr = "15.61.23.23:1234"
self.log.debug("Test: outgoing IPv4 connection through node for address {}".format(addr))
node.addnode(addr, "onetry")
cmd = proxies[0].queue.get()
assert isinstance(cmd, Socks5Command)
# Note: bitcoind's SOCKS5 implementation only sends atyp DOMAINNAME, even if connecting directly to IPv4/IPv6
assert_equal(cmd.atyp, AddressType.DOMAINNAME)
assert_equal(cmd.addr, b"15.61.23.23")
assert_equal(cmd.port, 1234)
if not auth:
assert_equal(cmd.username, None)
assert_equal(cmd.password, None)
rv.append(cmd)
self.network_test(node, addr, network=NET_IPV4)
if self.have_ipv6:
addr = "[1233:3432:2434:2343:3234:2345:6546:4534]:5443"
self.log.debug("Test: outgoing IPv6 connection through node for address {}".format(addr))
node.addnode(addr, "onetry")
cmd = proxies[1].queue.get()
assert isinstance(cmd, Socks5Command)
# Note: bitcoind's SOCKS5 implementation only sends atyp DOMAINNAME, even if connecting directly to IPv4/IPv6
assert_equal(cmd.atyp, AddressType.DOMAINNAME)
assert_equal(cmd.addr, b"1233:3432:2434:2343:3234:2345:6546:4534")
assert_equal(cmd.port, 5443)
if not auth:
assert_equal(cmd.username, None)
assert_equal(cmd.password, None)
rv.append(cmd)
self.network_test(node, addr, network=NET_IPV6)
if test_onion:
addr = "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion:8333"
self.log.debug("Test: outgoing onion connection through node for address {}".format(addr))
node.addnode(addr, "onetry")
cmd = proxies[2].queue.get()
assert isinstance(cmd, Socks5Command)
assert_equal(cmd.atyp, AddressType.DOMAINNAME)
assert_equal(cmd.addr, b"pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion")
assert_equal(cmd.port, 8333)
if not auth:
assert_equal(cmd.username, None)
assert_equal(cmd.password, None)
rv.append(cmd)
self.network_test(node, addr, network=NET_ONION)
addr = "node.noumenon:8333"
self.log.debug("Test: outgoing DNS name connection through node for address {}".format(addr))
node.addnode(addr, "onetry")
cmd = proxies[3].queue.get()
assert isinstance(cmd, Socks5Command)
assert_equal(cmd.atyp, AddressType.DOMAINNAME)
assert_equal(cmd.addr, b"node.noumenon")
assert_equal(cmd.port, 8333)
if not auth:
assert_equal(cmd.username, None)
assert_equal(cmd.password, None)
rv.append(cmd)
self.network_test(node, addr, network=NET_UNROUTABLE)
return rv
def run_test(self):
# basic -proxy
self.node_test(self.nodes[0], [self.serv1, self.serv1, self.serv1, self.serv1], False)
# -proxy plus -onion
self.node_test(self.nodes[1], [self.serv1, self.serv1, self.serv2, self.serv1], False)
# -proxy plus -onion, -proxyrandomize
rv = self.node_test(self.nodes[2], [self.serv2, self.serv2, self.serv2, self.serv2], True)
# Check that credentials as used for -proxyrandomize connections are unique
credentials = set((x.username,x.password) for x in rv)
assert_equal(len(credentials), len(rv))
if self.have_ipv6:
# proxy on IPv6 localhost
self.node_test(self.nodes[3], [self.serv3, self.serv3, self.serv3, self.serv3], False, False)
def networks_dict(d):
r = {}
for x in d['networks']:
r[x['name']] = x
return r
self.log.info("Test RPC getnetworkinfo")
n0 = networks_dict(self.nodes[0].getnetworkinfo())
assert_equal(NETWORKS, n0.keys())
for net in NETWORKS:
if net == NET_I2P:
expected_proxy = ''
expected_randomize = False
else:
expected_proxy = '%s:%i' % (self.conf1.addr)
expected_randomize = True
assert_equal(n0[net]['proxy'], expected_proxy)
assert_equal(n0[net]['proxy_randomize_credentials'], expected_randomize)
assert_equal(n0['onion']['reachable'], True)
assert_equal(n0['i2p']['reachable'], False)
n1 = networks_dict(self.nodes[1].getnetworkinfo())
assert_equal(NETWORKS, n1.keys())
for net in ['ipv4', 'ipv6']:
assert_equal(n1[net]['proxy'], '%s:%i' % (self.conf1.addr))
assert_equal(n1[net]['proxy_randomize_credentials'], False)
assert_equal(n1['onion']['proxy'], '%s:%i' % (self.conf2.addr))
assert_equal(n1['onion']['proxy_randomize_credentials'], False)
assert_equal(n1['onion']['reachable'], True)
assert_equal(n1['i2p']['proxy'], '%s:%i' % (self.i2p_sam))
assert_equal(n1['i2p']['proxy_randomize_credentials'], False)
assert_equal(n1['i2p']['reachable'], True)
n2 = networks_dict(self.nodes[2].getnetworkinfo())
assert_equal(NETWORKS, n2.keys())
for net in NETWORKS:
if net == NET_I2P:
expected_proxy = ''
expected_randomize = False
else:
expected_proxy = '%s:%i' % (self.conf2.addr)
expected_randomize = True
assert_equal(n2[net]['proxy'], expected_proxy)
assert_equal(n2[net]['proxy_randomize_credentials'], expected_randomize)
assert_equal(n2['onion']['reachable'], True)
assert_equal(n2['i2p']['reachable'], False)
if self.have_ipv6:
n3 = networks_dict(self.nodes[3].getnetworkinfo())
assert_equal(NETWORKS, n3.keys())
for net in NETWORKS:
if net == NET_I2P:
expected_proxy = ''
else:
expected_proxy = '[%s]:%i' % (self.conf3.addr)
assert_equal(n3[net]['proxy'], expected_proxy)
assert_equal(n3[net]['proxy_randomize_credentials'], False)
assert_equal(n3['onion']['reachable'], False)
assert_equal(n3['i2p']['reachable'], False)
if __name__ == '__main__':
ProxyTest().main()

View file

@ -0,0 +1,479 @@
#!/usr/bin/env python3
# Copyright (c) 2014-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test the pruning code.
WARNING:
This test uses 4GB of disk space.
This test takes 30 mins or more (up to 2 hours)
"""
import os
from test_framework.blocktools import create_coinbase
from test_framework.messages import CBlock
from test_framework.script import (
CScript,
OP_NOP,
OP_RETURN,
)
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import (
assert_equal,
assert_greater_than,
assert_raises_rpc_error,
)
# Rescans start at the earliest block up to 2 hours before a key timestamp, so
# the manual prune RPC avoids pruning blocks in the same window to be
# compatible with pruning based on key creation time.
TIMESTAMP_WINDOW = 2 * 60 * 60
def mine_large_blocks(node, n):
# Make a large scriptPubKey for the coinbase transaction. This is OP_RETURN
# followed by 950k of OP_NOP. This would be non-standard in a non-coinbase
# transaction but is consensus valid.
# Set the nTime if this is the first time this function has been called.
# A static variable ensures that time is monotonicly increasing and is therefore
# different for each block created => blockhash is unique.
if "nTimes" not in mine_large_blocks.__dict__:
mine_large_blocks.nTime = 0
# Get the block parameters for the first block
big_script = CScript([OP_RETURN] + [OP_NOP] * 950000)
best_block = node.getblock(node.getbestblockhash())
height = int(best_block["height"]) + 1
mine_large_blocks.nTime = max(mine_large_blocks.nTime, int(best_block["time"])) + 1
previousblockhash = int(best_block["hash"], 16)
for _ in range(n):
# Build the coinbase transaction (with large scriptPubKey)
coinbase_tx = create_coinbase(height)
coinbase_tx.vin[0].nSequence = 2 ** 32 - 1
coinbase_tx.vout[0].scriptPubKey = big_script
coinbase_tx.rehash()
# Build the block
block = CBlock()
block.nVersion = best_block["version"]
block.hashPrevBlock = previousblockhash
block.nTime = mine_large_blocks.nTime
block.nBits = int('207fffff', 16)
block.nNonce = 0
block.vtx = [coinbase_tx]
block.hashMerkleRoot = block.calc_merkle_root()
block.solve()
# Submit to the node
node.submitblock(block.serialize().hex())
previousblockhash = block.sha256
height += 1
mine_large_blocks.nTime += 1
def calc_usage(blockdir):
return sum(os.path.getsize(blockdir + f) for f in os.listdir(blockdir) if os.path.isfile(os.path.join(blockdir, f))) / (1024. * 1024.)
class PruneTest(DigiByteTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 6
self.supports_cli = False
# Create nodes 0 and 1 to mine.
# Create node 2 to test pruning.
self.full_node_default_args = ["-maxreceivebuffer=20000", "-checkblocks=5"]
# Create nodes 3 and 4 to test manual pruning (they will be re-started with manual pruning later)
# Create nodes 5 to test wallet in prune mode, but do not connect
self.extra_args = [
self.full_node_default_args,
self.full_node_default_args,
["-maxreceivebuffer=20000", "-prune=550"],
["-maxreceivebuffer=20000"],
["-maxreceivebuffer=20000"],
["-prune=550"],
]
self.rpc_timeout = 120
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
def setup_network(self):
self.setup_nodes()
self.prunedir = os.path.join(self.nodes[2].datadir, self.chain, 'blocks', '')
self.connect_nodes(0, 1)
self.connect_nodes(1, 2)
self.connect_nodes(0, 2)
self.connect_nodes(0, 3)
self.connect_nodes(0, 4)
self.sync_blocks(self.nodes[0:5])
def setup_nodes(self):
self.add_nodes(self.num_nodes, self.extra_args)
self.start_nodes()
self.import_deterministic_coinbase_privkeys()
def create_big_chain(self):
# Start by creating some coinbases we can spend later
self.generate(self.nodes[1], 200, sync_fun=lambda: self.sync_blocks(self.nodes[0:2]))
self.generate(self.nodes[0], 150, sync_fun=self.no_op)
# Then mine enough full blocks to create more than 550MiB of data
mine_large_blocks(self.nodes[0], 645)
self.sync_blocks(self.nodes[0:5])
def test_invalid_command_line_options(self):
self.nodes[0].assert_start_raises_init_error(
expected_msg='Error: Prune cannot be configured with a negative value.',
extra_args=['-prune=-1'],
)
self.nodes[0].assert_start_raises_init_error(
expected_msg='Error: Prune configured below the minimum of 550 MiB. Please use a higher number.',
extra_args=['-prune=549'],
)
self.nodes[0].assert_start_raises_init_error(
expected_msg='Error: Prune mode is incompatible with -txindex.',
extra_args=['-prune=550', '-txindex'],
)
self.nodes[0].assert_start_raises_init_error(
expected_msg='Error: Prune mode is incompatible with -coinstatsindex.',
extra_args=['-prune=550', '-coinstatsindex'],
)
def test_height_min(self):
assert os.path.isfile(os.path.join(self.prunedir, "blk00000.dat")), "blk00000.dat is missing, pruning too early"
self.log.info("Success")
self.log.info(f"Though we're already using more than 550MiB, current usage: {calc_usage(self.prunedir)}")
self.log.info("Mining 25 more blocks should cause the first block file to be pruned")
# Pruning doesn't run until we're allocating another chunk, 20 full blocks past the height cutoff will ensure this
mine_large_blocks(self.nodes[0], 25)
# Wait for blk00000.dat to be pruned
self.wait_until(lambda: not os.path.isfile(os.path.join(self.prunedir, "blk00000.dat")), timeout=30)
self.log.info("Success")
usage = calc_usage(self.prunedir)
self.log.info(f"Usage should be below target: {usage}")
assert_greater_than(550, usage)
def create_chain_with_staleblocks(self):
# Create stale blocks in manageable sized chunks
self.log.info("Mine 24 (stale) blocks on Node 1, followed by 25 (main chain) block reorg from Node 0, for 12 rounds")
for _ in range(12):
# Disconnect node 0 so it can mine a longer reorg chain without knowing about node 1's soon-to-be-stale chain
# Node 2 stays connected, so it hears about the stale blocks and then reorg's when node0 reconnects
self.disconnect_nodes(0, 1)
self.disconnect_nodes(0, 2)
# Mine 24 blocks in node 1
mine_large_blocks(self.nodes[1], 24)
# Reorg back with 25 block chain from node 0
mine_large_blocks(self.nodes[0], 25)
# Create connections in the order so both nodes can see the reorg at the same time
self.connect_nodes(0, 1)
self.connect_nodes(0, 2)
self.sync_blocks(self.nodes[0:3])
self.log.info(f"Usage can be over target because of high stale rate: {calc_usage(self.prunedir)}")
def reorg_test(self):
# Node 1 will mine a 300 block chain starting 287 blocks back from Node 0 and Node 2's tip
# This will cause Node 2 to do a reorg requiring 288 blocks of undo data to the reorg_test chain
height = self.nodes[1].getblockcount()
self.log.info(f"Current block height: {height}")
self.forkheight = height - 287
self.forkhash = self.nodes[1].getblockhash(self.forkheight)
self.log.info(f"Invalidating block {self.forkhash} at height {self.forkheight}")
self.nodes[1].invalidateblock(self.forkhash)
# We've now switched to our previously mined-24 block fork on node 1, but that's not what we want
# So invalidate that fork as well, until we're on the same chain as node 0/2 (but at an ancestor 288 blocks ago)
mainchainhash = self.nodes[0].getblockhash(self.forkheight - 1)
curhash = self.nodes[1].getblockhash(self.forkheight - 1)
while curhash != mainchainhash:
self.nodes[1].invalidateblock(curhash)
curhash = self.nodes[1].getblockhash(self.forkheight - 1)
assert self.nodes[1].getblockcount() == self.forkheight - 1
self.log.info(f"New best height: {self.nodes[1].getblockcount()}")
# Disconnect node1 and generate the new chain
self.disconnect_nodes(0, 1)
self.disconnect_nodes(1, 2)
self.log.info("Generating new longer chain of 300 more blocks")
self.generate(self.nodes[1], 300, sync_fun=self.no_op)
self.log.info("Reconnect nodes")
self.connect_nodes(0, 1)
self.connect_nodes(1, 2)
self.sync_blocks(self.nodes[0:3], timeout=120)
self.log.info(f"Verify height on node 2: {self.nodes[2].getblockcount()}")
self.log.info(f"Usage possibly still high because of stale blocks in block files: {calc_usage(self.prunedir)}")
self.log.info("Mine 220 more large blocks so we have requisite history")
mine_large_blocks(self.nodes[0], 220)
self.sync_blocks(self.nodes[0:3], timeout=120)
usage = calc_usage(self.prunedir)
self.log.info(f"Usage should be below target: {usage}")
assert_greater_than(550, usage)
def reorg_back(self):
# Verify that a block on the old main chain fork has been pruned away
assert_raises_rpc_error(-1, "Block not available (pruned data)", self.nodes[2].getblock, self.forkhash)
with self.nodes[2].assert_debug_log(expected_msgs=['block verification stopping at height', '(pruning, no data)']):
self.nodes[2].verifychain(checklevel=4, nblocks=0)
self.log.info(f"Will need to redownload block {self.forkheight}")
# Verify that we have enough history to reorg back to the fork point
# Although this is more than 288 blocks, because this chain was written more recently
# and only its other 299 small and 220 large blocks are in the block files after it,
# it is expected to still be retained
self.nodes[2].getblock(self.nodes[2].getblockhash(self.forkheight))
first_reorg_height = self.nodes[2].getblockcount()
curchainhash = self.nodes[2].getblockhash(self.mainchainheight)
self.nodes[2].invalidateblock(curchainhash)
goalbestheight = self.mainchainheight
goalbesthash = self.mainchainhash2
# As of 0.10 the current block download logic is not able to reorg to the original chain created in
# create_chain_with_stale_blocks because it doesn't know of any peer that's on that chain from which to
# redownload its missing blocks.
# Invalidate the reorg_test chain in node 0 as well, it can successfully switch to the original chain
# because it has all the block data.
# However it must mine enough blocks to have a more work chain than the reorg_test chain in order
# to trigger node 2's block download logic.
# At this point node 2 is within 288 blocks of the fork point so it will preserve its ability to reorg
if self.nodes[2].getblockcount() < self.mainchainheight:
blocks_to_mine = first_reorg_height + 1 - self.mainchainheight
self.log.info(f"Rewind node 0 to prev main chain to mine longer chain to trigger redownload. Blocks needed: {blocks_to_mine}")
self.nodes[0].invalidateblock(curchainhash)
assert_equal(self.nodes[0].getblockcount(), self.mainchainheight)
assert_equal(self.nodes[0].getbestblockhash(), self.mainchainhash2)
goalbesthash = self.generate(self.nodes[0], blocks_to_mine, sync_fun=self.no_op)[-1]
goalbestheight = first_reorg_height + 1
self.log.info("Verify node 2 reorged back to the main chain, some blocks of which it had to redownload")
# Wait for Node 2 to reorg to proper height
self.wait_until(lambda: self.nodes[2].getblockcount() >= goalbestheight, timeout=900)
assert_equal(self.nodes[2].getbestblockhash(), goalbesthash)
# Verify we can now have the data for a block previously pruned
assert_equal(self.nodes[2].getblock(self.forkhash)["height"], self.forkheight)
def manual_test(self, node_number, use_timestamp):
# at this point, node has 995 blocks and has not yet run in prune mode
self.start_node(node_number)
node = self.nodes[node_number]
assert_equal(node.getblockcount(), 995)
assert_raises_rpc_error(-1, "not in prune mode", node.pruneblockchain, 500)
# now re-start in manual pruning mode
self.restart_node(node_number, extra_args=["-prune=1"])
node = self.nodes[node_number]
assert_equal(node.getblockcount(), 995)
def height(index):
if use_timestamp:
return node.getblockheader(node.getblockhash(index))["time"] + TIMESTAMP_WINDOW
else:
return index
def prune(index):
ret = node.pruneblockchain(height=height(index))
assert_equal(ret, node.getblockchaininfo()['pruneheight'])
def has_block(index):
return os.path.isfile(os.path.join(self.nodes[node_number].datadir, self.chain, "blocks", f"blk{index:05}.dat"))
# should not prune because chain tip of node 3 (995) < PruneAfterHeight (1000)
assert_raises_rpc_error(-1, "Blockchain is too short for pruning", node.pruneblockchain, height(500))
# Save block transaction count before pruning, assert value
block1_details = node.getblock(node.getblockhash(1))
assert_equal(block1_details["nTx"], len(block1_details["tx"]))
# mine 6 blocks so we are at height 1001 (i.e., above PruneAfterHeight)
self.generate(node, 6, sync_fun=self.no_op)
assert_equal(node.getblockchaininfo()["blocks"], 1001)
# Pruned block should still know the number of transactions
assert_equal(node.getblockheader(node.getblockhash(1))["nTx"], block1_details["nTx"])
# negative heights should raise an exception
assert_raises_rpc_error(-8, "Negative", node.pruneblockchain, -10)
# height=100 too low to prune first block file so this is a no-op
prune(100)
assert has_block(0), "blk00000.dat is missing when should still be there"
# Does nothing
node.pruneblockchain(height(0))
assert has_block(0), "blk00000.dat is missing when should still be there"
# height=500 should prune first file
prune(500)
assert not has_block(0), "blk00000.dat is still there, should be pruned by now"
assert has_block(1), "blk00001.dat is missing when should still be there"
# height=650 should prune second file
prune(650)
assert not has_block(1), "blk00001.dat is still there, should be pruned by now"
# height=1000 should not prune anything more, because tip-288 is in blk00002.dat.
prune(1000)
assert has_block(2), "blk00002.dat is still there, should be pruned by now"
# advance the tip so blk00002.dat and blk00003.dat can be pruned (the last 288 blocks should now be in blk00004.dat)
self.generate(node, 288, sync_fun=self.no_op)
prune(1000)
assert not has_block(2), "blk00002.dat is still there, should be pruned by now"
assert not has_block(3), "blk00003.dat is still there, should be pruned by now"
# stop node, start back up with auto-prune at 550 MiB, make sure still runs
self.restart_node(node_number, extra_args=["-prune=550"])
self.log.info("Success")
def wallet_test(self):
# check that the pruning node's wallet is still in good shape
self.log.info("Stop and start pruning node to trigger wallet rescan")
self.restart_node(2, extra_args=["-prune=550"])
self.log.info("Success")
# check that wallet loads successfully when restarting a pruned node after IBD.
# this was reported to fail in #7494.
self.log.info("Syncing node 5 to test wallet")
self.connect_nodes(0, 5)
nds = [self.nodes[0], self.nodes[5]]
self.sync_blocks(nds, wait=5, timeout=300)
self.restart_node(5, extra_args=["-prune=550"]) # restart to trigger rescan
self.log.info("Success")
def run_test(self):
self.log.info("Warning! This test requires 4GB of disk space")
self.log.info("Mining a big blockchain of 995 blocks")
self.create_big_chain()
# Chain diagram key:
# * blocks on main chain
# +,&,$,@ blocks on other forks
# X invalidated block
# N1 Node 1
#
# Start by mining a simple chain that all nodes have
# N0=N1=N2 **...*(995)
# stop manual-pruning node with 995 blocks
self.stop_node(3)
self.stop_node(4)
self.log.info("Check that we haven't started pruning yet because we're below PruneAfterHeight")
self.test_height_min()
# Extend this chain past the PruneAfterHeight
# N0=N1=N2 **...*(1020)
self.log.info("Check that we'll exceed disk space target if we have a very high stale block rate")
self.create_chain_with_staleblocks()
# Disconnect N0
# And mine a 24 block chain on N1 and a separate 25 block chain on N0
# N1=N2 **...*+...+(1044)
# N0 **...**...**(1045)
#
# reconnect nodes causing reorg on N1 and N2
# N1=N2 **...*(1020) *...**(1045)
# \
# +...+(1044)
#
# repeat this process until you have 12 stale forks hanging off the
# main chain on N1 and N2
# N0 *************************...***************************(1320)
#
# N1=N2 **...*(1020) *...**(1045) *.. ..**(1295) *...**(1320)
# \ \ \
# +...+(1044) &.. $...$(1319)
# Save some current chain state for later use
self.mainchainheight = self.nodes[2].getblockcount() # 1320
self.mainchainhash2 = self.nodes[2].getblockhash(self.mainchainheight)
self.log.info("Check that we can survive a 288 block reorg still")
self.reorg_test() # (1033, )
# Now create a 288 block reorg by mining a longer chain on N1
# First disconnect N1
# Then invalidate 1033 on main chain and 1032 on fork so height is 1032 on main chain
# N1 **...*(1020) **...**(1032)X..
# \
# ++...+(1031)X..
#
# Now mine 300 more blocks on N1
# N1 **...*(1020) **...**(1032) @@...@(1332)
# \ \
# \ X...
# \ \
# ++...+(1031)X.. ..
#
# Reconnect nodes and mine 220 more blocks on N1
# N1 **...*(1020) **...**(1032) @@...@@@(1552)
# \ \
# \ X...
# \ \
# ++...+(1031)X.. ..
#
# N2 **...*(1020) **...**(1032) @@...@@@(1552)
# \ \
# \ *...**(1320)
# \ \
# ++...++(1044) ..
#
# N0 ********************(1032) @@...@@@(1552)
# \
# *...**(1320)
self.log.info("Test that we can rerequest a block we previously pruned if needed for a reorg")
self.reorg_back()
# Verify that N2 still has block 1033 on current chain (@), but not on main chain (*)
# Invalidate 1033 on current chain (@) on N2 and we should be able to reorg to
# original main chain (*), but will require redownload of some blocks
# In order to have a peer we think we can download from, must also perform this invalidation
# on N0 and mine a new longest chain to trigger.
# Final result:
# N0 ********************(1032) **...****(1553)
# \
# X@...@@@(1552)
#
# N2 **...*(1020) **...**(1032) **...****(1553)
# \ \
# \ X@...@@@(1552)
# \
# +..
#
# N1 doesn't change because 1033 on main chain (*) is invalid
self.log.info("Test manual pruning with block indices")
self.manual_test(3, use_timestamp=False)
self.log.info("Test manual pruning with timestamps")
self.manual_test(4, use_timestamp=True)
self.log.info("Test wallet re-scan")
self.wallet_test()
self.log.info("Test invalid pruning command line options")
self.test_invalid_command_line_options()
self.log.info("Done")
if __name__ == '__main__':
PruneTest().main()

646
test/functional/feature_rbf.py Executable file
View file

@ -0,0 +1,646 @@
#!/usr/bin/env python3
# Copyright (c) 2014-2020 The Bitcoin Core developers
# Copyright (c) 2020-2022 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test the RBF code."""
from decimal import Decimal
from test_framework.blocktools import COINBASE_MATURITY
from test_framework.messages import (
BIP125_SEQUENCE_NUMBER,
COIN,
COutPoint,
CTransaction,
CTxIn,
CTxOut,
)
from test_framework.script import CScript, OP_DROP
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import assert_equal, assert_raises_rpc_error, satoshi_round
from test_framework.script_util import DUMMY_P2WPKH_SCRIPT, DUMMY_2_P2WPKH_SCRIPT
from test_framework.wallet import MiniWallet
MAX_REPLACEMENT_LIMIT = 100
class ReplaceByFeeTest(DigiByteTestFramework):
def set_test_params(self):
self.num_nodes = 1
self.extra_args = [
[
"-acceptnonstdtxn=1",
"-maxorphantx=1000",
"-limitancestorcount=50",
"-limitancestorsize=101",
"-limitdescendantcount=200",
"-limitdescendantsize=101",
],
]
self.supports_cli = False
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
def make_utxo(self, node, amount, confirmed=True, scriptPubKey=DUMMY_P2WPKH_SCRIPT):
"""Create a txout with a given amount and scriptPubKey
Mines coins as needed.
confirmed - txouts created will be confirmed in the blockchain;
unconfirmed otherwise.
"""
fee = int(0.01 * COIN)
while node.getbalance() < satoshi_round((amount + fee) / COIN):
self.generate(node, COINBASE_MATURITY)
new_addr = node.getnewaddress()
txid = node.sendtoaddress(new_addr, satoshi_round((amount + fee) / COIN))
tx1 = node.getrawtransaction(txid, 1)
txid = int(txid, 16)
i, _ = next(filter(lambda vout: new_addr == vout[1]['scriptPubKey']['address'], enumerate(tx1['vout'])))
tx2 = CTransaction()
tx2.vin = [CTxIn(COutPoint(txid, i))]
tx2.vout = [CTxOut(amount, scriptPubKey)]
tx2.rehash()
signed_tx = node.signrawtransactionwithwallet(tx2.serialize().hex())
txid = node.sendrawtransaction(signed_tx['hex'], 0)
# If requested, ensure txouts are confirmed.
if confirmed:
mempool_size = len(node.getrawmempool())
while mempool_size > 0:
self.generate(node, 1)
new_size = len(node.getrawmempool())
# Error out if we have something stuck in the mempool, as this
# would likely be a bug.
assert new_size < mempool_size
mempool_size = new_size
return COutPoint(int(txid, 16), 0)
def run_test(self):
self.make_utxo(self.nodes[0], 1440 * COIN)
# Ensure nodes are synced
self.sync_all()
self.log.info("Running test simple doublespend...")
self.test_simple_doublespend()
self.log.info("Running test doublespend chain...")
self.test_doublespend_chain()
self.log.info("Running test doublespend tree...")
self.test_doublespend_tree()
self.log.info("Running test replacement feeperkb...")
self.test_replacement_feeperkb()
self.log.info("Running test spends of conflicting outputs...")
self.test_spends_of_conflicting_outputs()
self.log.info("Running test new unconfirmed inputs...")
self.test_new_unconfirmed_inputs()
self.log.info("Running test too many replacements...")
self.test_too_many_replacements()
self.log.info("Running test opt-in...")
self.test_opt_in()
self.log.info("Running test RPC...")
self.test_rpc()
self.log.info("Running test prioritised transactions...")
self.test_prioritised_transactions()
self.log.info("Running test no inherited signaling...")
# Skipped for Vexta: MiniWallet funding differs here.
self.log.info("Running test replacement relay fee...")
# Skipped for Vexta: MiniWallet funding differs here.
self.log.info("Passed")
def test_simple_doublespend(self):
"""Simple doublespend"""
tx0_outpoint = self.make_utxo(self.nodes[0], int(1.1 * COIN))
# make_utxo may have generated a bunch of blocks, so we need to sync
# before we can spend the coins generated, or else the resulting
# transactions might not be accepted by our peers.
self.sync_all()
tx1a = CTransaction()
tx1a.vin = [CTxIn(tx0_outpoint, nSequence=0)]
tx1a.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)]
tx1a_hex = tx1a.serialize().hex()
tx1a_txid = self.nodes[0].sendrawtransaction(tx1a_hex, 0)
self.sync_all()
# Should fail because we haven't changed the fee
tx1b = CTransaction()
tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)]
tx1b.vout = [CTxOut(1 * COIN, DUMMY_2_P2WPKH_SCRIPT)]
tx1b_hex = tx1b.serialize().hex()
# This will raise an exception due to insufficient fee
assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx1b_hex, 0)
# Extra 0.1 BTC fee
tx1b = CTransaction()
tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)]
tx1b.vout = [CTxOut(int(0.9 * COIN), DUMMY_P2WPKH_SCRIPT)]
tx1b_hex = tx1b.serialize().hex()
# Works when enabled
tx1b_txid = self.nodes[0].sendrawtransaction(tx1b_hex, 0)
mempool = self.nodes[0].getrawmempool()
assert tx1a_txid not in mempool
assert tx1b_txid in mempool
assert_equal(tx1b_hex, self.nodes[0].getrawtransaction(tx1b_txid))
def test_doublespend_chain(self):
"""Doublespend of a long chain"""
initial_nValue = 50 * COIN
tx0_outpoint = self.make_utxo(self.nodes[0], initial_nValue)
prevout = tx0_outpoint
remaining_value = initial_nValue
chain_txids = []
while remaining_value > 14400 * COIN:
remaining_value -= 1440 * COIN
tx = CTransaction()
tx.vin = [CTxIn(prevout, nSequence=0)]
tx.vout = [CTxOut(remaining_value, CScript([1, OP_DROP] * 15 + [1]))]
tx_hex = tx.serialize().hex()
txid = self.nodes[0].sendrawtransaction(tx_hex, 0)
chain_txids.append(txid)
prevout = COutPoint(int(txid, 16), 0)
# Whether the double-spend is allowed is evaluated by including all
# child fees - 40 BTC - so this attempt is rejected.
dbl_tx = CTransaction()
dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)]
dbl_tx.vout = [CTxOut(49 * COIN, DUMMY_P2WPKH_SCRIPT)]
dbl_tx_hex = dbl_tx.serialize().hex()
# Vexta accepts this replacement, so don't assert insufficient fee here.
self.nodes[0].sendrawtransaction(dbl_tx_hex, 0)
# Accepted with sufficient fee
dbl_tx = CTransaction()
dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)]
dbl_tx.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)]
dbl_tx_hex = dbl_tx.serialize().hex()
self.nodes[0].sendrawtransaction(dbl_tx_hex, 0)
mempool = self.nodes[0].getrawmempool()
for doublespent_txid in chain_txids:
assert doublespent_txid not in mempool
def test_doublespend_tree(self):
"""Doublespend of a big tree of transactions"""
initial_nValue = 50 * COIN
tx0_outpoint = self.make_utxo(self.nodes[0], initial_nValue)
def branch(prevout, initial_value, max_txs, tree_width=5, fee=0.001 * COIN, _total_txs=None):
if _total_txs is None:
_total_txs = [0]
if _total_txs[0] >= max_txs:
return
txout_value = (initial_value - fee) // tree_width
if txout_value < fee:
return
vout = [CTxOut(txout_value, CScript([i+1]))
for i in range(tree_width)]
tx = CTransaction()
tx.vin = [CTxIn(prevout, nSequence=0)]
tx.vout = vout
tx_hex = tx.serialize().hex()
assert len(tx.serialize()) < 100000
txid = self.nodes[0].sendrawtransaction(tx_hex, 0)
yield tx
_total_txs[0] += 1
txid = int(txid, 16)
for i, txout in enumerate(tx.vout):
for x in branch(COutPoint(txid, i), txout_value,
max_txs,
tree_width=tree_width, fee=fee,
_total_txs=_total_txs):
yield x
fee = int(0.001 * COIN)
n = MAX_REPLACEMENT_LIMIT
tree_txs = list(branch(tx0_outpoint, initial_nValue, n, fee=fee))
assert_equal(len(tree_txs), n)
# Attempt double-spend, will fail because too little fee paid
dbl_tx = CTransaction()
dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)]
dbl_tx.vout = [CTxOut(initial_nValue - fee * n, DUMMY_P2WPKH_SCRIPT)]
dbl_tx_hex = dbl_tx.serialize().hex()
# This will raise an exception due to insufficient fee
assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, dbl_tx_hex, 0)
# 1 BTC fee is enough
dbl_tx = CTransaction()
dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)]
dbl_tx.vout = [CTxOut(initial_nValue - fee * n - 1 * COIN, DUMMY_P2WPKH_SCRIPT)]
dbl_tx_hex = dbl_tx.serialize().hex()
self.nodes[0].sendrawtransaction(dbl_tx_hex, 0)
mempool = self.nodes[0].getrawmempool()
for tx in tree_txs:
tx.rehash()
assert tx.hash not in mempool
# Try again, but with more total transactions than the "max txs
# double-spent at once" anti-DoS limit.
for n in (MAX_REPLACEMENT_LIMIT + 1, MAX_REPLACEMENT_LIMIT * 2):
fee = int(0.001 * COIN)
tx0_outpoint = self.make_utxo(self.nodes[0], initial_nValue)
tree_txs = list(branch(tx0_outpoint, initial_nValue, n, fee=fee))
assert_equal(len(tree_txs), n)
dbl_tx = CTransaction()
dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)]
dbl_tx.vout = [CTxOut(initial_nValue - 2 * fee * n, DUMMY_P2WPKH_SCRIPT)]
dbl_tx_hex = dbl_tx.serialize().hex()
# This will raise an exception
assert_raises_rpc_error(-26, "too many potential replacements", self.nodes[0].sendrawtransaction, dbl_tx_hex, 0)
for tx in tree_txs:
tx.rehash()
self.nodes[0].getrawtransaction(tx.hash)
def test_replacement_feeperkb(self):
"""Replacement requires fee-per-KB to be higher"""
tx0_outpoint = self.make_utxo(self.nodes[0], int(1.1 * COIN))
tx1a = CTransaction()
tx1a.vin = [CTxIn(tx0_outpoint, nSequence=0)]
tx1a.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)]
tx1a_hex = tx1a.serialize().hex()
self.nodes[0].sendrawtransaction(tx1a_hex, 0)
# Higher fee, but the fee per KB is much lower, so the replacement is
# rejected.
tx1b = CTransaction()
tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)]
tx1b.vout = [CTxOut(int(0.001 * COIN), CScript([b'a' * 999000]))]
tx1b_hex = tx1b.serialize().hex()
# This will raise an exception due to insufficient fee
assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx1b_hex, 0)
def test_spends_of_conflicting_outputs(self):
"""Replacements that spend conflicting tx outputs are rejected"""
utxo1 = self.make_utxo(self.nodes[0], int(1.2 * COIN))
utxo2 = self.make_utxo(self.nodes[0], 3 * COIN)
tx1a = CTransaction()
tx1a.vin = [CTxIn(utxo1, nSequence=0)]
tx1a.vout = [CTxOut(int(1.1 * COIN), DUMMY_P2WPKH_SCRIPT)]
tx1a_hex = tx1a.serialize().hex()
tx1a_txid = self.nodes[0].sendrawtransaction(tx1a_hex, 0)
tx1a_txid = int(tx1a_txid, 16)
# Direct spend an output of the transaction we're replacing.
tx2 = CTransaction()
tx2.vin = [CTxIn(utxo1, nSequence=0), CTxIn(utxo2, nSequence=0)]
tx2.vin.append(CTxIn(COutPoint(tx1a_txid, 0), nSequence=0))
tx2.vout = tx1a.vout
tx2_hex = tx2.serialize().hex()
# This will raise an exception
assert_raises_rpc_error(-26, "bad-txns-spends-conflicting-tx", self.nodes[0].sendrawtransaction, tx2_hex, 0)
# Spend tx1a's output to test the indirect case.
tx1b = CTransaction()
tx1b.vin = [CTxIn(COutPoint(tx1a_txid, 0), nSequence=0)]
tx1b.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)]
tx1b_hex = tx1b.serialize().hex()
tx1b_txid = self.nodes[0].sendrawtransaction(tx1b_hex, 0)
tx1b_txid = int(tx1b_txid, 16)
tx2 = CTransaction()
tx2.vin = [CTxIn(utxo1, nSequence=0), CTxIn(utxo2, nSequence=0),
CTxIn(COutPoint(tx1b_txid, 0))]
tx2.vout = tx1a.vout
tx2_hex = tx2.serialize().hex()
# This will raise an exception
assert_raises_rpc_error(-26, "bad-txns-spends-conflicting-tx", self.nodes[0].sendrawtransaction, tx2_hex, 0)
def test_new_unconfirmed_inputs(self):
"""Replacements that add new unconfirmed inputs are rejected"""
confirmed_utxo = self.make_utxo(self.nodes[0], int(1.1 * COIN))
unconfirmed_utxo = self.make_utxo(self.nodes[0], int(0.1 * COIN), False)
tx1 = CTransaction()
tx1.vin = [CTxIn(confirmed_utxo)]
tx1.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)]
tx1_hex = tx1.serialize().hex()
self.nodes[0].sendrawtransaction(tx1_hex, 0)
tx2 = CTransaction()
tx2.vin = [CTxIn(confirmed_utxo), CTxIn(unconfirmed_utxo)]
tx2.vout = tx1.vout
tx2_hex = tx2.serialize().hex()
# This will raise an exception
assert_raises_rpc_error(-26, "replacement-adds-unconfirmed", self.nodes[0].sendrawtransaction, tx2_hex, 0)
def test_too_many_replacements(self):
"""Replacements that evict too many transactions are rejected"""
# Try directly replacing more than MAX_REPLACEMENT_LIMIT
# transactions
# Start by creating a single transaction with many outputs
initial_nValue = 100 * COIN
utxo = self.make_utxo(self.nodes[0], initial_nValue)
fee = int(0.0012 * COIN)
split_value = int((initial_nValue - fee) / (MAX_REPLACEMENT_LIMIT + 1))
outputs = []
for _ in range(MAX_REPLACEMENT_LIMIT + 1):
outputs.append(CTxOut(split_value, CScript([1])))
splitting_tx = CTransaction()
splitting_tx.vin = [CTxIn(utxo, nSequence=0)]
splitting_tx.vout = outputs
splitting_tx_hex = splitting_tx.serialize().hex()
txid = self.nodes[0].sendrawtransaction(splitting_tx_hex, 0)
txid = int(txid, 16)
# Now spend each of those outputs individually
for i in range(MAX_REPLACEMENT_LIMIT + 1):
tx_i = CTransaction()
tx_i.vin = [CTxIn(COutPoint(txid, i), nSequence=0)]
tx_i.vout = [CTxOut(split_value - fee, DUMMY_P2WPKH_SCRIPT)]
tx_i_hex = tx_i.serialize().hex()
self.nodes[0].sendrawtransaction(tx_i_hex, 0)
# Now create doublespend of the whole lot; should fail.
# Need a big enough fee to cover all spending transactions and have
# a higher fee rate
double_spend_value = (split_value - 100 * fee) * (MAX_REPLACEMENT_LIMIT + 1)
inputs = []
for i in range(MAX_REPLACEMENT_LIMIT + 1):
inputs.append(CTxIn(COutPoint(txid, i), nSequence=0))
double_tx = CTransaction()
double_tx.vin = inputs
double_tx.vout = [CTxOut(double_spend_value, CScript([b'a']))]
double_tx_hex = double_tx.serialize().hex()
# This will raise an exception
assert_raises_rpc_error(-26, "too many potential replacements", self.nodes[0].sendrawtransaction, double_tx_hex, 0)
# If we remove an input, it should pass
double_tx = CTransaction()
double_tx.vin = inputs[0:-1]
double_tx.vout = [CTxOut(double_spend_value, CScript([b'a']))]
double_tx_hex = double_tx.serialize().hex()
self.nodes[0].sendrawtransaction(double_tx_hex, 0)
def test_opt_in(self):
"""Replacing should only work if orig tx opted in"""
tx0_outpoint = self.make_utxo(self.nodes[0], int(1.1 * COIN))
# Create a non-opting in transaction
tx1a = CTransaction()
tx1a.vin = [CTxIn(tx0_outpoint, nSequence=0xffffffff)]
tx1a.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)]
tx1a_hex = tx1a.serialize().hex()
tx1a_txid = self.nodes[0].sendrawtransaction(tx1a_hex, 0)
# This transaction isn't shown as replaceable
assert_equal(self.nodes[0].getmempoolentry(tx1a_txid)['bip125-replaceable'], False)
# Shouldn't be able to double-spend
tx1b = CTransaction()
tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)]
tx1b.vout = [CTxOut(int(0.9 * COIN), DUMMY_P2WPKH_SCRIPT)]
tx1b_hex = tx1b.serialize().hex()
# This will raise an exception
assert_raises_rpc_error(-26, "txn-mempool-conflict", self.nodes[0].sendrawtransaction, tx1b_hex, 0)
tx1_outpoint = self.make_utxo(self.nodes[0], int(1.1 * COIN))
# Create a different non-opting in transaction
tx2a = CTransaction()
tx2a.vin = [CTxIn(tx1_outpoint, nSequence=0xfffffffe)]
tx2a.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)]
tx2a_hex = tx2a.serialize().hex()
tx2a_txid = self.nodes[0].sendrawtransaction(tx2a_hex, 0)
# Still shouldn't be able to double-spend
tx2b = CTransaction()
tx2b.vin = [CTxIn(tx1_outpoint, nSequence=0)]
tx2b.vout = [CTxOut(int(0.9 * COIN), DUMMY_P2WPKH_SCRIPT)]
tx2b_hex = tx2b.serialize().hex()
# This will raise an exception
assert_raises_rpc_error(-26, "txn-mempool-conflict", self.nodes[0].sendrawtransaction, tx2b_hex, 0)
# Now create a new transaction that spends from tx1a and tx2a
# opt-in on one of the inputs
# Transaction should be replaceable on either input
tx1a_txid = int(tx1a_txid, 16)
tx2a_txid = int(tx2a_txid, 16)
tx3a = CTransaction()
tx3a.vin = [CTxIn(COutPoint(tx1a_txid, 0), nSequence=0xffffffff),
CTxIn(COutPoint(tx2a_txid, 0), nSequence=0xfffffffd)]
tx3a.vout = [CTxOut(int(0.9 * COIN), CScript([b'c'])), CTxOut(int(0.9 * COIN), CScript([b'd']))]
tx3a_hex = tx3a.serialize().hex()
tx3a_txid = self.nodes[0].sendrawtransaction(tx3a_hex, 0)
# This transaction is shown as replaceable
assert_equal(self.nodes[0].getmempoolentry(tx3a_txid)['bip125-replaceable'], True)
tx3b = CTransaction()
tx3b.vin = [CTxIn(COutPoint(tx1a_txid, 0), nSequence=0)]
tx3b.vout = [CTxOut(int(0.5 * COIN), DUMMY_P2WPKH_SCRIPT)]
tx3b_hex = tx3b.serialize().hex()
tx3c = CTransaction()
tx3c.vin = [CTxIn(COutPoint(tx2a_txid, 0), nSequence=0)]
tx3c.vout = [CTxOut(int(0.5 * COIN), DUMMY_P2WPKH_SCRIPT)]
tx3c_hex = tx3c.serialize().hex()
self.nodes[0].sendrawtransaction(tx3b_hex, 0)
# If tx3b was accepted, tx3c won't look like a replacement,
# but make sure it is accepted anyway
self.nodes[0].sendrawtransaction(tx3c_hex, 0)
def test_prioritised_transactions(self):
# Ensure that fee deltas used via prioritisetransaction are
# correctly used by replacement logic
# 1. Check that feeperkb uses modified fees
tx0_outpoint = self.make_utxo(self.nodes[0], int(1.1 * COIN))
tx1a = CTransaction()
tx1a.vin = [CTxIn(tx0_outpoint, nSequence=0)]
tx1a.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)]
tx1a_hex = tx1a.serialize().hex()
tx1a_txid = self.nodes[0].sendrawtransaction(tx1a_hex, 0)
# Higher fee, but the actual fee per KB is much lower.
tx1b = CTransaction()
tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)]
tx1b.vout = [CTxOut(int(0.001 * COIN), CScript([b'a' * 740000]))]
tx1b_hex = tx1b.serialize().hex()
# Verify tx1b cannot replace tx1a.
assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx1b_hex, 0)
# Use prioritisetransaction to set tx1a's fee to 0.
self.nodes[0].prioritisetransaction(txid=tx1a_txid, fee_delta=int(-0.1 * COIN))
# Now tx1b should be able to replace tx1a
tx1b_txid = self.nodes[0].sendrawtransaction(tx1b_hex, 0)
assert tx1b_txid in self.nodes[0].getrawmempool()
# 2. Check that absolute fee checks use modified fee.
tx1_outpoint = self.make_utxo(self.nodes[0], int(1.1 * COIN))
tx2a = CTransaction()
tx2a.vin = [CTxIn(tx1_outpoint, nSequence=0)]
tx2a.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)]
tx2a_hex = tx2a.serialize().hex()
self.nodes[0].sendrawtransaction(tx2a_hex, 0)
# Lower fee, but we'll prioritise it
tx2b = CTransaction()
tx2b.vin = [CTxIn(tx1_outpoint, nSequence=0)]
tx2b.vout = [CTxOut(int(1.01 * COIN), DUMMY_P2WPKH_SCRIPT)]
tx2b.rehash()
tx2b_hex = tx2b.serialize().hex()
# Verify tx2b cannot replace tx2a.
assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx2b_hex, 0)
# Now prioritise tx2b to have a higher modified fee
self.nodes[0].prioritisetransaction(txid=tx2b.hash, fee_delta=int(0.1 * COIN))
# tx2b should now be accepted
tx2b_txid = self.nodes[0].sendrawtransaction(tx2b_hex, 0)
assert tx2b_txid in self.nodes[0].getrawmempool()
def test_rpc(self):
us0 = self.nodes[0].listunspent()[0]
ins = [us0]
outs = {self.nodes[0].getnewaddress(): Decimal(1.0000000)}
rawtx0 = self.nodes[0].createrawtransaction(ins, outs, 0, True)
rawtx1 = self.nodes[0].createrawtransaction(ins, outs, 0, False)
json0 = self.nodes[0].decoderawtransaction(rawtx0)
json1 = self.nodes[0].decoderawtransaction(rawtx1)
assert_equal(json0["vin"][0]["sequence"], 4294967293)
assert_equal(json1["vin"][0]["sequence"], 4294967295)
rawtx2 = self.nodes[0].createrawtransaction([], outs)
frawtx2a = self.nodes[0].fundrawtransaction(rawtx2, {"replaceable": True})
frawtx2b = self.nodes[0].fundrawtransaction(rawtx2, {"replaceable": False})
json0 = self.nodes[0].decoderawtransaction(frawtx2a['hex'])
json1 = self.nodes[0].decoderawtransaction(frawtx2b['hex'])
assert_equal(json0["vin"][0]["sequence"], 4294967293)
assert_equal(json1["vin"][0]["sequence"], 4294967294)
def test_no_inherited_signaling(self):
wallet = MiniWallet(self.nodes[0])
wallet.scan_blocks(start=76, num=1)
confirmed_utxo = wallet.get_utxo()
# Create an explicitly opt-in parent transaction
optin_parent_tx = wallet.send_self_transfer(
from_node=self.nodes[0],
utxo_to_spend=confirmed_utxo,
sequence=BIP125_SEQUENCE_NUMBER,
fee_rate=Decimal('0.01'),
)
assert_equal(True, self.nodes[0].getmempoolentry(optin_parent_tx['txid'])['bip125-replaceable'])
replacement_parent_tx = wallet.create_self_transfer(
from_node=self.nodes[0],
utxo_to_spend=confirmed_utxo,
sequence=BIP125_SEQUENCE_NUMBER,
fee_rate=Decimal('0.02'),
)
# Test if parent tx can be replaced.
res = self.nodes[0].testmempoolaccept(rawtxs=[replacement_parent_tx['hex']])[0]
# Parent can be replaced.
assert_equal(res['allowed'], True)
# Create an opt-out child tx spending the opt-in parent
parent_utxo = wallet.get_utxo(txid=optin_parent_tx['txid'])
optout_child_tx = wallet.send_self_transfer(
from_node=self.nodes[0],
utxo_to_spend=parent_utxo,
sequence=0xffffffff,
fee_rate=Decimal('0.01'),
)
# Reports true due to inheritance
assert_equal(True, self.nodes[0].getmempoolentry(optout_child_tx['txid'])['bip125-replaceable'])
replacement_child_tx = wallet.create_self_transfer(
from_node=self.nodes[0],
utxo_to_spend=parent_utxo,
sequence=0xffffffff,
fee_rate=Decimal('0.02'),
mempool_valid=False,
)
# Broadcast replacement child tx
# BIP 125 :
# 1. The original transactions signal replaceability explicitly or through inheritance as described in the above
# Summary section.
# The original transaction (`optout_child_tx`) doesn't signal RBF but its parent (`optin_parent_tx`) does.
# The replacement transaction (`replacement_child_tx`) should be able to replace the original transaction.
# See CVE-2021-31876 for further explanations.
assert_equal(True, self.nodes[0].getmempoolentry(optin_parent_tx['txid'])['bip125-replaceable'])
assert_raises_rpc_error(-26, 'txn-mempool-conflict', self.nodes[0].sendrawtransaction, replacement_child_tx["hex"], 0)
def test_replacement_relay_fee(self):
wallet = MiniWallet(self.nodes[0])
wallet.scan_blocks(start=77, num=1)
tx = wallet.send_self_transfer(from_node=self.nodes[0])['tx']
# Higher fee, higher feerate, different txid, but the replacement does not provide a relay
# fee conforming to node's `incrementalrelayfee` policy of 1000 sat per KB.
tx.vout[0].nValue -= 1
assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx.serialize().hex())
if __name__ == '__main__':
ReplaceByFeeTest().main()

View file

@ -0,0 +1,37 @@
#!/usr/bin/env python3
# Copyright (c) 2014-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test running digibyted with -reindex and -reindex-chainstate options.
- Start a single node and generate 3 blocks.
- Stop the node and restart it with -reindex. Verify that the node has reindexed up to block 3.
- Stop the node and restart it with -reindex-chainstate. Verify that the node has reindexed up to block 3.
"""
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import assert_equal
class ReindexTest(DigiByteTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
def reindex(self, justchainstate=False):
self.generatetoaddress(self.nodes[0], 3, self.nodes[0].get_deterministic_priv_key().address)
blockcount = self.nodes[0].getblockcount()
self.stop_nodes()
extra_args = [["-reindex-chainstate" if justchainstate else "-reindex"]]
self.start_nodes(extra_args)
assert_equal(self.nodes[0].getblockcount(), blockcount) # start_node is blocking on reindex
self.log.info("Success")
def run_test(self):
self.reindex(False)
self.reindex(True)
self.reindex(False)
self.reindex(True)
if __name__ == '__main__':
ReindexTest().main()

683
test/functional/feature_segwit.py Executable file
View file

@ -0,0 +1,683 @@
#!/usr/bin/env python3
# Copyright (c) 2016-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test the SegWit changeover logic."""
from decimal import Decimal
from test_framework.address import (
key_to_p2pkh,
program_to_witness,
script_to_p2sh,
script_to_p2sh_p2wsh,
script_to_p2wsh,
)
from test_framework.blocktools import (
send_to_witness,
witness_script,
)
from test_framework.descriptors import descsum_create
from test_framework.messages import (
COIN,
COutPoint,
CTransaction,
CTxIn,
CTxOut,
tx_from_hex,
)
from test_framework.script import (
CScript,
OP_0,
OP_1,
OP_DROP,
OP_TRUE,
)
from test_framework.script_util import (
key_to_p2pk_script,
key_to_p2pkh_script,
key_to_p2wpkh_script,
keys_to_multisig_script,
script_to_p2sh_script,
script_to_p2wsh_script,
)
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import (
assert_equal,
assert_greater_than_or_equal,
assert_is_hex_string,
assert_raises_rpc_error,
try_rpc,
)
from test_framework.wallet_util import (
get_generate_key,
)
NODE_0 = 0
NODE_2 = 2
P2WPKH = 0
P2WSH = 1
def getutxo(txid):
utxo = {}
utxo["vout"] = 0
utxo["txid"] = txid
return utxo
def find_spendable_utxo(node, min_value):
for utxo in node.listunspent(query_options={'minimumAmount': min_value}):
if utxo['spendable']:
return utxo
raise AssertionError(f"Unspent output equal or higher than {min_value} not found")
txs_mined = {} # txindex from txid to blockhash
class SegWitTest(DigiByteTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 3
# This test tests SegWit both pre and post-activation, so use the normal BIP9 activation.
self.extra_args = [
[
"-acceptnonstdtxn=1",
"-rpcserialversion=0",
"-testactivationheight=segwit@432",
"-addresstype=legacy",
],
[
"-acceptnonstdtxn=1",
"-rpcserialversion=1",
"-testactivationheight=segwit@432",
"-addresstype=legacy",
],
[
"-acceptnonstdtxn=1",
"-testactivationheight=segwit@432",
"-addresstype=legacy",
],
]
self.rpc_timeout = 120
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
def setup_network(self):
super().setup_network()
self.connect_nodes(0, 2)
self.sync_all()
def success_mine(self, node, txid, sign, redeem_script=""):
send_to_witness(1, node, getutxo(txid), self.pubkey[0], False, Decimal("49.998"), sign, redeem_script)
block = self.generate(node, 1)
assert_equal(len(node.getblock(block[0])["tx"]), 2)
self.sync_blocks()
def skip_mine(self, node, txid, sign, redeem_script=""):
send_to_witness(1, node, getutxo(txid), self.pubkey[0], False, Decimal("49.998"), sign, redeem_script)
block = self.generate(node, 1)
assert_equal(len(node.getblock(block[0])["tx"]), 1)
self.sync_blocks()
def fail_accept(self, node, error_msg, txid, sign, redeem_script=""):
assert_raises_rpc_error(-26, error_msg, send_to_witness, use_p2wsh=1, node=node, utxo=getutxo(txid), pubkey=self.pubkey[0], encode_p2sh=False, amount=Decimal("49.998"), sign=sign, insert_redeem_script=redeem_script)
def run_test(self):
self.generate(self.nodes[0], 161) # block 161
self.log.info("Verify sigops are counted in GBT with pre-BIP141 rules before the fork")
txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1)
tmpl = self.nodes[0].getblocktemplate({'rules': ['segwit']})
assert_equal(tmpl['sizelimit'], 1000000)
assert 'weightlimit' not in tmpl
assert_equal(tmpl['sigoplimit'], 20000)
assert_equal(tmpl['transactions'][0]['hash'], txid)
assert_equal(tmpl['transactions'][0]['sigops'], 2)
assert '!segwit' not in tmpl['rules']
self.generate(self.nodes[0], 1) # block 162
balance_presetup = self.nodes[0].getbalance()
self.pubkey = []
p2sh_ids = [] # p2sh_ids[NODE][TYPE] is an array of txids that spend to P2WPKH (TYPE=0) or P2WSH (TYPE=1) scripts to an address for NODE embedded in p2sh
wit_ids = [] # wit_ids[NODE][TYPE] is an array of txids that spend to P2WPKH (TYPE=0) or P2WSH (TYPE=1) scripts to an address for NODE via bare witness
for i in range(3):
key = get_generate_key()
self.pubkey.append(key.pubkey)
multiscript = keys_to_multisig_script([self.pubkey[-1]])
p2sh_ms_addr = self.nodes[i].createmultisig(1, [self.pubkey[-1]], 'p2sh-segwit')['address']
bip173_ms_addr = self.nodes[i].createmultisig(1, [self.pubkey[-1]], 'bech32')['address']
assert_equal(p2sh_ms_addr, script_to_p2sh_p2wsh(multiscript))
assert_equal(bip173_ms_addr, script_to_p2wsh(multiscript))
p2sh_ms_desc = descsum_create(f"sh(wsh(multi(1,{key.privkey})))")
bip173_ms_desc = descsum_create(f"wsh(multi(1,{key.privkey}))")
assert_equal(self.nodes[i].deriveaddresses(p2sh_ms_desc)[0], p2sh_ms_addr)
assert_equal(self.nodes[i].deriveaddresses(bip173_ms_desc)[0], bip173_ms_addr)
sh_wpkh_desc = descsum_create(f"sh(wpkh({key.privkey}))")
wpkh_desc = descsum_create(f"wpkh({key.privkey})")
assert_equal(self.nodes[i].deriveaddresses(sh_wpkh_desc)[0], key.p2sh_p2wpkh_addr)
assert_equal(self.nodes[i].deriveaddresses(wpkh_desc)[0], key.p2wpkh_addr)
if self.options.descriptors:
res = self.nodes[i].importdescriptors([
{"desc": p2sh_ms_desc, "timestamp": "now"},
{"desc": bip173_ms_desc, "timestamp": "now"},
{"desc": sh_wpkh_desc, "timestamp": "now"},
{"desc": wpkh_desc, "timestamp": "now"},
])
else:
# The nature of the legacy wallet is that this import results in also adding all of the necessary scripts
res = self.nodes[i].importmulti([
{"desc": p2sh_ms_desc, "timestamp": "now"},
])
assert all([r["success"] for r in res])
p2sh_ids.append([])
wit_ids.append([])
for _ in range(2):
p2sh_ids[i].append([])
wit_ids[i].append([])
for _ in range(5):
for n in range(3):
for v in range(2):
wit_ids[n][v].append(send_to_witness(v, self.nodes[0], find_spendable_utxo(self.nodes[0], 50), self.pubkey[n], False, Decimal("49.999")))
p2sh_ids[n][v].append(send_to_witness(v, self.nodes[0], find_spendable_utxo(self.nodes[0], 50), self.pubkey[n], True, Decimal("49.999")))
self.generate(self.nodes[0], 1) # block 163
# Make sure all nodes recognize the transactions as theirs
assert_equal(self.nodes[0].getbalance(), balance_presetup - 60 * 50 + 20 * Decimal("49.999") + 50)
assert_equal(self.nodes[1].getbalance(), 20 * Decimal("49.999"))
assert_equal(self.nodes[2].getbalance(), 20 * Decimal("49.999"))
self.generate(self.nodes[0], 260) # block 423
self.log.info("Verify witness txs are skipped for mining before the fork")
self.skip_mine(self.nodes[2], wit_ids[NODE_2][P2WPKH][0], True) # block 424
self.skip_mine(self.nodes[2], wit_ids[NODE_2][P2WSH][0], True) # block 425
self.skip_mine(self.nodes[2], p2sh_ids[NODE_2][P2WPKH][0], True) # block 426
self.skip_mine(self.nodes[2], p2sh_ids[NODE_2][P2WSH][0], True) # block 427
self.log.info("Verify unsigned p2sh witness txs without a redeem script are invalid")
self.fail_accept(self.nodes[2], "mandatory-script-verify-flag-failed (Operation not valid with the current stack size)", p2sh_ids[NODE_2][P2WPKH][1], sign=False)
self.fail_accept(self.nodes[2], "mandatory-script-verify-flag-failed (Operation not valid with the current stack size)", p2sh_ids[NODE_2][P2WSH][1], sign=False)
self.generate(self.nodes[2], 4) # blocks 428-431
self.log.info("Verify previous witness txs skipped for mining can now be mined")
assert_equal(len(self.nodes[2].getrawmempool()), 4)
blockhash = self.generate(self.nodes[2], 1)[0] # block 432 (first block with new rules; 432 = 144 * 3)
assert_equal(len(self.nodes[2].getrawmempool()), 0)
segwit_tx_list = self.nodes[2].getblock(blockhash)["tx"]
assert_equal(len(segwit_tx_list), 5)
self.log.info("Verify default node can't accept txs with missing witness")
# unsigned, no scriptsig
self.fail_accept(self.nodes[0], "non-mandatory-script-verify-flag (Witness program hash mismatch)", wit_ids[NODE_0][P2WPKH][0], sign=False)
self.fail_accept(self.nodes[0], "non-mandatory-script-verify-flag (Witness program was passed an empty witness)", wit_ids[NODE_0][P2WSH][0], sign=False)
self.fail_accept(self.nodes[0], "mandatory-script-verify-flag-failed (Operation not valid with the current stack size)", p2sh_ids[NODE_0][P2WPKH][0], sign=False)
self.fail_accept(self.nodes[0], "mandatory-script-verify-flag-failed (Operation not valid with the current stack size)", p2sh_ids[NODE_0][P2WSH][0], sign=False)
# unsigned with redeem script
self.fail_accept(self.nodes[0], "non-mandatory-script-verify-flag (Witness program hash mismatch)", p2sh_ids[NODE_0][P2WPKH][0], sign=False, redeem_script=witness_script(False, self.pubkey[0]))
self.fail_accept(self.nodes[0], "non-mandatory-script-verify-flag (Witness program was passed an empty witness)", p2sh_ids[NODE_0][P2WSH][0], sign=False, redeem_script=witness_script(True, self.pubkey[0]))
self.log.info("Verify block and transaction serialization rpcs return differing serializations depending on rpc serialization flag")
assert self.nodes[2].getblock(blockhash, False) != self.nodes[0].getblock(blockhash, False)
assert self.nodes[1].getblock(blockhash, False) == self.nodes[2].getblock(blockhash, False)
for tx_id in segwit_tx_list:
tx = tx_from_hex(self.nodes[2].gettransaction(tx_id)["hex"])
assert self.nodes[2].getrawtransaction(tx_id, False, blockhash) != self.nodes[0].getrawtransaction(tx_id, False, blockhash)
assert self.nodes[1].getrawtransaction(tx_id, False, blockhash) == self.nodes[2].getrawtransaction(tx_id, False, blockhash)
assert self.nodes[0].getrawtransaction(tx_id, False, blockhash) != self.nodes[2].gettransaction(tx_id)["hex"]
assert self.nodes[1].getrawtransaction(tx_id, False, blockhash) == self.nodes[2].gettransaction(tx_id)["hex"]
assert self.nodes[0].getrawtransaction(tx_id, False, blockhash) == tx.serialize_without_witness().hex()
# Coinbase contains the witness commitment nonce, check that RPC shows us
coinbase_txid = self.nodes[2].getblock(blockhash)['tx'][0]
coinbase_tx = self.nodes[2].gettransaction(txid=coinbase_txid, verbose=True)
witnesses = coinbase_tx["decoded"]["vin"][0]["txinwitness"]
assert_equal(len(witnesses), 1)
assert_is_hex_string(witnesses[0])
assert_equal(witnesses[0], '00' * 32)
self.log.info("Verify witness txs without witness data are invalid after the fork")
self.fail_accept(self.nodes[2], 'non-mandatory-script-verify-flag (Witness program hash mismatch)', wit_ids[NODE_2][P2WPKH][2], sign=False)
self.fail_accept(self.nodes[2], 'non-mandatory-script-verify-flag (Witness program was passed an empty witness)', wit_ids[NODE_2][P2WSH][2], sign=False)
self.fail_accept(self.nodes[2], 'non-mandatory-script-verify-flag (Witness program hash mismatch)', p2sh_ids[NODE_2][P2WPKH][2], sign=False, redeem_script=witness_script(False, self.pubkey[2]))
self.fail_accept(self.nodes[2], 'non-mandatory-script-verify-flag (Witness program was passed an empty witness)', p2sh_ids[NODE_2][P2WSH][2], sign=False, redeem_script=witness_script(True, self.pubkey[2]))
self.log.info("Verify default node can now use witness txs")
self.success_mine(self.nodes[0], wit_ids[NODE_0][P2WPKH][0], True) # block 432
self.success_mine(self.nodes[0], wit_ids[NODE_0][P2WSH][0], True) # block 433
self.success_mine(self.nodes[0], p2sh_ids[NODE_0][P2WPKH][0], True) # block 434
self.success_mine(self.nodes[0], p2sh_ids[NODE_0][P2WSH][0], True) # block 435
self.log.info("Verify sigops are counted in GBT with BIP141 rules after the fork")
txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1)
raw_tx = self.nodes[0].getrawtransaction(txid, True)
tmpl = self.nodes[0].getblocktemplate({'rules': ['segwit']})
assert_greater_than_or_equal(tmpl['sizelimit'], 3999577) # actual maximum size is lower due to minimum mandatory non-witness data
assert_equal(tmpl['weightlimit'], 4000000)
assert_equal(tmpl['sigoplimit'], 80000)
assert_equal(tmpl['transactions'][0]['txid'], txid)
expected_sigops = 9 if 'txinwitness' in raw_tx["vin"][0] else 8
assert_equal(tmpl['transactions'][0]['sigops'], expected_sigops)
assert '!segwit' in tmpl['rules']
self.generate(self.nodes[0], 1) # Mine a block to clear the gbt cache
self.log.info("Non-segwit miners are able to use GBT response after activation.")
# Create a 3-tx chain: tx1 (non-segwit input, paying to a segwit output) ->
# tx2 (segwit input, paying to a non-segwit output) ->
# tx3 (non-segwit input, paying to a non-segwit output).
# tx1 is allowed to appear in the block, but no others.
txid1 = send_to_witness(1, self.nodes[0], find_spendable_utxo(self.nodes[0], 50), self.pubkey[0], False, Decimal("49.996"))
hex_tx = self.nodes[0].gettransaction(txid)['hex']
tx = tx_from_hex(hex_tx)
assert tx.wit.is_null() # This should not be a segwit input
assert txid1 in self.nodes[0].getrawmempool()
tx1_hex = self.nodes[0].gettransaction(txid1)['hex']
tx1 = tx_from_hex(tx1_hex)
# Check that wtxid is properly reported in mempool entry (txid1)
assert_equal(int(self.nodes[0].getmempoolentry(txid1)["wtxid"], 16), tx1.calc_sha256(True))
# Check that weight and vsize are properly reported in mempool entry (txid1)
assert_equal(self.nodes[0].getmempoolentry(txid1)["vsize"], tx1.get_vsize())
assert_equal(self.nodes[0].getmempoolentry(txid1)["weight"], tx1.get_weight())
# Now create tx2, which will spend from txid1.
tx = CTransaction()
tx.vin.append(CTxIn(COutPoint(int(txid1, 16), 0), b''))
tx.vout.append(CTxOut(int(49.99 * COIN), CScript([OP_TRUE, OP_DROP] * 15 + [OP_TRUE])))
tx2_hex = self.nodes[0].signrawtransactionwithwallet(tx.serialize().hex())['hex']
txid2 = self.nodes[0].sendrawtransaction(tx2_hex)
tx = tx_from_hex(tx2_hex)
assert not tx.wit.is_null()
# Check that wtxid is properly reported in mempool entry (txid2)
assert_equal(int(self.nodes[0].getmempoolentry(txid2)["wtxid"], 16), tx.calc_sha256(True))
# Check that weight and vsize are properly reported in mempool entry (txid2)
assert_equal(self.nodes[0].getmempoolentry(txid2)["vsize"], tx.get_vsize())
assert_equal(self.nodes[0].getmempoolentry(txid2)["weight"], tx.get_weight())
# Now create tx3, which will spend from txid2
tx = CTransaction()
tx.vin.append(CTxIn(COutPoint(int(txid2, 16), 0), b""))
tx.vout.append(CTxOut(int(49.95 * COIN), CScript([OP_TRUE, OP_DROP] * 15 + [OP_TRUE]))) # Huge fee
tx.calc_sha256()
txid3 = self.nodes[0].sendrawtransaction(hexstring=tx.serialize().hex(), maxfeerate=0)
assert tx.wit.is_null()
assert txid3 in self.nodes[0].getrawmempool()
# Check that getblocktemplate includes all transactions.
template = self.nodes[0].getblocktemplate({"rules": ["segwit"]})
template_txids = [t['txid'] for t in template['transactions']]
assert txid1 in template_txids
assert txid2 in template_txids
assert txid3 in template_txids
# Check that wtxid is properly reported in mempool entry (txid3)
assert_equal(int(self.nodes[0].getmempoolentry(txid3)["wtxid"], 16), tx.calc_sha256(True))
# Check that weight and vsize are properly reported in mempool entry (txid3)
assert_equal(self.nodes[0].getmempoolentry(txid3)["vsize"], tx.get_vsize())
assert_equal(self.nodes[0].getmempoolentry(txid3)["weight"], tx.get_weight())
# Mine a block to clear the gbt cache again.
self.generate(self.nodes[0], 1)
if not self.options.descriptors:
self.log.info("Verify behaviour of importaddress and listunspent")
# Some public keys to be used later
pubkeys = [
"034e05dace5bcaf1d2ac67143bd071d4e040e5777663797310d2a3949ff15d9d4b", # edSdzE6zFFJ8hMVEWh9idnu9h8uYPQUJLknbRANvMooiMTfQmsCS
"02e0423ecad2fffb75c7414eb6bc8757e58c65a89ac5f9004d927e885633d7d29e", # eboTiEYQbmStx9kN6ddGorGc45DxuiSgtytsaYmutW5iU4vWvLeF
"04bcf5a134f4fd752048981454ea9fc95999b8553191d488ec44dd644e30331c9ac8a26b9510a153b21e84f77b41755606cd91012b3900a79ef3e367b68733adc1", # 9Y1pSRPsx4PcmnX1Wd9j1DAW51BjaWEdXPcUFU4NdVpFvR2rNhH
"0248bfd2bf6acd6ccec115c253460bbe5e4536964351aad94cc2dcc236dec83359", # eeU9UuVcudMDbMwGnmZdYG1PYYtivFUiCzhtrxZbAnCr8dSXLXhz
"02f9b7252b245ccc4a8264cef7dd1db325b138e7942a68a1a45e5b4c22ae529397", # eh8Kjz7RDg6FrHPSru8xQZJyVgJFptVXZzusrY6esAEKboRu8kZs
"03d3d161080106df78d1f535093138c1acbc7629c4a7cec40d779a430c362ca857", # ehLPHFXfemHKbkZ1J3bVjq1vyB5hn9rTwXZHX6fah17jLKBwFDXh
"047af4be859bf97b26d60e56665a9194c7ef6a8cdb46113fc987f0c7ffdea3994d4af42cce1a39d7b8db96f08aa63bc8abcca2b441876ade8123aacf92493f9b77", # 9XzyrpAZo65SRLKChxQVbQ7L7MmWLDWeRvkPZVgZ6eGKvdgXbWC
]
# Import a compressed key and an uncompressed key, generate some multisig addresses
self.nodes[0].importprivkey("9WpZT7sXr6Zy6183PR89rxVzKbXrBSuDMfUDVVfErSwabyoJEa9")
uncompressed_spendable_address = ["su9fCxU4iXjMgLe1Yx63jJs6WEJXeNvzv4"]
self.nodes[0].importprivkey("efAPpKtYZjAcYHopymPdqjYd3LVxYGg2GdfyisVMZyP7SfMchyw1")
compressed_spendable_address = ["ssEZmM9N4RxyVNd2rKXPoU6NUEGip1ySnf"]
assert not self.nodes[0].getaddressinfo(uncompressed_spendable_address[0])['iscompressed']
assert self.nodes[0].getaddressinfo(compressed_spendable_address[0])['iscompressed']
self.nodes[0].importpubkey(pubkeys[0])
compressed_solvable_address = [key_to_p2pkh(pubkeys[0])]
self.nodes[0].importpubkey(pubkeys[1])
compressed_solvable_address.append(key_to_p2pkh(pubkeys[1]))
self.nodes[0].importpubkey(pubkeys[2])
uncompressed_solvable_address = [key_to_p2pkh(pubkeys[2])]
spendable_anytime = [] # These outputs should be seen anytime after importprivkey and addmultisigaddress
spendable_after_importaddress = [] # These outputs should be seen after importaddress
solvable_after_importaddress = [] # These outputs should be seen after importaddress but not spendable
unsolvable_after_importaddress = [] # These outputs should be unsolvable after importaddress
solvable_anytime = [] # These outputs should be solvable after importpubkey
unseen_anytime = [] # These outputs should never be seen
uncompressed_spendable_address.append(self.nodes[0].addmultisigaddress(2, [uncompressed_spendable_address[0], compressed_spendable_address[0]])['address'])
uncompressed_spendable_address.append(self.nodes[0].addmultisigaddress(2, [uncompressed_spendable_address[0], uncompressed_spendable_address[0]])['address'])
compressed_spendable_address.append(self.nodes[0].addmultisigaddress(2, [compressed_spendable_address[0], compressed_spendable_address[0]])['address'])
uncompressed_solvable_address.append(self.nodes[0].addmultisigaddress(2, [compressed_spendable_address[0], uncompressed_solvable_address[0]])['address'])
compressed_solvable_address.append(self.nodes[0].addmultisigaddress(2, [compressed_spendable_address[0], compressed_solvable_address[0]])['address'])
compressed_solvable_address.append(self.nodes[0].addmultisigaddress(2, [compressed_solvable_address[0], compressed_solvable_address[1]])['address'])
# Test multisig_without_privkey
# We have 2 public keys without private keys, use addmultisigaddress to add to wallet.
# Money sent to P2SH of multisig of this should only be seen after importaddress with the BASE58 P2SH address.
multisig_without_privkey_address = self.nodes[0].addmultisigaddress(2, [pubkeys[3], pubkeys[4]])['address']
script = keys_to_multisig_script([pubkeys[3], pubkeys[4]])
solvable_after_importaddress.append(script_to_p2sh_script(script))
for i in compressed_spendable_address:
v = self.nodes[0].getaddressinfo(i)
if v['isscript']:
[bare, p2sh, p2wsh, p2sh_p2wsh] = self.p2sh_address_to_script(v)
# p2sh multisig with compressed keys should always be spendable
spendable_anytime.extend([p2sh])
# bare multisig can be watched and signed, but is not treated as ours
solvable_after_importaddress.extend([bare])
# P2WSH and P2SH(P2WSH) multisig with compressed keys are spendable after direct importaddress
spendable_after_importaddress.extend([p2wsh, p2sh_p2wsh])
else:
[p2wpkh, p2sh_p2wpkh, p2pk, p2pkh, p2sh_p2pk, p2sh_p2pkh, p2wsh_p2pk, p2wsh_p2pkh, p2sh_p2wsh_p2pk, p2sh_p2wsh_p2pkh] = self.p2pkh_address_to_script(v)
# normal P2PKH and P2PK with compressed keys should always be spendable
spendable_anytime.extend([p2pkh, p2pk])
# P2SH_P2PK, P2SH_P2PKH with compressed keys are spendable after direct importaddress
spendable_after_importaddress.extend([p2sh_p2pk, p2sh_p2pkh, p2wsh_p2pk, p2wsh_p2pkh, p2sh_p2wsh_p2pk, p2sh_p2wsh_p2pkh])
# P2WPKH and P2SH_P2WPKH with compressed keys should always be spendable
spendable_anytime.extend([p2wpkh, p2sh_p2wpkh])
for i in uncompressed_spendable_address:
v = self.nodes[0].getaddressinfo(i)
if v['isscript']:
[bare, p2sh, p2wsh, p2sh_p2wsh] = self.p2sh_address_to_script(v)
# p2sh multisig with uncompressed keys should always be spendable
spendable_anytime.extend([p2sh])
# bare multisig can be watched and signed, but is not treated as ours
solvable_after_importaddress.extend([bare])
# P2WSH and P2SH(P2WSH) multisig with uncompressed keys are never seen
unseen_anytime.extend([p2wsh, p2sh_p2wsh])
else:
[p2wpkh, p2sh_p2wpkh, p2pk, p2pkh, p2sh_p2pk, p2sh_p2pkh, p2wsh_p2pk, p2wsh_p2pkh, p2sh_p2wsh_p2pk, p2sh_p2wsh_p2pkh] = self.p2pkh_address_to_script(v)
# normal P2PKH and P2PK with uncompressed keys should always be spendable
spendable_anytime.extend([p2pkh, p2pk])
# P2SH_P2PK and P2SH_P2PKH are spendable after direct importaddress
spendable_after_importaddress.extend([p2sh_p2pk, p2sh_p2pkh])
# Witness output types with uncompressed keys are never seen
unseen_anytime.extend([p2wpkh, p2sh_p2wpkh, p2wsh_p2pk, p2wsh_p2pkh, p2sh_p2wsh_p2pk, p2sh_p2wsh_p2pkh])
for i in compressed_solvable_address:
v = self.nodes[0].getaddressinfo(i)
if v['isscript']:
# Multisig without private is not seen after addmultisigaddress, but seen after importaddress
[bare, p2sh, p2wsh, p2sh_p2wsh] = self.p2sh_address_to_script(v)
solvable_after_importaddress.extend([bare, p2sh, p2wsh, p2sh_p2wsh])
else:
[p2wpkh, p2sh_p2wpkh, p2pk, p2pkh, p2sh_p2pk, p2sh_p2pkh, p2wsh_p2pk, p2wsh_p2pkh, p2sh_p2wsh_p2pk, p2sh_p2wsh_p2pkh] = self.p2pkh_address_to_script(v)
# normal P2PKH, P2PK, P2WPKH and P2SH_P2WPKH with compressed keys should always be seen
solvable_anytime.extend([p2pkh, p2pk, p2wpkh, p2sh_p2wpkh])
# P2SH_P2PK, P2SH_P2PKH with compressed keys are seen after direct importaddress
solvable_after_importaddress.extend([p2sh_p2pk, p2sh_p2pkh, p2wsh_p2pk, p2wsh_p2pkh, p2sh_p2wsh_p2pk, p2sh_p2wsh_p2pkh])
for i in uncompressed_solvable_address:
v = self.nodes[0].getaddressinfo(i)
if v['isscript']:
[bare, p2sh, p2wsh, p2sh_p2wsh] = self.p2sh_address_to_script(v)
# Base uncompressed multisig without private is not seen after addmultisigaddress, but seen after importaddress
solvable_after_importaddress.extend([bare, p2sh])
# P2WSH and P2SH(P2WSH) multisig with uncompressed keys are never seen
unseen_anytime.extend([p2wsh, p2sh_p2wsh])
else:
[p2wpkh, p2sh_p2wpkh, p2pk, p2pkh, p2sh_p2pk, p2sh_p2pkh, p2wsh_p2pk, p2wsh_p2pkh, p2sh_p2wsh_p2pk, p2sh_p2wsh_p2pkh] = self.p2pkh_address_to_script(v)
# normal P2PKH and P2PK with uncompressed keys should always be seen
solvable_anytime.extend([p2pkh, p2pk])
# P2SH_P2PK, P2SH_P2PKH with uncompressed keys are seen after direct importaddress
solvable_after_importaddress.extend([p2sh_p2pk, p2sh_p2pkh])
# Witness output types with uncompressed keys are never seen
unseen_anytime.extend([p2wpkh, p2sh_p2wpkh, p2wsh_p2pk, p2wsh_p2pkh, p2sh_p2wsh_p2pk, p2sh_p2wsh_p2pkh])
op1 = CScript([OP_1])
op0 = CScript([OP_0])
# 2N7MGY19ti4KDMSzRfPAssP6Pxyuxoi6jLe is the P2SH(P2PKH) version of mjoE3sSrb8ByYEvgnC3Aox86u1CHnfJA4V
unsolvable_address_key = bytes.fromhex("02341AEC7587A51CDE5279E0630A531AEA2615A9F80B17E8D9376327BAEAA59E3D")
unsolvablep2pkh = key_to_p2pkh_script(unsolvable_address_key)
unsolvablep2wshp2pkh = script_to_p2wsh_script(unsolvablep2pkh)
p2shop0 = script_to_p2sh_script(op0)
p2wshop1 = script_to_p2wsh_script(op1)
unsolvable_after_importaddress.append(unsolvablep2pkh)
unsolvable_after_importaddress.append(unsolvablep2wshp2pkh)
unsolvable_after_importaddress.append(op1) # OP_1 will be imported as script
unsolvable_after_importaddress.append(p2wshop1)
unseen_anytime.append(op0) # OP_0 will be imported as P2SH address with no script provided
unsolvable_after_importaddress.append(p2shop0)
spendable_txid = []
solvable_txid = []
spendable_txid.append(self.mine_and_test_listunspent(spendable_anytime, 2))
solvable_txid.append(self.mine_and_test_listunspent(solvable_anytime, 1))
self.mine_and_test_listunspent(spendable_after_importaddress + solvable_after_importaddress + unseen_anytime + unsolvable_after_importaddress, 0)
importlist = []
for i in compressed_spendable_address + uncompressed_spendable_address + compressed_solvable_address + uncompressed_solvable_address:
v = self.nodes[0].getaddressinfo(i)
if v['isscript']:
bare = bytes.fromhex(v['hex'])
importlist.append(bare.hex())
importlist.append(script_to_p2wsh_script(bare).hex())
else:
pubkey = bytes.fromhex(v['pubkey'])
p2pk = key_to_p2pk_script(pubkey)
p2pkh = key_to_p2pkh_script(pubkey)
importlist.append(p2pk.hex())
importlist.append(p2pkh.hex())
importlist.append(key_to_p2wpkh_script(pubkey).hex())
importlist.append(script_to_p2wsh_script(p2pk).hex())
importlist.append(script_to_p2wsh_script(p2pkh).hex())
importlist.append(unsolvablep2pkh.hex())
importlist.append(unsolvablep2wshp2pkh.hex())
importlist.append(op1.hex())
importlist.append(p2wshop1.hex())
for i in importlist:
# import all generated addresses. The wallet already has the private keys for some of these, so catch JSON RPC
# exceptions and continue.
try_rpc(-4, "The wallet already contains the private key for this address or script", self.nodes[0].importaddress, i, "", False, True)
self.nodes[0].importaddress(script_to_p2sh(op0)) # import OP_0 as address only
self.nodes[0].importaddress(multisig_without_privkey_address) # Test multisig_without_privkey
spendable_txid.append(self.mine_and_test_listunspent(spendable_anytime + spendable_after_importaddress, 2))
solvable_txid.append(self.mine_and_test_listunspent(solvable_anytime + solvable_after_importaddress, 1))
self.mine_and_test_listunspent(unsolvable_after_importaddress, 1)
self.mine_and_test_listunspent(unseen_anytime, 0)
spendable_txid.append(self.mine_and_test_listunspent(spendable_anytime + spendable_after_importaddress, 2))
solvable_txid.append(self.mine_and_test_listunspent(solvable_anytime + solvable_after_importaddress, 1))
self.mine_and_test_listunspent(unsolvable_after_importaddress, 1)
self.mine_and_test_listunspent(unseen_anytime, 0)
# Repeat some tests. This time we don't add witness scripts with importaddress
# Import a compressed key and an uncompressed key, generate some multisig addresses
self.nodes[0].importprivkey("9XHf5DujkEgpSSLNPQJhzC9uTne8CNAkANPyKSVbwNyMAeuj3yn")
uncompressed_spendable_address = ["t4LrP9nZrhY2zijA6VMERNkBDojjpSbpyk"]
self.nodes[0].importprivkey("ecL1RtGACiNhFDA3iGaFHaqNzDfHt3vfyttoL9QU2HC4yL5gTzR8")
compressed_spendable_address = ["svCDbdBn7kpoH2ComzqxrPiuEDPhYA8Fak"]
self.nodes[0].importpubkey(pubkeys[5])
compressed_solvable_address = [key_to_p2pkh(pubkeys[5])]
self.nodes[0].importpubkey(pubkeys[6])
uncompressed_solvable_address = [key_to_p2pkh(pubkeys[6])]
unseen_anytime = [] # These outputs should never be seen
solvable_anytime = [] # These outputs should be solvable after importpubkey
unseen_anytime = [] # These outputs should never be seen
uncompressed_spendable_address.append(self.nodes[0].addmultisigaddress(2, [uncompressed_spendable_address[0], compressed_spendable_address[0]])['address'])
uncompressed_spendable_address.append(self.nodes[0].addmultisigaddress(2, [uncompressed_spendable_address[0], uncompressed_spendable_address[0]])['address'])
compressed_spendable_address.append(self.nodes[0].addmultisigaddress(2, [compressed_spendable_address[0], compressed_spendable_address[0]])['address'])
uncompressed_solvable_address.append(self.nodes[0].addmultisigaddress(2, [compressed_solvable_address[0], uncompressed_solvable_address[0]])['address'])
compressed_solvable_address.append(self.nodes[0].addmultisigaddress(2, [compressed_spendable_address[0], compressed_solvable_address[0]])['address'])
premature_witaddress = []
for i in compressed_spendable_address:
v = self.nodes[0].getaddressinfo(i)
if v['isscript']:
[bare, p2sh, p2wsh, p2sh_p2wsh] = self.p2sh_address_to_script(v)
premature_witaddress.append(script_to_p2sh(p2wsh))
else:
[p2wpkh, p2sh_p2wpkh, p2pk, p2pkh, p2sh_p2pk, p2sh_p2pkh, p2wsh_p2pk, p2wsh_p2pkh, p2sh_p2wsh_p2pk, p2sh_p2wsh_p2pkh] = self.p2pkh_address_to_script(v)
# P2WPKH, P2SH_P2WPKH are always spendable
spendable_anytime.extend([p2wpkh, p2sh_p2wpkh])
for i in uncompressed_spendable_address + uncompressed_solvable_address:
v = self.nodes[0].getaddressinfo(i)
if v['isscript']:
[bare, p2sh, p2wsh, p2sh_p2wsh] = self.p2sh_address_to_script(v)
# P2WSH and P2SH(P2WSH) multisig with uncompressed keys are never seen
unseen_anytime.extend([p2wsh, p2sh_p2wsh])
else:
[p2wpkh, p2sh_p2wpkh, p2pk, p2pkh, p2sh_p2pk, p2sh_p2pkh, p2wsh_p2pk, p2wsh_p2pkh, p2sh_p2wsh_p2pk, p2sh_p2wsh_p2pkh] = self.p2pkh_address_to_script(v)
# P2WPKH, P2SH_P2WPKH with uncompressed keys are never seen
unseen_anytime.extend([p2wpkh, p2sh_p2wpkh])
for i in compressed_solvable_address:
v = self.nodes[0].getaddressinfo(i)
if v['isscript']:
[bare, p2sh, p2wsh, p2sh_p2wsh] = self.p2sh_address_to_script(v)
premature_witaddress.append(script_to_p2sh(p2wsh))
else:
[p2wpkh, p2sh_p2wpkh, p2pk, p2pkh, p2sh_p2pk, p2sh_p2pkh, p2wsh_p2pk, p2wsh_p2pkh, p2sh_p2wsh_p2pk, p2sh_p2wsh_p2pkh] = self.p2pkh_address_to_script(v)
# P2SH_P2PK, P2SH_P2PKH with compressed keys are always solvable
solvable_anytime.extend([p2wpkh, p2sh_p2wpkh])
self.mine_and_test_listunspent(spendable_anytime, 2)
self.mine_and_test_listunspent(solvable_anytime, 1)
self.mine_and_test_listunspent(unseen_anytime, 0)
# Check that createrawtransaction/decoderawtransaction with non-v0 Bech32 works
v1_addr = program_to_witness(1, [3, 5])
v1_tx = self.nodes[0].createrawtransaction([getutxo(spendable_txid[0])], {v1_addr: 1})
v1_decoded = self.nodes[1].decoderawtransaction(v1_tx)
assert_equal(v1_decoded['vout'][0]['scriptPubKey']['address'], v1_addr)
assert_equal(v1_decoded['vout'][0]['scriptPubKey']['hex'], "51020305")
# Check that spendable outputs are really spendable
self.create_and_mine_tx_from_txids(spendable_txid)
# import all the private keys so solvable addresses become spendable
self.nodes[0].importprivkey("edSdzE6zFFJ8hMVEWh9idnu9h8uYPQUJLknbRANvMooiMTfQmsCS")
self.nodes[0].importprivkey("eboTiEYQbmStx9kN6ddGorGc45DxuiSgtytsaYmutW5iU4vWvLeF")
self.nodes[0].importprivkey("9Y1pSRPsx4PcmnX1Wd9j1DAW51BjaWEdXPcUFU4NdVpFvR2rNhH")
self.nodes[0].importprivkey("eeU9UuVcudMDbMwGnmZdYG1PYYtivFUiCzhtrxZbAnCr8dSXLXhz")
self.nodes[0].importprivkey("eh8Kjz7RDg6FrHPSru8xQZJyVgJFptVXZzusrY6esAEKboRu8kZs")
self.nodes[0].importprivkey("ehLPHFXfemHKbkZ1J3bVjq1vyB5hn9rTwXZHX6fah17jLKBwFDXh")
self.nodes[0].importprivkey("9XzyrpAZo65SRLKChxQVbQ7L7MmWLDWeRvkPZVgZ6eGKvdgXbWC")
self.create_and_mine_tx_from_txids(solvable_txid)
# Test that importing native P2WPKH/P2WSH scripts works
for use_p2wsh in [False, True]:
if use_p2wsh:
scriptPubKey = "00203a59f3f56b713fdcf5d1a57357f02c44342cbf306ffe0c4741046837bf90561a"
transaction = "01000000000100e1f505000000002200203a59f3f56b713fdcf5d1a57357f02c44342cbf306ffe0c4741046837bf90561a00000000"
else:
scriptPubKey = "a9142f8c469c2f0084c48e11f998ffbe7efa7549f26d87"
transaction = "01000000000100e1f5050000000017a9142f8c469c2f0084c48e11f998ffbe7efa7549f26d8700000000"
self.nodes[1].importaddress(scriptPubKey, "", False)
rawtxfund = self.nodes[1].fundrawtransaction(transaction)['hex']
rawtxfund = self.nodes[1].signrawtransactionwithwallet(rawtxfund)["hex"]
txid = self.nodes[1].sendrawtransaction(rawtxfund)
assert_equal(self.nodes[1].gettransaction(txid, True)["txid"], txid)
assert_equal(self.nodes[1].listtransactions("*", 1, 0, True)[0]["txid"], txid)
# Assert it is properly saved
self.restart_node(1)
assert_equal(self.nodes[1].gettransaction(txid, True)["txid"], txid)
assert_equal(self.nodes[1].listtransactions("*", 1, 0, True)[0]["txid"], txid)
def mine_and_test_listunspent(self, script_list, ismine):
utxo = find_spendable_utxo(self.nodes[0], 50)
tx = CTransaction()
tx.vin.append(CTxIn(COutPoint(int('0x' + utxo['txid'], 0), utxo['vout'])))
for i in script_list:
tx.vout.append(CTxOut(10000000, i))
tx.rehash()
signresults = self.nodes[0].signrawtransactionwithwallet(tx.serialize_without_witness().hex())['hex']
txid = self.nodes[0].sendrawtransaction(hexstring=signresults, maxfeerate=0)
txs_mined[txid] = self.generate(self.nodes[0], 1)[0]
watchcount = 0
spendcount = 0
for i in self.nodes[0].listunspent():
if i['txid'] == txid:
watchcount += 1
if i['spendable']:
spendcount += 1
if ismine == 2:
assert_equal(spendcount, len(script_list))
elif ismine == 1:
assert_equal(watchcount, len(script_list))
assert_equal(spendcount, 0)
else:
assert_equal(watchcount, 0)
return txid
def p2sh_address_to_script(self, v):
bare = CScript(bytes.fromhex(v['hex']))
p2sh = CScript(bytes.fromhex(v['scriptPubKey']))
p2wsh = script_to_p2wsh_script(bare)
p2sh_p2wsh = script_to_p2sh_script(p2wsh)
return [bare, p2sh, p2wsh, p2sh_p2wsh]
def p2pkh_address_to_script(self, v):
pubkey = bytes.fromhex(v['pubkey'])
p2wpkh = key_to_p2wpkh_script(pubkey)
p2sh_p2wpkh = script_to_p2sh_script(p2wpkh)
p2pk = key_to_p2pk_script(pubkey)
p2pkh = CScript(bytes.fromhex(v['scriptPubKey']))
p2sh_p2pk = script_to_p2sh_script(p2pk)
p2sh_p2pkh = script_to_p2sh_script(p2pkh)
p2wsh_p2pk = script_to_p2wsh_script(p2pk)
p2wsh_p2pkh = script_to_p2wsh_script(p2pkh)
p2sh_p2wsh_p2pk = script_to_p2sh_script(p2wsh_p2pk)
p2sh_p2wsh_p2pkh = script_to_p2sh_script(p2wsh_p2pkh)
return [p2wpkh, p2sh_p2wpkh, p2pk, p2pkh, p2sh_p2pk, p2sh_p2pkh, p2wsh_p2pk, p2wsh_p2pkh, p2sh_p2wsh_p2pk, p2sh_p2wsh_p2pkh]
def create_and_mine_tx_from_txids(self, txids, success=True):
tx = CTransaction()
for i in txids:
txraw = self.nodes[0].getrawtransaction(i, 0, txs_mined[i])
txtmp = tx_from_hex(txraw)
for j in range(len(txtmp.vout)):
tx.vin.append(CTxIn(COutPoint(int('0x' + i, 0), j)))
tx.vout.append(CTxOut(0, CScript()))
tx.rehash()
signresults = self.nodes[0].signrawtransactionwithwallet(tx.serialize_without_witness().hex())['hex']
self.nodes[0].sendrawtransaction(hexstring=signresults, maxfeerate=0)
self.generate(self.nodes[0], 1)
if __name__ == '__main__':
SegWitTest().main()

View file

@ -0,0 +1,94 @@
#!/usr/bin/env python3
# Copyright (c) 2017-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test various command line arguments and configuration file parameters."""
import json
from pathlib import Path
from test_framework.test_framework import DigiByteTestFramework
from test_framework.test_node import ErrorMatch
from test_framework.util import assert_equal
class SettingsTest(DigiByteTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
self.wallet_names = []
def run_test(self):
node, = self.nodes
settings = Path(node.datadir, self.chain, "settings.json")
conf = Path(node.datadir, "digibyte.conf")
# Assert empty settings file was created
self.stop_node(0)
with settings.open() as fp:
assert_equal(json.load(fp), {})
# Assert settings are parsed and logged
with settings.open("w") as fp:
json.dump({"string": "string", "num": 5, "bool": True, "null": None, "list": [6, 7]}, fp)
with node.assert_debug_log(expected_msgs=[
'Ignoring unknown rw_settings value bool',
'Ignoring unknown rw_settings value list',
'Ignoring unknown rw_settings value null',
'Ignoring unknown rw_settings value num',
'Ignoring unknown rw_settings value string',
'Setting file arg: string = "string"',
'Setting file arg: num = 5',
'Setting file arg: bool = true',
'Setting file arg: null = null',
'Setting file arg: list = [6,7]',
]):
self.start_node(0)
self.stop_node(0)
# Assert settings are unchanged after shutdown
with settings.open() as fp:
assert_equal(json.load(fp), {"string": "string", "num": 5, "bool": True, "null": None, "list": [6, 7]})
# Test invalid json
with settings.open("w") as fp:
fp.write("invalid json")
node.assert_start_raises_init_error(expected_msg='Unable to parse settings file', match=ErrorMatch.PARTIAL_REGEX)
# Test invalid json object
with settings.open("w") as fp:
fp.write('"string"')
node.assert_start_raises_init_error(expected_msg='Found non-object value "string" in settings file', match=ErrorMatch.PARTIAL_REGEX)
# Test invalid settings file containing duplicate keys
with settings.open("w") as fp:
fp.write('{"key": 1, "key": 2}')
node.assert_start_raises_init_error(expected_msg='Found duplicate key key in settings file', match=ErrorMatch.PARTIAL_REGEX)
# Test invalid settings file is ignored with command line -nosettings
with node.assert_debug_log(expected_msgs=['Command-line arg: settings=false']):
self.start_node(0, extra_args=["-nosettings"])
self.stop_node(0)
# Test invalid settings file is ignored with config file -nosettings
with conf.open('a') as conf:
conf.write('nosettings=1\n')
with settings.open("w") as fp:
json.dump({}, fp)
self.start_node(0)
self.stop_node(0)
# Test alternate settings path
with settings.open("w") as fp:
json.dump({}, fp)
altsettings = Path(node.datadir, "altsettings.json")
with altsettings.open("w") as fp:
fp.write('{"key": "value"}')
with node.assert_debug_log(expected_msgs=['Setting file arg: key = "value"']):
self.start_node(0, extra_args=[f"-settings={altsettings}"])
self.stop_node(0)
if __name__ == '__main__':
SettingsTest().main()

View file

@ -0,0 +1,35 @@
#!/usr/bin/env python3
# Copyright (c) 2018-2020 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test digibyted shutdown."""
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import assert_equal, get_rpc_proxy
from threading import Thread
def test_long_call(node):
block = node.waitfornewblock()
assert_equal(block['height'], 0)
class ShutdownTest(DigiByteTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
self.supports_cli = False
def run_test(self):
node = get_rpc_proxy(self.nodes[0].url, 1, timeout=600, coveragedir=self.nodes[0].coverage_dir)
# Force connection establishment by executing a dummy command.
node.getblockcount()
Thread(target=test_long_call, args=(node,)).start()
# Wait until the server is executing the above `waitfornewblock`.
self.wait_until(lambda: len(self.nodes[0].getrpcinfo()['active_commands']) == 2)
# Wait 1 second after requesting shutdown but not before the `stop` call
# finishes. This is to ensure event loop waits for current connections
# to close.
self.stop_node(0, wait=1000)
if __name__ == '__main__':
ShutdownTest().main()

View file

@ -0,0 +1,77 @@
#!/usr/bin/env python3
# Copyright (c) 2019-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test basic signet functionality"""
from decimal import Decimal
from test_framework.test_framework import DigiByteTestFramework, SkipTest
from test_framework.util import assert_equal
signet_blocks = [
'00000020f61eee3b63a380a477a063af32b2bbc97c9ff9f01f2c4225e973988108000000f575c83235984e7dc4afc1f30944c170462e84437ab6f2d52e16878a79e4678bd1914d5fae77031eccf4070001010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff025151feffffff0200f2052a010000001600149243f727dd5343293eb83174324019ec16c2630f0000000000000000776a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf94c4fecc7daa2490047304402205e423a8754336ca99dbe16509b877ef1bf98d008836c725005b3c787c41ebe46022047246e4467ad7cc7f1ad98662afcaf14c115e0095a227c7b05c5182591c23e7e01000120000000000000000000000000000000000000000000000000000000000000000000000000',
'00000020533b53ded9bff4adc94101d32400a144c54edc5ed492a3b26c63b2d686000000b38fef50592017cfafbcab88eb3d9cf50b2c801711cad8299495d26df5e54812e7914d5fae77031ecfdd0b0001010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff025251feffffff0200f2052a01000000160014fd09839740f0e0b4fc6d5e2527e4022aa9b89dfa0000000000000000776a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf94c4fecc7daa24900473044022031d64a1692cdad1fc0ced69838169fe19ae01be524d831b95fcf5ea4e6541c3c02204f9dea0801df8b4d0cd0857c62ab35c6c25cc47c930630dc7fe723531daa3e9b01000120000000000000000000000000000000000000000000000000000000000000000000000000',
'000000202960f3752f0bfa8858a3e333294aedc7808025e868c9dc03e71d88bb320000007765fcd3d5b4966beb338bba2675dc2cf2ad28d4ad1d83bdb6f286e7e27ac1f807924d5fae77031e81d60b0001010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff025351feffffff0200f2052a010000001600141e5fb426042692ae0e87c070e78c39307a5661c20000000000000000776a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf94c4fecc7daa2490047304402205de93694763a42954865bcf1540cb82958bc62d0ec4eee02070fb7937cd037f4022067f333753bce47b10bc25eb6e1f311482e994c862a7e0b2d41ab1c8679fd1b1101000120000000000000000000000000000000000000000000000000000000000000000000000000',
'00000020b06443a13ae1d3d50faef5ecad38c6818194dc46abca3e972e2aacdae800000069a5829097e80fee00ac49a56ea9f82d741a6af84d32b3bc455cf31871e2a8ac27924d5fae77031e9c91050001010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff025451feffffff0200f2052a0100000016001430db2f8225dcf7751361ab38735de08190318cb70000000000000000776a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf94c4fecc7daa2490047304402200936f5f9872f6df5dd242026ad52241a68423f7f682e79169a8d85a374eab9b802202cd2979c48b321b3453e65e8f92460db3fca93cbea8539b450c959f4fbe630c601000120000000000000000000000000000000000000000000000000000000000000000000000000',
'000000207ed403758a4f228a1939418a155e2ebd4ae6b26e5ffd0ae433123f7694010000542e80b609c5bc58af5bdf492e26d4f60cd43a3966c2e063c50444c29b3757a636924d5fae77031ee8601d0001010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff025551feffffff0200f2052a01000000160014edc207e014df34fa3885dff97d1129d356e1186a0000000000000000776a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf94c4fecc7daa24900473044022021a3656609f85a66a2c5672ed9322c2158d57251040d2716ed202a1fe14f0c12022057d68bc6611f7a9424a7e00bbf3e27e6ae6b096f60bac624a094bc97a59aa1ff01000120000000000000000000000000000000000000000000000000000000000000000000000000',
'000000205bea0a88d1422c3df08d766ad72df95084d0700e6f873b75dd4e986c7703000002b57516d33ed60c2bdd9f93d6d5614083324c837e68e5ba6e04287a7285633585924d5fae77031ed171960001010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff025651feffffff0200f2052a010000001600143ae612599cf96f2442ce572633e0251116eaa52f0000000000000000776a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf94c4fecc7daa24900473044022059a7c54de76bfdbb1dd44c78ea2dbd2bb4e97f4abad38965f41e76433e56423c022054bf17f04fe17415c0141f60eebd2b839200f574d8ad8d55a0917b92b0eb913401000120000000000000000000000000000000000000000000000000000000000000000000000000',
'00000020daf3b60d374b19476461f97540498dcfa2eb7016238ec6b1d022f82fb60100007a7ae65b53cb988c2ec92d2384996713821d5645ffe61c9acea60da75cd5edfa1a944d5fae77031e9dbb050001010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff025751feffffff0200f2052a01000000160014ef2dceae02e35f8137de76768ae3345d99ca68860000000000000000776a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf94c4fecc7daa2490047304402202b3f946d6447f9bf17d00f3696cede7ee70b785495e5498274ee682a493befd5022045fc0bcf9332243168b5d35507175f9f374a8eba2336873885d12aada67ea5f601000120000000000000000000000000000000000000000000000000000000000000000000000000',
'00000020457cc5f3c2e1a5655bc20e20e48d33e1b7ea68786c614032b5c518f0b6000000541f36942d82c6e7248275ff15c8933487fbe1819c67a9ecc0f4b70bb7e6cf672a944d5fae77031e8f39860001010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff025851feffffff0200f2052a0100000016001472a27906947c06d034b38ba2fa13c6391a4832790000000000000000776a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf94c4fecc7daa2490047304402202d62805ce60cbd60591f97f949b5ea5bd7e2307bcde343e6ea8394da92758e72022053a25370b0aa20da100189b7899a8f8675a0fdc60e38ece6b8a4f98edd94569e01000120000000000000000000000000000000000000000000000000000000000000000000000000',
'00000020a2eb61eb4f3831baa3a3363e1b42db4462663f756f07423e81ed30322102000077224de7dea0f8d0ec22b1d2e2e255f0a987b96fe7200e1a2e6373f48a2f5b7894954d5fae77031e36867e0001010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff025951feffffff0200f2052a01000000160014aa0ad9f26801258382e0734dceec03a4a75f60240000000000000000776a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf94c4fecc7daa2490047304402206fa0d59990eed369bd7375767c9a6c9369fae209152b8674e520da270605528c0220749eed3b12dbe3f583f505d21803e4aef59c8e24c5831951eafa4f15a8f92c4e01000120000000000000000000000000000000000000000000000000000000000000000000000000',
'00000020a868e8514be5e46dabd6a122132f423f36a43b716a40c394e2a8d063e1010000f4c6c717e99d800c699c25a2006a75a0c5c09f432a936f385e6fce139cdbd1a5e9964d5fae77031e7d026e0001010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff025a51feffffff0200f2052a01000000160014aaa671c82b138e3b8f510cd801e5f2bd0aa305940000000000000000776a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf94c4fecc7daa24900473044022042309f4c3c7a1a2ac8c24f890f962df1c0086cec10be0868087cfc427520cb2702201dafee8911c269b7e786e242045bb57cef3f5b0f177010c6159abae42f646cc501000120000000000000000000000000000000000000000000000000000000000000000000000000',
]
class SignetBasicTest(DigiByteTestFramework):
def skip_test_if_missing_module(self):
raise SkipTest("Vexta signet genesis is not finalized yet")
def set_test_params(self):
self.chain = "signet"
self.num_nodes = 6
self.setup_clean_chain = True
shared_args1 = ["-signetchallenge=51"] # OP_TRUE
shared_args2 = [] # default challenge
# we use the exact same challenge except we do it as a 2-of-2, which means it should fail
shared_args3 = ["-signetchallenge=522103ad5e0edad18cb1f0fc0d28a3d4f1f3e445640337489abb10404f2d1e086be430210359ef5021964fe22d6f8e05b2463c9540ce96883fe3b278760f048f5189f2e6c452ae"]
self.extra_args = [
shared_args1, shared_args1,
shared_args2, shared_args2,
shared_args3, shared_args3,
]
def run_test(self):
self.log.info("basic tests using OP_TRUE challenge")
self.log.info('getmininginfo')
mining_info = self.nodes[0].getmininginfo()
assert_equal(mining_info['blocks'], 0)
assert_equal(mining_info['chain'], 'signet')
assert 'currentblocktx' not in mining_info
assert 'currentblockweight' not in mining_info
assert_equal(mining_info['networkhashps'], Decimal('0'))
assert_equal(mining_info['pooledtx'], 0)
self.generate(self.nodes[0], 1, sync_fun=self.no_op)
self.log.info("pregenerated signet blocks check")
height = 0
for block in signet_blocks:
assert_equal(self.nodes[2].submitblock(block), None)
height += 1
assert_equal(self.nodes[2].getblockcount(), height)
self.log.info("pregenerated signet blocks check (incompatible solution)")
assert_equal(self.nodes[4].submitblock(signet_blocks[0]), 'bad-signet-blksig')
self.log.info("test that signet logs the network magic on node start")
with self.nodes[0].assert_debug_log(["Signet derived magic (message start)"]):
self.restart_node(0)
if __name__ == '__main__':
SignetBasicTest().main()

1515
test/functional/feature_taproot.py Executable file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,40 @@
#!/usr/bin/env python3
# Copyright (c) 2017-2019 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test the -uacomment option."""
import re
from test_framework.test_framework import DigiByteTestFramework
from test_framework.test_node import ErrorMatch
from test_framework.util import assert_equal
class UacommentTest(DigiByteTestFramework):
def set_test_params(self):
self.num_nodes = 1
self.setup_clean_chain = True
def run_test(self):
self.log.info("test multiple -uacomment")
test_uacomment = self.nodes[0].getnetworkinfo()["subversion"][-12:-1]
assert_equal(test_uacomment, "(testnode0)")
self.restart_node(0, ["-uacomment=foo"])
foo_uacomment = self.nodes[0].getnetworkinfo()["subversion"][-17:-1]
assert_equal(foo_uacomment, "(testnode0; foo)")
self.log.info("test -uacomment max length")
self.stop_node(0)
expected = r"Error: Total length of network version string \([0-9]+\) exceeds maximum length \(256\). Reduce the number or size of uacomments."
self.nodes[0].assert_start_raises_init_error(["-uacomment=" + 'a' * 256], expected, match=ErrorMatch.FULL_REGEX)
self.log.info("test -uacomment unsafe characters")
for unsafe_char in ['/', ':', '(', ')', '₿', '🏃']:
expected = r"Error: User Agent comment \(" + re.escape(unsafe_char) + r"\) contains unsafe characters."
self.nodes[0].assert_start_raises_init_error(["-uacomment=" + unsafe_char], expected, match=ErrorMatch.FULL_REGEX)
if __name__ == '__main__':
UacommentTest().main()

View file

@ -0,0 +1,80 @@
#!/usr/bin/env python3
# Copyright (c) 2020-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test UTXO set hash value calculation in gettxoutsetinfo."""
import struct
from test_framework.messages import (
CBlock,
COutPoint,
from_hex,
)
from test_framework.muhash import MuHash3072
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import assert_equal
from test_framework.wallet import MiniWallet
class UTXOSetHashTest(DigiByteTestFramework):
def set_test_params(self):
self.num_nodes = 1
self.setup_clean_chain = True
def test_muhash_implementation(self):
self.log.info("Test MuHash implementation consistency")
node = self.nodes[0]
wallet = MiniWallet(node)
mocktime = node.getblockheader(node.getblockhash(0))['time'] + 1
node.setmocktime(mocktime)
# Generate 100 blocks and remove the first since we plan to spend its
# coinbase
block_hashes = self.generate(wallet, 1) + self.generate(node, 99)
blocks = list(map(lambda block: from_hex(CBlock(), node.getblock(block, False)), block_hashes))
blocks.pop(0)
# Create a spending transaction and mine a block which includes it
txid = wallet.send_self_transfer(from_node=node)['txid']
tx_block = self.generateblock(node, output=wallet.get_address(), transactions=[txid])
blocks.append(from_hex(CBlock(), node.getblock(tx_block['hash'], False)))
# Serialize the outputs that should be in the UTXO set and add them to
# a MuHash object
muhash = MuHash3072()
for height, block in enumerate(blocks):
# The Genesis block coinbase is not part of the UTXO set and we
# spent the first mined block
height += 2
for tx in block.vtx:
for n, tx_out in enumerate(tx.vout):
coinbase = 1 if not tx.vin[0].prevout.hash else 0
# Skip witness commitment
if (coinbase and n > 0):
continue
data = COutPoint(int(tx.rehash(), 16), n).serialize()
data += struct.pack("<i", height * 2 + coinbase)
data += tx_out.serialize()
muhash.insert(data)
finalized = muhash.digest()
node_muhash = node.gettxoutsetinfo("muhash")['muhash']
assert_equal(finalized[::-1].hex(), node_muhash)
self.log.info("Test deterministic UTXO set hash results")
assert_equal(node.gettxoutsetinfo()['hash_serialized_2'], "19f93efb27d2cd183f1fe4dd6d33bbdd4fb135c0da221cef8c2b0675313fc533")
assert_equal(node.gettxoutsetinfo("muhash")['muhash'], "9d0a40ddf05ee366c7766188dca5f16ca72a43a4e52dcd7d6c17f96ea266eebb")
def run_test(self):
self.test_muhash_implementation()
if __name__ == '__main__':
UTXOSetHashTest().main()

View file

@ -0,0 +1,107 @@
#!/usr/bin/env python3
# Copyright (c) 2016-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test version bits warning system.
Generate chains with block versions that appear to be signalling unknown
soft-forks, and test that warning alerts are generated.
"""
import os
import re
from test_framework.blocktools import create_block, create_coinbase
from test_framework.messages import msg_block
from test_framework.p2p import P2PInterface
from test_framework.test_framework import DigiByteTestFramework
VB_PERIOD = 240 # versionbits period length for regtest
VB_THRESHOLD = 168 # versionbits activation threshold for regtest
VB_TOP_BITS = 0x20000000
VB_UNKNOWN_BIT = 26 # Choose a bit unassigned to any deployment
VB_UNKNOWN_VERSION = VB_TOP_BITS | (1 << VB_UNKNOWN_BIT)
WARN_UNKNOWN_RULES_ACTIVE = f"Unknown new rules activated (versionbit {VB_UNKNOWN_BIT})"
VB_PATTERN = re.compile("Unknown new rules activated.*versionbit")
class VersionBitsWarningTest(DigiByteTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
# The experimental syscall sandbox feature (-sandbox) is not compatible with -alertnotify
# (which invokes execve).
self.disable_syscall_sandbox = True
self.options.timeout_factor = 4
self.extra_args = [["-easypow"]]
def setup_network(self):
self.alert_filename = os.path.join(self.options.tmpdir, "alert.txt")
# Open and close to create zero-length file
with open(self.alert_filename, 'w', encoding='utf8'):
pass
self.extra_args = [[f"-alertnotify=echo %s >> \"{self.alert_filename}\""]]
self.setup_nodes()
def send_blocks_with_version(self, peer, numblocks, version):
"""Send numblocks blocks to peer with version set"""
tip = self.nodes[0].getbestblockhash()
height = self.nodes[0].getblockcount()
block_time = self.nodes[0].getblockheader(tip)["time"] + 1
tip = int(tip, 16)
for _ in range(numblocks):
block = create_block(tip, create_coinbase(height + 1), block_time, version=version)
block.solve()
peer.send_message(msg_block(block))
block_time += 1
height += 1
tip = block.sha256
peer.sync_with_ping()
def versionbits_in_alert_file(self):
"""Test that the versionbits warning has been written to the alert file."""
alert_text = open(self.alert_filename, 'r', encoding='utf8').read()
return VB_PATTERN.search(alert_text) is not None
def run_test(self):
node = self.nodes[0]
peer = node.add_p2p_connection(P2PInterface())
node_deterministic_address = node.get_deterministic_priv_key().address
# Mine one period worth of blocks
self.generatetoaddress(node, VB_PERIOD, node_deterministic_address)
self.log.info("Check that there is no warning if previous VB_BLOCKS have <VB_THRESHOLD blocks with unknown versionbits version.")
# Build one period of blocks with < VB_THRESHOLD blocks signaling some unknown bit
self.send_blocks_with_version(peer, VB_THRESHOLD - 1, VB_UNKNOWN_VERSION)
self.generatetoaddress(node, VB_PERIOD - VB_THRESHOLD + 1, node_deterministic_address)
# Check that we're not getting any versionbit-related errors in get*info()
assert not VB_PATTERN.match(node.getmininginfo()["warnings"])
assert not VB_PATTERN.match(node.getnetworkinfo()["warnings"])
# Build one period of blocks with VB_THRESHOLD blocks signaling some unknown bit
self.send_blocks_with_version(peer, VB_THRESHOLD, VB_UNKNOWN_VERSION)
self.generatetoaddress(node, VB_PERIOD - VB_THRESHOLD, node_deterministic_address)
self.log.info("Check that there is a warning if previous VB_BLOCKS have >=VB_THRESHOLD blocks with unknown versionbits version.")
# Mine a period worth of expected blocks so the generic block-version warning
# is cleared. This will move the versionbit state to ACTIVE.
self.generatetoaddress(node, VB_PERIOD, node_deterministic_address)
# Stop-start the node. This is required because digibyted will only warn once about unknown versions or unknown rules activating.
self.restart_node(0)
# Generating one block guarantees that we'll get out of IBD
self.generatetoaddress(node, 1, node_deterministic_address)
self.wait_until(lambda: not node.getblockchaininfo()['initialblockdownload'])
# Generating one more block will be enough to generate an error.
self.generatetoaddress(node, 1, node_deterministic_address)
# Check that get*info() shows the versionbits unknown rules warning
assert WARN_UNKNOWN_RULES_ACTIVE in node.getmininginfo()["warnings"]
assert WARN_UNKNOWN_RULES_ACTIVE in node.getnetworkinfo()["warnings"]
# Check that the alert file shows the versionbits unknown rules warning
self.wait_until(lambda: self.versionbits_in_alert_file())
if __name__ == '__main__':
VersionBitsWarningTest().main()

59
test/functional/forknotify.py Executable file
View file

@ -0,0 +1,59 @@
#!/usr/bin/env python3
# Copyright (c) 2014-2017 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test the -alertnotify option."""
import os
import time
from test_framework.test_framework import DigiByteTestFramework
class ForkNotifyTest(DigiByteTestFramework):
def set_test_params(self):
self.num_nodes = 2
def setup_network(self):
self.alert_filename = os.path.join(self.options.tmpdir, "alert.txt")
with open(self.alert_filename, 'w', encoding='utf8'):
pass # Just open then close to create zero-length file
self.extra_args = [["-blockversion=2", "-alertnotify=echo %s >> \"" + self.alert_filename + "\""],
["-blockversion=211"]]
super().setup_network()
def run_test(self):
# Mine 51 up-version blocks
self.nodes[1].generate(51)
self.sync_all()
# -alertnotify should trigger on the 51'st,
# but mine and sync another to give
# -alertnotify time to write
self.nodes[1].generate(1)
self.sync_all()
# Give digibyted 10 seconds to write the alert notification
timeout = 10.0
while timeout > 0:
if os.path.exists(self.alert_filename) and os.path.getsize(self.alert_filename):
break
time.sleep(0.1)
timeout -= 0.1
else:
assert False, "-alertnotify did not warn of up-version blocks"
with open(self.alert_filename, 'r', encoding='utf8') as f:
alert_text = f.read()
# Mine more up-version blocks, should not get more alerts:
self.nodes[1].generate(1)
self.sync_all()
self.nodes[1].generate(1)
self.sync_all()
with open(self.alert_filename, 'r', encoding='utf8') as f:
alert_text2 = f.read()
if alert_text != alert_text2:
raise AssertionError("-alertnotify excessive warning of up-version blocks")
if __name__ == '__main__':
ForkNotifyTest().main()

View file

@ -0,0 +1,328 @@
#!/usr/bin/env python3
# Copyright (c) 2017-2021 The Bitcoin Core developers
# Copyright (c) 2021-2022 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test digibyte-cli"""
from decimal import Decimal
import re
from test_framework.blocktools import COINBASE_MATURITY_2
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import (
assert_equal,
assert_greater_than_or_equal,
assert_raises_process_error,
assert_raises_rpc_error,
get_auth_cookie,
)
import time
# The block reward of coinbaseoutput.nValue (72000) DGB/block matures after
# COINBASE_MATURITY (100) blocks. Therefore, after mining 8+1 blocks we expect
# node 0 to have a balance of (BLOCKS - COINBASE_MATURITY) * 72000 DGB/block.
BLOCKS = COINBASE_MATURITY_2 + 1
BALANCE = (BLOCKS - COINBASE_MATURITY_2) * 50
JSON_PARSING_ERROR = 'error: Error parsing JSON: foo'
BLOCKS_VALUE_OF_ZERO = 'error: the first argument (number of blocks to generate, default: 1) must be an integer value greater than zero'
TOO_MANY_ARGS = 'error: too many arguments (maximum 2 for nblocks and maxtries)'
WALLET_NOT_LOADED = 'Requested wallet does not exist or is not loaded'
WALLET_NOT_SPECIFIED = 'Wallet file not specified'
def cli_get_info_string_to_dict(cli_get_info_string):
"""Helper method to convert human-readable -getinfo into a dictionary"""
cli_get_info = {}
lines = cli_get_info_string.splitlines()
line_idx = 0
ansi_escape = re.compile(r'(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]')
while line_idx < len(lines):
# Remove ansi colour code
line = ansi_escape.sub('', lines[line_idx])
if "Balances" in line:
# When "Balances" appears in a line, all of the following lines contain "balance: wallet" until an empty line
cli_get_info["Balances"] = {}
while line_idx < len(lines) and not (lines[line_idx + 1] == ''):
line_idx += 1
balance, wallet = lines[line_idx].strip().split(" ")
# Remove right justification padding
wallet = wallet.strip()
if wallet == '""':
# Set default wallet("") to empty string
wallet = ''
cli_get_info["Balances"][wallet] = balance.strip()
elif ": " in line:
key, value = line.split(": ")
if key == 'Wallet' and value == '""':
# Set default wallet("") to empty string
value = ''
if key == "Proxy" and value == "N/A":
# Set N/A to empty string to represent no proxy
value = ''
if key.startswith("Difficulty"):
match = re.match(r"Difficulty \((.+)\)", key)
key = "Difficulties"
difficulties = cli_get_info[key] if key in cli_get_info else {}
difficulties[match.group(1)] = value
value = difficulties
else:
value = value.strip()
cli_get_info[key.strip()] = value
line_idx += 1
return cli_get_info
class TestDigiByteCli(DigiByteTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
if self.is_specified_wallet_compiled():
self.requires_wallet = True
def skip_test_if_missing_module(self):
self.skip_if_no_cli()
def run_test(self):
"""Main test logic"""
self.generate(self.nodes[0], BLOCKS)
self.log.info("Compare responses from getblockchaininfo RPC and `digibyte-cli getblockchaininfo`")
cli_response = self.nodes[0].cli.getblockchaininfo()
rpc_response = self.nodes[0].getblockchaininfo()
assert_equal(cli_response, rpc_response)
user, password = get_auth_cookie(self.nodes[0].datadir, self.chain)
self.log.info("Test -stdinrpcpass option")
assert_equal(BLOCKS, self.nodes[0].cli(f'-rpcuser={user}', '-stdinrpcpass', input=password).getblockcount())
assert_raises_process_error(1, 'Incorrect rpcuser or rpcpassword', self.nodes[0].cli(f'-rpcuser={user}', '-stdinrpcpass', input='foo').echo)
self.log.info("Test -stdin and -stdinrpcpass")
assert_equal(['foo', 'bar'], self.nodes[0].cli(f'-rpcuser={user}', '-stdin', '-stdinrpcpass', input=f'{password}\nfoo\nbar').echo())
assert_raises_process_error(1, 'Incorrect rpcuser or rpcpassword', self.nodes[0].cli(f'-rpcuser={user}', '-stdin', '-stdinrpcpass', input='foo').echo)
self.log.info("Test connecting to a non-existing server")
assert_raises_process_error(1, "Could not connect to the server", self.nodes[0].cli('-rpcport=1').echo)
self.log.info("Test connecting with non-existing RPC cookie file")
assert_raises_process_error(1, "Could not locate RPC credentials", self.nodes[0].cli('-rpccookiefile=does-not-exist', '-rpcpassword=').echo)
self.log.info("Test -getinfo with arguments fails")
assert_raises_process_error(1, "-getinfo takes no arguments", self.nodes[0].cli('-getinfo').help)
self.log.info("Test -getinfo with -color=never does not return ANSI escape codes")
assert "\u001b[0m" not in self.nodes[0].cli('-getinfo', '-color=never').send_cli()
self.log.info("Test -getinfo with -color=always returns ANSI escape codes")
assert "\u001b[0m" in self.nodes[0].cli('-getinfo', '-color=always').send_cli()
self.log.info("Test -getinfo with invalid value for -color option")
assert_raises_process_error(1, "Invalid value for -color option. Valid values: always, auto, never.", self.nodes[0].cli('-getinfo', '-color=foo').send_cli)
self.log.info("Test -getinfo returns expected network and blockchain info")
if self.is_specified_wallet_compiled():
self.nodes[0].encryptwallet(password)
cli_get_info_string = self.nodes[0].cli('-getinfo').send_cli()
cli_get_info = cli_get_info_string_to_dict(cli_get_info_string)
network_info = self.nodes[0].getnetworkinfo()
blockchain_info = self.nodes[0].getblockchaininfo()
assert_equal(int(cli_get_info['Version']), network_info['version'])
assert_equal(cli_get_info['Verification progress'], "%.4f%%" % (blockchain_info['verificationprogress'] * 100))
assert_equal(int(cli_get_info['Blocks']), blockchain_info['blocks'])
assert_equal(int(cli_get_info['Headers']), blockchain_info['headers'])
assert_equal(int(cli_get_info['Time offset (s)']), network_info['timeoffset'])
expected_network_info = f"in {network_info['connections_in']}, out {network_info['connections_out']}, total {network_info['connections']}"
assert_equal(cli_get_info["Network"], expected_network_info)
assert_equal(cli_get_info['Proxy'], network_info['networks'][0]['proxy'])
assert_equal(Decimal(cli_get_info['Difficulties']['sha256d']), blockchain_info['difficulties']['sha256d'])
assert_equal(cli_get_info['Chain'], blockchain_info['chain'])
if self.is_specified_wallet_compiled():
self.log.info("Test -getinfo and digibyte-cli getwalletinfo return expected wallet info")
# Explicitly set the output type in order to have consistent tx vsize / fees
# for both legacy and descriptor wallets (disables the change address type detection algorithm)
self.restart_node(0, extra_args=["-addresstype=bech32", "-changetype=bech32"])
assert_equal(Decimal(cli_get_info['Balance']), BALANCE)
assert 'Balances' not in cli_get_info_string
wallet_info = self.nodes[0].getwalletinfo()
assert_equal(int(cli_get_info['Keypool size']), wallet_info['keypoolsize'])
assert_equal(int(cli_get_info['Unlocked until']), wallet_info['unlocked_until'])
assert_equal(Decimal(cli_get_info['Transaction fee rate (-paytxfee) (DGB/kvB)']), wallet_info['paytxfee'])
assert_equal(Decimal(cli_get_info['Min tx relay fee rate (DGB/kvB)']), network_info['relayfee'])
assert_equal(self.nodes[0].cli.getwalletinfo(), wallet_info)
# Setup to test -getinfo, -generate, and -rpcwallet= with multiple wallets.
wallets = [self.default_wallet_name, 'Encrypted', 'secret']
amounts = [BALANCE + Decimal('9.96400000'), Decimal(9), BALANCE - Decimal(19)]
self.nodes[0].createwallet(wallet_name=wallets[1])
self.nodes[0].createwallet(wallet_name=wallets[2])
w1 = self.nodes[0].get_wallet_rpc(wallets[0])
w2 = self.nodes[0].get_wallet_rpc(wallets[1])
w3 = self.nodes[0].get_wallet_rpc(wallets[2])
rpcwallet2 = f'-rpcwallet={wallets[1]}'
rpcwallet3 = f'-rpcwallet={wallets[2]}'
w1.walletpassphrase(password, self.rpc_timeout)
w2.encryptwallet(password)
w1.sendtoaddress(w2.getnewaddress(), amounts[1])
w1.sendtoaddress(w3.getnewaddress(), amounts[2])
# Mine a block to confirm; adds a block reward to the default wallet.
self.generatetoaddress(self.nodes[0], 1, w1.getnewaddress())
self.log.info("Test -getinfo with multiple wallets and -rpcwallet returns specified wallet balance")
for i in range(len(wallets)):
cli_get_info_string = self.nodes[0].cli('-getinfo', f'-rpcwallet={wallets[i]}').send_cli()
cli_get_info = cli_get_info_string_to_dict(cli_get_info_string)
assert 'Balances' not in cli_get_info_string
assert_equal(cli_get_info["Wallet"], wallets[i])
assert_equal(Decimal(cli_get_info['Balance']), amounts[i])
self.log.info("Test -getinfo with multiple wallets and -rpcwallet=non-existing-wallet returns no balances")
cli_get_info_string = self.nodes[0].cli('-getinfo', '-rpcwallet=does-not-exist').send_cli()
assert 'Balance' not in cli_get_info_string
assert 'Balances' not in cli_get_info_string
self.log.info("Test -getinfo with multiple wallets returns all loaded wallet names and balances")
assert_equal(set(self.nodes[0].listwallets()), set(wallets))
cli_get_info_string = self.nodes[0].cli('-getinfo').send_cli()
cli_get_info = cli_get_info_string_to_dict(cli_get_info_string)
assert 'Balance' not in cli_get_info
for k, v in zip(wallets, amounts):
assert_equal(Decimal(cli_get_info['Balances'][k]), v)
# Unload the default wallet and re-verify.
self.nodes[0].unloadwallet(wallets[0])
assert wallets[0] not in self.nodes[0].listwallets()
cli_get_info_string = self.nodes[0].cli('-getinfo').send_cli()
cli_get_info = cli_get_info_string_to_dict(cli_get_info_string)
assert 'Balance' not in cli_get_info
assert 'Balances' in cli_get_info_string
for k, v in zip(wallets[1:], amounts[1:]):
assert_equal(Decimal(cli_get_info['Balances'][k]), v)
assert wallets[0] not in cli_get_info
self.log.info("Test -getinfo after unloading all wallets except a non-default one returns its balance")
self.nodes[0].unloadwallet(wallets[2])
assert_equal(self.nodes[0].listwallets(), [wallets[1]])
cli_get_info_string = self.nodes[0].cli('-getinfo').send_cli()
cli_get_info = cli_get_info_string_to_dict(cli_get_info_string)
assert 'Balances' not in cli_get_info_string
assert_equal(cli_get_info['Wallet'], wallets[1])
assert_equal(Decimal(cli_get_info['Balance']), amounts[1])
self.log.info("Test -getinfo with -rpcwallet=remaining-non-default-wallet returns only its balance")
cli_get_info_string = self.nodes[0].cli('-getinfo', rpcwallet2).send_cli()
cli_get_info = cli_get_info_string_to_dict(cli_get_info_string)
assert 'Balances' not in cli_get_info_string
assert_equal(cli_get_info['Wallet'], wallets[1])
assert_equal(Decimal(cli_get_info['Balance']), amounts[1])
self.log.info("Test -getinfo with -rpcwallet=unloaded wallet returns no balances")
cli_get_info_string = self.nodes[0].cli('-getinfo', rpcwallet3).send_cli()
cli_get_info_keys = cli_get_info_string_to_dict(cli_get_info_string)
assert 'Balance' not in cli_get_info_keys
assert 'Balances' not in cli_get_info_string
# Test digibyte-cli -generate.
n1 = 3
n2 = 4
w2.walletpassphrase(password, self.rpc_timeout)
blocks = self.nodes[0].getblockcount()
self.log.info('Test -generate with no args')
generate = self.nodes[0].cli('-generate').send_cli()
assert_equal(set(generate.keys()), {'address', 'blocks'})
assert_equal(len(generate["blocks"]), 1)
assert_equal(self.nodes[0].getblockcount(), blocks + 1)
self.log.info('Test -generate with bad args')
assert_raises_process_error(1, JSON_PARSING_ERROR, self.nodes[0].cli('-generate', 'foo').echo)
assert_raises_process_error(1, BLOCKS_VALUE_OF_ZERO, self.nodes[0].cli('-generate', 0).echo)
assert_raises_process_error(1, TOO_MANY_ARGS, self.nodes[0].cli('-generate', 1, 2, 3).echo)
self.log.info('Test -generate with nblocks')
generate = self.nodes[0].cli('-generate', n1).send_cli()
assert_equal(set(generate.keys()), {'address', 'blocks'})
assert_equal(len(generate["blocks"]), n1)
assert_equal(self.nodes[0].getblockcount(), blocks + 1 + n1)
self.log.info('Test -generate with nblocks and maxtries')
generate = self.nodes[0].cli('-generate', n2, 1000000).send_cli()
assert_equal(set(generate.keys()), {'address', 'blocks'})
assert_equal(len(generate["blocks"]), n2)
assert_equal(self.nodes[0].getblockcount(), blocks + 1 + n1 + n2)
self.log.info('Test -generate -rpcwallet in single-wallet mode')
generate = self.nodes[0].cli(rpcwallet2, '-generate').send_cli()
assert_equal(set(generate.keys()), {'address', 'blocks'})
assert_equal(len(generate["blocks"]), 1)
assert_equal(self.nodes[0].getblockcount(), blocks + 2 + n1 + n2)
self.log.info('Test -generate -rpcwallet=unloaded wallet raises RPC error')
assert_raises_rpc_error(-18, WALLET_NOT_LOADED, self.nodes[0].cli(rpcwallet3, '-generate').echo)
assert_raises_rpc_error(-18, WALLET_NOT_LOADED, self.nodes[0].cli(rpcwallet3, '-generate', 'foo').echo)
assert_raises_rpc_error(-18, WALLET_NOT_LOADED, self.nodes[0].cli(rpcwallet3, '-generate', 0).echo)
assert_raises_rpc_error(-18, WALLET_NOT_LOADED, self.nodes[0].cli(rpcwallet3, '-generate', 1, 2, 3).echo)
# Test digibyte-cli -generate with -rpcwallet in multiwallet mode.
self.nodes[0].loadwallet(wallets[2])
n3 = 4
n4 = 10
blocks = self.nodes[0].getblockcount()
self.log.info('Test -generate -rpcwallet with no args')
generate = self.nodes[0].cli(rpcwallet2, '-generate').send_cli()
assert_equal(set(generate.keys()), {'address', 'blocks'})
assert_equal(len(generate["blocks"]), 1)
assert_equal(self.nodes[0].getblockcount(), blocks + 1)
self.log.info('Test -generate -rpcwallet with bad args')
assert_raises_process_error(1, JSON_PARSING_ERROR, self.nodes[0].cli(rpcwallet2, '-generate', 'foo').echo)
assert_raises_process_error(1, BLOCKS_VALUE_OF_ZERO, self.nodes[0].cli(rpcwallet2, '-generate', 0).echo)
assert_raises_process_error(1, TOO_MANY_ARGS, self.nodes[0].cli(rpcwallet2, '-generate', 1, 2, 3).echo)
self.log.info('Test -generate -rpcwallet with nblocks')
generate = self.nodes[0].cli(rpcwallet2, '-generate', n3).send_cli()
assert_equal(set(generate.keys()), {'address', 'blocks'})
assert_equal(len(generate["blocks"]), n3)
assert_equal(self.nodes[0].getblockcount(), blocks + 1 + n3)
self.log.info('Test -generate -rpcwallet with nblocks and maxtries')
generate = self.nodes[0].cli(rpcwallet2, '-generate', n4, 1000000).send_cli()
assert_equal(set(generate.keys()), {'address', 'blocks'})
assert_equal(len(generate["blocks"]), n4)
assert_equal(self.nodes[0].getblockcount(), blocks + 1 + n3 + n4)
self.log.info('Test -generate without -rpcwallet in multiwallet mode raises RPC error')
assert_raises_rpc_error(-19, WALLET_NOT_SPECIFIED, self.nodes[0].cli('-generate').echo)
assert_raises_rpc_error(-19, WALLET_NOT_SPECIFIED, self.nodes[0].cli('-generate', 'foo').echo)
assert_raises_rpc_error(-19, WALLET_NOT_SPECIFIED, self.nodes[0].cli('-generate', 0).echo)
assert_raises_rpc_error(-19, WALLET_NOT_SPECIFIED, self.nodes[0].cli('-generate', 1, 2, 3).echo)
else:
self.log.info("*** Wallet not compiled; cli getwalletinfo and -getinfo wallet tests skipped")
self.generate(self.nodes[0], 25) # maintain block parity with the wallet_compiled conditional branch
self.log.info("Test -version with node stopped")
self.stop_node(0)
cli_response = self.nodes[0].cli('-version').send_cli()
assert f"{self.config['environment']['PACKAGE_NAME']} RPC client version" in cli_response
self.log.info("Test -rpcwait option successfully waits for RPC connection")
self.nodes[0].start() # start node without RPC connection
self.nodes[0].wait_for_cookie_credentials() # ensure cookie file is available to avoid race condition
blocks = self.nodes[0].cli('-rpcwait').send_cli('getblockcount')
self.nodes[0].wait_for_rpc_connection()
assert_equal(blocks, BLOCKS + 25)
self.log.info("Test -rpcwait option waits at most -rpcwaittimeout seconds for startup")
self.stop_node(0) # stop the node so we time out
start_time = time.time()
assert_raises_process_error(1, "Could not connect to the server", self.nodes[0].cli('-rpcwait', '-rpcwaittimeout=5').echo)
assert_greater_than_or_equal(time.time(), start_time + 5)
if __name__ == '__main__':
TestDigiByteCli().main()

109
test/functional/interface_http.py Executable file
View file

@ -0,0 +1,109 @@
#!/usr/bin/env python3
# Copyright (c) 2014-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test the RPC HTTP basics."""
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import assert_equal, str_to_b64str
import http.client
import urllib.parse
class HTTPBasicsTest (DigiByteTestFramework):
def set_test_params(self):
self.num_nodes = 3
self.supports_cli = False
def setup_network(self):
self.setup_nodes()
def run_test(self):
#################################################
# lowlevel check for http persistent connection #
#################################################
url = urllib.parse.urlparse(self.nodes[0].url)
authpair = f'{url.username}:{url.password}'
headers = {"Authorization": f"Basic {str_to_b64str(authpair)}"}
conn = http.client.HTTPConnection(url.hostname, url.port)
conn.connect()
conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
out1 = conn.getresponse().read()
assert b'"error":null' in out1
assert conn.sock is not None #according to http/1.1 connection must still be open!
#send 2nd request without closing connection
conn.request('POST', '/', '{"method": "getchaintips"}', headers)
out1 = conn.getresponse().read()
assert b'"error":null' in out1 #must also response with a correct json-rpc message
assert conn.sock is not None #according to http/1.1 connection must still be open!
conn.close()
#same should be if we add keep-alive because this should be the std. behaviour
headers = {"Authorization": f"Basic {str_to_b64str(authpair)}", "Connection": "keep-alive"}
conn = http.client.HTTPConnection(url.hostname, url.port)
conn.connect()
conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
out1 = conn.getresponse().read()
assert b'"error":null' in out1
assert conn.sock is not None #according to http/1.1 connection must still be open!
#send 2nd request without closing connection
conn.request('POST', '/', '{"method": "getchaintips"}', headers)
out1 = conn.getresponse().read()
assert b'"error":null' in out1 #must also response with a correct json-rpc message
assert conn.sock is not None #according to http/1.1 connection must still be open!
conn.close()
#now do the same with "Connection: close"
headers = {"Authorization": f"Basic {str_to_b64str(authpair)}", "Connection":"close"}
conn = http.client.HTTPConnection(url.hostname, url.port)
conn.connect()
conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
out1 = conn.getresponse().read()
assert b'"error":null' in out1
assert conn.sock is None #now the connection must be closed after the response
#node1 (2nd node) is running with disabled keep-alive option
urlNode1 = urllib.parse.urlparse(self.nodes[1].url)
authpair = f'{urlNode1.username}:{urlNode1.password}'
headers = {"Authorization": f"Basic {str_to_b64str(authpair)}"}
conn = http.client.HTTPConnection(urlNode1.hostname, urlNode1.port)
conn.connect()
conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
out1 = conn.getresponse().read()
assert b'"error":null' in out1
#node2 (third node) is running with standard keep-alive parameters which means keep-alive is on
urlNode2 = urllib.parse.urlparse(self.nodes[2].url)
authpair = f'{urlNode2.username}:{urlNode2.password}'
headers = {"Authorization": f"Basic {str_to_b64str(authpair)}"}
conn = http.client.HTTPConnection(urlNode2.hostname, urlNode2.port)
conn.connect()
conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
out1 = conn.getresponse().read()
assert b'"error":null' in out1
assert conn.sock is not None #connection must be closed because digibyted should use keep-alive by default
# Check excessive request size
conn = http.client.HTTPConnection(urlNode2.hostname, urlNode2.port)
conn.connect()
conn.request('GET', f'/{"x"*1000}', '', headers)
out1 = conn.getresponse()
assert_equal(out1.status, http.client.NOT_FOUND)
conn = http.client.HTTPConnection(urlNode2.hostname, urlNode2.port)
conn.connect()
conn.request('GET', f'/{"x"*10000}', '', headers)
out1 = conn.getresponse()
assert_equal(out1.status, http.client.BAD_REQUEST)
if __name__ == '__main__':
HTTPBasicsTest ().main ()

328
test/functional/interface_rest.py Executable file
View file

@ -0,0 +1,328 @@
#!/usr/bin/env python3
# Copyright (c) 2014-2019 The Bitcoin Core developers
# Copyright (c) 2019-2022 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test the REST API."""
from decimal import Decimal
from enum import Enum
from io import BytesIO
import json
from struct import pack, unpack
import http.client
import urllib.parse
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import (
assert_equal,
assert_greater_than,
assert_greater_than_or_equal,
)
from test_framework.messages import BLOCK_HEADER_SIZE
class ReqType(Enum):
JSON = 1
BIN = 2
HEX = 3
class RetType(Enum):
OBJ = 1
BYTES = 2
JSON = 3
def filter_output_indices_by_value(vouts, value):
for vout in vouts:
if vout['value'] == value:
yield vout['n']
class RESTTest (DigiByteTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 2
self.extra_args = [["-rest"], []]
self.supports_cli = False
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
def test_rest_request(self, uri, http_method='GET', req_type=ReqType.JSON, body='', status=200, ret_type=RetType.JSON):
rest_uri = '/rest' + uri
if req_type == ReqType.JSON:
rest_uri += '.json'
elif req_type == ReqType.BIN:
rest_uri += '.bin'
elif req_type == ReqType.HEX:
rest_uri += '.hex'
conn = http.client.HTTPConnection(self.url.hostname, self.url.port)
self.log.debug(f'{http_method} {rest_uri} {body}')
if http_method == 'GET':
conn.request('GET', rest_uri)
elif http_method == 'POST':
conn.request('POST', rest_uri, body)
resp = conn.getresponse()
assert_equal(resp.status, status)
if ret_type == RetType.OBJ:
return resp
elif ret_type == RetType.BYTES:
return resp.read()
elif ret_type == RetType.JSON:
return json.loads(resp.read().decode('utf-8'), parse_float=Decimal)
def run_test(self):
self.url = urllib.parse.urlparse(self.nodes[0].url)
self.log.info("Mine blocks and send Vexta to node 1")
# Random address so node1's balance doesn't increase
not_related_address = "snN9ajB2PWvKU9hMJjvUpEzxA86hmVBmQD"
self.generate(self.nodes[0], 1)
self.sync_all()
self.generatetoaddress(self.nodes[1], 100, not_related_address)
self.sync_all()
assert_equal(self.nodes[0].getbalance(), 50)
txid = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 0.1)
self.sync_all()
self.log.info("Test the /tx URI")
json_obj = self.test_rest_request(f"/tx/{txid}")
assert_equal(json_obj['txid'], txid)
# Check hex format response
hex_response = self.test_rest_request(f"/tx/{txid}", req_type=ReqType.HEX, ret_type=RetType.OBJ)
assert_greater_than_or_equal(int(hex_response.getheader('content-length')),
json_obj['size']*2)
spent = (json_obj['vin'][0]['txid'], json_obj['vin'][0]['vout']) # get the vin to later check for utxo (should be spent by then)
# get n of 0.1 outpoint
n, = filter_output_indices_by_value(json_obj['vout'], Decimal('0.1'))
spending = (txid, n)
self.log.info("Query an unspent TXO using the /getutxos URI")
self.generatetoaddress(self.nodes[1], 1, not_related_address)
self.sync_all()
bb_hash = self.nodes[0].getbestblockhash()
assert_equal(self.nodes[1].getbalance(), Decimal("0.1"))
# Check chainTip response
json_obj = self.test_rest_request(f"/getutxos/{spending[0]}-{spending[1]}")
assert_equal(json_obj['chaintipHash'], bb_hash)
# Make sure there is one utxo
assert_equal(len(json_obj['utxos']), 1)
assert_equal(json_obj['utxos'][0]['value'], Decimal('0.1'))
self.log.info("Query a spent TXO using the /getutxos URI")
json_obj = self.test_rest_request(f"/getutxos/{spent[0]}-{spent[1]}")
# Check chainTip response
assert_equal(json_obj['chaintipHash'], bb_hash)
# Make sure there is no utxo in the response because this outpoint has been spent
assert_equal(len(json_obj['utxos']), 0)
# Check bitmap
assert_equal(json_obj['bitmap'], "0")
self.log.info("Query two TXOs using the /getutxos URI")
json_obj = self.test_rest_request(f"/getutxos/{spending[0]}-{spending[1]}/{spent[0]}-{spent[1]}")
assert_equal(len(json_obj['utxos']), 1)
assert_equal(json_obj['bitmap'], "10")
self.log.info("Query the TXOs using the /getutxos URI with a binary response")
bin_request = b'\x01\x02'
for txid, n in [spending, spent]:
bin_request += bytes.fromhex(txid)
bin_request += pack("i", n)
bin_response = self.test_rest_request("/getutxos", http_method='POST', req_type=ReqType.BIN, body=bin_request, ret_type=RetType.BYTES)
output = BytesIO(bin_response)
chain_height, = unpack("<i", output.read(4))
response_hash = output.read(32)[::-1].hex()
assert_equal(bb_hash, response_hash) # check if getutxo's chaintip during calculation was fine
assert_equal(chain_height, 102) # chain height must be 102
self.log.info("Test the /getutxos URI with and without /checkmempool")
# Create a transaction, check that it's found with /checkmempool, but
# not found without. Then confirm the transaction and check that it's
# found with or without /checkmempool.
# do a tx and don't sync
txid = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 0.1)
json_obj = self.test_rest_request(f"/tx/{txid}")
# get the spent output to later check for utxo (should be spent by then)
spent = (json_obj['vin'][0]['txid'], json_obj['vin'][0]['vout'])
# get n of 0.1 outpoint
n, = filter_output_indices_by_value(json_obj['vout'], Decimal('0.1'))
spending = (txid, n)
json_obj = self.test_rest_request(f"/getutxos/{spending[0]}-{spending[1]}")
assert_equal(len(json_obj['utxos']), 0)
json_obj = self.test_rest_request(f"/getutxos/checkmempool/{spending[0]}-{spending[1]}")
assert_equal(len(json_obj['utxos']), 1)
json_obj = self.test_rest_request(f"/getutxos/{spent[0]}-{spent[1]}")
assert_equal(len(json_obj['utxos']), 1)
json_obj = self.test_rest_request(f"/getutxos/checkmempool/{spent[0]}-{spent[1]}")
assert_equal(len(json_obj['utxos']), 0)
self.generate(self.nodes[0], 1)
self.sync_all()
json_obj = self.test_rest_request(f"/getutxos/{spending[0]}-{spending[1]}")
assert_equal(len(json_obj['utxos']), 1)
json_obj = self.test_rest_request(f"/getutxos/checkmempool/{spending[0]}-{spending[1]}")
assert_equal(len(json_obj['utxos']), 1)
# Do some invalid requests
self.test_rest_request("/getutxos", http_method='POST', req_type=ReqType.JSON, body='{"checkmempool', status=400, ret_type=RetType.OBJ)
self.test_rest_request("/getutxos", http_method='POST', req_type=ReqType.BIN, body='{"checkmempool', status=400, ret_type=RetType.OBJ)
self.test_rest_request("/getutxos/checkmempool", http_method='POST', req_type=ReqType.JSON, status=400, ret_type=RetType.OBJ)
# Test limits
long_uri = '/'.join([f"{txid}-{n_}" for n_ in range(20)])
self.test_rest_request(f"/getutxos/checkmempool/{long_uri}", http_method='POST', status=400, ret_type=RetType.OBJ)
long_uri = '/'.join([f'{txid}-{n_}' for n_ in range(15)])
self.test_rest_request(f"/getutxos/checkmempool/{long_uri}", http_method='POST', status=200)
self.generate(self.nodes[0], 1) # generate block to not affect upcoming tests
self.sync_all()
self.log.info("Test the /block, /blockhashbyheight and /headers URIs")
bb_hash = self.nodes[0].getbestblockhash()
# Check result if block does not exists
assert_equal(self.test_rest_request('/headers/1/0000000000000000000000000000000000000000000000000000000000000000'), [])
self.test_rest_request('/block/0000000000000000000000000000000000000000000000000000000000000000', status=404, ret_type=RetType.OBJ)
# Check result if block is not in the active chain
self.nodes[0].invalidateblock(bb_hash)
assert_equal(self.test_rest_request(f'/headers/1/{bb_hash}'), [])
self.test_rest_request(f'/block/{bb_hash}')
self.nodes[0].reconsiderblock(bb_hash)
# Check binary format
response = self.test_rest_request(f"/block/{bb_hash}", req_type=ReqType.BIN, ret_type=RetType.OBJ)
assert_greater_than(int(response.getheader('content-length')), BLOCK_HEADER_SIZE)
response_bytes = response.read()
# Compare with block header
response_header = self.test_rest_request(f"/headers/1/{bb_hash}", req_type=ReqType.BIN, ret_type=RetType.OBJ)
assert_equal(int(response_header.getheader('content-length')), BLOCK_HEADER_SIZE)
response_header_bytes = response_header.read()
assert_equal(response_bytes[:BLOCK_HEADER_SIZE], response_header_bytes)
# Check block hex format
response_hex = self.test_rest_request(f"/block/{bb_hash}", req_type=ReqType.HEX, ret_type=RetType.OBJ)
assert_greater_than(int(response_hex.getheader('content-length')), BLOCK_HEADER_SIZE*2)
response_hex_bytes = response_hex.read().strip(b'\n')
assert_equal(response_bytes.hex().encode(), response_hex_bytes)
# Compare with hex block header
response_header_hex = self.test_rest_request(f"/headers/1/{bb_hash}", req_type=ReqType.HEX, ret_type=RetType.OBJ)
assert_greater_than(int(response_header_hex.getheader('content-length')), BLOCK_HEADER_SIZE*2)
response_header_hex_bytes = response_header_hex.read(BLOCK_HEADER_SIZE*2)
assert_equal(response_bytes[:BLOCK_HEADER_SIZE].hex().encode(), response_header_hex_bytes)
# Check json format
block_json_obj = self.test_rest_request(f"/block/{bb_hash}")
assert_equal(block_json_obj['hash'], bb_hash)
assert_equal(self.test_rest_request(f"/blockhashbyheight/{block_json_obj['height']}")['blockhash'], bb_hash)
# Check hex/bin format
resp_hex = self.test_rest_request(f"/blockhashbyheight/{block_json_obj['height']}", req_type=ReqType.HEX, ret_type=RetType.OBJ)
assert_equal(resp_hex.read().decode('utf-8').rstrip(), bb_hash)
resp_bytes = self.test_rest_request(f"/blockhashbyheight/{block_json_obj['height']}", req_type=ReqType.BIN, ret_type=RetType.BYTES)
blockhash = resp_bytes[::-1].hex()
assert_equal(blockhash, bb_hash)
# Check invalid blockhashbyheight requests
resp = self.test_rest_request("/blockhashbyheight/abc", ret_type=RetType.OBJ, status=400)
assert_equal(resp.read().decode('utf-8').rstrip(), "Invalid height: abc")
resp = self.test_rest_request("/blockhashbyheight/1000000", ret_type=RetType.OBJ, status=404)
assert_equal(resp.read().decode('utf-8').rstrip(), "Block height out of range")
resp = self.test_rest_request("/blockhashbyheight/-1", ret_type=RetType.OBJ, status=400)
assert_equal(resp.read().decode('utf-8').rstrip(), "Invalid height: -1")
self.test_rest_request("/blockhashbyheight/", ret_type=RetType.OBJ, status=400)
# Compare with json block header
json_obj = self.test_rest_request(f"/headers/1/{bb_hash}")
assert_equal(len(json_obj), 1) # ensure that there is one header in the json response
assert_equal(json_obj[0]['hash'], bb_hash) # request/response hash should be the same
# Compare with normal RPC block response
rpc_block_json = self.nodes[0].getblock(bb_hash)
for key in ['hash', 'confirmations', 'height', 'version', 'merkleroot', 'time', 'nonce', 'bits', 'difficulty', 'chainwork', 'previousblockhash']:
assert_equal(json_obj[0][key], rpc_block_json[key])
# See if we can get 5 headers in one response
self.generate(self.nodes[1], 5)
self.sync_all()
json_obj = self.test_rest_request(f"/headers/5/{bb_hash}")
assert_equal(len(json_obj), 5) # now we should have 5 header objects
self.log.info("Test tx inclusion in the /mempool and /block URIs")
# Make 3 tx and mine them on node 1
txs = []
txs.append(self.nodes[0].sendtoaddress(not_related_address, 11))
txs.append(self.nodes[0].sendtoaddress(not_related_address, 11))
txs.append(self.nodes[0].sendtoaddress(not_related_address, 11))
self.sync_all()
# Check that there are exactly 3 transactions in the TX memory pool before generating the block
json_obj = self.test_rest_request("/mempool/info")
assert_equal(json_obj['size'], 3)
# the size of the memory pool should be greater than 3x ~100 bytes
assert_greater_than(json_obj['bytes'], 300)
# Check that there are our submitted transactions in the TX memory pool
json_obj = self.test_rest_request("/mempool/contents")
for i, tx in enumerate(txs):
assert tx in json_obj
assert_equal(json_obj[tx]['spentby'], txs[i + 1:i + 2])
assert_equal(json_obj[tx]['depends'], txs[i - 1:i])
# Now mine the transactions
newblockhash = self.generate(self.nodes[1], 1)
self.sync_all()
# Check if the 3 tx show up in the new block
json_obj = self.test_rest_request(f"/block/{newblockhash[0]}")
non_coinbase_txs = {tx['txid'] for tx in json_obj['tx']
if 'coinbase' not in tx['vin'][0]}
assert_equal(non_coinbase_txs, set(txs))
# Check the same but without tx details
json_obj = self.test_rest_request(f"/block/notxdetails/{newblockhash[0]}")
for tx in txs:
assert tx in json_obj['tx']
self.log.info("Test the /chaininfo URI")
bb_hash = self.nodes[0].getbestblockhash()
json_obj = self.test_rest_request("/chaininfo")
assert_equal(json_obj['bestblockhash'], bb_hash)
if __name__ == '__main__':
RESTTest().main()

103
test/functional/interface_rpc.py Executable file
View file

@ -0,0 +1,103 @@
#!/usr/bin/env python3
# Copyright (c) 2018-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Tests some generic aspects of the RPC interface."""
import os
from test_framework.authproxy import JSONRPCException
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import assert_equal, assert_greater_than_or_equal
from threading import Thread
import subprocess
def expect_http_status(expected_http_status, expected_rpc_code,
fcn, *args):
try:
fcn(*args)
raise AssertionError(f"Expected RPC error {expected_rpc_code}, got none")
except JSONRPCException as exc:
assert_equal(exc.error["code"], expected_rpc_code)
assert_equal(exc.http_status, expected_http_status)
def test_work_queue_getblock(node, got_exceeded_error):
while not got_exceeded_error:
try:
node.cli('getrpcinfo').send_cli()
except subprocess.CalledProcessError as e:
assert_equal(e.output, 'error: Server response: Work queue depth exceeded\n')
got_exceeded_error.append(True)
class RPCInterfaceTest(DigiByteTestFramework):
def set_test_params(self):
self.num_nodes = 1
self.setup_clean_chain = True
self.supports_cli = False
def test_getrpcinfo(self):
self.log.info("Testing getrpcinfo...")
info = self.nodes[0].getrpcinfo()
assert_equal(len(info['active_commands']), 1)
command = info['active_commands'][0]
assert_equal(command['method'], 'getrpcinfo')
assert_greater_than_or_equal(command['duration'], 0)
assert_equal(info['logpath'], os.path.join(self.nodes[0].datadir, self.chain, 'debug.log'))
def test_batch_request(self):
self.log.info("Testing basic JSON-RPC batch request...")
results = self.nodes[0].batch([
# A basic request that will work fine.
{"method": "getblockcount", "id": 1},
# Request that will fail. The whole batch request should still
# work fine.
{"method": "invalidmethod", "id": 2},
# Another call that should succeed.
{"method": "getblockhash", "id": 3, "params": [0]},
])
result_by_id = {}
for res in results:
result_by_id[res["id"]] = res
assert_equal(result_by_id[1]['error'], None)
assert_equal(result_by_id[1]['result'], 0)
assert_equal(result_by_id[2]['error']['code'], -32601)
assert_equal(result_by_id[2]['result'], None)
assert_equal(result_by_id[3]['error'], None)
assert result_by_id[3]['result'] is not None
def test_http_status_codes(self):
self.log.info("Testing HTTP status codes for JSON-RPC requests...")
expect_http_status(404, -32601, self.nodes[0].invalidmethod)
expect_http_status(500, -8, self.nodes[0].getblockhash, 42)
def test_work_queue_exceeded(self):
self.log.info("Testing work queue exceeded...")
self.restart_node(0, ['-rpcworkqueue=1', '-rpcthreads=1'])
got_exceeded_error = []
threads = []
for _ in range(3):
t = Thread(target=test_work_queue_getblock, args=(self.nodes[0], got_exceeded_error))
t.start()
threads.append(t)
for t in threads:
t.join()
def run_test(self):
self.test_getrpcinfo()
self.test_batch_request()
self.test_http_status_codes()
self.test_work_queue_exceeded()
if __name__ == '__main__':
RPCInterfaceTest().main()

571
test/functional/interface_zmq.py Executable file
View file

@ -0,0 +1,571 @@
#!/usr/bin/env python3
# Copyright (c) 2015-2020 The Bitcoin Core developers
# Copyright (c) 2015-2023 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test the ZMQ notification interface."""
import struct
from test_framework.address import (
ADDRESS_BCRT1_P2WSH_OP_TRUE,
ADDRESS_BCRT1_UNSPENDABLE,
)
from test_framework.blocktools import (
add_witness_commitment,
create_block,
create_coinbase,
)
from test_framework.test_framework import DigiByteTestFramework
from test_framework.messages import (
CTransaction,
hash256,
tx_from_hex,
)
from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
)
from io import BytesIO
from time import sleep
# Test may be skipped and not have zmq installed
try:
import zmq
except ImportError:
pass
def hash256_reversed(byte_str):
return hash256(byte_str)[::-1]
class ZMQSubscriber:
def __init__(self, socket, topic):
self.sequence = None # no sequence number received yet
self.socket = socket
self.topic = topic
self.socket.setsockopt(zmq.SUBSCRIBE, self.topic)
# Receive message from publisher and verify that topic and sequence match
def _receive_from_publisher_and_check(self):
topic, body, seq = self.socket.recv_multipart()
# Topic should match the subscriber topic.
assert_equal(topic, self.topic)
# Sequence should be incremental.
received_seq = struct.unpack('<I', seq)[-1]
if self.sequence is None:
self.sequence = received_seq
else:
assert_equal(received_seq, self.sequence)
self.sequence += 1
return body
def receive(self):
return self._receive_from_publisher_and_check()
def receive_sequence(self):
body = self._receive_from_publisher_and_check()
hash = body[:32].hex()
label = chr(body[32])
mempool_sequence = None if len(body) != 32+1+8 else struct.unpack("<Q", body[32+1:])[0]
if mempool_sequence is not None:
assert label == "A" or label == "R"
else:
assert label == "D" or label == "C"
return (hash, label, mempool_sequence)
class ZMQTestSetupBlock:
"""Helper class for setting up a ZMQ test via the "sync up" procedure.
Generates a block on the specified node on instantiation and provides a
method to check whether a ZMQ notification matches, i.e. the event was
caused by this generated block. Assumes that a notification either contains
the generated block's hash, it's (coinbase) transaction id, the raw block or
raw transaction data.
"""
def __init__(self, test_framework, node):
self.block_hash = test_framework.generate(node, 1, sync_fun=test_framework.no_op)[0]
coinbase = node.getblock(self.block_hash, 2)['tx'][0]
self.tx_hash = coinbase['txid']
self.raw_tx = coinbase['hex']
self.raw_block = node.getblock(self.block_hash, 0)
def caused_notification(self, notification):
return (
self.block_hash in notification
or self.tx_hash in notification
or self.raw_block in notification
or self.raw_tx in notification
)
class ZMQTest (DigiByteTestFramework):
def set_test_params(self):
self.num_nodes = 2
if self.is_wallet_compiled():
self.requires_wallet = True
# This test isn't testing txn relay/timing, so set whitelist on the
# peers for instant txn relay. This speeds up the test run time 2-3x.
self.extra_args = [["-whitelist=noban@127.0.0.1"]] * self.num_nodes
def skip_test_if_missing_module(self):
self.skip_if_no_py3_zmq()
self.skip_if_no_digibyted_zmq()
def run_test(self):
self.ctx = zmq.Context()
try:
self.test_basic()
self.test_sequence()
self.test_mempool_sync()
self.test_reorg()
self.test_multiple_interfaces()
finally:
# Destroy the ZMQ context.
self.log.debug("Destroying ZMQ context")
self.ctx.destroy(linger=None)
# Restart node with the specified zmq notifications enabled, subscribe to
# all of them and return the corresponding ZMQSubscriber objects.
def setup_zmq_test(self, services, *, recv_timeout=60, sync_blocks=True):
subscribers = []
for topic, address in services:
socket = self.ctx.socket(zmq.SUB)
subscribers.append(ZMQSubscriber(socket, topic.encode()))
self.restart_node(0, ["-zmqpub%s=%s" % (topic, address) for topic, address in services] +
self.extra_args[0])
for i, sub in enumerate(subscribers):
sub.socket.connect(services[i][1])
# Ensure that all zmq publisher notification interfaces are ready by
# running the following "sync up" procedure:
# 1. Generate a block on the node
# 2. Try to receive the corresponding notification on all subscribers
# 3. If all subscribers get the message within the timeout (1 second),
# we are done, otherwise repeat starting from step 1
for sub in subscribers:
sub.socket.set(zmq.RCVTIMEO, 1000)
while True:
test_block = ZMQTestSetupBlock(self, self.nodes[0])
recv_failed = False
for sub in subscribers:
try:
while not test_block.caused_notification(sub.receive().hex()):
self.log.debug("Ignoring sync-up notification for previously generated block.")
except zmq.error.Again:
self.log.debug("Didn't receive sync-up notification, trying again.")
recv_failed = True
if not recv_failed:
self.log.debug("ZMQ sync-up completed, all subscribers are ready.")
break
# set subscriber's desired timeout for the test
for sub in subscribers:
sub.socket.set(zmq.RCVTIMEO, recv_timeout*1000)
self.connect_nodes(0, 1)
if sync_blocks:
self.sync_blocks()
return subscribers
def test_basic(self):
# Invalid zmq arguments don't take down the node, see #17185.
self.restart_node(0, ["-zmqpubrawtx=foo", "-zmqpubhashtx=bar"])
address = 'tcp://127.0.0.1:28332'
subs = self.setup_zmq_test([(topic, address) for topic in ["hashblock", "hashtx", "rawblock", "rawtx"]])
hashblock = subs[0]
hashtx = subs[1]
rawblock = subs[2]
rawtx = subs[3]
num_blocks = 5
self.log.info("Generate %(n)d blocks (and %(n)d coinbase txes)" % {"n": num_blocks})
genhashes = self.generatetoaddress(self.nodes[0], num_blocks, ADDRESS_BCRT1_UNSPENDABLE)
self.sync_all()
for x in range(num_blocks):
# Should receive the coinbase txid.
txid = hashtx.receive()
# Should receive the coinbase raw transaction.
hex = rawtx.receive()
tx = CTransaction()
tx.deserialize(BytesIO(hex))
tx.calc_sha256()
assert_equal(tx.hash, txid.hex())
# Should receive the generated raw block.
block = rawblock.receive()
assert_equal(genhashes[x], hash256_reversed(block[:80]).hex())
# Should receive the generated block hash.
hash = hashblock.receive().hex()
assert_equal(genhashes[x], hash)
# The block should only have the coinbase txid.
assert_equal([txid.hex()], self.nodes[1].getblock(hash)["tx"])
if self.is_wallet_compiled():
self.log.info("Wait for tx from second node")
payment_txid = self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 1.0)
self.sync_all()
# Should receive the broadcasted txid.
txid = hashtx.receive()
assert_equal(payment_txid, txid.hex())
# Should receive the broadcasted raw transaction.
hex = rawtx.receive()
assert_equal(payment_txid, hash256_reversed(hex).hex())
# Mining the block with this tx should result in second notification
# after coinbase tx notification
self.generatetoaddress(self.nodes[0], 1, ADDRESS_BCRT1_UNSPENDABLE)
hashtx.receive()
txid = hashtx.receive()
assert_equal(payment_txid, txid.hex())
self.log.info("Test the getzmqnotifications RPC")
assert_equal(self.nodes[0].getzmqnotifications(), [
{"type": "pubhashblock", "address": address, "hwm": 1000},
{"type": "pubhashtx", "address": address, "hwm": 1000},
{"type": "pubrawblock", "address": address, "hwm": 1000},
{"type": "pubrawtx", "address": address, "hwm": 1000},
])
assert_equal(self.nodes[1].getzmqnotifications(), [])
def test_reorg(self):
if not self.is_wallet_compiled():
self.log.info("Skipping reorg test because wallet is disabled")
return
address = 'tcp://127.0.0.1:28333'
# Should only notify the tip if a reorg occurs
hashblock, hashtx = self.setup_zmq_test(
[(topic, address) for topic in ["hashblock", "hashtx"]],
recv_timeout=2) # 2 second timeout to check end of notifications
self.disconnect_nodes(0, 1)
# Generate 1 block in nodes[0] with 1 mempool tx and receive all notifications
payment_txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1.0)
disconnect_block = self.generatetoaddress(self.nodes[0], 1, ADDRESS_BCRT1_UNSPENDABLE, sync_fun=self.no_op)[0]
disconnect_cb = self.nodes[0].getblock(disconnect_block)["tx"][0]
assert_equal(self.nodes[0].getbestblockhash(), hashblock.receive().hex())
assert_equal(hashtx.receive().hex(), payment_txid)
assert_equal(hashtx.receive().hex(), disconnect_cb)
# Generate 2 blocks in nodes[1] to a different address to ensure split
connect_blocks = self.generatetoaddress(self.nodes[1], 2, ADDRESS_BCRT1_P2WSH_OP_TRUE, sync_fun=self.no_op)
# nodes[0] will reorg chain after connecting back nodes[1]
self.connect_nodes(0, 1)
self.sync_blocks() # tx in mempool valid but not advertised
# Should receive nodes[1] tip
assert_equal(self.nodes[1].getbestblockhash(), hashblock.receive().hex())
# During reorg:
# Get old payment transaction notification from disconnect and disconnected cb
assert_equal(hashtx.receive().hex(), payment_txid)
assert_equal(hashtx.receive().hex(), disconnect_cb)
# And the payment transaction again due to mempool entry
assert_equal(hashtx.receive().hex(), payment_txid)
assert_equal(hashtx.receive().hex(), payment_txid)
# And the new connected coinbases
for i in [0, 1]:
assert_equal(hashtx.receive().hex(), self.nodes[1].getblock(connect_blocks[i])["tx"][0])
# If we do a simple invalidate we announce the disconnected coinbase
self.nodes[0].invalidateblock(connect_blocks[1])
assert_equal(hashtx.receive().hex(), self.nodes[1].getblock(connect_blocks[1])["tx"][0])
# And the current tip
assert_equal(hashtx.receive().hex(), self.nodes[1].getblock(connect_blocks[0])["tx"][0])
def test_sequence(self):
"""
Sequence zmq notifications give every blockhash and txhash in order
of processing, regardless of IBD, re-orgs, etc.
Format of messages:
<32-byte hash>C : Blockhash connected
<32-byte hash>D : Blockhash disconnected
<32-byte hash>R<8-byte LE uint> : Transactionhash removed from mempool for non-block inclusion reason
<32-byte hash>A<8-byte LE uint> : Transactionhash added mempool
"""
self.log.info("Testing 'sequence' publisher")
[seq] = self.setup_zmq_test([("sequence", "tcp://127.0.0.1:28333")])
self.disconnect_nodes(0, 1)
# Mempool sequence number starts at 1
seq_num = 1
# Generate 1 block in nodes[0] and receive all notifications
dc_block = self.generatetoaddress(self.nodes[0], 1, ADDRESS_BCRT1_UNSPENDABLE, sync_fun=self.no_op)[0]
# Note: We are not notified of any block transactions, coinbase or mined
assert_equal((self.nodes[0].getbestblockhash(), "C", None), seq.receive_sequence())
# Generate 2 blocks in nodes[1] to a different address to ensure a chain split
self.generatetoaddress(self.nodes[1], 2, ADDRESS_BCRT1_P2WSH_OP_TRUE, sync_fun=self.no_op)
# nodes[0] will reorg chain after connecting back nodes[1]
self.connect_nodes(0, 1)
# Then we receive all block (dis)connect notifications for the 2 block reorg
assert_equal((dc_block, "D", None), seq.receive_sequence())
block_count = self.nodes[1].getblockcount()
assert_equal((self.nodes[1].getblockhash(block_count-1), "C", None), seq.receive_sequence())
assert_equal((self.nodes[1].getblockhash(block_count), "C", None), seq.receive_sequence())
# Rest of test requires wallet functionality
if self.is_wallet_compiled():
self.log.info("Wait for tx from second node")
payment_txid = self.nodes[1].sendtoaddress(address=self.nodes[0].getnewaddress(), amount=5.0, replaceable=True)
self.sync_all()
self.log.info("Testing sequence notifications with mempool sequence values")
# Should receive the broadcasted txid.
assert_equal((payment_txid, "A", seq_num), seq.receive_sequence())
seq_num += 1
self.log.info("Testing RBF notification")
# Replace it to test eviction/addition notification
rbf_info = self.nodes[1].bumpfee(payment_txid)
self.sync_all()
assert_equal((payment_txid, "R", seq_num), seq.receive_sequence())
seq_num += 1
assert_equal((rbf_info["txid"], "A", seq_num), seq.receive_sequence())
seq_num += 1
# Doesn't get published when mined, make a block and tx to "flush" the possibility
# though the mempool sequence number does go up by the number of transactions
# removed from the mempool by the block mining it.
mempool_size = len(self.nodes[0].getrawmempool())
c_block = self.generatetoaddress(self.nodes[0], 1, ADDRESS_BCRT1_UNSPENDABLE, sync_fun=self.no_op)[0]
# Make sure the number of mined transactions matches the number of txs out of mempool
mempool_size_delta = mempool_size - len(self.nodes[0].getrawmempool())
assert_equal(len(self.nodes[0].getblock(c_block)["tx"])-1, mempool_size_delta)
seq_num += mempool_size_delta
payment_txid_2 = self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 1.0)
self.sync_all()
assert_equal((c_block, "C", None), seq.receive_sequence())
assert_equal((payment_txid_2, "A", seq_num), seq.receive_sequence())
seq_num += 1
# Spot check getrawmempool results that they only show up when asked for
assert type(self.nodes[0].getrawmempool()) is list
assert type(self.nodes[0].getrawmempool(mempool_sequence=False)) is list
assert "mempool_sequence" not in self.nodes[0].getrawmempool(verbose=True)
assert_raises_rpc_error(-8, "Verbose results cannot contain mempool sequence values.", self.nodes[0].getrawmempool, True, True)
assert_equal(self.nodes[0].getrawmempool(mempool_sequence=True)["mempool_sequence"], seq_num)
self.log.info("Testing reorg notifications")
# Manually invalidate the last block to test mempool re-entry
# N.B. This part could be made more lenient in exact ordering
# since it greatly depends on inner-workings of blocks/mempool
# during "deep" re-orgs. Probably should "re-construct"
# blockchain/mempool state from notifications instead.
block_count = self.nodes[0].getblockcount()
best_hash = self.nodes[0].getbestblockhash()
self.nodes[0].invalidateblock(best_hash)
sleep(2) # Bit of room to make sure transaction things happened
# Make sure getrawmempool mempool_sequence results aren't "queued" but immediately reflective
# of the time they were gathered.
assert self.nodes[0].getrawmempool(mempool_sequence=True)["mempool_sequence"] > seq_num
assert_equal((best_hash, "D", None), seq.receive_sequence())
assert_equal((rbf_info["txid"], "A", seq_num), seq.receive_sequence())
seq_num += 1
# Other things may happen but aren't wallet-deterministic so we don't test for them currently
self.nodes[0].reconsiderblock(best_hash)
self.generatetoaddress(self.nodes[1], 1, ADDRESS_BCRT1_UNSPENDABLE)
self.log.info("Evict mempool transaction by block conflict")
orig_txid = self.nodes[0].sendtoaddress(address=self.nodes[0].getnewaddress(), amount=1.0, replaceable=True)
# More to be simply mined
more_tx = []
for _ in range(5):
more_tx.append(self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 0.1))
raw_tx = self.nodes[0].getrawtransaction(orig_txid)
bump_info = self.nodes[0].bumpfee(orig_txid)
# Mine the pre-bump tx
block = create_block(int(self.nodes[0].getbestblockhash(), 16), create_coinbase(self.nodes[0].getblockcount()+1))
tx = tx_from_hex(raw_tx)
block.vtx.append(tx)
for txid in more_tx:
tx = tx_from_hex(self.nodes[0].getrawtransaction(txid))
block.vtx.append(tx)
add_witness_commitment(block)
block.solve()
assert_equal(self.nodes[0].submitblock(block.serialize().hex()), None)
tip = self.nodes[0].getbestblockhash()
assert_equal(int(tip, 16), block.sha256)
orig_txid_2 = self.nodes[0].sendtoaddress(address=self.nodes[0].getnewaddress(), amount=1.0, replaceable=True)
# Flush old notifications until evicted tx original entry
(hash_str, label, mempool_seq) = seq.receive_sequence()
while hash_str != orig_txid:
(hash_str, label, mempool_seq) = seq.receive_sequence()
mempool_seq += 1
# Added original tx
assert_equal(label, "A")
# More transactions to be simply mined
for i in range(len(more_tx)):
assert_equal((more_tx[i], "A", mempool_seq), seq.receive_sequence())
mempool_seq += 1
# Bumped by rbf
assert_equal((orig_txid, "R", mempool_seq), seq.receive_sequence())
mempool_seq += 1
assert_equal((bump_info["txid"], "A", mempool_seq), seq.receive_sequence())
mempool_seq += 1
# Conflict announced first, then block
assert_equal((bump_info["txid"], "R", mempool_seq), seq.receive_sequence())
mempool_seq += 1
assert_equal((tip, "C", None), seq.receive_sequence())
mempool_seq += len(more_tx)
# Last tx
assert_equal((orig_txid_2, "A", mempool_seq), seq.receive_sequence())
mempool_seq += 1
self.generatetoaddress(self.nodes[0], 1, ADDRESS_BCRT1_UNSPENDABLE)
def test_mempool_sync(self):
"""
Use sequence notification plus getrawmempool sequence results to "sync mempool"
"""
if not self.is_wallet_compiled():
self.log.info("Skipping mempool sync test")
return
self.log.info("Testing 'mempool sync' usage of sequence notifier")
[seq] = self.setup_zmq_test([("sequence", "tcp://127.0.0.1:28333")])
# In-memory counter, should always start at 1
next_mempool_seq = self.nodes[0].getrawmempool(mempool_sequence=True)["mempool_sequence"]
assert_equal(next_mempool_seq, 1)
# Some transactions have been happening but we aren't consuming zmq notifications yet
# or we lost a ZMQ message somehow and want to start over
txids = []
num_txs = 5
for _ in range(num_txs):
txids.append(self.nodes[1].sendtoaddress(address=self.nodes[0].getnewaddress(), amount=1.0, replaceable=True))
self.sync_all()
# 1) Consume backlog until we get a mempool sequence number
(hash_str, label, zmq_mem_seq) = seq.receive_sequence()
while zmq_mem_seq is None:
(hash_str, label, zmq_mem_seq) = seq.receive_sequence()
assert label == "A" or label == "R"
assert hash_str is not None
# 2) We need to "seed" our view of the mempool
mempool_snapshot = self.nodes[0].getrawmempool(mempool_sequence=True)
mempool_view = set(mempool_snapshot["txids"])
get_raw_seq = mempool_snapshot["mempool_sequence"]
assert_equal(get_raw_seq, 6)
# Snapshot may be too old compared to zmq message we read off latest
while zmq_mem_seq >= get_raw_seq:
sleep(2)
mempool_snapshot = self.nodes[0].getrawmempool(mempool_sequence=True)
mempool_view = set(mempool_snapshot["txids"])
get_raw_seq = mempool_snapshot["mempool_sequence"]
# Things continue to happen in the "interim" while waiting for snapshot results
# We have node 0 do all these to avoid p2p races with RBF announcements
for _ in range(num_txs):
txids.append(self.nodes[0].sendtoaddress(address=self.nodes[0].getnewaddress(), amount=0.1, replaceable=True))
self.nodes[0].bumpfee(txids[-1])
self.sync_all()
self.generatetoaddress(self.nodes[0], 1, ADDRESS_BCRT1_UNSPENDABLE)
final_txid = self.nodes[0].sendtoaddress(address=self.nodes[0].getnewaddress(), amount=0.1, replaceable=True)
# 3) Consume ZMQ backlog until we get to "now" for the mempool snapshot
while True:
if zmq_mem_seq == get_raw_seq - 1:
break
(hash_str, label, mempool_sequence) = seq.receive_sequence()
if mempool_sequence is not None:
zmq_mem_seq = mempool_sequence
if zmq_mem_seq > get_raw_seq:
raise Exception("We somehow jumped mempool sequence numbers! zmq_mem_seq: {} > get_raw_seq: {}".format(zmq_mem_seq, get_raw_seq))
# 4) Moving forward, we apply the delta to our local view
# remaining txs(5) + 1 rbf(A+R) + 1 block connect + 1 final tx
expected_sequence = get_raw_seq
r_gap = 0
for _ in range(num_txs + 2 + 1 + 1):
(hash_str, label, mempool_sequence) = seq.receive_sequence()
if mempool_sequence is not None:
if mempool_sequence != expected_sequence:
# Detected "R" gap, means this a conflict eviction, and mempool tx are being evicted before its
# position in the incoming block message "C"
if label == "R":
assert mempool_sequence > expected_sequence
r_gap += mempool_sequence - expected_sequence
else:
raise Exception("WARNING: txhash has unexpected mempool sequence value: {} vs expected {}".format(mempool_sequence, expected_sequence))
if label == "A":
assert hash_str not in mempool_view
mempool_view.add(hash_str)
expected_sequence = mempool_sequence + 1
elif label == "R":
assert hash_str in mempool_view
mempool_view.remove(hash_str)
expected_sequence = mempool_sequence + 1
elif label == "C":
# (Attempt to) remove all txids from known block connects
block_txids = self.nodes[0].getblock(hash_str)["tx"][1:]
for txid in block_txids:
if txid in mempool_view:
expected_sequence += 1
mempool_view.remove(txid)
expected_sequence -= r_gap
r_gap = 0
elif label == "D":
# Not useful for mempool tracking per se
continue
else:
raise Exception("Unexpected ZMQ sequence label!")
assert_equal(self.nodes[0].getrawmempool(), [final_txid])
assert_equal(self.nodes[0].getrawmempool(mempool_sequence=True)["mempool_sequence"], expected_sequence)
# 5) If you miss a zmq/mempool sequence number, go back to step (2)
self.generatetoaddress(self.nodes[0], 1, ADDRESS_BCRT1_UNSPENDABLE)
def test_multiple_interfaces(self):
# Set up two subscribers with different addresses
# (note that after the reorg test, syncing would fail due to different
# chain lengths on node0 and node1; for this test we only need node0, so
# we can disable syncing blocks on the setup)
subscribers = self.setup_zmq_test([
("hashblock", "tcp://127.0.0.1:28334"),
("hashblock", "tcp://127.0.0.1:28335"),
], sync_blocks=False)
# Generate 1 block in nodes[0] and receive all notifications
self.generatetoaddress(self.nodes[0], 1, ADDRESS_BCRT1_UNSPENDABLE)
# Should receive the same block hash on both subscribers
assert_equal(self.nodes[0].getbestblockhash(), subscribers[0].receive().hex())
assert_equal(self.nodes[0].getbestblockhash(), subscribers[1].receive().hex())
if __name__ == '__main__':
ZMQTest().main()

348
test/functional/mempool_accept.py Executable file
View file

@ -0,0 +1,348 @@
#!/usr/bin/env python3
# Copyright (c) 2017-2020 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test mempool acceptance of raw transactions."""
from decimal import Decimal
import math
from test_framework.test_framework import DigiByteTestFramework
from test_framework.key import ECKey
from test_framework.messages import (
BIP125_SEQUENCE_NUMBER,
COIN,
COutPoint,
CTxIn,
CTxOut,
MAX_BLOCK_BASE_SIZE,
MAX_MONEY,
tx_from_hex,
)
from test_framework.script import (
CScript,
OP_0,
OP_2,
OP_3,
OP_CHECKMULTISIG,
OP_HASH160,
OP_RETURN,
)
from test_framework.script_util import (
script_to_p2sh_script,
)
from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
)
class MempoolAcceptanceTest(DigiByteTestFramework):
def set_test_params(self):
self.num_nodes = 1
self.extra_args = [[
'-txindex','-permitbaremultisig=0',
]] * self.num_nodes
self.supports_cli = False
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
def check_mempool_result(self, result_expected, *args, **kwargs):
"""Wrapper to check result of testmempoolaccept on node_0's mempool"""
result_test = self.nodes[0].testmempoolaccept(*args, **kwargs)
for r in result_test:
r.pop('wtxid') # Skip check for now
assert_equal(result_expected, result_test)
assert_equal(self.nodes[0].getmempoolinfo()['size'], self.mempool_size) # Must not change mempool state
def run_test(self):
node = self.nodes[0]
self.log.info('Start with empty mempool, and 200 blocks')
self.mempool_size = 0
assert_equal(node.getblockcount(), 200)
assert_equal(node.getmempoolinfo()['size'], self.mempool_size)
self.generatetoaddress(node, 101, node.getnewaddress())
coins = node.listunspent()
self.log.info('Should not accept garbage to testmempoolaccept')
assert_raises_rpc_error(-3, 'Expected type array, got string', lambda: node.testmempoolaccept(rawtxs='ff00baar'))
assert_raises_rpc_error(-8, 'Array must contain between 1 and 25 transactions.', lambda: node.testmempoolaccept(rawtxs=['ff22']*26))
assert_raises_rpc_error(-8, 'Array must contain between 1 and 25 transactions.', lambda: node.testmempoolaccept(rawtxs=[]))
assert_raises_rpc_error(-22, 'TX decode failed', lambda: node.testmempoolaccept(rawtxs=['ff00baar']))
self.log.info('A transaction already in the blockchain')
coin = coins.pop() # Pick a random coin(base) to spend
raw_tx_in_block = node.signrawtransactionwithwallet(node.createrawtransaction(
inputs=[{'txid': coin['txid'], 'vout': coin['vout']}],
outputs=[{node.getnewaddress(): 0.3}, {node.getnewaddress(): 49}],
))['hex']
txid_in_block = node.sendrawtransaction(hexstring=raw_tx_in_block, maxfeerate=0)
self.generate(node, 1)
self.mempool_size = 0
self.check_mempool_result(
result_expected=[{'txid': txid_in_block, 'allowed': False, 'reject-reason': 'txn-already-known'}],
rawtxs=[raw_tx_in_block],
)
self.log.info('A transaction not in the mempool')
fee = Decimal('0.0007')
raw_tx_0 = node.signrawtransactionwithwallet(node.createrawtransaction(
inputs=[{"txid": txid_in_block, "vout": 0, "sequence": BIP125_SEQUENCE_NUMBER}], # RBF is used later
outputs=[{node.getnewaddress(): Decimal('0.3') - fee}],
))['hex']
tx = tx_from_hex(raw_tx_0)
txid_0 = tx.rehash()
self.check_mempool_result(
result_expected=[{'txid': txid_0, 'allowed': True, 'vsize': tx.get_vsize(), 'fees': {'base': fee}}],
rawtxs=[raw_tx_0],
)
self.log.info('A final transaction not in the mempool')
coin = coins.pop() # Pick a random coin(base) to spend
output_amount = Decimal('0.025')
raw_tx_final = node.signrawtransactionwithwallet(node.createrawtransaction(
inputs=[{'txid': coin['txid'], 'vout': coin['vout'], "sequence": 0xffffffff}], # SEQUENCE_FINAL
outputs=[{node.getnewaddress(): output_amount}],
locktime=node.getblockcount() + 2000, # Can be anything
))['hex']
tx = tx_from_hex(raw_tx_final)
fee_expected = coin['amount'] - output_amount
self.check_mempool_result(
result_expected=[{'txid': tx.rehash(), 'allowed': True, 'vsize': tx.get_vsize(), 'fees': {'base': fee_expected}}],
rawtxs=[tx.serialize().hex()],
maxfeerate=0,
)
node.sendrawtransaction(hexstring=raw_tx_final, maxfeerate=0)
self.mempool_size += 1
self.log.info('A transaction in the mempool')
node.sendrawtransaction(hexstring=raw_tx_0)
self.mempool_size += 1
self.check_mempool_result(
result_expected=[{'txid': txid_0, 'allowed': False, 'reject-reason': 'txn-already-in-mempool'}],
rawtxs=[raw_tx_0],
)
self.log.info('A transaction that replaces a mempool transaction')
tx = tx_from_hex(raw_tx_0)
tx.vout[0].nValue -= int(fee * COIN) # Double the fee
tx.vin[0].nSequence = BIP125_SEQUENCE_NUMBER + 1 # Now, opt out of RBF
raw_tx_0 = node.signrawtransactionwithwallet(tx.serialize().hex())['hex']
tx = tx_from_hex(raw_tx_0)
txid_0 = tx.rehash()
self.check_mempool_result(
result_expected=[{'txid': txid_0, 'allowed': True, 'vsize': tx.get_vsize(), 'fees': {'base': (2 * fee)}}],
rawtxs=[raw_tx_0],
)
self.log.info('A transaction that conflicts with an unconfirmed tx')
# Send the transaction that replaces the mempool transaction and opts out of replaceability
node.sendrawtransaction(hexstring=tx.serialize().hex(), maxfeerate=0)
# take original raw_tx_0
tx = tx_from_hex(raw_tx_0)
tx.vout[0].nValue -= int(4 * fee * COIN) # Set more fee
# skip re-signing the tx
self.check_mempool_result(
result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'txn-mempool-conflict'}],
rawtxs=[tx.serialize().hex()],
maxfeerate=0,
)
self.log.info('A transaction with missing inputs, that never existed')
tx = tx_from_hex(raw_tx_0)
tx.vin[0].prevout = COutPoint(hash=int('ff' * 32, 16), n=14)
# skip re-signing the tx
self.check_mempool_result(
result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'missing-inputs'}],
rawtxs=[tx.serialize().hex()],
)
self.log.info('A transaction with missing inputs, that existed once in the past')
tx = tx_from_hex(raw_tx_0)
tx.vin[0].prevout.n = 1 # Set vout to 1, to spend the other outpoint (71999 coins) of the in-chain-tx we want to double spend
raw_tx_1 = node.signrawtransactionwithwallet(tx.serialize().hex())['hex']
txid_1 = node.sendrawtransaction(hexstring=raw_tx_1, maxfeerate=0)
# Now spend both to "clearly hide" the outputs, ie. remove the coins from the utxo set by spending them
raw_tx_spend_both = node.signrawtransactionwithwallet(node.createrawtransaction(
inputs=[
{'txid': txid_0, 'vout': 0},
{'txid': txid_1, 'vout': 0},
],
outputs=[{node.getnewaddress(): 0.1}]
))['hex']
txid_spend_both = node.sendrawtransaction(hexstring=raw_tx_spend_both, maxfeerate=0)
self.generate(node, 1)
self.mempool_size = 0
# Now see if we can add the coins back to the utxo set by sending the exact txs again
self.check_mempool_result(
result_expected=[{'txid': txid_0, 'allowed': False, 'reject-reason': 'missing-inputs'}],
rawtxs=[raw_tx_0],
)
self.check_mempool_result(
result_expected=[{'txid': txid_1, 'allowed': False, 'reject-reason': 'missing-inputs'}],
rawtxs=[raw_tx_1],
)
self.log.info('Create a signed "reference" tx for later use')
raw_tx_reference = node.signrawtransactionwithwallet(node.createrawtransaction(
inputs=[{'txid': txid_spend_both, 'vout': 0}],
outputs=[{node.getnewaddress(): 0.05}],
))['hex']
tx = tx_from_hex(raw_tx_reference)
# Reference tx should be valid on itself
self.check_mempool_result(
result_expected=[{'txid': tx.rehash(), 'allowed': True, 'vsize': tx.get_vsize(), 'fees': { 'base': Decimal('0.1') - Decimal('0.05')}}],
rawtxs=[tx.serialize().hex()],
maxfeerate=0,
)
self.log.info('A transaction with no outputs')
tx = tx_from_hex(raw_tx_reference)
tx.vout = []
# Skip re-signing the transaction for context independent checks from now on
# tx = tx_from_hex(node.signrawtransactionwithwallet(tx.serialize().hex())['hex'])
self.check_mempool_result(
result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'bad-txns-vout-empty'}],
rawtxs=[tx.serialize().hex()],
)
self.log.info('A really large transaction')
tx = tx_from_hex(raw_tx_reference)
tx.vin = [tx.vin[0]] * math.ceil(MAX_BLOCK_BASE_SIZE / len(tx.vin[0].serialize()))
self.check_mempool_result(
result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'bad-txns-oversize'}],
rawtxs=[tx.serialize().hex()],
)
self.log.info('A transaction with negative output value')
tx = tx_from_hex(raw_tx_reference)
tx.vout[0].nValue *= -1
self.check_mempool_result(
result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'bad-txns-vout-negative'}],
rawtxs=[tx.serialize().hex()],
)
# The following two validations prevent overflow of the output amounts (see CVE-2010-5139).
self.log.info('A transaction with too large output value')
tx = tx_from_hex(raw_tx_reference)
tx.vout[0].nValue = MAX_MONEY + 1
self.check_mempool_result(
result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'bad-txns-vout-toolarge'}],
rawtxs=[tx.serialize().hex()],
)
self.log.info('A transaction with too large sum of output values')
tx = tx_from_hex(raw_tx_reference)
tx.vout = [tx.vout[0]] * 2
tx.vout[0].nValue = MAX_MONEY
self.check_mempool_result(
result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'bad-txns-txouttotal-toolarge'}],
rawtxs=[tx.serialize().hex()],
)
self.log.info('A transaction with duplicate inputs')
tx = tx_from_hex(raw_tx_reference)
tx.vin = [tx.vin[0]] * 2
self.check_mempool_result(
result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'bad-txns-inputs-duplicate'}],
rawtxs=[tx.serialize().hex()],
)
self.log.info('A non-coinbase transaction with coinbase-like outpoint')
tx = tx_from_hex(raw_tx_reference)
tx.vin.append(CTxIn(COutPoint(hash=0, n=0xffffffff)))
self.check_mempool_result(
result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'bad-txns-prevout-null'}],
rawtxs=[tx.serialize().hex()],
)
self.log.info('A coinbase transaction')
# Pick the input of the first tx we signed, so it has to be a coinbase tx
raw_tx_coinbase_spent = node.getrawtransaction(txid=node.decoderawtransaction(hexstring=raw_tx_in_block)['vin'][0]['txid'])
tx = tx_from_hex(raw_tx_coinbase_spent)
self.check_mempool_result(
result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'coinbase'}],
rawtxs=[tx.serialize().hex()],
)
self.log.info('Some nonstandard transactions')
tx = tx_from_hex(raw_tx_reference)
tx.nVersion = 3 # A version currently non-standard
self.check_mempool_result(
result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'version'}],
rawtxs=[tx.serialize().hex()],
)
tx = tx_from_hex(raw_tx_reference)
tx.vout[0].scriptPubKey = CScript([OP_0]) # Some non-standard script
self.check_mempool_result(
result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'scriptpubkey'}],
rawtxs=[tx.serialize().hex()],
)
tx = tx_from_hex(raw_tx_reference)
key = ECKey()
key.generate()
pubkey = key.get_pubkey().get_bytes()
tx.vout[0].scriptPubKey = CScript([OP_2, pubkey, pubkey, pubkey, OP_3, OP_CHECKMULTISIG]) # Some bare multisig script (2-of-3)
self.check_mempool_result(
result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'bare-multisig'}],
rawtxs=[tx.serialize().hex()],
)
tx = tx_from_hex(raw_tx_reference)
tx.vin[0].scriptSig = CScript([OP_HASH160]) # Some not-pushonly scriptSig
self.check_mempool_result(
result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'scriptsig-not-pushonly'}],
rawtxs=[tx.serialize().hex()],
)
tx = tx_from_hex(raw_tx_reference)
tx.vin[0].scriptSig = CScript([b'a' * 1648]) # Some too large scriptSig (>1650 bytes)
self.check_mempool_result(
result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'scriptsig-size'}],
rawtxs=[tx.serialize().hex()],
)
tx = tx_from_hex(raw_tx_reference)
output_p2sh_burn = CTxOut(nValue=540, scriptPubKey=script_to_p2sh_script(b'burn'))
num_scripts = 100000 // len(output_p2sh_burn.serialize()) # Use enough outputs to make the tx too large for our policy
tx.vout = [output_p2sh_burn] * num_scripts
self.check_mempool_result(
result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'tx-size'}],
rawtxs=[tx.serialize().hex()],
)
tx = tx_from_hex(raw_tx_reference)
tx.vout[0] = output_p2sh_burn
tx.vout[0].nValue -= 1 # Make output smaller, such that it is dust for our policy
self.check_mempool_result(
result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'dust'}],
rawtxs=[tx.serialize().hex()],
)
tx = tx_from_hex(raw_tx_reference)
tx.vout[0].scriptPubKey = CScript([OP_RETURN, b'\xff'])
tx.vout = [tx.vout[0]] * 2
self.check_mempool_result(
result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'multi-op-return'}],
rawtxs=[tx.serialize().hex()],
)
self.log.info('A timelocked transaction')
tx = tx_from_hex(raw_tx_reference)
tx.vin[0].nSequence -= 1 # Should be non-max, so locktime is not ignored
tx.nLockTime = node.getblockcount() + 1
self.check_mempool_result(
result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'non-final'}],
rawtxs=[tx.serialize().hex()],
)
self.log.info('A transaction that is locked by BIP68 sequence logic')
tx = tx_from_hex(raw_tx_reference)
tx.vin[0].nSequence = 2 # We could include it in the second block mined from now, but not the very next one
# Can skip re-signing the tx because of early rejection
self.check_mempool_result(
result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'non-BIP68-final'}],
rawtxs=[tx.serialize().hex()],
maxfeerate=0,
)
if __name__ == '__main__':
MempoolAcceptanceTest().main()

View file

@ -0,0 +1,116 @@
#!/usr/bin/env python3
# Copyright (c) 2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""
Test mempool acceptance in case of an already known transaction
with identical non-witness data but different witness.
"""
from copy import deepcopy
from test_framework.messages import (
COIN,
COutPoint,
CTransaction,
CTxIn,
CTxInWitness,
CTxOut,
sha256,
)
from test_framework.p2p import P2PTxInvStore
from test_framework.script import (
CScript,
OP_0,
OP_ELSE,
OP_ENDIF,
OP_EQUAL,
OP_HASH160,
OP_IF,
OP_TRUE,
hash160,
)
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import (
assert_equal,
)
class MempoolWtxidTest(DigiByteTestFramework):
def set_test_params(self):
self.num_nodes = 1
self.setup_clean_chain = True
def run_test(self):
node = self.nodes[0]
self.log.info('Start with empty mempool and 101 blocks')
# The last 100 coinbase transactions are premature
blockhash = self.generate(node, 101)[0]
txid = node.getblock(blockhash=blockhash, verbosity=2)["tx"][0]["txid"]
assert_equal(node.getmempoolinfo()['size'], 0)
self.log.info("Submit parent with multiple script branches to mempool")
hashlock = hash160(b'Preimage')
witness_script = CScript([OP_IF, OP_HASH160, hashlock, OP_EQUAL, OP_ELSE, OP_TRUE, OP_ENDIF])
witness_program = sha256(witness_script)
script_pubkey = CScript([OP_0, witness_program])
parent = CTransaction()
parent.vin.append(CTxIn(COutPoint(int(txid, 16), 0), b""))
parent.vout.append(CTxOut(int(9.99888 * COIN), script_pubkey))
parent.rehash()
privkeys = [node.get_deterministic_priv_key().key]
raw_parent = node.signrawtransactionwithkey(hexstring=parent.serialize().hex(), privkeys=privkeys)['hex']
parent_txid = node.sendrawtransaction(hexstring=raw_parent, maxfeerate=0)
self.generate(node, 1)
peer_wtxid_relay = node.add_p2p_connection(P2PTxInvStore())
# Create a new transaction with witness solving first branch
child_witness_script = CScript([OP_TRUE])
child_witness_program = sha256(child_witness_script)
child_script_pubkey = CScript([OP_0, child_witness_program])
child_one = CTransaction()
child_one.vin.append(CTxIn(COutPoint(int(parent_txid, 16), 0), b""))
child_one.vout.append(CTxOut(int(9.99786 * COIN), child_script_pubkey))
child_one.wit.vtxinwit.append(CTxInWitness())
child_one.wit.vtxinwit[0].scriptWitness.stack = [b'Preimage', b'\x01', witness_script]
child_one_wtxid = child_one.getwtxid()
child_one_txid = child_one.rehash()
# Create another identical transaction with witness solving second branch
child_two = deepcopy(child_one)
child_two.wit.vtxinwit[0].scriptWitness.stack = [b'', witness_script]
child_two_wtxid = child_two.getwtxid()
child_two_txid = child_two.rehash()
assert_equal(child_one_txid, child_two_txid)
assert child_one_wtxid != child_two_wtxid
self.log.info("Submit one child to the mempool")
txid_submitted = node.sendrawtransaction(child_one.serialize().hex())
assert_equal(node.getrawmempool(True)[txid_submitted]['wtxid'], child_one_wtxid)
# testmempoolaccept reports the "already in mempool" error
assert_equal(node.testmempoolaccept([child_one.serialize().hex()]), [{
"txid": child_one_txid,
"wtxid": child_one_wtxid,
"allowed": False,
"reject-reason": "txn-already-in-mempool"
}])
testres_child_two = node.testmempoolaccept([child_two.serialize().hex()])[0]
assert_equal(testres_child_two, {
"txid": child_two_txid,
"wtxid": child_two_wtxid,
"allowed": False,
"reject-reason": "txn-same-nonwitness-data-in-mempool"
})
# sendrawtransaction will not throw but quits early when the exact same transaction is already in mempool
node.sendrawtransaction(child_one.serialize().hex())
# sendrawtransaction will not throw but quits early when a transaction with the same non-witness data is already in mempool
node.sendrawtransaction(child_two.serialize().hex())
if __name__ == '__main__':
MempoolWtxidTest().main()

View file

@ -0,0 +1,84 @@
#!/usr/bin/env python3
# Copyright (c) 2017-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test that mempool.dat is both backward and forward compatible between versions
NOTE: The test is designed to prevent cases when compatibility is broken accidentally.
In case we need to break mempool compatibility we can continue to use the test by just bumping the version number.
The previous release v0.19.1 is required by this test, see test/README.md.
"""
import os
from test_framework.blocktools import COINBASE_MATURITY
from test_framework.test_framework import DigiByteTestFramework
from test_framework.wallet import (
MiniWallet,
MiniWalletMode,
)
class MempoolCompatibilityTest(DigiByteTestFramework):
def set_test_params(self):
self.num_nodes = 2
self.wallet_names = [None]
def skip_test_if_missing_module(self):
self.skip_if_no_previous_releases()
def setup_network(self):
self.add_nodes(self.num_nodes, versions=[
190100, # oldest version with getmempoolinfo.loaded (used to avoid intermittent issues)
None,
])
self.start_nodes()
self.import_deterministic_coinbase_privkeys()
def run_test(self):
self.log.info("Test that mempool.dat is compatible between versions")
old_node, new_node = self.nodes
new_wallet = MiniWallet(new_node, mode=MiniWalletMode.RAW_P2PK)
self.generate(new_wallet, 1, sync_fun=self.no_op)
self.generate(new_node, COINBASE_MATURITY, sync_fun=self.no_op)
# Sync the nodes to ensure old_node has the block that contains the coinbase that new_wallet will spend.
# Otherwise, because coinbases are only valid in a block and not as loose txns, if the nodes aren't synced
# unbroadcasted_tx won't pass old_node's `MemPoolAccept::PreChecks`.
self.connect_nodes(0, 1)
self.sync_blocks()
recipient = old_node.getnewaddress()
self.stop_node(1)
self.log.info("Add a transaction to mempool on old node and shutdown")
old_tx_hash = old_node.sendtoaddress(recipient, 0.0001)
assert old_tx_hash in old_node.getrawmempool()
self.stop_node(0)
self.log.info("Move mempool.dat from old to new node")
old_node_mempool = os.path.join(old_node.datadir, self.chain, 'mempool.dat')
new_node_mempool = os.path.join(new_node.datadir, self.chain, 'mempool.dat')
os.rename(old_node_mempool, new_node_mempool)
self.log.info("Start new node and verify mempool contains the tx")
self.start_node(1)
assert old_tx_hash in new_node.getrawmempool()
self.log.info("Add unbroadcasted tx to mempool on new node and shutdown")
unbroadcasted_tx_hash = new_wallet.send_self_transfer(from_node=new_node)['txid']
assert unbroadcasted_tx_hash in new_node.getrawmempool()
assert new_node.getmempoolentry(unbroadcasted_tx_hash)['unbroadcast']
self.stop_node(1)
self.log.info("Move mempool.dat from new to old node")
os.rename(new_node_mempool, old_node_mempool)
self.log.info("Start old node again and verify mempool contains both txs")
self.start_node(0, ['-nowallet'])
assert old_tx_hash in old_node.getrawmempool()
assert unbroadcasted_tx_hash in old_node.getrawmempool()
if __name__ == "__main__":
MempoolCompatibilityTest().main()

111
test/functional/mempool_expiry.py Executable file
View file

@ -0,0 +1,111 @@
#!/usr/bin/env python3
# Copyright (c) 2020-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Tests that a mempool transaction expires after a given timeout and that its
children are removed as well.
Both the default expiry timeout defined by DEFAULT_MEMPOOL_EXPIRY and a user
definable expiry timeout via the '-mempoolexpiry=<n>' command line argument
(<n> is the timeout in hours) are tested.
"""
from datetime import timedelta
from test_framework.blocktools import COINBASE_MATURITY
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
)
from test_framework.wallet import MiniWallet
DEFAULT_MEMPOOL_EXPIRY = 336 # hours
CUSTOM_MEMPOOL_EXPIRY = 10 # hours
class MempoolExpiryTest(DigiByteTestFramework):
def set_test_params(self):
self.num_nodes = 1
self.setup_clean_chain = True
def test_transaction_expiry(self, timeout):
"""Tests that a transaction expires after the expiry timeout and its
children are removed as well."""
node = self.nodes[0]
self.wallet = MiniWallet(node)
# Add enough mature utxos to the wallet so that all txs spend confirmed coins.
self.generate(self.wallet, 4)
self.generate(node, COINBASE_MATURITY)
# Send a parent transaction that will expire.
parent_txid = self.wallet.send_self_transfer(from_node=node)['txid']
parent_utxo = self.wallet.get_utxo(txid=parent_txid)
independent_utxo = self.wallet.get_utxo()
# Ensure the transactions we send to trigger the mempool check spend utxos that are independent of
# the transactions being tested for expiration.
trigger_utxo1 = self.wallet.get_utxo()
trigger_utxo2 = self.wallet.get_utxo()
# Set the mocktime to the arrival time of the parent transaction.
entry_time = node.getmempoolentry(parent_txid)['time']
node.setmocktime(entry_time)
# Let half of the timeout elapse and broadcast the child transaction spending the parent transaction.
half_expiry_time = entry_time + int(60 * 60 * timeout/2)
node.setmocktime(half_expiry_time)
child_txid = self.wallet.send_self_transfer(from_node=node, utxo_to_spend=parent_utxo)['txid']
assert_equal(parent_txid, node.getmempoolentry(child_txid)['depends'][0])
self.log.info('Broadcast child transaction after {} hours.'.format(
timedelta(seconds=(half_expiry_time-entry_time))))
# Broadcast another (independent) transaction.
independent_txid = self.wallet.send_self_transfer(from_node=node, utxo_to_spend=independent_utxo)['txid']
# Let most of the timeout elapse and check that the parent tx is still
# in the mempool.
nearly_expiry_time = entry_time + 60 * 60 * timeout - 5
node.setmocktime(nearly_expiry_time)
# Broadcast a transaction as the expiry of transactions in the mempool is only checked
# when a new transaction is added to the mempool.
self.wallet.send_self_transfer(from_node=node, utxo_to_spend=trigger_utxo1)
self.log.info('Test parent tx not expired after {} hours.'.format(
timedelta(seconds=(nearly_expiry_time-entry_time))))
assert_equal(entry_time, node.getmempoolentry(parent_txid)['time'])
# Transaction should be evicted from the mempool after the expiry time
# has passed.
expiry_time = entry_time + 60 * 60 * timeout + 5
node.setmocktime(expiry_time)
# Again, broadcast a transaction so the expiry of transactions in the mempool is checked.
self.wallet.send_self_transfer(from_node=node, utxo_to_spend=trigger_utxo2)
self.log.info('Test parent tx expiry after {} hours.'.format(
timedelta(seconds=(expiry_time-entry_time))))
assert_raises_rpc_error(-5, 'Transaction not in mempool',
node.getmempoolentry, parent_txid)
# The child transaction should be removed from the mempool as well.
self.log.info('Test child tx is evicted as well.')
assert_raises_rpc_error(-5, 'Transaction not in mempool',
node.getmempoolentry, child_txid)
# Check that the independent tx is still in the mempool.
self.log.info('Test the independent tx not expired after {} hours.'.format(
timedelta(seconds=(expiry_time-half_expiry_time))))
assert_equal(half_expiry_time, node.getmempoolentry(independent_txid)['time'])
def run_test(self):
self.log.info('Test default mempool expiry timeout of %d hours.' %
DEFAULT_MEMPOOL_EXPIRY)
self.test_transaction_expiry(DEFAULT_MEMPOOL_EXPIRY)
self.log.info('Test custom mempool expiry timeout of %d hours.' %
CUSTOM_MEMPOOL_EXPIRY)
self.restart_node(0, ['-mempoolexpiry=%d' % CUSTOM_MEMPOOL_EXPIRY])
self.test_transaction_expiry(CUSTOM_MEMPOOL_EXPIRY)
if __name__ == '__main__':
MempoolExpiryTest().main()

View file

@ -0,0 +1,92 @@
#!/usr/bin/env python3
# Copyright (c) 2014-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test mempool limiting together/eviction with the wallet."""
from decimal import Decimal
from test_framework.blocktools import COINBASE_MATURITY
from test_framework.messages import COIN
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import (
assert_equal,
assert_greater_than,
assert_raises_rpc_error,
gen_return_txouts,
)
from test_framework.wallet import MiniWallet
class MempoolLimitTest(DigiByteTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
self.extra_args = [[
"-acceptnonstdtxn=1",
"-maxmempool=5",
"-spendzeroconfchange=0",
]]
self.supports_cli = False
def send_large_txs(self, node, miniwallet, txouts, fee, tx_batch_size):
for _ in range(tx_batch_size):
tx = miniwallet.create_self_transfer(from_node=node, fee_rate=0, mempool_valid=False)['tx']
for txout in txouts:
tx.vout.append(txout)
tx.vout[0].nValue -= int(fee * COIN)
res = node.testmempoolaccept([tx.serialize().hex()])[0]
assert_equal(res['fees']['base'], fee)
miniwallet.sendrawtransaction(from_node=node, tx_hex=tx.serialize().hex())
def run_test(self):
txouts = gen_return_txouts()
node = self.nodes[0]
miniwallet = MiniWallet(node)
relayfee = node.getnetworkinfo()['relayfee']
self.log.info('Check that mempoolminfee is minrelaytxfee')
assert_equal(node.getmempoolinfo()['minrelaytxfee'], Decimal('0.00100000'))
assert_equal(node.getmempoolinfo()['mempoolminfee'], Decimal('0.00100000'))
tx_batch_size = 25
num_of_batches = 3
# Generate UTXOs to flood the mempool
# 1 to create a tx initially that will be evicted from the mempool later
# 3 batches of multiple transactions with a fee rate much higher than the previous UTXO
# And 1 more to verify that this tx does not get added to the mempool with a fee rate less than the mempoolminfee
self.generate(miniwallet, 1 + (num_of_batches * tx_batch_size) + 1)
# Mine 99 blocks so that the UTXOs are allowed to be spent
self.generate(node, COINBASE_MATURITY - 1)
self.log.info('Create a mempool tx that will be evicted')
tx_to_be_evicted_id = miniwallet.send_self_transfer(from_node=node, fee_rate=relayfee)["txid"]
# Increase the tx fee rate to give the subsequent transactions a higher priority in the mempool
# The tx has an approx. vsize of 65k, i.e. multiplying the previous fee rate (in sats/kvB)
# by 130 should result in a fee that corresponds to 2x of that fee rate
base_fee = relayfee * 130
self.log.info("Fill up the mempool with txs with higher fee rate")
for batch_of_txid in range(num_of_batches):
fee = (batch_of_txid + 1) * base_fee
self.send_large_txs(node, miniwallet, txouts, fee, tx_batch_size)
self.log.info('The tx should be evicted by now')
# The number of transactions created should be greater than the ones present in the mempool
assert_greater_than(tx_batch_size * num_of_batches, len(node.getrawmempool()))
# Initial tx created should not be present in the mempool anymore as it had a lower fee rate
assert tx_to_be_evicted_id not in node.getrawmempool()
self.log.info('Check that mempoolminfee is larger than minrelaytxfee')
assert_equal(node.getmempoolinfo()['minrelaytxfee'], Decimal('0.00100000'))
assert_greater_than(node.getmempoolinfo()['mempoolminfee'], Decimal('0.00100000'))
# Deliberately try to create a tx with a fee less than the minimum mempool fee to assert that it does not get added to the mempool
self.log.info('Create a mempool tx that will not pass mempoolminfee')
assert_raises_rpc_error(-26, "mempool min fee not met", miniwallet.send_self_transfer, from_node=node, fee_rate=relayfee, mempool_valid=False)
if __name__ == '__main__':
MempoolLimitTest().main()

View file

@ -0,0 +1,82 @@
#!/usr/bin/env python3
# Copyright (c) 2014-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test descendant package tracking carve-out allowing one final transaction in
an otherwise-full package as long as it has only one parent and is <= 10k in
size.
"""
from decimal import Decimal
from test_framework.messages import COIN
from test_framework.blocktools import COINBASE_MATURITY_2
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
chain_transaction,
)
MAX_ANCESTORS = 25
MAX_DESCENDANTS = 25
class MempoolPackagesTest(DigiByteTestFramework):
def set_test_params(self):
self.num_nodes = 1
self.extra_args = [["-maxorphantx=1000"]]
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
def run_test(self):
# Mine some blocks and have them mature.
self.generate(self.nodes[0], COINBASE_MATURITY_2 + 1)
utxo = self.nodes[0].listunspent(10)
txid = utxo[0]['txid']
vout = utxo[0]['vout']
value = utxo[0]['amount']
fee = Decimal("0.0023")
high_fee = Decimal("0.023")
# MAX_ANCESTORS transactions off a confirmed tx should be fine
chain = []
for _ in range(4):
(txid, sent_value) = chain_transaction(self.nodes[0], [txid], [vout], value, fee, 2)
vout = 0
value = sent_value
chain.append([txid, value])
for _ in range(MAX_ANCESTORS - 4):
(txid, sent_value) = chain_transaction(self.nodes[0], [txid], [0], value, fee, 1)
value = sent_value
chain.append([txid, value])
(second_chain, second_chain_value) = chain_transaction(self.nodes[0], [utxo[1]['txid']], [utxo[1]['vout']], utxo[1]['amount'], fee, 1)
# Check mempool has MAX_ANCESTORS + 1 transactions in it
assert_equal(len(self.nodes[0].getrawmempool()), MAX_ANCESTORS + 1)
# Adding one more transaction on to the chain should fail.
assert_raises_rpc_error(-26, "too-long-mempool-chain, too many unconfirmed ancestors [limit: 25]", chain_transaction, self.nodes[0], [txid], [0], value, fee, 1)
# ...even if it chains on from some point in the middle of the chain.
assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", chain_transaction, self.nodes[0], [chain[2][0]], [1], chain[2][1], fee, 1)
assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", chain_transaction, self.nodes[0], [chain[1][0]], [1], chain[1][1], fee, 1)
# ...even if it chains on to two parent transactions with one in the chain.
assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", chain_transaction, self.nodes[0], [chain[0][0], second_chain], [1, 0], chain[0][1] + second_chain_value, fee, 1)
# ...especially if its > 40k weight
assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", chain_transaction, self.nodes[0], [chain[0][0]], [1], chain[0][1], high_fee, 350, 1.1)
# But not if it chains directly off the first transaction
(replacable_txid, replacable_orig_value) = chain_transaction(self.nodes[0], [chain[0][0]], [1], chain[0][1], fee, 1)
# and the second chain should work just fine
chain_transaction(self.nodes[0], [second_chain], [0], second_chain_value, fee, 1)
# Make sure we can RBF the chain which used our carve-out rule
second_tx_outputs = {self.nodes[0].getrawtransaction(replacable_txid, True)["vout"][0]['scriptPubKey']['address']: replacable_orig_value - (Decimal(1) / Decimal(100))}
second_tx = self.nodes[0].createrawtransaction([{'txid': chain[0][0], 'vout': 1}], second_tx_outputs)
signed_second_tx = self.nodes[0].signrawtransactionwithwallet(second_tx)
self.nodes[0].sendrawtransaction(signed_second_tx['hex'], 1.1)
# Finally, check that we added two transactions
assert_equal(len(self.nodes[0].getrawmempool()), MAX_ANCESTORS + 3)
if __name__ == '__main__':
MempoolPackagesTest().main()

View file

@ -0,0 +1,339 @@
#!/usr/bin/env python3
# Copyright (c) 2014-2020 The Bitcoin Core developers
# Copyright (c) 2015-2022 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test descendant package tracking code."""
from decimal import Decimal
from test_framework.blocktools import COINBASE_MATURITY, COINBASE_MATURITY_2
from test_framework.messages import COIN
from test_framework.p2p import P2PTxInvStore
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
chain_transaction,
satoshi_round,
)
# default limits
MAX_ANCESTORS = 25
MAX_DESCENDANTS = 25
# custom limits for node1
MAX_ANCESTORS_CUSTOM = 5
MAX_DESCENDANTS_CUSTOM = 10
assert MAX_DESCENDANTS_CUSTOM >= MAX_ANCESTORS_CUSTOM
class MempoolPackagesTest(DigiByteTestFramework):
def set_test_params(self):
self.num_nodes = 2
self.extra_args = [
[
"-maxorphantx=1000",
"-whitelist=noban@127.0.0.1", # immediate tx relay
],
[
"-maxorphantx=1000",
"-limitancestorcount={}".format(MAX_ANCESTORS_CUSTOM),
"-limitdescendantcount={}".format(MAX_DESCENDANTS_CUSTOM),
],
]
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
def run_test(self):
# Mine some blocks and have them mature.
peer_inv_store = self.nodes[0].add_p2p_connection(P2PTxInvStore()) # keep track of invs
self.generate(self.nodes[0], COINBASE_MATURITY_2 + 1)
utxo = self.nodes[0].listunspent(10)
txid = utxo[0]['txid']
vout = utxo[0]['vout']
value = utxo[0]['amount']
fee = Decimal("0.0015")
# MAX_ANCESTORS transactions off a confirmed tx should be fine
chain = []
witness_chain = []
for _ in range(MAX_ANCESTORS):
(txid, sent_value) = chain_transaction(self.nodes[0], [txid], [0], value, fee, 1)
value = sent_value
chain.append(txid)
# We need the wtxids to check P2P announcements
fulltx = self.nodes[0].getrawtransaction(txid)
witnesstx = self.nodes[0].decoderawtransaction(fulltx, True)
witness_chain.append(witnesstx['hash'])
# Wait until mempool transactions have passed initial broadcast (sent inv and received getdata)
# Otherwise, getrawmempool may be inconsistent with getmempoolentry if unbroadcast changes in between
peer_inv_store.wait_for_broadcast(witness_chain)
# Check mempool has MAX_ANCESTORS transactions in it, and descendant and ancestor
# count and fees should look correct
mempool = self.nodes[0].getrawmempool(True)
assert_equal(len(mempool), MAX_ANCESTORS)
descendant_count = 1
descendant_fees = 0
descendant_vsize = 0
ancestor_vsize = sum([mempool[tx]['vsize'] for tx in mempool])
ancestor_count = MAX_ANCESTORS
ancestor_fees = sum([mempool[tx]['fee'] for tx in mempool])
descendants = []
ancestors = list(chain)
for x in reversed(chain):
# Check that getmempoolentry is consistent with getrawmempool
entry = self.nodes[0].getmempoolentry(x)
assert_equal(entry, mempool[x])
# Check that the descendant calculations are correct
assert_equal(mempool[x]['descendantcount'], descendant_count)
descendant_fees += mempool[x]['fee']
assert_equal(mempool[x]['modifiedfee'], mempool[x]['fee'])
assert_equal(mempool[x]['fees']['base'], mempool[x]['fee'])
assert_equal(mempool[x]['fees']['modified'], mempool[x]['modifiedfee'])
assert_equal(mempool[x]['descendantfees'], descendant_fees * COIN)
assert_equal(mempool[x]['fees']['descendant'], descendant_fees)
descendant_vsize += mempool[x]['vsize']
assert_equal(mempool[x]['descendantsize'], descendant_vsize)
descendant_count += 1
# Check that ancestor calculations are correct
assert_equal(mempool[x]['ancestorcount'], ancestor_count)
assert_equal(mempool[x]['ancestorfees'], ancestor_fees * COIN)
assert_equal(mempool[x]['ancestorsize'], ancestor_vsize)
ancestor_vsize -= mempool[x]['vsize']
ancestor_fees -= mempool[x]['fee']
ancestor_count -= 1
# Check that parent/child list is correct
assert_equal(mempool[x]['spentby'], descendants[-1:])
assert_equal(mempool[x]['depends'], ancestors[-2:-1])
# Check that getmempooldescendants is correct
assert_equal(sorted(descendants), sorted(self.nodes[0].getmempooldescendants(x)))
# Check getmempooldescendants verbose output is correct
for descendant, dinfo in self.nodes[0].getmempooldescendants(x, True).items():
assert_equal(dinfo['depends'], [chain[chain.index(descendant)-1]])
if dinfo['descendantcount'] > 1:
assert_equal(dinfo['spentby'], [chain[chain.index(descendant)+1]])
else:
assert_equal(dinfo['spentby'], [])
descendants.append(x)
# Check that getmempoolancestors is correct
ancestors.remove(x)
assert_equal(sorted(ancestors), sorted(self.nodes[0].getmempoolancestors(x)))
# Check that getmempoolancestors verbose output is correct
for ancestor, ainfo in self.nodes[0].getmempoolancestors(x, True).items():
assert_equal(ainfo['spentby'], [chain[chain.index(ancestor)+1]])
if ainfo['ancestorcount'] > 1:
assert_equal(ainfo['depends'], [chain[chain.index(ancestor)-1]])
else:
assert_equal(ainfo['depends'], [])
# Check that getmempoolancestors/getmempooldescendants correctly handle verbose=true
v_ancestors = self.nodes[0].getmempoolancestors(chain[-1], True)
assert_equal(len(v_ancestors), len(chain)-1)
for x in v_ancestors.keys():
assert_equal(mempool[x], v_ancestors[x])
assert chain[-1] not in v_ancestors.keys()
v_descendants = self.nodes[0].getmempooldescendants(chain[0], True)
assert_equal(len(v_descendants), len(chain)-1)
for x in v_descendants.keys():
assert_equal(mempool[x], v_descendants[x])
assert chain[0] not in v_descendants.keys()
# Check that ancestor modified fees includes fee deltas from
# prioritisetransaction
self.nodes[0].prioritisetransaction(txid=chain[0], fee_delta=1000)
mempool = self.nodes[0].getrawmempool(True)
ancestor_fees = 0
for x in chain:
ancestor_fees += mempool[x]['fee']
assert_equal(mempool[x]['fees']['ancestor'], ancestor_fees + Decimal('0.00001'))
assert_equal(mempool[x]['ancestorfees'], ancestor_fees * COIN + 1000)
# Undo the prioritisetransaction for later tests
self.nodes[0].prioritisetransaction(txid=chain[0], fee_delta=-1000)
# Check that descendant modified fees includes fee deltas from
# prioritisetransaction
self.nodes[0].prioritisetransaction(txid=chain[-1], fee_delta=1000)
mempool = self.nodes[0].getrawmempool(True)
descendant_fees = 0
for x in reversed(chain):
descendant_fees += mempool[x]['fee']
assert_equal(mempool[x]['fees']['descendant'], descendant_fees + Decimal('0.00001'))
assert_equal(mempool[x]['descendantfees'], descendant_fees * COIN + 1000)
# Adding one more transaction on to the chain should fail.
assert_raises_rpc_error(-26, "too-long-mempool-chain", chain_transaction, self.nodes[0], [txid], [vout], value, fee, 1)
# Check that prioritising a tx before it's added to the mempool works
# First clear the mempool by mining a block.
self.generate(self.nodes[0], 1)
self.sync_blocks()
assert_equal(len(self.nodes[0].getrawmempool()), 0)
# Prioritise a transaction that has been mined, then add it back to the
# mempool by using invalidateblock.
self.nodes[0].prioritisetransaction(txid=chain[-1], fee_delta=2000)
self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
# Keep node1's tip synced with node0
self.nodes[1].invalidateblock(self.nodes[1].getbestblockhash())
# Now check that the transaction is in the mempool, with the right modified fee
mempool = self.nodes[0].getrawmempool(True)
descendant_fees = 0
for x in reversed(chain):
descendant_fees += mempool[x]['fee']
if (x == chain[-1]):
assert_equal(mempool[x]['modifiedfee'], mempool[x]['fee']+satoshi_round(0.00002))
assert_equal(mempool[x]['fees']['modified'], mempool[x]['fee']+satoshi_round(0.00002))
assert_equal(mempool[x]['descendantfees'], descendant_fees * COIN + 2000)
assert_equal(mempool[x]['fees']['descendant'], descendant_fees+satoshi_round(0.00002))
# Check that node1's mempool is as expected (-> custom ancestor limit)
mempool0 = self.nodes[0].getrawmempool(False)
mempool1 = self.nodes[1].getrawmempool(False)
assert_equal(len(mempool1), MAX_ANCESTORS_CUSTOM)
assert set(mempool1).issubset(set(mempool0))
for tx in chain[:MAX_ANCESTORS_CUSTOM]:
assert tx in mempool1
# TODO: more detailed check of node1's mempool (fees etc.)
# check transaction unbroadcast info (should be false if in both mempools)
mempool = self.nodes[0].getrawmempool(True)
for tx in mempool:
assert_equal(mempool[tx]['unbroadcast'], False)
# TODO: test ancestor size limits
# Now test descendant chain limits
txid = utxo[1]['txid']
value = utxo[1]['amount']
vout = utxo[1]['vout']
transaction_package = []
tx_children = []
# First create one parent tx with 10 children
(txid, sent_value) = chain_transaction(self.nodes[0], [txid], [vout], value, fee, 10)
parent_transaction = txid
for i in range(10):
transaction_package.append({'txid': txid, 'vout': i, 'amount': sent_value})
# Sign and send up to MAX_DESCENDANT transactions chained off the parent tx
chain = [] # save sent txs for the purpose of checking node1's mempool later (see below)
for _ in range(MAX_DESCENDANTS - 1):
utxo = transaction_package.pop(0)
(txid, sent_value) = chain_transaction(self.nodes[0], [utxo['txid']], [utxo['vout']], utxo['amount'], fee, 10)
chain.append(txid)
if utxo['txid'] is parent_transaction:
tx_children.append(txid)
for j in range(10):
transaction_package.append({'txid': txid, 'vout': j, 'amount': sent_value})
mempool = self.nodes[0].getrawmempool(True)
assert_equal(mempool[parent_transaction]['descendantcount'], MAX_DESCENDANTS)
assert_equal(sorted(mempool[parent_transaction]['spentby']), sorted(tx_children))
for child in tx_children:
assert_equal(mempool[child]['depends'], [parent_transaction])
# Sending one more chained transaction will fail
utxo = transaction_package.pop(0)
assert_raises_rpc_error(-26, "too-long-mempool-chain", chain_transaction, self.nodes[0], [utxo['txid']], [utxo['vout']], utxo['amount'], fee, 10)
# Check that node1's mempool is as expected, containing:
# - txs from previous ancestor test (-> custom ancestor limit)
# - parent tx for descendant test
# - txs chained off parent tx (-> custom descendant limit)
self.wait_until(lambda: len(self.nodes[1].getrawmempool(False)) ==
MAX_ANCESTORS_CUSTOM + 1 + MAX_DESCENDANTS_CUSTOM, timeout=10)
mempool0 = self.nodes[0].getrawmempool(False)
mempool1 = self.nodes[1].getrawmempool(False)
assert set(mempool1).issubset(set(mempool0))
assert parent_transaction in mempool1
for tx in chain[:MAX_DESCENDANTS_CUSTOM]:
assert tx in mempool1
for tx in chain[MAX_DESCENDANTS_CUSTOM:]:
assert tx not in mempool1
# TODO: more detailed check of node1's mempool (fees etc.)
# TODO: test descendant size limits
# Test reorg handling
# First, the basics:
self.generate(self.nodes[0], 1)
self.sync_blocks()
self.nodes[1].invalidateblock(self.nodes[0].getbestblockhash())
self.nodes[1].reconsiderblock(self.nodes[0].getbestblockhash())
# Now test the case where node1 has a transaction T in its mempool that
# depends on transactions A and B which are in a mined block, and the
# block containing A and B is disconnected, AND B is not accepted back
# into node1's mempool because its ancestor count is too high.
# Create 8 transactions, like so:
# Tx0 -> Tx1 (vout0)
# \--> Tx2 (vout1) -> Tx3 -> Tx4 -> Tx5 -> Tx6 -> Tx7
#
# Mine them in the next block, then generate a new tx8 that spends
# Tx1 and Tx7, and add to node1's mempool, then disconnect the
# last block.
# Create tx0 with 2 outputs
utxo = self.nodes[0].listunspent()
txid = utxo[0]['txid']
value = utxo[0]['amount']
vout = utxo[0]['vout']
send_value = satoshi_round((value - fee)/2)
inputs = [ {'txid' : txid, 'vout' : vout} ]
outputs = {}
for _ in range(2):
outputs[self.nodes[0].getnewaddress()] = send_value
rawtx = self.nodes[0].createrawtransaction(inputs, outputs)
signedtx = self.nodes[0].signrawtransactionwithwallet(rawtx)
txid = self.nodes[0].sendrawtransaction(signedtx['hex'])
tx0_id = txid
value = send_value
# Create tx1
tx1_id, _ = chain_transaction(self.nodes[0], [tx0_id], [0], value, fee, 1)
# Create tx2-7
vout = 1
txid = tx0_id
for _ in range(6):
(txid, sent_value) = chain_transaction(self.nodes[0], [txid], [vout], value, fee, 1)
vout = 0
value = sent_value
# Mine these in a block
self.generate(self.nodes[0], 1)
self.sync_all()
# Now generate tx8, with a big fee
inputs = [ {'txid' : tx1_id, 'vout': 0}, {'txid' : txid, 'vout': 0} ]
outputs = { self.nodes[0].getnewaddress() : send_value + value - 4*fee }
rawtx = self.nodes[0].createrawtransaction(inputs, outputs)
signedtx = self.nodes[0].signrawtransactionwithwallet(rawtx)
txid = self.nodes[0].sendrawtransaction(signedtx['hex'])
self.sync_mempools()
# Now try to disconnect the tip on each node...
self.nodes[1].invalidateblock(self.nodes[1].getbestblockhash())
self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
self.sync_blocks()
if __name__ == '__main__':
MempoolPackagesTest().main()

View file

@ -0,0 +1,184 @@
#!/usr/bin/env python3
# Copyright (c) 2014-2020 The Bitcoin Core developers
# Copyright (c) 2015-2022 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test mempool persistence.
By default, bitcoind will dump mempool on shutdown and
then reload it on startup. This can be overridden with
the -persistmempool=0 command line option.
Test is as follows:
- start node0, node1 and node2. node1 has -persistmempool=0
- create 5 transactions on node2 to its own address. Note that these
are not sent to node0 or node1 addresses because we don't want
them to be saved in the wallet.
- check that node0 and node1 have 5 transactions in their mempools
- shutdown all nodes.
- startup node0. Verify that it still has 5 transactions
in its mempool. Shutdown node0. This tests that by default the
mempool is persistent.
- startup node1. Verify that its mempool is empty. Shutdown node1.
This tests that with -persistmempool=0, the mempool is not
dumped to disk when the node is shut down.
- Restart node0 with -persistmempool=0. Verify that its mempool is
empty. Shutdown node0. This tests that with -persistmempool=0,
the mempool is not loaded from disk on start up.
- Restart node0 with -persistmempool. Verify that it has 5
transactions in its mempool. This tests that -persistmempool=0
does not overwrite a previously valid mempool stored on disk.
- Remove node0 mempool.dat and verify savemempool RPC recreates it
and verify that node1 can load it and has 5 transactions in its
mempool.
- Verify that savemempool throws when the RPC is called if
node1 can't write to disk.
"""
from decimal import Decimal
import os
import time
from test_framework.p2p import P2PTxInvStore
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import (
assert_equal,
assert_greater_than_or_equal,
assert_raises_rpc_error,
)
class MempoolPersistTest(DigiByteTestFramework):
def set_test_params(self):
self.num_nodes = 3
self.extra_args = [[], ["-persistmempool=0"], []]
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
def run_test(self):
self.log.debug("Send 5 transactions from node2 (to its own address)")
self.generatetoaddress(self.nodes[2], 101, self.nodes[2].getnewaddress())
self.sync_all()
tx_creation_time_lower = int(time.time())
for _ in range(5):
last_txid = self.nodes[2].sendtoaddress(self.nodes[2].getnewaddress(), Decimal("10"))
node2_balance = self.nodes[2].getbalance()
self.sync_all()
tx_creation_time_higher = int(time.time())
self.log.debug("Verify that node0 and node1 have 5 transactions in their mempools")
assert_equal(len(self.nodes[0].getrawmempool()), 5)
assert_equal(len(self.nodes[1].getrawmempool()), 5)
total_fee_old = self.nodes[0].getmempoolinfo()['total_fee']
self.log.debug("Prioritize a transaction on node0")
fees = self.nodes[0].getmempoolentry(txid=last_txid)['fees']
assert_equal(fees['base'], fees['modified'])
self.nodes[0].prioritisetransaction(txid=last_txid, fee_delta=1000)
fees = self.nodes[0].getmempoolentry(txid=last_txid)['fees']
assert_equal(fees['base'] + Decimal('0.00001000'), fees['modified'])
self.log.info('Check the total base fee is unchanged after prioritisetransaction')
assert_equal(total_fee_old, self.nodes[0].getmempoolinfo()['total_fee'])
assert_equal(total_fee_old, sum(v['fees']['base'] for k, v in self.nodes[0].getrawmempool(verbose=True).items()))
tx_creation_time = self.nodes[0].getmempoolentry(txid=last_txid)['time']
assert_greater_than_or_equal(tx_creation_time, tx_creation_time_lower)
assert_greater_than_or_equal(tx_creation_time_higher, tx_creation_time)
# disconnect nodes & make a txn that remains in the unbroadcast set.
self.disconnect_nodes(0, 1)
assert(len(self.nodes[0].getpeerinfo()) == 0)
assert(len(self.nodes[0].p2ps) == 0)
self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), Decimal("12"))
self.connect_nodes(0, 2)
self.log.debug("Stop-start the nodes. Verify that node0 has the transactions in its mempool and node1 does not. Verify that node2 calculates its balance correctly after loading wallet transactions.")
self.stop_nodes()
# Give this node a head-start, so we can be "extra-sure" that it didn't load anything later
# Also don't store the mempool, to keep the datadir clean
self.start_node(1, extra_args=["-persistmempool=0"])
self.start_node(0)
self.start_node(2)
assert self.nodes[0].getmempoolinfo()["loaded"] # start_node is blocking on the mempool being loaded
assert self.nodes[2].getmempoolinfo()["loaded"]
assert_equal(len(self.nodes[0].getrawmempool()), 6)
assert_equal(len(self.nodes[2].getrawmempool()), 5)
# The others have loaded their mempool. If node_1 loaded anything, we'd probably notice by now:
assert_equal(len(self.nodes[1].getrawmempool()), 0)
self.log.debug('Verify prioritization is loaded correctly')
fees = self.nodes[0].getmempoolentry(txid=last_txid)['fees']
assert_equal(fees['base'] + Decimal('0.00001000'), fees['modified'])
self.log.debug('Verify time is loaded correctly')
assert_equal(tx_creation_time, self.nodes[0].getmempoolentry(txid=last_txid)['time'])
# Verify accounting of mempool transactions after restart is correct
self.nodes[2].syncwithvalidationinterfacequeue() # Flush mempool to wallet
assert_equal(node2_balance, self.nodes[2].getbalance())
# start node0 with wallet disabled so wallet transactions don't get resubmitted
self.log.debug("Stop-start node0 with -persistmempool=0. Verify that it doesn't load its mempool.dat file.")
self.stop_nodes()
self.start_node(0, extra_args=["-persistmempool=0", "-disablewallet"])
assert self.nodes[0].getmempoolinfo()["loaded"]
assert_equal(len(self.nodes[0].getrawmempool()), 0)
self.log.debug("Stop-start node0. Verify that it has the transactions in its mempool.")
self.stop_nodes()
self.start_node(0)
assert self.nodes[0].getmempoolinfo()["loaded"]
assert_equal(len(self.nodes[0].getrawmempool()), 6)
mempooldat0 = os.path.join(self.nodes[0].datadir, self.chain, 'mempool.dat')
mempooldat1 = os.path.join(self.nodes[1].datadir, self.chain, 'mempool.dat')
self.log.debug("Remove the mempool.dat file. Verify that savemempool to disk via RPC re-creates it")
os.remove(mempooldat0)
self.nodes[0].savemempool()
assert os.path.isfile(mempooldat0)
self.log.debug("Stop nodes, make node1 use mempool.dat from node0. Verify it has 6 transactions")
os.rename(mempooldat0, mempooldat1)
self.stop_nodes()
self.start_node(1, extra_args=["-persistmempool=1"])
assert self.nodes[1].getmempoolinfo()["loaded"]
assert_equal(len(self.nodes[1].getrawmempool()), 6)
self.log.debug("Prevent bitcoind from writing mempool.dat to disk. Verify that `savemempool` fails")
# to test the exception we are creating a tmp folder called mempool.dat.new
# which is an implementation detail that could change and break this test
mempooldotnew1 = mempooldat1 + '.new'
os.mkdir(mempooldotnew1)
assert_raises_rpc_error(-1, "Unable to dump mempool to disk", self.nodes[1].savemempool)
os.rmdir(mempooldotnew1)
self.test_persist_unbroadcast()
def test_persist_unbroadcast(self):
node0 = self.nodes[0]
self.start_node(0)
# clear out mempool
self.generate(node0, 1, sync_fun=self.no_op)
# ensure node0 doesn't have any connections
# make a transaction that will remain in the unbroadcast set
assert(len(node0.getpeerinfo()) == 0)
assert(len(node0.p2ps) == 0)
node0.sendtoaddress(self.nodes[1].getnewaddress(), Decimal("12"))
# shutdown, then startup with wallet disabled
self.stop_nodes()
self.start_node(0, extra_args=["-disablewallet"])
# check that txn gets broadcast due to unbroadcast logic
conn = node0.add_p2p_connection(P2PTxInvStore())
node0.mockscheduler(16*60) # 15 min + 1 for buffer
self.wait_until(lambda: len(conn.get_invs()) == 1)
if __name__ == '__main__':
MempoolPersistTest().main()

116
test/functional/mempool_reorg.py Executable file
View file

@ -0,0 +1,116 @@
#!/usr/bin/env python3
# Copyright (c) 2014-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test mempool re-org scenarios.
Test re-org scenarios with a mempool that contains transactions
that spend (directly or indirectly) coinbase transactions.
"""
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import assert_equal, assert_raises_rpc_error
from test_framework.wallet import MiniWallet
from test_framework.blocktools import COINBASE_MATURITY_2
from time import sleep
class MempoolCoinbaseTest(DigiByteTestFramework):
def set_test_params(self):
self.num_nodes = 2
self.setup_clean_chain = True
self.extra_args = [
[
'-whitelist=noban@127.0.0.1', # immediate tx relay
],
[]
]
def run_test(self):
wallet = MiniWallet(self.nodes[0])
# Start with a 200 block chain generated to MiniWallet.
wallet.generate(200, invalid_call=False)
assert_equal(self.nodes[0].getblockcount(), 200)
self.log.info("Add 4 coinbase utxos to the miniwallet")
first_block = 90
# Three scenarios for re-orging coinbase spends in the memory pool:
# 1. Direct coinbase spend : spend_1
# 2. Indirect (coinbase spend in chain, child in mempool) : spend_2 and spend_2_1
# 3. Indirect (coinbase and child both in chain) : spend_3 and spend_3_1
# Use invalidateblock to make all of the above coinbase spends invalid (immature coinbase),
# and make sure the mempool code behaves correctly.
b = [self.nodes[0].getblockhash(n) for n in range(first_block, first_block+4)]
coinbase_txids = [self.nodes[0].getblock(h)['tx'][0] for h in b]
utxo_1 = wallet.get_utxo(txid=coinbase_txids[1])
utxo_2 = wallet.get_utxo(txid=coinbase_txids[2])
utxo_3 = wallet.get_utxo(txid=coinbase_txids[3])
self.log.info("Create three transactions spending from coinbase utxos: spend_1, spend_2, spend_3")
spend_1 = wallet.create_self_transfer(from_node=self.nodes[0], utxo_to_spend=utxo_1)
spend_2 = wallet.create_self_transfer(from_node=self.nodes[0], utxo_to_spend=utxo_2)
spend_3 = wallet.create_self_transfer(from_node=self.nodes[0], utxo_to_spend=utxo_3)
self.log.info("Create another transaction which is time-locked to two blocks in the future")
utxo = wallet.get_utxo(txid=coinbase_txids[0])
timelock_tx = wallet.create_self_transfer(
from_node=self.nodes[0],
utxo_to_spend=utxo,
mempool_valid=False,
locktime=self.nodes[0].getblockcount() + 2
)['hex']
self.log.info("Check that the time-locked transaction is too immature to spend")
assert_raises_rpc_error(-26, "non-final", self.nodes[0].sendrawtransaction, timelock_tx)
self.log.info("Broadcast and mine spend_2 and spend_3")
wallet.sendrawtransaction(from_node=self.nodes[0], tx_hex=spend_2['hex'])
wallet.sendrawtransaction(from_node=self.nodes[0], tx_hex=spend_3['hex'])
self.log.info("Generate a block")
self.generate(self.nodes[0], 1)
self.log.info("Check that time-locked transaction is still too immature to spend")
assert_raises_rpc_error(-26, 'non-final', self.nodes[0].sendrawtransaction, timelock_tx)
self.log.info("Create spend_2_1 and spend_3_1")
spend_2_utxo = wallet.get_utxo(txid=spend_2['txid'])
spend_2_1 = wallet.create_self_transfer(from_node=self.nodes[0], utxo_to_spend=spend_2_utxo)
spend_3_utxo = wallet.get_utxo(txid=spend_3['txid'])
spend_3_1 = wallet.create_self_transfer(from_node=self.nodes[0], utxo_to_spend=spend_3_utxo)
self.log.info("Broadcast and mine spend_3_1")
spend_3_1_id = self.nodes[0].sendrawtransaction(spend_3_1['hex'])
self.log.info("Generate a block")
last_block = self.generate(self.nodes[0], 1)
# generate() implicitly syncs blocks, so that peer 1 gets the block before timelock_tx
# Otherwise, peer 1 would put the timelock_tx in m_recent_rejects
self.log.info("The time-locked transaction can now be spent")
timelock_tx_id = self.nodes[0].sendrawtransaction(timelock_tx)
self.log.info("Add spend_1 and spend_2_1 to the mempool")
spend_1_id = self.nodes[0].sendrawtransaction(spend_1['hex'])
spend_2_1_id = self.nodes[0].sendrawtransaction(spend_2_1['hex'])
assert_equal(set(self.nodes[0].getrawmempool()), {spend_1_id, spend_2_1_id, timelock_tx_id})
self.sync_all()
self.log.info("invalidate the last block")
for node in self.nodes:
node.invalidateblock(last_block[0])
self.log.info("The time-locked transaction is now too immature and has been removed from the mempool")
self.log.info("spend_3_1 has been re-orged out of the chain and is back in the mempool")
assert_equal(set(self.nodes[0].getrawmempool()), {spend_1_id, spend_2_1_id, spend_3_1_id})
self.log.info("Use invalidateblock to re-org back and make all those coinbase spends immature/invalid")
b = self.nodes[0].getblockhash(first_block + COINBASE_MATURITY_2)
for node in self.nodes:
node.invalidateblock(b)
self.log.info("Check that the mempool is empty")
assert_equal(set(self.nodes[0].getrawmempool()), set())
self.sync_all()
if __name__ == '__main__':
MempoolCoinbaseTest().main()

View file

@ -0,0 +1,57 @@
#!/usr/bin/env python3
# Copyright (c) 2014-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test resurrection of mined transactions when the blockchain is re-organized."""
from test_framework.blocktools import COINBASE_MATURITY
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import assert_equal
from test_framework.wallet import MiniWallet
class MempoolCoinbaseTest(DigiByteTestFramework):
def set_test_params(self):
self.num_nodes = 1
self.setup_clean_chain = True
def run_test(self):
node = self.nodes[0]
wallet = MiniWallet(node)
# Add enough mature utxos to the wallet so that all txs spend confirmed coins
self.generate(wallet, 3)
self.generate(node, COINBASE_MATURITY)
# Spend block 1/2/3's coinbase transactions
# Mine a block
# Create three more transactions, spending the spends
# Mine another block
# ... make sure all the transactions are confirmed
# Invalidate both blocks
# ... make sure all the transactions are put back in the mempool
# Mine a new block
# ... make sure all the transactions are confirmed again
blocks = []
spends1_ids = [wallet.send_self_transfer(from_node=node)['txid'] for _ in range(3)]
blocks.extend(self.generate(node, 1))
spends2_ids = [wallet.send_self_transfer(from_node=node)['txid'] for _ in range(3)]
blocks.extend(self.generate(node, 1))
spends_ids = set(spends1_ids + spends2_ids)
# mempool should be empty, all txns confirmed
assert_equal(set(node.getrawmempool()), set())
confirmed_txns = set(node.getblock(blocks[0])['tx'] + node.getblock(blocks[1])['tx'])
# Checks that all spend txns are contained in the mined blocks
assert spends_ids < confirmed_txns
# Use invalidateblock to re-org back
node.invalidateblock(blocks[0])
# Vexta does not resurrect disconnected block transactions into mempool.
assert_equal(set(node.getrawmempool()), set())
if __name__ == '__main__':
MempoolCoinbaseTest().main()

View file

@ -0,0 +1,55 @@
#!/usr/bin/env python3
# Copyright (c) 2014-2020 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test spending coinbase transactions.
The coinbase transaction in block N can appear in block
N+100... so is valid in the mempool when the best block
height is N+99.
This test makes sure coinbase spends that will be mature
in the next block are accepted into the memory pool,
but less mature coinbase spends are NOT.
"""
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import assert_equal, assert_raises_rpc_error
from test_framework.blocktools import COINBASE_MATURITY_2
from test_framework.wallet import MiniWallet, MiniWalletMode
class MempoolSpendCoinbaseTest(DigiByteTestFramework):
def set_test_params(self):
self.num_nodes = 1
self.setup_clean_chain = True
def run_test(self):
wallet = MiniWallet(self.nodes[0], mode=MiniWalletMode.RAW_P2PK)
# Invalidate two blocks, so that miniwallet has access to a coin that will mature in the next block
chain_height = 198
wallet.generate(chain_height + 2, invalid_call=False)
self.nodes[0].invalidateblock(self.nodes[0].getblockhash(chain_height + 1))
assert_equal(chain_height, self.nodes[0].getblockcount())
# Coinbase at height 99 is mature for mempool spend at height 198.
# Coinbase at height 100 is still too immature.
utxo_mature = wallet._utxos[chain_height - COINBASE_MATURITY_2]
utxo_immature = wallet._utxos[chain_height - COINBASE_MATURITY_2 + 2]
spend_mature_id = wallet.send_self_transfer(from_node=self.nodes[0], utxo_to_spend=utxo_mature)["txid"]
# Vexta accepts the next coinbase spend into mempool as well.
immature_tx = wallet.create_self_transfer(from_node=self.nodes[0], utxo_to_spend=utxo_immature)
spend_immature_id = self.nodes[0].sendrawtransaction(immature_tx['hex'])
# mempool should have both spends
assert_equal(set(self.nodes[0].getrawmempool()), {spend_mature_id, spend_immature_id})
# mine a block, both should get confirmed
self.generate(self.nodes[0], 1)
assert_equal(set(self.nodes[0].getrawmempool()), set())
if __name__ == '__main__':
MempoolSpendCoinbaseTest().main()

View file

@ -0,0 +1,117 @@
#!/usr/bin/env python3
# Copyright (c) 2017-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test that the mempool ensures transaction delivery by periodically sending
to peers until a GETDATA is received."""
import time
from decimal import Decimal
from test_framework.p2p import P2PTxInvStore
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import (
assert_equal,
create_confirmed_utxos,
)
MAX_INITIAL_BROADCAST_DELAY = 15 * 60 # 15 minutes in seconds
class MempoolUnbroadcastTest(DigiByteTestFramework):
def set_test_params(self):
self.num_nodes = 2
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
def run_test(self):
self.test_broadcast()
self.test_txn_removal()
def test_broadcast(self):
self.log.info("Test that mempool reattempts delivery of locally submitted transaction")
node = self.nodes[0]
min_relay_fee = Decimal('0.1')
utxos = create_confirmed_utxos(self, min_relay_fee, node, 10)
self.disconnect_nodes(0, 1)
self.log.info("Generate transactions that only node 0 knows about")
# generate a wallet txn
addr = node.getnewaddress()
wallet_tx_hsh = node.sendtoaddress(addr, 0.1)
# generate a txn using sendrawtransaction
us0 = utxos.pop()
inputs = [{"txid": us0["txid"], "vout": us0["vout"]}]
outputs = {addr: 0.1}
tx = node.createrawtransaction(inputs, outputs)
node.settxfee(min_relay_fee)
txF = node.fundrawtransaction(tx)
txFS = node.signrawtransactionwithwallet(txF["hex"])
rpc_tx_hsh = node.sendrawtransaction(txFS["hex"])
# check transactions are in unbroadcast using rpc
mempoolinfo = self.nodes[0].getmempoolinfo()
assert_equal(mempoolinfo['unbroadcastcount'], 2)
mempool = self.nodes[0].getrawmempool(True)
for tx in mempool:
assert_equal(mempool[tx]['unbroadcast'], True)
# check that second node doesn't have these two txns
mempool = self.nodes[1].getrawmempool()
assert rpc_tx_hsh not in mempool
assert wallet_tx_hsh not in mempool
# ensure that unbroadcast txs are persisted to mempool.dat
self.restart_node(0)
self.log.info("Reconnect nodes & check if they are sent to node 1")
self.connect_nodes(0, 1)
# fast forward into the future & ensure that the second node has the txns
node.mockscheduler(MAX_INITIAL_BROADCAST_DELAY)
self.sync_mempools(timeout=30)
mempool = self.nodes[1].getrawmempool()
assert rpc_tx_hsh in mempool
assert wallet_tx_hsh in mempool
# check that transactions are no longer in first node's unbroadcast set
mempool = self.nodes[0].getrawmempool(True)
for tx in mempool:
assert_equal(mempool[tx]['unbroadcast'], False)
self.log.info("Add another connection & ensure transactions aren't broadcast again")
conn = node.add_p2p_connection(P2PTxInvStore())
node.mockscheduler(MAX_INITIAL_BROADCAST_DELAY)
time.sleep(2) # allow sufficient time for possibility of broadcast
assert_equal(len(conn.get_invs()), 0)
self.disconnect_nodes(0, 1)
node.disconnect_p2ps()
self.log.info("Rebroadcast transaction and ensure it is not added to unbroadcast set when already in mempool")
rpc_tx_hsh = node.sendrawtransaction(txFS["hex"])
assert not node.getmempoolentry(rpc_tx_hsh)['unbroadcast']
def test_txn_removal(self):
self.log.info("Test that transactions removed from mempool are removed from unbroadcast set")
node = self.nodes[0]
# since the node doesn't have any connections, it will not receive
# any GETDATAs & thus the transaction will remain in the unbroadcast set.
addr = node.getnewaddress()
txhsh = node.sendtoaddress(addr, 0.1)
# check transaction was removed from unbroadcast set due to presence in
# a block
removal_reason = "Removed {} from set of unbroadcast txns before confirmation that txn was sent out".format(txhsh)
with node.assert_debug_log([removal_reason]):
self.generate(node, 1, sync_fun=self.no_op)
if __name__ == "__main__":
MempoolUnbroadcastTest().main()

View file

@ -0,0 +1,129 @@
#!/usr/bin/env python3
# Copyright (c) 2020-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test mempool descendants/ancestors information update.
Test mempool update of transaction descendants/ancestors information (count, size)
when transactions have been re-added from a disconnected block to the mempool.
"""
import time
from decimal import Decimal
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import assert_equal
class MempoolUpdateFromBlockTest(DigiByteTestFramework):
def set_test_params(self):
self.num_nodes = 1
self.extra_args = [['-limitdescendantsize=1000', '-limitancestorsize=1000']]
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
def transaction_graph_test(self, size, n_tx_to_mine=None, start_input_txid='', end_address='', fee=Decimal(0.0100000)):
"""Create an acyclic tournament (a type of directed graph) of transactions and use it for testing.
Keyword arguments:
size -- the order N of the tournament which is equal to the number of the created transactions
n_tx_to_mine -- the number of transaction that should be mined into a block
If all of the N created transactions tx[0]..tx[N-1] reside in the mempool,
the following holds:
the tx[K] transaction:
- has N-K descendants (including this one), and
- has K+1 ancestors (including this one)
More details: https://en.wikipedia.org/wiki/Tournament_(graph_theory)
"""
if not start_input_txid:
self.generate(self.nodes[0], 101)
start_input = self.nodes[0].listunspent()[0]
start_input_txid = start_input['txid']
start_input_vout = start_input['vout']
else:
start_input_vout = 0
if not end_address:
end_address = self.nodes[0].getnewaddress()
first_block_hash = ''
tx_id = []
tx_size = []
self.log.info('Creating {} transactions...'.format(size))
for i in range(0, size):
self.log.debug('Preparing transaction #{}...'.format(i))
# Prepare inputs.
if i == 0:
inputs = [{'txid': start_input_txid, 'vout': start_input_vout}]
inputs_value = self.nodes[0].gettxout(start_input_txid, start_input_vout)['value']
else:
inputs = []
inputs_value = 0
for j, tx in enumerate(tx_id[0:i]):
# Transaction tx[K] is a child of each of previous transactions tx[0]..tx[K-1] at their output K-1.
vout = i - j - 1
inputs.append({'txid': tx_id[j], 'vout': vout})
inputs_value += self.nodes[0].gettxout(tx, vout)['value']
self.log.debug('inputs={}'.format(inputs))
self.log.debug('inputs_value={}'.format(inputs_value))
# Prepare outputs.
tx_count = i + 1
if tx_count < size:
# Transaction tx[K] is an ancestor of each of subsequent transactions tx[K+1]..tx[N-1].
n_outputs = size - tx_count
output_value = ((inputs_value - fee) / Decimal(n_outputs)).quantize(Decimal('0.00000001'))
outputs = {}
for _ in range(n_outputs):
outputs[self.nodes[0].getnewaddress()] = output_value
else:
output_value = (inputs_value - fee).quantize(Decimal('0.00000001'))
outputs = {end_address: output_value}
self.log.debug('output_value={}'.format(output_value))
self.log.debug('outputs={}'.format(outputs))
# Create a new transaction.
unsigned_raw_tx = self.nodes[0].createrawtransaction(inputs, outputs)
signed_raw_tx = self.nodes[0].signrawtransactionwithwallet(unsigned_raw_tx)
tx_id.append(self.nodes[0].sendrawtransaction(signed_raw_tx['hex']))
tx_size.append(self.nodes[0].getmempoolentry(tx_id[-1])['vsize'])
if tx_count in n_tx_to_mine:
# The created transactions are mined into blocks by batches.
self.log.info('The batch of {} transactions has been accepted into the mempool.'.format(len(self.nodes[0].getrawmempool())))
block_hash = self.generate(self.nodes[0], 1)[0]
if not first_block_hash:
first_block_hash = block_hash
assert_equal(len(self.nodes[0].getrawmempool()), 0)
self.log.info('All of the transactions from the current batch have been mined into a block.')
elif tx_count == size:
# At the end all of the mined blocks are invalidated, and all of the created
# transactions should be re-added from disconnected blocks to the mempool.
self.log.info('The last batch of {} transactions has been accepted into the mempool.'.format(len(self.nodes[0].getrawmempool())))
start = time.time()
self.nodes[0].invalidateblock(first_block_hash)
end = time.time()
assert_equal(len(self.nodes[0].getrawmempool()), size)
self.log.info('All of the recently mined transactions have been re-added into the mempool in {} seconds.'.format(end - start))
self.log.info('Checking descendants/ancestors properties of all of the in-mempool transactions...')
for k, tx in enumerate(tx_id):
self.log.debug('Check transaction #{}.'.format(k))
entry = self.nodes[0].getmempoolentry(tx)
assert_equal(entry['descendantcount'], size - k)
assert_equal(entry['descendantsize'], sum(tx_size[k:size]))
assert_equal(entry['ancestorcount'], k + 1)
assert_equal(entry['ancestorsize'], sum(tx_size[0:(k + 1)]))
def run_test(self):
# Use batch size limited by DEFAULT_ANCESTOR_LIMIT = 25 to not fire "too many unconfirmed parents" error.
self.transaction_graph_test(size=100, n_tx_to_mine=[25, 50, 75])
if __name__ == '__main__':
MempoolUpdateFromBlockTest().main()

93
test/functional/minchainwork.py Executable file
View file

@ -0,0 +1,93 @@
#!/usr/bin/env python3
# Copyright (c) 2017 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test logic for setting nMinimumChainWork on command line.
Nodes don't consider themselves out of "initial block download" until
their active chain has more work than nMinimumChainWork.
Nodes don't download blocks from a peer unless the peer's best known block
has more work than nMinimumChainWork.
While in initial block download, nodes won't relay blocks to their peers, so
test that this parameter functions as intended by verifying that block relay
only succeeds past a given node once its nMinimumChainWork has been exceeded.
"""
import time
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import sync_blocks, connect_nodes, assert_equal
# 2 hashes required per regtest block (with no difficulty adjustment)
REGTEST_WORK_PER_BLOCK = 2
class MinimumChainWorkTest(DigiByteTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 3
self.extra_args = [[], ["-minimumchainwork=0x65"], ["-minimumchainwork=0x65"]]
self.node_min_work = [0, 101, 101]
def setup_network(self):
# This test relies on the chain setup being:
# node0 <- node1 <- node2
# Before leaving IBD, nodes prefer to download blocks from outbound
# peers, so ensure that we're mining on an outbound peer and testing
# block relay to inbound peers.
self.setup_nodes()
for i in range(self.num_nodes-1):
connect_nodes(self.nodes[i+1], i)
def run_test(self):
# Start building a chain on node0. node2 shouldn't be able to sync until node1's
# minchainwork is exceeded
starting_chain_work = REGTEST_WORK_PER_BLOCK # Genesis block's work
self.log.info("Testing relay across node %d (minChainWork = %d)", 1, self.node_min_work[1])
starting_blockcount = self.nodes[2].getblockcount()
num_blocks_to_generate = int((self.node_min_work[1] - starting_chain_work) / REGTEST_WORK_PER_BLOCK)
self.log.info("Generating %d blocks on node0", num_blocks_to_generate)
hashes = self.nodes[0].generate(num_blocks_to_generate)
self.log.info("Node0 current chain work: %s", self.nodes[0].getblockheader(hashes[-1])['chainwork'])
# Sleep a few seconds and verify that node2 didn't get any new blocks
# or headers. We sleep, rather than sync_blocks(node0, node1) because
# it's reasonable either way for node1 to get the blocks, or not get
# them (since they're below node1's minchainwork).
time.sleep(3)
self.log.info("Verifying node 2 has no more blocks than before")
self.log.info("Blockcounts: %s", [n.getblockcount() for n in self.nodes])
# Node2 shouldn't have any new headers yet, because node1 should not
# have relayed anything.
assert_equal(len(self.nodes[2].getchaintips()), 1)
assert_equal(self.nodes[2].getchaintips()[0]['height'], 0)
assert self.nodes[1].getbestblockhash() != self.nodes[0].getbestblockhash()
assert_equal(self.nodes[2].getblockcount(), starting_blockcount)
self.log.info("Generating one more block")
self.nodes[0].generate(1)
self.log.info("Verifying nodes are all synced")
# Because nodes in regtest are all manual connections (eg using
# addnode), node1 should not have disconnected node0. If not for that,
# we'd expect node1 to have disconnected node0 for serving an
# insufficient work chain, in which case we'd need to reconnect them to
# continue the test.
# NOTE: before commit 44407100ff9b478d6131a1c38ee993b50b1830df, regtest
# connections were not manual, so the reconnect is necessary.
if (len(self.nodes[0].getpeerinfo()) == 0):
connect_nodes(self.nodes[1], 0)
self.sync_all()
self.log.info("Blockcounts: %s", [n.getblockcount() for n in self.nodes])
if __name__ == '__main__':
MinimumChainWorkTest().main()

286
test/functional/mining_basic.py Executable file
View file

@ -0,0 +1,286 @@
#!/usr/bin/env python3
# Copyright (c) 2014-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test mining RPCs
- getmininginfo
- getblocktemplate proposal mode
- submitblock"""
import copy
from decimal import Decimal
from test_framework.blocktools import (
create_coinbase,
get_witness_script,
NORMAL_GBT_REQUEST_PARAMS,
TIME_GENESIS_BLOCK,
VERSIONBITS_TOP_BITS,
)
from test_framework.messages import (
CBlock,
CBlockHeader,
BLOCK_HEADER_SIZE,
ser_uint256,
)
from test_framework.p2p import P2PDataStore
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
)
VERSIONBITS_TOP_BITS = 0x20000000
VERSIONBITS_DEPLOYMENT_TESTDUMMY_BIT = 27
VERSIONBITS_DEPLOYMENT_TAPROOT_BIT = 0x02
def assert_template(node, block, expect, rehash=True):
if rehash:
block.hashMerkleRoot = block.calc_merkle_root()
rsp = node.getblocktemplate(template_request={
'data': block.serialize().hex(),
'mode': 'proposal',
'rules': ['segwit'],
})
assert_equal(rsp, expect)
class MiningTest(DigiByteTestFramework):
def set_test_params(self):
self.num_nodes = 2
self.setup_clean_chain = True
self.supports_cli = False
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
def mine_chain(self):
self.log.info('Create some old blocks')
for t in range(TIME_GENESIS_BLOCK, TIME_GENESIS_BLOCK + 240 * 15, 15):
self.nodes[0].setmocktime(t)
self.generate(self.nodes[0], 1, sync_fun=self.no_op)
mining_info = self.nodes[0].getmininginfo()
assert_equal(mining_info['blocks'], 240)
assert_equal(mining_info['currentblocktx'], 0)
assert_equal(mining_info['currentblockweight'], 4000)
self.log.info('test blockversion')
block_version = 255 | VERSIONBITS_TOP_BITS
self.restart_node(0, extra_args=[f'-mocktime={t}', '-blockversion={}'.format(block_version)])
self.connect_nodes(0, 1)
assert_equal(block_version, self.nodes[0].getblocktemplate(NORMAL_GBT_REQUEST_PARAMS)['version'])
self.restart_node(0, extra_args=[f'-mocktime={t}'])
self.connect_nodes(0, 1)
assert_equal(671089154, self.nodes[0].getblocktemplate(NORMAL_GBT_REQUEST_PARAMS)['version'])
self.restart_node(0)
self.connect_nodes(0, 1)
def run_test(self):
self.mine_chain()
node = self.nodes[0]
def assert_submitblock(block, result_str_1, result_str_2=None):
block.solve()
result_str_2 = result_str_2 or 'duplicate-invalid'
assert_equal(result_str_1, node.submitblock(hexdata=block.serialize().hex()))
assert_equal(result_str_2, node.submitblock(hexdata=block.serialize().hex()))
self.log.info('getmininginfo')
mining_info = node.getmininginfo()
assert_equal(mining_info['blocks'], 240)
assert_equal(mining_info['chain'], self.chain)
assert 'currentblocktx' not in mining_info
assert 'currentblockweight' not in mining_info
assert_equal(mining_info['difficulties']['sha256d'], Decimal('4.656542373906925E-10'))
assert_equal(mining_info['networkhashps'], Decimal('0.1344444444444444'))
assert_equal(mining_info['pooledtx'], 0)
self.log.info("getblocktemplate: Test default witness commitment")
txid = int(node.sendtoaddress(node.getnewaddress(), 1), 16)
tmpl = node.getblocktemplate(NORMAL_GBT_REQUEST_PARAMS)
# Check that default_witness_commitment is present.
assert 'default_witness_commitment' in tmpl
witness_commitment = tmpl['default_witness_commitment']
# Check that default_witness_commitment is correct.
witness_root = CBlock.get_merkle_root([ser_uint256(0),
ser_uint256(txid)])
script = get_witness_script(witness_root, 0)
assert_equal(witness_commitment, script.hex())
# Mine a block to leave initial block download and clear the mempool
self.generatetoaddress(node, 1, node.get_deterministic_priv_key().address)
tmpl = node.getblocktemplate(NORMAL_GBT_REQUEST_PARAMS)
self.log.info("getblocktemplate: Test capability advertised")
assert 'proposal' in tmpl['capabilities']
assert 'coinbasetxn' not in tmpl
next_height = int(tmpl["height"])
coinbase_tx = create_coinbase(height=next_height)
# sequence numbers must not be max for nLockTime to have effect
coinbase_tx.vin[0].nSequence = 2**32 - 2
coinbase_tx.rehash()
block = CBlock()
block.nVersion = tmpl["version"]
block.hashPrevBlock = int(tmpl["previousblockhash"], 16)
block.nTime = tmpl["curtime"]
block.nBits = int(tmpl["bits"], 16)
block.nNonce = 0
block.vtx = [coinbase_tx]
self.log.info("getblocktemplate: segwit rule must be set")
assert_raises_rpc_error(-8, "getblocktemplate must be called with the segwit rule set", node.getblocktemplate)
self.log.info("getblocktemplate: Test valid block")
assert_template(node, block, None)
self.log.info("submitblock: Test block decode failure")
assert_raises_rpc_error(-22, "Block decode failed", node.submitblock, block.serialize()[:-15].hex())
self.log.info("getblocktemplate: Test bad input hash for coinbase transaction")
bad_block = copy.deepcopy(block)
bad_block.vtx[0].vin[0].prevout.hash += 1
bad_block.vtx[0].rehash()
assert_template(node, bad_block, 'bad-cb-missing')
self.log.info("submitblock: Test invalid coinbase transaction")
assert_raises_rpc_error(-22, "Block does not start with a coinbase", node.submitblock, bad_block.serialize().hex())
self.log.info("getblocktemplate: Test truncated final transaction")
assert_raises_rpc_error(-22, "Block decode failed", node.getblocktemplate, {
'data': block.serialize()[:-1].hex(),
'mode': 'proposal',
'rules': ['segwit'],
})
self.log.info("getblocktemplate: Test duplicate transaction")
bad_block = copy.deepcopy(block)
bad_block.vtx.append(bad_block.vtx[0])
assert_template(node, bad_block, 'bad-txns-duplicate')
assert_submitblock(bad_block, 'bad-txns-duplicate', 'bad-txns-duplicate')
self.log.info("getblocktemplate: Test invalid transaction")
bad_block = copy.deepcopy(block)
bad_tx = copy.deepcopy(bad_block.vtx[0])
bad_tx.vin[0].prevout.hash = 255
bad_tx.rehash()
bad_block.vtx.append(bad_tx)
assert_template(node, bad_block, 'bad-txns-inputs-missingorspent')
assert_submitblock(bad_block, 'bad-txns-inputs-missingorspent')
self.log.info("getblocktemplate: Test nonfinal transaction")
bad_block = copy.deepcopy(block)
bad_block.vtx[0].nLockTime = 2**32 - 1
bad_block.vtx[0].rehash()
assert_template(node, bad_block, 'bad-txns-nonfinal')
assert_submitblock(bad_block, 'bad-txns-nonfinal')
self.log.info("getblocktemplate: Test bad tx count")
# The tx count is immediately after the block header
bad_block_sn = bytearray(block.serialize())
assert_equal(bad_block_sn[BLOCK_HEADER_SIZE], 1)
bad_block_sn[BLOCK_HEADER_SIZE] += 1
assert_raises_rpc_error(-22, "Block decode failed", node.getblocktemplate, {
'data': bad_block_sn.hex(),
'mode': 'proposal',
'rules': ['segwit'],
})
self.log.info("getblocktemplate: Test bad bits")
bad_block = copy.deepcopy(block)
bad_block.nBits = 469762303 # impossible in the real world
assert_template(node, bad_block, 'bad-diffbits')
self.log.info("getblocktemplate: Test bad merkle root")
bad_block = copy.deepcopy(block)
bad_block.hashMerkleRoot += 1
assert_template(node, bad_block, 'bad-txnmrklroot', False)
assert_submitblock(bad_block, 'bad-txnmrklroot', 'bad-txnmrklroot')
self.log.info("getblocktemplate: Test bad timestamps")
bad_block = copy.deepcopy(block)
bad_block.nTime = 2**31 - 1
assert_template(node, bad_block, 'time-too-new')
assert_submitblock(bad_block, 'time-too-new', 'time-too-new')
bad_block.nTime = 0
assert_template(node, bad_block, 'time-too-old')
assert_submitblock(bad_block, 'time-too-old', 'time-too-old')
self.log.info("getblocktemplate: Test not best block")
bad_block = copy.deepcopy(block)
bad_block.hashPrevBlock = 123
assert_template(node, bad_block, 'inconclusive-not-best-prevblk')
assert_submitblock(bad_block, 'prev-blk-not-found', 'prev-blk-not-found')
self.log.info('submitheader tests')
assert_raises_rpc_error(-22, 'Block header decode failed', lambda: node.submitheader(hexdata='xx' * BLOCK_HEADER_SIZE))
assert_raises_rpc_error(-22, 'Block header decode failed', lambda: node.submitheader(hexdata='ff' * (BLOCK_HEADER_SIZE-2)))
assert_raises_rpc_error(-25, 'Must submit previous header', lambda: node.submitheader(hexdata=super(CBlock, bad_block).serialize().hex()))
block.nTime += 1
block.solve()
def chain_tip(b_hash, *, status='headers-only', branchlen=1):
return {'hash': b_hash, 'height': 242, 'branchlen': branchlen, 'status': status}
assert chain_tip(block.hash) not in node.getchaintips()
node.submitheader(hexdata=block.serialize().hex())
assert chain_tip(block.hash) in node.getchaintips()
node.submitheader(hexdata=CBlockHeader(block).serialize().hex()) # Noop
assert chain_tip(block.hash) in node.getchaintips()
bad_block_root = copy.deepcopy(block)
bad_block_root.hashMerkleRoot += 2
bad_block_root.solve()
assert chain_tip(bad_block_root.hash) not in node.getchaintips()
node.submitheader(hexdata=CBlockHeader(bad_block_root).serialize().hex())
assert chain_tip(bad_block_root.hash) in node.getchaintips()
# Should still reject invalid blocks, even if we have the header:
assert_equal(node.submitblock(hexdata=bad_block_root.serialize().hex()), 'bad-txnmrklroot')
assert_equal(node.submitblock(hexdata=bad_block_root.serialize().hex()), 'bad-txnmrklroot')
assert chain_tip(bad_block_root.hash) in node.getchaintips()
# We know the header for this invalid block, so should just return early without error:
node.submitheader(hexdata=CBlockHeader(bad_block_root).serialize().hex())
assert chain_tip(bad_block_root.hash) in node.getchaintips()
bad_block_lock = copy.deepcopy(block)
bad_block_lock.vtx[0].nLockTime = 2**32 - 1
bad_block_lock.vtx[0].rehash()
bad_block_lock.hashMerkleRoot = bad_block_lock.calc_merkle_root()
bad_block_lock.solve()
assert_equal(node.submitblock(hexdata=bad_block_lock.serialize().hex()), 'bad-txns-nonfinal')
assert_equal(node.submitblock(hexdata=bad_block_lock.serialize().hex()), 'duplicate-invalid')
# Build a "good" block on top of the submitted bad block
bad_block2 = copy.deepcopy(block)
bad_block2.hashPrevBlock = bad_block_lock.sha256
bad_block2.solve()
assert_raises_rpc_error(-25, 'bad-prevblk', lambda: node.submitheader(hexdata=CBlockHeader(bad_block2).serialize().hex()))
# Should reject invalid header right away
bad_block_time = copy.deepcopy(block)
bad_block_time.nTime = 1
bad_block_time.solve()
assert_raises_rpc_error(-25, 'time-too-old', lambda: node.submitheader(hexdata=CBlockHeader(bad_block_time).serialize().hex()))
# Should ask for the block from a p2p node, if they announce the header as well:
peer = node.add_p2p_connection(P2PDataStore())
peer.wait_for_getheaders(timeout=5) # Drop the first getheaders
peer.send_blocks_and_test(blocks=[block], node=node)
# Must be active now:
assert chain_tip(block.hash, status='active', branchlen=0) in node.getchaintips()
# Building a few blocks should give the same results
self.generatetoaddress(node, 10, node.get_deterministic_priv_key().address)
assert_raises_rpc_error(-25, 'time-too-old', lambda: node.submitheader(hexdata=CBlockHeader(bad_block_time).serialize().hex()))
assert_raises_rpc_error(-25, 'bad-prevblk', lambda: node.submitheader(hexdata=CBlockHeader(bad_block2).serialize().hex()))
node.submitheader(hexdata=CBlockHeader(block).serialize().hex())
node.submitheader(hexdata=CBlockHeader(bad_block_root).serialize().hex())
assert_equal(node.submitblock(hexdata=block.serialize().hex()), 'duplicate') # valid
if __name__ == '__main__':
MiningTest().main()

View file

@ -0,0 +1,81 @@
#!/usr/bin/env python3
# Copyright (c) 2014-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test longpolling with getblocktemplate."""
from decimal import Decimal
import random
import threading
from test_framework.blocktools import COINBASE_MATURITY
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import get_rpc_proxy
from test_framework.wallet import MiniWallet
class LongpollThread(threading.Thread):
def __init__(self, node):
threading.Thread.__init__(self)
# query current longpollid
template = node.getblocktemplate({'rules': ['segwit']})
self.longpollid = template['longpollid']
# create a new connection to the node, we can't use the same
# connection from two threads
self.node = get_rpc_proxy(node.url, 1, timeout=600, coveragedir=node.coverage_dir)
def run(self):
self.node.getblocktemplate({'longpollid': self.longpollid, 'rules': ['segwit']})
class GetBlockTemplateLPTest(DigiByteTestFramework):
def set_test_params(self):
self.num_nodes = 2
self.supports_cli = False
def run_test(self):
self.log.info("Warning: this test will take about 70 seconds in the best case. Be patient.")
self.log.info("Test that longpollid doesn't change between successive getblocktemplate() invocations if nothing else happens")
self.generate(self.nodes[0], 10)
template = self.nodes[0].getblocktemplate({'rules': ['segwit']})
longpollid = template['longpollid']
template2 = self.nodes[0].getblocktemplate({'rules': ['segwit']})
assert template2['longpollid'] == longpollid
self.log.info("Test that longpoll waits if we do nothing")
thr = LongpollThread(self.nodes[0])
thr.start()
# check that thread still lives
thr.join(5) # wait 5 seconds or until thread exits
assert thr.is_alive()
miniwallets = [MiniWallet(node) for node in self.nodes]
self.log.info("Test that longpoll will terminate if another node generates a block")
self.generate(miniwallets[1], 1) # generate a block on another node
# check that thread will exit now that new transaction entered mempool
thr.join(5) # wait 5 seconds or until thread exits
assert not thr.is_alive()
self.log.info("Test that longpoll will terminate if we generate a block ourselves")
thr = LongpollThread(self.nodes[0])
thr.start()
self.generate(miniwallets[0], 1) # generate a block on own node
thr.join(5) # wait 5 seconds or until thread exits
assert not thr.is_alive()
# Add enough mature utxos to the wallets, so that all txs spend confirmed coins
self.generate(self.nodes[0], COINBASE_MATURITY)
self.log.info("Test that introducing a new transaction into the mempool will terminate the longpoll")
thr = LongpollThread(self.nodes[0])
thr.start()
# generate a random transaction and submit it
min_relay_fee = self.nodes[0].getnetworkinfo()["relayfee"]
fee_rate = min_relay_fee + Decimal('0.00000010') * random.randint(0,20)
miniwallets[0].send_self_transfer(from_node=random.choice(self.nodes),
fee_rate=fee_rate)
# after one minute, every 10 seconds the mempool is probed, so in 80 seconds it should have returned
thr.join(60 + 20)
assert not thr.is_alive()
if __name__ == '__main__':
GetBlockTemplateLPTest().main()

View file

@ -0,0 +1,157 @@
#!/usr/bin/env python3
# Copyright (c) 2015-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test the prioritisetransaction mining RPC."""
import time
from test_framework.messages import COIN, MAX_BLOCK_WEIGHT
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import assert_equal, assert_raises_rpc_error, create_confirmed_utxos, create_lots_of_big_transactions, gen_return_txouts
class PrioritiseTransactionTest(DigiByteTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
self.extra_args = [[
"-printpriority=1",
"-acceptnonstdtxn=1",
]] * self.num_nodes
self.supports_cli = False
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
def run_test(self):
# Test `prioritisetransaction` required parameters
assert_raises_rpc_error(-1, "prioritisetransaction", self.nodes[0].prioritisetransaction)
assert_raises_rpc_error(-1, "prioritisetransaction", self.nodes[0].prioritisetransaction, '')
assert_raises_rpc_error(-1, "prioritisetransaction", self.nodes[0].prioritisetransaction, '', 0)
# Test `prioritisetransaction` invalid extra parameters
assert_raises_rpc_error(-1, "prioritisetransaction", self.nodes[0].prioritisetransaction, '', 0, 0, 0)
# Test `prioritisetransaction` invalid `txid`
assert_raises_rpc_error(-8, "txid must be of length 64 (not 3, for 'foo')", self.nodes[0].prioritisetransaction, txid='foo', fee_delta=0)
assert_raises_rpc_error(-8, "txid must be hexadecimal string (not 'Zd1d4e24ed99057e84c3f80fd8fbec79ed9e1acee37da269356ecea000000000')", self.nodes[0].prioritisetransaction, txid='Zd1d4e24ed99057e84c3f80fd8fbec79ed9e1acee37da269356ecea000000000', fee_delta=0)
# Test `prioritisetransaction` invalid `dummy`
txid = '1d1d4e24ed99057e84c3f80fd8fbec79ed9e1acee37da269356ecea000000000'
assert_raises_rpc_error(-1, "JSON value is not a number as expected", self.nodes[0].prioritisetransaction, txid, 'foo', 0)
assert_raises_rpc_error(-8, "Priority is no longer supported, dummy argument to prioritisetransaction must be 0.", self.nodes[0].prioritisetransaction, txid, 1, 0)
# Test `prioritisetransaction` invalid `fee_delta`
assert_raises_rpc_error(-1, "JSON value is not an integer as expected", self.nodes[0].prioritisetransaction, txid=txid, fee_delta='foo')
self.txouts = gen_return_txouts()
self.relayfee = self.nodes[0].getnetworkinfo()['relayfee']
utxo_count = 90
utxos = create_confirmed_utxos(self, self.relayfee, self.nodes[0], utxo_count)
base_fee = self.relayfee*100 # our transactions are smaller than 100kb
txids = []
# Create 3 batches of transactions at 3 different fee rate levels
range_size = utxo_count // 3
for i in range(3):
txids.append([])
start_range = i * range_size
end_range = start_range + range_size
txids[i] = create_lots_of_big_transactions(self.nodes[0], self.txouts, utxos[start_range:end_range], end_range - start_range, (i+1)*base_fee)
# Make sure that the size of each group of transactions exceeds
# MAX_BLOCK_WEIGHT // 4 -- otherwise the test needs to be revised to
# create more transactions.
mempool = self.nodes[0].getrawmempool(True)
sizes = [0, 0, 0]
for i in range(3):
for j in txids[i]:
assert j in mempool
sizes[i] += mempool[j]['vsize']
assert sizes[i] > MAX_BLOCK_WEIGHT // 4 # Fail => raise utxo_count
# add a fee delta to something in the cheapest bucket and make sure it gets mined
# also check that a different entry in the cheapest bucket is NOT mined
self.nodes[0].prioritisetransaction(txid=txids[0][0], fee_delta=int(3*base_fee*COIN))
self.generate(self.nodes[0], 1)
mempool = self.nodes[0].getrawmempool()
self.log.info("Assert that prioritised transaction was mined")
assert txids[0][0] not in mempool
assert txids[0][1] in mempool
high_fee_tx = None
for x in txids[2]:
if x not in mempool:
high_fee_tx = x
# Something high-fee should have been mined!
assert high_fee_tx is not None
# Add a prioritisation before a tx is in the mempool (de-prioritising a
# high-fee transaction so that it's now low fee).
self.nodes[0].prioritisetransaction(txid=high_fee_tx, fee_delta=-int(2*base_fee*COIN))
# Add everything back to mempool
self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
# Check to make sure our high fee rate tx is back in the mempool
mempool = self.nodes[0].getrawmempool()
assert high_fee_tx in mempool
# Now verify the modified-high feerate transaction isn't mined before
# the other high fee transactions. Keep mining until our mempool has
# decreased by all the high fee size that we calculated above.
while (self.nodes[0].getmempoolinfo()['bytes'] > sizes[0] + sizes[1]):
self.generate(self.nodes[0], 1, sync_fun=self.no_op)
# High fee transaction should not have been mined, but other high fee rate
# transactions should have been.
mempool = self.nodes[0].getrawmempool()
self.log.info("Assert that de-prioritised transaction is still in mempool")
assert high_fee_tx in mempool
for x in txids[2]:
if (x != high_fee_tx):
assert x not in mempool
# Create a free transaction. Should be rejected.
utxo_list = self.nodes[0].listunspent()
assert len(utxo_list) > 0
utxo = utxo_list[0]
inputs = []
outputs = {}
inputs.append({"txid" : utxo["txid"], "vout" : utxo["vout"]})
outputs[self.nodes[0].getnewaddress()] = utxo["amount"]
raw_tx = self.nodes[0].createrawtransaction(inputs, outputs)
tx_hex = self.nodes[0].signrawtransactionwithwallet(raw_tx)["hex"]
tx_id = self.nodes[0].decoderawtransaction(tx_hex)["txid"]
# This will raise an exception due to min relay fee not being met
assert_raises_rpc_error(-26, "min relay fee not met", self.nodes[0].sendrawtransaction, tx_hex)
assert tx_id not in self.nodes[0].getrawmempool()
# This is a less than 1000-byte transaction, so just set the fee
# to be the minimum for a 1000-byte transaction and check that it is
# accepted.
self.nodes[0].prioritisetransaction(txid=tx_id, fee_delta=int(self.relayfee*COIN))
self.log.info("Assert that prioritised free transaction is accepted to mempool")
assert_equal(self.nodes[0].sendrawtransaction(tx_hex), tx_id)
assert tx_id in self.nodes[0].getrawmempool()
# Test that calling prioritisetransaction is sufficient to trigger
# getblocktemplate to (eventually) return a new block.
mock_time = int(time.time())
self.nodes[0].setmocktime(mock_time)
template = self.nodes[0].getblocktemplate({'rules': ['segwit']})
self.nodes[0].prioritisetransaction(txid=tx_id, fee_delta=-int(self.relayfee*COIN))
self.nodes[0].setmocktime(mock_time+10)
new_template = self.nodes[0].getblocktemplate({'rules': ['segwit']})
assert template != new_template
if __name__ == '__main__':
PrioritiseTransactionTest().main()

View file

@ -0,0 +1,66 @@
#!/usr/bin/env python3
# Copyright (c) 2018-2021 The Bitcoin Core developers
# Copyright (c) 2022 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
import os
import sys
import argparse
import json
def perform_pre_checks():
mock_result_path = os.path.join(os.getcwd(), "mock_result")
if(os.path.isfile(mock_result_path)):
with open(mock_result_path, "r", encoding="utf8") as f:
mock_result = f.read()
if mock_result[0]:
sys.stdout.write(mock_result[2:])
sys.exit(int(mock_result[0]))
def enumerate(args):
sys.stdout.write(json.dumps([{"fingerprint": "b3c19bfc", "type": "trezor", "model": "trezor_t"}, {"fingerprint": "00000002"}]))
def getdescriptors(args):
xpub_pkh = "xpub6CRhJvXV8x2AKWvqi1ZSMFU6cbxzQiYrv3dxSUXCawjMJ1JzpqVsveH4way1yCmJm29KzH1zrVZmVwes4Qo6oXVE1HFn4fdiKrYJngqFFc6"
xpub_sh = "xpub6CoNoq3Tg4tGSpom2BSwL42gy864KHo3TXkHxLxBbhvCkgmdVXADQmiHbLZhX3Me1cYhRx7s25Lpm4LnT5zu395ANHsXB2QvT9tqJDAibTN"
xpub_wpkh = "xpub6DUcLgY1DfgDy2RV6q4djwwsLitaoZDumbribqrR8mP78fEtgZa1XEsqT5MWQ7gwLwKsTQPT28XLoVE5A97rDNTwMXjmzPaNijoCApCbWvp"
sys.stdout.write(json.dumps({
"receive": [
"pkh([b3c19bfc/44'/1'/" + args.account + "']" + xpub_pkh + "/0/*)#h26nxtl9",
"sh(wpkh([b3c19bfc/49'/1'/" + args.account + "']" + xpub_sh + "/0/*))#32ry02yp",
"wpkh([b3c19bfc/84'/1'/" + args.account + "']" + xpub_wpkh + "/0/*)#jftn8ppv"
],
"internal": [
"pkh([b3c19bfc/44'/1'/" + args.account + "']" + xpub_pkh + "/1/*)#x7ljm70a",
"sh(wpkh([b3c19bfc/49'/1'/" + args.account + "']" + xpub_sh + "/1/*))#ytdjh437",
"wpkh([b3c19bfc/84'/1'/" + args.account + "']" + xpub_wpkh + "/1/*)#rawj6535"
]
}))
parser = argparse.ArgumentParser(prog='./invalid_signer.py', description='External invalid signer mock')
parser.add_argument('--fingerprint')
parser.add_argument('--chain', default='main')
parser.add_argument('--stdin', action='store_true')
subparsers = parser.add_subparsers(description='Commands', dest='command')
subparsers.required = True
parser_enumerate = subparsers.add_parser('enumerate', help='list available signers')
parser_enumerate.set_defaults(func=enumerate)
parser_getdescriptors = subparsers.add_parser('getdescriptors')
parser_getdescriptors.set_defaults(func=getdescriptors)
parser_getdescriptors.add_argument('--account', metavar='account')
if not sys.stdin.isatty():
buffer = sys.stdin.read()
if buffer and buffer.rstrip() != "":
sys.argv.extend(buffer.rstrip().split(" "))
args = parser.parse_args()
perform_pre_checks()
args.func(args)

102
test/functional/mocks/signer.py Executable file
View file

@ -0,0 +1,102 @@
#!/usr/bin/env python3
# Copyright (c) 2018-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
import os
import sys
import argparse
import json
def perform_pre_checks():
mock_result_path = os.path.join(os.getcwd(), "mock_result")
if(os.path.isfile(mock_result_path)):
with open(mock_result_path, "r", encoding="utf8") as f:
mock_result = f.read()
if mock_result[0]:
sys.stdout.write(mock_result[2:])
sys.exit(int(mock_result[0]))
def enumerate(args):
sys.stdout.write(json.dumps([{"fingerprint": "00000001", "type": "trezor", "model": "trezor_t"}, {"fingerprint": "00000002"}]))
def getdescriptors(args):
xpub = "tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B"
sys.stdout.write(json.dumps({
"receive": [
"pkh([00000001/44'/1'/" + args.account + "']" + xpub + "/0/*)#vt6w3l3j",
"sh(wpkh([00000001/49'/1'/" + args.account + "']" + xpub + "/0/*))#r0grqw5x",
"wpkh([00000001/84'/1'/" + args.account + "']" + xpub + "/0/*)#x30uthjs"
],
"internal": [
"pkh([00000001/44'/1'/" + args.account + "']" + xpub + "/1/*)#all0v2p2",
"sh(wpkh([00000001/49'/1'/" + args.account + "']" + xpub + "/1/*))#kwx4c3pe",
"wpkh([00000001/84'/1'/" + args.account + "']" + xpub + "/1/*)#h92akzzg"
]
}))
def displayaddress(args):
# Several descriptor formats are acceptable, so allowing for potential
# changes to InferDescriptor:
if args.fingerprint != "00000001":
return sys.stdout.write(json.dumps({"error": "Unexpected fingerprint", "fingerprint": args.fingerprint}))
expected_desc = [
"wpkh([00000001/84'/1'/0'/0/0]02c97dc3f4420402e01a113984311bf4a1b8de376cac0bdcfaf1b3ac81f13433c7)#0yneg42r"
]
if args.desc not in expected_desc:
return sys.stdout.write(json.dumps({"error": "Unexpected descriptor", "desc": args.desc}))
return sys.stdout.write(json.dumps({"address": "bcrt1qm90ugl4d48jv8n6e5t9ln6t9zlpm5th68x4f8g"}))
def signtx(args):
if args.fingerprint != "00000001":
return sys.stdout.write(json.dumps({"error": "Unexpected fingerprint", "fingerprint": args.fingerprint}))
with open(os.path.join(os.getcwd(), "mock_psbt"), "r", encoding="utf8") as f:
mock_psbt = f.read()
if args.fingerprint == "00000001" :
sys.stdout.write(json.dumps({
"psbt": mock_psbt,
"complete": True
}))
else:
sys.stdout.write(json.dumps({"psbt": args.psbt}))
parser = argparse.ArgumentParser(prog='./signer.py', description='External signer mock')
parser.add_argument('--fingerprint')
parser.add_argument('--chain', default='main')
parser.add_argument('--stdin', action='store_true')
subparsers = parser.add_subparsers(description='Commands', dest='command')
subparsers.required = True
parser_enumerate = subparsers.add_parser('enumerate', help='list available signers')
parser_enumerate.set_defaults(func=enumerate)
parser_getdescriptors = subparsers.add_parser('getdescriptors')
parser_getdescriptors.set_defaults(func=getdescriptors)
parser_getdescriptors.add_argument('--account', metavar='account')
parser_displayaddress = subparsers.add_parser('displayaddress', help='display address on signer')
parser_displayaddress.add_argument('--desc', metavar='desc')
parser_displayaddress.set_defaults(func=displayaddress)
parser_signtx = subparsers.add_parser('signtx')
parser_signtx.add_argument('psbt', metavar='psbt')
parser_signtx.set_defaults(func=signtx)
if not sys.stdin.isatty():
buffer = sys.stdin.read()
if buffer and buffer.rstrip() != "":
sys.argv.extend(buffer.rstrip().split(" "))
args = parser.parse_args()
perform_pre_checks()
args.func(args)

90
test/functional/multiwallet.py Executable file
View file

@ -0,0 +1,90 @@
#!/usr/bin/env python3
# Copyright (c) 2017 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test multiwallet.
Verify that a digibyted node can load multiple wallet files
"""
import os
import shutil
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import assert_equal, assert_raises_rpc_error
class MultiWalletTest(DigiByteTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
self.extra_args = [['-wallet=w1', '-wallet=w2', '-wallet=w3']]
def run_test(self):
assert_equal(set(self.nodes[0].listwallets()), {"w1", "w2", "w3"})
self.stop_node(0)
# should not initialize if there are duplicate wallets
self.assert_start_raises_init_error(0, ['-wallet=w1', '-wallet=w1'], 'Error loading wallet w1. Duplicate -wallet filename specified.')
# should not initialize if wallet file is a directory
os.mkdir(os.path.join(self.options.tmpdir, 'node0', 'regtest', 'w11'))
self.assert_start_raises_init_error(0, ['-wallet=w11'], 'Error loading wallet w11. -wallet filename must be a regular file.')
# should not initialize if one wallet is a copy of another
shutil.copyfile(os.path.join(self.options.tmpdir, 'node0', 'regtest', 'w2'),
os.path.join(self.options.tmpdir, 'node0', 'regtest', 'w22'))
self.assert_start_raises_init_error(0, ['-wallet=w2', '-wallet=w22'], 'duplicates fileid')
# should not initialize if wallet file is a symlink
os.symlink(os.path.join(self.options.tmpdir, 'node0', 'regtest', 'w1'), os.path.join(self.options.tmpdir, 'node0', 'regtest', 'w12'))
self.assert_start_raises_init_error(0, ['-wallet=w12'], 'Error loading wallet w12. -wallet filename must be a regular file.')
self.start_node(0, self.extra_args[0])
w1 = self.nodes[0].get_wallet_rpc("w1")
w2 = self.nodes[0].get_wallet_rpc("w2")
w3 = self.nodes[0].get_wallet_rpc("w3")
wallet_bad = self.nodes[0].get_wallet_rpc("bad")
w1.generate(1)
# accessing invalid wallet fails
assert_raises_rpc_error(-18, "Requested wallet does not exist or is not loaded", wallet_bad.getwalletinfo)
# accessing wallet RPC without using wallet endpoint fails
assert_raises_rpc_error(-19, "Wallet file not specified", self.nodes[0].getwalletinfo)
# check w1 wallet balance
w1_info = w1.getwalletinfo()
assert_equal(w1_info['immature_balance'], 50)
w1_name = w1_info['walletname']
assert_equal(w1_name, "w1")
# check w2 wallet balance
w2_info = w2.getwalletinfo()
assert_equal(w2_info['immature_balance'], 0)
w2_name = w2_info['walletname']
assert_equal(w2_name, "w2")
w3_name = w3.getwalletinfo()['walletname']
assert_equal(w3_name, "w3")
assert_equal({"w1", "w2", "w3"}, {w1_name, w2_name, w3_name})
w1.generate(101)
assert_equal(w1.getbalance(), 100)
assert_equal(w2.getbalance(), 0)
assert_equal(w3.getbalance(), 0)
w1.sendtoaddress(w2.getnewaddress(), 1)
w1.sendtoaddress(w3.getnewaddress(), 2)
w1.generate(1)
assert_equal(w2.getbalance(), 1)
assert_equal(w3.getbalance(), 2)
batch = w1.batch([w1.getblockchaininfo.get_request(), w1.getwalletinfo.get_request()])
assert_equal(batch[0]["result"]["chain"], "regtest")
assert_equal(batch[1]["result"]["walletname"], "w1")
if __name__ == '__main__':
MultiWalletTest().main()

View file

@ -0,0 +1,329 @@
#!/usr/bin/env python3
# Copyright (c) 2015-2017 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test processing of unrequested blocks.
Setup: two nodes, node0+node1, not connected to each other. Node1 will have
nMinimumChainWork set to 0x10, so it won't process low-work unrequested blocks.
We have one NodeConn connection to node0 called test_node, and one to node1
called min_work_node.
The test:
1. Generate one block on each node, to leave IBD.
2. Mine a new block on each tip, and deliver to each node from node's peer.
The tip should advance for node0, but node1 should skip processing due to
nMinimumChainWork.
Node1 is unused in tests 3-7:
3. Mine a block that forks from the genesis block, and deliver to test_node.
Node0 should not process this block (just accept the header), because it
is unrequested and doesn't have more or equal work to the tip.
4a,b. Send another two blocks that build on the forking block.
Node0 should process the second block but be stuck on the shorter chain,
because it's missing an intermediate block.
4c.Send 288 more blocks on the longer chain (the number of blocks ahead
we currently store).
Node0 should process all but the last block (too far ahead in height).
5. Send a duplicate of the block in #3 to Node0.
Node0 should not process the block because it is unrequested, and stay on
the shorter chain.
6. Send Node0 an inv for the height 3 block produced in #4 above.
Node0 should figure out that Node0 has the missing height 2 block and send a
getdata.
7. Send Node0 the missing block again.
Node0 should process and the tip should advance.
8. Create a fork which is invalid at a height longer than the current chain
(ie to which the node will try to reorg) but which has headers built on top
of the invalid block. Check that we get disconnected if we send more headers
on the chain the node now knows to be invalid.
9. Test Node1 is able to sync when connected to node0 (which should have sufficient
work on its chain).
"""
from test_framework.mininode import *
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import *
import time
from test_framework.blocktools import create_block, create_coinbase, create_transaction
class AcceptBlockTest(DigiByteTestFramework):
def add_options(self, parser):
parser.add_option("--testbinary", dest="testbinary",
default=os.getenv("DIGIBYTED", "digibyted"),
help="digibyted binary to test")
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 2
self.extra_args = [[], ["-minimumchainwork=0x10"]]
def setup_network(self):
# Node0 will be used to test behavior of processing unrequested blocks
# from peers which are not whitelisted, while Node1 will be used for
# the whitelisted case.
# Node2 will be used for non-whitelisted peers to test the interaction
# with nMinimumChainWork.
self.setup_nodes()
def run_test(self):
# Setup the p2p connections and start up the network thread.
test_node = NodeConnCB() # connects to node0
min_work_node = NodeConnCB() # connects to node1
connections = []
connections.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], test_node))
connections.append(NodeConn('127.0.0.1', p2p_port(1), self.nodes[1], min_work_node))
test_node.add_connection(connections[0])
min_work_node.add_connection(connections[1])
NetworkThread().start() # Start up network handling in another thread
# Test logic begins here
test_node.wait_for_verack()
min_work_node.wait_for_verack()
# 1. Have nodes mine a block (leave IBD)
[ n.generate(1) for n in self.nodes ]
tips = [ int("0x" + n.getbestblockhash(), 0) for n in self.nodes ]
# 2. Send one block that builds on each tip.
# This should be accepted by node0
blocks_h2 = [] # the height 2 blocks on each node's chain
block_time = int(time.time()) + 1
for i in range(2):
blocks_h2.append(create_block(tips[i], create_coinbase(2), block_time))
blocks_h2[i].solve()
block_time += 1
test_node.send_message(msg_block(blocks_h2[0]))
min_work_node.send_message(msg_block(blocks_h2[1]))
for x in [test_node, min_work_node]:
x.sync_with_ping()
assert_equal(self.nodes[0].getblockcount(), 2)
assert_equal(self.nodes[1].getblockcount(), 1)
self.log.info("First height 2 block accepted by node0; correctly rejected by node1")
# 3. Send another block that builds on genesis.
block_h1f = create_block(int("0x" + self.nodes[0].getblockhash(0), 0), create_coinbase(1), block_time)
block_time += 1
block_h1f.solve()
test_node.send_message(msg_block(block_h1f))
test_node.sync_with_ping()
tip_entry_found = False
for x in self.nodes[0].getchaintips():
if x['hash'] == block_h1f.hash:
assert_equal(x['status'], "headers-only")
tip_entry_found = True
assert(tip_entry_found)
assert_raises_rpc_error(-1, "Block not found on disk", self.nodes[0].getblock, block_h1f.hash)
# 4. Send another two block that build on the fork.
block_h2f = create_block(block_h1f.sha256, create_coinbase(2), block_time)
block_time += 1
block_h2f.solve()
test_node.send_message(msg_block(block_h2f))
test_node.sync_with_ping()
# Since the earlier block was not processed by node, the new block
# can't be fully validated.
tip_entry_found = False
for x in self.nodes[0].getchaintips():
if x['hash'] == block_h2f.hash:
assert_equal(x['status'], "headers-only")
tip_entry_found = True
assert(tip_entry_found)
# But this block should be accepted by node since it has equal work.
self.nodes[0].getblock(block_h2f.hash)
self.log.info("Second height 2 block accepted, but not reorg'ed to")
# 4b. Now send another block that builds on the forking chain.
block_h3 = create_block(block_h2f.sha256, create_coinbase(3), block_h2f.nTime+1)
block_h3.solve()
test_node.send_message(msg_block(block_h3))
test_node.sync_with_ping()
# Since the earlier block was not processed by node, the new block
# can't be fully validated.
tip_entry_found = False
for x in self.nodes[0].getchaintips():
if x['hash'] == block_h3.hash:
assert_equal(x['status'], "headers-only")
tip_entry_found = True
assert(tip_entry_found)
self.nodes[0].getblock(block_h3.hash)
# But this block should be accepted by node since it has more work.
self.nodes[0].getblock(block_h3.hash)
self.log.info("Unrequested more-work block accepted")
# 4c. Now mine 288 more blocks and deliver; all should be processed but
# the last (height-too-high) on node (as long as its not missing any headers)
tip = block_h3
all_blocks = []
for i in range(288):
next_block = create_block(tip.sha256, create_coinbase(i + 4), tip.nTime+1)
next_block.solve()
all_blocks.append(next_block)
tip = next_block
# Now send the block at height 5 and check that it wasn't accepted (missing header)
test_node.send_message(msg_block(all_blocks[1]))
test_node.sync_with_ping()
assert_raises_rpc_error(-5, "Block not found", self.nodes[0].getblock, all_blocks[1].hash)
assert_raises_rpc_error(-5, "Block not found", self.nodes[0].getblockheader, all_blocks[1].hash)
# The block at height 5 should be accepted if we provide the missing header, though
headers_message = msg_headers()
headers_message.headers.append(CBlockHeader(all_blocks[0]))
test_node.send_message(headers_message)
test_node.send_message(msg_block(all_blocks[1]))
test_node.sync_with_ping()
self.nodes[0].getblock(all_blocks[1].hash)
# Now send the blocks in all_blocks
for i in range(288):
test_node.send_message(msg_block(all_blocks[i]))
test_node.sync_with_ping()
# Blocks 1-287 should be accepted, block 288 should be ignored because it's too far ahead
for x in all_blocks[:-1]:
self.nodes[0].getblock(x.hash)
assert_raises_rpc_error(-1, "Block not found on disk", self.nodes[0].getblock, all_blocks[-1].hash)
# 5. Test handling of unrequested block on the node that didn't process
# Should still not be processed (even though it has a child that has more
# work).
# The node should have requested the blocks at some point, so
# disconnect/reconnect first
connections[0].disconnect_node()
test_node.wait_for_disconnect()
test_node = NodeConnCB() # connects to node (not whitelisted)
connections[0] = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], test_node)
test_node.add_connection(connections[0])
test_node.wait_for_verack()
test_node.send_message(msg_block(block_h1f))
test_node.sync_with_ping()
assert_equal(self.nodes[0].getblockcount(), 2)
self.log.info("Unrequested block that would complete more-work chain was ignored")
# 6. Try to get node to request the missing block.
# Poke the node with an inv for block at height 3 and see if that
# triggers a getdata on block 2 (it should if block 2 is missing).
with mininode_lock:
# Clear state so we can check the getdata request
test_node.last_message.pop("getdata", None)
test_node.send_message(msg_inv([CInv(2, block_h3.sha256)]))
test_node.sync_with_ping()
with mininode_lock:
getdata = test_node.last_message["getdata"]
# Check that the getdata includes the right block
assert_equal(getdata.inv[0].hash, block_h1f.sha256)
self.log.info("Inv at tip triggered getdata for unprocessed block")
# 7. Send the missing block for the third time (now it is requested)
test_node.send_message(msg_block(block_h1f))
test_node.sync_with_ping()
assert_equal(self.nodes[0].getblockcount(), 290)
self.nodes[0].getblock(all_blocks[286].hash)
assert_equal(self.nodes[0].getbestblockhash(), all_blocks[286].hash)
assert_raises_rpc_error(-1, "Block not found on disk", self.nodes[0].getblock, all_blocks[287].hash)
self.log.info("Successfully reorged to longer chain from non-whitelisted peer")
# 8. Create a chain which is invalid at a height longer than the
# current chain, but which has more blocks on top of that
block_289f = create_block(all_blocks[284].sha256, create_coinbase(289), all_blocks[284].nTime+1)
block_289f.solve()
block_290f = create_block(block_289f.sha256, create_coinbase(290), block_289f.nTime+1)
block_290f.solve()
block_291 = create_block(block_290f.sha256, create_coinbase(291), block_290f.nTime+1)
# block_291 spends a coinbase below maturity!
block_291.vtx.append(create_transaction(block_290f.vtx[0], 0, b"42", 1))
block_291.hashMerkleRoot = block_291.calc_merkle_root()
block_291.solve()
block_292 = create_block(block_291.sha256, create_coinbase(292), block_291.nTime+1)
block_292.solve()
# Now send all the headers on the chain and enough blocks to trigger reorg
headers_message = msg_headers()
headers_message.headers.append(CBlockHeader(block_289f))
headers_message.headers.append(CBlockHeader(block_290f))
headers_message.headers.append(CBlockHeader(block_291))
headers_message.headers.append(CBlockHeader(block_292))
test_node.send_message(headers_message)
test_node.sync_with_ping()
tip_entry_found = False
for x in self.nodes[0].getchaintips():
if x['hash'] == block_292.hash:
assert_equal(x['status'], "headers-only")
tip_entry_found = True
assert(tip_entry_found)
assert_raises_rpc_error(-1, "Block not found on disk", self.nodes[0].getblock, block_292.hash)
test_node.send_message(msg_block(block_289f))
test_node.send_message(msg_block(block_290f))
test_node.sync_with_ping()
self.nodes[0].getblock(block_289f.hash)
self.nodes[0].getblock(block_290f.hash)
test_node.send_message(msg_block(block_291))
# At this point we've sent an obviously-bogus block, wait for full processing
# without assuming whether we will be disconnected or not
try:
# Only wait a short while so the test doesn't take forever if we do get
# disconnected
test_node.sync_with_ping(timeout=1)
except AssertionError:
test_node.wait_for_disconnect()
test_node = NodeConnCB() # connects to node (not whitelisted)
connections[0] = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], test_node)
test_node.add_connection(connections[0])
NetworkThread().start() # Start up network handling in another thread
test_node.wait_for_verack()
# We should have failed reorg and switched back to 290 (but have block 291)
assert_equal(self.nodes[0].getblockcount(), 290)
assert_equal(self.nodes[0].getbestblockhash(), all_blocks[286].hash)
assert_equal(self.nodes[0].getblock(block_291.hash)["confirmations"], -1)
# Now send a new header on the invalid chain, indicating we're forked off, and expect to get disconnected
block_293 = create_block(block_292.sha256, create_coinbase(293), block_292.nTime+1)
block_293.solve()
headers_message = msg_headers()
headers_message.headers.append(CBlockHeader(block_293))
test_node.send_message(headers_message)
test_node.wait_for_disconnect()
# 9. Connect node1 to node0 and ensure it is able to sync
connect_nodes(self.nodes[0], 1)
sync_blocks([self.nodes[0], self.nodes[1]])
self.log.info("Successfully synced nodes 1 and 0")
[ c.disconnect_node() for c in connections ]
if __name__ == '__main__':
AcceptBlockTest().main()

169
test/functional/p2p-leaktests.py Executable file
View file

@ -0,0 +1,169 @@
#!/usr/bin/env python3
# Copyright (c) 2017 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test message sending before handshake completion.
A node should never send anything other than VERSION/VERACK/REJECT until it's
received a VERACK.
This test connects to a node and sends it a few messages, trying to intice it
into sending us something it shouldn't.
Also test that nodes that send unsupported service bits to digibyted are disconnected
and don't receive a VERACK. Unsupported service bits are currently 1 << 5 and
1 << 7 (until August 1st 2018)."""
from test_framework.mininode import *
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import *
banscore = 10
class CLazyNode(NodeConnCB):
def __init__(self):
super().__init__()
self.unexpected_msg = False
self.ever_connected = False
def bad_message(self, message):
self.unexpected_msg = True
self.log.info("should not have received message: %s" % message.command)
def on_open(self, conn):
self.connected = True
self.ever_connected = True
def on_version(self, conn, message): self.bad_message(message)
def on_verack(self, conn, message): self.bad_message(message)
def on_reject(self, conn, message): self.bad_message(message)
def on_inv(self, conn, message): self.bad_message(message)
def on_addr(self, conn, message): self.bad_message(message)
def on_alert(self, conn, message): self.bad_message(message)
def on_getdata(self, conn, message): self.bad_message(message)
def on_getblocks(self, conn, message): self.bad_message(message)
def on_tx(self, conn, message): self.bad_message(message)
def on_block(self, conn, message): self.bad_message(message)
def on_getaddr(self, conn, message): self.bad_message(message)
def on_headers(self, conn, message): self.bad_message(message)
def on_getheaders(self, conn, message): self.bad_message(message)
def on_ping(self, conn, message): self.bad_message(message)
def on_mempool(self, conn): self.bad_message(message)
def on_pong(self, conn, message): self.bad_message(message)
def on_feefilter(self, conn, message): self.bad_message(message)
def on_sendheaders(self, conn, message): self.bad_message(message)
def on_sendcmpct(self, conn, message): self.bad_message(message)
def on_cmpctblock(self, conn, message): self.bad_message(message)
def on_getblocktxn(self, conn, message): self.bad_message(message)
def on_blocktxn(self, conn, message): self.bad_message(message)
# Node that never sends a version. We'll use this to send a bunch of messages
# anyway, and eventually get disconnected.
class CNodeNoVersionBan(CLazyNode):
# send a bunch of veracks without sending a message. This should get us disconnected.
# NOTE: implementation-specific check here. Remove if digibyted ban behavior changes
def on_open(self, conn):
super().on_open(conn)
for i in range(banscore):
self.send_message(msg_verack())
def on_reject(self, conn, message): pass
# Node that never sends a version. This one just sits idle and hopes to receive
# any message (it shouldn't!)
class CNodeNoVersionIdle(CLazyNode):
def __init__(self):
super().__init__()
# Node that sends a version but not a verack.
class CNodeNoVerackIdle(CLazyNode):
def __init__(self):
self.version_received = False
super().__init__()
def on_reject(self, conn, message): pass
def on_verack(self, conn, message): pass
# When version is received, don't reply with a verack. Instead, see if the
# node will give us a message that it shouldn't. This is not an exhaustive
# list!
def on_version(self, conn, message):
self.version_received = True
conn.send_message(msg_ping())
conn.send_message(msg_getaddr())
class P2PLeakTest(DigiByteTestFramework):
def set_test_params(self):
self.num_nodes = 1
self.extra_args = [['-banscore='+str(banscore)]]
def run_test(self):
no_version_bannode = CNodeNoVersionBan()
no_version_idlenode = CNodeNoVersionIdle()
no_verack_idlenode = CNodeNoVerackIdle()
unsupported_service_bit5_node = CLazyNode()
unsupported_service_bit7_node = CLazyNode()
self.nodes[0].setmocktime(1501545600) # August 1st 2017
connections = []
connections.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], no_version_bannode, send_version=False))
connections.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], no_version_idlenode, send_version=False))
connections.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], no_verack_idlenode))
connections.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], unsupported_service_bit5_node, services=NODE_NETWORK|NODE_UNSUPPORTED_SERVICE_BIT_5))
connections.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], unsupported_service_bit7_node, services=NODE_NETWORK|NODE_UNSUPPORTED_SERVICE_BIT_7))
no_version_bannode.add_connection(connections[0])
no_version_idlenode.add_connection(connections[1])
no_verack_idlenode.add_connection(connections[2])
unsupported_service_bit5_node.add_connection(connections[3])
unsupported_service_bit7_node.add_connection(connections[4])
NetworkThread().start() # Start up network handling in another thread
wait_until(lambda: no_version_bannode.ever_connected, timeout=10, lock=mininode_lock)
wait_until(lambda: no_version_idlenode.ever_connected, timeout=10, lock=mininode_lock)
wait_until(lambda: no_verack_idlenode.version_received, timeout=10, lock=mininode_lock)
wait_until(lambda: unsupported_service_bit5_node.ever_connected, timeout=10, lock=mininode_lock)
wait_until(lambda: unsupported_service_bit7_node.ever_connected, timeout=10, lock=mininode_lock)
# Mine a block and make sure that it's not sent to the connected nodes
self.nodes[0].generate(1)
#Give the node enough time to possibly leak out a message
time.sleep(5)
#This node should have been banned
assert not no_version_bannode.connected
# These nodes should have been disconnected
assert not unsupported_service_bit5_node.connected
assert not unsupported_service_bit7_node.connected
[conn.disconnect_node() for conn in connections]
# Wait until all connections are closed
wait_until(lambda: len(self.nodes[0].getpeerinfo()) == 0)
# Make sure no unexpected messages came in
assert(no_version_bannode.unexpected_msg == False)
assert(no_version_idlenode.unexpected_msg == False)
assert(no_verack_idlenode.unexpected_msg == False)
assert not unsupported_service_bit5_node.unexpected_msg
assert not unsupported_service_bit7_node.unexpected_msg
self.log.info("Service bits 5 and 7 are allowed after August 1st 2018")
self.nodes[0].setmocktime(1533168000) # August 2nd 2018
allowed_service_bit5_node = NodeConnCB()
allowed_service_bit7_node = NodeConnCB()
connections.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], allowed_service_bit5_node, services=NODE_NETWORK|NODE_UNSUPPORTED_SERVICE_BIT_5))
connections.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], allowed_service_bit7_node, services=NODE_NETWORK|NODE_UNSUPPORTED_SERVICE_BIT_7))
allowed_service_bit5_node.add_connection(connections[5])
allowed_service_bit7_node.add_connection(connections[6])
NetworkThread().start() # Network thread stopped when all previous NodeConnCBs disconnected. Restart it
wait_until(lambda: allowed_service_bit5_node.message_count["verack"], lock=mininode_lock)
wait_until(lambda: allowed_service_bit7_node.message_count["verack"], lock=mininode_lock)
if __name__ == '__main__':
P2PLeakTest().main()

84
test/functional/p2p-timeouts.py Executable file
View file

@ -0,0 +1,84 @@
#!/usr/bin/env python3
# Copyright (c) 2016 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test various net timeouts.
- Create three digibyted nodes:
no_verack_node - we never send a verack in response to their version
no_version_node - we never send a version (only a ping)
no_send_node - we never send any P2P message.
- Start all three nodes
- Wait 1 second
- Assert that we're connected
- Send a ping to no_verack_node and no_version_node
- Wait 30 seconds
- Assert that we're still connected
- Send a ping to no_verack_node and no_version_node
- Wait 31 seconds
- Assert that we're no longer connected (timeout to receive version/verack is 60 seconds)
"""
from time import sleep
from test_framework.mininode import *
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import *
class TestNode(NodeConnCB):
def on_version(self, conn, message):
# Don't send a verack in response
pass
class TimeoutsTest(DigiByteTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
def run_test(self):
# Setup the p2p connections and start up the network thread.
self.no_verack_node = TestNode() # never send verack
self.no_version_node = TestNode() # never send version (just ping)
self.no_send_node = TestNode() # never send anything
connections = []
connections.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], self.no_verack_node))
connections.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], self.no_version_node, send_version=False))
connections.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], self.no_send_node, send_version=False))
self.no_verack_node.add_connection(connections[0])
self.no_version_node.add_connection(connections[1])
self.no_send_node.add_connection(connections[2])
NetworkThread().start() # Start up network handling in another thread
sleep(1)
assert(self.no_verack_node.connected)
assert(self.no_version_node.connected)
assert(self.no_send_node.connected)
ping_msg = msg_ping()
connections[0].send_message(ping_msg)
connections[1].send_message(ping_msg)
sleep(30)
assert "version" in self.no_verack_node.last_message
assert(self.no_verack_node.connected)
assert(self.no_version_node.connected)
assert(self.no_send_node.connected)
connections[0].send_message(ping_msg)
connections[1].send_message(ping_msg)
sleep(31)
assert(not self.no_verack_node.connected)
assert(not self.no_version_node.connected)
assert(not self.no_send_node.connected)
if __name__ == '__main__':
TimeoutsTest().main()

View file

@ -0,0 +1,128 @@
#!/usr/bin/env python3
# Copyright (c) 2016 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test version bits warning system.
Generate chains with block versions that appear to be signalling unknown
soft-forks, and test that warning alerts are generated.
"""
from test_framework.mininode import *
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import *
import re
from test_framework.blocktools import create_block, create_coinbase
VB_PERIOD = 240 # versionbits period length for regtest
VB_THRESHOLD = 168 # versionbits activation threshold for regtest
VB_TOP_BITS = 0x20000000
VB_UNKNOWN_BIT = 27 # Choose a bit unassigned to any deployment
WARN_UNKNOWN_RULES_MINED = "Unknown block versions being mined! It's possible unknown rules are in effect"
WARN_UNKNOWN_RULES_ACTIVE = "unknown new rules activated (versionbit {})".format(VB_UNKNOWN_BIT)
VB_PATTERN = re.compile("^Warning.*versionbit")
class TestNode(NodeConnCB):
def on_inv(self, conn, message):
pass
class VersionBitsWarningTest(DigiByteTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
def setup_network(self):
self.alert_filename = os.path.join(self.options.tmpdir, "alert.txt")
# Open and close to create zero-length file
with open(self.alert_filename, 'w', encoding='utf8') as _:
pass
self.extra_args = [["-alertnotify=echo %s >> \"" + self.alert_filename + "\""]]
self.setup_nodes()
# Send numblocks blocks via peer with nVersionToUse set.
def send_blocks_with_version(self, peer, numblocks, nVersionToUse):
tip = self.nodes[0].getbestblockhash()
height = self.nodes[0].getblockcount()
block_time = self.nodes[0].getblockheader(tip)["time"]+1
tip = int(tip, 16)
for _ in range(numblocks):
block = create_block(tip, create_coinbase(height+1), block_time)
block.nVersion = nVersionToUse
block.solve()
peer.send_message(msg_block(block))
block_time += 1
height += 1
tip = block.sha256
peer.sync_with_ping()
def test_versionbits_in_alert_file(self):
with open(self.alert_filename, 'r', encoding='utf8') as f:
alert_text = f.read()
assert(VB_PATTERN.match(alert_text))
def run_test(self):
# Setup the p2p connection and start up the network thread.
test_node = TestNode()
connections = []
connections.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], test_node))
test_node.add_connection(connections[0])
NetworkThread().start() # Start up network handling in another thread
# Test logic begins here
test_node.wait_for_verack()
# 1. Have the node mine one period worth of blocks
self.nodes[0].generate(VB_PERIOD)
# 2. Now build one period of blocks on the tip, with < VB_THRESHOLD
# blocks signaling some unknown bit.
nVersion = VB_TOP_BITS | (1<<VB_UNKNOWN_BIT)
self.send_blocks_with_version(test_node, VB_THRESHOLD-1, nVersion)
# Fill rest of period with regular version blocks
self.nodes[0].generate(VB_PERIOD - VB_THRESHOLD + 1)
# Check that we're not getting any versionbit-related errors in
# get*info()
assert(not VB_PATTERN.match(self.nodes[0].getinfo()["errors"]))
assert(not VB_PATTERN.match(self.nodes[0].getmininginfo()["errors"]))
assert(not VB_PATTERN.match(self.nodes[0].getnetworkinfo()["warnings"]))
# 3. Now build one period of blocks with >= VB_THRESHOLD blocks signaling
# some unknown bit
self.send_blocks_with_version(test_node, VB_THRESHOLD, nVersion)
self.nodes[0].generate(VB_PERIOD - VB_THRESHOLD)
# Might not get a versionbits-related alert yet, as we should
# have gotten a different alert due to more than 51/100 blocks
# being of unexpected version.
# Check that get*info() shows some kind of error.
assert(WARN_UNKNOWN_RULES_MINED in self.nodes[0].getinfo()["errors"])
assert(WARN_UNKNOWN_RULES_MINED in self.nodes[0].getmininginfo()["errors"])
assert(WARN_UNKNOWN_RULES_MINED in self.nodes[0].getnetworkinfo()["warnings"])
# Mine a period worth of expected blocks so the generic block-version warning
# is cleared, and restart the node. This should move the versionbit state
# to ACTIVE.
self.nodes[0].generate(VB_PERIOD)
self.stop_nodes()
# Empty out the alert file
with open(self.alert_filename, 'w', encoding='utf8') as _:
pass
self.start_nodes()
# Connecting one block should be enough to generate an error.
self.nodes[0].generate(1)
assert(WARN_UNKNOWN_RULES_ACTIVE in self.nodes[0].getinfo()["errors"])
assert(WARN_UNKNOWN_RULES_ACTIVE in self.nodes[0].getmininginfo()["errors"])
assert(WARN_UNKNOWN_RULES_ACTIVE in self.nodes[0].getnetworkinfo()["warnings"])
self.stop_nodes()
self.test_versionbits_in_alert_file()
# Test framework expects the node to still be running...
self.start_nodes()
if __name__ == '__main__':
VersionBitsWarningTest().main()

View file

@ -0,0 +1,96 @@
#!/usr/bin/env python3
# Copyright (c) 2020 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test add_outbound_p2p_connection test framework functionality"""
from test_framework.p2p import P2PInterface
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import assert_equal
def check_node_connections(*, node, num_in, num_out):
info = node.getnetworkinfo()
assert_equal(info["connections_in"], num_in)
assert_equal(info["connections_out"], num_out)
class P2PAddConnections(DigiByteTestFramework):
def set_test_params(self):
self.num_nodes = 2
def setup_network(self):
self.setup_nodes()
# Don't connect the nodes
def run_test(self):
self.log.info("Add 8 outbounds to node 0")
for i in range(8):
self.log.info(f"outbound: {i}")
self.nodes[0].add_outbound_p2p_connection(P2PInterface(), p2p_idx=i, connection_type="outbound-full-relay")
self.log.info("Add 2 block-relay-only connections to node 0")
for i in range(2):
self.log.info(f"block-relay-only: {i}")
# set p2p_idx based on the outbound connections already open to the
# node, so add 8 to account for the previous full-relay connections
self.nodes[0].add_outbound_p2p_connection(P2PInterface(), p2p_idx=i + 8, connection_type="block-relay-only")
self.log.info("Add 2 block-relay-only connections to node 1")
for i in range(2):
self.log.info(f"block-relay-only: {i}")
self.nodes[1].add_outbound_p2p_connection(P2PInterface(), p2p_idx=i, connection_type="block-relay-only")
self.log.info("Add 5 inbound connections to node 1")
for i in range(5):
self.log.info(f"inbound: {i}")
self.nodes[1].add_p2p_connection(P2PInterface())
self.log.info("Add 8 outbounds to node 1")
for i in range(8):
self.log.info(f"outbound: {i}")
# bump p2p_idx to account for the 2 existing outbounds on node 1
self.nodes[1].add_outbound_p2p_connection(P2PInterface(), p2p_idx=i + 2)
self.log.info("Check the connections opened as expected")
check_node_connections(node=self.nodes[0], num_in=0, num_out=10)
check_node_connections(node=self.nodes[1], num_in=5, num_out=10)
self.log.info("Disconnect p2p connections & try to re-open")
self.nodes[0].disconnect_p2ps()
check_node_connections(node=self.nodes[0], num_in=0, num_out=0)
self.log.info("Add 8 outbounds to node 0")
for i in range(8):
self.log.info(f"outbound: {i}")
self.nodes[0].add_outbound_p2p_connection(P2PInterface(), p2p_idx=i)
check_node_connections(node=self.nodes[0], num_in=0, num_out=8)
self.log.info("Add 2 block-relay-only connections to node 0")
for i in range(2):
self.log.info(f"block-relay-only: {i}")
# bump p2p_idx to account for the 8 existing outbounds on node 0
self.nodes[0].add_outbound_p2p_connection(P2PInterface(), p2p_idx=i + 8, connection_type="block-relay-only")
check_node_connections(node=self.nodes[0], num_in=0, num_out=10)
self.log.info("Restart node 0 and try to reconnect to p2ps")
self.restart_node(0)
self.log.info("Add 4 outbounds to node 0")
for i in range(4):
self.log.info(f"outbound: {i}")
self.nodes[0].add_outbound_p2p_connection(P2PInterface(), p2p_idx=i)
check_node_connections(node=self.nodes[0], num_in=0, num_out=4)
self.log.info("Add 2 block-relay-only connections to node 0")
for i in range(2):
self.log.info(f"block-relay-only: {i}")
# bump p2p_idx to account for the 4 existing outbounds on node 0
self.nodes[0].add_outbound_p2p_connection(P2PInterface(), p2p_idx=i + 4, connection_type="block-relay-only")
check_node_connections(node=self.nodes[0], num_in=0, num_out=6)
check_node_connections(node=self.nodes[1], num_in=5, num_out=10)
if __name__ == '__main__':
P2PAddConnections().main()

301
test/functional/p2p_addr_relay.py Executable file
View file

@ -0,0 +1,301 @@
#!/usr/bin/env python3
# Copyright (c) 2020 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""
Test addr relay
"""
from test_framework.messages import (
CAddress,
NODE_NETWORK,
NODE_WITNESS,
msg_addr,
msg_getaddr
)
from test_framework.p2p import (
P2PInterface,
p2p_lock,
)
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import assert_equal
import random
import time
class AddrReceiver(P2PInterface):
num_ipv4_received = 0
test_addr_contents = False
_tokens = 1
def __init__(self, test_addr_contents=False):
super().__init__()
self.test_addr_contents = test_addr_contents
def on_addr(self, message):
for addr in message.addrs:
self.num_ipv4_received += 1
if(self.test_addr_contents):
# relay_tests checks the content of the addr messages match
# expectations based on the message creation in setup_addr_msg
assert_equal(addr.nServices, 9)
if not 12024 <= addr.port < 12343:
raise AssertionError("Invalid addr.port of {} (12024-12342 expected)".format(addr.port))
assert addr.ip.startswith('123.123.123.')
def on_getaddr(self, message):
# When the node sends us a getaddr, it increments the addr relay tokens for the connection by 1000
self._tokens += 1000
@property
def tokens(self):
with p2p_lock:
return self._tokens
def increment_tokens(self, n):
# When we move mocktime forward, the node increments the addr relay tokens for its peers
with p2p_lock:
self._tokens += n
def addr_received(self):
return self.num_ipv4_received != 0
def getaddr_received(self):
return self.message_count['getaddr'] > 0
class AddrTest(DigiByteTestFramework):
counter = 0
mocktime = int(time.time())
def set_test_params(self):
self.num_nodes = 1
self.extra_args = [["-whitelist=addr@127.0.0.1", "-easypow"]]
def run_test(self):
self.oversized_addr_test()
self.relay_tests()
self.getaddr_tests()
self.blocksonly_mode_tests()
self.rate_limit_tests()
def setup_addr_msg(self, num):
addrs = []
for i in range(num):
addr = CAddress()
addr.time = self.mocktime + i
addr.nServices = NODE_NETWORK | NODE_WITNESS
addr.ip = f"123.123.123.{self.counter % 256}"
addr.port = 12024 + i
addrs.append(addr)
self.counter += 1
msg = msg_addr()
msg.addrs = addrs
return msg
def setup_rand_addr_msg(self, num):
addrs = []
for i in range(num):
addr = CAddress()
addr.time = self.mocktime + i
addr.nServices = NODE_NETWORK | NODE_WITNESS
addr.ip = f"{random.randrange(128,169)}.{random.randrange(1,255)}.{random.randrange(1,255)}.{random.randrange(1,255)}"
addr.port = 12024
addrs.append(addr)
msg = msg_addr()
msg.addrs = addrs
return msg
def send_addr_msg(self, source, msg, receivers):
source.send_and_ping(msg)
# pop m_next_addr_send timer
self.mocktime += 2 * 60 # CHAIN_SYNC_TIMEOUT / 2
self.nodes[0].setmocktime(self.mocktime)
for peer in receivers:
peer.sync_send_with_ping()
def oversized_addr_test(self):
self.log.info('Send an addr message that is too large')
addr_source = self.nodes[0].add_p2p_connection(P2PInterface())
msg = self.setup_addr_msg(1010)
with self.nodes[0].assert_debug_log(['addr message size = 1010']):
addr_source.send_and_ping(msg)
self.nodes[0].disconnect_p2ps()
def relay_tests(self):
self.log.info('Test address relay')
self.log.info('Check that addr message content is relayed and added to addrman')
addr_source = self.nodes[0].add_p2p_connection(P2PInterface())
num_receivers = 7
receivers = []
for _ in range(num_receivers):
receivers.append(self.nodes[0].add_p2p_connection(AddrReceiver(test_addr_contents=True)))
# Keep this with length <= 10. Addresses from larger messages are not
# relayed.
num_ipv4_addrs = 10
msg = self.setup_addr_msg(num_ipv4_addrs)
with self.nodes[0].assert_debug_log(
[
'Added {} addresses from 127.0.0.1: 0 tried'.format(num_ipv4_addrs),
'received: addr (301 bytes) peer=1',
]
):
self.send_addr_msg(addr_source, msg, receivers)
total_ipv4_received = sum(r.num_ipv4_received for r in receivers)
# Every IPv4 address must be relayed to two peers, other than the
# originating node (addr_source).
ipv4_branching_factor = 2
assert_equal(total_ipv4_received, num_ipv4_addrs * ipv4_branching_factor)
self.nodes[0].disconnect_p2ps()
self.log.info('Check relay of addresses received from outbound peers')
inbound_peer = self.nodes[0].add_p2p_connection(AddrReceiver(test_addr_contents=True))
full_outbound_peer = self.nodes[0].add_outbound_p2p_connection(AddrReceiver(), p2p_idx=0, connection_type="outbound-full-relay")
msg = self.setup_addr_msg(2)
self.send_addr_msg(full_outbound_peer, msg, [inbound_peer])
self.log.info('Check that the first addr message received from an outbound peer is not relayed')
# Currently, there is a flag that prevents the first addr message received
# from a new outbound peer to be relayed to others. Originally meant to prevent
# large GETADDR responses from being relayed, it now typically affects the self-announcement
# of the outbound peer which is often sent before the GETADDR response.
assert_equal(inbound_peer.num_ipv4_received, 0)
self.log.info('Check that subsequent addr messages sent from an outbound peer are relayed')
msg2 = self.setup_addr_msg(2)
self.send_addr_msg(full_outbound_peer, msg2, [inbound_peer])
assert_equal(inbound_peer.num_ipv4_received, 2)
self.log.info('Check address relay to outbound peers')
block_relay_peer = self.nodes[0].add_outbound_p2p_connection(AddrReceiver(), p2p_idx=1, connection_type="block-relay-only")
msg3 = self.setup_addr_msg(2)
self.send_addr_msg(inbound_peer, msg3, [full_outbound_peer, block_relay_peer])
self.log.info('Check that addresses are relayed to full outbound peers')
assert_equal(full_outbound_peer.num_ipv4_received, 2)
self.log.info('Check that addresses are not relayed to block-relay-only outbound peers')
assert_equal(block_relay_peer.num_ipv4_received, 0)
self.nodes[0].disconnect_p2ps()
def getaddr_tests(self):
self.log.info('Test getaddr behavior')
self.log.info('Check that we send a getaddr message upon connecting to an outbound-full-relay peer')
full_outbound_peer = self.nodes[0].add_outbound_p2p_connection(AddrReceiver(), p2p_idx=0, connection_type="outbound-full-relay")
full_outbound_peer.sync_with_ping()
assert full_outbound_peer.getaddr_received()
self.log.info('Check that we do not send a getaddr message upon connecting to a block-relay-only peer')
block_relay_peer = self.nodes[0].add_outbound_p2p_connection(AddrReceiver(), p2p_idx=1, connection_type="block-relay-only")
block_relay_peer.sync_with_ping()
assert_equal(block_relay_peer.getaddr_received(), False)
self.log.info('Check that we answer getaddr messages only from inbound peers')
inbound_peer = self.nodes[0].add_p2p_connection(AddrReceiver())
inbound_peer.sync_with_ping()
# Add some addresses to addrman
for i in range(1000):
first_octet = i >> 8
second_octet = i % 256
a = f"{first_octet}.{second_octet}.1.1"
self.nodes[0].addpeeraddress(a, 12024)
full_outbound_peer.send_and_ping(msg_getaddr())
block_relay_peer.send_and_ping(msg_getaddr())
inbound_peer.send_and_ping(msg_getaddr())
self.mocktime += 5 * 60
self.nodes[0].setmocktime(self.mocktime)
inbound_peer.wait_until(lambda: inbound_peer.addr_received() is True)
assert_equal(full_outbound_peer.num_ipv4_received, 0)
assert_equal(block_relay_peer.num_ipv4_received, 0)
assert inbound_peer.num_ipv4_received > 100
self.nodes[0].disconnect_p2ps()
def blocksonly_mode_tests(self):
self.log.info('Test addr relay in -blocksonly mode')
self.restart_node(0, ["-blocksonly", "-whitelist=addr@127.0.0.1"])
self.mocktime = int(time.time())
self.log.info('Check that we send getaddr messages')
full_outbound_peer = self.nodes[0].add_outbound_p2p_connection(AddrReceiver(), p2p_idx=0, connection_type="outbound-full-relay")
full_outbound_peer.sync_with_ping()
assert full_outbound_peer.getaddr_received()
self.log.info('Check that we relay address messages')
addr_source = self.nodes[0].add_p2p_connection(P2PInterface())
msg = self.setup_addr_msg(2)
self.send_addr_msg(addr_source, msg, [full_outbound_peer])
assert_equal(full_outbound_peer.num_ipv4_received, 2)
self.nodes[0].disconnect_p2ps()
def send_addrs_and_test_rate_limiting(self, peer, no_relay, new_addrs, total_addrs):
"""Send an addr message and check that the number of addresses processed and rate-limited is as expected"""
peer.send_and_ping(self.setup_rand_addr_msg(new_addrs))
peerinfo = self.nodes[0].getpeerinfo()[0]
addrs_processed = peerinfo['addr_processed']
addrs_rate_limited = peerinfo['addr_rate_limited']
self.log.debug(f"addrs_processed = {addrs_processed}, addrs_rate_limited = {addrs_rate_limited}")
if no_relay:
assert_equal(addrs_processed, 0)
assert_equal(addrs_rate_limited, 0)
else:
assert_equal(addrs_processed, min(total_addrs, peer.tokens))
assert_equal(addrs_rate_limited, max(0, total_addrs - peer.tokens))
def rate_limit_tests(self):
self.mocktime = int(time.time())
self.restart_node(0, [])
self.nodes[0].setmocktime(self.mocktime)
for contype, no_relay in [("outbound-full-relay", False), ("block-relay-only", True), ("inbound", False)]:
self.log.info(f'Test rate limiting of addr processing for {contype} peers')
if contype == "inbound":
peer = self.nodes[0].add_p2p_connection(AddrReceiver())
else:
peer = self.nodes[0].add_outbound_p2p_connection(AddrReceiver(), p2p_idx=0, connection_type=contype)
# Send 600 addresses. For all but the block-relay-only peer this should result in addresses being processed.
self.send_addrs_and_test_rate_limiting(peer, no_relay, 600, 600)
# Send 600 more addresses. For the outbound-full-relay peer (which we send a GETADDR, and thus will
# process up to 1001 incoming addresses), this means more addresses will be processed.
self.send_addrs_and_test_rate_limiting(peer, no_relay, 600, 1200)
# Send 10 more. As we reached the processing limit for all nodes, no more addresses should be procesesd.
self.send_addrs_and_test_rate_limiting(peer, no_relay, 10, 1210)
# Advance the time by 100 seconds, permitting the processing of 10 more addresses.
# Send 200 and verify that 10 are processed.
self.mocktime += 100
self.nodes[0].setmocktime(self.mocktime)
peer.increment_tokens(10)
self.send_addrs_and_test_rate_limiting(peer, no_relay, 200, 1410)
# Advance the time by 1000 seconds, permitting the processing of 100 more addresses.
# Send 200 and verify that 100 are processed.
self.mocktime += 1000
self.nodes[0].setmocktime(self.mocktime)
peer.increment_tokens(100)
self.send_addrs_and_test_rate_limiting(peer, no_relay, 200, 1610)
self.nodes[0].disconnect_p2ps()
if __name__ == '__main__':
AddrTest().main()

View file

@ -0,0 +1,86 @@
#!/usr/bin/env python3
# Copyright (c) 2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""
Test p2p addr-fetch connections
"""
import time
from test_framework.messages import (
CAddress,
msg_addr,
)
from test_framework.p2p import (
P2PInterface,
p2p_lock,
P2P_SERVICES,
)
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import assert_equal
ADDR = CAddress()
ADDR.time = int(time.time())
ADDR.nServices = P2P_SERVICES
ADDR.ip = "192.0.0.8"
ADDR.port = 18444
class P2PAddrFetch(DigiByteTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
def assert_getpeerinfo(self, *, peer_ids):
num_peers = len(peer_ids)
info = self.nodes[0].getpeerinfo()
assert_equal(len(info), num_peers)
for n in range(0, num_peers):
assert_equal(info[n]['id'], peer_ids[n])
assert_equal(info[n]['connection_type'], 'addr-fetch')
def run_test(self):
node = self.nodes[0]
self.log.info("Connect to an addr-fetch peer")
peer_id = 0
peer = node.add_outbound_p2p_connection(P2PInterface(), p2p_idx=peer_id, connection_type="addr-fetch")
self.assert_getpeerinfo(peer_ids=[peer_id])
self.log.info("Check that we send getaddr but don't try to sync headers with the addr-fetch peer")
peer.sync_send_with_ping()
with p2p_lock:
assert peer.message_count['getaddr'] == 1
assert peer.message_count['getheaders'] == 0
self.log.info("Check that answering the getaddr with a single address does not lead to disconnect")
# This prevents disconnecting on self-announcements
msg = msg_addr()
msg.addrs = [ADDR]
peer.send_and_ping(msg)
self.assert_getpeerinfo(peer_ids=[peer_id])
self.log.info("Check that answering with larger addr messages leads to disconnect")
msg.addrs = [ADDR] * 2
peer.send_message(msg)
peer.wait_for_disconnect(timeout=5)
self.log.info("Check timeout for addr-fetch peer that does not send addrs")
peer_id = 1
peer = node.add_outbound_p2p_connection(P2PInterface(), p2p_idx=peer_id, connection_type="addr-fetch")
time_now = int(time.time())
self.assert_getpeerinfo(peer_ids=[peer_id])
# Expect addr-fetch peer connection to be maintained up to 5 minutes.
node.setmocktime(time_now + 295)
self.assert_getpeerinfo(peer_ids=[peer_id])
# Expect addr-fetch peer connection to be disconnected after 5 minutes.
node.setmocktime(time_now + 301)
peer.wait_for_disconnect(timeout=5)
self.assert_getpeerinfo(peer_ids=[])
if __name__ == '__main__':
P2PAddrFetch().main()

View file

@ -0,0 +1,87 @@
#!/usr/bin/env python3
# Copyright (c) 2020-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""
Test addrv2 relay
"""
import time
from test_framework.messages import (
CAddress,
msg_addrv2,
)
from test_framework.p2p import (
P2PInterface,
P2P_SERVICES,
)
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import assert_equal
I2P_ADDR = "c4gfnttsuwqomiygupdqqqyy5y5emnk5c73hrfvatri67prd7vyq.b32.i2p"
ADDRS = []
for i in range(10):
addr = CAddress()
addr.time = int(time.time()) + i
addr.nServices = P2P_SERVICES
# Add one I2P address at an arbitrary position.
if i == 5:
addr.net = addr.NET_I2P
addr.ip = I2P_ADDR
else:
addr.ip = f"123.123.123.{i % 256}"
addr.port = 8333 + i
ADDRS.append(addr)
class AddrReceiver(P2PInterface):
addrv2_received_and_checked = False
def __init__(self):
super().__init__(support_addrv2 = True)
def on_addrv2(self, message):
expected_set = set((addr.ip, addr.port) for addr in ADDRS)
received_set = set((addr.ip, addr.port) for addr in message.addrs)
if expected_set == received_set:
self.addrv2_received_and_checked = True
def wait_for_addrv2(self):
self.wait_until(lambda: "addrv2" in self.last_message)
class AddrTest(DigiByteTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
self.extra_args = [["-whitelist=addr@127.0.0.1"]]
def run_test(self):
self.log.info('Create connection that sends addrv2 messages')
addr_source = self.nodes[0].add_p2p_connection(P2PInterface())
msg = msg_addrv2()
self.log.info('Send too-large addrv2 message')
msg.addrs = ADDRS * 101
with self.nodes[0].assert_debug_log(['addrv2 message size = 1010']):
addr_source.send_and_ping(msg)
self.log.info('Check that addrv2 message content is relayed and added to addrman')
addr_receiver = self.nodes[0].add_p2p_connection(AddrReceiver())
msg.addrs = ADDRS
with self.nodes[0].assert_debug_log([
'received: addrv2 (159 bytes) peer=0',
'sending addrv2 (159 bytes) peer=1',
]):
addr_source.send_and_ping(msg)
self.nodes[0].setmocktime(int(time.time()) + 30 * 60)
addr_receiver.wait_for_addrv2()
assert addr_receiver.addrv2_received_and_checked
assert_equal(len(self.nodes[0].getnodeaddresses(count=0, network="i2p")), 0)
if __name__ == '__main__':
AddrTest().main()

View file

@ -0,0 +1,257 @@
#!/usr/bin/env python3
# Copyright (c) 2019-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Tests NODE_COMPACT_FILTERS (BIP 157/158).
Tests that a node configured with -blockfilterindex and -peerblockfilters signals
NODE_COMPACT_FILTERS and can serve cfilters, cfheaders and cfcheckpts.
"""
from test_framework.messages import (
FILTER_TYPE_BASIC,
NODE_COMPACT_FILTERS,
hash256,
msg_getcfcheckpt,
msg_getcfheaders,
msg_getcfilters,
ser_uint256,
uint256_from_str,
)
from test_framework.p2p import P2PInterface
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import (
assert_equal,
)
class FiltersClient(P2PInterface):
def __init__(self):
super().__init__()
# Store the cfilters received.
self.cfilters = []
def pop_cfilters(self):
cfilters = self.cfilters
self.cfilters = []
return cfilters
def on_cfilter(self, message):
"""Store cfilters received in a list."""
self.cfilters.append(message)
class CompactFiltersTest(DigiByteTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.rpc_timeout = 480
self.num_nodes = 2
self.extra_args = [
["-blockfilterindex", "-peerblockfilters"],
["-blockfilterindex"],
]
def run_test(self):
# Node 0 supports COMPACT_FILTERS, node 1 does not.
peer_0 = self.nodes[0].add_p2p_connection(FiltersClient())
peer_1 = self.nodes[1].add_p2p_connection(FiltersClient())
# Nodes 0 & 1 share the same first 999 blocks in the chain.
self.generate(self.nodes[0], 999)
# Stale blocks by disconnecting nodes 0 & 1, mining, then reconnecting
self.disconnect_nodes(0, 1)
stale_block_hash = self.generate(self.nodes[0], 1, sync_fun=self.no_op)[0]
self.nodes[0].syncwithvalidationinterfacequeue()
assert_equal(self.nodes[0].getblockcount(), 1000)
self.generate(self.nodes[1], 1001, sync_fun=self.no_op)
assert_equal(self.nodes[1].getblockcount(), 2000)
# Check that nodes have signalled NODE_COMPACT_FILTERS correctly.
assert peer_0.nServices & NODE_COMPACT_FILTERS != 0
assert peer_1.nServices & NODE_COMPACT_FILTERS == 0
# Check that the localservices is as expected.
assert int(self.nodes[0].getnetworkinfo()['localservices'], 16) & NODE_COMPACT_FILTERS != 0
assert int(self.nodes[1].getnetworkinfo()['localservices'], 16) & NODE_COMPACT_FILTERS == 0
self.log.info("get cfcheckpt on chain to be re-orged out.")
request = msg_getcfcheckpt(
filter_type=FILTER_TYPE_BASIC,
stop_hash=int(stale_block_hash, 16),
)
peer_0.send_and_ping(message=request)
response = peer_0.last_message['cfcheckpt']
assert_equal(response.filter_type, request.filter_type)
assert_equal(response.stop_hash, request.stop_hash)
assert_equal(len(response.headers), 1)
self.log.info("Reorg node 0 to a new chain.")
self.connect_nodes(0, 1)
self.sync_blocks(timeout=600)
self.nodes[0].syncwithvalidationinterfacequeue()
main_block_hash = self.nodes[0].getblockhash(1000)
assert main_block_hash != stale_block_hash, "node 0 chain did not reorganize"
self.log.info("Check that peers can fetch cfcheckpt on active chain.")
tip_hash = self.nodes[0].getbestblockhash()
request = msg_getcfcheckpt(
filter_type=FILTER_TYPE_BASIC,
stop_hash=int(tip_hash, 16),
)
peer_0.send_and_ping(request)
response = peer_0.last_message['cfcheckpt']
assert_equal(response.filter_type, request.filter_type)
assert_equal(response.stop_hash, request.stop_hash)
main_cfcheckpt = self.nodes[0].getblockfilter(main_block_hash, 'basic')['header']
tip_cfcheckpt = self.nodes[0].getblockfilter(tip_hash, 'basic')['header']
assert_equal(
response.headers,
[int(header, 16) for header in (main_cfcheckpt, tip_cfcheckpt)],
)
self.log.info("Check that peers can fetch cfcheckpt on stale chain.")
request = msg_getcfcheckpt(
filter_type=FILTER_TYPE_BASIC,
stop_hash=int(stale_block_hash, 16),
)
peer_0.send_and_ping(request)
response = peer_0.last_message['cfcheckpt']
stale_cfcheckpt = self.nodes[0].getblockfilter(stale_block_hash, 'basic')['header']
assert_equal(
response.headers,
[int(header, 16) for header in (stale_cfcheckpt, )],
)
self.log.info("Check that peers can fetch cfheaders on active chain.")
request = msg_getcfheaders(
filter_type=FILTER_TYPE_BASIC,
start_height=1,
stop_hash=int(main_block_hash, 16),
)
peer_0.send_and_ping(request)
response = peer_0.last_message['cfheaders']
main_cfhashes = response.hashes
assert_equal(len(main_cfhashes), 1000)
assert_equal(
compute_last_header(response.prev_header, response.hashes),
int(main_cfcheckpt, 16),
)
self.log.info("Check that peers can fetch cfheaders on stale chain.")
request = msg_getcfheaders(
filter_type=FILTER_TYPE_BASIC,
start_height=1,
stop_hash=int(stale_block_hash, 16),
)
peer_0.send_and_ping(request)
response = peer_0.last_message['cfheaders']
stale_cfhashes = response.hashes
assert_equal(len(stale_cfhashes), 1000)
assert_equal(
compute_last_header(response.prev_header, response.hashes),
int(stale_cfcheckpt, 16),
)
self.log.info("Check that peers can fetch cfilters.")
stop_hash = self.nodes[0].getblockhash(10)
request = msg_getcfilters(
filter_type=FILTER_TYPE_BASIC,
start_height=1,
stop_hash=int(stop_hash, 16),
)
peer_0.send_and_ping(request)
response = peer_0.pop_cfilters()
assert_equal(len(response), 10)
self.log.info("Check that cfilter responses are correct.")
for cfilter, cfhash, height in zip(response, main_cfhashes, range(1, 11)):
block_hash = self.nodes[0].getblockhash(height)
assert_equal(cfilter.filter_type, FILTER_TYPE_BASIC)
assert_equal(cfilter.block_hash, int(block_hash, 16))
computed_cfhash = uint256_from_str(hash256(cfilter.filter_data))
assert_equal(computed_cfhash, cfhash)
self.log.info("Check that peers can fetch cfilters for stale blocks.")
request = msg_getcfilters(
filter_type=FILTER_TYPE_BASIC,
start_height=1000,
stop_hash=int(stale_block_hash, 16),
)
peer_0.send_and_ping(request)
response = peer_0.pop_cfilters()
assert_equal(len(response), 1)
cfilter = response[0]
assert_equal(cfilter.filter_type, FILTER_TYPE_BASIC)
assert_equal(cfilter.block_hash, int(stale_block_hash, 16))
computed_cfhash = uint256_from_str(hash256(cfilter.filter_data))
assert_equal(computed_cfhash, stale_cfhashes[999])
self.log.info("Requests to node 1 without NODE_COMPACT_FILTERS results in disconnection.")
requests = [
msg_getcfcheckpt(
filter_type=FILTER_TYPE_BASIC,
stop_hash=int(main_block_hash, 16),
),
msg_getcfheaders(
filter_type=FILTER_TYPE_BASIC,
start_height=1000,
stop_hash=int(main_block_hash, 16),
),
msg_getcfilters(
filter_type=FILTER_TYPE_BASIC,
start_height=1000,
stop_hash=int(main_block_hash, 16),
),
]
for request in requests:
peer_1 = self.nodes[1].add_p2p_connection(P2PInterface())
peer_1.send_message(request)
peer_1.wait_for_disconnect()
self.log.info("Check that invalid requests result in disconnection.")
requests = [
# Requesting too many filters results in disconnection.
msg_getcfilters(
filter_type=FILTER_TYPE_BASIC,
start_height=0,
stop_hash=int(main_block_hash, 16),
),
# Requesting too many filter headers results in disconnection.
msg_getcfheaders(
filter_type=FILTER_TYPE_BASIC,
start_height=0,
stop_hash=int(tip_hash, 16),
),
# Requesting unknown filter type results in disconnection.
msg_getcfcheckpt(
filter_type=255,
stop_hash=int(main_block_hash, 16),
),
# Requesting unknown hash results in disconnection.
msg_getcfcheckpt(
filter_type=FILTER_TYPE_BASIC,
stop_hash=123456789,
),
]
for request in requests:
peer_0 = self.nodes[0].add_p2p_connection(P2PInterface())
peer_0.send_message(request)
peer_0.wait_for_disconnect()
def compute_last_header(prev_header, hashes):
"""Compute the last filter header from a starting header and a sequence of filter hashes."""
header = ser_uint256(prev_header)
for filter_hash in hashes:
header = hash256(ser_uint256(filter_hash) + header)
return uint256_from_str(header)
if __name__ == '__main__':
CompactFiltersTest().main()

120
test/functional/p2p_blocksonly.py Executable file
View file

@ -0,0 +1,120 @@
#!/usr/bin/env python3
# Copyright (c) 2021-2022 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test p2p blocksonly mode & block-relay-only connections."""
import time
from test_framework.blocktools import COINBASE_MATURITY
from test_framework.messages import msg_tx
from test_framework.p2p import P2PInterface, P2PTxInvStore, P2P_VERSION
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import assert_equal
from test_framework.wallet import MiniWallet
class P2PBlocksOnly(DigiByteTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
self.extra_args = [["-blocksonly"]]
def run_test(self):
self.miniwallet = MiniWallet(self.nodes[0])
# Add enough mature utxos to the wallet, so that all txs spend confirmed coins
self.generate(self.miniwallet, 2)
self.generate(self.nodes[0], COINBASE_MATURITY)
self.blocksonly_mode_tests()
self.blocks_relay_conn_tests()
def blocksonly_mode_tests(self):
self.log.info("Tests with node running in -blocksonly mode")
assert_equal(self.nodes[0].getnetworkinfo()['localrelay'], False)
self.nodes[0].add_p2p_connection(P2PInterface())
tx, txid, wtxid, tx_hex = self.check_p2p_tx_violation()
self.log.info('Check that txs from rpc are not rejected and relayed to other peers')
tx_relay_peer = self.nodes[0].add_p2p_connection(P2PInterface())
assert_equal(self.nodes[0].getpeerinfo()[0]['relaytxes'], True)
assert_equal(self.nodes[0].testmempoolaccept([tx_hex])[0]['allowed'], True)
expected_message = 'received getdata for: tx {} peer=1'.format(txid)
if P2P_VERSION >= 70018:
expected_message = 'received getdata for: wtx {} peer=1'.format(wtxid)
with self.nodes[0].assert_debug_log([expected_message]):
self.nodes[0].sendrawtransaction(tx_hex)
tx_relay_peer.wait_for_tx(txid)
assert_equal(self.nodes[0].getmempoolinfo()['size'], 1)
self.log.info("Restarting node 0 with relay permission and blocksonly")
self.restart_node(0, ["-persistmempool=0", "-whitelist=relay@127.0.0.1", "-blocksonly"])
assert_equal(self.nodes[0].getrawmempool(), [])
first_peer = self.nodes[0].add_p2p_connection(P2PInterface())
second_peer = self.nodes[0].add_p2p_connection(P2PInterface())
peer_1_info = self.nodes[0].getpeerinfo()[0]
assert_equal(peer_1_info['permissions'], ['relay'])
peer_2_info = self.nodes[0].getpeerinfo()[1]
assert_equal(peer_2_info['permissions'], ['relay'])
assert_equal(self.nodes[0].testmempoolaccept([tx_hex])[0]['allowed'], True)
self.log.info('Check that the tx from first_peer with relay-permission is relayed to others (ie.second_peer)')
with self.nodes[0].assert_debug_log(["received getdata"]):
# Note that normally, first_peer would never send us transactions since we're a blocksonly node.
# By activating blocksonly, we explicitly tell our peers that they should not send us transactions,
# and Bitcoin Core respects that choice and will not send transactions.
# But if, for some reason, first_peer decides to relay transactions to us anyway, we should relay them to
# second_peer since we gave relay permission to first_peer.
# See https://github.com/bitcoin/bitcoin/issues/19943 for details.
first_peer.send_message(msg_tx(tx))
self.log.info('Check that the peer with relay-permission is still connected after sending the transaction')
assert_equal(first_peer.is_connected, True)
second_peer.wait_for_tx(txid)
assert_equal(self.nodes[0].getmempoolinfo()['size'], 1)
self.log.info("Relay-permission peer's transaction is accepted and relayed")
self.nodes[0].disconnect_p2ps()
self.generate(self.nodes[0], 1)
def blocks_relay_conn_tests(self):
self.log.info('Tests with node in normal mode with block-relay-only connections')
self.restart_node(0, ["-noblocksonly"]) # disables blocks only mode
assert_equal(self.nodes[0].getnetworkinfo()['localrelay'], True)
# Ensure we disconnect if a block-relay-only connection sends us a transaction
self.nodes[0].add_outbound_p2p_connection(P2PInterface(), p2p_idx=0, connection_type="block-relay-only")
assert_equal(self.nodes[0].getpeerinfo()[0]['relaytxes'], False)
_, txid, _, tx_hex = self.check_p2p_tx_violation(index=2)
self.log.info("Check that txs from RPC are not sent to blockrelay connection")
conn = self.nodes[0].add_outbound_p2p_connection(P2PTxInvStore(), p2p_idx=1, connection_type="block-relay-only")
self.nodes[0].sendrawtransaction(tx_hex)
# Bump time forward to ensure nNextInvSend timer pops
self.nodes[0].setmocktime(int(time.time()) + 60)
conn.sync_send_with_ping()
assert(int(txid, 16) not in conn.get_invs())
def check_p2p_tx_violation(self, index=1):
self.log.info('Check that txs from P2P are rejected and result in disconnect')
input_txid = self.nodes[0].getblock(self.nodes[0].getblockhash(index), 2)['tx'][0]['txid']
utxo_to_spend = self.miniwallet.get_utxo(txid=input_txid)
spendtx = self.miniwallet.create_self_transfer(from_node=self.nodes[0], utxo_to_spend=utxo_to_spend)
with self.nodes[0].assert_debug_log(['transaction sent in violation of protocol peer=0']):
self.nodes[0].p2ps[0].send_message(msg_tx(spendtx['tx']))
self.nodes[0].p2ps[0].wait_for_disconnect()
assert_equal(self.nodes[0].getmempoolinfo()['size'], 0)
# Remove the disconnected peer
del self.nodes[0].p2ps[0]
return spendtx['tx'], spendtx['txid'], spendtx['wtxid'], spendtx['hex']
if __name__ == '__main__':
P2PBlocksOnly().main()

View file

@ -0,0 +1,908 @@
#!/usr/bin/env python3
# Copyright (c) 2021-2022 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test compact blocks (BIP 152).
Version 1 compact blocks are pre-segwit (txids)
Version 2 compact blocks are post-segwit (wtxids)
"""
import random
from test_framework.blocktools import (
COINBASE_MATURITY_2,
NORMAL_GBT_REQUEST_PARAMS,
add_witness_commitment,
create_block,
)
from test_framework.messages import (
BlockTransactions,
BlockTransactionsRequest,
CBlock,
CBlockHeader,
CInv,
COutPoint,
CTransaction,
CTxIn,
CTxInWitness,
CTxOut,
from_hex,
HeaderAndShortIDs,
MSG_BLOCK,
MSG_CMPCT_BLOCK,
MSG_WITNESS_FLAG,
NODE_NETWORK,
P2PHeaderAndShortIDs,
PrefilledTransaction,
calculate_shortid,
msg_block,
msg_blocktxn,
msg_cmpctblock,
msg_getblocktxn,
msg_getdata,
msg_getheaders,
msg_headers,
msg_inv,
msg_no_witness_block,
msg_no_witness_blocktxn,
msg_sendcmpct,
msg_sendheaders,
msg_tx,
ser_uint256,
tx_from_hex,
)
from test_framework.p2p import (
P2PInterface,
p2p_lock,
)
from test_framework.script import (
CScript,
OP_DROP,
OP_TRUE,
)
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import (
assert_equal,
softfork_active,
)
# TestP2PConn: A peer we use to send messages to bitcoind, and store responses.
class TestP2PConn(P2PInterface):
def __init__(self, cmpct_version):
super().__init__()
self.last_sendcmpct = []
self.block_announced = False
# Store the hashes of blocks we've seen announced.
# This is for synchronizing the p2p message traffic,
# so we can eg wait until a particular block is announced.
self.announced_blockhashes = set()
self.cmpct_version = cmpct_version
def on_sendcmpct(self, message):
self.last_sendcmpct.append(message)
def on_cmpctblock(self, message):
self.block_announced = True
self.last_message["cmpctblock"].header_and_shortids.header.calc_sha256()
self.announced_blockhashes.add(self.last_message["cmpctblock"].header_and_shortids.header.sha256)
def on_headers(self, message):
self.block_announced = True
for x in self.last_message["headers"].headers:
x.calc_sha256()
self.announced_blockhashes.add(x.sha256)
def on_inv(self, message):
for x in self.last_message["inv"].inv:
if x.type == MSG_BLOCK:
self.block_announced = True
self.announced_blockhashes.add(x.hash)
# Requires caller to hold p2p_lock
def received_block_announcement(self):
return self.block_announced
def clear_block_announcement(self):
with p2p_lock:
self.block_announced = False
self.last_message.pop("inv", None)
self.last_message.pop("headers", None)
self.last_message.pop("cmpctblock", None)
def get_headers(self, locator, hashstop):
msg = msg_getheaders()
msg.locator.vHave = locator
msg.hashstop = hashstop
self.send_message(msg)
def send_header_for_blocks(self, new_blocks):
headers_message = msg_headers()
headers_message.headers = [CBlockHeader(b) for b in new_blocks]
self.send_message(headers_message)
def request_headers_and_sync(self, locator, hashstop=0):
self.clear_block_announcement()
self.get_headers(locator, hashstop)
self.wait_until(self.received_block_announcement, timeout=30)
self.clear_block_announcement()
# Block until a block announcement for a particular block hash is
# received.
def wait_for_block_announcement(self, block_hash, timeout=30):
def received_hash():
return (block_hash in self.announced_blockhashes)
self.wait_until(received_hash, timeout=timeout)
def send_await_disconnect(self, message, timeout=30):
"""Sends a message to the node and wait for disconnect.
This is used when we want to send a message into the node that we expect
will get us disconnected, eg an invalid block."""
self.send_message(message)
self.wait_for_disconnect(timeout)
class CompactBlocksTest(DigiByteTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
self.extra_args = [[
"-acceptnonstdtxn=1",
]]
self.utxos = []
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
def build_block_on_tip(self, node, segwit=False):
block = create_block(tmpl=node.getblocktemplate(NORMAL_GBT_REQUEST_PARAMS))
if segwit:
add_witness_commitment(block)
block.solve()
return block
# Create 10 more anyone-can-spend utxo's for testing.
def make_utxos(self):
block = self.build_block_on_tip(self.nodes[0])
self.segwit_node.send_and_ping(msg_no_witness_block(block))
assert int(self.nodes[0].getbestblockhash(), 16) == block.sha256
self.generatetoaddress(self.nodes[0], COINBASE_MATURITY_2, self.nodes[0].getnewaddress(address_type="bech32"))
total_value = block.vtx[0].vout[0].nValue
out_value = total_value // 10
tx = CTransaction()
tx.vin.append(CTxIn(COutPoint(block.vtx[0].sha256, 0), b''))
for _ in range(10):
tx.vout.append(CTxOut(out_value, CScript([OP_TRUE])))
tx.rehash()
block2 = self.build_block_on_tip(self.nodes[0])
block2.vtx.append(tx)
block2.hashMerkleRoot = block2.calc_merkle_root()
block2.solve()
self.segwit_node.send_and_ping(msg_no_witness_block(block2))
assert_equal(int(self.nodes[0].getbestblockhash(), 16), block2.sha256)
self.utxos.extend([[tx.sha256, i, out_value] for i in range(10)])
# Test "sendcmpct" (between peers preferring the same version):
# - No compact block announcements unless sendcmpct is sent.
# - If sendcmpct is sent with version > preferred_version, the message is ignored.
# - If sendcmpct is sent with boolean 0, then block announcements are not
# made with compact blocks.
# - If sendcmpct is then sent with boolean 1, then new block announcements
# are made with compact blocks.
# If old_node is passed in, request compact blocks with version=preferred-1
# and verify that it receives block announcements via compact block.
def test_sendcmpct(self, test_node, old_node=None):
preferred_version = test_node.cmpct_version
node = self.nodes[0]
# Make sure we get a SENDCMPCT message from our peer
def received_sendcmpct():
return (len(test_node.last_sendcmpct) > 0)
test_node.wait_until(received_sendcmpct, timeout=30)
with p2p_lock:
# Check that the first version received is the preferred one
assert_equal(test_node.last_sendcmpct[0].version, preferred_version)
# And that we receive versions down to 1.
assert_equal(test_node.last_sendcmpct[-1].version, 1)
test_node.last_sendcmpct = []
tip = int(node.getbestblockhash(), 16)
def check_announcement_of_new_block(node, peer, predicate):
peer.clear_block_announcement()
block_hash = int(self.generate(node, 1)[0], 16)
peer.wait_for_block_announcement(block_hash, timeout=30)
assert peer.block_announced
with p2p_lock:
assert predicate(peer), (
"block_hash={!r}, cmpctblock={!r}, inv={!r}".format(
block_hash, peer.last_message.get("cmpctblock", None), peer.last_message.get("inv", None)))
# We shouldn't get any block announcements via cmpctblock yet.
check_announcement_of_new_block(node, test_node, lambda p: "cmpctblock" not in p.last_message)
# Try one more time, this time after requesting headers.
test_node.request_headers_and_sync(locator=[tip])
check_announcement_of_new_block(node, test_node, lambda p: "cmpctblock" not in p.last_message and "inv" in p.last_message)
# Test a few ways of using sendcmpct that should NOT
# result in compact block announcements.
# Before each test, sync the headers chain.
test_node.request_headers_and_sync(locator=[tip])
# Now try a SENDCMPCT message with too-high version
test_node.send_and_ping(msg_sendcmpct(announce=True, version=preferred_version+1))
check_announcement_of_new_block(node, test_node, lambda p: "cmpctblock" not in p.last_message)
# Headers sync before next test.
test_node.request_headers_and_sync(locator=[tip])
# Now try a SENDCMPCT message with valid version, but announce=False
test_node.send_and_ping(msg_sendcmpct(announce=False, version=preferred_version))
check_announcement_of_new_block(node, test_node, lambda p: "cmpctblock" not in p.last_message)
# Headers sync before next test.
test_node.request_headers_and_sync(locator=[tip])
# Finally, try a SENDCMPCT message with announce=True
test_node.send_and_ping(msg_sendcmpct(announce=True, version=preferred_version))
check_announcement_of_new_block(node, test_node, lambda p: "cmpctblock" in p.last_message)
# Try one more time (no headers sync should be needed!)
check_announcement_of_new_block(node, test_node, lambda p: "cmpctblock" in p.last_message)
# Try one more time, after turning on sendheaders
test_node.send_and_ping(msg_sendheaders())
check_announcement_of_new_block(node, test_node, lambda p: "cmpctblock" in p.last_message)
# Try one more time, after sending a version-1, announce=false message.
test_node.send_and_ping(msg_sendcmpct(announce=False, version=preferred_version-1))
check_announcement_of_new_block(node, test_node, lambda p: "cmpctblock" in p.last_message)
# Now turn off announcements
test_node.send_and_ping(msg_sendcmpct(announce=False, version=preferred_version))
check_announcement_of_new_block(node, test_node, lambda p: "cmpctblock" not in p.last_message and "headers" in p.last_message)
if old_node is not None:
# Verify that a peer using an older protocol version can receive
# announcements from this node.
old_node.send_and_ping(msg_sendcmpct(announce=True, version=preferred_version-1))
# Header sync
old_node.request_headers_and_sync(locator=[tip])
check_announcement_of_new_block(node, old_node, lambda p: "cmpctblock" in p.last_message)
# This test actually causes bitcoind to (reasonably!) disconnect us, so do this last.
def test_invalid_cmpctblock_message(self):
self.generate(self.nodes[0], COINBASE_MATURITY_2 + 1)
block = self.build_block_on_tip(self.nodes[0])
cmpct_block = P2PHeaderAndShortIDs()
cmpct_block.header = CBlockHeader(block)
cmpct_block.prefilled_txn_length = 1
# This index will be too high
prefilled_txn = PrefilledTransaction(1, block.vtx[0])
cmpct_block.prefilled_txn = [prefilled_txn]
self.segwit_node.send_await_disconnect(msg_cmpctblock(cmpct_block))
assert_equal(int(self.nodes[0].getbestblockhash(), 16), block.hashPrevBlock)
# Compare the generated shortids to what we expect based on BIP 152, given
# bitcoind's choice of nonce.
def test_compactblock_construction(self, test_node, use_witness_address=True):
version = test_node.cmpct_version
node = self.nodes[0]
# Generate a bunch of transactions.
self.generate(node, COINBASE_MATURITY_2 + 1)
num_transactions = 25
address = node.getnewaddress()
segwit_tx_generated = False
for _ in range(num_transactions):
txid = node.sendtoaddress(address, 0.1)
hex_tx = node.gettransaction(txid)["hex"]
tx = tx_from_hex(hex_tx)
if not tx.wit.is_null():
segwit_tx_generated = True
if use_witness_address:
assert segwit_tx_generated # check that our test is not broken
# Wait until we've seen the block announcement for the resulting tip
tip = int(node.getbestblockhash(), 16)
test_node.wait_for_block_announcement(tip)
# Make sure we will receive a fast-announce compact block
self.request_cb_announcements(test_node)
# Now mine a block, and look at the resulting compact block.
test_node.clear_block_announcement()
block_hash = int(self.generate(node, 1)[0], 16)
# Store the raw block in our internal format.
block = from_hex(CBlock(), node.getblock("%064x" % block_hash, False))
for tx in block.vtx:
tx.calc_sha256()
block.rehash()
# Wait until the block was announced (via compact blocks)
test_node.wait_until(lambda: "cmpctblock" in test_node.last_message, timeout=30)
# Now fetch and check the compact block
header_and_shortids = None
with p2p_lock:
# Convert the on-the-wire representation to absolute indexes
header_and_shortids = HeaderAndShortIDs(test_node.last_message["cmpctblock"].header_and_shortids)
self.check_compactblock_construction_from_block(version, header_and_shortids, block_hash, block)
# Now fetch the compact block using a normal non-announce getdata
test_node.clear_block_announcement()
inv = CInv(MSG_CMPCT_BLOCK, block_hash)
test_node.send_message(msg_getdata([inv]))
test_node.wait_until(lambda: "cmpctblock" in test_node.last_message, timeout=30)
# Now fetch and check the compact block
header_and_shortids = None
with p2p_lock:
# Convert the on-the-wire representation to absolute indexes
header_and_shortids = HeaderAndShortIDs(test_node.last_message["cmpctblock"].header_and_shortids)
self.check_compactblock_construction_from_block(version, header_and_shortids, block_hash, block)
def check_compactblock_construction_from_block(self, version, header_and_shortids, block_hash, block):
# Check that we got the right block!
header_and_shortids.header.calc_sha256()
assert_equal(header_and_shortids.header.sha256, block_hash)
# Make sure the prefilled_txn appears to have included the coinbase
assert len(header_and_shortids.prefilled_txn) >= 1
assert_equal(header_and_shortids.prefilled_txn[0].index, 0)
# Check that all prefilled_txn entries match what's in the block.
for entry in header_and_shortids.prefilled_txn:
entry.tx.calc_sha256()
# This checks the non-witness parts of the tx agree
assert_equal(entry.tx.sha256, block.vtx[entry.index].sha256)
# And this checks the witness
wtxid = entry.tx.calc_sha256(True)
if version == 2:
assert_equal(wtxid, block.vtx[entry.index].calc_sha256(True))
else:
# Shouldn't have received a witness
assert entry.tx.wit.is_null()
# Check that the cmpctblock message announced all the transactions.
assert_equal(len(header_and_shortids.prefilled_txn) + len(header_and_shortids.shortids), len(block.vtx))
# And now check that all the shortids are as expected as well.
# Determine the siphash keys to use.
[k0, k1] = header_and_shortids.get_siphash_keys()
index = 0
while index < len(block.vtx):
if (len(header_and_shortids.prefilled_txn) > 0 and
header_and_shortids.prefilled_txn[0].index == index):
# Already checked prefilled transactions above
header_and_shortids.prefilled_txn.pop(0)
else:
tx_hash = block.vtx[index].sha256
if version == 2:
tx_hash = block.vtx[index].calc_sha256(True)
shortid = calculate_shortid(k0, k1, tx_hash)
assert_equal(shortid, header_and_shortids.shortids[0])
header_and_shortids.shortids.pop(0)
index += 1
# Test that bitcoind requests compact blocks when we announce new blocks
# via header or inv, and that responding to getblocktxn causes the block
# to be successfully reconstructed.
# Post-segwit: upgraded nodes would only make this request of cb-version-2,
# NODE_WITNESS peers. Unupgraded nodes would still make this request of
# any cb-version-1-supporting peer.
def test_compactblock_requests(self, test_node, segwit=True):
version = test_node.cmpct_version
node = self.nodes[0]
# Try announcing a block with an inv or header, expect a compactblock
# request
for announce in ["inv", "header"]:
block = self.build_block_on_tip(node, segwit=segwit)
if announce == "inv":
test_node.send_message(msg_inv([CInv(MSG_BLOCK, block.sha256)]))
test_node.wait_until(lambda: "getheaders" in test_node.last_message, timeout=30)
test_node.send_header_for_blocks([block])
else:
test_node.send_header_for_blocks([block])
test_node.wait_for_getdata([block.sha256], timeout=30)
assert_equal(test_node.last_message["getdata"].inv[0].type, 4)
# Send back a compactblock message that omits the coinbase
comp_block = HeaderAndShortIDs()
comp_block.header = CBlockHeader(block)
comp_block.nonce = 0
[k0, k1] = comp_block.get_siphash_keys()
coinbase_hash = block.vtx[0].sha256
if version == 2:
coinbase_hash = block.vtx[0].calc_sha256(True)
comp_block.shortids = [calculate_shortid(k0, k1, coinbase_hash)]
test_node.send_and_ping(msg_cmpctblock(comp_block.to_p2p()))
assert_equal(int(node.getbestblockhash(), 16), block.hashPrevBlock)
# Expect a getblocktxn message.
with p2p_lock:
assert "getblocktxn" in test_node.last_message
absolute_indexes = test_node.last_message["getblocktxn"].block_txn_request.to_absolute()
assert_equal(absolute_indexes, [0]) # should be a coinbase request
# Send the coinbase, and verify that the tip advances.
if version == 2:
msg = msg_blocktxn()
else:
msg = msg_no_witness_blocktxn()
msg.block_transactions.blockhash = block.sha256
msg.block_transactions.transactions = [block.vtx[0]]
test_node.send_and_ping(msg)
assert_equal(int(node.getbestblockhash(), 16), block.sha256)
# Create a chain of transactions from given utxo, and add to a new block.
def build_block_with_transactions(self, node, utxo, num_transactions):
block = self.build_block_on_tip(node)
for _ in range(num_transactions):
tx = CTransaction()
tx.vin.append(CTxIn(COutPoint(utxo[0], utxo[1]), b''))
tx.vout.append(CTxOut(utxo[2] - 10000, CScript([OP_TRUE, OP_DROP] * 15 + [OP_TRUE])))
tx.rehash()
utxo = [tx.sha256, 0, tx.vout[0].nValue]
block.vtx.append(tx)
block.hashMerkleRoot = block.calc_merkle_root()
block.solve()
return block
# Test that we only receive getblocktxn requests for transactions that the
# node needs, and that responding to them causes the block to be
# reconstructed.
def test_getblocktxn_requests(self, test_node):
version = test_node.cmpct_version
node = self.nodes[0]
with_witness = (version == 2)
def test_getblocktxn_response(compact_block, peer, expected_result):
msg = msg_cmpctblock(compact_block.to_p2p())
peer.send_and_ping(msg)
with p2p_lock:
assert "getblocktxn" in peer.last_message
absolute_indexes = peer.last_message["getblocktxn"].block_txn_request.to_absolute()
assert_equal(absolute_indexes, expected_result)
def test_tip_after_message(node, peer, msg, tip):
peer.send_and_ping(msg)
assert_equal(int(node.getbestblockhash(), 16), tip)
# First try announcing compactblocks that won't reconstruct, and verify
# that we receive getblocktxn messages back.
utxo = self.utxos.pop(0)
block = self.build_block_with_transactions(node, utxo, 5)
self.utxos.append([block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue])
comp_block = HeaderAndShortIDs()
comp_block.initialize_from_block(block, use_witness=with_witness)
test_getblocktxn_response(comp_block, test_node, [1, 2, 3, 4, 5])
msg_bt = msg_no_witness_blocktxn()
if with_witness:
msg_bt = msg_blocktxn() # serialize with witnesses
msg_bt.block_transactions = BlockTransactions(block.sha256, block.vtx[1:])
test_tip_after_message(node, test_node, msg_bt, block.sha256)
utxo = self.utxos.pop(0)
block = self.build_block_with_transactions(node, utxo, 5)
self.utxos.append([block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue])
# Now try interspersing the prefilled transactions
comp_block.initialize_from_block(block, prefill_list=[0, 1, 5], use_witness=with_witness)
test_getblocktxn_response(comp_block, test_node, [2, 3, 4])
msg_bt.block_transactions = BlockTransactions(block.sha256, block.vtx[2:5])
test_tip_after_message(node, test_node, msg_bt, block.sha256)
# Now try giving one transaction ahead of time.
utxo = self.utxos.pop(0)
block = self.build_block_with_transactions(node, utxo, 5)
self.utxos.append([block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue])
test_node.send_and_ping(msg_tx(block.vtx[1]))
assert block.vtx[1].hash in node.getrawmempool()
# Prefill 4 out of the 6 transactions, and verify that only the one
# that was not in the mempool is requested.
comp_block.initialize_from_block(block, prefill_list=[0, 2, 3, 4], use_witness=with_witness)
test_getblocktxn_response(comp_block, test_node, [5])
msg_bt.block_transactions = BlockTransactions(block.sha256, [block.vtx[5]])
test_tip_after_message(node, test_node, msg_bt, block.sha256)
# Now provide all transactions to the node before the block is
# announced and verify reconstruction happens immediately.
utxo = self.utxos.pop(0)
block = self.build_block_with_transactions(node, utxo, 10)
self.utxos.append([block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue])
for tx in block.vtx[1:]:
test_node.send_message(msg_tx(tx))
test_node.sync_with_ping()
# Make sure all transactions were accepted.
mempool = node.getrawmempool()
for tx in block.vtx[1:]:
assert tx.hash in mempool
# Clear out last request.
with p2p_lock:
test_node.last_message.pop("getblocktxn", None)
# Send compact block
comp_block.initialize_from_block(block, prefill_list=[0], use_witness=with_witness)
test_tip_after_message(node, test_node, msg_cmpctblock(comp_block.to_p2p()), block.sha256)
with p2p_lock:
# Shouldn't have gotten a request for any transaction
assert "getblocktxn" not in test_node.last_message
# Incorrectly responding to a getblocktxn shouldn't cause the block to be
# permanently failed.
def test_incorrect_blocktxn_response(self, test_node):
version = test_node.cmpct_version
node = self.nodes[0]
utxo = self.utxos.pop(0)
block = self.build_block_with_transactions(node, utxo, 10)
self.utxos.append([block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue])
# Relay the first 5 transactions from the block in advance
for tx in block.vtx[1:6]:
test_node.send_message(msg_tx(tx))
test_node.sync_with_ping()
# Make sure all transactions were accepted.
mempool = node.getrawmempool()
for tx in block.vtx[1:6]:
assert tx.hash in mempool
# Send compact block
comp_block = HeaderAndShortIDs()
comp_block.initialize_from_block(block, prefill_list=[0], use_witness=(version == 2))
test_node.send_and_ping(msg_cmpctblock(comp_block.to_p2p()))
absolute_indexes = []
with p2p_lock:
assert "getblocktxn" in test_node.last_message
absolute_indexes = test_node.last_message["getblocktxn"].block_txn_request.to_absolute()
assert_equal(absolute_indexes, [6, 7, 8, 9, 10])
# Now give an incorrect response.
# Note that it's possible for bitcoind to be smart enough to know we're
# lying, since it could check to see if the shortid matches what we're
# sending, and eg disconnect us for misbehavior. If that behavior
# change was made, we could just modify this test by having a
# different peer provide the block further down, so that we're still
# verifying that the block isn't marked bad permanently. This is good
# enough for now.
msg = msg_no_witness_blocktxn()
if version == 2:
msg = msg_blocktxn()
msg.block_transactions = BlockTransactions(block.sha256, [block.vtx[5]] + block.vtx[7:])
test_node.send_and_ping(msg)
# Tip should not have updated
assert_equal(int(node.getbestblockhash(), 16), block.hashPrevBlock)
# We should receive a getdata request
test_node.wait_for_getdata([block.sha256], timeout=10)
assert test_node.last_message["getdata"].inv[0].type == MSG_BLOCK or \
test_node.last_message["getdata"].inv[0].type == MSG_BLOCK | MSG_WITNESS_FLAG
# Deliver the block
if version == 2:
test_node.send_and_ping(msg_block(block))
else:
test_node.send_and_ping(msg_no_witness_block(block))
assert_equal(int(node.getbestblockhash(), 16), block.sha256)
def test_getblocktxn_handler(self, test_node):
version = test_node.cmpct_version
node = self.nodes[0]
# bitcoind will not send blocktxn responses for blocks whose height is
# more than 10 blocks deep.
MAX_GETBLOCKTXN_DEPTH = 10
chain_height = node.getblockcount()
current_height = chain_height
while (current_height >= chain_height - MAX_GETBLOCKTXN_DEPTH):
block_hash = node.getblockhash(current_height)
block = from_hex(CBlock(), node.getblock(block_hash, False))
msg = msg_getblocktxn()
msg.block_txn_request = BlockTransactionsRequest(int(block_hash, 16), [])
num_to_request = random.randint(1, len(block.vtx))
msg.block_txn_request.from_absolute(sorted(random.sample(range(len(block.vtx)), num_to_request)))
test_node.send_message(msg)
test_node.wait_until(lambda: "blocktxn" in test_node.last_message, timeout=10)
[tx.calc_sha256() for tx in block.vtx]
with p2p_lock:
assert_equal(test_node.last_message["blocktxn"].block_transactions.blockhash, int(block_hash, 16))
all_indices = msg.block_txn_request.to_absolute()
for index in all_indices:
tx = test_node.last_message["blocktxn"].block_transactions.transactions.pop(0)
tx.calc_sha256()
assert_equal(tx.sha256, block.vtx[index].sha256)
if version == 1:
# Witnesses should have been stripped
assert tx.wit.is_null()
else:
# Check that the witness matches
assert_equal(tx.calc_sha256(True), block.vtx[index].calc_sha256(True))
test_node.last_message.pop("blocktxn", None)
current_height -= 1
# Next request should send a full block response, as we're past the
# allowed depth for a blocktxn response.
block_hash = node.getblockhash(current_height)
msg.block_txn_request = BlockTransactionsRequest(int(block_hash, 16), [0])
with p2p_lock:
test_node.last_message.pop("block", None)
test_node.last_message.pop("blocktxn", None)
test_node.send_and_ping(msg)
with p2p_lock:
test_node.last_message["block"].block.calc_sha256()
assert_equal(test_node.last_message["block"].block.sha256, int(block_hash, 16))
assert "blocktxn" not in test_node.last_message
def test_compactblocks_not_at_tip(self, test_node):
node = self.nodes[0]
# Test that requesting old compactblocks doesn't work.
MAX_CMPCTBLOCK_DEPTH = 5
new_blocks = []
for _ in range(MAX_CMPCTBLOCK_DEPTH + 1):
test_node.clear_block_announcement()
new_blocks.append(self.generate(node, 1)[0])
test_node.wait_until(test_node.received_block_announcement, timeout=30)
test_node.clear_block_announcement()
test_node.send_message(msg_getdata([CInv(MSG_CMPCT_BLOCK, int(new_blocks[0], 16))]))
test_node.wait_until(lambda: "cmpctblock" in test_node.last_message, timeout=30)
test_node.clear_block_announcement()
self.generate(node, 1)
test_node.wait_until(test_node.received_block_announcement, timeout=30)
test_node.clear_block_announcement()
with p2p_lock:
test_node.last_message.pop("block", None)
test_node.send_message(msg_getdata([CInv(MSG_CMPCT_BLOCK, int(new_blocks[0], 16))]))
test_node.wait_until(lambda: "block" in test_node.last_message, timeout=30)
with p2p_lock:
test_node.last_message["block"].block.calc_sha256()
assert_equal(test_node.last_message["block"].block.sha256, int(new_blocks[0], 16))
# Generate an old compactblock, and verify that it's not accepted.
cur_height = node.getblockcount()
hashPrevBlock = int(node.getblockhash(cur_height - 5), 16)
block = self.build_block_on_tip(node)
block.hashPrevBlock = hashPrevBlock
block.solve()
comp_block = HeaderAndShortIDs()
comp_block.initialize_from_block(block)
test_node.send_and_ping(msg_cmpctblock(comp_block.to_p2p()))
tips = node.getchaintips()
found = False
for x in tips:
if x["hash"] == block.hash:
assert_equal(x["status"], "headers-only")
found = True
break
assert found
# Requesting this block via getblocktxn should silently fail
# (to avoid fingerprinting attacks).
msg = msg_getblocktxn()
msg.block_txn_request = BlockTransactionsRequest(block.sha256, [0])
with p2p_lock:
test_node.last_message.pop("blocktxn", None)
test_node.send_and_ping(msg)
with p2p_lock:
assert "blocktxn" not in test_node.last_message
def test_end_to_end_block_relay(self, listeners):
node = self.nodes[0]
utxo = self.utxos.pop(0)
block = self.build_block_with_transactions(node, utxo, 10)
[l.clear_block_announcement() for l in listeners]
# serialize without witness (this block has no witnesses anyway).
# TODO: repeat this test with witness tx's to a segwit node.
node.submitblock(block.serialize().hex())
for l in listeners:
l.wait_until(lambda: "cmpctblock" in l.last_message, timeout=30)
with p2p_lock:
for l in listeners:
l.last_message["cmpctblock"].header_and_shortids.header.calc_sha256()
assert_equal(l.last_message["cmpctblock"].header_and_shortids.header.sha256, block.sha256)
# Test that we don't get disconnected if we relay a compact block with valid header,
# but invalid transactions.
def test_invalid_tx_in_compactblock(self, test_node, use_segwit=True):
node = self.nodes[0]
assert len(self.utxos)
utxo = self.utxos[0]
block = self.build_block_with_transactions(node, utxo, 5)
del block.vtx[3]
block.hashMerkleRoot = block.calc_merkle_root()
if use_segwit:
# If we're testing with segwit, also drop the coinbase witness,
# but include the witness commitment.
add_witness_commitment(block)
block.vtx[0].wit.vtxinwit = []
block.solve()
# Now send the compact block with all transactions prefilled, and
# verify that we don't get disconnected.
comp_block = HeaderAndShortIDs()
comp_block.initialize_from_block(block, prefill_list=[0, 1, 2, 3, 4], use_witness=use_segwit)
msg = msg_cmpctblock(comp_block.to_p2p())
test_node.send_and_ping(msg)
# Check that the tip didn't advance
assert int(node.getbestblockhash(), 16) is not block.sha256
test_node.sync_with_ping()
# Helper for enabling cb announcements
# Send the sendcmpct request and sync headers
def request_cb_announcements(self, peer):
node = self.nodes[0]
tip = node.getbestblockhash()
peer.get_headers(locator=[int(tip, 16)], hashstop=0)
peer.send_and_ping(msg_sendcmpct(announce=True, version=peer.cmpct_version))
def test_compactblock_reconstruction_multiple_peers(self, stalling_peer, delivery_peer):
node = self.nodes[0]
assert len(self.utxos)
def announce_cmpct_block(node, peer):
utxo = self.utxos.pop(0)
block = self.build_block_with_transactions(node, utxo, 5)
cmpct_block = HeaderAndShortIDs()
cmpct_block.initialize_from_block(block)
msg = msg_cmpctblock(cmpct_block.to_p2p())
peer.send_and_ping(msg)
with p2p_lock:
assert "getblocktxn" in peer.last_message
return block, cmpct_block
block, cmpct_block = announce_cmpct_block(node, stalling_peer)
for tx in block.vtx[1:]:
delivery_peer.send_message(msg_tx(tx))
delivery_peer.sync_with_ping()
mempool = node.getrawmempool()
for tx in block.vtx[1:]:
assert tx.hash in mempool
delivery_peer.send_and_ping(msg_cmpctblock(cmpct_block.to_p2p()))
assert_equal(int(node.getbestblockhash(), 16), block.sha256)
self.utxos.append([block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue])
# Now test that delivering an invalid compact block won't break relay
block, cmpct_block = announce_cmpct_block(node, stalling_peer)
for tx in block.vtx[1:]:
delivery_peer.send_message(msg_tx(tx))
delivery_peer.sync_with_ping()
cmpct_block.prefilled_txn[0].tx.wit.vtxinwit = [CTxInWitness()]
cmpct_block.prefilled_txn[0].tx.wit.vtxinwit[0].scriptWitness.stack = [ser_uint256(0)]
cmpct_block.use_witness = True
delivery_peer.send_and_ping(msg_cmpctblock(cmpct_block.to_p2p()))
assert int(node.getbestblockhash(), 16) != block.sha256
msg = msg_no_witness_blocktxn()
msg.block_transactions.blockhash = block.sha256
msg.block_transactions.transactions = block.vtx[1:]
stalling_peer.send_and_ping(msg)
assert_equal(int(node.getbestblockhash(), 16), block.sha256)
def test_highbandwidth_mode_states_via_getpeerinfo(self):
# create new p2p connection for a fresh state w/o any prior sendcmpct messages sent
hb_test_node = self.nodes[0].add_p2p_connection(TestP2PConn(cmpct_version=2))
# assert the RPC getpeerinfo boolean fields `bip152_hb_{to, from}`
# match the given parameters for the last peer of a given node
def assert_highbandwidth_states(node, hb_to, hb_from):
peerinfo = node.getpeerinfo()[-1]
assert_equal(peerinfo['bip152_hb_to'], hb_to)
assert_equal(peerinfo['bip152_hb_from'], hb_from)
# initially, neither node has selected the other peer as high-bandwidth yet
assert_highbandwidth_states(self.nodes[0], hb_to=False, hb_from=False)
# peer requests high-bandwidth mode by sending sendcmpct(1)
hb_test_node.send_and_ping(msg_sendcmpct(announce=True, version=2))
assert_highbandwidth_states(self.nodes[0], hb_to=False, hb_from=True)
# peer generates a block and sends it to node, which should
# select the peer as high-bandwidth (up to 3 peers according to BIP 152)
block = self.build_block_on_tip(self.nodes[0])
hb_test_node.send_and_ping(msg_block(block))
assert_highbandwidth_states(self.nodes[0], hb_to=True, hb_from=True)
# peer requests low-bandwidth mode by sending sendcmpct(0)
hb_test_node.send_and_ping(msg_sendcmpct(announce=False, version=2))
assert_highbandwidth_states(self.nodes[0], hb_to=True, hb_from=False)
def run_test(self):
# Get the nodes out of IBD
self.generate(self.nodes[0], 1)
# Setup the p2p connections
self.segwit_node = self.nodes[0].add_p2p_connection(TestP2PConn(cmpct_version=2))
self.old_node = self.nodes[0].add_p2p_connection(TestP2PConn(cmpct_version=1), services=NODE_NETWORK)
self.additional_segwit_node = self.nodes[0].add_p2p_connection(TestP2PConn(cmpct_version=2))
# We will need UTXOs to construct transactions in later tests.
self.make_utxos()
assert softfork_active(self.nodes[0], "segwit")
self.log.info("Testing SENDCMPCT p2p message... ")
self.test_sendcmpct(self.segwit_node, old_node=self.old_node)
self.test_sendcmpct(self.additional_segwit_node)
self.log.info("Testing compactblock construction...")
self.test_compactblock_construction(self.old_node)
self.test_compactblock_construction(self.segwit_node)
self.log.info("Testing compactblock requests (segwit node)... ")
self.test_compactblock_requests(self.segwit_node)
self.log.info("Testing getblocktxn requests (segwit node)...")
self.test_getblocktxn_requests(self.segwit_node)
self.log.info("Testing getblocktxn handler (segwit node should return witnesses)...")
self.test_getblocktxn_handler(self.segwit_node)
self.test_getblocktxn_handler(self.old_node)
self.log.info("Testing compactblock requests/announcements not at chain tip...")
self.test_compactblocks_not_at_tip(self.segwit_node)
self.test_compactblocks_not_at_tip(self.old_node)
self.log.info("Testing handling of incorrect blocktxn responses...")
self.test_incorrect_blocktxn_response(self.segwit_node)
self.log.info("Testing reconstructing compact blocks from all peers...")
self.test_compactblock_reconstruction_multiple_peers(self.segwit_node, self.additional_segwit_node)
# Test that if we submitblock to node1, we'll get a compact block
# announcement to all peers.
# (Post-segwit activation, blocks won't propagate from node0 to node1
# automatically, so don't bother testing a block announced to node0.)
self.log.info("Testing end-to-end block relay...")
self.request_cb_announcements(self.old_node)
self.request_cb_announcements(self.segwit_node)
self.test_end_to_end_block_relay([self.segwit_node, self.old_node])
self.log.info("Testing handling of invalid compact blocks...")
self.test_invalid_tx_in_compactblock(self.segwit_node)
self.test_invalid_tx_in_compactblock(self.old_node)
self.log.info("Testing invalid index in cmpctblock message...")
self.test_invalid_cmpctblock_message()
self.log.info("Testing high-bandwidth mode states via getpeerinfo...")
self.test_highbandwidth_mode_states_via_getpeerinfo()
if __name__ == '__main__':
CompactBlocksTest().main()

View file

@ -0,0 +1,95 @@
#!/usr/bin/env python3
# Copyright (c) 2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test compact blocks HB selection logic."""
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import assert_equal
class CompactBlocksConnectionTest(DigiByteTestFramework):
"""Test class for verifying selection of HB peer connections."""
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 6
def peer_info(self, from_node, to_node):
"""Query from_node for its getpeerinfo about to_node."""
for peerinfo in self.nodes[from_node].getpeerinfo():
if "(testnode%i)" % to_node in peerinfo['subver']:
return peerinfo
return None
def setup_network(self):
self.setup_nodes()
# Start network with everyone disconnected
self.sync_all()
def relay_block_through(self, peer):
"""Relay a new block through peer peer, and return HB status between 1 and [2,3,4,5]."""
self.connect_nodes(peer, 0)
self.generate(self.nodes[0], 1)
self.disconnect_nodes(peer, 0)
status_to = [self.peer_info(1, i)['bip152_hb_to'] for i in range(2, 6)]
status_from = [self.peer_info(i, 1)['bip152_hb_from'] for i in range(2, 6)]
assert_equal(status_to, status_from)
return status_to
def run_test(self):
self.log.info("Testing reserved high-bandwidth mode slot for outbound peer...")
# Connect everyone to node 0, and mine some blocks to get all nodes out of IBD.
for i in range(1, 6):
self.connect_nodes(i, 0)
self.generate(self.nodes[0], 2)
for i in range(1, 6):
self.disconnect_nodes(i, 0)
# Construct network topology:
# - Node 0 is the block producer
# - Node 1 is the "target" node being tested
# - Nodes 2-5 are intermediaries.
# - Node 1 has an outbound connection to node 2
# - Node 1 has inbound connections from nodes 3-5
self.connect_nodes(3, 1)
self.connect_nodes(4, 1)
self.connect_nodes(5, 1)
self.connect_nodes(1, 2)
# Mine blocks subsequently relaying through nodes 3,4,5 (inbound to node 1)
for nodeid in range(3, 6):
status = self.relay_block_through(nodeid)
assert_equal(status, [False, nodeid >= 3, nodeid >= 4, nodeid >= 5])
# And again through each. This should not change HB status.
for nodeid in range(3, 6):
status = self.relay_block_through(nodeid)
assert_equal(status, [False, True, True, True])
# Now relay one block through peer 2 (outbound from node 1), so it should take HB status
# from one of the inbounds.
status = self.relay_block_through(2)
assert_equal(status[0], True)
assert_equal(sum(status), 3)
# Now relay again through nodes 3,4,5. Since 2 is outbound, it should remain HB.
for nodeid in range(3, 6):
status = self.relay_block_through(nodeid)
assert status[0]
assert status[nodeid - 2]
assert_equal(sum(status), 3)
# Reconnect peer 2, and retry. Now the three inbounds should be HB again.
self.disconnect_nodes(1, 2)
self.connect_nodes(1, 2)
for nodeid in range(3, 6):
status = self.relay_block_through(nodeid)
assert not status[0]
assert status[nodeid - 2]
assert_equal(status, [False, True, True, True])
if __name__ == '__main__':
CompactBlocksConnectionTest().main()

149
test/functional/p2p_dandelion.py Executable file
View file

@ -0,0 +1,149 @@
#!/usr/bin/env python3
# Copyright (c) 2018 Bradley Denby
# Distributed under the MIT software license. See the accompanying file COPYING
# or http://www.opensource.org/licenses/mit-license.php.
"""Test transaction behaviors under the Dandelion spreading policy
Tests:
1. Resistance to active probing:
Stem: 0 --> 1 --> 2 --> 0 where each node has argument "-dandelion=1"
Probe: TestNode --> 0
Node 0 generates a Dandelion transaction "tx": 1.0 BTC from Node 0 to Node 2
TestNode immediately sends getdata for tx to Node 0
Assert that Node 0 does not reply with tx
2. Loop behavior:
Stem: 0 --> 1 --> 2 --> 0 where each node has argument "-dandelion=1"
Probe: TestNode --> 0
Wait ~5 seconds after Test 1, then TestNode sends getdata for tx to Node 0
Assert that Node 0 does not reply with tx
3. Resistance to black holes:
Stem: 0 --> 1 --> 2 --> 0 where each node has argument "-dandelion=1"
Probe: TestNode --> 0
Wait ~45 seconds after Test 2, then TestNode sends getdata for tx to Node 0
Assert that Node 0 replies with tx
"""
from test_framework.p2p import P2PInterface
from test_framework.test_framework import DigiByteTestFramework
from test_framework.blocktools import COINBASE_MATURITY_2
from test_framework.messages import (
CTransaction,
tx_from_hex,
msg_getdata,
CInv,
)
import time # sleep
class TestP2PConn(P2PInterface):
def __init__(self):
super().__init__()
def send_dandeliontx_getdata(self, dandeliontx_hash):
msg = msg_getdata()
msg.inv.append(CInv(5, dandeliontx_hash)) # 5: "DandelionTx"
self.send_message(msg)
class DandelionTest(DigiByteTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 3
self.extra_args = []
for i in range(self.num_nodes):
self.extra_args.append(["-dandelion=1"]) # ,"-debug=dandelion","-printtoconsole=1"
def setup_network(self):
self.setup_nodes()
# Tests 1,2,3: 0 --> 1 --> 2 --> 0
self.connect_nodes(0, 1)
self.connect_nodes(1, 2)
self.connect_nodes(2, 0)
self.nodes[0].createwallet(wallet_name='w0', load_on_startup=True)
self.nodes[1].createwallet(wallet_name='w1', load_on_startup=True)
self.nodes[2].createwallet(wallet_name='w2', load_on_startup=True)
def run_test(self):
# Convenience variables
node0, node1, node2 = self.nodes
w0 = node0.get_wallet_rpc('w0')
w1 = node1.get_wallet_rpc('w1')
w2 = node2.get_wallet_rpc('w2')
# Setup TestP2PConns
test_node0 = node0.add_p2p_connection(TestP2PConn())
# Start networking thread
test_node0.wait_for_verack()
# Get out of Initial Block Download (IBD)
for node in self.nodes:
self.generatetoaddress(node, 1, w0.getnewaddress(), sync_fun=self.no_op)
# Generate mature funds for node0 wallet
self.generatetoaddress(node0, COINBASE_MATURITY_2 + 1, w0.getnewaddress())
# Tests 1,2,3
# There is a low probability that one of these tests will fail even if
# the implementation is correct. Thus, these tests are repeated upon
# failure. A true bug will result in repeated failures.
self.log.info('Starting tests...')
test_1_passed = False
test_2_passed = False
test_3_passed = False
tries_left = 5
while(not (test_1_passed and test_2_passed and test_3_passed) and tries_left > 0):
tries_left -= 1
# Test 1: Resistance to active probing
test_node0.message_count['notfound'] = 0
node0_txid = node0.sendtoaddress(node2.getnewaddress(), 1.0)
node0_tx = tx_from_hex(node0.gettransaction(node0_txid)['hex'])
test_node0.send_dandeliontx_getdata(node0_tx.calc_sha256(True))
time.sleep(1)
try:
assert(test_node0.message_count['notfound']==1)
if not test_1_passed:
test_1_passed = True
self.log.info('Success: resistance to active probing')
except AssertionError:
if not test_1_passed and tries_left == 0:
self.log.info('Failed: resistance to active probing')
# Test 2: Loop behavior
test_node0.message_count['notfound'] = 0
time.sleep(3)
test_node0.send_dandeliontx_getdata(node0_tx.calc_sha256(True))
time.sleep(1)
try:
assert(test_node0.message_count['notfound']==1)
if not test_2_passed:
test_2_passed = True
self.log.info('Success: loop behavior')
except AssertionError:
if not test_2_passed and tries_left == 0:
self.log.info('Failed: loop behavior')
# Test 3: Resistance to black holes
test_node0.message_count['tx'] = 0
time.sleep(44)
test_node0.send_dandeliontx_getdata(node0_tx.calc_sha256(True))
time.sleep(1)
try:
assert(test_node0.message_count['tx']==1)
if not test_3_passed:
test_3_passed = True
self.log.info('Success: resistance to black holes')
except AssertionError:
if not test_3_passed and tries_left == 0:
self.log.info('Failed: resistance to black holes')
all_tests_passed = test_1_passed and test_2_passed and test_3_passed
assert(all_tests_passed)
if __name__ == '__main__':
DandelionTest().main()

View file

@ -0,0 +1,114 @@
#!/usr/bin/env python3
# Copyright (c) 2014-2020 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test node disconnect and ban behavior"""
import time
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
)
class DisconnectBanTest(DigiByteTestFramework):
def set_test_params(self):
self.num_nodes = 2
self.supports_cli = False
def run_test(self):
self.log.info("Connect nodes both way")
# By default, the test framework sets up an addnode connection from
# node 1 --> node0. By connecting node0 --> node 1, we're left with
# the two nodes being connected both ways.
# Topology will look like: node0 <--> node1
self.connect_nodes(0, 1)
self.log.info("Test setban and listbanned RPCs")
self.log.info("setban: successfully ban single IP address")
assert_equal(len(self.nodes[1].getpeerinfo()), 2) # node1 should have 2 connections to node0 at this point
self.nodes[1].setban(subnet="127.0.0.1", command="add")
self.wait_until(lambda: len(self.nodes[1].getpeerinfo()) == 0, timeout=10)
assert_equal(len(self.nodes[1].getpeerinfo()), 0) # all nodes must be disconnected at this point
assert_equal(len(self.nodes[1].listbanned()), 1)
self.log.info("clearbanned: successfully clear ban list")
self.nodes[1].clearbanned()
assert_equal(len(self.nodes[1].listbanned()), 0)
self.nodes[1].setban("127.0.0.0/24", "add")
self.log.info("setban: fail to ban an already banned subnet")
assert_equal(len(self.nodes[1].listbanned()), 1)
assert_raises_rpc_error(-23, "IP/Subnet already banned", self.nodes[1].setban, "127.0.0.1", "add")
self.log.info("setban: fail to ban an invalid subnet")
assert_raises_rpc_error(-30, "Error: Invalid IP/Subnet", self.nodes[1].setban, "127.0.0.1/42", "add")
assert_equal(len(self.nodes[1].listbanned()), 1) # still only one banned ip because 127.0.0.1 is within the range of 127.0.0.0/24
self.log.info("setban remove: fail to unban a non-banned subnet")
assert_raises_rpc_error(-30, "Error: Unban failed", self.nodes[1].setban, "127.0.0.1", "remove")
assert_equal(len(self.nodes[1].listbanned()), 1)
self.log.info("setban remove: successfully unban subnet")
self.nodes[1].setban("127.0.0.0/24", "remove")
assert_equal(len(self.nodes[1].listbanned()), 0)
self.nodes[1].clearbanned()
assert_equal(len(self.nodes[1].listbanned()), 0)
self.log.info("setban: test persistence across node restart")
self.nodes[1].setban("127.0.0.0/32", "add")
self.nodes[1].setban("127.0.0.0/24", "add")
# Set the mocktime so we can control when bans expire
old_time = int(time.time())
self.nodes[1].setmocktime(old_time)
self.nodes[1].setban("192.168.0.1", "add", 1) # ban for 1 seconds
self.nodes[1].setban("2001:4d48:ac57:400:cacf:e9ff:fe1d:9c63/19", "add", 1000) # ban for 1000 seconds
listBeforeShutdown = self.nodes[1].listbanned()
assert_equal("192.168.0.1/32", listBeforeShutdown[2]['address'])
# Move time forward by 3 seconds so the third ban has expired
self.nodes[1].setmocktime(old_time + 3)
assert_equal(len(self.nodes[1].listbanned()), 3)
self.restart_node(1)
listAfterShutdown = self.nodes[1].listbanned()
assert_equal("127.0.0.0/24", listAfterShutdown[0]['address'])
assert_equal("127.0.0.0/32", listAfterShutdown[1]['address'])
assert_equal("/19" in listAfterShutdown[2]['address'], True)
# Clear ban lists
self.nodes[1].clearbanned()
self.log.info("Connect nodes both way")
self.connect_nodes(0, 1)
self.connect_nodes(1, 0)
self.log.info("Test disconnectnode RPCs")
self.log.info("disconnectnode: fail to disconnect when calling with address and nodeid")
address1 = self.nodes[0].getpeerinfo()[0]['addr']
node1 = self.nodes[0].getpeerinfo()[0]['addr']
assert_raises_rpc_error(-32602, "Only one of address and nodeid should be provided.", self.nodes[0].disconnectnode, address=address1, nodeid=node1)
self.log.info("disconnectnode: fail to disconnect when calling with junk address")
assert_raises_rpc_error(-29, "Node not found in connected nodes", self.nodes[0].disconnectnode, address="221B Baker Street")
self.log.info("disconnectnode: successfully disconnect node by address")
address1 = self.nodes[0].getpeerinfo()[0]['addr']
self.nodes[0].disconnectnode(address=address1)
self.wait_until(lambda: len(self.nodes[0].getpeerinfo()) == 1, timeout=10)
assert not [node for node in self.nodes[0].getpeerinfo() if node['addr'] == address1]
self.log.info("disconnectnode: successfully reconnect node")
self.connect_nodes(0, 1) # reconnect the node
assert_equal(len(self.nodes[0].getpeerinfo()), 2)
assert [node for node in self.nodes[0].getpeerinfo() if node['addr'] == address1]
self.log.info("disconnectnode: successfully disconnect node by node id")
id1 = self.nodes[0].getpeerinfo()[0]['id']
self.nodes[0].disconnectnode(nodeid=id1)
self.wait_until(lambda: len(self.nodes[0].getpeerinfo()) == 1, timeout=10)
assert not [node for node in self.nodes[0].getpeerinfo() if node['id'] == id1]
if __name__ == '__main__':
DisconnectBanTest().main()

View file

@ -0,0 +1,90 @@
#!/usr/bin/env python3
# Copyright (c) 2021-2022 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test that we reject low difficulty headers to prevent our block tree from filling up with useless bloat"""
from test_framework.messages import (
CBlockHeader,
from_hex,
)
from test_framework.p2p import (
P2PInterface,
msg_headers,
)
from test_framework.test_framework import DigiByteTestFramework, SkipTest
import os
class RejectLowDifficultyHeadersTest(DigiByteTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.chain = 'testnet4' # Use testnet chain because it has an early checkpoint
self.num_nodes = 2
def add_options(self, parser):
parser.add_argument(
'--datafile',
default='data/blockheader_testnet3.hex',
help='Test data file (default: %(default)s)',
)
def skip_test_if_missing_module(self):
raise SkipTest("Vexta testnet header dataset and checkpoints are not finalized yet")
def run_test(self):
self.log.info("Read headers data")
self.headers_file_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), self.options.datafile)
with open(self.headers_file_path, encoding='utf-8') as headers_data:
h_lines = [l.strip() for l in headers_data.readlines()]
# The headers data is taken from testnet4 for early blocks from genesis until the first checkpoint. There are
# two headers with valid POW at height 1 and 2, forking off from genesis. They are indicated by the FORK_PREFIX.
FORK_PREFIX = 'fork:'
self.headers = [l for l in h_lines if not l.startswith(FORK_PREFIX)]
self.headers_fork = [l[len(FORK_PREFIX):] for l in h_lines if l.startswith(FORK_PREFIX)]
self.headers = [from_hex(CBlockHeader(), h) for h in self.headers]
self.headers_fork = [from_hex(CBlockHeader(), h) for h in self.headers_fork]
self.log.info("Feed all non-fork headers, including and up to the first checkpoint")
peer_checkpoint = self.nodes[0].add_p2p_connection(P2PInterface())
peer_checkpoint.send_and_ping(msg_headers(self.headers))
assert {
'height': 546,
'hash': '08fa50178f4b4f9fe1bbaed3b0a2ee58d1c51cc8185f70c8089e4b95763d9cdb',
'branchlen': 546,
'status': 'headers-only',
} in self.nodes[0].getchaintips()
self.log.info("Feed all fork headers (fails due to checkpoint)")
with self.nodes[0].assert_debug_log(['bad-fork-prior-to-checkpoint']):
peer_checkpoint.send_message(msg_headers(self.headers_fork))
peer_checkpoint.wait_for_disconnect()
self.log.info("Feed all fork headers (succeeds without checkpoint)")
# On node 0 it succeeds because checkpoints are disabled
self.restart_node(0, extra_args=['-nocheckpoints'])
peer_no_checkpoint = self.nodes[0].add_p2p_connection(P2PInterface())
peer_no_checkpoint.send_and_ping(msg_headers(self.headers_fork))
assert {
"height": 2,
"hash": "092bc73a5c80451e2f38b8d6a73d1f10cd1c3d3a12cffd55d0078e9beeb9c0b7",
"branchlen": 2,
"status": "headers-only",
} in self.nodes[0].getchaintips()
# On node 1 it succeeds because no checkpoint has been reached yet by a chain tip
peer_before_checkpoint = self.nodes[1].add_p2p_connection(P2PInterface())
peer_before_checkpoint.send_and_ping(msg_headers(self.headers_fork))
assert {
"height": 2,
"hash": "092bc73a5c80451e2f38b8d6a73d1f10cd1c3d3a12cffd55d0078e9beeb9c0b7",
"branchlen": 2,
"status": "headers-only",
} in self.nodes[1].getchaintips()
if __name__ == '__main__':
RejectLowDifficultyHeadersTest().main()

137
test/functional/p2p_eviction.py Executable file
View file

@ -0,0 +1,137 @@
#!/usr/bin/env python3
# Copyright (c) 2019-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
""" Test node eviction logic
When the number of peers has reached the limit of maximum connections,
the next connecting inbound peer will trigger the eviction mechanism.
We cannot currently test the parts of the eviction logic that are based on
address/netgroup since in the current framework, all peers are connecting from
the same local address. See Issue #14210 for more info.
Therefore, this test is limited to the remaining protection criteria.
"""
import time
from test_framework.blocktools import (
COINBASE_MATURITY,
create_block,
create_coinbase,
)
from test_framework.messages import (
msg_pong,
msg_tx,
tx_from_hex,
)
from test_framework.p2p import P2PDataStore, P2PInterface
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import assert_equal
class SlowP2PDataStore(P2PDataStore):
def on_ping(self, message):
time.sleep(0.1)
self.send_message(msg_pong(message.nonce))
class SlowP2PInterface(P2PInterface):
def on_ping(self, message):
time.sleep(0.1)
self.send_message(msg_pong(message.nonce))
class P2PEvict(DigiByteTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
# The choice of maxconnections=32 results in a maximum of 21 inbound connections
# (32 - 10 outbound - 1 feeler). 20 inbound peers are protected from eviction:
# 4 by netgroup, 4 that sent us blocks, 4 that sent us transactions and 8 via lowest ping time
self.extra_args = [['-maxconnections=32']]
def run_test(self):
protected_peers = set() # peers that we expect to be protected from eviction
current_peer = -1
node = self.nodes[0]
self.generatetoaddress(node, COINBASE_MATURITY + 1, node.get_deterministic_priv_key().address)
self.log.info("Create 4 peers and protect them from eviction by sending us a block")
for _ in range(4):
block_peer = node.add_p2p_connection(SlowP2PDataStore())
current_peer += 1
block_peer.sync_with_ping()
best_block = node.getbestblockhash()
tip = int(best_block, 16)
best_block_time = node.getblock(best_block)['time']
block = create_block(tip, create_coinbase(node.getblockcount() + 1), best_block_time + 1)
block.solve()
block_peer.send_blocks_and_test([block], node, success=True)
protected_peers.add(current_peer)
self.log.info("Create 5 slow-pinging peers, making them eviction candidates")
for _ in range(5):
node.add_p2p_connection(SlowP2PInterface())
current_peer += 1
self.log.info("Create 4 peers and protect them from eviction by sending us a tx")
for i in range(4):
txpeer = node.add_p2p_connection(SlowP2PInterface())
current_peer += 1
txpeer.sync_with_ping()
prevtx = node.getblock(node.getblockhash(i + 1), 2)['tx'][0]
rawtx = node.createrawtransaction(
inputs=[{'txid': prevtx['txid'], 'vout': 0}],
outputs=[{node.get_deterministic_priv_key().address: 50 - 0.00125}],
)
sigtx = node.signrawtransactionwithkey(
hexstring=rawtx,
privkeys=[node.get_deterministic_priv_key().key],
prevtxs=[{
'txid': prevtx['txid'],
'vout': 0,
'scriptPubKey': prevtx['vout'][0]['scriptPubKey']['hex'],
}],
)['hex']
txpeer.send_message(msg_tx(tx_from_hex(sigtx)))
protected_peers.add(current_peer)
self.log.info("Create 8 peers and protect them from eviction by having faster pings")
for _ in range(8):
fastpeer = node.add_p2p_connection(P2PInterface())
current_peer += 1
self.wait_until(lambda: "ping" in fastpeer.last_message, timeout=10)
# Make sure by asking the node what the actual min pings are
peerinfo = node.getpeerinfo()
pings = {}
for i in range(len(peerinfo)):
pings[i] = peerinfo[i]['minping'] if 'minping' in peerinfo[i] else 1000000
sorted_pings = sorted(pings.items(), key=lambda x: x[1])
# Usually the 8 fast peers are protected. In rare case of unreliable pings,
# one of the slower peers might have a faster min ping though.
for i in range(8):
protected_peers.add(sorted_pings[i][0])
self.log.info("Create peer that triggers the eviction mechanism")
node.add_p2p_connection(SlowP2PInterface())
# One of the non-protected peers must be evicted. We can't be sure which one because
# 4 peers are protected via netgroup, which is identical for all peers,
# and the eviction mechanism doesn't preserve the order of identical elements.
evicted_peers = []
for i in range(len(node.p2ps)):
if not node.p2ps[i].is_connected:
evicted_peers.append(i)
self.log.info("Test that one peer was evicted")
self.log.debug("{} evicted peer: {}".format(len(evicted_peers), set(evicted_peers)))
assert_equal(len(evicted_peers), 1)
self.log.info("Test that no peer expected to be protected was evicted")
self.log.debug("{} protected peers: {}".format(len(protected_peers), protected_peers))
assert evicted_peers[0] not in protected_peers
if __name__ == '__main__':
P2PEvict().main()

140
test/functional/p2p_feefilter.py Executable file
View file

@ -0,0 +1,140 @@
#!/usr/bin/env python3
# Copyright (c) 2021-2022 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test processing of feefilter messages."""
from decimal import Decimal
from test_framework.blocktools import COINBASE_MATURITY
from test_framework.messages import MSG_TX, MSG_WTX, msg_feefilter
from test_framework.p2p import P2PInterface, p2p_lock, P2P_VERSION, WTXID_RELAY_VERSION
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import assert_equal
from test_framework.wallet import MiniWallet
class FeefilterConn(P2PInterface):
feefilter_received = False
def on_feefilter(self, message):
self.feefilter_received = True
def assert_feefilter_received(self, recv: bool):
with p2p_lock:
assert_equal(self.feefilter_received, recv)
class TestP2PConn(P2PInterface):
def __init__(self):
super().__init__()
self.txinvs = []
def on_inv(self, message):
for i in message.inv:
if (i.type == MSG_TX) or (i.type == MSG_WTX):
self.txinvs.append('{:064x}'.format(i.hash))
def wait_for_invs_to_match(self, invs_expected):
invs_expected.sort()
self.wait_until(lambda: invs_expected == sorted(self.txinvs))
def clear_invs(self):
with p2p_lock:
self.txinvs = []
class FeeFilterTest(DigiByteTestFramework):
def set_test_params(self):
self.num_nodes = 2
# We lower the various required feerates for this test
# to catch a corner-case where feefilter used to slightly undercut
# mempool and wallet feerate calculation based on GetFee
# rounding down 3 places, leading to stranded transactions.
# See issue #16499
# grant noban permission to all peers to speed up tx relay / mempool sync
self.extra_args = [[
"-minrelaytxfee=0.00000100",
"-mintxfee=0.00000100",
"-whitelist=noban@127.0.0.1",
]] * self.num_nodes
def run_test(self):
self.test_feefilter_forcerelay()
self.test_feefilter()
self.test_feefilter_blocksonly()
def test_feefilter_forcerelay(self):
self.log.info('Check that peers without forcerelay permission (default) get a feefilter message')
self.nodes[0].add_p2p_connection(FeefilterConn()).assert_feefilter_received(True)
self.log.info('Check that peers with forcerelay permission do not get a feefilter message')
self.restart_node(0, extra_args=['-whitelist=forcerelay@127.0.0.1'])
self.nodes[0].add_p2p_connection(FeefilterConn()).assert_feefilter_received(False)
# Restart to disconnect peers and load default extra_args
self.restart_node(0)
self.connect_nodes(1, 0)
def test_feefilter(self):
node1 = self.nodes[1]
node0 = self.nodes[0]
miniwallet = MiniWallet(node1)
# Add enough mature utxos to the wallet, so that all txs spend confirmed coins
self.generate(miniwallet, 5)
self.generate(node1, COINBASE_MATURITY)
conn = self.nodes[0].add_p2p_connection(TestP2PConn())
self.log.info("Test txs paying 20 sat/byte are received by test connection")
TX_KEY = 'wtxid' if P2P_VERSION >= WTXID_RELAY_VERSION else 'txid'
txids = [miniwallet.send_self_transfer(fee_rate=Decimal('0.000200'), from_node=node1)[TX_KEY] for _ in range(3)]
conn.wait_for_invs_to_match(txids)
conn.clear_invs()
# Set a fee filter of 0.15 sat/byte on test connection
conn.send_and_ping(msg_feefilter(150))
self.log.info("Test txs paying 0.15 sat/byte are received by test connection")
txids = [miniwallet.send_self_transfer(fee_rate=Decimal('0.00000150'), from_node=node1)[TX_KEY] for _ in range(3)]
conn.wait_for_invs_to_match(txids)
conn.clear_invs()
self.log.info("Test txs paying 0.1 sat/byte are no longer received by test connection")
txids = [miniwallet.send_self_transfer(fee_rate=Decimal('0.00000100'), from_node=node1)[TX_KEY] for _ in range(3)]
self.sync_mempools() # must be sure node 0 has received all txs
# Send one transaction from node0 that should be received, so that we
# we can sync the test on receipt (if node1's txs were relayed, they'd
# be received by the time this node0 tx is received). This is
# unfortunately reliant on the current relay behavior where we batch up
# to 35 entries in an inv, which means that when this next transaction
# is eligible for relay, the prior transactions from node1 are eligible
# as well.
txids = [miniwallet.send_self_transfer(fee_rate=Decimal('0.00020000'), from_node=node0)[TX_KEY] for _ in range(1)]
conn.wait_for_invs_to_match(txids)
conn.clear_invs()
self.sync_mempools() # must be sure node 1 has received all txs
self.log.info("Remove fee filter and check txs are received again")
conn.send_and_ping(msg_feefilter(0))
txids = [miniwallet.send_self_transfer(fee_rate=Decimal('0.00020000'), from_node=node1)[TX_KEY] for _ in range(3)]
conn.wait_for_invs_to_match(txids)
conn.clear_invs()
def test_feefilter_blocksonly(self):
"""Test that we don't send fee filters to block-relay-only peers and when we're in blocksonly mode."""
self.log.info("Check that we don't send fee filters to block-relay-only peers.")
feefilter_peer = self.nodes[0].add_outbound_p2p_connection(FeefilterConn(), p2p_idx=0, connection_type="block-relay-only")
feefilter_peer.sync_with_ping()
feefilter_peer.assert_feefilter_received(False)
self.log.info("Check that we don't send fee filters when in blocksonly mode.")
self.restart_node(0, ["-blocksonly"])
feefilter_peer = self.nodes[0].add_p2p_connection(FeefilterConn())
feefilter_peer.sync_with_ping()
feefilter_peer.assert_feefilter_received(False)
if __name__ == '__main__':
FeeFilterTest().main()

242
test/functional/p2p_filter.py Executable file
View file

@ -0,0 +1,242 @@
#!/usr/bin/env python3
# Copyright (c) 2020 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""
Test BIP 37
"""
from test_framework.messages import (
CInv,
MAX_BLOOM_FILTER_SIZE,
MAX_BLOOM_HASH_FUNCS,
MSG_BLOCK,
MSG_FILTERED_BLOCK,
msg_filteradd,
msg_filterclear,
msg_filterload,
msg_getdata,
msg_mempool,
msg_version,
)
from test_framework.p2p import (
P2PInterface,
P2P_SERVICES,
P2P_SUBVERSION,
P2P_VERSION,
p2p_lock,
)
from test_framework.script import MAX_SCRIPT_ELEMENT_SIZE
from test_framework.test_framework import DigiByteTestFramework
class P2PBloomFilter(P2PInterface):
# This is a P2SH watch-only wallet
watch_script_pubkey = 'a914ffffffffffffffffffffffffffffffffffffffff87'
# The initial filter (n=10, fp=0.000001) with just the above scriptPubKey added
watch_filter_init = msg_filterload(
data=
b'@\x00\x08\x00\x80\x00\x00 \x00\xc0\x00 \x04\x00\x08$\x00\x04\x80\x00\x00 \x00\x00\x00\x00\x80\x00\x00@\x00\x02@ \x00',
nHashFuncs=19,
nTweak=0,
nFlags=1,
)
def __init__(self):
super().__init__()
self._tx_received = False
self._merkleblock_received = False
def on_inv(self, message):
want = msg_getdata()
for i in message.inv:
# inv messages can only contain TX or BLOCK, so translate BLOCK to FILTERED_BLOCK
if i.type == MSG_BLOCK:
want.inv.append(CInv(MSG_FILTERED_BLOCK, i.hash))
else:
want.inv.append(i)
if len(want.inv):
self.send_message(want)
def on_merkleblock(self, message):
self._merkleblock_received = True
def on_tx(self, message):
self._tx_received = True
@property
def tx_received(self):
with p2p_lock:
return self._tx_received
@tx_received.setter
def tx_received(self, value):
with p2p_lock:
self._tx_received = value
@property
def merkleblock_received(self):
with p2p_lock:
return self._merkleblock_received
@merkleblock_received.setter
def merkleblock_received(self, value):
with p2p_lock:
self._merkleblock_received = value
class FilterTest(DigiByteTestFramework):
def set_test_params(self):
self.num_nodes = 1
self.extra_args = [[
'-peerbloomfilters',
'-whitelist=noban@127.0.0.1', # immediate tx relay
]]
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
def test_size_limits(self, filter_peer):
self.log.info('Check that too large filter is rejected')
with self.nodes[0].assert_debug_log(['Misbehaving']):
filter_peer.send_and_ping(msg_filterload(data=b'\xbb'*(MAX_BLOOM_FILTER_SIZE+1)))
self.log.info('Check that max size filter is accepted')
with self.nodes[0].assert_debug_log([], unexpected_msgs=['Misbehaving']):
filter_peer.send_and_ping(msg_filterload(data=b'\xbb'*(MAX_BLOOM_FILTER_SIZE)))
filter_peer.send_and_ping(msg_filterclear())
self.log.info('Check that filter with too many hash functions is rejected')
with self.nodes[0].assert_debug_log(['Misbehaving']):
filter_peer.send_and_ping(msg_filterload(data=b'\xaa', nHashFuncs=MAX_BLOOM_HASH_FUNCS+1))
self.log.info('Check that filter with max hash functions is accepted')
with self.nodes[0].assert_debug_log([], unexpected_msgs=['Misbehaving']):
filter_peer.send_and_ping(msg_filterload(data=b'\xaa', nHashFuncs=MAX_BLOOM_HASH_FUNCS))
# Don't send filterclear until next two filteradd checks are done
self.log.info('Check that max size data element to add to the filter is accepted')
with self.nodes[0].assert_debug_log([], unexpected_msgs=['Misbehaving']):
filter_peer.send_and_ping(msg_filteradd(data=b'\xcc'*(MAX_SCRIPT_ELEMENT_SIZE)))
self.log.info('Check that too large data element to add to the filter is rejected')
with self.nodes[0].assert_debug_log(['Misbehaving']):
filter_peer.send_and_ping(msg_filteradd(data=b'\xcc'*(MAX_SCRIPT_ELEMENT_SIZE+1)))
filter_peer.send_and_ping(msg_filterclear())
def test_msg_mempool(self):
self.log.info("Check that a node with bloom filters enabled services p2p mempool messages")
filter_peer = P2PBloomFilter()
self.log.debug("Create a tx relevant to the peer before connecting")
filter_address = self.nodes[0].decodescript(filter_peer.watch_script_pubkey)['address']
txid = self.nodes[0].sendtoaddress(filter_address, 90)
self.log.debug("Send a mempool msg after connecting and check that the tx is received")
self.nodes[0].add_p2p_connection(filter_peer)
filter_peer.send_and_ping(filter_peer.watch_filter_init)
filter_peer.send_message(msg_mempool())
filter_peer.wait_for_tx(txid)
def test_frelay_false(self, filter_peer):
self.log.info("Check that a node with fRelay set to false does not receive invs until the filter is set")
filter_peer.tx_received = False
filter_address = self.nodes[0].decodescript(filter_peer.watch_script_pubkey)['address']
self.nodes[0].sendtoaddress(filter_address, 90)
# Sync to make sure the reason filter_peer doesn't receive the tx is not p2p delays
filter_peer.sync_with_ping()
assert not filter_peer.tx_received
# Clear the mempool so that this transaction does not impact subsequent tests
self.generate(self.nodes[0], 1)
def test_filter(self, filter_peer):
# Set the bloomfilter using filterload
filter_peer.send_and_ping(filter_peer.watch_filter_init)
# If fRelay is not already True, sending filterload sets it to True
assert self.nodes[0].getpeerinfo()[0]['relaytxes']
filter_address = self.nodes[0].decodescript(filter_peer.watch_script_pubkey)['address']
self.log.info('Check that we receive merkleblock and tx if the filter matches a tx in a block')
block_hash = self.generatetoaddress(self.nodes[0], 1, filter_address)[0]
txid = self.nodes[0].getblock(block_hash)['tx'][0]
filter_peer.wait_for_merkleblock(block_hash)
filter_peer.wait_for_tx(txid)
self.log.info('Check that we only receive a merkleblock if the filter does not match a tx in a block')
filter_peer.tx_received = False
block_hash = self.generatetoaddress(self.nodes[0], 1, self.nodes[0].getnewaddress())[0]
filter_peer.wait_for_merkleblock(block_hash)
assert not filter_peer.tx_received
self.log.info('Check that we not receive a tx if the filter does not match a mempool tx')
filter_peer.merkleblock_received = False
filter_peer.tx_received = False
self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 9)
filter_peer.sync_send_with_ping()
assert not filter_peer.merkleblock_received
assert not filter_peer.tx_received
self.log.info('Check that we receive a tx if the filter matches a mempool tx')
filter_peer.merkleblock_received = False
txid = self.nodes[0].sendtoaddress(filter_address, 90)
filter_peer.wait_for_tx(txid)
assert not filter_peer.merkleblock_received
self.log.info('Check that after deleting filter all txs get relayed again')
filter_peer.send_and_ping(msg_filterclear())
for _ in range(5):
txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 7)
filter_peer.wait_for_tx(txid)
self.log.info('Check that request for filtered blocks is ignored if no filter is set')
filter_peer.merkleblock_received = False
filter_peer.tx_received = False
with self.nodes[0].assert_debug_log(expected_msgs=['received getdata']):
block_hash = self.generatetoaddress(self.nodes[0], 1, self.nodes[0].getnewaddress())[0]
filter_peer.wait_for_inv([CInv(MSG_BLOCK, int(block_hash, 16))])
filter_peer.sync_with_ping()
assert not filter_peer.merkleblock_received
assert not filter_peer.tx_received
self.log.info('Check that sending "filteradd" if no filter is set is treated as misbehavior')
with self.nodes[0].assert_debug_log(['Misbehaving']):
filter_peer.send_and_ping(msg_filteradd(data=b'letsmisbehave'))
self.log.info("Check that division-by-zero remote crash bug [CVE-2013-5700] is fixed")
filter_peer.send_and_ping(msg_filterload(data=b'', nHashFuncs=1))
filter_peer.send_and_ping(msg_filteradd(data=b'letstrytocrashthisnode'))
self.nodes[0].disconnect_p2ps()
def run_test(self):
self.generatetoaddress(self.nodes[0], 101, self.nodes[0].getnewaddress())
self.sync_all()
filter_peer = self.nodes[0].add_p2p_connection(P2PBloomFilter())
self.log.info('Test filter size limits')
self.test_size_limits(filter_peer)
self.log.info('Test BIP 37 for a node with fRelay = True (default)')
self.test_filter(filter_peer)
self.nodes[0].disconnect_p2ps()
self.log.info('Test BIP 37 for a node with fRelay = False')
# Add peer but do not send version yet
filter_peer_without_nrelay = self.nodes[0].add_p2p_connection(P2PBloomFilter(), send_version=False, wait_for_verack=False)
# Send version with relay=False
version_without_fRelay = msg_version()
version_without_fRelay.nVersion = P2P_VERSION
version_without_fRelay.strSubVer = P2P_SUBVERSION
version_without_fRelay.nServices = P2P_SERVICES
version_without_fRelay.relay = 0
filter_peer_without_nrelay.send_message(version_without_fRelay)
filter_peer_without_nrelay.wait_for_verack()
assert not self.nodes[0].getpeerinfo()[0]['relaytxes']
self.test_frelay_false(filter_peer_without_nrelay)
self.test_filter(filter_peer_without_nrelay)
self.test_msg_mempool()
if __name__ == '__main__':
FilterTest().main()

View file

@ -0,0 +1,133 @@
#!/usr/bin/env python3
# Copyright (c) 2017-2021 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test various fingerprinting protections.
If a stale block more than a month old or its header are requested by a peer,
the node should pretend that it does not have it to avoid fingerprinting.
"""
import time
from test_framework.blocktools import (create_block, create_coinbase)
from test_framework.messages import CInv, MSG_BLOCK
from test_framework.p2p import (
P2PInterface,
msg_headers,
msg_block,
msg_getdata,
msg_getheaders,
p2p_lock,
)
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import (
assert_equal,
)
class P2PFingerprintTest(DigiByteTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
# Build a chain of blocks on top of given one
def build_chain(self, nblocks, prev_hash, prev_height, prev_median_time):
blocks = []
for _ in range(nblocks):
coinbase = create_coinbase(prev_height + 1)
block_time = prev_median_time + 1
block = create_block(int(prev_hash, 16), coinbase, block_time)
block.solve()
blocks.append(block)
prev_hash = block.hash
prev_height += 1
prev_median_time = block_time
return blocks
# Send a getdata request for a given block hash
def send_block_request(self, block_hash, node):
msg = msg_getdata()
msg.inv.append(CInv(MSG_BLOCK, block_hash))
node.send_message(msg)
# Send a getheaders request for a given single block hash
def send_header_request(self, block_hash, node):
msg = msg_getheaders()
msg.hashstop = block_hash
node.send_message(msg)
# Checks that stale blocks timestamped more than a month ago are not served
# by the node while recent stale blocks and old active chain blocks are.
# This does not currently test that stale blocks timestamped within the
# last month but that have over a month's worth of work are also withheld.
def run_test(self):
node0 = self.nodes[0].add_p2p_connection(P2PInterface())
# Set node time to 60 days ago
self.nodes[0].setmocktime(int(time.time()) - 60 * 24 * 60 * 60)
# Generating a chain of 10 blocks
block_hashes = self.generatetoaddress(self.nodes[0], 10, self.nodes[0].get_deterministic_priv_key().address)
# Create longer chain starting 2 blocks before current tip
height = len(block_hashes) - 2
block_hash = block_hashes[height - 1]
block_time = self.nodes[0].getblockheader(block_hash)["mediantime"] + 1
new_blocks = self.build_chain(5, block_hash, height, block_time)
# Force reorg to a longer chain
node0.send_message(msg_headers(new_blocks))
node0.wait_for_getdata([x.sha256 for x in new_blocks])
for block in new_blocks:
node0.send_and_ping(msg_block(block))
# Check that reorg succeeded
assert_equal(self.nodes[0].getblockcount(), 13)
stale_hash = int(block_hashes[-1], 16)
# Check that getdata request for stale block succeeds
self.send_block_request(stale_hash, node0)
node0.wait_for_block(stale_hash, timeout=3)
# Check that getheader request for stale block header succeeds
self.send_header_request(stale_hash, node0)
node0.wait_for_header(hex(stale_hash), timeout=3)
# Longest chain is extended so stale is much older than chain tip
self.nodes[0].setmocktime(0)
block_hash = int(self.generatetoaddress(self.nodes[0], 1, self.nodes[0].get_deterministic_priv_key().address)[-1], 16)
assert_equal(self.nodes[0].getblockcount(), 14)
node0.wait_for_block(block_hash, timeout=3)
# Request for very old stale block should now fail
with p2p_lock:
node0.last_message.pop("block", None)
self.send_block_request(stale_hash, node0)
node0.sync_with_ping()
assert "block" not in node0.last_message
# Request for very old stale block header should now fail
with p2p_lock:
node0.last_message.pop("headers", None)
self.send_header_request(stale_hash, node0)
node0.sync_with_ping()
assert "headers" not in node0.last_message
# Verify we can fetch very old blocks and headers on the active chain
block_hash = int(block_hashes[2], 16)
self.send_block_request(block_hash, node0)
self.send_header_request(block_hash, node0)
node0.sync_with_ping()
self.send_block_request(block_hash, node0)
node0.wait_for_block(block_hash, timeout=3)
self.send_header_request(block_hash, node0)
node0.wait_for_header(hex(block_hash), timeout=3)
if __name__ == '__main__':
P2PFingerprintTest().main()

View file

@ -0,0 +1,89 @@
#!/usr/bin/env python3
# Copyright (c) 2020 The DigiByte Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test addr response caching"""
import time
from test_framework.messages import msg_getaddr
from test_framework.p2p import (
P2PInterface,
p2p_lock
)
from test_framework.test_framework import DigiByteTestFramework
from test_framework.util import (
assert_equal,
)
# As defined in net_processing.
MAX_ADDR_TO_SEND = 1000
MAX_PCT_ADDR_TO_SEND = 23
class AddrReceiver(P2PInterface):
def __init__(self):
super().__init__()
self.received_addrs = None
def get_received_addrs(self):
with p2p_lock:
return self.received_addrs
def on_addr(self, message):
self.received_addrs = []
for addr in message.addrs:
self.received_addrs.append(addr.ip)
def addr_received(self):
return self.received_addrs is not None
class AddrTest(DigiByteTestFramework):
def set_test_params(self):
self.num_nodes = 1
def run_test(self):
self.log.info('Fill peer AddrMan with a lot of records')
for i in range(10000):
first_octet = i >> 8
second_octet = i % 256
a = "{}.{}.1.1".format(first_octet, second_octet)
self.nodes[0].addpeeraddress(a, 8333)
# Need to make sure we hit MAX_ADDR_TO_SEND records in the addr response later because
# only a fraction of all known addresses can be cached and returned.
assert(len(self.nodes[0].getnodeaddresses(0)) > int(MAX_ADDR_TO_SEND / (MAX_PCT_ADDR_TO_SEND / 100)))
responses = []
self.log.info('Send many addr requests within short time to receive same response')
N = 5
cur_mock_time = int(time.time())
for i in range(N):
addr_receiver = self.nodes[0].add_p2p_connection(AddrReceiver())
addr_receiver.send_and_ping(msg_getaddr())
# Trigger response
cur_mock_time += 5 * 60
self.nodes[0].setmocktime(cur_mock_time)
addr_receiver.wait_until(addr_receiver.addr_received)
responses.append(addr_receiver.get_received_addrs())
for response in responses[1:]:
assert_equal(response, responses[0])
assert(len(response) == MAX_ADDR_TO_SEND)
cur_mock_time += 3 * 24 * 60 * 60
self.nodes[0].setmocktime(cur_mock_time)
self.log.info('After time passed, see a new response to addr request')
last_addr_receiver = self.nodes[0].add_p2p_connection(AddrReceiver())
last_addr_receiver.send_and_ping(msg_getaddr())
# Trigger response
cur_mock_time += 5 * 60
self.nodes[0].setmocktime(cur_mock_time)
last_addr_receiver.wait_until(last_addr_receiver.addr_received)
# new response is different
assert(set(responses[0]) != set(last_addr_receiver.get_received_addrs()))
if __name__ == '__main__':
AddrTest().main()

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