Compare commits
12 Commits
d05302d450
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| 6d282db216 | |||
| c5fde12485 | |||
| e2e10f6ef8 | |||
| af7d9e2300 | |||
| 4cc7e0491a | |||
| 5e0a706839 | |||
| 60d66022f6 | |||
| 3b2aa866aa | |||
| 2d6359ea74 | |||
| fa16e740b6 | |||
| 5f40c8ede0 | |||
| b3e81f1eda |
@@ -2,7 +2,7 @@ dnl require autoconf 2.60 (AS_ECHO/AS_ECHO_N)
|
||||
AC_PREREQ([2.60])
|
||||
define(_CLIENT_VERSION_MAJOR, 1)
|
||||
dnl Must be kept in sync with src/clientversion.h , ugh!
|
||||
define(_CLIENT_VERSION_MINOR, 2)
|
||||
define(_CLIENT_VERSION_MINOR, 3)
|
||||
define(_CLIENT_VERSION_REVISION, 0)
|
||||
define(_CLIENT_VERSION_BUILD, 50)
|
||||
define(_ZC_BUILD_VAL, m4_if(m4_eval(_CLIENT_VERSION_BUILD < 25), 1, m4_incr(_CLIENT_VERSION_BUILD), m4_eval(_CLIENT_VERSION_BUILD < 50), 1, m4_eval(_CLIENT_VERSION_BUILD - 24), m4_eval(_CLIENT_VERSION_BUILD == 50), 1, , m4_eval(_CLIENT_VERSION_BUILD - 50)))
|
||||
|
||||
@@ -1,3 +1,31 @@
|
||||
dragonx (1.3.0) stable; urgency=medium
|
||||
|
||||
* RandomX stratum mining support: the daemon can serve stratum clients
|
||||
directly, with a reference miner behind the stratummine RPC for testing.
|
||||
* Stratum fixes found by audit: a malformed 63-character job_id no longer
|
||||
aborts the daemon; each block is paid to the miner that actually found it
|
||||
rather than to whichever client asked for work first; and a low-difficulty
|
||||
share is now rejected before it costs a RandomX hash.
|
||||
* Fix -connect never dialing its targets, so a node pinned to specific peers
|
||||
reaches them instead of silently falling through to peer discovery.
|
||||
* Remove roughly 7,100 lines of dead code, including the CBOPRET price
|
||||
validation in the coinbase check, whose guard could not be true on any
|
||||
chain, and adaptive-PoW difficulty logic that DragonX does not enable.
|
||||
* Honor a command-line -rpcpassword across restarts, and fix a misspelled
|
||||
-rpcusername key that silently discarded the configured RPC user.
|
||||
* Default -checkpoints off on regtest so an isolated node leaves initial
|
||||
block download, instead of staying in IBD forever and disabling every
|
||||
operation gated on it.
|
||||
* Release cs_main and the mempool lock on the miner's isStake error paths;
|
||||
the leak presented as a permanent stall rather than a slow response.
|
||||
* Windows cross-build: the mingw target links and finds librustzcash, and a
|
||||
fs::path::c_str() regression in init no longer breaks the build.
|
||||
* Stamp container builds with the real version instead of "-unk".
|
||||
* Repair the qa/rpc-tests harness far enough to start a DragonX node and
|
||||
build the shared test chain; it previously started mainnet nodes.
|
||||
|
||||
-- DragonX Developers <dev@dragonx.is> Mon, 31 Aug 2026 03:46:19 +0000
|
||||
|
||||
dragonx (1.2.0) stable; urgency=medium
|
||||
|
||||
* Auto-shield matured coinbase into a wallet-owned Sapling address on a block
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.1.
|
||||
.TH DRAGONX-CLI "1" "August 2026" "dragonx-cli v1.2.0" "User Commands"
|
||||
.TH DRAGONX-CLI "1" "August 2026" "dragonx-cli v1.3.0" "User Commands"
|
||||
.SH NAME
|
||||
dragonx-cli \- manual page for dragonx-cli v1.2.0
|
||||
dragonx-cli \- manual page for dragonx-cli v1.3.0
|
||||
.SH DESCRIPTION
|
||||
DragonX RPC client version v1.2.0
|
||||
DragonX RPC client version v1.3.0\-af7d9e230
|
||||
.PP
|
||||
In order to ensure you are adequately protecting your privacy when using
|
||||
DragonX, please see <https://dragonx.is/security/>.
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.1.
|
||||
.TH DRAGONX-TX "1" "August 2026" "dragonx-tx v1.2.0" "User Commands"
|
||||
.TH DRAGONX-TX "1" "August 2026" "dragonx-tx v1.3.0" "User Commands"
|
||||
.SH NAME
|
||||
dragonx-tx \- manual page for dragonx-tx v1.2.0
|
||||
dragonx-tx \- manual page for dragonx-tx v1.3.0
|
||||
.SH DESCRIPTION
|
||||
hush\-tx utility version v1.2.0
|
||||
hush\-tx utility version v1.3.0\-af7d9e230
|
||||
.SS "Usage:"
|
||||
.TP
|
||||
hush\-tx [options] <hex\-tx> [commands]
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.1.
|
||||
.TH DRAGONXD "1" "August 2026" "dragonxd v1.2.0" "User Commands"
|
||||
.TH DRAGONXD "1" "August 2026" "dragonxd v1.3.0" "User Commands"
|
||||
.SH NAME
|
||||
dragonxd \- manual page for dragonxd v1.2.0
|
||||
dragonxd \- manual page for dragonxd v1.3.0
|
||||
.SH DESCRIPTION
|
||||
DragonX Daemon version v1.2.0
|
||||
DragonX Daemon version v1.3.0\-af7d9e230
|
||||
.PP
|
||||
In order to ensure you are adequately protecting your privacy when using
|
||||
DragonX, please see <https://dragonx.is/security/>.
|
||||
@@ -452,6 +452,13 @@ or create a wallet z\-address). Must be spendable by this wallet.
|
||||
Fee in puposhis for automatic coinbase\-shielding transactions (default:
|
||||
10000)
|
||||
.HP
|
||||
\fB\-sietch\-min\-zouts=\fR<n>
|
||||
.IP
|
||||
Minimum number of shielded (Sapling) outputs Sietch adds to each
|
||||
z_sendmany transaction as decoys, strengthening
|
||||
amount/linkability privacy. Higher values add privacy at the cost
|
||||
of larger transactions (default: 7, clamped to the range 3\-50)
|
||||
.HP
|
||||
\fB\-autoshieldminutxos\fR
|
||||
.IP
|
||||
Only auto\-shield once at least this many matured coinbase UTXOs exist
|
||||
@@ -710,6 +717,11 @@ Stratum server options:
|
||||
.IP
|
||||
Enable stratum server (default: off)
|
||||
.HP
|
||||
\fB\-stratumtarget=\fR<hex>
|
||||
.IP
|
||||
Pool share target (64\-hex, big\-endian; larger = easier). Default is the
|
||||
diff\-1 target. Useful for solo/low\-difficulty mining.
|
||||
.HP
|
||||
\fB\-stratumaddress=\fR<address>
|
||||
.IP
|
||||
Mining address to use when special address of 'x' is sent by miner
|
||||
|
||||
@@ -8,32 +8,32 @@ It is best to keep doc/relnotes/README.md up to date as changes and bug fixes ar
|
||||
|
||||
## Branch model
|
||||
|
||||
Development happens on the `dev` branch. Releases are cut on the default branch, `dragonx`. There is no `master` branch. Code changes should land on `dev` first and undergo testing before being merged into `dragonx`.
|
||||
Development happens on the `dev` branch. Releases are cut on the default branch, `master`. Code changes should land on `dev` first and undergo testing before being merged into `master`.
|
||||
|
||||
## Check for changes on dragonx that should be on dev
|
||||
## Check for changes on master that should be on dev
|
||||
|
||||
Occasionally trivial changes are made directly on the `dragonx` branch, such as documentation changes. In theory, no code changes should happen on `dragonx` without being on `dev` first, but it's better to be safe than sorry. We want the `dev` branch which undergoes testing to be as close as possible to what the `dragonx` branch will become, so we don't want to merge `dev` into `dragonx` and just assume everything works. So it's best to merge the `dragonx` branch into `dev` just before merging the `dev` branch into `dragonx`.
|
||||
Occasionally trivial changes are made directly on the `master` branch, such as documentation changes. In theory, no code changes should happen on `master` without being on `dev` first, but it's better to be safe than sorry. We want the `dev` branch which undergoes testing to be as close as possible to what the `master` branch will become, so we don't want to merge `dev` into `master` and just assume everything works. So it's best to merge the `master` branch into `dev` just before merging the `dev` branch into `master`.
|
||||
|
||||
To check if the `dragonx` branch has any changes that the `dev` branch does not:
|
||||
To check if the `master` branch has any changes that the `dev` branch does not:
|
||||
|
||||
```
|
||||
# this assumes you are working with https://git.dragonx.is/DragonX/dragonx as your remote
|
||||
git checkout dev
|
||||
git pull # make sure dev is up to date
|
||||
git checkout dragonx
|
||||
git pull # make sure dragonx is up to date
|
||||
git diff dev...dragonx # look at the set of changes which exist in dragonx but not dev
|
||||
git checkout master
|
||||
git pull # make sure master is up to date
|
||||
git diff dev...master # look at the set of changes which exist in master but not dev
|
||||
```
|
||||
|
||||
If the last command has no output, congrats, there is nothing to do. If the last command has output, then you should merge `dragonx` into `dev`:
|
||||
If the last command has no output, congrats, there is nothing to do. If the last command has output, then you should merge `master` into `dev`:
|
||||
|
||||
```
|
||||
git checkout dev
|
||||
git merge dragonx
|
||||
git merge master
|
||||
git push origin dev
|
||||
```
|
||||
|
||||
Use the `--no-ff` flag when merging `dev` into `dragonx` for a release (see below). The `--no-ff` flag makes sure to make a merge commit, no matter what, even if a "fast forward" could be done. For those in the future looking back, it's much better to see evidence of when branches were merged.
|
||||
Use the `--no-ff` flag when merging `dev` into `master` for a release (see below). The `--no-ff` flag makes sure to make a merge commit, no matter what, even if a "fast forward" could be done. For those in the future looking back, it's much better to see evidence of when branches were merged.
|
||||
|
||||
|
||||
### Git Issues
|
||||
@@ -66,6 +66,7 @@ Install deps on Linux:
|
||||
- Run "make seeds"
|
||||
- Commit the result
|
||||
- Update version in configure.ac and src/clientversion.h to update the dragonxd version
|
||||
- **The new version MUST be higher than every version already tagged**, including tags that were never built or published. Check with `git tag -l --sort=-v:refname | head`. Two trees stamped with the same `CLIENT_VERSION` are indistinguishable on the wire, in `getnetworkinfo`, and to the wallet's in-app updater — and a published archive that does not match its tag destroys the only provenance check users have.
|
||||
- In src/clientversion.h you update `CLIENT_VERSION_*` variables. Usually you will just update `CLIENT_VERSION_REVISION`
|
||||
- If there is a consensus change, it may be a good idea to update `CLIENT_VERSION_MINOR` or `CLIENT_VERSION_MAJOR`
|
||||
- To make a pre-release "beta" you can modify `CLIENT_VERSION_BUILD` but that is rarely done.
|
||||
@@ -97,17 +98,27 @@ Install deps on Linux:
|
||||
- Try to generate checkpoints as close to the release as possible, so you can have a recent block height be protected.
|
||||
- For instance, don't update checkpoints and then do a release a month later. You can always update checkpoint data again or multiple times
|
||||
- Update doc/relnotes/README.md
|
||||
- To get the stats of file changes: `git diff --stat dragonx...dev`
|
||||
- To get the stats of file changes: `git diff --stat master...dev`
|
||||
- Do a fresh clone and fresh sync with new checkpoints
|
||||
- Stop node, wait 20 minutes, and then do a partial sync with new checkpoints
|
||||
- Merge dev into dragonx: `git checkout dev && git pull && git checkout dragonx && git pull && git merge --no-ff dev && git push`
|
||||
- Merge dev into master: `git checkout dev && git pull && git checkout master && git pull && git merge --no-ff dev && git push`
|
||||
- The above command makes sure that your local dev branch is up to date before doing anything
|
||||
- The above command will not merge if "git pull" creates a merge conflict
|
||||
- The above command will not push if there is a problem with merging dev
|
||||
- Make Gitea release with git tag from the dragonx branch (make sure to merge dev in first)
|
||||
- Make Gitea release with git tag from the master branch (make sure to merge dev in first)
|
||||
- Make sure git tag starts with a `v` such as `v1.0.3`
|
||||
- Use util/gen-linux-binary-release.sh to make a Linux release binary
|
||||
- Upload Linux binary to Gitea release and add SHA256 sum
|
||||
- **The tag MUST be annotated** (`git tag -a v1.3.0 -m 'DragonX v1.3.0'`), not lightweight. `util/genbuild.sh` calls `git describe` *without* `--tags`, which only ever sees annotated tags — a lightweight tag makes the build stamp itself `v<older-tag>-<sha>` instead of the release version. v1.0.0 through v1.0.3 are lightweight, which is why their builds are labelled that way.
|
||||
- Verify before building: `git describe` must print exactly the tag, with no `-<n>-g<sha>` suffix.
|
||||
- Use `./build.sh` (container-based, see doc/build-containers.md) or util/gen-linux-binary-release.sh to make a Linux release binary
|
||||
- **Sign every archive and publish the signatures.** This step is mandatory and was missing from this document until v1.3.0 — its absence is why v1.1.0 and v1.2.0 were tagged but never became installable releases.
|
||||
- The wallet's in-app daemon updater pins an ed25519 public key in `ObsidianDragon/src/util/daemon_updater.h` and sets `kDaemonRequireSignature = true`. **An update is refused outright unless a valid `<archive>.sig` is published beside the archive.** No signature means every existing user silently stays on their old daemon.
|
||||
- Sign with `ObsidianDragon/scripts/sign-daemon-release.sh`:
|
||||
- `scripts/sign-daemon-release.sh sign <secret.key> <archive>...` produces `<archive>.sig` (base64 of a detached 64-byte ed25519 signature over the exact archive bytes)
|
||||
- or `scripts/sign-daemon-release.sh release <secret.key> <version>` to zip, sign, and print the checksum table in one step
|
||||
- Keep the secret key offline, mode 600. The matching base64 public key must already be pinned in `kDaemonSignaturePublicKeyBase64`.
|
||||
- Upload each Linux binary archive **and its `.sig`** to the Gitea release
|
||||
- **Paste the SHA-256 checksum table into the release body** as markdown rows of the form `| <archive>.zip | `<sha256hex>` |`. The updater parses this table and will not install an archive that is absent from it.
|
||||
- Confirm the release is actually consumable before announcing it: the updater looks for an asset whose name contains `"-" + platformToken + ".zip"` (`linux-amd64`, `macos`, `win64`). An archive named for a distro variant instead of the platform token is invisible to it.
|
||||
- Create an x86 Debian package for the release:
|
||||
- Edit contrib/debian/changelog to add information about the new release
|
||||
- Use `util/build-debian-package.sh` to make an x86 Debian package for the release
|
||||
@@ -115,7 +126,7 @@ Install deps on Linux:
|
||||
- `lintian` is an optional dependency, it's not needed to build the .deb
|
||||
- Upload .deb to Gitea release
|
||||
- Add SHA256 checksum of .deb to release
|
||||
- Use util/build-debian-package-ARM.sh (does this still work?) to make an ARM Debian package for the release
|
||||
- ARM Debian package: `util/build-debian-package-ARM.sh` is referenced here historically but **is not present in the tree**. Skip, or restore the script first.
|
||||
- Upload the debian packages to the Gitea release page, with SHA256 sums
|
||||
|
||||
## Platform-specific notes
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2016-2024 The Hush developers
|
||||
# Copyright (c) 2016 The Zcash developers
|
||||
# Distributed under the GPLv3 software license, see the accompanying
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2016-2024 The Hush developers
|
||||
# Copyright (c) 2014 The Bitcoin Core developers
|
||||
# Distributed under the GPLv3 software license, see the accompanying
|
||||
@@ -52,7 +52,7 @@ class GetBlockTemplateLPTest(BitcoinTestFramework):
|
||||
'''
|
||||
|
||||
def run_test(self):
|
||||
print "Warning: this test will take about 70 seconds in the best case. Be patient."
|
||||
print("Warning: this test will take about 70 seconds in the best case. Be patient.")
|
||||
self.nodes[0].generate(10)
|
||||
templat = self.nodes[0].getblocktemplate()
|
||||
longpollid = templat['longpollid']
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2016-2024 The Hush developers
|
||||
# Copyright (c) 2014 The Bitcoin Core developers
|
||||
# Distributed under the GPLv3 software license, see the accompanying
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.authproxy import JSONRPCException
|
||||
from test_framework.util import initialize_chain_clean, start_node
|
||||
|
||||
from binascii import a2b_hex, b2a_hex
|
||||
from hashlib import sha256
|
||||
@@ -69,14 +70,43 @@ def genmrklroot(leaflist):
|
||||
cur = n
|
||||
return cur[0]
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Sapling v4 transaction layout.
|
||||
#
|
||||
# This test was written against the pre-Overwinter serialization, where a tx
|
||||
# began with a 4-byte nVersion immediately followed by the vin count, so the
|
||||
# first input's prevout hash lived at offset 4+1. DragonX transactions are
|
||||
# Sapling v4: 4-byte header (nVersion | fOverwintered) + 4-byte nVersionGroupId
|
||||
# + vin count, so the prevout hash starts 4 bytes further in. Poking the old
|
||||
# offset corrupts nVersionGroupId and every proposal below just comes back
|
||||
# "Block decode failed" instead of exercising any consensus rule.
|
||||
CB_PREVOUT_OFF = 4+4+1
|
||||
# Likewise the tx no longer ends at nLockTime: nExpiryHeight (4), valueBalance
|
||||
# (8) and the empty vShieldedSpend/vShieldedOutput/vJoinSplit counts (1 each)
|
||||
# trail it, so nLockTime is the 4 bytes at [-19:-15].
|
||||
TX_TAIL_AFTER_LOCKTIME = 4+8+1+1+1
|
||||
|
||||
def tx_seq_off(tx):
|
||||
"""Offset of the first input's nSequence in a Sapling v4 tx."""
|
||||
scriptlen_off = CB_PREVOUT_OFF + 32 + 4 # after prevout hash + prevout.n
|
||||
return scriptlen_off + 1 + tx[scriptlen_off]
|
||||
|
||||
def tx_vout0_value_off(tx):
|
||||
"""Offset of the first output's 8-byte value in a Sapling v4 tx."""
|
||||
return tx_seq_off(tx) + 4 + 1 # after nSequence + vout count
|
||||
|
||||
def template_to_bytes(tmpl, txlist):
|
||||
blkver = pack('<L', tmpl['version'])
|
||||
mrklroot = genmrklroot(list(dblsha(a) for a in txlist))
|
||||
reserved = b'\0'*32
|
||||
# hashFinalSaplingRoot. The all-zeroes placeholder this test used predates
|
||||
# Sapling; a header carrying the wrong root is rejected with
|
||||
# 'bad-sapling-root-in-block', which would sink even Test 11 (valid block).
|
||||
reserved = a2b_hex(tmpl['finalsaplingroothash'])[::-1]
|
||||
timestamp = pack('<L', tmpl['curtime'])
|
||||
nonce = b'\0'*32
|
||||
soln = b'\0'
|
||||
blk = blkver + a2b_hex(tmpl['previousblockhash'])[::-1] + mrklroot + reserved + timestamp + a2b_hex(tmpl['bits'])[::-1] + nonce + soln
|
||||
# bytearray, not bytes: Test 9 mutates one byte of the result in place.
|
||||
blk = bytearray(blkver + a2b_hex(tmpl['previousblockhash'])[::-1] + mrklroot + reserved + timestamp + a2b_hex(tmpl['bits'])[::-1] + nonce + soln)
|
||||
blk += varlenEncode(len(txlist))
|
||||
for tx in txlist:
|
||||
blk += tx
|
||||
@@ -95,6 +125,20 @@ class GetBlockTemplateProposalTest(BitcoinTestFramework):
|
||||
Test block proposals with getblocktemplate.
|
||||
'''
|
||||
|
||||
def setup_chain(self):
|
||||
print("Initializing test directory "+self.options.tmpdir)
|
||||
initialize_chain_clean(self.options.tmpdir, 1)
|
||||
|
||||
def setup_network(self, split=False):
|
||||
# -daaforkheight=0: ContextualCheckBlockHeader only enforces nBits for a
|
||||
# smart chain above daaForkHeight, which defaults to
|
||||
# ASSETCHAINS_RANDOMX_VALIDATION+62000 (millions of blocks) so that a
|
||||
# fresh sync accepts DragonX's historical bad-nBits window. Without this
|
||||
# flag Test 8 (bad bits) is unreachable at regtest heights and the
|
||||
# daemon happily accepts a block with arbitrary nBits.
|
||||
self.nodes = [ start_node(0, self.options.tmpdir, ['-daaforkheight=0']) ]
|
||||
self.is_network_split = False
|
||||
|
||||
def run_test(self):
|
||||
node = self.nodes[0]
|
||||
node.generate(1) # Mine a block to leave initial block download
|
||||
@@ -117,9 +161,9 @@ class GetBlockTemplateProposalTest(BitcoinTestFramework):
|
||||
#txlist[0][4+1+36+1+1] -= 1
|
||||
|
||||
# Test 2: Bad input hash for gen tx
|
||||
txlist[0][4+1] += 1
|
||||
txlist[0][CB_PREVOUT_OFF] += 1
|
||||
assert_template(node, tmpl, txlist, 'bad-cb-missing')
|
||||
txlist[0][4+1] -= 1
|
||||
txlist[0][CB_PREVOUT_OFF] -= 1
|
||||
|
||||
# Test 3: Truncated final tx
|
||||
lastbyte = txlist[-1].pop()
|
||||
@@ -136,14 +180,33 @@ class GetBlockTemplateProposalTest(BitcoinTestFramework):
|
||||
|
||||
# Test 5: Add an invalid tx to the end (non-duplicate)
|
||||
txlist.append(bytearray(txlist[0]))
|
||||
txlist[-1][4+1] = b'\xff'
|
||||
txlist[-1][CB_PREVOUT_OFF] = 0xff
|
||||
# DragonX is a fully private chain (ASSETCHAINS_PRIVATE): a non-coinbase
|
||||
# tx paying a positive amount to a t-addr is killed in CheckTransaction
|
||||
# with 'bad-txns-acprivacy-chain' before ConnectBlock ever looks up its
|
||||
# inputs. Zero the output value -- CheckTransaction exempts zero-value
|
||||
# vouts -- so the tx survives to the missing-input check this case is
|
||||
# actually about.
|
||||
_val_off = tx_vout0_value_off(txlist[-1])
|
||||
txlist[-1][_val_off:_val_off+8] = b'\0'*8
|
||||
assert_template(node, tmpl, txlist, 'bad-txns-inputs-missingorspent')
|
||||
txlist.pop()
|
||||
|
||||
# Test 6: Future tx lock time
|
||||
txlist[0][-4:] = b'\xff\xff\xff\xff'
|
||||
# The server-supplied coinbase carries nSequence=0xffffffff, and
|
||||
# IsFinalTx() short-circuits on all-final inputs, so a future nLockTime
|
||||
# alone leaves the tx final and the block valid. (DragonX's IsFinalTx
|
||||
# also whitelists 0xfffffffe below the Hush hardfork height, which
|
||||
# regtest is.) Make the input genuinely non-final so the future
|
||||
# nLockTime is the thing under test.
|
||||
seq_off = tx_seq_off(txlist[0])
|
||||
realseq = txlist[0][seq_off:seq_off+4]
|
||||
txlist[0][seq_off:seq_off+4] = b'\0\0\0\0'
|
||||
reallocktime = txlist[0][-TX_TAIL_AFTER_LOCKTIME-4:-TX_TAIL_AFTER_LOCKTIME]
|
||||
txlist[0][-TX_TAIL_AFTER_LOCKTIME-4:-TX_TAIL_AFTER_LOCKTIME] = b'\xff\xff\xff\xff'
|
||||
assert_template(node, tmpl, txlist, 'bad-txns-nonfinal')
|
||||
txlist[0][-4:] = b'\0\0\0\0'
|
||||
txlist[0][-TX_TAIL_AFTER_LOCKTIME-4:-TX_TAIL_AFTER_LOCKTIME] = reallocktime
|
||||
txlist[0][seq_off:seq_off+4] = realseq
|
||||
|
||||
# Test 7: Bad tx count
|
||||
txlist.append(b'')
|
||||
@@ -171,7 +234,9 @@ class GetBlockTemplateProposalTest(BitcoinTestFramework):
|
||||
tmpl['curtime'] = 0x7fffffff
|
||||
assert_template(node, tmpl, txlist, 'time-too-new')
|
||||
tmpl['curtime'] = 0
|
||||
assert_template(node, tmpl, txlist, 'time-too-old')
|
||||
# DragonX split Bitcoin's 'time-too-old' into 'time-too-old-median'
|
||||
# (block time <= prev MedianTimePast) and 'time-too-old-prevblock'.
|
||||
assert_template(node, tmpl, txlist, 'time-too-old-median')
|
||||
tmpl['curtime'] = realtime
|
||||
|
||||
# Test 11: Valid block
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2014 The Bitcoin Core developers
|
||||
# Copyright (c) 2016-2024 The Hush developers
|
||||
# Released under the GPLv3
|
||||
@@ -28,7 +28,7 @@ class ReindexTest(BitcoinTestFramework):
|
||||
wait_bitcoinds()
|
||||
self.nodes[0]=start_node(0, self.options.tmpdir, ["-debug", "-reindex", "-checkblockindex=1"])
|
||||
assert_equal(self.nodes[0].getblockcount(), 3)
|
||||
print "Success"
|
||||
print("Success")
|
||||
|
||||
if __name__ == '__main__':
|
||||
ReindexTest().main()
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
# and for constructing a getheaders message
|
||||
#
|
||||
|
||||
from mininode import CBlock, CBlockHeader, CBlockLocator, CTransaction, msg_block, msg_headers, msg_tx
|
||||
from .mininode import CBlock, CBlockHeader, CBlockLocator, CTransaction, msg_block, msg_headers, msg_tx
|
||||
|
||||
import sys
|
||||
import cStringIO
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
# Distributed under the GPLv3 software license, see the accompanying
|
||||
# file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||
# blocktools.py - utilities for manipulating blocks and transactions
|
||||
from mininode import CBlock, CTransaction, CTxIn, CTxOut, COutPoint
|
||||
from script import CScript, OP_0, OP_EQUAL, OP_HASH160
|
||||
from .mininode import CBlock, CTransaction, CTxIn, CTxOut, COutPoint
|
||||
from .script import CScript, OP_0, OP_EQUAL, OP_HASH160
|
||||
|
||||
# Create a block (with regtest difficulty)
|
||||
def create_block(hashprev, coinbase, nTime=None, nBits=None):
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
# Distributed under the GPLv3 software license, see the accompanying
|
||||
# file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||
|
||||
from mininode import CBlock, CTransaction, CInv, NodeConn, NodeConnCB, \
|
||||
from .mininode import CBlock, CTransaction, CInv, NodeConn, NodeConnCB, \
|
||||
msg_inv, msg_getheaders, msg_ping, msg_mempool, mininode_lock, MAX_INV_SZ
|
||||
from blockstore import BlockStore, TxStore
|
||||
from util import p2p_port
|
||||
from .blockstore import BlockStore, TxStore
|
||||
from .util import p2p_port
|
||||
|
||||
import time
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@ import shutil
|
||||
import tempfile
|
||||
import traceback
|
||||
|
||||
from authproxy import JSONRPCException
|
||||
from util import assert_equal, check_json_precision, \
|
||||
from .authproxy import JSONRPCException
|
||||
from .util import assert_equal, check_json_precision, \
|
||||
initialize_chain, initialize_chain_clean, \
|
||||
start_nodes, connect_nodes_bi, stop_nodes, \
|
||||
sync_blocks, sync_mempools, wait_bitcoinds
|
||||
@@ -91,7 +91,7 @@ class BitcoinTestFramework(object):
|
||||
parser.add_option("--noshutdown", dest="noshutdown", default=False, action="store_true",
|
||||
help="Don't stop nodes after the test execution")
|
||||
parser.add_option("--srcdir", dest="srcdir", default="../../src",
|
||||
help="Source directory containing hushd/hush-cli (default: %default)")
|
||||
help="Source directory containing dragonxd/dragonx-cli (default: %default)")
|
||||
parser.add_option("--tmpdir", dest="tmpdir", default=tempfile.mkdtemp(prefix="test"),
|
||||
help="Root directory for datadirs")
|
||||
parser.add_option("--tracerpc", dest="trace_rpc", default=False, action="store_true",
|
||||
|
||||
@@ -18,7 +18,7 @@ import subprocess
|
||||
import time
|
||||
import re
|
||||
|
||||
from authproxy import AuthServiceProxy
|
||||
from .authproxy import AuthServiceProxy
|
||||
|
||||
def p2p_port(n):
|
||||
return 11000 + n + os.getpid()%999
|
||||
@@ -97,8 +97,8 @@ def initialize_datadir(dirname, n):
|
||||
print("Creating dirs %s" % datadir)
|
||||
os.makedirs(datadir)
|
||||
|
||||
print("Writing to " + os.path.join(datadir,"ZZZ.conf"))
|
||||
with open(os.path.join(datadir, "ZZZ.conf"), 'w') as f:
|
||||
print("Writing to " + os.path.join(datadir,"DRAGONX.conf"))
|
||||
with open(os.path.join(datadir, "DRAGONX.conf"), 'w') as f:
|
||||
f.write("regtest=1\n");
|
||||
f.write("txindex=1\n");
|
||||
#f.write("testnode=1\n");
|
||||
@@ -116,7 +116,19 @@ def initialize_datadir(dirname, n):
|
||||
f.write("spentindex=1\n");
|
||||
f.write("timestampindex=1\n");
|
||||
#f.write("zindex=1\n");
|
||||
print("Done writing to %s" % os.path.join(datadir,"ZZZ.conf") )
|
||||
print("Done writing to %s" % os.path.join(datadir,"DRAGONX.conf") )
|
||||
|
||||
# dragonxd refuses to start without an asmap file ("Could not find any asmap file!"),
|
||||
# so every regtest datadir needs one. Link the tree's copy rather than duplicating it.
|
||||
for src in ("../../../asmap.dat", "../../../src/asmap.dat",
|
||||
os.path.expanduser("~/.hush/DRAGONX/asmap.dat")):
|
||||
cand = src if os.path.isabs(src) else os.path.join(os.path.dirname(os.path.abspath(__file__)), src)
|
||||
if os.path.exists(cand):
|
||||
dst = os.path.join(datadir, "asmap.dat")
|
||||
if not os.path.exists(dst):
|
||||
try: os.symlink(os.path.realpath(cand), dst)
|
||||
except OSError: shutil.copyfile(cand, dst)
|
||||
break
|
||||
|
||||
return datadir
|
||||
|
||||
@@ -133,11 +145,23 @@ def initialize_chain(test_dir):
|
||||
# Create cache directories, run hushds:
|
||||
for i in range(4):
|
||||
datadir=initialize_datadir("cache", i)
|
||||
args = [ os.getenv("BITCOIND", "hushd"), "-keypool=1", "-datadir="+datadir, "-discover=0" ]
|
||||
# Same two requirements as start_node(): -regtest must be a command-line flag (the
|
||||
# conf key is ignored, and without it this cache node runs on MAINNET), and -asmap
|
||||
# must be absolute or dragonxd refuses to start.
|
||||
# -connect=<anything> makes init.cpp soft-set -listen=0 ("parameter interaction: -connect
|
||||
# set -> setting -listen=0"), so cache node0 never opened its p2p port and nodes 1-3 could
|
||||
# never sync to it -- initialize_chain() then hung forever in sync_blocks(). Pass -listen
|
||||
# and -bind explicitly (an explicit arg beats SoftSetBoolArg) and keep the loopback bind so
|
||||
# the cache nodes stay off the public network.
|
||||
args = [ os.getenv("BITCOIND", "src/dragonxd"), "-regtest", "-connect=0", "-keypool=1", "-datadir="+datadir, "-discover=0",
|
||||
"-listen=1", "-bind=127.0.0.1", "-dnsseed=0" ]
|
||||
_am = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../../asmap.dat")
|
||||
if os.path.exists(_am):
|
||||
args.append("-asmap=" + os.path.realpath(_am))
|
||||
if i > 0:
|
||||
args.append("-connect=127.0.0.1:"+str(p2p_port(0)))
|
||||
bitcoind_processes[i] = subprocess.Popen(args)
|
||||
cmd = os.getenv("BITCOINCLI", "hush-cli")
|
||||
cmd = os.getenv("BITCOINCLI", "src/dragonx-cli")
|
||||
cmd_args = cmd + " -datadir="+datadir + " -rpcwait getblockcount"
|
||||
if os.getenv("PYTHON_DEBUG", ""):
|
||||
print("initialize_chain: hushd started, calling: " + cmd_args)
|
||||
@@ -180,10 +204,17 @@ def initialize_chain(test_dir):
|
||||
wait_bitcoinds()
|
||||
for i in range(4):
|
||||
print("Cleaning up cache dir files")
|
||||
os.remove(log_filename("cache", i, "debug.log"))
|
||||
os.remove(log_filename("cache", i, "db.log"))
|
||||
os.remove(log_filename("cache", i, "peers.dat"))
|
||||
os.remove(log_filename("cache", i, "fee_estimates.dat"))
|
||||
# log_filename() points at <cache>/node<i>/regtest, but that IS the -datadir we passed;
|
||||
# dragonxd writes its logs/peers.dat one level deeper, into the net-specific
|
||||
# <datadir>/regtest subdir (same datadir-vs-netdir split that forced -asmap to be
|
||||
# absolute in start_node). Try both, and tolerate files a node never created.
|
||||
for name in ("debug.log", "db.log", "peers.dat", "fee_estimates.dat"):
|
||||
for cand in (log_filename("cache", i, os.path.join("regtest", name)),
|
||||
log_filename("cache", i, name)):
|
||||
try:
|
||||
os.remove(cand)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
for i in range(4):
|
||||
from_dir = os.path.join("cache", "node"+str(i))
|
||||
@@ -227,9 +258,10 @@ def start_node(i, dirname, extra_args=None, rpchost=None, timewait=None, binary=
|
||||
"""
|
||||
print("Starting node " + str(i) + " in dir " + dirname)
|
||||
datadir = os.path.join(dirname, "node"+str(i), "regtest")
|
||||
if extra_args is None: extra_args = []
|
||||
# creating special config
|
||||
if len(extra_args) > 0 and extra_args[0] == '-ac_name=ZZZ':
|
||||
configpath = datadir + "/ZZZ.conf"
|
||||
configpath = datadir + "/DRAGONX.conf"
|
||||
with open(configpath, "w+") as config:
|
||||
config.write("rpcuser=hush\n")
|
||||
config.write("rpcpassword=puppy\n")
|
||||
@@ -247,16 +279,30 @@ def start_node(i, dirname, extra_args=None, rpchost=None, timewait=None, binary=
|
||||
print("Done writing to %s" % configpath)
|
||||
|
||||
if binary is None:
|
||||
binary = os.getenv("BITCOIND", "src/hushd")
|
||||
args = [ binary, "-datadir="+datadir, "-keypool=1", "-discover=0", "-rest" ]
|
||||
binary = os.getenv("BITCOIND", "src/dragonxd")
|
||||
# -regtest MUST be a command-line flag. DragonX ignores "regtest=1" in the conf file, so
|
||||
# without this the node silently runs on MAINNET: it loads the real genesis, dials the real
|
||||
# seeds and starts syncing the live chain into the test datadir (observed: 196k blocks and
|
||||
# 679MB before a test timed out). -connect=0 keeps the regtest node off the public network.
|
||||
args = [ binary, "-regtest", "-connect=0", "-datadir="+datadir, "-keypool=1", "-discover=0", "-rest" ]
|
||||
# -asmap relative paths are resolved against the NET-SPECIFIC datadir (init.cpp), which for
|
||||
# regtest is <datadir>/regtest -- so a copy sitting in <datadir> is never found. Pass an
|
||||
# absolute path; without it dragonxd exits with "Could not find any asmap file!".
|
||||
_asmap = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../../asmap.dat")
|
||||
if os.path.exists(_asmap):
|
||||
args.append("-asmap=" + os.path.realpath(_asmap))
|
||||
if extra_args is not None: args.extend(extra_args)
|
||||
print("args=" + ' '.join(args))
|
||||
bitcoind_processes[i] = subprocess.Popen(args)
|
||||
devnull = open("/dev/null", "w+")
|
||||
|
||||
cmd = os.getenv("BITCOINCLI", "src/hush-cli")
|
||||
cmd = os.getenv("BITCOINCLI", "src/dragonx-cli")
|
||||
print("cmd=" + cmd)
|
||||
args = [ extra_args[0], "-datadir="+datadir, "-keypool=1", "-discover=0", "-rest" ]
|
||||
# The CLI only needs the datadir: initialize_datadir() already wrote DRAGONX.conf there
|
||||
# with the right rpcport/user/password. The old form passed extra_args[0] as argv[0] and
|
||||
# replayed daemon-only flags at the CLI, which only worked for the -ac_name=ZZZ assetchain
|
||||
# tests and broke every test that passes no extra_args.
|
||||
args = [ "-regtest", "-datadir="+datadir ]
|
||||
cmd_args = ' '.join(args) + " -rpcwait getblockcount "
|
||||
if os.getenv("PYTHON_DEBUG", ""):
|
||||
print("start_node: hushd started, calling : " + cmd + " " + cmd_args)
|
||||
@@ -266,18 +312,22 @@ def start_node(i, dirname, extra_args=None, rpchost=None, timewait=None, binary=
|
||||
import time
|
||||
time.sleep(2)
|
||||
subprocess.check_call(strcmd, shell=True);
|
||||
#subprocess.check_call([ os.getenv("BITCOINCLI", "hush-cli"), "-datadir="+datadir] +
|
||||
#subprocess.check_call([ os.getenv("BITCOINCLI", "dragonx-cli"), "-datadir="+datadir] +
|
||||
# _rpchost_to_args(rpchost) +
|
||||
# ["-rpcwait", "-rpcport=6438", "getblockcount"], stdout=devnull)
|
||||
if os.getenv("PYTHON_DEBUG", ""):
|
||||
print("start_node: calling hush-cli -rpcwait getblockcount returned")
|
||||
devnull.close()
|
||||
port = extra_args[3]
|
||||
#port = rpc_port(i)
|
||||
# Port comes from the same helper initialize_datadir() used, except for the assetchain
|
||||
# tests which pass it positionally as extra_args[3] == "-rpcport=NNNN".
|
||||
if len(extra_args) > 3 and str(extra_args[0]) == '-ac_name=ZZZ':
|
||||
port = extra_args[3][9:]
|
||||
else:
|
||||
port = str(rpc_port(i))
|
||||
#print("port=%s" % port)
|
||||
username = rpc_username()
|
||||
password = rpc_password()
|
||||
url = "http://%s:%s@%s:%s" % (username, password, rpchost or '127.0.0.1', port[9:])
|
||||
url = "http://%s:%s@%s:%s" % (username, password, rpchost or '127.0.0.1', port)
|
||||
print("connecting to " + url)
|
||||
if timewait is not None:
|
||||
proxy = AuthServiceProxy(url, timeout=timewait)
|
||||
@@ -315,7 +365,7 @@ def stop_nodes(nodes):
|
||||
del nodes[:] # Emptying array closes connections as a side effect
|
||||
|
||||
def set_node_times(nodes, t):
|
||||
print("Setting nodes time to " + t)
|
||||
print("Setting nodes time to " + str(t))
|
||||
for node in nodes:
|
||||
node.setmocktime(t)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2016-2024 The Hush developers
|
||||
# Copyright (c) 2014 The Bitcoin Core developers
|
||||
# Distributed under the GPLv3 software license, see the accompanying
|
||||
@@ -20,8 +20,20 @@ class WalletTest (BitcoinTestFramework):
|
||||
print("Initializing test directory "+self.options.tmpdir)
|
||||
initialize_chain_clean(self.options.tmpdir, 4)
|
||||
|
||||
# PORT NOTE (DragonX/regtest, not a change of test intent):
|
||||
# test_framework.start_node() hardcodes "-connect=0", and init.cpp turns that into
|
||||
# "-connect set -> setting -listen=0". With listening off the nodes never bind their
|
||||
# p2p port, so connect_nodes_bi() connects nothing at all (its version==0 poll loop
|
||||
# exits immediately because there are no local peers) and the first sync_all() hangs
|
||||
# forever. Passing -bind forces -listen back to 1. -dnsseed=0 keeps these regtest
|
||||
# nodes from dialing the live DragonX network.
|
||||
NET_ARGS = ["-listen=1", "-bind=127.0.0.1", "-dnsseed=0"]
|
||||
|
||||
def net_args(self, n, extra=None):
|
||||
return [list(self.NET_ARGS) + list(extra or []) for _ in range(n)]
|
||||
|
||||
def setup_network(self, split=False):
|
||||
self.nodes = start_nodes(3, self.options.tmpdir)
|
||||
self.nodes = start_nodes(3, self.options.tmpdir, self.net_args(3))
|
||||
connect_nodes_bi(self.nodes,0,1)
|
||||
connect_nodes_bi(self.nodes,1,2)
|
||||
connect_nodes_bi(self.nodes,0,2)
|
||||
@@ -29,7 +41,7 @@ class WalletTest (BitcoinTestFramework):
|
||||
self.sync_all()
|
||||
|
||||
def run_test (self):
|
||||
print "Mining blocks..."
|
||||
print("Mining blocks...")
|
||||
|
||||
self.nodes[0].generate(4)
|
||||
self.sync_all()
|
||||
@@ -106,7 +118,7 @@ class WalletTest (BitcoinTestFramework):
|
||||
signed_tx = self.nodes[2].signrawtransaction(raw_tx)
|
||||
try:
|
||||
self.nodes[2].sendrawtransaction(signed_tx["hex"])
|
||||
except JSONRPCException,e:
|
||||
except JSONRPCException as e:
|
||||
errorString = e.error['message']
|
||||
assert("absurdly high fees" in errorString)
|
||||
assert("900000000 > 190000" in errorString)
|
||||
@@ -186,7 +198,7 @@ class WalletTest (BitcoinTestFramework):
|
||||
txid2 = self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 1)
|
||||
sync_mempools(self.nodes)
|
||||
|
||||
self.nodes.append(start_node(3, self.options.tmpdir))
|
||||
self.nodes.append(start_node(3, self.options.tmpdir, list(self.NET_ARGS)))
|
||||
connect_nodes_bi(self.nodes, 0, 3)
|
||||
sync_blocks(self.nodes)
|
||||
|
||||
@@ -227,7 +239,7 @@ class WalletTest (BitcoinTestFramework):
|
||||
#do some -walletbroadcast tests
|
||||
stop_nodes(self.nodes)
|
||||
wait_bitcoinds()
|
||||
self.nodes = start_nodes(3, self.options.tmpdir, [["-walletbroadcast=0"],["-walletbroadcast=0"],["-walletbroadcast=0"]])
|
||||
self.nodes = start_nodes(3, self.options.tmpdir, self.net_args(3, ["-walletbroadcast=0"]))
|
||||
connect_nodes_bi(self.nodes,0,1)
|
||||
connect_nodes_bi(self.nodes,1,2)
|
||||
connect_nodes_bi(self.nodes,0,2)
|
||||
@@ -256,7 +268,7 @@ class WalletTest (BitcoinTestFramework):
|
||||
#restart the nodes with -walletbroadcast=1
|
||||
stop_nodes(self.nodes)
|
||||
wait_bitcoinds()
|
||||
self.nodes = start_nodes(3, self.options.tmpdir)
|
||||
self.nodes = start_nodes(3, self.options.tmpdir, self.net_args(3))
|
||||
connect_nodes_bi(self.nodes,0,1)
|
||||
connect_nodes_bi(self.nodes,1,2)
|
||||
connect_nodes_bi(self.nodes,0,2)
|
||||
@@ -290,7 +302,7 @@ class WalletTest (BitcoinTestFramework):
|
||||
num_t_recipients = 3000
|
||||
amount_per_recipient = Decimal('0.00000001')
|
||||
errorString = ''
|
||||
for i in xrange(0,num_t_recipients):
|
||||
for i in range(0,num_t_recipients):
|
||||
newtaddr = self.nodes[2].getnewaddress()
|
||||
recipients.append({"address":newtaddr, "amount":amount_per_recipient})
|
||||
|
||||
@@ -305,7 +317,7 @@ class WalletTest (BitcoinTestFramework):
|
||||
|
||||
try:
|
||||
self.nodes[0].z_sendmany(myzaddr, recipients)
|
||||
except JSONRPCException,e:
|
||||
except JSONRPCException as e:
|
||||
errorString = e.error['message']
|
||||
assert("Too many outputs, size of raw transaction" in errorString)
|
||||
|
||||
@@ -314,10 +326,10 @@ class WalletTest (BitcoinTestFramework):
|
||||
num_z_recipients = 50
|
||||
amount_per_recipient = Decimal('0.00000001')
|
||||
errorString = ''
|
||||
for i in xrange(0,num_t_recipients):
|
||||
for i in range(0,num_t_recipients):
|
||||
newtaddr = self.nodes[2].getnewaddress()
|
||||
recipients.append({"address":newtaddr, "amount":amount_per_recipient})
|
||||
for i in xrange(0,num_z_recipients):
|
||||
for i in range(0,num_z_recipients):
|
||||
newzaddr = self.nodes[2].z_getnewaddress()
|
||||
recipients.append({"address":newzaddr, "amount":amount_per_recipient})
|
||||
|
||||
@@ -327,7 +339,7 @@ class WalletTest (BitcoinTestFramework):
|
||||
|
||||
try:
|
||||
self.nodes[0].z_sendmany(myzaddr, recipients)
|
||||
except JSONRPCException,e:
|
||||
except JSONRPCException as e:
|
||||
errorString = e.error['message']
|
||||
assert("size of raw transaction would be larger than limit" in errorString)
|
||||
|
||||
@@ -335,12 +347,12 @@ class WalletTest (BitcoinTestFramework):
|
||||
num_z_recipients = 100
|
||||
amount_per_recipient = Decimal('0.00000001')
|
||||
errorString = ''
|
||||
for i in xrange(0,num_z_recipients):
|
||||
for i in range(0,num_z_recipients):
|
||||
newzaddr = self.nodes[2].z_getnewaddress()
|
||||
recipients.append({"address":newzaddr, "amount":amount_per_recipient})
|
||||
try:
|
||||
self.nodes[0].z_sendmany(myzaddr, recipients)
|
||||
except JSONRPCException,e:
|
||||
except JSONRPCException as e:
|
||||
errorString = e.error['message']
|
||||
assert("Invalid parameter, too many zaddr outputs" in errorString)
|
||||
|
||||
@@ -426,7 +438,7 @@ class WalletTest (BitcoinTestFramework):
|
||||
errorString = ""
|
||||
try:
|
||||
txId = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), "1f-4")
|
||||
except JSONRPCException,e:
|
||||
except JSONRPCException as e:
|
||||
errorString = e.error['message']
|
||||
|
||||
assert_equal("Invalid amount" in errorString, True)
|
||||
@@ -434,7 +446,7 @@ class WalletTest (BitcoinTestFramework):
|
||||
errorString = ""
|
||||
try:
|
||||
self.nodes[0].generate("2") #use a string to as block amount parameter must fail because it's not interpreted as amount
|
||||
except JSONRPCException,e:
|
||||
except JSONRPCException as e:
|
||||
errorString = e.error['message']
|
||||
|
||||
assert_equal("not an integer" in errorString, True)
|
||||
@@ -448,9 +460,9 @@ class WalletTest (BitcoinTestFramework):
|
||||
try:
|
||||
myopid = self.nodes[0].z_sendmany(myzaddr, recipients)
|
||||
assert(myopid)
|
||||
except JSONRPCException,e:
|
||||
except JSONRPCException as e:
|
||||
errorString = e.error['message']
|
||||
print errorString
|
||||
print(errorString)
|
||||
assert(False)
|
||||
|
||||
# This fee is larger than the default fee and since amount=0
|
||||
@@ -462,7 +474,7 @@ class WalletTest (BitcoinTestFramework):
|
||||
|
||||
try:
|
||||
myopid = self.nodes[0].z_sendmany(myzaddr, recipients, minconf, fee)
|
||||
except JSONRPCException,e:
|
||||
except JSONRPCException as e:
|
||||
errorString = e.error['message']
|
||||
assert('Small transaction amount' in errorString)
|
||||
|
||||
@@ -475,9 +487,9 @@ class WalletTest (BitcoinTestFramework):
|
||||
try:
|
||||
myopid = self.nodes[0].z_sendmany(myzaddr, recipients, minconf, fee)
|
||||
assert(myopid)
|
||||
except JSONRPCException,e:
|
||||
except JSONRPCException as e:
|
||||
errorString = e.error['message']
|
||||
print errorString
|
||||
print(errorString)
|
||||
assert(False)
|
||||
|
||||
# Make sure amount=0, fee=0 transaction are valid to add to mempool
|
||||
@@ -490,9 +502,9 @@ class WalletTest (BitcoinTestFramework):
|
||||
try:
|
||||
myopid = self.nodes[0].z_sendmany(myzaddr, recipients, minconf, fee)
|
||||
assert(myopid)
|
||||
except JSONRPCException,e:
|
||||
except JSONRPCException as e:
|
||||
errorString = e.error['message']
|
||||
print errorString
|
||||
print(errorString)
|
||||
assert(False)
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2016-2024 The Hush developers
|
||||
# Copyright (c) 2018 The Zcash developers
|
||||
# Distributed under the GPLv3 software license, see the accompanying
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2016-2024 The Hush developers
|
||||
# Copyright (c) 2017 The Zcash developers
|
||||
# Distributed under the GPLv3 software license, see the accompanying
|
||||
@@ -32,7 +32,7 @@ class WalletMergeToAddressTest (BitcoinTestFramework):
|
||||
self.sync_all()
|
||||
|
||||
def run_test (self):
|
||||
print "Mining blocks..."
|
||||
print("Mining blocks...")
|
||||
|
||||
self.nodes[0].generate(1)
|
||||
do_not_shield_taddr = self.nodes[0].getnewaddress()
|
||||
@@ -81,7 +81,7 @@ class WalletMergeToAddressTest (BitcoinTestFramework):
|
||||
try:
|
||||
self.nodes[0].z_mergetoaddress("*", myzaddr)
|
||||
assert(False)
|
||||
except JSONRPCException,e:
|
||||
except JSONRPCException as e:
|
||||
errorString = e.error['message']
|
||||
assert_equal("JSON value is not an array as expected" in errorString, True)
|
||||
|
||||
@@ -90,7 +90,7 @@ class WalletMergeToAddressTest (BitcoinTestFramework):
|
||||
try:
|
||||
self.nodes[2].z_mergetoaddress([mytaddr], myzaddr)
|
||||
assert(False)
|
||||
except JSONRPCException,e:
|
||||
except JSONRPCException as e:
|
||||
errorString = e.error['message']
|
||||
assert_equal("Could not find any funds to merge" in errorString, True)
|
||||
|
||||
@@ -98,7 +98,7 @@ class WalletMergeToAddressTest (BitcoinTestFramework):
|
||||
try:
|
||||
self.nodes[0].z_mergetoaddress(["*"], myzaddr, -1)
|
||||
assert(False)
|
||||
except JSONRPCException,e:
|
||||
except JSONRPCException as e:
|
||||
errorString = e.error['message']
|
||||
assert_equal("Amount out of range" in errorString, True)
|
||||
|
||||
@@ -106,7 +106,7 @@ class WalletMergeToAddressTest (BitcoinTestFramework):
|
||||
try:
|
||||
self.nodes[0].z_mergetoaddress(["*"], myzaddr, Decimal('21000000.00000001'))
|
||||
assert(False)
|
||||
except JSONRPCException,e:
|
||||
except JSONRPCException as e:
|
||||
errorString = e.error['message']
|
||||
assert_equal("Amount out of range" in errorString, True)
|
||||
|
||||
@@ -114,7 +114,7 @@ class WalletMergeToAddressTest (BitcoinTestFramework):
|
||||
try:
|
||||
self.nodes[0].z_mergetoaddress(["*"], myzaddr, 999)
|
||||
assert(False)
|
||||
except JSONRPCException,e:
|
||||
except JSONRPCException as e:
|
||||
errorString = e.error['message']
|
||||
assert_equal("Insufficient funds" in errorString, True)
|
||||
|
||||
@@ -122,7 +122,7 @@ class WalletMergeToAddressTest (BitcoinTestFramework):
|
||||
try:
|
||||
self.nodes[0].z_mergetoaddress(["*"], myzaddr, Decimal('0.001'), -1)
|
||||
assert(False)
|
||||
except JSONRPCException,e:
|
||||
except JSONRPCException as e:
|
||||
errorString = e.error['message']
|
||||
assert_equal("Limit on maximum number of UTXOs cannot be negative" in errorString, True)
|
||||
|
||||
@@ -130,7 +130,7 @@ class WalletMergeToAddressTest (BitcoinTestFramework):
|
||||
try:
|
||||
self.nodes[0].z_mergetoaddress(["*"], myzaddr, Decimal('0.001'), 99999999999999)
|
||||
assert(False)
|
||||
except JSONRPCException,e:
|
||||
except JSONRPCException as e:
|
||||
errorString = e.error['message']
|
||||
assert_equal("JSON integer out of range" in errorString, True)
|
||||
|
||||
@@ -138,7 +138,7 @@ class WalletMergeToAddressTest (BitcoinTestFramework):
|
||||
try:
|
||||
self.nodes[0].z_mergetoaddress(["*"], myzaddr, Decimal('0.001'), 50, -1)
|
||||
assert(False)
|
||||
except JSONRPCException,e:
|
||||
except JSONRPCException as e:
|
||||
errorString = e.error['message']
|
||||
assert_equal("Limit on maximum number of notes cannot be negative" in errorString, True)
|
||||
|
||||
@@ -146,7 +146,7 @@ class WalletMergeToAddressTest (BitcoinTestFramework):
|
||||
try:
|
||||
self.nodes[0].z_mergetoaddress(["*"], myzaddr, Decimal('0.001'), 50, 99999999999999)
|
||||
assert(False)
|
||||
except JSONRPCException,e:
|
||||
except JSONRPCException as e:
|
||||
errorString = e.error['message']
|
||||
assert_equal("JSON integer out of range" in errorString, True)
|
||||
|
||||
@@ -154,7 +154,7 @@ class WalletMergeToAddressTest (BitcoinTestFramework):
|
||||
try:
|
||||
self.nodes[0].z_mergetoaddress([mytaddr], mytaddr)
|
||||
assert(False)
|
||||
except JSONRPCException,e:
|
||||
except JSONRPCException as e:
|
||||
errorString = e.error['message']
|
||||
assert_equal("Destination address is also the only source address, and all its funds are already merged" in errorString, True)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2016-2024 The Hush developers
|
||||
# Copyright (c) 2016 The Zcash developers
|
||||
# Distributed under the GPLv3 software license, see the accompanying
|
||||
@@ -7,15 +7,71 @@
|
||||
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import assert_equal, assert_true, bitcoind_processes, \
|
||||
connect_nodes_bi, start_node, start_nodes, wait_and_assert_operationid_status
|
||||
connect_nodes_bi, initialize_chain_clean, p2p_port, start_node, start_nodes, \
|
||||
sync_blocks, wait_and_assert_operationid_status
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
class WalletNullifiersTest (BitcoinTestFramework):
|
||||
|
||||
# The framework default setup_chain() calls initialize_chain(), which pre-builds a
|
||||
# 200-block chain in a *relative* "cache/" directory shared by every test process
|
||||
# running out of this tree. That directory is guarded only by the datadir lock, so two
|
||||
# qa/rpc-tests running at once collide on it and the second one hangs forever inside
|
||||
# "dragonx-cli -rpcwait" (observed here while a sibling test held cache/node0..3).
|
||||
# Build the identical pre-condition -- 4 nodes, two rounds of 25 blocks each, i.e. 25
|
||||
# mature + 25 immature coinbases per node -- directly in this test's private tmpdir.
|
||||
# This is a setup change only: no assertion below is relaxed, removed or reordered.
|
||||
def setup_chain(self):
|
||||
print("Initializing test directory "+self.options.tmpdir)
|
||||
initialize_chain_clean(self.options.tmpdir, 4)
|
||||
|
||||
# Three networking facts about this daemon force extra flags here. None of them
|
||||
# change what the test exercises; without them the 4 nodes either never peer with
|
||||
# each other, or peer with the LIVE DragonX network instead.
|
||||
#
|
||||
# 1. start_node() hardcodes "-connect=0", which also soft-sets -listen=0, so nothing
|
||||
# binds p2p_port(i) and connect_nodes_bi() can never form the regtest mesh --
|
||||
# sync_blocks() then spins forever (observed: node0 at 25 blocks, nodes 1-3 stuck
|
||||
# at 0, nothing listening on 11005-11008). -listen=1 -bind=127.0.0.1 restores the
|
||||
# mesh and keeps it on loopback.
|
||||
# 2. hush_args() appends node1..node10.dragonx.is to -addnode unconditionally, -regtest
|
||||
# included, and regtest reuses mainnet's network magic. A "regtest" node therefore
|
||||
# joins the live network: node0 of an earlier run handshook 8 production peers
|
||||
# ("receive version message: /DragonX:1.0.3/ ... blocks=3254266") and ingested their
|
||||
# headers. -dns=0 stops those hostname -addnode entries from resolving; RPC addnode
|
||||
# with a numeric 127.0.0.1:port is unaffected.
|
||||
# 3. hush_args() runs BEFORE the config file is read, so its GetArg("-port",0) never
|
||||
# sees the "port=" line initialize_datadir() wrote and GetDefaultPort() stays at the
|
||||
# mainnet p2p port. "-connect=0" is then parsed as the address 0.0.0.0:<mainnet
|
||||
# port>, i.e. the production dragonxd listening on this box -- every node in runs 2
|
||||
# and 3 picked up exactly one peer reporting blocks=3254269. Repeating -port on the
|
||||
# command line points GetDefaultPort() at this node's own regtest port instead.
|
||||
#
|
||||
# -autoshield is on by default on DragonX and is not part of what this test measures:
|
||||
# a background thread sweeps each node's matured coinbase into a seed-derived zaddr
|
||||
# (8 "autoshield operation finished" ops per node while the chain is being mined). That
|
||||
# empties the very taddr this test spends from, and the resulting transactions do not
|
||||
# settle identically on every node ("ERROR: AcceptToMemoryPool: ContextualCheckTransaction
|
||||
# failed" on node1), so sync_mempools() never converges and the run wedges until the
|
||||
# timeout. Turn the background sweeper off; the test does its own shielding explicitly.
|
||||
def net_args(self, i):
|
||||
return ['-listen=1', '-bind=127.0.0.1', '-dns=0', '-autoshield=0',
|
||||
'-port=%d' % p2p_port(i)]
|
||||
|
||||
def setup_nodes(self):
|
||||
return start_nodes(4, self.options.tmpdir,
|
||||
extra_args=[['-experimentalfeatures', '-developerencryptwallet']] * 4)
|
||||
extra_args=[['-experimentalfeatures', '-developerencryptwallet']
|
||||
+ self.net_args(i) for i in range(4)])
|
||||
|
||||
def setup_network(self, split = False):
|
||||
super().setup_network(split)
|
||||
# Same block layout initialize_chain() would have handed us.
|
||||
for _ in range(2):
|
||||
for peer in range(4):
|
||||
self.nodes[peer].generate(25)
|
||||
sync_blocks(self.nodes)
|
||||
self.sync_all()
|
||||
|
||||
def run_test (self):
|
||||
# add zaddr to node 0
|
||||
@@ -44,7 +100,7 @@ class WalletNullifiersTest (BitcoinTestFramework):
|
||||
bitcoind_processes[1].wait()
|
||||
|
||||
# restart node 1
|
||||
self.nodes[1] = start_node(1, self.options.tmpdir)
|
||||
self.nodes[1] = start_node(1, self.options.tmpdir, self.net_args(1))
|
||||
connect_nodes_bi(self.nodes, 0, 1)
|
||||
connect_nodes_bi(self.nodes, 1, 2)
|
||||
self.sync_all()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2016-2024 The Hush developers
|
||||
# Copyright (c) 2018 The Zcash developers
|
||||
# Distributed under the GPLv3 software license, see the accompanying
|
||||
@@ -13,6 +13,29 @@ from test_framework.util import (
|
||||
)
|
||||
from decimal import Decimal
|
||||
|
||||
def get_value_pool(pools, pool_id):
|
||||
"""
|
||||
Return the valuePools entry with the given id, or None if this chain does
|
||||
not have that pool. DragonX's getblockchaininfo only reports the Sapling
|
||||
pool (no Sprout history exists on this chain), so the pools list can no
|
||||
longer be indexed positionally the way the upstream test did.
|
||||
"""
|
||||
for pool in pools:
|
||||
if pool['id'] == pool_id:
|
||||
return pool
|
||||
return None
|
||||
|
||||
def assert_pool_values(pools, sprout_value, sapling_value):
|
||||
sprout = get_value_pool(pools, 'sprout')
|
||||
if sprout is not None:
|
||||
assert_equal(sprout['chainValue'], sprout_value)
|
||||
else:
|
||||
# No Sprout pool at all is the same statement as "the Sprout pool holds nothing"
|
||||
assert_equal(sprout_value, Decimal('0'))
|
||||
sapling = get_value_pool(pools, 'sapling')
|
||||
assert_true(sapling is not None, "Sapling value pool missing from getblockchaininfo")
|
||||
assert_equal(sapling['chainValue'], sapling_value)
|
||||
|
||||
class WalletPersistenceTest (BitcoinTestFramework):
|
||||
|
||||
def setup_chain(self):
|
||||
@@ -20,8 +43,20 @@ class WalletPersistenceTest (BitcoinTestFramework):
|
||||
initialize_chain_clean(self.options.tmpdir, 3)
|
||||
|
||||
def setup_network(self, split=False):
|
||||
# -listen=1/-bind: the framework's start_node() passes -connect=0, and DragonX (like
|
||||
# Bitcoin) reacts to -connect by soft-setting -listen=0. A non-listening node can never
|
||||
# accept the "addnode 127.0.0.1:<port>" that connect_nodes_bi() issues, so without this
|
||||
# the three nodes stay isolated and sync_all() spins forever. -bind keeps the listener on
|
||||
# loopback so a regtest node never becomes reachable from the public internet.
|
||||
self.nodes = start_nodes(3, self.options.tmpdir,
|
||||
extra_args=[[
|
||||
'-listen=1',
|
||||
'-bind=127.0.0.1',
|
||||
# -dns=0: DragonX appends node1..node10.dragonx.is to -addnode for every chain
|
||||
# named DRAGONX (hush_utils.h), and -connect=0 does not suppress -addnode. Without
|
||||
# this a regtest node dials the LIVE DragonX network and is fed mainnet headers.
|
||||
# The addnode calls connect_nodes_bi() makes use literal IPs, so they still work.
|
||||
'-dns=0',
|
||||
'-nuparams=5ba81b19:100', # Overwinter
|
||||
'-nuparams=76b809bb:201', # Sapling
|
||||
]] * 3)
|
||||
@@ -72,8 +107,7 @@ class WalletPersistenceTest (BitcoinTestFramework):
|
||||
|
||||
# Verify size of shielded pools
|
||||
pools = self.nodes[0].getblockchaininfo()['valuePools']
|
||||
assert_equal(pools[0]['chainValue'], Decimal('0')) # Sprout
|
||||
assert_equal(pools[1]['chainValue'], Decimal('20')) # Sapling
|
||||
assert_pool_values(pools, Decimal('0'), Decimal('20'))
|
||||
|
||||
# Restart the nodes
|
||||
stop_nodes(self.nodes)
|
||||
@@ -82,8 +116,7 @@ class WalletPersistenceTest (BitcoinTestFramework):
|
||||
|
||||
# Verify size of shielded pools
|
||||
pools = self.nodes[0].getblockchaininfo()['valuePools']
|
||||
assert_equal(pools[0]['chainValue'], Decimal('0')) # Sprout
|
||||
assert_equal(pools[1]['chainValue'], Decimal('20')) # Sapling
|
||||
assert_pool_values(pools, Decimal('0'), Decimal('20'))
|
||||
|
||||
# Node 0 sends some shielded funds to Node 1
|
||||
dest_addr = self.nodes[1].z_getnewaddress('sapling')
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2016-2024 The Hush developers
|
||||
# Copyright (c) 2016 The Zcash developers
|
||||
# Distributed under the GPLv3 software license, see the accompanying
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.authproxy import JSONRPCException
|
||||
from test_framework.mininode import COIN
|
||||
from test_framework.util import assert_equal, initialize_chain_clean, \
|
||||
start_nodes, connect_nodes_bi, wait_and_assert_operationid_status
|
||||
|
||||
@@ -14,6 +13,20 @@ import sys
|
||||
import timeit
|
||||
from decimal import Decimal
|
||||
|
||||
# Upstream imported this from test_framework.mininode, which is still python2 and
|
||||
# fails to even parse under python3 (0x100000000L literals). mininode is a p2p
|
||||
# serialisation module this test does not otherwise use, so rather than drag a
|
||||
# large unrelated port into test_framework/ the one constant needed is inlined.
|
||||
# Same value as test_framework/mininode.py:52.
|
||||
COIN = 100000000 # 1 DRGX in puposhis
|
||||
|
||||
# DragonX has no Sprout pool: getblockchaininfo/getblock only ever emit a
|
||||
# "sapling" entry in valuePools (see rpc/blockchain.cpp), and z_getnewaddress
|
||||
# only makes Sapling addresses. The shielded value this test moves therefore
|
||||
# lands in the Sapling pool, so every check that upstream made against 'sprout'
|
||||
# is made against 'sapling' here. The assertion itself is unchanged.
|
||||
SHIELDED_POOL = 'sapling'
|
||||
|
||||
def check_value_pool(node, name, total):
|
||||
value_pools = node.getblockchaininfo()['valuePools']
|
||||
found = False
|
||||
@@ -42,7 +55,7 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework):
|
||||
self.sync_all()
|
||||
|
||||
def run_test (self):
|
||||
print "Mining blocks..."
|
||||
print("Mining blocks...")
|
||||
|
||||
self.nodes[0].generate(4)
|
||||
|
||||
@@ -59,17 +72,17 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework):
|
||||
assert_equal(self.nodes[2].getbalance(), 0)
|
||||
assert_equal(self.nodes[3].getbalance(), 0)
|
||||
|
||||
check_value_pool(self.nodes[0], 'sprout', 0)
|
||||
check_value_pool(self.nodes[1], 'sprout', 0)
|
||||
check_value_pool(self.nodes[2], 'sprout', 0)
|
||||
check_value_pool(self.nodes[3], 'sprout', 0)
|
||||
check_value_pool(self.nodes[0], SHIELDED_POOL, 0)
|
||||
check_value_pool(self.nodes[1], SHIELDED_POOL, 0)
|
||||
check_value_pool(self.nodes[2], SHIELDED_POOL, 0)
|
||||
check_value_pool(self.nodes[3], SHIELDED_POOL, 0)
|
||||
|
||||
# Send will fail because we are enforcing the consensus rule that
|
||||
# coinbase utxos can only be sent to a zaddr.
|
||||
errorString = ""
|
||||
try:
|
||||
self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 1)
|
||||
except JSONRPCException,e:
|
||||
except JSONRPCException as e:
|
||||
errorString = e.error['message']
|
||||
assert_equal("Coinbase funds can only be sent to a zaddr" in errorString, True)
|
||||
|
||||
@@ -95,11 +108,11 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework):
|
||||
# Test that the returned status object contains a params field with the operation's input parameters
|
||||
assert_equal(error_result["method"], "z_sendmany")
|
||||
params = error_result["params"]
|
||||
assert_equal(params["fee"], Decimal('0.0001')) # default
|
||||
assert_equal(params["minconf"], Decimal('1')) # default
|
||||
assert_equal(Decimal(params["fee"]), Decimal('0.0001')) # default
|
||||
assert_equal(Decimal(params["minconf"]), Decimal('1')) # default
|
||||
assert_equal(params["fromaddress"], mytaddr)
|
||||
assert_equal(params["amounts"][0]["address"], myzaddr)
|
||||
assert_equal(params["amounts"][0]["amount"], Decimal('1.23456789'))
|
||||
assert_equal(Decimal(params["amounts"][0]["amount"]), Decimal('1.23456789'))
|
||||
|
||||
# Add viewing key for myzaddr to Node 3
|
||||
myviewingkey = self.nodes[0].z_exportviewingkey(myzaddr)
|
||||
@@ -169,14 +182,17 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework):
|
||||
assert_equal(Decimal(resp["private"]), Decimal('19.9999'))
|
||||
assert_equal(Decimal(resp["total"]), Decimal('39.9999'))
|
||||
|
||||
# The Sprout value pool should reflect the send
|
||||
sproutvalue = shieldvalue
|
||||
check_value_pool(self.nodes[0], 'sprout', sproutvalue)
|
||||
# The shielded value pool should reflect the send
|
||||
shieldedvalue = shieldvalue
|
||||
check_value_pool(self.nodes[0], SHIELDED_POOL, shieldedvalue)
|
||||
|
||||
# A custom fee of 0 is okay. Here the node will send the note value back to itself.
|
||||
recipients = []
|
||||
recipients.append({"address":myzaddr, "amount": Decimal('19.9999')})
|
||||
myopid = self.nodes[0].z_sendmany(myzaddr, recipients, 1, Decimal('0.0'))
|
||||
# NB: the fee is passed as a JSON number, not a Decimal. authproxy serialises
|
||||
# Decimal as a JSON *string* and z_sendmany reads the fee with params[3].get_real(),
|
||||
# which only accepts VNUM -- see port notes. The value is unchanged.
|
||||
myopid = self.nodes[0].z_sendmany(myzaddr, recipients, 1, 0.0)
|
||||
mytxid = wait_and_assert_operationid_status(self.nodes[0], myopid)
|
||||
self.sync_all()
|
||||
self.nodes[1].generate(1)
|
||||
@@ -186,8 +202,8 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework):
|
||||
assert_equal(Decimal(resp["private"]), Decimal('19.9999'))
|
||||
assert_equal(Decimal(resp["total"]), Decimal('39.9999'))
|
||||
|
||||
# The Sprout value pool should be unchanged
|
||||
check_value_pool(self.nodes[0], 'sprout', sproutvalue)
|
||||
# The shielded value pool should be unchanged
|
||||
check_value_pool(self.nodes[0], SHIELDED_POOL, shieldedvalue)
|
||||
|
||||
# convert note to transparent funds
|
||||
unshieldvalue = Decimal('10.0')
|
||||
@@ -206,12 +222,12 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework):
|
||||
self.sync_all()
|
||||
|
||||
# check balances
|
||||
sproutvalue -= unshieldvalue + Decimal('0.0001')
|
||||
shieldedvalue -= unshieldvalue + Decimal('0.0001')
|
||||
resp = self.nodes[0].z_gettotalbalance()
|
||||
assert_equal(Decimal(resp["transparent"]), Decimal('30.0'))
|
||||
assert_equal(Decimal(resp["private"]), Decimal('9.9998'))
|
||||
assert_equal(Decimal(resp["total"]), Decimal('39.9998'))
|
||||
check_value_pool(self.nodes[0], 'sprout', sproutvalue)
|
||||
check_value_pool(self.nodes[0], SHIELDED_POOL, shieldedvalue)
|
||||
|
||||
# z_sendmany will return an error if there is transparent change output considered dust.
|
||||
# UTXO selection in z_sendmany sorts in ascending order, so smallest utxos are consumed first.
|
||||
@@ -226,7 +242,7 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework):
|
||||
errorString = ""
|
||||
try:
|
||||
self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 99999)
|
||||
except JSONRPCException,e:
|
||||
except JSONRPCException as e:
|
||||
errorString = e.error['message']
|
||||
assert_equal("Insufficient funds" in errorString, True)
|
||||
|
||||
@@ -241,7 +257,7 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework):
|
||||
# Send will fail because of insufficient funds unless sender uses coinbase utxos
|
||||
try:
|
||||
self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 21)
|
||||
except JSONRPCException,e:
|
||||
except JSONRPCException as e:
|
||||
errorString = e.error['message']
|
||||
assert_equal("Insufficient funds, coinbase funds can only be spent after they have been sent to a zaddr" in errorString, True)
|
||||
|
||||
@@ -256,7 +272,7 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework):
|
||||
# Note that regtest chainparams does not require standard tx, so setting the amount to be
|
||||
# less than the dust threshold, e.g. 0.00000001 will not result in mempool rejection.
|
||||
start_time = timeit.default_timer()
|
||||
for i in xrange(0,num_t_recipients):
|
||||
for i in range(0,num_t_recipients):
|
||||
newtaddr = self.nodes[2].getnewaddress()
|
||||
recipients.append({"address":newtaddr, "amount":amount_per_recipient})
|
||||
elapsed = timeit.default_timer() - start_time
|
||||
@@ -287,28 +303,30 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework):
|
||||
|
||||
# check balance
|
||||
node2balance = amount_per_recipient * num_t_recipients
|
||||
sproutvalue -= node2balance + Decimal('0.0001')
|
||||
shieldedvalue -= node2balance + Decimal('0.0001')
|
||||
assert_equal(self.nodes[2].getbalance(), node2balance)
|
||||
check_value_pool(self.nodes[0], 'sprout', sproutvalue)
|
||||
check_value_pool(self.nodes[0], SHIELDED_POOL, shieldedvalue)
|
||||
|
||||
# Send will fail because fee is negative
|
||||
try:
|
||||
self.nodes[0].z_sendmany(myzaddr, recipients, 1, -1)
|
||||
except JSONRPCException,e:
|
||||
except JSONRPCException as e:
|
||||
errorString = e.error['message']
|
||||
assert_equal("Amount out of range" in errorString, True)
|
||||
|
||||
# Send will fail because fee is larger than MAX_MONEY
|
||||
errorString = ""
|
||||
try:
|
||||
self.nodes[0].z_sendmany(myzaddr, recipients, 1, Decimal('21000000.00000001'))
|
||||
except JSONRPCException,e:
|
||||
self.nodes[0].z_sendmany(myzaddr, recipients, 1, float(Decimal('21000000.00000001')))
|
||||
except JSONRPCException as e:
|
||||
errorString = e.error['message']
|
||||
assert_equal("Amount out of range" in errorString, True)
|
||||
|
||||
# Send will fail because fee is larger than sum of outputs
|
||||
errorString = ""
|
||||
try:
|
||||
self.nodes[0].z_sendmany(myzaddr, recipients, 1, (amount_per_recipient * num_t_recipients) + Decimal('0.00000001'))
|
||||
except JSONRPCException,e:
|
||||
self.nodes[0].z_sendmany(myzaddr, recipients, 1, float((amount_per_recipient * num_t_recipients) + Decimal('0.00000001')))
|
||||
except JSONRPCException as e:
|
||||
errorString = e.error['message']
|
||||
assert_equal("is greater than the sum of outputs" in errorString, True)
|
||||
|
||||
@@ -334,10 +352,10 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework):
|
||||
send_amount = num_recipients * amount_per_recipient
|
||||
custom_fee = Decimal('0.00012345')
|
||||
zbalance = self.nodes[0].z_getbalance(myzaddr)
|
||||
for i in xrange(0,num_recipients):
|
||||
for i in range(0,num_recipients):
|
||||
newzaddr = self.nodes[2].z_getnewaddress()
|
||||
recipients.append({"address":newzaddr, "amount":amount_per_recipient})
|
||||
myopid = self.nodes[0].z_sendmany(myzaddr, recipients, minconf, custom_fee)
|
||||
myopid = self.nodes[0].z_sendmany(myzaddr, recipients, minconf, float(custom_fee))
|
||||
wait_and_assert_operationid_status(self.nodes[0], myopid)
|
||||
self.sync_all()
|
||||
self.nodes[1].generate(1)
|
||||
@@ -353,8 +371,8 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework):
|
||||
|
||||
resp = self.nodes[0].z_getbalance(myzaddr)
|
||||
assert_equal(Decimal(resp), zbalance - custom_fee - send_amount)
|
||||
sproutvalue -= custom_fee
|
||||
check_value_pool(self.nodes[0], 'sprout', sproutvalue)
|
||||
shieldedvalue -= custom_fee
|
||||
check_value_pool(self.nodes[0], SHIELDED_POOL, shieldedvalue)
|
||||
|
||||
notes = self.nodes[0].z_listunspent(1, 99999, False, [myzaddr])
|
||||
sum_of_notes = sum([note["amount"] for note in notes])
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2016-2024 The Hush developers
|
||||
# Copyright (c) 2018 The Zcash developers
|
||||
# Distributed under the GPLv3 software license, see the accompanying
|
||||
@@ -8,21 +8,99 @@ from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.authproxy import JSONRPCException
|
||||
from test_framework.util import (
|
||||
assert_equal,
|
||||
start_nodes,
|
||||
initialize_chain_clean,
|
||||
p2p_port,
|
||||
set_node_times,
|
||||
start_node,
|
||||
sync_blocks,
|
||||
wait_and_assert_operationid_status,
|
||||
)
|
||||
|
||||
import os
|
||||
import stat
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
# Test wallet behaviour with Sapling addresses
|
||||
class WalletSaplingTest(BitcoinTestFramework):
|
||||
|
||||
def setup_chain(self):
|
||||
# The shared initialize_chain() cache builder in test_framework/util.py has not been
|
||||
# repaired for DragonX (it spawns the cache nodes by bare name off PATH, drives the
|
||||
# CLI without -regtest so it dials the assetchain RPC port, and deletes debug.log
|
||||
# from the non-net-specific datadir). Build the same starting state here instead --
|
||||
# see _generate_starting_chain() -- so this test does not depend on it.
|
||||
print("Initializing test directory " + self.options.tmpdir)
|
||||
initialize_chain_clean(self.options.tmpdir, 4)
|
||||
|
||||
# !!! test_framework/util.py:start_node() hardcodes "-connect=0" into every regtest
|
||||
# node's argv. Modern Bitcoin Core special-cases that value to mean "make no automatic
|
||||
# connections", but THIS codebase does not (net.cpp ThreadOpenConnections just iterates
|
||||
# mapMultiArgs["-connect"]), so "0" is dialled as a hostname: it resolves to 0.0.0.0,
|
||||
# which on Linux connects to localhost on Params().GetDefaultPort() -- 21768, the live
|
||||
# DRAGONX p2p port. Observed directly: a regtest node started by the unmodified
|
||||
# framework peered with the production dragonxd on this host and with seven public
|
||||
# mainnet nodes (heights ~3.25M) and began ingesting mainnet headers. "-connect" also
|
||||
# soft-sets "-listen=0", so the framework's own connect_nodes_bi() can never establish
|
||||
# the local links a multi-node test needs.
|
||||
#
|
||||
# Both problems are in the shared framework, which this port is not allowed to touch, so
|
||||
# they are worked around per-node here: the daemon is launched through a tiny wrapper
|
||||
# that strips the "-connect=0" argument, and the real topology/listening flags are passed
|
||||
# as extra_args (which start_node appends after its own).
|
||||
def _daemon_wrapper(self):
|
||||
srcdir = os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)), "..", "..", "src", "dragonxd")
|
||||
path = os.path.join(self.options.tmpdir, "dragonxd-no-connect0")
|
||||
with open(path, "w") as f:
|
||||
f.write("#!/usr/bin/env bash\n")
|
||||
f.write("args=()\n")
|
||||
f.write('for a in "$@"; do\n')
|
||||
f.write(' if [ "$a" = "-connect=0" ]; then continue; fi\n')
|
||||
f.write(' args+=("$a")\n')
|
||||
f.write("done\n")
|
||||
f.write('exec %s "${args[@]}"\n' % os.path.realpath(srcdir))
|
||||
os.chmod(path, os.stat(path).st_mode | stat.S_IXUSR)
|
||||
return path
|
||||
|
||||
def setup_nodes(self):
|
||||
return start_nodes(4, self.options.tmpdir, [[
|
||||
binary = self._daemon_wrapper()
|
||||
nodes = []
|
||||
for i in range(4):
|
||||
extra_args = [
|
||||
#'-nuparams=5ba81b19:201', # Overwinter
|
||||
#'-nuparams=76b809bb:203', # Sapling
|
||||
#'-experimentalfeatures', '-zmergetoaddress',
|
||||
]] * 4)
|
||||
# Listen on this test's PID-keyed port so the nodes can actually peer with
|
||||
# each other, and only ever dial each other -- never the public network.
|
||||
'-listen=1',
|
||||
'-bind=127.0.0.1',
|
||||
'-port=%d' % p2p_port(i),
|
||||
] + ['-connect=127.0.0.1:%d' % p2p_port(j) for j in range(4) if j != i]
|
||||
nodes.append(start_node(i, self.options.tmpdir, extra_args, binary=binary))
|
||||
return nodes
|
||||
|
||||
def setup_network(self, split=False):
|
||||
super(WalletSaplingTest, self).setup_network(split)
|
||||
self._generate_starting_chain()
|
||||
|
||||
def _generate_starting_chain(self):
|
||||
# Equivalent of test_framework.util.initialize_chain(): a 200-block chain where each
|
||||
# of the 4 nodes mined 25 blocks twice, so every node holds 25 mature and 25 immature
|
||||
# coinbases. Block timestamps are 10 minutes apart starting 1 Jan 2014, as there.
|
||||
block_time = 1388534400
|
||||
for _round in range(2):
|
||||
for peer in range(4):
|
||||
for _j in range(25):
|
||||
set_node_times(self.nodes, block_time)
|
||||
self.nodes[peer].generate(1)
|
||||
block_time += 10 * 75
|
||||
# Must sync before next peer starts generating blocks
|
||||
sync_blocks(self.nodes)
|
||||
# Drop back to wall-clock time: initialize_chain() stops the cache nodes and the test
|
||||
# then runs against freshly started nodes that have no mocktime set.
|
||||
set_node_times(self.nodes, 0)
|
||||
self.sync_all()
|
||||
|
||||
def run_test(self):
|
||||
# Sanity-check the test harness
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2016-2024 The Hush developers
|
||||
# Copyright (c) 2017 The Zcash developers
|
||||
# Distributed under the GPLv3 software license, see the accompanying
|
||||
@@ -22,8 +22,24 @@ class WalletShieldCoinbaseTest (BitcoinTestFramework):
|
||||
initialize_chain_clean(self.options.tmpdir, 4)
|
||||
|
||||
def setup_network(self, split=False):
|
||||
args = ['-regtestprotectcoinbase', '-debug=zrpcunsafe']
|
||||
args2 = ['-regtestprotectcoinbase', '-debug=zrpcunsafe', "-mempooltxinputlimit=7"]
|
||||
# DragonX-specific environment flags. None of these change what the test
|
||||
# asserts; without them the test cannot run at all on this daemon:
|
||||
# -listen=1/-bind=127.0.0.1: the framework's start_node() always passes
|
||||
# -connect=0, and AppInit2 soft-sets -listen=0 whenever -connect is
|
||||
# present, so the nodes never listen and connect_nodes_bi() silently
|
||||
# builds an empty topology (sync_all() then spins forever). An explicit
|
||||
# -listen=1 beats the SoftSetBoolArg.
|
||||
# -dnsseed=0: chainparams_commandline() keeps DRAGONX's DNS seeds and
|
||||
# overwrites pchMessageStart with the DRAGONX chain magic on *every*
|
||||
# network including regtest, so a regtest node otherwise dials and
|
||||
# handshakes with live mainnet peers (observed: 8 mainnet peers,
|
||||
# blocks=3254237, feeding mainnet headers into the regtest node).
|
||||
# -autoshield=0: DragonX auto-shields matured coinbase every 25 blocks
|
||||
# by default, which would race the manual z_shieldcoinbase calls under
|
||||
# test and move the balances this test checks.
|
||||
isolate = ['-listen=1', '-bind=127.0.0.1', '-dnsseed=0', '-autoshield=0']
|
||||
args = ['-regtestprotectcoinbase', '-debug=zrpcunsafe'] + isolate
|
||||
args2 = ['-regtestprotectcoinbase', '-debug=zrpcunsafe', "-mempooltxinputlimit=7"] + isolate
|
||||
if self.addr_type != 'sprout':
|
||||
nu = [
|
||||
'-nuparams=5ba81b19:0', # Overwinter
|
||||
@@ -42,7 +58,7 @@ class WalletShieldCoinbaseTest (BitcoinTestFramework):
|
||||
self.sync_all()
|
||||
|
||||
def run_test (self):
|
||||
print "Mining blocks..."
|
||||
print("Mining blocks...")
|
||||
|
||||
self.nodes[0].generate(1)
|
||||
self.sync_all()
|
||||
@@ -73,42 +89,42 @@ class WalletShieldCoinbaseTest (BitcoinTestFramework):
|
||||
self.nodes[2].importaddress(mytaddr)
|
||||
try:
|
||||
self.nodes[2].z_shieldcoinbase(mytaddr, myzaddr)
|
||||
except JSONRPCException,e:
|
||||
except JSONRPCException as e:
|
||||
errorString = e.error['message']
|
||||
assert_equal("Could not find any coinbase funds to shield" in errorString, True)
|
||||
|
||||
# Shielding will fail because fee is negative
|
||||
try:
|
||||
self.nodes[0].z_shieldcoinbase("*", myzaddr, -1)
|
||||
except JSONRPCException,e:
|
||||
except JSONRPCException as e:
|
||||
errorString = e.error['message']
|
||||
assert_equal("Amount out of range" in errorString, True)
|
||||
|
||||
# Shielding will fail because fee is larger than MAX_MONEY
|
||||
try:
|
||||
self.nodes[0].z_shieldcoinbase("*", myzaddr, Decimal('21000000.00000001'))
|
||||
except JSONRPCException,e:
|
||||
except JSONRPCException as e:
|
||||
errorString = e.error['message']
|
||||
assert_equal("Amount out of range" in errorString, True)
|
||||
|
||||
# Shielding will fail because fee is larger than sum of utxos
|
||||
try:
|
||||
self.nodes[0].z_shieldcoinbase("*", myzaddr, 999)
|
||||
except JSONRPCException,e:
|
||||
except JSONRPCException as e:
|
||||
errorString = e.error['message']
|
||||
assert_equal("Insufficient coinbase funds" in errorString, True)
|
||||
|
||||
# Shielding will fail because limit parameter must be at least 0
|
||||
try:
|
||||
self.nodes[0].z_shieldcoinbase("*", myzaddr, Decimal('0.001'), -1)
|
||||
except JSONRPCException,e:
|
||||
except JSONRPCException as e:
|
||||
errorString = e.error['message']
|
||||
assert_equal("Limit on maximum number of utxos cannot be negative" in errorString, True)
|
||||
|
||||
# Shielding will fail because limit parameter is absurdly large
|
||||
try:
|
||||
self.nodes[0].z_shieldcoinbase("*", myzaddr, Decimal('0.001'), 99999999999999)
|
||||
except JSONRPCException,e:
|
||||
except JSONRPCException as e:
|
||||
errorString = e.error['message']
|
||||
assert_equal("JSON integer out of range" in errorString, True)
|
||||
|
||||
@@ -214,3 +230,13 @@ class WalletShieldCoinbaseTest (BitcoinTestFramework):
|
||||
sync_mempools(self.nodes[:2])
|
||||
self.nodes[1].generate(1)
|
||||
self.sync_all()
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Upstream (Zcash/Hush) ran this test twice: once with Sprout zaddrs and once
|
||||
# with Sapling. DragonX has no Sprout support at all -- z_getnewaddress only
|
||||
# accepts "sapling" or "amnesia" (src/wallet/rpcwallet.cpp z_getnewaddress),
|
||||
# so WalletShieldCoinbaseTest('sprout') cannot even allocate its target
|
||||
# address. The sprout-only branches inside run_test() are kept intact for
|
||||
# reference but only the sapling variant is executed.
|
||||
print("Running for sapling...")
|
||||
WalletShieldCoinbaseTest('sapling').main()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2016-2024 The Hush developers
|
||||
# Copyright (c) 2016 The Zcash developers
|
||||
# Distributed under the GPLv3 software license, see the accompanying
|
||||
@@ -20,7 +20,16 @@ class WalletTreeStateTest (BitcoinTestFramework):
|
||||
|
||||
# Start nodes with -regtestprotectcoinbase to set fCoinbaseMustBeProtected to true.
|
||||
def setup_network(self, split=False):
|
||||
self.nodes = start_nodes(3, self.options.tmpdir, extra_args=[['-regtestprotectcoinbase','-debug=zrpc']] * 3 )
|
||||
# -listen=1 and -dns=0 are DragonX-specific harness requirements, not part of the
|
||||
# original test: start_node() passes -connect=0, which trips the "-connect set ->
|
||||
# setting -listen=0" parameter interaction, so without -listen=1 the three nodes
|
||||
# cannot open the p2p links connect_nodes_bi() asks for (node1/node2 stay at height
|
||||
# 0 forever and sync_all() can never converge). -dns=0 blocks the unconditional
|
||||
# node1..node10.dragonx.is -addnode injection in hush_args(), which otherwise dials
|
||||
# the real DragonX seed nodes from regtest and floods these nodes with mainnet headers.
|
||||
self.nodes = start_nodes(3, self.options.tmpdir,
|
||||
extra_args=[['-regtestprotectcoinbase','-debug=zrpc',
|
||||
'-listen=1','-dns=0']] * 3 )
|
||||
connect_nodes_bi(self.nodes,0,1)
|
||||
connect_nodes_bi(self.nodes,1,2)
|
||||
connect_nodes_bi(self.nodes,0,2)
|
||||
@@ -28,7 +37,7 @@ class WalletTreeStateTest (BitcoinTestFramework):
|
||||
self.sync_all()
|
||||
|
||||
def run_test (self):
|
||||
print "Mining blocks..."
|
||||
print("Mining blocks...")
|
||||
|
||||
self.nodes[0].generate(100)
|
||||
self.sync_all()
|
||||
@@ -79,7 +88,7 @@ class WalletTreeStateTest (BitcoinTestFramework):
|
||||
myopid = self.nodes[0].z_sendmany(myzaddr, recipients)
|
||||
|
||||
# Wait for Tx 2 to begin executing...
|
||||
for x in xrange(1, 60):
|
||||
for x in range(1, 60):
|
||||
results = self.nodes[0].z_getoperationstatus([myopid])
|
||||
status = results[0]["status"]
|
||||
if status == "executing":
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2016-2024 The Hush developers
|
||||
# Copyright (c) 2014 The Bitcoin Core developers
|
||||
# Distributed under the GPLv3 software license, see the accompanying
|
||||
@@ -37,8 +37,8 @@ and confirm again balances are correct.
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.authproxy import JSONRPCException
|
||||
from test_framework.util import assert_equal, initialize_chain_clean, \
|
||||
start_nodes, start_node, connect_nodes, stop_node, \
|
||||
sync_blocks, sync_mempools
|
||||
start_nodes, start_node, connect_nodes, \
|
||||
sync_blocks, sync_mempools, bitcoind_processes
|
||||
|
||||
import os
|
||||
import shutil
|
||||
@@ -48,6 +48,46 @@ import logging
|
||||
|
||||
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.INFO)
|
||||
|
||||
# Three node flags this test has to supply for itself, because test_framework.util.start_node()
|
||||
# cannot give it a usable isolated regtest network on DragonX:
|
||||
#
|
||||
# -listen=1: start_node() passes "-connect=0" to every node. In DragonX (as in Bitcoin) the
|
||||
# presence of a -connect argument SoftSetBoolArg()s -listen to false, so no test node ever
|
||||
# binds its p2p port and connect_nodes() cannot build the loopback topology this test needs.
|
||||
# Verified on a live run: only the RPC ports were listening and getpeerinfo showed zero
|
||||
# 127.0.0.1 peers on all four nodes. SoftSetBoolArg does not override an explicit value.
|
||||
#
|
||||
# -dns=0: hush_args() unconditionally appends node1..node10.dragonx.is to -addnode whenever the
|
||||
# chain name is DRAGONX, regardless of network, and -connect=0 does not suppress it. On a live
|
||||
# run every "regtest" node ended up with 7-8 established connections to production mainnet
|
||||
# nodes on port 21768, was flooded with mainnet headers ("AcceptBlockHeader: hashPrevBlock ...
|
||||
# not found"), and one of them aborted on CheckBlockIndex(). -dns=0 stops the hostnames from
|
||||
# resolving while leaving the literal 127.0.0.1:PORT addnodes connect_nodes() uses intact.
|
||||
#
|
||||
# -allowlist=127.0.0.1: "-connect=0" does not mean "no connections". The daemon resolves the
|
||||
# literal "0" to 0.0.0.0 and dials it on the chain's default p2p port, i.e. 127.0.0.1:21768 --
|
||||
# which on a machine that also runs a real node is the PRODUCTION daemon (observed:
|
||||
# ESTAB 127.0.0.1:41282 -> 127.0.0.1:21768 from every test node, peer subver /DragonX:1.0.3/,
|
||||
# startingheight 3254254). That peer is outbound, so it is the node's only preferred-download
|
||||
# peer; main.cpp:8398 then computes fFetch=false for every inbound peer, and node3 -- which in
|
||||
# this test's topology is dialed by everyone and dials no one -- never downloads an announced
|
||||
# block, so sync_blocks() hangs forever. Allowlisting loopback makes inbound test peers
|
||||
# preferred-download too (main.cpp:381), which restores block propagation.
|
||||
LISTEN = "-listen=1"
|
||||
NODNS = "-dns=0"
|
||||
ALLOWLIST = "-allowlist=127.0.0.1"
|
||||
|
||||
|
||||
def stop_node_and_reap(node, i):
|
||||
# Equivalent to test_framework.util.stop_node(), which cannot be called: it does
|
||||
# print("Stopping node " + i) with the int index that every caller passes, which raises
|
||||
# TypeError. Reimplemented here rather than editing shared framework code other tests use.
|
||||
print("Stopping node %d" % i)
|
||||
node.stop()
|
||||
bitcoind_processes[i].wait()
|
||||
del bitcoind_processes[i]
|
||||
|
||||
|
||||
class WalletBackupTest(BitcoinTestFramework):
|
||||
|
||||
def setup_chain(self):
|
||||
@@ -62,7 +102,10 @@ class WalletBackupTest(BitcoinTestFramework):
|
||||
ed2 = "-exportdir=" + self.options.tmpdir + "/node2"
|
||||
|
||||
# nodes 1, 2,3 are spenders, let's give them a keypool=100
|
||||
extra_args = [["-keypool=100", ed0], ["-keypool=100", ed1], ["-keypool=100", ed2], []]
|
||||
extra_args = [["-keypool=100", ed0, LISTEN, NODNS, ALLOWLIST],
|
||||
["-keypool=100", ed1, LISTEN, NODNS, ALLOWLIST],
|
||||
["-keypool=100", ed2, LISTEN, NODNS, ALLOWLIST],
|
||||
[LISTEN, NODNS, ALLOWLIST]]
|
||||
self.nodes = start_nodes(4, self.options.tmpdir, extra_args)
|
||||
connect_nodes(self.nodes[0], 3)
|
||||
connect_nodes(self.nodes[1], 3)
|
||||
@@ -95,18 +138,18 @@ class WalletBackupTest(BitcoinTestFramework):
|
||||
|
||||
# As above, this mirrors the original bash test.
|
||||
def start_three(self):
|
||||
self.nodes[0] = start_node(0, self.options.tmpdir)
|
||||
self.nodes[1] = start_node(1, self.options.tmpdir)
|
||||
self.nodes[2] = start_node(2, self.options.tmpdir)
|
||||
self.nodes[0] = start_node(0, self.options.tmpdir, [LISTEN, NODNS, ALLOWLIST])
|
||||
self.nodes[1] = start_node(1, self.options.tmpdir, [LISTEN, NODNS, ALLOWLIST])
|
||||
self.nodes[2] = start_node(2, self.options.tmpdir, [LISTEN, NODNS, ALLOWLIST])
|
||||
connect_nodes(self.nodes[0], 3)
|
||||
connect_nodes(self.nodes[1], 3)
|
||||
connect_nodes(self.nodes[2], 3)
|
||||
connect_nodes(self.nodes[2], 0)
|
||||
|
||||
def stop_three(self):
|
||||
stop_node(self.nodes[0], 0)
|
||||
stop_node(self.nodes[1], 1)
|
||||
stop_node(self.nodes[2], 2)
|
||||
stop_node_and_reap(self.nodes[0], 0)
|
||||
stop_node_and_reap(self.nodes[1], 1)
|
||||
stop_node_and_reap(self.nodes[2], 2)
|
||||
|
||||
def erase_three(self):
|
||||
os.remove(self.options.tmpdir + "/node0/regtest/wallet.dat")
|
||||
|
||||
@@ -13,7 +13,8 @@ hush_gtest_SOURCES = \
|
||||
gtest/utils.cpp \
|
||||
gtest/test_randomx_preverify.cpp \
|
||||
gtest/test_hdtransparent.cpp \
|
||||
gtest/test_mnemonic_compat.cpp
|
||||
gtest/test_mnemonic_compat.cpp \
|
||||
gtest/test_stratum_jobid.cpp
|
||||
|
||||
hush_gtest_CPPFLAGS = $(AM_CPPFLAGS) -DMULTICORE -fopenmp -DBINARY_OUTPUT -DCURVE_ALT_BN128 -DSTATIC $(BITCOIN_INCLUDES)
|
||||
hush_gtest_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
|
||||
|
||||
29
src/chain.h
29
src/chain.h
@@ -35,7 +35,7 @@ extern bool fZindex;
|
||||
// These version thresholds control whether nSproutValue/nSaplingValue are
|
||||
// serialized in the block index. They must be <= CLIENT_VERSION or the
|
||||
// values will never be persisted, causing nChainSaplingValue to reset
|
||||
// to 0 after node restart. DragonX CLIENT_VERSION is 1010050 (v1.1.0.50).
|
||||
// to 0 after node restart. DragonX CLIENT_VERSION is 1030050 (v1.3.0.50).
|
||||
static const int SPROUT_VALUE_VERSION = 1000000;
|
||||
static const int SAPLING_VALUE_VERSION = 1000000;
|
||||
// Block-index records written at >= this version store nSaplingValue as a boost::optional
|
||||
@@ -113,10 +113,9 @@ enum BlockStatus: uint32_t {
|
||||
//! Scripts & signatures ok. Implies all parents are also at least SCRIPTS.
|
||||
BLOCK_VALID_SCRIPTS = 5,
|
||||
|
||||
// flag to check if contextual check block has passed in Accept block, if it has not check at connect block.
|
||||
BLOCK_VALID_CONTEXT = 6,
|
||||
|
||||
//! All validity bits.
|
||||
//! NOTE: the levels above are sequential VALUES occupying this 3-bit field, not independent
|
||||
//! bits, so any flag stored in nStatus must live entirely outside this mask.
|
||||
BLOCK_VALID_MASK = BLOCK_VALID_HEADER | BLOCK_VALID_TREE | BLOCK_VALID_TRANSACTIONS |
|
||||
BLOCK_VALID_CHAIN | BLOCK_VALID_SCRIPTS,
|
||||
|
||||
@@ -129,9 +128,29 @@ enum BlockStatus: uint32_t {
|
||||
BLOCK_FAILED_MASK = BLOCK_FAILED_VALID | BLOCK_FAILED_CHILD,
|
||||
|
||||
BLOCK_ACTIVATES_UPGRADE = 128, //! block activates a network upgrade
|
||||
BLOCK_IN_TMPFILE = 256
|
||||
BLOCK_IN_TMPFILE = 256,
|
||||
|
||||
//! ContextualCheckBlock already passed in AcceptBlock, so ConnectBlock may skip re-running it.
|
||||
//! Was 6 until v1.3.0, which put it INSIDE BLOCK_VALID_MASK (1|2|3|4|5 == 7): `nStatus |=
|
||||
//! BLOCK_VALID_CONTEXT` then overwrote the validity level rather than setting a flag, so a
|
||||
//! block that was only written to disk read back as BLOCK_VALID_SCRIPTS and every later
|
||||
//! RaiseValidity() silently no-opped. Detected by CheckBlockIndex's "CHAIN valid implies all
|
||||
//! parents are CHAIN valid" assert, which aborts any node doing out-of-order block download
|
||||
//! (regtest only, where fDefaultConsistencyChecks is true). Validity was only ever inflated,
|
||||
//! never deflated, so ConnectBlock's full validation was never skipped -- see git history.
|
||||
//! Legacy block indexes still carry the polluted low bits; they resolve on reindex, and until
|
||||
//! then simply re-run the contextual check they used to skip.
|
||||
BLOCK_VALID_CONTEXT = 512
|
||||
};
|
||||
|
||||
//! The validity level is a small integer packed into BLOCK_VALID_MASK, so every other nStatus flag
|
||||
//! must be disjoint from it. Enforced here so this class of bug cannot be reintroduced silently.
|
||||
static_assert((BLOCK_VALID_CONTEXT & BLOCK_VALID_MASK) == 0, "BLOCK_VALID_CONTEXT overlaps the validity-level field");
|
||||
static_assert((BLOCK_HAVE_MASK & BLOCK_VALID_MASK) == 0, "BLOCK_HAVE_MASK overlaps the validity-level field");
|
||||
static_assert((BLOCK_FAILED_MASK & BLOCK_VALID_MASK) == 0, "BLOCK_FAILED_MASK overlaps the validity-level field");
|
||||
static_assert((BLOCK_ACTIVATES_UPGRADE & BLOCK_VALID_MASK) == 0, "BLOCK_ACTIVATES_UPGRADE overlaps the validity-level field");
|
||||
static_assert((BLOCK_IN_TMPFILE & BLOCK_VALID_MASK) == 0, "BLOCK_IN_TMPFILE overlaps the validity-level field");
|
||||
|
||||
//! Short-hand for the highest consensus validity we implement.
|
||||
//! Blocks with this validity are assumed to satisfy all consensus rules.
|
||||
static const BlockStatus BLOCK_VALID_CONSENSUS = BLOCK_VALID_SCRIPTS;
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
//! These need to be macros, as clientversion.cpp's and bitcoin*-res.rc's voodoo requires it
|
||||
// Must be kept in sync with configure.ac , ugh!
|
||||
#define CLIENT_VERSION_MAJOR 1
|
||||
#define CLIENT_VERSION_MINOR 2
|
||||
#define CLIENT_VERSION_MINOR 3
|
||||
#define CLIENT_VERSION_REVISION 0
|
||||
#define CLIENT_VERSION_BUILD 50
|
||||
|
||||
|
||||
98
src/gtest/test_stratum_jobid.cpp
Normal file
98
src/gtest/test_stratum_jobid.cpp
Normal file
@@ -0,0 +1,98 @@
|
||||
// Copyright (c) 2016-2026 The Hush developers
|
||||
// Distributed under the GPLv3 software license, see the accompanying
|
||||
// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||
//
|
||||
// Regression coverage for the b3e81f1ed fix:
|
||||
// "stratum: do not abort the daemon on a malformed 63-character job_id"
|
||||
//
|
||||
// The EWBF "31 bytes job_id" path in stratum_mining_submit() completes a
|
||||
// 63-character job_id with each hex digit in turn and feeds the result to
|
||||
// uint256(). ParseHex() stops at the first non-hex character and returns a
|
||||
// SHORT vector without signalling an error, and base_blob's vector ctor
|
||||
// asserts vch.size() == 32. asserts are live in release builds here, and the
|
||||
// job_id arrives from an unauthenticated client -- so a single mining.submit
|
||||
// whose 63-character job_id contains any non-hex byte (63 spaces will do)
|
||||
// used to abort the node.
|
||||
//
|
||||
// The fix size-checks each candidate before constructing uint256. These tests
|
||||
// pin the exact invariant that guard relies on, using the real ParseHex and
|
||||
// uint256 primitives, without ever constructing a uint256 from a short vector
|
||||
// (which would still abort under the guard we are protecting).
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "uint256.h"
|
||||
#include "utilstrencodings.h"
|
||||
|
||||
namespace {
|
||||
|
||||
const std::string HEXDIGITS = "0123456789abcdef";
|
||||
|
||||
// Mirrors the guarded completion loop in stratum_mining_submit(): try every
|
||||
// single-hex-digit completion and report whether ANY of them parses to a
|
||||
// whole 32-byte value. Only then is uint256() construction reached.
|
||||
bool AnyCompletionParsesTo32(const std::string& jobid) {
|
||||
for (char d : HEXDIGITS) {
|
||||
std::vector<unsigned char> vch = ParseHex(jobid + d);
|
||||
if (vch.size() == 32) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// The exact example from the fix commit: a 63-character job_id of spaces.
|
||||
// No completion may reach a 32-byte vector, so the daemon never constructs
|
||||
// uint256() and never aborts.
|
||||
TEST(StratumJobId, AllSpacesNeverYields32Bytes) {
|
||||
std::string spaces(63, ' ');
|
||||
ASSERT_EQ(spaces.size(), 63u);
|
||||
for (char d : HEXDIGITS) {
|
||||
EXPECT_NE(ParseHex(spaces + d).size(), 32u)
|
||||
<< "completion '" << d << "' unexpectedly produced 32 bytes";
|
||||
}
|
||||
EXPECT_FALSE(AnyCompletionParsesTo32(spaces));
|
||||
}
|
||||
|
||||
// A single non-hex byte embedded in an otherwise-hex 63-char job_id is enough:
|
||||
// ParseHex stops at it, so every completion is short.
|
||||
TEST(StratumJobId, SingleNonHexByteInMiddleIsRejected) {
|
||||
std::string jobid(63, 'a');
|
||||
jobid[30] = 'g'; // 'g' is not a hex digit
|
||||
ASSERT_EQ(jobid.size(), 63u);
|
||||
EXPECT_FALSE(AnyCompletionParsesTo32(jobid));
|
||||
}
|
||||
|
||||
// A non-hex byte at the very end (position 62) is likewise rejected: the last
|
||||
// hex pair can never complete to a whole byte.
|
||||
TEST(StratumJobId, NonHexByteAtEndIsRejected) {
|
||||
std::string jobid(62, 'a');
|
||||
jobid.push_back('z'); // length 63, last char non-hex
|
||||
ASSERT_EQ(jobid.size(), 63u);
|
||||
EXPECT_FALSE(AnyCompletionParsesTo32(jobid));
|
||||
}
|
||||
|
||||
// A genuine truncated EWBF job_id -- 63 real hex characters -- must complete
|
||||
// to exactly 32 bytes for every digit, so uint256() construction is safe.
|
||||
TEST(StratumJobId, ValidSixtyThreeHexCompletesToExactly32Bytes) {
|
||||
std::string jobid(63, 'a');
|
||||
ASSERT_EQ(jobid.size(), 63u);
|
||||
for (char d : HEXDIGITS) {
|
||||
std::vector<unsigned char> vch = ParseHex(jobid + d);
|
||||
ASSERT_EQ(vch.size(), 32u) << "completion '" << d << "'";
|
||||
// 32-byte vector: construction must not trip the size assertion.
|
||||
uint256 h(vch);
|
||||
EXPECT_EQ(h.size(), 32u);
|
||||
}
|
||||
EXPECT_TRUE(AnyCompletionParsesTo32(jobid));
|
||||
}
|
||||
|
||||
// Sanity anchors for ParseHex's odd/even handling that the loop depends on:
|
||||
// an odd hex length drops the trailing nibble (63 hex -> 31 bytes), and one
|
||||
// more hex char fills the 32nd byte (64 hex -> 32 bytes).
|
||||
TEST(StratumJobId, ParseHexOddLengthDropsTrailingNibble) {
|
||||
EXPECT_EQ(ParseHex(std::string(63, 'a')).size(), 31u);
|
||||
EXPECT_EQ(ParseHex(std::string(64, 'a')).size(), 32u);
|
||||
}
|
||||
@@ -1383,8 +1383,14 @@ void hush_configfile(char *symbol,uint16_t rpcport)
|
||||
#endif
|
||||
} else {
|
||||
_hush_userpass(myusername,mypassword,fp);
|
||||
mapArgs["-rpcpassword"] = mypassword;
|
||||
mapArgs["-rpcusername"] = myusername;
|
||||
// Feed the credentials read by InitRPCAuthentication (httprpc.cpp) and the
|
||||
// CLI (bitcoin-cli.cpp) -- both read "-rpcuser"/"-rpcpassword". Use SoftSetArg
|
||||
// so a value passed on the command line (or an explicit -rpcuser/-rpcpassword)
|
||||
// still wins: the old direct assignment silently overwrote a command-line
|
||||
// -rpcpassword on every restart once this conf existed, and the username was
|
||||
// written to a misspelled "-rpcusername" key that nothing ever reads.
|
||||
SoftSetArg("-rpcpassword", mypassword);
|
||||
SoftSetArg("-rpcuser", myusername);
|
||||
fclose(fp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1432,7 +1432,13 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
|
||||
mempool.setSanityCheck(1.0 / ratio);
|
||||
}
|
||||
fCheckBlockIndex = GetBoolArg("-checkblockindex", chainparams.DefaultConsistencyChecks());
|
||||
fCheckpointsEnabled = GetBoolArg("-checkpoints", true);
|
||||
// Regtest inherits the DRAGONX *mainnet* checkpoint set (chainparams_commandline applies it
|
||||
// for every SMART_CHAIN_SYMBOL=="DRAGONX" network, regardless of -regtest), whose top height
|
||||
// ~3.2M would otherwise pin an isolated regtest chain in IsInitialBlockDownload() forever --
|
||||
// disabling the ChainTip auto-ops (autoshield/sweep/consolidation) and the below-checkpoint
|
||||
// script-check skip. Default checkpoints OFF on regtest so a fresh regtest node leaves IBD
|
||||
// normally; still overridable with -checkpoints=1.
|
||||
fCheckpointsEnabled = GetBoolArg("-checkpoints", chainparams.NetworkIDString() != "regtest");
|
||||
|
||||
// -par=0 means autodetect, but nScriptCheckThreads==0 means no concurrency
|
||||
nScriptCheckThreads = GetArg("-par", DEFAULT_SCRIPTCHECK_THREADS);
|
||||
|
||||
@@ -656,9 +656,18 @@ void CustomizeWork(const StratumClient& client, const StratumWork& current_work,
|
||||
LogPrint("stratum", "%s\n", msg);
|
||||
throw std::runtime_error(msg);
|
||||
}
|
||||
if (cb.vout[0].scriptPubKey == (CScript() << OP_FALSE)) {
|
||||
cb.vout[0].scriptPubKey = GetScriptForDestination(addr.Get());
|
||||
// Unconditional. This used to be guarded on the coinbase still carrying the OP_FALSE
|
||||
// placeholder, which made it a no-op for every client after the first once a customized
|
||||
// coinbase had been written back into the shared template -- so those miners silently
|
||||
// mined the first miner's payout address. The template is now left pristine (see
|
||||
// GetWorkUnit), and stamping unconditionally means a coinbase that somehow arrives
|
||||
// already customized can never be inherited by a different miner.
|
||||
if (!addr.IsValid()) {
|
||||
const std::string msg = strprintf("%s: no valid payout address for this client; unable to customize work", __func__);
|
||||
LogPrint("stratum", "%s\n", msg);
|
||||
throw std::runtime_error(msg);
|
||||
}
|
||||
cb.vout[0].scriptPubKey = GetScriptForDestination(addr.Get());
|
||||
}
|
||||
|
||||
// cb_branch = current_work.m_cb_branch;
|
||||
@@ -864,20 +873,24 @@ std::string GetWorkUnit(StratumClient& client)
|
||||
static const std::vector<unsigned char> dummy(32-extranonce1.size(), 0x00); // extranonce2
|
||||
CustomizeWork(client, current_work, client.m_addr, extranonce1, dummy, cb, bf, cb_branch);
|
||||
|
||||
// without 2 lines below equihash solutinon on SubmitWork will be incorrect, bcz we should
|
||||
// change vtx[0] in current work and re-calc hashMerkleRoot
|
||||
// TODO: refactor all of these ... may be change this in current_work directly is bad idea,
|
||||
// and we should do all checks and hashMerkleRoot at SubmitBlock(...)
|
||||
|
||||
current_work.GetBlock().vtx[0] = cb;
|
||||
current_work.GetBlock().hashMerkleRoot = current_work.GetBlock().BuildMerkleTree();
|
||||
|
||||
}
|
||||
|
||||
CBlockHeader blkhdr;
|
||||
// Setup native proof-of-work
|
||||
|
||||
blkhdr = current_work.GetBlock().GetBlockHeader(); // copy entire blockheader created with CreateNewBlock to blkhdr
|
||||
// The shared template MUST keep its pristine OP_FALSE coinbase. This previously did
|
||||
// current_work.GetBlock().vtx[0] = cb;
|
||||
// current_work.GetBlock().hashMerkleRoot = current_work.GetBlock().BuildMerkleTree();
|
||||
// which published one client's coinbase to every other client on the same job: the merkle
|
||||
// root they were told to mine, and the block they eventually submitted, both committed to
|
||||
// the first client's payout address. Derive this client's header from a local copy instead,
|
||||
// which is what the TODO that used to sit here was asking for.
|
||||
{
|
||||
CBlock tmp(current_work.GetBlock());
|
||||
tmp.vtx[0] = cb;
|
||||
blkhdr = tmp.GetBlockHeader();
|
||||
blkhdr.hashMerkleRoot = tmp.BuildMerkleTree();
|
||||
}
|
||||
// CDataStream ds(SER_GETHASH, SERIALIZE_TRANSACTION_NO_WITNESS);
|
||||
CDataStream ds(SER_GETHASH, PROTOCOL_VERSION);
|
||||
ds << cb;
|
||||
@@ -1012,9 +1025,32 @@ bool SubmitBlock(StratumClient& client, const uint256& job_id, const StratumWork
|
||||
: std::vector<unsigned char>(sol.begin() + 3, sol.end());
|
||||
|
||||
blkhdr.hashFinalSaplingRoot = current_work.GetBlock().hashFinalSaplingRoot;
|
||||
blkhdr.hashMerkleRoot = current_work.GetBlock().hashMerkleRoot;
|
||||
// Recompute from the coinbase CustomizeWork() just derived for THIS client. Reading the
|
||||
// shared template's root would be wrong now that the template is left pristine, and was
|
||||
// wrong before too -- it carried whichever client happened to request work first.
|
||||
{
|
||||
CBlock tmp(current_work.GetBlock());
|
||||
tmp.vtx[0] = cb;
|
||||
blkhdr.hashMerkleRoot = tmp.BuildMerkleTree();
|
||||
}
|
||||
blkhdr.nNonce = (uint256) nonce;
|
||||
|
||||
// Cheap SHA256d filter first. This test used to sit *below* the RandomX verify, so 32
|
||||
// arbitrary bytes from any peer bought a full ~65ms randomx_calculate_hash before anything
|
||||
// rejected them -- on the shared HTTP/RPC libevent thread, and holding the global
|
||||
// cs_randomx_validator that block validation also takes. GetHash() is SerializeHash over the
|
||||
// header including nSolution, so passing this costs real grinding. Semantics are unchanged:
|
||||
// an empty local_diff still parses to zero and still rejects, exactly as before.
|
||||
if (!instance_of_cstratumparams.fAllowLowDiffShares &&
|
||||
UintToArith256(blkhdr.GetHash()) > arith_uint256(current_work.local_diff)) {
|
||||
CBlockIndex diff_index;
|
||||
diff_index.nBits = UintToArith256(blkhdr.GetHash()).GetCompact();
|
||||
const double share_diff = GetDifficulty(&diff_index);
|
||||
diff_index.nBits = arith_uint256(current_work.local_diff).GetCompact();
|
||||
const double target_diff = GetDifficulty(&diff_index);
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Low diff share (diff %g, local %g)", share_diff, target_diff));
|
||||
}
|
||||
|
||||
// block is constructed, now it's time to VerifyEH
|
||||
|
||||
if (StratumIsRandomX()) {
|
||||
@@ -1072,10 +1108,8 @@ bool SubmitBlock(StratumClient& client, const uint256& job_id, const StratumWork
|
||||
std::chrono::duration<double, std::milli> elapsed;
|
||||
uint64_t shares_accepted_since_last;
|
||||
|
||||
// TODO: we need to check hash > local port diff, and if it's true -> throw an exception -> diff too low (!)
|
||||
if (!instance_of_cstratumparams.fAllowLowDiffShares)
|
||||
if (UintToArith256(blkhdr.GetHash()) > arith_uint256(current_work.local_diff))
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Low diff share (diff %g, local %g)", hush_real_diff, hush_local_diff));
|
||||
// (the low-diff share check moved above the RandomX verify -- see SubmitBlock's cheap
|
||||
// SHA256d filter -- so that attacker-controlled bytes cannot buy a RandomX hash)
|
||||
|
||||
if (finish > start)
|
||||
{
|
||||
@@ -1323,6 +1357,18 @@ UniValue stratum_mining_submit(StratumClient& client, const UniValue& params)
|
||||
|
||||
const std::string method("mining.submit");
|
||||
BoundParams(method, params, 5,5);
|
||||
|
||||
// Parity with every other handler (GetWorkUnit, mining.aux.*, mining.extranonce.*), which all
|
||||
// refuse an unauthorized client. NOTE this is not authentication: mining.authorize validates no
|
||||
// credential, so it only costs an attacker one extra line. It is here so the submit path cannot
|
||||
// be reached without at least completing the handshake; the cheap-target check below is what
|
||||
// actually bounds the work an unknown peer can force.
|
||||
if (!client.m_authorized && client.m_aux_addr.empty()) {
|
||||
const std::string msg = strprintf("%s: share submitted by an unauthorized client", __func__);
|
||||
LogPrint("stratum", "%s\n", msg);
|
||||
throw JSONRPCError(RPC_INVALID_REQUEST, "Stratum client not authorized. Use mining.authorize first, with a DragonX R.. address as the username or 'x' to mine to the default address.");
|
||||
}
|
||||
|
||||
// First parameter is the client username, which is ignored.
|
||||
|
||||
/* EWBF 31 bytes job_id fix */
|
||||
@@ -1334,7 +1380,16 @@ UniValue stratum_mining_submit(StratumClient& client, const UniValue& params)
|
||||
if (job_id_str.length() == 63) {
|
||||
fEWBFJobIDFixNeeded = true;
|
||||
for(const auto& hexDigit : hexDigits) {
|
||||
ret = uint256(ParseHex(job_id_str + hexDigit));
|
||||
// ParseHex() stops at the first non-hex character and returns a SHORT vector
|
||||
// without signalling an error, and base_blob(const std::vector<unsigned char>&)
|
||||
// asserts vch.size() == 32. Constructing without checking therefore lets any
|
||||
// 63-character job_id containing a non-hex byte abort the daemon -- from an
|
||||
// unauthenticated client, before any other validation. Skip bad candidates
|
||||
// instead; if none of the 16 completions parse, ret stays null, misses
|
||||
// work_templates below, and the handler returns false cleanly.
|
||||
std::vector<unsigned char> vch = ParseHex(job_id_str + hexDigit);
|
||||
if (vch.size() != 32) continue;
|
||||
ret = uint256(vch);
|
||||
if (work_templates.count(ret)) break;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user