Initial Vexta Core v0.1.0 release
This commit is contained in:
commit
25c454329d
2481 changed files with 689289 additions and 0 deletions
295
test/README.md
Normal file
295
test/README.md
Normal file
|
|
@ -0,0 +1,295 @@
|
|||
This directory contains integration tests that test digibyted and its
|
||||
utilities in their entirety. It does not contain unit tests, which
|
||||
can be found in [/src/test](/src/test), [/src/wallet/test](/src/wallet/test),
|
||||
etc.
|
||||
|
||||
This directory contains the following sets of tests:
|
||||
|
||||
- [functional](/test/functional) which test the functionality of
|
||||
digibyted and digibyte-qt by interacting with them through the RPC and P2P
|
||||
interfaces.
|
||||
- [util](/test/util) which tests the digibyte utilities, currently only
|
||||
digibyte-tx.
|
||||
- [lint](/test/lint/) which perform various static analysis checks.
|
||||
|
||||
The util tests are run as part of `make check` target. The functional
|
||||
tests and lint scripts can be run as explained in the sections below.
|
||||
|
||||
# Running tests locally
|
||||
|
||||
Before tests can be run locally, DigiByte Core must be built. See the [building instructions](/doc#building) for help.
|
||||
|
||||
|
||||
### Functional tests
|
||||
|
||||
#### Dependencies
|
||||
|
||||
The ZMQ functional test requires a python ZMQ library. To install it:
|
||||
|
||||
- on Unix, run `sudo apt-get install python3-zmq`
|
||||
- on mac OS, run `pip3 install pyzmq`
|
||||
|
||||
#### Running the tests
|
||||
|
||||
Individual tests can be run by directly calling the test script, e.g.:
|
||||
|
||||
```
|
||||
test/functional/feature_rbf.py
|
||||
```
|
||||
|
||||
or can be run through the test_runner harness, eg:
|
||||
|
||||
```
|
||||
test/functional/test_runner.py feature_rbf.py
|
||||
```
|
||||
|
||||
You can run any combination (incl. duplicates) of tests by calling:
|
||||
|
||||
```
|
||||
test/functional/test_runner.py <testname1> <testname2> <testname3> ...
|
||||
```
|
||||
|
||||
Wildcard test names can be passed, if the paths are coherent and the test runner
|
||||
is called from a `bash` shell or similar that does the globbing. For example,
|
||||
to run all the wallet tests:
|
||||
|
||||
```
|
||||
test/functional/test_runner.py test/functional/wallet*
|
||||
functional/test_runner.py functional/wallet* (called from the test/ directory)
|
||||
test_runner.py wallet* (called from the test/functional/ directory)
|
||||
```
|
||||
|
||||
but not
|
||||
|
||||
```
|
||||
test/functional/test_runner.py wallet*
|
||||
```
|
||||
|
||||
Combinations of wildcards can be passed:
|
||||
|
||||
```
|
||||
test/functional/test_runner.py ./test/functional/tool* test/functional/mempool*
|
||||
test_runner.py tool* mempool*
|
||||
```
|
||||
|
||||
Run the regression test suite with:
|
||||
|
||||
```
|
||||
test/functional/test_runner.py
|
||||
```
|
||||
|
||||
Run all possible tests with
|
||||
|
||||
```
|
||||
test/functional/test_runner.py --extended
|
||||
```
|
||||
|
||||
In order to run backwards compatibility tests, download the previous node binaries:
|
||||
|
||||
```
|
||||
test/get_previous_releases.py -b v0.20.1 v0.19.1 v0.18.1 v0.17.2 v0.16.3 v0.15.2
|
||||
```
|
||||
|
||||
By default, up to 4 tests will be run in parallel by test_runner. To specify
|
||||
how many jobs to run, append `--jobs=n`
|
||||
|
||||
The individual tests and the test_runner harness have many command-line
|
||||
options. Run `test/functional/test_runner.py -h` to see them all.
|
||||
|
||||
#### Troubleshooting and debugging test failures
|
||||
|
||||
##### Resource contention
|
||||
|
||||
The P2P and RPC ports used by the digibyted nodes-under-test are chosen to make
|
||||
conflicts with other processes unlikely. However, if there is another digibyted
|
||||
process running on the system (perhaps from a previous test which hasn't successfully
|
||||
killed all its digibyted nodes), then there may be a port conflict which will
|
||||
cause the test to fail. It is recommended that you run the tests on a system
|
||||
where no other digibyted processes are running.
|
||||
|
||||
On linux, the test framework will warn if there is another
|
||||
digibyted process running when the tests are started.
|
||||
|
||||
If there are zombie digibyted processes after test failure, you can kill them
|
||||
by running the following commands. **Note that these commands will kill all
|
||||
digibyted processes running on the system, so should not be used if any non-test
|
||||
digibyted processes are being run.**
|
||||
|
||||
```bash
|
||||
killall digibyted
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```bash
|
||||
pkill -9 digibyted
|
||||
```
|
||||
|
||||
|
||||
##### Data directory cache
|
||||
|
||||
A pre-mined blockchain with 200 blocks is generated the first time a
|
||||
functional test is run and is stored in test/cache. This speeds up
|
||||
test startup times since new blockchains don't need to be generated for
|
||||
each test. However, the cache may get into a bad state, in which case
|
||||
tests will fail. If this happens, remove the cache directory (and make
|
||||
sure digibyted processes are stopped as above):
|
||||
|
||||
```bash
|
||||
rm -rf test/cache
|
||||
killall digibyted
|
||||
```
|
||||
|
||||
##### Test logging
|
||||
|
||||
The tests contain logging at five different levels (DEBUG, INFO, WARNING, ERROR
|
||||
and CRITICAL). From within your functional tests you can log to these different
|
||||
levels using the logger included in the test_framework, e.g.
|
||||
`self.log.debug(object)`. By default:
|
||||
|
||||
- when run through the test_runner harness, *all* logs are written to
|
||||
`test_framework.log` and no logs are output to the console.
|
||||
- when run directly, *all* logs are written to `test_framework.log` and INFO
|
||||
level and above are output to the console.
|
||||
- when run by [our CI (Continuous Integration)](/ci/README.md), no logs are output to the console. However, if a test
|
||||
fails, the `test_framework.log` and digibyted `debug.log`s will all be dumped
|
||||
to the console to help troubleshooting.
|
||||
|
||||
These log files can be located under the test data directory (which is always
|
||||
printed in the first line of test output):
|
||||
- `<test data directory>/test_framework.log`
|
||||
- `<test data directory>/node<node number>/regtest/debug.log`.
|
||||
|
||||
The node number identifies the relevant test node, starting from `node0`, which
|
||||
corresponds to its position in the nodes list of the specific test,
|
||||
e.g. `self.nodes[0]`.
|
||||
|
||||
To change the level of logs output to the console, use the `-l` command line
|
||||
argument.
|
||||
|
||||
`test_framework.log` and digibyted `debug.log`s can be combined into a single
|
||||
aggregate log by running the `combine_logs.py` script. The output can be plain
|
||||
text, colorized text or html. For example:
|
||||
|
||||
```
|
||||
test/functional/combine_logs.py -c <test data directory> | less -r
|
||||
```
|
||||
|
||||
will pipe the colorized logs from the test into less.
|
||||
|
||||
Use `--tracerpc` to trace out all the RPC calls and responses to the console. For
|
||||
some tests (eg any that use `submitblock` to submit a full block over RPC),
|
||||
this can result in a lot of screen output.
|
||||
|
||||
By default, the test data directory will be deleted after a successful run.
|
||||
Use `--nocleanup` to leave the test data directory intact. The test data
|
||||
directory is never deleted after a failed test.
|
||||
|
||||
##### Attaching a debugger
|
||||
|
||||
A python debugger can be attached to tests at any point. Just add the line:
|
||||
|
||||
```py
|
||||
import pdb; pdb.set_trace()
|
||||
```
|
||||
|
||||
anywhere in the test. You will then be able to inspect variables, as well as
|
||||
call methods that interact with the digibyted nodes-under-test.
|
||||
|
||||
If further introspection of the digibyted instances themselves becomes
|
||||
necessary, this can be accomplished by first setting a pdb breakpoint
|
||||
at an appropriate location, running the test to that point, then using
|
||||
`gdb` (or `lldb` on macOS) to attach to the process and debug.
|
||||
|
||||
For instance, to attach to `self.node[1]` during a run you can get
|
||||
the pid of the node within `pdb`.
|
||||
|
||||
```
|
||||
(pdb) self.node[1].process.pid
|
||||
```
|
||||
|
||||
Alternatively, you can find the pid by inspecting the temp folder for the specific test
|
||||
you are running. The path to that folder is printed at the beginning of every
|
||||
test run:
|
||||
|
||||
```bash
|
||||
2017-06-27 14:13:56.686000 TestFramework (INFO): Initializing test directory /tmp/user/1000/testo9vsdjo3
|
||||
```
|
||||
|
||||
Use the path to find the pid file in the temp folder:
|
||||
|
||||
```bash
|
||||
cat /tmp/user/1000/testo9vsdjo3/node1/regtest/digibyted.pid
|
||||
```
|
||||
|
||||
Then you can use the pid to start `gdb`:
|
||||
|
||||
```bash
|
||||
gdb /home/example/digibyted <pid>
|
||||
```
|
||||
|
||||
Note: gdb attach step may require ptrace_scope to be modified, or `sudo` preceding the `gdb`.
|
||||
See this link for considerations: https://www.kernel.org/doc/Documentation/security/Yama.txt
|
||||
|
||||
Often while debugging rpc calls from functional tests, the test might reach timeout before
|
||||
process can return a response. Use `--timeout-factor 0` to disable all rpc timeouts for that partcular
|
||||
functional test. Ex: `test/functional/wallet_hd.py --timeout-factor 0`.
|
||||
|
||||
##### Profiling
|
||||
|
||||
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).
|
||||
|
||||
To generate a profile during test suite runs, use the `--perf` flag.
|
||||
|
||||
To see render the output to text, run
|
||||
|
||||
```sh
|
||||
perf report -i /path/to/datadir/send-big-msgs.perf.data.xxxx --stdio | c++filt | less
|
||||
```
|
||||
|
||||
For ways to generate more granular profiles, see the README in
|
||||
[test/functional](/test/functional).
|
||||
|
||||
### Util tests
|
||||
|
||||
Util tests can be run locally by running `test/util/digibyte-util-test.py`.
|
||||
Use the `-v` option for verbose output.
|
||||
|
||||
### Lint tests
|
||||
|
||||
#### Dependencies
|
||||
|
||||
| Lint test | Dependency | Version [used by CI](../ci/lint/04_install.sh) | Installation
|
||||
|-----------|:----------:|:-------------------------------------------:|--------------
|
||||
| [`lint-python.sh`](lint/lint-python.sh) | [flake8](https://gitlab.com/pycqa/flake8) | [3.8.3](https://github.com/digibyte/digibyte/pull/19348) | `pip3 install flake8==3.8.3`
|
||||
| [`lint-python.sh`](lint/lint-python.sh) | [mypy](https://github.com/python/mypy) | [0.781](https://github.com/digibyte/digibyte/pull/19348) | `pip3 install mypy==0.781`
|
||||
| [`lint-shell.sh`](lint/lint-shell.sh) | [ShellCheck](https://github.com/koalaman/shellcheck) | [0.7.2](https://github.com/digibyte/digibyte/pull/21749) | [details...](https://github.com/koalaman/shellcheck#installing)
|
||||
| [`lint-shell.sh`](lint/lint-shell.sh) | [yq](https://github.com/kislyuk/yq) | default | `pip3 install yq`
|
||||
| [`lint-spelling.sh`](lint/lint-spelling.sh) | [codespell](https://github.com/codespell-project/codespell) | [2.0.0](https://github.com/digibyte/digibyte/pull/20817) | `pip3 install codespell==2.0.0`
|
||||
|
||||
Please be aware that on Linux distributions all dependencies are usually available as packages, but could be outdated.
|
||||
|
||||
#### Running the tests
|
||||
|
||||
Individual tests can be run by directly calling the test script, e.g.:
|
||||
|
||||
```
|
||||
test/lint/lint-files.sh
|
||||
```
|
||||
|
||||
You can run all the shell-based lint tests by running:
|
||||
|
||||
```
|
||||
test/lint/lint-all.sh
|
||||
```
|
||||
|
||||
# Writing functional tests
|
||||
|
||||
You are encouraged to write functional tests for new or existing features.
|
||||
Further information about the functional test framework and individual
|
||||
tests is found in [test/functional](/test/functional).
|
||||
27
test/config.ini.in
Normal file
27
test/config.ini.in
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# Copyright (c) 2009-2020 The Bitcoin Core developers
|
||||
# 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.
|
||||
|
||||
# These environment variables are set by the build process and read by
|
||||
# test/functional/test_runner.py and test/util/digibyte-util-test.py
|
||||
|
||||
[environment]
|
||||
PACKAGE_NAME=@PACKAGE_NAME@
|
||||
PACKAGE_BUGREPORT=@PACKAGE_BUGREPORT@
|
||||
SRCDIR=@abs_top_srcdir@
|
||||
BUILDDIR=@abs_top_builddir@
|
||||
EXEEXT=@EXEEXT@
|
||||
RPCAUTH=@abs_top_srcdir@/share/rpcauth/rpcauth.py
|
||||
|
||||
[components]
|
||||
# Which components are enabled. These are commented out by `configure` if they were disabled when running config.
|
||||
@ENABLE_WALLET_TRUE@ENABLE_WALLET=true
|
||||
@USE_SQLITE_TRUE@USE_SQLITE=true
|
||||
@USE_BDB_TRUE@USE_BDB=true
|
||||
@BUILD_DIGIBYTE_CLI_TRUE@ENABLE_CLI=true
|
||||
@BUILD_DIGIBYTE_WALLET_TRUE@ENABLE_WALLET_TOOL=true
|
||||
@BUILD_DIGIBYTED_TRUE@ENABLE_DIGIBYTED=true
|
||||
@ENABLE_FUZZ_TRUE@ENABLE_FUZZ=true
|
||||
@ENABLE_ZMQ_TRUE@ENABLE_ZMQ=true
|
||||
@ENABLE_EXTERNAL_SIGNER_TRUE@ENABLE_EXTERNAL_SIGNER=true
|
||||
2
test/functional/.gitignore
vendored
Normal file
2
test/functional/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
*.pyc
|
||||
cache
|
||||
192
test/functional/README.md
Normal file
192
test/functional/README.md
Normal 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
|
||||
25
test/functional/bitcoin_cli.py
Normal file
25
test/functional/bitcoin_cli.py
Normal 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
201
test/functional/combine_logs.py
Executable 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()
|
||||
40
test/functional/combined_log_template.html
Normal file
40
test/functional/combined_log_template.html
Normal 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
27
test/functional/create_cache.py
Executable 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()
|
||||
0
test/functional/data/__init__.py
Normal file
0
test/functional/data/__init__.py
Normal file
548
test/functional/data/blockheader_testnet3.hex
Normal file
548
test/functional/data/blockheader_testnet3.hex
Normal 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
|
||||
283
test/functional/data/invalid_txs.py
Normal file
283
test/functional/data/invalid_txs.py
Normal 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__()
|
||||
58
test/functional/data/rpc_bip67.json
Normal file
58
test/functional/data/rpc_bip67.json
Normal 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"
|
||||
}
|
||||
]
|
||||
86
test/functional/data/rpc_decodescript.json
Normal file
86
test/functional/data/rpc_decodescript.json
Normal 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"
|
||||
}
|
||||
]
|
||||
]
|
||||
222
test/functional/data/rpc_getblockstats.json
Normal file
222
test/functional/data/rpc_getblockstats.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
||||
139
test/functional/data/rpc_psbt.json
Normal file
139
test/functional/data/rpc_psbt.json
Normal 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
25
test/functional/digibyte_cli.py
Executable 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
218
test/functional/example_test.py
Executable 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()
|
||||
50
test/functional/feature_abortnode.py
Executable file
50
test/functional/feature_abortnode.py
Executable 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()
|
||||
83
test/functional/feature_anchors.py
Executable file
83
test/functional/feature_anchors.py
Executable 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
113
test/functional/feature_asmap.py
Executable 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()
|
||||
185
test/functional/feature_assumevalid.py
Executable file
185
test/functional/feature_assumevalid.py
Executable 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()
|
||||
424
test/functional/feature_backwards_compatibility.py
Executable file
424
test/functional/feature_backwards_compatibility.py
Executable 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()
|
||||
95
test/functional/feature_bind_extra.py
Executable file
95
test/functional/feature_bind_extra.py
Executable 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()
|
||||
428
test/functional/feature_bip68_sequence.py
Executable file
428
test/functional/feature_bip68_sequence.py
Executable 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()
|
||||
283
test/functional/feature_bip9_softforks.py
Executable file
283
test/functional/feature_bip9_softforks.py
Executable 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
1459
test/functional/feature_block.py
Executable file
File diff suppressed because it is too large
Load diff
84
test/functional/feature_blockfilterindex_prune.py
Executable file
84
test/functional/feature_blockfilterindex_prune.py
Executable 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()
|
||||
38
test/functional/feature_blocksdir.py
Executable file
38
test/functional/feature_blocksdir.py
Executable 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
199
test/functional/feature_cltv.py
Executable 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()
|
||||
316
test/functional/feature_coinstatsindex.py
Executable file
316
test/functional/feature_coinstatsindex.py
Executable 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()
|
||||
282
test/functional/feature_config_args.py
Executable file
282
test/functional/feature_config_args.py
Executable 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()
|
||||
488
test/functional/feature_csv_activation.py
Executable file
488
test/functional/feature_csv_activation.py
Executable 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()
|
||||
289
test/functional/feature_dbcrash.py
Executable file
289
test/functional/feature_dbcrash.py
Executable 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
152
test/functional/feature_dersig.py
Executable 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()
|
||||
290
test/functional/feature_fee_estimation.py
Executable file
290
test/functional/feature_fee_estimation.py
Executable 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()
|
||||
49
test/functional/feature_filelock.py
Executable file
49
test/functional/feature_filelock.py
Executable 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
62
test/functional/feature_help.py
Executable 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()
|
||||
90
test/functional/feature_includeconf.py
Executable file
90
test/functional/feature_includeconf.py
Executable 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()
|
||||
84
test/functional/feature_loadblock.py
Executable file
84
test/functional/feature_loadblock.py
Executable 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()
|
||||
74
test/functional/feature_logging.py
Executable file
74
test/functional/feature_logging.py
Executable 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()
|
||||
165
test/functional/feature_maxuploadtarget.py
Executable file
165
test/functional/feature_maxuploadtarget.py
Executable 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()
|
||||
89
test/functional/feature_minchainwork.py
Executable file
89
test/functional/feature_minchainwork.py
Executable 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()
|
||||
188
test/functional/feature_notifications.py
Executable file
188
test/functional/feature_notifications.py
Executable 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()
|
||||
139
test/functional/feature_nulldummy.py
Executable file
139
test/functional/feature_nulldummy.py
Executable 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
259
test/functional/feature_proxy.py
Executable 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()
|
||||
479
test/functional/feature_pruning.py
Executable file
479
test/functional/feature_pruning.py
Executable 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
646
test/functional/feature_rbf.py
Executable 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()
|
||||
37
test/functional/feature_reindex.py
Executable file
37
test/functional/feature_reindex.py
Executable 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
683
test/functional/feature_segwit.py
Executable 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()
|
||||
94
test/functional/feature_settings.py
Executable file
94
test/functional/feature_settings.py
Executable 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()
|
||||
35
test/functional/feature_shutdown.py
Executable file
35
test/functional/feature_shutdown.py
Executable 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()
|
||||
77
test/functional/feature_signet.py
Executable file
77
test/functional/feature_signet.py
Executable 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
1515
test/functional/feature_taproot.py
Executable file
File diff suppressed because it is too large
Load diff
40
test/functional/feature_uacomment.py
Executable file
40
test/functional/feature_uacomment.py
Executable 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()
|
||||
80
test/functional/feature_utxo_set_hash.py
Executable file
80
test/functional/feature_utxo_set_hash.py
Executable 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()
|
||||
107
test/functional/feature_versionbits_warning.py
Executable file
107
test/functional/feature_versionbits_warning.py
Executable 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
59
test/functional/forknotify.py
Executable 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()
|
||||
328
test/functional/interface_digibyte_cli.py
Executable file
328
test/functional/interface_digibyte_cli.py
Executable 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
109
test/functional/interface_http.py
Executable 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
328
test/functional/interface_rest.py
Executable 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
103
test/functional/interface_rpc.py
Executable 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
571
test/functional/interface_zmq.py
Executable 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
348
test/functional/mempool_accept.py
Executable 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()
|
||||
116
test/functional/mempool_accept_wtxid.py
Executable file
116
test/functional/mempool_accept_wtxid.py
Executable 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()
|
||||
84
test/functional/mempool_compatibility.py
Executable file
84
test/functional/mempool_compatibility.py
Executable 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
111
test/functional/mempool_expiry.py
Executable 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()
|
||||
92
test/functional/mempool_limit.py
Executable file
92
test/functional/mempool_limit.py
Executable 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()
|
||||
82
test/functional/mempool_package_onemore.py
Executable file
82
test/functional/mempool_package_onemore.py
Executable 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()
|
||||
339
test/functional/mempool_packages.py
Executable file
339
test/functional/mempool_packages.py
Executable 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()
|
||||
184
test/functional/mempool_persist.py
Executable file
184
test/functional/mempool_persist.py
Executable 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
116
test/functional/mempool_reorg.py
Executable 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()
|
||||
57
test/functional/mempool_resurrect.py
Executable file
57
test/functional/mempool_resurrect.py
Executable 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()
|
||||
55
test/functional/mempool_spend_coinbase.py
Executable file
55
test/functional/mempool_spend_coinbase.py
Executable 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()
|
||||
117
test/functional/mempool_unbroadcast.py
Executable file
117
test/functional/mempool_unbroadcast.py
Executable 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()
|
||||
129
test/functional/mempool_updatefromblock.py
Executable file
129
test/functional/mempool_updatefromblock.py
Executable 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
93
test/functional/minchainwork.py
Executable 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
286
test/functional/mining_basic.py
Executable 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()
|
||||
81
test/functional/mining_getblocktemplate_longpoll.py
Executable file
81
test/functional/mining_getblocktemplate_longpoll.py
Executable 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()
|
||||
157
test/functional/mining_prioritisetransaction.py
Executable file
157
test/functional/mining_prioritisetransaction.py
Executable 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()
|
||||
66
test/functional/mocks/invalid_signer.py
Executable file
66
test/functional/mocks/invalid_signer.py
Executable 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
102
test/functional/mocks/signer.py
Executable 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
90
test/functional/multiwallet.py
Executable 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()
|
||||
329
test/functional/p2p-acceptblock.py
Executable file
329
test/functional/p2p-acceptblock.py
Executable 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
169
test/functional/p2p-leaktests.py
Executable 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
84
test/functional/p2p-timeouts.py
Executable 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()
|
||||
128
test/functional/p2p-versionbits-warning.py
Executable file
128
test/functional/p2p-versionbits-warning.py
Executable 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()
|
||||
96
test/functional/p2p_add_connections.py
Executable file
96
test/functional/p2p_add_connections.py
Executable 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
301
test/functional/p2p_addr_relay.py
Executable 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()
|
||||
86
test/functional/p2p_addrfetch.py
Executable file
86
test/functional/p2p_addrfetch.py
Executable 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()
|
||||
87
test/functional/p2p_addrv2_relay.py
Executable file
87
test/functional/p2p_addrv2_relay.py
Executable 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()
|
||||
257
test/functional/p2p_blockfilters.py
Executable file
257
test/functional/p2p_blockfilters.py
Executable 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
120
test/functional/p2p_blocksonly.py
Executable 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()
|
||||
908
test/functional/p2p_compactblocks.py
Executable file
908
test/functional/p2p_compactblocks.py
Executable 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()
|
||||
95
test/functional/p2p_compactblocks_hb.py
Executable file
95
test/functional/p2p_compactblocks_hb.py
Executable 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
149
test/functional/p2p_dandelion.py
Executable 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()
|
||||
114
test/functional/p2p_disconnect_ban.py
Executable file
114
test/functional/p2p_disconnect_ban.py
Executable 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()
|
||||
90
test/functional/p2p_dos_header_tree.py
Executable file
90
test/functional/p2p_dos_header_tree.py
Executable 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
137
test/functional/p2p_eviction.py
Executable 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
140
test/functional/p2p_feefilter.py
Executable 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
242
test/functional/p2p_filter.py
Executable 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()
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue