From ff44327c23947da8ddc3d674127ed1b29d36dc9b Mon Sep 17 00:00:00 2001 From: Patrick Gray Date: Tue, 29 Jun 2021 16:55:33 -0400 Subject: [PATCH 1/4] fix: typos in Clarity docs --- src/vm/docs/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vm/docs/mod.rs b/src/vm/docs/mod.rs index b87760c9c..e98fc537e 100644 --- a/src/vm/docs/mod.rs +++ b/src/vm/docs/mod.rs @@ -266,7 +266,7 @@ const AND_API: SimpleFunctionAPI = SimpleFunctionAPI { const OR_API: SimpleFunctionAPI = SimpleFunctionAPI { name: None, signature: "(or b1 b2 ...)", - description: "Returns `true` if any boolean inputs are `true`. Importantly, the supplied arguments are evaluated in-order and lazily. Lazy evaluation means that if one of the arguments returns `false`, the function short-circuits, and no subsequent arguments are evaluated.", + description: "Returns `true` if any boolean inputs are `true`. Importantly, the supplied arguments are evaluated in-order and lazily. Lazy evaluation means that if one of the arguments returns `true`, the function short-circuits, and no subsequent arguments are evaluated.", example: "(or true false) ;; Returns true (or (is-eq (+ 1 2) 1) (is-eq 4 4)) ;; Returns true (or (is-eq (+ 1 2) 1) (is-eq 3 4)) ;; Returns false @@ -426,7 +426,7 @@ which must return the same type. In the case that the boolean input is `true`, t }; const LET_API: SpecialAPI = SpecialAPI { - input_type: "((name2 AnyType) (name2 AnyType) ...), AnyType, ... A", + input_type: "((name1 AnyType) (name2 AnyType) ...), AnyType, ... A", output_type: "A", signature: "(let ((name1 expr1) (name2 expr2) ...) expr-body1 expr-body2 ... expr-body-last)", description: "The `let` function accepts a list of `variable name` and `expression` pairs, From 5c092033e9cc74b4162baef7a71a0bd4e8836791 Mon Sep 17 00:00:00 2001 From: Patrick Gray Date: Tue, 13 Jul 2021 08:24:46 -0400 Subject: [PATCH 2/4] docs: rupdate testnet information and run lint --- README.md | 45 +++++++++++++++++++-------------------------- 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 826b4ab18..c47a5176c 100644 --- a/README.md +++ b/README.md @@ -8,14 +8,14 @@ Stacks 2.0 is a layer-1 blockchain that connects to Bitcoin for security and ena ## Repository -| Blockstack Topic/Tech | Where to learn more more | -| ------------------------ | --------------------------------------------------------------------------------- | -| Stacks 2.0 | [master branch](https://github.com/blockstack/stacks-blockchain/tree/master) | -| Stacks 1.0 | [legacy branch](https://github.com/blockstack/stacks-blockchain/tree/stacks-1.0) | -| Use the package | [our core docs](https://docs.blockstack.org/core/naming/introduction.html) | -| Develop a Blockstack App | [our developer docs](https://docs.blockstack.org/browser/hello-blockstack.html) | -| Use a Blockstack App | [our browser docs](https://docs.blockstack.org/browser/browser-introduction.html) | -| Blockstack PBC the company | [our website](https://blockstack.org) | +| Blockstack Topic/Tech | Where to learn more more | +| -------------------------- | --------------------------------------------------------------------------------- | +| Stacks 2.0 | [master branch](https://github.com/blockstack/stacks-blockchain/tree/master) | +| Stacks 1.0 | [legacy branch](https://github.com/blockstack/stacks-blockchain/tree/stacks-1.0) | +| Use the package | [our core docs](https://docs.blockstack.org/core/naming/introduction.html) | +| Develop a Blockstack App | [our developer docs](https://docs.blockstack.org/browser/hello-blockstack.html) | +| Use a Blockstack App | [our browser docs](https://docs.blockstack.org/browser/browser-introduction.html) | +| Blockstack PBC the company | [our website](https://blockstack.org) | ## Release Schedule and Hotfixes @@ -54,12 +54,12 @@ to upgrade to `2.0.10.1.0` or `2.0.10.0.1`. However, upgrading to `2.0.11.0.0` w - [x] [SIP 001: Burn Election](https://github.com/stacksgov/sips/blob/main/sips/sip-001/sip-001-burn-election.md) - [x] [SIP 002: Clarity, a language for predictable smart contracts](https://github.com/stacksgov/sips/blob/main/sips/sip-002/sip-002-smart-contract-language.md) -- [X] [SIP 003: Peer Network](https://github.com/stacksgov/sips/blob/main/sips/sip-003/sip-003-peer-network.md) +- [x] [SIP 003: Peer Network](https://github.com/stacksgov/sips/blob/main/sips/sip-003/sip-003-peer-network.md) - [x] [SIP 004: Cryptographic Committment to Materialized Views](https://github.com/stacksgov/sips/blob/main/sips/sip-004/sip-004-materialized-view.md) - [x] [SIP 005: Blocks, Transactions, and Accounts](https://github.com/stacksgov/sips/blob/main/sips/sip-005/sip-005-blocks-and-transactions.md) -- [X] [SIP 006: Clarity Execution Cost Assessment](https://github.com/stacksgov/sips/blob/main/sips/sip-006/sip-006-runtime-cost-assessment.md) +- [x] [SIP 006: Clarity Execution Cost Assessment](https://github.com/stacksgov/sips/blob/main/sips/sip-006/sip-006-runtime-cost-assessment.md) - [x] [SIP 007: Stacking Consensus](https://github.com/stacksgov/sips/blob/main/sips/sip-007/sip-007-stacking-consensus.md) -- [X] [SIP 008: Clarity Parsing and Analysis Cost Assessment](https://github.com/stacksgov/sips/blob/main/sips/sip-008/sip-008-analysis-cost-assessment.md) +- [x] [SIP 008: Clarity Parsing and Analysis Cost Assessment](https://github.com/stacksgov/sips/blob/main/sips/sip-008/sip-008-analysis-cost-assessment.md) Stacks improvement proposals (SIPs) are aimed at describing the implementation of the Stacks blockchain, as well as proposing improvements. They should contain concise technical specifications of features or standards and the rationale behind it. SIPs are intended to be the primary medium for proposing new features, for collecting community input on a system-wide issue, and for documenting design decisions. @@ -69,19 +69,11 @@ The SIPs are now located in the [stacksgov/sips](https://github.com/stacksgov/si ### Testnet versions -- [x] **Helium** is a developer local setup, mono-node, assembling SIP 001, SIP 002, SIP 004 and SIP 005. With this version, developers can not only run Stacks 2.0 on their development machines, but also write, execute, and test smart contracts. See the instructions below for more details. +- [x] **Krypton** is a testnet with a fixed, two-minute block time. Regtest is generally unstable for regular use, and is reset often. See the [regtest documentation](https://docs.stacks.co/understand-stacks/testnet) for more information on using regtest. -- [X] **Neon** is the first version of our public testnet, which shipped in Q2 2020. This testnet added SIP 003, and will be an open-membership public network, where participants will be able to validate and participate in mining testnet blocks. +- [x] **Xenon** is the Stacks 2.0 public testnet, which runs on the Bitcoin testnet. It is the full implementation of the Stacks 2.0 blockchain, and should be considered a stable testnet for developing Clarity smart contracts. See the [testnet documentation](https://docs.stacks.co/understand-stacks/testnet) for more information on the public testnet. -- [X] **Argon** is the second version of our public testnet, which shipped in Q2 2020. This testnet improved on the stability of the Neon testnet. - -- [X] **Krypton** is the third version of our public testnet, which incorporates a partial implementation of SIP 007. It allows developers to test a simple version of Stacking and PoX consensus. - -- [X] **Xenon** is the upcoming version of our public testnet, which will run on the Bitcoin testnet. It will include SIP 006 and SIP 008, and will contain bugfixes and improvements to the implementation of SIP 007. - -- [X] **Mainnet** is the fully functional version, that is estimated for Q4 2020. - -See the [testnet website](https://testnet.blockstack.org) and ["when mainnet?" FAQ](https://github.com/blockstack/stacks/blob/master/whenmainnet.md) for details. +- [x] **Mainnet** is the fully functional Stacks 2.0 blockchain, see the [Stacks overview](https://docs.stacks.co/understand-stacks/overview) for information on running a Stacks node, mining, stacking, and writing Clarity smart contracts. ## Getting started @@ -129,7 +121,8 @@ cargo run --bin blockstack-cli generate-sk --testnet # stacksAddress: "ST2ZRX0K27GW0SP3GJCEMHD95TQGJMKB7G9Y0X1MH" # } ``` -This keypair is already registered in the `testnet-follower-conf.toml` file, so it can be used as presented here. + +This keypair is already registered in the `testnet-follower-conf.toml` file, so it can be used as presented here. We will interact with the following simple contract `kv-store`. In our examples, we will assume this contract is saved to `./kv-store.clar`: @@ -162,7 +155,7 @@ With the following arguments: cargo run --bin blockstack-cli publish b8d99fd45da58038d630d9855d3ca2466e8e0f89d3894c4724f0efc9ff4b51f001 515 0 kv-store ./kv-store.clar --testnet ``` -The `515` is the transaction fee, denominated in microSTX. Right now, the +The `515` is the transaction fee, denominated in microSTX. Right now, the testnet requires one microSTX per byte minimum, and this transaction should be less than 515 bytes. The third argument `0` is a nonce, that must be increased monotonically with each new transaction. @@ -181,7 +174,7 @@ You can observe the state machine in action locally by running: cargo testnet start --config=./testnet/stacks-node/conf/testnet-follower-conf.toml ``` -`testnet-follower-conf.toml` is a configuration file that you can use for setting genesis balances or configuring Event observers. You can grant an address an initial account balance by adding the following entries: +`testnet-follower-conf.toml` is a configuration file that you can use for setting genesis balances or configuring Event observers. You can grant an address an initial account balance by adding the following entries: ``` [[ustx_balance]] @@ -190,7 +183,7 @@ amount = 100000000 ``` The `address` field is the Stacks testnet address, and the `amount` field is the -number of microSTX to grant to it in the genesis block. The addresses of the +number of microSTX to grant to it in the genesis block. The addresses of the private keys used in the tutorial below are already added. ### Publish your contract From a4ce2c3da71a5ed9c0e24a7117dd75810d86e2ad Mon Sep 17 00:00:00 2001 From: Patrick Gray Date: Wed, 14 Jul 2021 09:57:48 -0400 Subject: [PATCH 3/4] docs: add regtest name and address reviewer comments --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c47a5176c..ffcf51ad7 100644 --- a/README.md +++ b/README.md @@ -69,11 +69,11 @@ The SIPs are now located in the [stacksgov/sips](https://github.com/stacksgov/si ### Testnet versions -- [x] **Krypton** is a testnet with a fixed, two-minute block time. Regtest is generally unstable for regular use, and is reset often. See the [regtest documentation](https://docs.stacks.co/understand-stacks/testnet) for more information on using regtest. +- [x] **Krypton** is a Stacks 2 testnet with a fixed, two-minute block time, called `regtest`. Regtest is generally unstable for regular use, and is reset often. See the [regtest documentation](https://docs.stacks.co/understand-stacks/testnet) for more information on using regtest. -- [x] **Xenon** is the Stacks 2.0 public testnet, which runs on the Bitcoin testnet. It is the full implementation of the Stacks 2.0 blockchain, and should be considered a stable testnet for developing Clarity smart contracts. See the [testnet documentation](https://docs.stacks.co/understand-stacks/testnet) for more information on the public testnet. +- [x] **Xenon** is the Stacks 2 public testnet, which runs PoX against the Bitcoin testnet. It is the full implementation of the Stacks 2 blockchain, and should be considered a stable testnet for developing Clarity smart contracts. See the [testnet documentation](https://docs.stacks.co/understand-stacks/testnet) for more information on the public testnet. -- [x] **Mainnet** is the fully functional Stacks 2.0 blockchain, see the [Stacks overview](https://docs.stacks.co/understand-stacks/overview) for information on running a Stacks node, mining, stacking, and writing Clarity smart contracts. +- [x] **Mainnet** is the fully functional Stacks 2 blockchain, see the [Stacks overview](https://docs.stacks.co/understand-stacks/overview) for information on running a Stacks node, mining, stacking, and writing Clarity smart contracts. ## Getting started From 63d24b1185bca6a35988aa90667bc5f0a226acb9 Mon Sep 17 00:00:00 2001 From: Gregory Coppola <60008382+gregorycoppola@users.noreply.github.com> Date: Mon, 2 Aug 2021 10:45:03 -0500 Subject: [PATCH 4/4] Release 2.0.11.2.0 (#2750) * feat: add lcov compatible coverage reporting to clarity-cli * make lcov output configurable * chore: cleanup/refactor miner tenure information gathering * chore: updates for latest bitcoind * typo * fix: ensure microblocks mined by the node itself are emitted to the event observer * feat: emit microblock header hash to event observer, required for micro-fork detection * feat: include microblock parent hash in /new_microblocks event paylaod * test: added test to ensure microblock hashes were properly added to the mb event * fix: #2668 microblock data missing from event emitter while syncing * Added prev burn block stats to the new_block event * Added burn block stats for new_microblock event; refactored variable names for burn block stats for the new_block event * Switched using to using for all the burn block stats, handled getting burn block stats in the case that the parent_consensus_hash was the * Add some string-ascii and string-utf8 types to the docs * ran format * Changed panic! to warn! statement * feat: add output_serialized to clarity-cli * added some additional examples * added all the different examples * added concat example * feat: make eval_all function public (#2689) * running format * Addressed comments. * Addressed failing tests * Use a virtual type 'sequence' to condense complicated disjoined types Instead of having long lists of disjoined types, e.g., buff|(list A)|string-ascii|string-utf8 We now condense these to being a 'virtual' type called 'sequence', which doesn't actually exist in the Clarity type hierarchy, but is used in the docs, and which is explained in each description in which it is used. * Ran 'cargo fmt' * Fixed some signatures Some signatures were inconsistent with the input/output types. * Fixed one more signature This changes a signature that antedates this PR to be more consistent. * Re-worked the types and signatures Various changes to the focus functions were made for clarity and consistency. * fix: #2644 reorder UTXO staleness and RBF-limit check * Addressing comments by @pavitthrap * Fixes build warnings for 'cargo build' in 'develop' branch * Added new-microblocks event to event dispatcher docs * chore: remove tini dependency * Fixed a vm:docs test case * Fixed some typos * removed some files that aren't in 'develop' * Trying to rever CHANGELOG.md * Reverting files to the 'develop' branch versions * fix: logic bug in mocknet/helium miner #2710 * Removed a TODO, the question it relates to is now in issue #2712 * Removed a trailing space character. * chore: try to speed up some integration tests * chore: remove infinite loop in microblock_integration test * test: use wait_for_microblocks rather than a wait_ms * dont assert microblocks bumped -- it could have been bumped before the call to wait_for_microblocks * test: wait for microblock to be processed before fetching /v2/info, use loop on wait_for_microblocks * test: oops, flip equality in /v2/info loop * docs: adding release process to README.md * docs: add info on creating release candidate, and Hiro testing envs * chore: genesis accounts key rotation * chore: update magic bytes (X6 to T2) * chore: update POX constants * chore: update genesis anchoring (block #2000000) * chore: update testnet command * fix: address regressions * chore: cargo fmt * chore: update readme * fix: addressing regression in integration test * fix: silence non-fmt panic warnings * fix: remove potentiall runtime panic that can arise when a sortition is queried while it is in the process of being invalidated * fix: disable non-fmt panic warnings * fix: disable non-fmt panic warnings * refactor: use PeerNetwork::with_http() to expose the underlying HttpPeer * fix: when loading block headers, if we encounter an InvalidPoxSortition error from the GetBlocksInv handler, reply a NACK instead of erroring out * feat: query the list of bootstrap nodes distinct from always-allowed nodes * fix: address #2730 by considering whether or not we are in the IBD phase when choosing where to start scanning for new block downloads. Also, use the PeerNetwork::with_http() interface to access the inner HTTP peer when downloading blocks and microblocks. * fix: address #2728 by only rescanning a neighbor's inventory from just before the reward cycle it reported that the local peer diverged. Also, record this sortition height for the block downloader's consumption. * fix: address #2719 by introducing a Transient(..) error type which indicates that a network error can be resolved by simply re-trying the poll loop. * refactor: make the HTTP peer an Optional<..> so the PeerNetwork instance can be passed to the HTTP handler code. Also, do a better job at propagating hints from the inventory state-machine to the downloader as part of #2730. * refactor: use new test API for adding peers * fix: take a PeerNetwork as an argument for handling requests, and address #2738 by way of using cached data in the PeerNetwork to construct a /v2/info response without doing any I/O * refactor: take a PeerNetwork instance as an argument instead of a hodge-podge of fields from it, so it can be passed into the HTTP request handler * fix: address #2701 by catching Transient(..) errors and simply logging them * fix: address #2713 by starting processing from the first burnchain block of the reward cycle in which the canonical Stacks chain state starts * refactor: use latest rust-ism for spin loops * fix: fix compiler errors in unit tests * fix: more compile-time errors * docs: address PR feedback * fix: update the cached burnchain view *before* servicing HTTP requests, so /v2/info acts on the *current* chain view * Update README.md Co-authored-by: Ludo Galabru * Update README.md Co-authored-by: Ludo Galabru * Update README.md Co-authored-by: Ludo Galabru * docs: add optionality on when to open develop->master PR * fix: sync one more blocks inventory on PoX disagreement so we don't stall, and use the cached burnchain_tip in PeerNetwork to determine whether or not the PoX bitvector needs to be reloaded. In addition, use the _new_ PoX bitvector length to carry out an inv trucation if it's shorter * fix: refresh cached sortition state before doing anything else in the peer network run loop * fix: mask net_error::Transient in the p2p state machine so we don't accidentally drop receipts * fix: always panic on network error, now that the network masks transient errors * fix: re-enable non_fmt_panic lint * style: make bufflength instance variable private * fix: only set the block/microblock start sortition if the downloader isn't in the middle of processing blocks * fix: when starting a block inventory scan from a remote bootstrap peer that's diverged from us, start at either the reward cycle that contains the highest processed Stacks block, or INV_REWARD_CYCLES fewer reward cycles than the diverged reward cycle -- whichever is lower * fix: when starting a downloader pass, start from the sortition height of either the highest processed Stacks block, or the inventory sortition height hint from the inv state machine -- whichever is lower * chore: address feedbacks * fix: use constant * chore: fix all warns in `cargo check --tests --workspace` * chore: simplify fmt/asserts * Add Changelog for 2.0.11.2.0 (#2760) * changelog for 2.0.11.2.0 * fixed spelling error * fixed grammar error in changelog * fix: issue 2771 * fix: tests::neon_integrations::bitcoind_forking_test * fix: move log * fix: move comment * feat(docs): Add to "Non-Consensus Breaking Release Process" (#2766) * changelog for 2.0.11.2.0 * start to discuss the version number * changelog section * fixed the wording around conensus breaking change * updates to the changelog section * added to release timing. * added to timing section * fix: test + patch * chore: cargo fmt * fix: edge case * fix: restore timeout * chore: add comments * fix: revert patch * chore: cargo fmt * Update testnet/stacks-node/src/tests/neon_integrations.rs * Update testnet/stacks-node/src/tests/neon_integrations.rs * Update testnet/stacks-node/src/tests/neon_integrations.rs * fix: bitcoind_forking_test assertions added * fix: bitcoind_forking_test assertions added * Update src/burnchains/burnchain.rs * fix: only consider target block if relevant * chore: cargo fmt * chore: add log when ignoring target * Update src/burnchains/burnchain.rs * Update src/burnchains/burnchain.rs * fix: log * fix: switched all occurrences of max_value/min_value to MAX/MIN because it will deprecated in a future version of Rust * feat(Changelog) Add some PR's that got missed for 2.0.11.2.0 (#2789) * added micro-blocks PR to the changelog * add PR 2647 * removed a space * added some notes about the reset of the testnet * "master -> develop" ahead of Release 2.0.11.2.0 (#2796) * fix: typos in Clarity docs * docs: rupdate testnet information and run lint * docs: add regtest name and address reviewer comments Co-authored-by: Patrick Gray Co-authored-by: pavitthrap * feat(Changelog): Adds in two patches to the Changelog (#2795) * Add Ludo's bug fixes to the change log. * removed extra space Co-authored-by: Aaron Blankstein Co-authored-by: Matthew Little Co-authored-by: Pavitthra Pandurangan Co-authored-by: Greg Coppola Co-authored-by: Hank Stoever Co-authored-by: Reed Rosenbluth <331327+reedrosenbluth@users.noreply.github.com> Co-authored-by: Jude Nelson Co-authored-by: CharlieC3 <2747302+CharlieC3@users.noreply.github.com> Co-authored-by: Ludo Galabru Co-authored-by: Patrick Gray --- .../Dockerfile.bitcoin-tests | 1 + CHANGELOG.md | 41 ++ Cargo.lock | 7 - Cargo.toml | 1 - README.md | 87 ++- deployment/helm/stacks-blockchain/values.yaml | 16 +- docs/event-dispatcher.md | 70 ++- src/burnchains/bitcoin/indexer.rs | 231 +------- src/burnchains/burnchain.rs | 102 ++-- src/burnchains/indexer.rs | 7 - src/burnchains/mod.rs | 12 +- src/chainstate/burn/db/sortdb.rs | 11 +- src/chainstate/burn/mod.rs | 40 +- src/chainstate/coordinator/comm.rs | 4 +- src/chainstate/coordinator/mod.rs | 6 + src/chainstate/coordinator/tests.rs | 7 +- src/chainstate/stacks/boot/contract_tests.rs | 13 +- src/chainstate/stacks/boot/mod.rs | 44 +- src/chainstate/stacks/boot/pox-testnet.clar | 8 +- src/chainstate/stacks/db/accounts.rs | 8 +- src/chainstate/stacks/db/blocks.rs | 102 ++-- src/chainstate/stacks/db/headers.rs | 22 +- src/chainstate/stacks/db/mod.rs | 5 +- src/chainstate/stacks/db/transactions.rs | 10 +- src/chainstate/stacks/db/unconfirmed.rs | 40 +- src/chainstate/stacks/events.rs | 3 +- src/chainstate/stacks/miner.rs | 4 +- src/clarity.rs | 44 +- src/clarity_vm/clarity.rs | 8 +- src/codec/mod.rs | 2 +- src/core/mempool.rs | 2 +- src/core/mod.rs | 38 +- src/deps/bitcoin/network/encodable.rs | 12 +- src/lib.rs | 1 - src/main.rs | 25 +- src/net/atlas/download.rs | 76 +-- src/net/chat.rs | 2 +- src/net/db.rs | 19 +- src/net/download.rs | 531 ++++++++++-------- src/net/http.rs | 70 ++- src/net/inv.rs | 421 +++++++++----- src/net/mod.rs | 11 +- src/net/p2p.rs | 235 +++++--- src/net/poll.rs | 10 +- src/net/relay.rs | 15 +- src/net/rpc.rs | 167 ++---- src/net/server.rs | 63 +-- src/types/proof.rs | 2 +- src/util/db.rs | 4 +- src/util/retry.rs | 8 +- src/util/uint.rs | 2 +- src/vm/analysis/errors.rs | 2 +- src/vm/analysis/type_checker/natives/mod.rs | 2 +- src/vm/contexts.rs | 17 +- src/vm/costs/mod.rs | 22 +- src/vm/coverage.rs | 224 ++++++++ src/vm/database/sqlite.rs | 12 +- src/vm/docs/mod.rs | 172 ++++-- src/vm/functions/arithmetic.rs | 2 +- src/vm/functions/crypto.rs | 24 +- src/vm/mod.rs | 16 +- src/vm/tests/assets.rs | 2 +- src/vm/tests/simple_apply_eval.rs | 20 +- src/vm/types/mod.rs | 4 +- src/vm/types/serialization.rs | 12 +- src/vm/types/signatures.rs | 37 +- .../conf/regtest-follower-conf.toml | 8 +- .../conf/testnet-follower-conf.toml | 8 +- .../stacks-node/conf/testnet-miner-conf.toml | 8 +- .../burnchains/bitcoin_regtest_controller.rs | 24 +- testnet/stacks-node/src/config.rs | 154 +---- testnet/stacks-node/src/event_dispatcher.rs | 63 ++- testnet/stacks-node/src/main.rs | 16 +- testnet/stacks-node/src/neon_node.rs | 246 ++++---- testnet/stacks-node/src/node.rs | 37 +- testnet/stacks-node/src/run_loop/neon.rs | 39 +- testnet/stacks-node/src/syncctl.rs | 8 +- .../src/tests/neon_integrations.rs | 288 +++++++--- 78 files changed, 2400 insertions(+), 1737 deletions(-) create mode 100644 src/vm/coverage.rs diff --git a/.github/actions/bitcoin-int-tests/Dockerfile.bitcoin-tests b/.github/actions/bitcoin-int-tests/Dockerfile.bitcoin-tests index 9fa7f6183..0a29e968e 100644 --- a/.github/actions/bitcoin-int-tests/Dockerfile.bitcoin-tests +++ b/.github/actions/bitcoin-int-tests/Dockerfile.bitcoin-tests @@ -21,6 +21,7 @@ RUN cargo test -- --test-threads 1 --ignored tests::neon_integrations::bitcoind_ RUN cargo test -- --test-threads 1 --ignored tests::neon_integrations::liquid_ustx_integration RUN cargo test -- --test-threads 1 --ignored tests::neon_integrations::stx_transfer_btc_integration_test RUN cargo test -- --test-threads 1 --ignored tests::neon_integrations::bitcoind_forking_test +RUN cargo test -- --test-threads 1 --ignored tests::neon_integrations::should_fix_2771 RUN cargo test -- --test-threads 1 --ignored tests::neon_integrations::pox_integration_test RUN cargo test -- --test-threads 1 --ignored tests::bitcoin_regtest::bitcoind_integration_test RUN cargo test -- --test-threads 1 --ignored tests::should_succeed_handling_malformed_and_valid_txs diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e036e048..9d93bdbdb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,47 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to the versioning scheme outlined in the [README.md](README.md). +## [2.0.11.2.0] + +NOTE: This change resets the `testnet`. Users running a testnet node will need +to reset their chain states. + +### Added + +- `clarity-cli` will now also print a serialized version of the resulting + output from `eval` and `execute` commands. This serialization is in + hexademical string format and supports integration with other tools. (#2684) +- The creation of a Bitcoin wallet with BTC version `> 0.19` is now supported + on a private testnet. (#2647) +- `lcov`-compatible coverage reporting has been added to `clarity-cli` for + Clarity contract testing. (#2592) +- The `README.md` file has new documentation about the release process. (#2726) + +### Changed + +- This change resets the testnet. (#2742) +- Caching has been added to speed up `/v2/info` responses. (#2746) + +### Fixed + +- PoX syncing will only look back to the reward cycle prior to divergence, + instead of looking back over all history. This will speed up running a + follower node. (#2746) +- The UTXO staleness check is re-ordered so that it occurs before the RBF-limit + check. This way, if stale UTXOs reached the "RBF limit" a miner will recover + by resetting the UTXO cache. (#2694) +- Microblock events were being sent to the event observer when microblock data + was received by a peer, but were not emitted if the node mined the + microblocks itself. This made something like the private-testnet setup + incapable of emitting microblock events. Microblock events are now sent + even when self-mined. (#2653) +- A bug is fixed in the mocknet/helium miner that would lead to a panic if a + burn block occurred without a sortition in it. (#2711) +- Two bugs that caused problems syncing with the bitcoin chain during a + bitcoin reorg have been fixed (#2771, #2780). +- Documentation is fixed in cases where string and buffer types are allowed + but not covered in the documentation. (#2676) + ## [2.0.11.1.0] This software update is our monthly release. It introduces fixes and features for both developers and miners. diff --git a/Cargo.lock b/Cargo.lock index a7daf0409..e4e81e9f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -263,7 +263,6 @@ dependencies = [ "slog-term", "stx-genesis", "time 0.2.23", - "tini", "url", ] @@ -2446,12 +2445,6 @@ dependencies = [ "syn", ] -[[package]] -name = "tini" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11eeaa68267376df2aacbaaed9b0092544ebbc897cd59f61e81a1105fbaf102e" - [[package]] name = "tinyvec" version = "0.3.3" diff --git a/Cargo.toml b/Cargo.toml index f3ef5a137..dabcd856e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,6 @@ name = "block_limits" harness = false [dependencies] -tini = "0.2" rand = "=0.7.2" rand_chacha = "=0.2.2" serde = "1" diff --git a/README.md b/README.md index ffcf51ad7..9dbb1c6a0 100644 --- a/README.md +++ b/README.md @@ -171,7 +171,7 @@ cargo run --bin blockstack-cli publish b8d99fd45da58038d630d9855d3ca2466e8e0f89d You can observe the state machine in action locally by running: ```bash -cargo testnet start --config=./testnet/stacks-node/conf/testnet-follower-conf.toml +cargo stacks-node start --config=./testnet/stacks-node/conf/testnet-follower-conf.toml ``` `testnet-follower-conf.toml` is a configuration file that you can use for setting genesis balances or configuring Event observers. You can grant an address an initial account balance by adding the following entries: @@ -317,6 +317,91 @@ You can automatically reformat your commit via: cargo fmt --all ``` +## Non-Consensus Breaking Release Process + +For non-consensus breaking releases, this project uses the following release process: + +1. The release must be timed so that it does not interfere with a *prepare +phase*. The timing of the next Stacking cycle can be found +[here](https://stacking.club/cycles/next). A release to `mainnet` should happen +at least 24 hours before the start of a new cycle, to avoid interfering +with the prepare phase. So, start by being aware of when the release can +happen. + +1. Before creating the release, the release manager must determine the *version +number* for this release. The factors that determine the version number are +discussed in [Versioning](#versioning). We assume, in this section, +that the change is not consensus-breaking. So, the release manager must first +determine whether there are any "non-consensus-breaking changes that require a +fresh chainstate". This means, in other words, that the database schema has +changed. Then, the release manager should determine whether this is a feature +release, as opposed to a hot fix or a patch. Given the answers to these +questions, the version number can be computed. + +1. The release manager enumerates the PRs or issues that would _block_ + the release. A label should be applied to each such issue/PR as + `2.0.x.y.z-blocker`. The release manager should ping these + issue/PR owners for updates on whether or not those issues/PRs have + any blockers or are waiting on feedback. + +1. The release manager should open a `develop -> master` PR. This can be done before + all the blocker PRs have merged, as it is helpful for the manager and others + to see the staged changes. + +1. The release manager must update the `CHANGELOG.md` file with summaries what +was `Added`, `Changed`, and `Fixed`. The pull requests merged into `develop` +can be found +[here](https://github.com/blockstack/stacks-blockchain/pulls?q=is%3Apr+is%3Aclosed+base%3Adevelop+sort%3Aupdated-desc). Note, however, that GitHub apparently does not allow sorting by +*merge time*, so, when sorting by some proxy criterion, some care should +be used to understand which PR's were *merged* after the last `develop -> +master` release PR. This `CHANGELOG.md` should also be used as the description +of the `develop -> master` so that it acts as *release notes* when the branch +is tagged. + +1. Once the blocker PRs have merged, the release manager will create a new tag + by manually triggering the [`stacks-blockchain` Github Actions workflow](https://github.com/blockstack/stacks-blockchain/actions/workflows/stacks-blockchain.yml) + against the `develop` branch, inputting the release candidate tag, `2.0.x.y.z-rc0`, + in the Action's input textbox. + +1. Once the release candidate has been built, and docker images, etc. are available, + the release manager will notify various ecosystem participants to test the release + candidate on various staging infrastructure: + + 1. Stacks Foundation staging environments. + 1. Hiro PBC regtest network. + 1. Hiro PBC testnet network. + 1. Hiro PBC mainnet mock miner. + + The release candidate should be announced in the `#stacks-core-devs` channel in the + Stacks Discord. For coordinating rollouts on specific infrastructure, the release + manager should contact the above participants directly either through e-mail or + Discord DM. The release manager should also confirm that the built release on the + [Github releases](https://github.com/blockstack/stacks-blockchain/releases/) + page is marked as `Pre-Release`. + +1. The release manager will test that the release candidate successfully syncs with + the current chain from genesis both in testnet and mainnet. This requires starting + the release candidate with an empty chainstate and confirming that it synchronizes + with the current chain tip. + +1. If bugs or issues emerge from the rollout on staging infrastructure, the release + will be delayed until those regressions are resolved. As regressions are resolved, + additional release candidates should be tagged. The release manager is responsible + for updating the `develop -> master` PR with information about the discovered issues, + even if other community members and developers may be addressing the discovered + issues. + +1. Once the final release candidate has rolled out successfully without issue on the + above staging infrastructure, the release manager tags 2 additional `stacks-blockchain` + team members to review the `develop -> master` PR. + +1. Once reviewed and approved, the release manager merges the PR, and tags the release + via the [`stacks-blockchain` Github action]((https://github.com/blockstack/stacks-blockchain/actions/workflows/stacks-blockchain.yml)) + by clicking "Run workflow" and providing the release version as the tag (e.g., + `2.0.11.1.0`) This creates a release and release images. Once the release has been + created, the release manager should update the Github release text with the + `CHANGELOG.md` "top-matter" for the release. + ## Copyright and License The code and documentation copyright are attributed to blockstack.org for the year of 2020. diff --git a/deployment/helm/stacks-blockchain/values.yaml b/deployment/helm/stacks-blockchain/values.yaml index 21cbd0a30..24d39c4ef 100644 --- a/deployment/helm/stacks-blockchain/values.yaml +++ b/deployment/helm/stacks-blockchain/values.yaml @@ -231,13 +231,13 @@ config: rpc_port: 18443 peer_port: 18444 ustx_balance: - - address: STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6 + - address: ST2QKZ4FKHAH1NQKYKYAYZPY440FEPK7GZ1R5HBP2 amount: "10000000000000000" - - address: ST11NJTTKGVT6D1HY4NJRVQWMQM7TVAR091EJ8P2Y + - address: ST319CF5WV77KYR1H3GT0GZ7B8Q4AQPY42ETP1VPF amount: "10000000000000000" - - address: ST1HB1T8WRNBYB0Y3T7WXZS38NKKPTBR3EG9EPJKR + - address: ST221Z6TDTC5E0BYR2V624Q2ST6R0Q71T78WTAX6H amount: "10000000000000000" - - address: STRYYQQ9M8KAF4NS7WNZQYY59X93XEKR31JP64CP + - address: ST2TFVBMRPS5SSNP98DQKQ5JNB2B6NZM91C4K3P7B amount: "10000000000000000" ## Uncommenting this block will give you greater control over the settings in the configmap @@ -262,16 +262,16 @@ config: # peer_port = 18444 # [[ustx_balance]] - # address = "STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6" + # address = "ST2QKZ4FKHAH1NQKYKYAYZPY440FEPK7GZ1R5HBP2" # amount = 10000000000000000 # [[ustx_balance]] - # address = "ST11NJTTKGVT6D1HY4NJRVQWMQM7TVAR091EJ8P2Y" + # address = "ST319CF5WV77KYR1H3GT0GZ7B8Q4AQPY42ETP1VPF" # amount = 10000000000000000 # [[ustx_balance]] - # address = "ST1HB1T8WRNBYB0Y3T7WXZS38NKKPTBR3EG9EPJKR" + # address = "ST221Z6TDTC5E0BYR2V624Q2ST6R0Q71T78WTAX6H" # amount = 10000000000000000 # [[ustx_balance]] - # address = "STRYYQQ9M8KAF4NS7WNZQYY59X93XEKR31JP64CP" + # address = "ST2TFVBMRPS5SSNP98DQKQ5JNB2B6NZM91C4K3P7B" # amount = 10000000000000000 ## Annotations to be added to the Configmap diff --git a/docs/event-dispatcher.md b/docs/event-dispatcher.md index 1826a7eff..e3b704839 100644 --- a/docs/event-dispatcher.md +++ b/docs/event-dispatcher.md @@ -27,6 +27,9 @@ These events are sent to the configured endpoint at two URLs: This payload includes data related to a newly processed block, and any events emitted from Stacks transactions during the block. +If the transaction originally comes from the parent microblock stream +preceding this block, the microblock related fields will be filled in. + Example: ```json @@ -58,7 +61,10 @@ Example: "raw_tx": "0x808000000004008bc5147525b8f477f0bc4522a88c8339b2494db50000000000000002000000000000000001015814daf929d8700af344987681f44e913890a12e38550abe8e40f149ef5269f40f4008083a0f2e0ddf65dcd05ecfc151c7ff8a5308ad04c77c0e87b5aeadad31010200000000040000000000000000000000000000000000000000000000000000000000000000", "status": "success", "tx_index": 0, - "txid": "0x3e04ada5426332bfef446ba0a06d124aace4ade5c11840f541bf88e2e919faf6" + "txid": "0x3e04ada5426332bfef446ba0a06d124aace4ade5c11840f541bf88e2e919faf6", + "microblock_sequence": "None", + "microblock_hash": "None", + "microblock_parent_hash": "None" }, { "contract_abi": null, @@ -66,7 +72,10 @@ Example: "raw_tx": "0x80800000000400f942874ce525e87f21bbe8c121b12fac831d02f4000000000000000000000000000003e800006ae29867aec4b0e4f776bebdcea7f6d9a24eeff370c8c739defadfcbb52659b30736ad4af021e8fb741520a6c65da419fdec01989fdf0032fc1838f427a9a36102010000000000051ac2d519faccba2e435f3272ff042b89435fd160ff00000000000003e800000000000000000000000000000000000000000000000000000000000000000000", "status": "success", "tx_index": 1, - "txid": "0x738e4d44636023efa08374033428e44eca490582bd39a6e61f3b6cf749b4214c" + "txid": "0x738e4d44636023efa08374033428e44eca490582bd39a6e61f3b6cf749b4214c", + "microblock_sequence": "3", + "microblock_hash": "0x9304fcbcc6daf5ac3f264522e0df50eddb5be85df6ee8a9fc2384c54274daaac", + "microblock_parent_hash": "0x4893ab44636023efa08374033428e44eca490582bd39a6e61f3b6cf749b474bd" } ], "matured_miner_rewards": [ @@ -116,6 +125,63 @@ Example: PoX commitments during this block. These addresses may not actually receive rewards during this block if the block is faster than miners have an opportunity to commit. +### `POST /new_microblocks` + +This payload includes data related to one or more microblocks that are either emmitted by the +node itself, or received through the network. + +Example: + +```json +{ + "parent_index_block_hash": "0x999b38d44d6af72703a476dde4cea683ec965346d9e9a7ded2d773fb4f257a3b", + "events": [ + { + "event_index": 1, + "committed": true, + "stx_transfer_event": { + "amount": "1000", + "recipient": "ST31DA6FTSJX2WGTZ69SFY11BH51NZMB0ZZ239N96", + "sender": "ST3WM51TCWMJYGZS1QFMC28DH5YP86782YGR113C1" + }, + "txid": "0x738e4d44636023efa08374033428e44eca490582bd39a6e61f3b6cf749b4214c", + "type": "stx_transfer_event" + } + ], + "transactions": [ + { + "contract_abi": null, + "raw_result": "0x03", + "raw_tx": "0x808000000004008bc5147525b8f477f0bc4522a88c8339b2494db50000000000000002000000000000000001015814daf929d8700af344987681f44e913890a12e38550abe8e40f149ef5269f40f4008083a0f2e0ddf65dcd05ecfc151c7ff8a5308ad04c77c0e87b5aeadad31010200000000040000000000000000000000000000000000000000000000000000000000000000", + "status": "success", + "tx_index": 0, + "txid": "0x3e04ada5426332bfef446ba0a06d124aace4ade5c11840f541bf88e2e919faf6", + "microblock_sequence": "3", + "microblock_hash": "0x9304fcbcc6daf5ac3f264522e0df50eddb5be85df6ee8a9fc2384c54274daaac", + "microblock_parent_hash": "0x4893ab44636023efa08374033428e44eca490582bd39a6e61f3b6cf749b474bd" + }, + { + "contract_abi": null, + "raw_result": "0x03", + "raw_tx": "0x80800000000400f942874ce525e87f21bbe8c121b12fac831d02f4000000000000000000000000000003e800006ae29867aec4b0e4f776bebdcea7f6d9a24eeff370c8c739defadfcbb52659b30736ad4af021e8fb741520a6c65da419fdec01989fdf0032fc1838f427a9a36102010000000000051ac2d519faccba2e435f3272ff042b89435fd160ff00000000000003e800000000000000000000000000000000000000000000000000000000000000000000", + "status": "success", + "tx_index": 1, + "txid": "0x738e4d44636023efa08374033428e44eca490582bd39a6e61f3b6cf749b4214c", + "microblock_sequence": "4", + "microblock_hash": "0xfcd4fc34c6daf5ac3f264522e0df50eddb5be85df6ee8a9fc2384c5427459e43", + "microblock_parent_hash": "0x9304fcbcc6daf5ac3f264522e0df50eddb5be85df6ee8a9fc2384c54274daaac" + } + ], + "burn_block_hash": "0x4eaabcd105865e471f697eff5dd5bd85d47ecb5a26a3379d74fae0ae87c40904", + "burn_block_height": 331, + "burn_block_timestamp": 1651301734 +} +``` + +* `burn_block_{}` are the stats related to the burn block that is associated with the stacks + block that precedes this microblock stream. +* Each transaction json object includes information about the microblock the transaction was packaged into. + ### `POST /new_mempool_tx` This payload includes raw transactions newly received in the diff --git a/src/burnchains/bitcoin/indexer.rs b/src/burnchains/bitcoin/indexer.rs index 4a3924e4e..9736c01aa 100644 --- a/src/burnchains/bitcoin/indexer.rs +++ b/src/burnchains/bitcoin/indexer.rs @@ -25,8 +25,6 @@ use std::path::PathBuf; use std::time; use std::time::Duration; -use tini::Ini; - use burnchains::bitcoin::blocks::BitcoinHeaderIPC; use burnchains::bitcoin::messages::BitcoinMessageHandler; use burnchains::bitcoin::spv::*; @@ -46,7 +44,7 @@ use burnchains::BLOCKSTACK_MAGIC_MAINNET; use deps::bitcoin::blockdata::block::LoneBlockHeader; use deps::bitcoin::network::message::NetworkMessage; use deps::bitcoin::network::serialize::BitcoinHash; - +use deps::bitcoin::network::serialize::Error as btc_serialization_err; use util::log; pub const USER_AGENT: &'static str = "Stacks/2.0"; @@ -137,165 +135,6 @@ impl BitcoinIndexerConfig { magic_bytes: BLOCKSTACK_MAGIC_MAINNET.clone(), } } - - pub fn to_file(&self, path: &String) -> Result<(), btc_error> { - let username = self.username.clone().unwrap_or("".to_string()); - let password = self.password.clone().unwrap_or("".to_string()); - - let conf = Ini::new() - .section("bitcoin") - .item("server", self.peer_host.as_str()) - .item("p2p_port", format!("{}", self.peer_port).as_str()) - .item("rpc_port", format!("{}", self.rpc_port).as_str()) - .item("rpc_ssl", format!("{}", self.rpc_ssl).as_str()) - .item("username", username.as_str()) - .item("password", password.as_str()) - .item("timeout", format!("{}", self.timeout).as_str()) - .item("spv_path", self.spv_headers_path.as_str()) - .item("first_block", format!("{}", self.first_block).as_str()) - .section("blockstack") - .item( - "network_id", - format!( - "{}{}", - self.magic_bytes.as_bytes()[0] as char, - self.magic_bytes.as_bytes()[1] as char - ) - .as_str(), - ); - - conf.to_file(&path).map_err(|e| btc_error::Io(e)) - } - - pub fn from_file(path: &String) -> Result { - let conf_path = PathBuf::from(path); - if !conf_path.is_file() { - return Err(btc_error::ConfigError( - "Failed to load BitcoinIndexerConfig file: No such file or directory".to_string(), - )); - } - - let default_config = BitcoinIndexerConfig::default(0); - - match Ini::from_file(path) { - Ok(ini_file) => { - // [bitcoin] - let peer_host = ini_file - .get("bitcoin", "server") - .unwrap_or(default_config.peer_host); - - let peer_port = ini_file - .get("bitcoin", "p2p_port") - .unwrap_or(format!("{}", default_config.peer_port)) - .trim() - .parse() - .map_err(|_e| { - btc_error::ConfigError("Invalid bitcoin:p2p_port value".to_string()) - })?; - - if peer_port <= 1024 || peer_port == 65535 { - return Err(btc_error::ConfigError("Invalid p2p_port".to_string())); - } - - let rpc_port = ini_file - .get("bitcoin", "rpc_port") - .unwrap_or(format!("{}", default_config.rpc_port)) - .trim() - .parse() - .map_err(|_e| { - btc_error::ConfigError("Invalid bitcoin:port value".to_string()) - })?; - - if rpc_port <= 1024 || rpc_port == 65535 { - return Err(btc_error::ConfigError("Invalid rpc_port".to_string())); - } - - let username: Option = - ini_file.get("bitcoin", "username").and_then(|s| Some(s)); - let password: Option = - ini_file.get("bitcoin", "password").and_then(|s| Some(s)); - - let timeout = ini_file - .get("bitcoin", "timeout") - .unwrap_or(format!("{}", default_config.timeout)) - .trim() - .parse() - .map_err(|_e| { - btc_error::ConfigError("Invalid bitcoin:timeout value".to_string()) - })?; - - let spv_headers_path_cfg = ini_file - .get("bitcoin", "spv_path") - .unwrap_or(default_config.spv_headers_path); - - let spv_headers_path = - if path::is_separator(spv_headers_path_cfg.chars().next().ok_or( - btc_error::ConfigError("Invalid bitcoin:spv_path value".to_string()), - )?) { - // absolute - spv_headers_path_cfg - } else { - // relative to config file - let mut p = PathBuf::from(path); - p.pop(); - let s = p.join(&spv_headers_path_cfg); - s.to_str().unwrap().to_string() - }; - - let first_block = ini_file - .get("bitcoin", "first_block") - .unwrap_or(format!("{}", 0)) - .trim() - .parse() - .map_err(|_e| { - btc_error::ConfigError("Invalid bitcoin:first_block value".to_string()) - })?; - - let rpc_ssl_str = ini_file - .get("bitcoin", "ssl") - .unwrap_or(format!("{}", default_config.rpc_ssl)); - - let rpc_ssl = rpc_ssl_str == "1" || rpc_ssl_str == "true"; - - // [blockstack] - let blockstack_magic_str = - ini_file.get("blockstack", "network_id").unwrap_or(format!( - "{}{}", - BLOCKSTACK_MAGIC_MAINNET.as_bytes()[0] as char, - BLOCKSTACK_MAGIC_MAINNET.as_bytes()[1] as char - )); - - if blockstack_magic_str.len() != 2 { - return Err(btc_error::ConfigError( - "Invalid blockstack:network_id value: must be two bytes".to_string(), - )); - } - - let blockstack_magic = MagicBytes([ - blockstack_magic_str.as_bytes()[0] as u8, - blockstack_magic_str.as_bytes()[1] as u8, - ]); - - let cfg = BitcoinIndexerConfig { - peer_host: peer_host, - peer_port: peer_port, - rpc_port: rpc_port, - rpc_ssl: rpc_ssl, - username: username, - password: password, - timeout: timeout, - spv_headers_path: spv_headers_path, - first_block: first_block, - magic_bytes: blockstack_magic, - }; - - Ok(cfg) - } - Err(_) => Err(btc_error::ConfigError( - "Failed to parse BitcoinConfigIndexer config file".to_string(), - )), - } - } } impl BitcoinIndexerRuntime { @@ -323,18 +162,6 @@ impl BitcoinIndexer { } } - pub fn from_file( - network_id: BitcoinNetworkType, - config_file: &String, - ) -> Result { - let config = BitcoinIndexerConfig::from_file(config_file)?; - let runtime = BitcoinIndexerRuntime::new(network_id); - Ok(BitcoinIndexer { - config: config, - runtime: runtime, - }) - } - pub fn dup(&self) -> BitcoinIndexer { BitcoinIndexer { config: self.config.clone(), @@ -497,6 +324,11 @@ impl BitcoinIndexer { Err(btc_error::ConnectionBroken) => { do_handshake = true; } + Err(btc_error::SerializationError( + btc_serialization_err::UnrecognizedNetworkCommand(s), + )) => { + debug!("Received unrecognized network command while receiving a message: {}, ignoring", s); + } Err(e) => { warn!("Unhandled error while receiving a message: {:?}", e); do_handshake = true; @@ -770,57 +602,6 @@ impl Drop for BitcoinIndexer { impl BurnchainIndexer for BitcoinIndexer { type P = BitcoinBlockParser; - /// Instantiate the Bitcoin indexer, and connect to the peer network. - /// Instead, load our configuration state and sanity-check it. - /// - /// Pass a directory (working_dir) that contains a "bitcoin.ini" file. - fn init( - working_dir: &String, - network_name: &String, - first_block_height: u64, - ) -> Result { - let conf_path_str = - Burnchain::get_chainstate_config_path(working_dir, &"bitcoin".to_string()); - - let network_id_opt = match network_name.as_ref() { - BITCOIN_MAINNET_NAME => Some(BitcoinNetworkType::Mainnet), - BITCOIN_TESTNET_NAME => Some(BitcoinNetworkType::Testnet), - BITCOIN_REGTEST_NAME => Some(BitcoinNetworkType::Regtest), - _ => None, - }; - - if network_id_opt.is_none() { - return Err(burnchain_error::Bitcoin(btc_error::ConfigError(format!( - "Unrecognized network name '{}'", - network_name - )))); - } - let bitcoin_network_id = network_id_opt.unwrap(); - - if !PathBuf::from(&conf_path_str).exists() { - let default_config = BitcoinIndexerConfig::default(first_block_height); - default_config - .to_file(&conf_path_str) - .map_err(burnchain_error::Bitcoin)?; - } - - let mut indexer = BitcoinIndexer::from_file(bitcoin_network_id, &conf_path_str) - .map_err(burnchain_error::Bitcoin)?; - - SpvClient::new( - &indexer.config.spv_headers_path, - 0, - None, - indexer.runtime.network_id, - true, - false, - ) - .map_err(burnchain_error::Bitcoin)?; - - indexer.connect()?; - Ok(indexer) - } - /// Connect to the Bitcoin peer network. /// Use the peer host and peer port given in the config file, /// and loaded in on setup. Don't call this before init(). diff --git a/src/burnchains/burnchain.rs b/src/burnchains/burnchain.rs index 7a01abc0c..32d4095ac 100644 --- a/src/burnchains/burnchain.rs +++ b/src/burnchains/burnchain.rs @@ -25,7 +25,7 @@ use std::sync::{ Arc, }; use std::thread; -use std::time::Instant; +use std::time::{Duration, Instant}; use crate::types::chainstate::StacksAddress; use crate::types::proof::TrieHash; @@ -577,17 +577,6 @@ impl Burnchain { Ok(()) } - pub fn make_indexer(&self) -> Result { - Burnchain::setup_chainstate_dirs(&self.working_dir)?; - - let indexer: I = BurnchainIndexer::init( - &self.working_dir, - &self.network_name, - self.first_block_height, - )?; - Ok(indexer) - } - fn setup_chainstate( &self, indexer: &mut I, @@ -629,16 +618,14 @@ impl Burnchain { db_path } - pub fn connect_db( + pub fn connect_db( &self, - indexer: &I, readwrite: bool, + first_block_header_hash: BurnchainHeaderHash, + first_block_header_timestamp: u64, ) -> Result<(SortitionDB, BurnchainDB), burnchain_error> { Burnchain::setup_chainstate_dirs(&self.working_dir)?; - let first_block_header_hash = indexer.get_first_block_header_hash()?; - let first_block_header_timestamp = indexer.get_first_block_header_timestamp()?; - let db_path = self.get_db_path(); let burnchain_db_path = self.get_burnchaindb_path(); @@ -904,8 +891,8 @@ impl Burnchain { } /// Determine if there has been a chain reorg, given our current canonical burnchain tip. - /// Return the new chain tip - fn sync_reorg(indexer: &mut I) -> Result { + /// Return the new chain tip and a boolean signaling the presence of a reorg + fn sync_reorg(indexer: &mut I) -> Result<(u64, bool), burnchain_error> { let headers_path = indexer.get_headers_path(); // sanity check -- what is the height of our highest header @@ -918,7 +905,7 @@ impl Burnchain { })?; if headers_height == 0 { - return Ok(0); + return Ok((0, false)); } // did we encounter a reorg since last sync? Find the highest common ancestor of the @@ -934,32 +921,13 @@ impl Burnchain { "Burnchain reorg detected: highest common ancestor at height {}", reorg_height ); - return Ok(reorg_height); + return Ok((reorg_height, true)); } else { // no reorg - return Ok(headers_height); + return Ok((headers_height, false)); } } - /// Top-level burnchain sync. - /// Returns new latest block height. - pub fn sync( - &mut self, - comms: &CoordinatorChannels, - target_block_height_opt: Option, - max_blocks_opt: Option, - ) -> Result { - let mut indexer: I = self.make_indexer()?; - let chain_tip = self.sync_with_indexer( - &mut indexer, - comms.clone(), - target_block_height_opt, - max_blocks_opt, - None, - )?; - Ok(chain_tip.block_height) - } - /// Deprecated top-level burnchain sync. /// Returns (snapshot of new burnchain tip, last state-transition processed if any) /// If this method returns Err(burnchain_error::TrySyncAgain), then call this method again. @@ -968,7 +936,11 @@ impl Burnchain { indexer: &mut I, ) -> Result<(BlockSnapshot, Option), burnchain_error> { self.setup_chainstate(indexer)?; - let (mut sortdb, mut burnchain_db) = self.connect_db(indexer, true)?; + let (mut sortdb, mut burnchain_db) = self.connect_db( + true, + indexer.get_first_block_header_hash()?, + indexer.get_first_block_header_timestamp()?, + )?; let burn_chain_tip = burnchain_db.get_canonical_chain_tip().map_err(|e| { error!("Failed to query burn chain tip from burn DB: {}", e); e @@ -987,7 +959,7 @@ impl Burnchain { // handle reorgs let orig_header_height = indexer.get_headers_height()?; // 1-indexed - let sync_height = Burnchain::sync_reorg(indexer)?; + let (sync_height, _) = Burnchain::sync_reorg(indexer)?; if sync_height + 1 < orig_header_height { // a reorg happened warn!( @@ -1185,7 +1157,11 @@ impl Burnchain { I: BurnchainIndexer + 'static, { self.setup_chainstate(indexer)?; - let (_, mut burnchain_db) = self.connect_db(indexer, true)?; + let (_, mut burnchain_db) = self.connect_db( + true, + indexer.get_first_block_header_hash()?, + indexer.get_first_block_header_timestamp()?, + )?; let burn_chain_tip = burnchain_db.get_canonical_chain_tip().map_err(|e| { error!("Failed to query burn chain tip from burn DB: {}", e); e @@ -1194,9 +1170,8 @@ impl Burnchain { let db_height = burn_chain_tip.block_height; // handle reorgs - let orig_header_height = indexer.get_headers_height()?; // 1-indexed - let sync_height = Burnchain::sync_reorg(indexer)?; - if sync_height + 1 < orig_header_height { + let (sync_height, did_reorg) = Burnchain::sync_reorg(indexer)?; + if did_reorg { // a reorg happened warn!( "Dropping headers higher than {} due to burnchain reorg", @@ -1210,6 +1185,23 @@ impl Burnchain { // fetch all headers, no matter what let mut end_block = indexer.sync_headers(sync_height, None)?; + if did_reorg && sync_height > 0 { + // a reorg happened, and the last header fetched + // is on a smaller fork than the one we just + // invalidated. Wait for more blocks. + while end_block < db_height { + if let Some(ref should_keep_running) = should_keep_running { + if !should_keep_running.load(Ordering::SeqCst) { + return Err(burnchain_error::CoordinatorClosed); + } + } + let end_height = target_block_height_opt.unwrap_or(0).max(db_height); + info!("Burnchain reorg happened at height {} invalidating chain tip {} but only {} headers presents on canonical chain. Retry in 2s", sync_height, db_height, end_block); + thread::sleep(Duration::from_millis(2000)); + end_block = indexer.sync_headers(sync_height, Some(end_height))?; + } + } + let mut start_block = sync_height; if db_height < start_block { start_block = db_height; @@ -1217,16 +1209,25 @@ impl Burnchain { debug!( "Sync'ed headers from {} to {}. DB at {}", - start_block, end_block, db_height + sync_height, end_block, db_height ); if let Some(target_block_height) = target_block_height_opt { - if target_block_height < end_block { + // `target_block_height` is used as a hint, but could also be completely off + // in certain situations. This function is directly reading the + // headers and syncing with the bitcoin-node, and the interval of blocks + // to download computed here should be considered as our source of truth. + if target_block_height > start_block && target_block_height < end_block { debug!( "Will download up to max burn block height {}", target_block_height ); end_block = target_block_height; + } else { + debug!( + "Ignoring target block height {} considered as irrelevant", + target_block_height + ); } } @@ -1351,7 +1352,8 @@ impl Burnchain { while let Ok(Some(burnchain_block)) = db_recv.recv() { debug!("Try recv next parsed block"); - if burnchain_block.block_height() == 0 { + let block_height = burnchain_block.block_height(); + if block_height == 0 { continue; } diff --git a/src/burnchains/indexer.rs b/src/burnchains/indexer.rs index 94951d94b..f7ded2170 100644 --- a/src/burnchains/indexer.rs +++ b/src/burnchains/indexer.rs @@ -57,13 +57,6 @@ pub trait BurnchainBlockParser { pub trait BurnchainIndexer { type P: BurnchainBlockParser + Send + Sync; - fn init( - working_dir: &String, - network_name: &String, - first_block_height: u64, - ) -> Result - where - Self: Sized; fn connect(&mut self) -> Result<(), burnchain_error>; fn get_first_block_height(&self) -> u64; diff --git a/src/burnchains/mod.rs b/src/burnchains/mod.rs index 2bd9a4d9b..823c0fa79 100644 --- a/src/burnchains/mod.rs +++ b/src/burnchains/mod.rs @@ -167,7 +167,7 @@ pub trait PrivateKey: Clone + fmt::Debug + serde::Serialize + serde::de::Deseria pub trait Address: Clone + fmt::Debug + fmt::Display { fn to_bytes(&self) -> Vec; - fn from_string(&str) -> Option + fn from_string(from: &str) -> Option where Self: Sized; fn is_burn(&self) -> bool; @@ -379,11 +379,11 @@ impl PoxConstants { pub fn testnet_default() -> PoxConstants { PoxConstants::new( - 50, // 40 reward slots; 10 prepare-phase slots - 10, - 6, - 3333333333333333, - 1, + POX_REWARD_CYCLE_LENGTH / 2, // 1050 + POX_PREPARE_WINDOW_LENGTH / 2, // 50 + 40, + 12, + 2, BITCOIN_TESTNET_FIRST_BLOCK_HEIGHT + POX_SUNSET_START, BITCOIN_TESTNET_FIRST_BLOCK_HEIGHT + POX_SUNSET_END, ) // total liquid supply is 40000000000000000 µSTX diff --git a/src/chainstate/burn/db/sortdb.rs b/src/chainstate/burn/db/sortdb.rs index 8eb0f6978..db1353e41 100644 --- a/src/chainstate/burn/db/sortdb.rs +++ b/src/chainstate/burn/db/sortdb.rs @@ -2176,7 +2176,7 @@ impl<'a> SortitionDBConn<'a> { } /// Given a burnchain consensus hash, - /// go get the last N Stacks block headers that won sortition + /// go get the last N Stacks block headers that won sortition /// leading up to the given header hash. The ith slot in the vector will be Some(...) if there /// was a sortition, and None if not. /// Returns up to num_headers prior block header hashes. @@ -2233,10 +2233,11 @@ impl<'a> SortitionDBConn<'a> { ancestor_consensus_hash )); - assert!( - ancestor_snapshot.pox_valid, - "BUG: ancestor is not on the valid PoX fork" - ); + // this can happen if this call is interleaved with a PoX invalidation transaction + if !ancestor_snapshot.pox_valid { + warn!("Consensus hash {:?} corresponds to a sortition that is not on the canonical PoX fork", ancestor_consensus_hash); + return Err(db_error::InvalidPoxSortition); + } let header_hash_opt = if ancestor_snapshot.sortition { Some(ancestor_snapshot.winning_stacks_block_hash.clone()) diff --git a/src/chainstate/burn/mod.rs b/src/chainstate/burn/mod.rs index 8aa5437ba..a975178cf 100644 --- a/src/chainstate/burn/mod.rs +++ b/src/chainstate/burn/mod.rs @@ -100,25 +100,39 @@ pub enum Opcodes { // a burnchain block snapshot #[derive(Debug, Clone, PartialEq)] pub struct BlockSnapshot { + /// the burn block height of this sortition pub block_height: u64, pub burn_header_timestamp: u64, pub burn_header_hash: BurnchainHeaderHash, pub parent_burn_header_hash: BurnchainHeaderHash, pub consensus_hash: ConsensusHash, pub ops_hash: OpsHash, - pub total_burn: u64, // how many burn tokens have been destroyed since genesis - pub sortition: bool, // whether or not a sortition happened in this block (will be false if there were no burns) - pub sortition_hash: SortitionHash, // rolling hash of the burn chain's block headers -- this gets mixed with the sortition VRF seed - pub winning_block_txid: Txid, // txid of the leader block commit that won sortition. Will all 0's if sortition is false. - pub winning_stacks_block_hash: BlockHeaderHash, // hash of Stacks block that won sortition (will be all 0's if sortition is false) - pub index_root: TrieHash, // root hash of the index over the materialized view of all inserted data - pub num_sortitions: u64, // how many stacks blocks exist - pub stacks_block_accepted: bool, // did we download, store, and incorporate the stacks block into the chain state - pub stacks_block_height: u64, // if we accepted a block, this is its height - pub arrival_index: u64, // this is the $(arrival_index)-th block to be accepted - pub canonical_stacks_tip_height: u64, // memoized canonical stacks chain tip - pub canonical_stacks_tip_hash: BlockHeaderHash, // memoized canonical stacks chain tip - pub canonical_stacks_tip_consensus_hash: ConsensusHash, // memoized canonical stacks chain tip + /// how many burn tokens have been destroyed since genesis + pub total_burn: u64, + /// whether or not a sortition happened in this block (will be false if there were no burns) + pub sortition: bool, + /// rolling hash of the burn chain's block headers -- this gets mixed with the sortition VRF seed + pub sortition_hash: SortitionHash, + /// txid of the leader block commit that won sortition. Will all 0's if sortition is false. + pub winning_block_txid: Txid, + /// hash of Stacks block that won sortition (will be all 0's if sortition is false) + pub winning_stacks_block_hash: BlockHeaderHash, + /// root hash of the index over the materialized view of all inserted data + pub index_root: TrieHash, + /// how many stacks blocks exist + pub num_sortitions: u64, + /// did we download, store, and incorporate the stacks block into the chain state + pub stacks_block_accepted: bool, + /// if we accepted a block, this is its height + pub stacks_block_height: u64, + /// this is the $(arrival_index)-th block to be accepted + pub arrival_index: u64, + /// memoized canonical stacks chain tip + pub canonical_stacks_tip_height: u64, + /// memoized canonical stacks chain tip + pub canonical_stacks_tip_hash: BlockHeaderHash, + /// memoized canonical stacks chain tip + pub canonical_stacks_tip_consensus_hash: ConsensusHash, pub sortition_id: SortitionId, pub parent_sortition_id: SortitionId, pub pox_valid: bool, diff --git a/src/chainstate/coordinator/comm.rs b/src/chainstate/coordinator/comm.rs index 143933049..e095b14ca 100644 --- a/src/chainstate/coordinator/comm.rs +++ b/src/chainstate/coordinator/comm.rs @@ -167,7 +167,7 @@ impl CoordinatorChannels { return false; } thread::sleep(Duration::from_millis(100)); - std::sync::atomic::spin_loop_hint(); + std::hint::spin_loop(); } return true; } @@ -179,7 +179,7 @@ impl CoordinatorChannels { return false; } thread::sleep(Duration::from_millis(100)); - std::sync::atomic::spin_loop_hint(); + std::hint::spin_loop(); } return true; } diff --git a/src/chainstate/coordinator/mod.rs b/src/chainstate/coordinator/mod.rs index 88623e508..95dae303f 100644 --- a/src/chainstate/coordinator/mod.rs +++ b/src/chainstate/coordinator/mod.rs @@ -120,6 +120,9 @@ pub trait BlockEventDispatcher { winner_txid: Txid, matured_rewards: Vec, matured_rewards_info: Option, + parent_burn_block_hash: BurnchainHeaderHash, + parent_burn_block_height: u32, + parent_burn_block_timestamp: u64, ); /// called whenever a burn block is about to be @@ -760,6 +763,9 @@ impl<'a, T: BlockEventDispatcher, N: CoordinatorNotices, U: RewardSetProvider> winner_txid, block_receipt.matured_rewards, block_receipt.matured_rewards_info, + block_receipt.parent_burn_block_hash, + block_receipt.parent_burn_block_height, + block_receipt.parent_burn_block_timestamp, ); } diff --git a/src/chainstate/coordinator/tests.rs b/src/chainstate/coordinator/tests.rs index 6e3aad1aa..6ba68770f 100644 --- a/src/chainstate/coordinator/tests.rs +++ b/src/chainstate/coordinator/tests.rs @@ -304,6 +304,9 @@ impl BlockEventDispatcher for NullEventDispatcher { _winner_txid: Txid, _rewards: Vec, _rewards_info: Option, + _parent_burn_block_hash: BurnchainHeaderHash, + _parent_burn_block_height: u32, + _parent_burn_block_timestamp: u64, ) { assert!( false, @@ -365,8 +368,8 @@ fn make_reward_set_coordinator<'a>( pub fn get_burnchain(path: &str, pox_consts: Option) -> Burnchain { let mut b = Burnchain::regtest(&format!("{}/burnchain/db/", path)); - b.pox_constants = pox_consts - .unwrap_or_else(|| PoxConstants::new(5, 3, 3, 25, 5, u64::max_value(), u64::max_value())); + b.pox_constants = + pox_consts.unwrap_or_else(|| PoxConstants::new(5, 3, 3, 25, 5, u64::MAX, u64::MAX)); b } diff --git a/src/chainstate/stacks/boot/contract_tests.rs b/src/chainstate/stacks/boot/contract_tests.rs index da4f9551e..41b08fbce 100644 --- a/src/chainstate/stacks/boot/contract_tests.rs +++ b/src/chainstate/stacks/boot/contract_tests.rs @@ -70,7 +70,7 @@ lazy_static! { ) .unwrap(); static ref LIQUID_SUPPLY: u128 = USTX_PER_HOLDER * (POX_ADDRS.len() as u128); - static ref MIN_THRESHOLD: u128 = *LIQUID_SUPPLY / 480; + static ref MIN_THRESHOLD: u128 = *LIQUID_SUPPLY / super::test::TESTNET_STACKING_THRESHOLD_25; } impl From<&StacksPrivateKey> for StandardPrincipalData { @@ -341,6 +341,7 @@ fn recency_tests() { fn delegation_tests() { let mut sim = ClarityTestSim::new(); let delegator = StacksPrivateKey::new(); + const REWARD_CYCLE_LENGTH: u128 = 1050; sim.execute_next_block(|env| { env.initialize_contract(POX_CONTRACT_TESTNET.clone(), &BOOT_CODE_POX_TESTNET) @@ -406,7 +407,7 @@ fn delegation_tests() { &symbols_from_values(vec![ Value::UInt(USTX_PER_HOLDER), (&delegator).into(), - Value::some(Value::UInt(300)).unwrap(), + Value::some(Value::UInt(REWARD_CYCLE_LENGTH * 2)).unwrap(), Value::none() ]) ) @@ -512,7 +513,7 @@ fn delegation_tests() { "(ok {{ stacker: '{}, lock-amount: {}, unlock-burn-height: {} }})", Value::from(&USER_KEYS[0]), Value::UInt(*MIN_THRESHOLD - 1), - Value::UInt(450) + Value::UInt(REWARD_CYCLE_LENGTH * 3) )) ); @@ -620,7 +621,7 @@ fn delegation_tests() { "(ok {{ stacker: '{}, lock-amount: {}, unlock-burn-height: {} }})", Value::from(&USER_KEYS[2]), Value::UInt(*MIN_THRESHOLD - 1), - Value::UInt(300) + Value::UInt(REWARD_CYCLE_LENGTH * 2) )) ); @@ -724,7 +725,7 @@ fn delegation_tests() { "(ok {{ stacker: '{}, lock-amount: {}, unlock-burn-height: {} }})", Value::from(&USER_KEYS[3]), Value::UInt(*MIN_THRESHOLD), - Value::UInt(450) + Value::UInt(REWARD_CYCLE_LENGTH * 3) )) ); @@ -835,7 +836,7 @@ fn delegation_tests() { "(ok {{ stacker: '{}, lock-amount: {}, unlock-burn-height: {} }})", Value::from(&USER_KEYS[1]), Value::UInt(*MIN_THRESHOLD), - Value::UInt(450) + Value::UInt(REWARD_CYCLE_LENGTH * 3) )) ); diff --git a/src/chainstate/stacks/boot/mod.rs b/src/chainstate/stacks/boot/mod.rs index 23cf6d766..780e2dbfe 100644 --- a/src/chainstate/stacks/boot/mod.rs +++ b/src/chainstate/stacks/boot/mod.rs @@ -459,6 +459,8 @@ pub mod test { use super::*; + pub const TESTNET_STACKING_THRESHOLD_25: u128 = 8000; + #[test] fn make_reward_set_units() { let threshold = 1_000; @@ -1565,7 +1567,7 @@ pub mod test { chainstate.get_stacking_minimum(sortdb, &tip_index_block) }) .unwrap(); - assert_eq!(min_ustx, total_liquid_ustx / 480); + assert_eq!(min_ustx, total_liquid_ustx / TESTNET_STACKING_THRESHOLD_25); // no reward addresses let reward_addrs = with_sortdb(&mut peer, |ref mut chainstate, ref sortdb| { @@ -1635,7 +1637,7 @@ pub mod test { // miner rewards increased liquid supply, so less than 25% is locked. // minimum participation decreases. assert!(total_liquid_ustx > 4 * 1024 * POX_THRESHOLD_STEPS_USTX); - assert_eq!(min_ustx, total_liquid_ustx / 480); + assert_eq!(min_ustx, total_liquid_ustx / TESTNET_STACKING_THRESHOLD_25); } else { // still at 25% or more locked assert!(total_liquid_ustx <= 4 * 1024 * POX_THRESHOLD_STEPS_USTX); @@ -1814,7 +1816,7 @@ pub mod test { chainstate.get_stacking_minimum(sortdb, &tip_index_block) }) .unwrap(); - assert_eq!(min_ustx, total_liquid_ustx / 480); + assert_eq!(min_ustx, total_liquid_ustx / TESTNET_STACKING_THRESHOLD_25); // no reward addresses let reward_addrs = with_sortdb(&mut peer, |ref mut chainstate, ref sortdb| { @@ -1886,7 +1888,7 @@ pub mod test { // miner rewards increased liquid supply, so less than 25% is locked. // minimum participation decreases. assert!(total_liquid_ustx > 4 * 1024 * POX_THRESHOLD_STEPS_USTX); - assert_eq!(min_ustx, total_liquid_ustx / 480); + assert_eq!(min_ustx, total_liquid_ustx / TESTNET_STACKING_THRESHOLD_25); } else { // still at 25% or more locked assert!(total_liquid_ustx <= 4 * 1024 * POX_THRESHOLD_STEPS_USTX); @@ -2030,7 +2032,7 @@ pub mod test { chainstate.get_stacking_minimum(sortdb, &tip_index_block) }) .unwrap(); - assert_eq!(min_ustx, total_liquid_ustx / 480); + assert_eq!(min_ustx, total_liquid_ustx / TESTNET_STACKING_THRESHOLD_25); // no reward addresses let reward_addrs = with_sortdb(&mut peer, |ref mut chainstate, ref sortdb| { @@ -2102,7 +2104,7 @@ pub mod test { // miner rewards increased liquid supply, so less than 25% is locked. // minimum participation decreases. assert!(total_liquid_ustx > 4 * 1024 * POX_THRESHOLD_STEPS_USTX); - assert_eq!(min_ustx, total_liquid_ustx / 480); + assert_eq!(min_ustx, total_liquid_ustx / TESTNET_STACKING_THRESHOLD_25); } else { // still at 25% or more locked assert!(total_liquid_ustx <= 4 * 1024 * POX_THRESHOLD_STEPS_USTX); @@ -2307,7 +2309,7 @@ pub mod test { chainstate.get_stacking_minimum(sortdb, &tip_index_block) }) .unwrap(); - assert_eq!(min_ustx, total_liquid_ustx / 480); + assert_eq!(min_ustx, total_liquid_ustx / TESTNET_STACKING_THRESHOLD_25); // no reward addresses let reward_addrs = with_sortdb(&mut peer, |ref mut chainstate, ref sortdb| { @@ -2384,7 +2386,7 @@ pub mod test { } // well over 25% locked, so this is always true - assert_eq!(min_ustx, total_liquid_ustx / 480); + assert_eq!(min_ustx, total_liquid_ustx / TESTNET_STACKING_THRESHOLD_25); // two reward addresses, and they're Alice's and Bob's. // They are present in sorted order @@ -2714,7 +2716,7 @@ pub mod test { chainstate.get_stacking_minimum(sortdb, &tip_index_block) }) .unwrap(); - assert_eq!(min_ustx, total_liquid_ustx / 480); + assert_eq!(min_ustx, total_liquid_ustx / TESTNET_STACKING_THRESHOLD_25); // no reward addresses let reward_addrs = with_sortdb(&mut peer, |ref mut chainstate, ref sortdb| { @@ -2782,7 +2784,7 @@ pub mod test { // miner rewards increased liquid supply, so less than 25% is locked. // minimum participation decreases. assert!(total_liquid_ustx > 4 * 1024 * POX_THRESHOLD_STEPS_USTX); - assert_eq!(min_ustx, total_liquid_ustx / 480); + assert_eq!(min_ustx, total_liquid_ustx / TESTNET_STACKING_THRESHOLD_25); } if cur_reward_cycle == alice_reward_cycle { @@ -2836,7 +2838,7 @@ pub mod test { assert_eq!(reward_addrs.len(), 0); // min STX is reset - assert_eq!(min_ustx, total_liquid_ustx / 480); + assert_eq!(min_ustx, total_liquid_ustx / TESTNET_STACKING_THRESHOLD_25); // Unlock is lazy let alice_account = @@ -3037,7 +3039,7 @@ pub mod test { chainstate.get_stacking_minimum(sortdb, &tip_index_block) }) .unwrap(); - assert_eq!(min_ustx, total_liquid_ustx / 480); + assert_eq!(min_ustx, total_liquid_ustx / TESTNET_STACKING_THRESHOLD_25); // no reward addresses assert_eq!(reward_addrs.len(), 0); @@ -3073,7 +3075,7 @@ pub mod test { chainstate.get_stacking_minimum(sortdb, &tip_index_block) }) .unwrap(); - assert_eq!(min_ustx, total_liquid_ustx / 480); + assert_eq!(min_ustx, total_liquid_ustx / TESTNET_STACKING_THRESHOLD_25); // no reward addresses assert_eq!(reward_addrs.len(), 0); @@ -3096,13 +3098,13 @@ pub mod test { // miner rewards increased liquid supply, so less than 25% is locked. // minimum participation decreases. assert!(total_liquid_ustx > 4 * 1024 * POX_THRESHOLD_STEPS_USTX); - assert_eq!(min_ustx, total_liquid_ustx / 480); + assert_eq!(min_ustx, total_liquid_ustx / TESTNET_STACKING_THRESHOLD_25); } else if tenure_id >= 1 && cur_reward_cycle < first_reward_cycle { // still at 25% or more locked assert!(total_liquid_ustx <= 4 * 1024 * POX_THRESHOLD_STEPS_USTX); } else if tenure_id < 1 { // nothing locked yet - assert_eq!(min_ustx, total_liquid_ustx / 480); + assert_eq!(min_ustx, total_liquid_ustx / TESTNET_STACKING_THRESHOLD_25); } if first_reward_cycle > 0 && second_reward_cycle == 0 { @@ -3204,7 +3206,7 @@ pub mod test { assert_eq!(reward_addrs.len(), 0); // min STX is reset - assert_eq!(min_ustx, total_liquid_ustx / 480); + assert_eq!(min_ustx, total_liquid_ustx / TESTNET_STACKING_THRESHOLD_25); // Unlock is lazy let alice_account = get_account(&mut peer, &key_to_stacks_addr(&alice).into()); @@ -3334,7 +3336,7 @@ pub mod test { assert_eq!(reward_addrs.len(), 0); // min STX is reset - assert_eq!(min_ustx, total_liquid_ustx / 480); + assert_eq!(min_ustx, total_liquid_ustx / TESTNET_STACKING_THRESHOLD_25); // Unlock is lazy let alice_account = get_account(&mut peer, &key_to_stacks_addr(&alice).into()); @@ -3674,7 +3676,7 @@ pub mod test { assert_eq!(balance, expected_balance); } } - assert_eq!(min_ustx, total_liquid_ustx / 480); + assert_eq!(min_ustx, total_liquid_ustx / TESTNET_STACKING_THRESHOLD_25); // no reward addresses let reward_addrs = with_sortdb(&mut peer, |ref mut chainstate, ref sortdb| { @@ -3790,7 +3792,7 @@ pub mod test { assert_eq!(reward_addrs.len(), 0); // min STX is reset - assert_eq!(min_ustx, total_liquid_ustx / 480); + assert_eq!(min_ustx, total_liquid_ustx / TESTNET_STACKING_THRESHOLD_25); } } @@ -3990,7 +3992,7 @@ pub mod test { assert_eq!(alice_account.stx_balance.unlock_height, 0); } - assert_eq!(min_ustx, total_liquid_ustx / 480); + assert_eq!(min_ustx, total_liquid_ustx / TESTNET_STACKING_THRESHOLD_25); // no reward addresses assert_eq!(reward_addrs.len(), 0); @@ -4046,7 +4048,7 @@ pub mod test { // miner rewards increased liquid supply, so less than 25% is locked. // minimum participation decreases. assert!(total_liquid_ustx > 4 * 1024 * POX_THRESHOLD_STEPS_USTX); - assert_eq!(min_ustx, total_liquid_ustx / 480); + assert_eq!(min_ustx, total_liquid_ustx / TESTNET_STACKING_THRESHOLD_25); } else { // still at 25% or more locked assert!(total_liquid_ustx <= 4 * 1024 * POX_THRESHOLD_STEPS_USTX); diff --git a/src/chainstate/stacks/boot/pox-testnet.clar b/src/chainstate/stacks/boot/pox-testnet.clar index 92eb2e623..a29b89ee7 100644 --- a/src/chainstate/stacks/boot/pox-testnet.clar +++ b/src/chainstate/stacks/boot/pox-testnet.clar @@ -4,10 +4,10 @@ (define-constant MAX_POX_REWARD_CYCLES u12) ;; Default length of the PoX registration window, in burnchain blocks. -(define-constant PREPARE_CYCLE_LENGTH u30) +(define-constant PREPARE_CYCLE_LENGTH u50) ;; Default length of the PoX reward cycle, in burnchain blocks. -(define-constant REWARD_CYCLE_LENGTH u150) +(define-constant REWARD_CYCLE_LENGTH u1050) ;; Valid values for burnchain address versions. ;; These correspond to address hash modes in Stacks 2.0. @@ -17,5 +17,5 @@ (define-constant ADDRESS_VERSION_P2WSH 0x03) ;; Stacking thresholds -(define-constant STACKING_THRESHOLD_25 u480) -(define-constant STACKING_THRESHOLD_100 u120) +(define-constant STACKING_THRESHOLD_25 u8000) +(define-constant STACKING_THRESHOLD_100 u2000) diff --git a/src/chainstate/stacks/db/accounts.rs b/src/chainstate/stacks/db/accounts.rs index 460027e30..a2186819d 100644 --- a/src/chainstate/stacks/db/accounts.rs +++ b/src/chainstate/stacks/db/accounts.rs @@ -314,9 +314,9 @@ impl StacksChainState { block_reward: &MinerPaymentSchedule, user_burns: &Vec, ) -> Result<(), Error> { - assert!(block_reward.burnchain_commit_burn < i64::max_value() as u64); - assert!(block_reward.burnchain_sortition_burn < i64::max_value() as u64); - assert!(block_reward.stacks_block_height < i64::max_value() as u64); + assert!(block_reward.burnchain_commit_burn < i64::MAX as u64); + assert!(block_reward.burnchain_sortition_burn < i64::MAX as u64); + assert!(block_reward.stacks_block_height < i64::MAX as u64); let index_block_hash = StacksBlockHeader::make_index_block_hash( &block_reward.consensus_hash, @@ -364,7 +364,7 @@ impl StacksChainState { .map_err(|e| Error::DBError(db_error::SqliteError(e)))?; for user_support in user_burns.iter() { - assert!(user_support.burn_amount < i64::max_value() as u64); + assert!(user_support.burn_amount < i64::MAX as u64); let args: &[&dyn ToSql] = &[ &user_support.address.to_string(), diff --git a/src/chainstate/stacks/db/blocks.rs b/src/chainstate/stacks/db/blocks.rs index c4bc5e4a4..a523dd2aa 100644 --- a/src/chainstate/stacks/db/blocks.rs +++ b/src/chainstate/stacks/db/blocks.rs @@ -77,6 +77,7 @@ use crate::types::chainstate::{ StacksAddress, StacksBlockHeader, StacksBlockId, StacksMicroblockHeader, }; use crate::{types, util}; +use types::chainstate::BurnchainHeaderHash; #[derive(Debug, Clone, PartialEq)] pub struct StagingMicroblock { @@ -1557,8 +1558,8 @@ impl StacksChainState { block.block_hash(), parent_consensus_hash ); - assert!(commit_burn < i64::max_value() as u64); - assert!(sortition_burn < i64::max_value() as u64); + assert!(commit_burn < i64::MAX as u64); + assert!(sortition_burn < i64::MAX as u64); let block_hash = block.block_hash(); let index_block_hash = @@ -1728,7 +1729,7 @@ impl StacksChainState { burn_supports: &Vec, ) -> Result<(), Error> { for burn_support in burn_supports.iter() { - assert!(burn_support.burn_fee < i64::max_value() as u64); + assert!(burn_support.burn_fee < i64::MAX as u64); } for burn_support in burn_supports.iter() { @@ -3875,10 +3876,11 @@ impl StacksChainState { for microblock in microblocks.iter() { debug!("Process microblock {}", µblock.block_hash()); for tx in microblock.txs.iter() { - let (tx_fee, tx_receipt) = + let (tx_fee, mut tx_receipt) = StacksChainState::process_transaction(clarity_tx, tx, false) .map_err(|e| (e, microblock.block_hash()))?; + tx_receipt.microblock_header = Some(microblock.header.clone()); fees = fees.checked_add(tx_fee as u128).expect("Fee overflow"); burns = burns .checked_add(tx_receipt.stx_burned as u128) @@ -3946,6 +3948,7 @@ impl StacksChainState { stx_burned: 0, contract_analysis: None, execution_cost, + microblock_header: None, }; all_receipts.push(receipt); @@ -3999,6 +4002,7 @@ impl StacksChainState { stx_burned: 0, contract_analysis: None, execution_cost: ExecutionCost::zero(), + microblock_header: None, }), Err(e) => { info!("TransferStx burn op processing error."; @@ -4231,6 +4235,9 @@ impl StacksChainState { block_execution_cost, matured_rewards, matured_rewards_info, + parent_burn_block_hash, + parent_burn_block_height, + parent_burn_block_timestamp, ) = { let (parent_consensus_hash, parent_block_hash) = if block.is_first_mined() { // has to be the sentinal hashes if this block has no parent @@ -4245,6 +4252,31 @@ impl StacksChainState { ) }; + // get previous burn block stats + let (parent_burn_block_hash, parent_burn_block_height, parent_burn_block_timestamp) = + if block.is_first_mined() { + (BurnchainHeaderHash([0; 32]), 0, 0) + } else { + match SortitionDB::get_block_snapshot_consensus( + burn_dbconn, + &parent_consensus_hash, + )? { + Some(sn) => ( + sn.burn_header_hash, + sn.block_height as u32, + sn.burn_header_timestamp, + ), + None => { + // shouldn't happen + warn!( + "CORRUPTION: block {}/{} does not correspond to a burn block", + &parent_consensus_hash, &parent_block_hash + ); + (BurnchainHeaderHash([0; 32]), 0, 0) + } + } + }; + let (last_microblock_hash, last_microblock_seq) = if microblocks.len() > 0 { let _first_mblock_hash = microblocks[0].block_hash(); let num_mblocks = microblocks.len(); @@ -4580,6 +4612,9 @@ impl StacksChainState { block_cost, matured_rewards, matured_rewards_info, + parent_burn_block_hash, + parent_burn_block_height, + parent_burn_block_timestamp, ) }; @@ -4614,6 +4649,9 @@ impl StacksChainState { matured_rewards_info, parent_microblocks_cost: microblock_execution_cost, anchored_block_cost: block_execution_cost, + parent_burn_block_hash, + parent_burn_block_height, + parent_burn_block_timestamp, }; Ok(epoch_receipt) @@ -6498,7 +6536,7 @@ pub mod test { &block.block_hash() ), 0, - u16::max_value() + u16::MAX ) .unwrap() .is_none()); @@ -6567,7 +6605,7 @@ pub mod test { &block.block_hash() ), 0, - u16::max_value() + u16::MAX ) .unwrap() .unwrap(), @@ -6666,7 +6704,7 @@ pub mod test { &block.block_hash() ), 0, - u16::max_value() + u16::MAX ) .unwrap() .is_some()); @@ -6678,7 +6716,7 @@ pub mod test { &block.block_hash() ), 0, - u16::max_value() + u16::MAX ) .unwrap() .unwrap(), @@ -6722,7 +6760,7 @@ pub mod test { &block.block_hash() ), 0, - u16::max_value() + u16::MAX ) .unwrap() .is_none()); @@ -6792,7 +6830,7 @@ pub mod test { &block.block_hash() ), 0, - u16::max_value() + u16::MAX ) .unwrap() .unwrap(), @@ -6928,7 +6966,7 @@ pub mod test { &block.block_hash() ), 0, - u16::max_value() + u16::MAX ) .unwrap() .is_some()); @@ -6940,7 +6978,7 @@ pub mod test { &block.block_hash() ), 0, - u16::max_value() + u16::MAX ) .unwrap() .unwrap(), @@ -6984,7 +7022,7 @@ pub mod test { &block.block_hash() ), 0, - u16::max_value() + u16::MAX ) .unwrap() .is_none()); @@ -7069,7 +7107,7 @@ pub mod test { &block.block_hash() ), 0, - u16::max_value() + u16::MAX ) .unwrap() .is_none()); @@ -8246,7 +8284,7 @@ pub mod test { &block.block_hash() ), 0, - u16::max_value() + u16::MAX ) .unwrap() .unwrap() @@ -8259,7 +8297,7 @@ pub mod test { &chainstate.db(), &StacksBlockHeader::make_index_block_hash(&consensus_hash, &block.block_hash()), 0, - u16::max_value() + u16::MAX ) .unwrap() .is_none()); @@ -8962,21 +9000,17 @@ pub mod test { for j in 0..(i + 1) { assert!( block_inv_all.has_ith_block(j as u16), - format!( - "Missing block {} from bitvec {}", - j, - to_hex(&block_inv_all.block_bitvec) - ) + "Missing block {} from bitvec {}", + j, + to_hex(&block_inv_all.block_bitvec) ); // microblocks not stored yet, so they should be marked absent assert!( !block_inv_all.has_ith_microblock_stream(j as u16), - format!( - "Have microblock {} from bitvec {}", - j, - to_hex(&block_inv_all.microblocks_bitvec) - ) + "Have microblock {} from bitvec {}", + j, + to_hex(&block_inv_all.microblocks_bitvec) ); } for j in i + 1..blocks.len() { @@ -9056,19 +9090,15 @@ pub mod test { test_debug!("Test bit {} ({})", j, i); assert!( !block_inv_all.has_ith_block(j as u16), - format!( - "Have orphaned block {} from bitvec {}", - j, - to_hex(&block_inv_all.block_bitvec) - ) + "Have orphaned block {} from bitvec {}", + j, + to_hex(&block_inv_all.block_bitvec) ); assert!( !block_inv_all.has_ith_microblock_stream(j as u16), - format!( - "Still have microblock {} from bitvec {}", - j, - to_hex(&block_inv_all.microblocks_bitvec) - ) + "Still have microblock {} from bitvec {}", + j, + to_hex(&block_inv_all.microblocks_bitvec) ); } for j in (i + 1)..blocks.len() { diff --git a/src/chainstate/stacks/db/headers.rs b/src/chainstate/stacks/db/headers.rs index 9bc4360f3..fa92e465f 100644 --- a/src/chainstate/stacks/db/headers.rs +++ b/src/chainstate/stacks/db/headers.rs @@ -123,7 +123,7 @@ impl StacksChainState { tip_info.block_height, tip_info.anchored_header.total_work.work ); - assert!(tip_info.burn_header_timestamp < i64::max_value() as u64); + assert!(tip_info.burn_header_timestamp < i64::MAX as u64); let header = &tip_info.anchored_header; let index_root = &tip_info.index_root; @@ -142,7 +142,7 @@ impl StacksChainState { let index_block_hash = StacksBlockHeader::make_index_block_hash(&consensus_hash, &block_hash); - assert!(block_height < (i64::max_value() as u64)); + assert!(block_height < (i64::MAX as u64)); let args: &[&dyn ToSql] = &[ &header.version, @@ -227,16 +227,12 @@ impl StacksChainState { consensus_hash: &ConsensusHash, block_hash: &BlockHeaderHash, ) -> Result, Error> { - let sql = - "SELECT * FROM block_headers WHERE consensus_hash = ?1 AND block_hash = ?2".to_string(); + let sql = "SELECT * FROM block_headers WHERE consensus_hash = ?1 AND block_hash = ?2"; let args: &[&dyn ToSql] = &[&consensus_hash, &block_hash]; - let mut rows = - query_rows::(conn, &sql, args).map_err(Error::DBError)?; - if rows.len() > 1 { - unreachable!("FATAL: multiple rows for the same block hash") // should be unreachable, since block_hash/consensus_hash is the primary key - } - - Ok(rows.pop()) + query_row_panic(conn, sql, args, || { + "FATAL: multiple rows for the same block hash".to_string() + }) + .map_err(Error::DBError) } /// Get a stacks header info by index block hash (i.e. by the hash of the burn block header @@ -245,8 +241,8 @@ impl StacksChainState { conn: &Connection, index_block_hash: &StacksBlockId, ) -> Result, Error> { - let sql = "SELECT * FROM block_headers WHERE index_block_hash = ?1".to_string(); - query_row_panic(conn, &sql, &[&index_block_hash], || { + let sql = "SELECT * FROM block_headers WHERE index_block_hash = ?1"; + query_row_panic(conn, sql, &[&index_block_hash], || { "FATAL: multiple rows for the same block hash".to_string() }) .map_err(Error::DBError) diff --git a/src/chainstate/stacks/db/mod.rs b/src/chainstate/stacks/db/mod.rs index 8889a8867..87943caf0 100644 --- a/src/chainstate/stacks/db/mod.rs +++ b/src/chainstate/stacks/db/mod.rs @@ -162,6 +162,9 @@ pub struct StacksEpochReceipt { pub matured_rewards_info: Option, pub parent_microblocks_cost: ExecutionCost, pub anchored_block_cost: ExecutionCost, + pub parent_burn_block_hash: BurnchainHeaderHash, + pub parent_burn_block_height: u32, + pub parent_burn_block_timestamp: u64, } #[derive(Debug, Clone, PartialEq)] @@ -2156,7 +2159,7 @@ pub mod test { // Just update the expected value assert_eq!( genesis_root_hash.to_string(), - "3102b7d9c7fdddc49910ea49a3ed4e322b772b9e5ee9505d9fb3f566affd1c54" + "c771616ff6acb710051238c9f4a3c48020a6d70cda637d34b89f2311a7e27886" ); } diff --git a/src/chainstate/stacks/db/transactions.rs b/src/chainstate/stacks/db/transactions.rs index 3be99ed5a..44a193a86 100644 --- a/src/chainstate/stacks/db/transactions.rs +++ b/src/chainstate/stacks/db/transactions.rs @@ -82,6 +82,7 @@ impl StacksTransactionReceipt { contract_analysis: None, transaction: tx.into(), execution_cost: cost, + microblock_header: None, } } @@ -100,6 +101,7 @@ impl StacksTransactionReceipt { stx_burned: burned, contract_analysis: None, execution_cost: cost, + microblock_header: None, } } @@ -118,6 +120,7 @@ impl StacksTransactionReceipt { stx_burned: burned, contract_analysis: None, execution_cost: cost, + microblock_header: None, } } @@ -136,6 +139,7 @@ impl StacksTransactionReceipt { stx_burned: burned, contract_analysis: Some(analysis), execution_cost: cost, + microblock_header: None, } } @@ -154,6 +158,7 @@ impl StacksTransactionReceipt { stx_burned: burned, contract_analysis: Some(analysis), execution_cost: cost, + microblock_header: None, } } @@ -166,6 +171,7 @@ impl StacksTransactionReceipt { stx_burned: 0, contract_analysis: None, execution_cost: ExecutionCost::zero(), + microblock_header: None, } } @@ -181,6 +187,7 @@ impl StacksTransactionReceipt { stx_burned: 0, contract_analysis: None, execution_cost: analysis_cost, + microblock_header: None, } } @@ -197,6 +204,7 @@ impl StacksTransactionReceipt { stx_burned: 0, contract_analysis: None, execution_cost: cost, + microblock_header: None, } } @@ -1466,7 +1474,7 @@ pub mod test { match res { Err(Error::InvalidStacksTransaction(msg, false)) => { - assert!(msg.contains(&err_frag), err_frag); + assert!(msg.contains(&err_frag), "{}", err_frag); } _ => { eprintln!("bad error: {:?}", &res); diff --git a/src/chainstate/stacks/db/unconfirmed.rs b/src/chainstate/stacks/db/unconfirmed.rs index 4ad55ebba..3f221c863 100644 --- a/src/chainstate/stacks/db/unconfirmed.rs +++ b/src/chainstate/stacks/db/unconfirmed.rs @@ -38,6 +38,8 @@ use vm::database::NULL_HEADER_DB; use crate::clarity_vm::database::marf::MarfedKV; use crate::types::chainstate::{StacksBlockHeader, StacksBlockId, StacksMicroblockHeader}; +use chainstate::burn::db::sortdb::SortitionDB; +use types::chainstate::BurnchainHeaderHash; pub type UnconfirmedTxMap = HashMap; @@ -45,8 +47,12 @@ pub struct ProcessedUnconfirmedState { pub total_burns: u128, pub total_fees: u128, // each element of this vector is a tuple, where each tuple contains a microblock - // sequence number, and a vector of transaction receipts for that microblock - pub receipts: Vec<(u16, Vec)>, + // sequence number, microblock header, and a vector of transaction receipts + // for that microblock + pub receipts: Vec<(u16, StacksMicroblockHeader, Vec)>, + pub burn_block_hash: BurnchainHeaderHash, + pub burn_block_height: u32, + pub burn_block_timestamp: u64, } impl Default for ProcessedUnconfirmedState { @@ -55,6 +61,9 @@ impl Default for ProcessedUnconfirmedState { total_burns: 0, total_fees: 0, receipts: vec![], + burn_block_hash: BurnchainHeaderHash([0; 32]), + burn_block_height: 0, + burn_block_timestamp: 0, } } } @@ -155,7 +164,7 @@ impl UnconfirmedState { burn_dbconn: &dyn BurnStateDB, mblocks: Vec, ) -> Result { - if self.last_mblock_seq == u16::max_value() { + if self.last_mblock_seq == u16::MAX { // drop them -- nothing to do return Ok(Default::default()); } @@ -166,6 +175,17 @@ impl UnconfirmedState { mblocks.len() ); + let headers_db = chainstate.db(); + let burn_block_hash = headers_db + .get_burn_header_hash_for_block(&self.confirmed_chain_tip) + .expect("BUG: unable to get burn block hash based on chain tip"); + let burn_block_height = headers_db + .get_burn_block_height_for_block(&self.confirmed_chain_tip) + .expect("BUG: unable to get burn block height based on chain tip"); + let burn_block_timestamp = headers_db + .get_burn_block_time_for_block(&self.confirmed_chain_tip) + .expect("BUG: unable to get burn block timestamp based on chain tip"); + let mut last_mblock = self.last_mblock.take(); let mut last_mblock_seq = self.last_mblock_seq; let db_config = chainstate.config(); @@ -229,7 +249,7 @@ impl UnconfirmedState { total_fees += stx_fees; total_burns += stx_burns; num_new_mblocks += 1; - all_receipts.push((seq, receipts)); + all_receipts.push((seq, mblock.header, receipts)); last_mblock = Some(mblock_header); last_mblock_seq = seq; @@ -245,10 +265,7 @@ impl UnconfirmedState { }; for tx in &mblock.txs { - mined_txs.insert( - tx.txid(), - (tx.clone(), mblock.block_hash(), mblock.header.sequence), - ); + mined_txs.insert(tx.txid(), (tx.clone(), mblock_hash, seq)); } } @@ -277,6 +294,9 @@ impl UnconfirmedState { total_fees, total_burns, receipts: all_receipts, + burn_block_hash, + burn_block_height, + burn_block_timestamp, }) } @@ -297,7 +317,7 @@ impl UnconfirmedState { &chainstate.db(), &StacksBlockHeader::make_index_block_hash(&consensus_hash, &anchored_block_hash), 0, - u16::max_value(), + u16::MAX, ) } @@ -313,7 +333,7 @@ impl UnconfirmedState { "BUG: code tried to write unconfirmed state to a read-only instance" ); - if self.last_mblock_seq == u16::max_value() { + if self.last_mblock_seq == u16::MAX { // no-op return Ok(Default::default()); } diff --git a/src/chainstate/stacks/events.rs b/src/chainstate/stacks/events.rs index 110c1ad65..26d3768e9 100644 --- a/src/chainstate/stacks/events.rs +++ b/src/chainstate/stacks/events.rs @@ -14,8 +14,8 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::codec::StacksMessageCodec; use crate::types::chainstate::StacksAddress; +use crate::{codec::StacksMessageCodec, types::chainstate::StacksMicroblockHeader}; use burnchains::Txid; use chainstate::stacks::StacksTransaction; use vm::analysis::ContractAnalysis; @@ -60,6 +60,7 @@ pub struct StacksTransactionReceipt { pub stx_burned: u128, pub contract_analysis: Option, pub execution_cost: ExecutionCost, + pub microblock_header: Option, } #[derive(Debug, Clone, PartialEq)] diff --git a/src/chainstate/stacks/miner.rs b/src/chainstate/stacks/miner.rs index a0a62e424..565862e6f 100644 --- a/src/chainstate/stacks/miner.rs +++ b/src/chainstate/stacks/miner.rs @@ -4870,7 +4870,7 @@ pub mod test { &all_microblocks_1[i].1, ), 0, - u16::max_value(), + u16::MAX, ) .unwrap(); let chunk_2_opt = StacksChainState::load_descendant_staging_microblock_stream( @@ -4880,7 +4880,7 @@ pub mod test { &all_microblocks_2[i].1, ), 0, - u16::max_value(), + u16::MAX, ) .unwrap(); diff --git a/src/clarity.rs b/src/clarity.rs index 930b8b927..104b88d86 100644 --- a/src/clarity.rs +++ b/src/clarity.rs @@ -32,7 +32,7 @@ use rusqlite::{Connection, OpenFlags, NO_PARAMS}; use address::c32::c32_address; use chainstate::stacks::index::{storage::TrieFileStorage, MarfTrieId}; use util::db::FromColumn; -use util::hash::Sha512Trunc256Sum; +use util::hash::{bytes_to_hex, Sha512Trunc256Sum}; use util::log; use vm::ContractName; @@ -65,6 +65,8 @@ use serde::Serialize; use serde_json::json; use util::strings::StacksString; +use codec::StacksMessageCodec; + use std::convert::TryFrom; use crate::clarity_vm::database::marf::MarfedKV; @@ -766,6 +768,14 @@ pub fn add_assets(result: &mut serde_json::Value, assets: bool, asset_map: Asset } } +pub fn add_serialized_output(result: &mut serde_json::Value, value: Value) { + let result_raw = { + let bytes = (&value).serialize_to_vec(); + bytes_to_hex(&bytes) + }; + result["output_serialized"] = serde_json::to_value(result_raw.as_str()).unwrap(); +} + /// Returns (process-exit-code, Option) pub fn invoke_command(invoked_by: &str, args: &[String]) -> (i32, Option) { if args.len() < 1 { @@ -1158,9 +1168,11 @@ pub fn invoke_command(invoked_by: &str, args: &[String]) -> (i32, Option { let mut result_json = json!({ - "output": serde_json::to_value(&result).unwrap() + "output": serde_json::to_value(&result).unwrap(), + "success": true, }); + add_serialized_output(&mut result_json, result); add_costs(&mut result_json, costs, cost); (0, Some(result_json)) @@ -1169,7 +1181,8 @@ pub fn invoke_command(invoked_by: &str, args: &[String]) -> (i32, Option (i32, Option { let mut result_json = json!({ - "output": serde_json::to_value(&result).unwrap() + "output": serde_json::to_value(&result).unwrap(), + "success": true, }); + add_serialized_output(&mut result_json, result); add_costs(&mut result_json, costs, cost); (0, Some(result_json)) @@ -1219,7 +1234,8 @@ pub fn invoke_command(invoked_by: &str, args: &[String]) -> (i32, Option (i32, Option { let mut result_json = json!({ - "output": serde_json::to_value(&result).unwrap() + "output": serde_json::to_value(&result).unwrap(), + "success": true, }); + add_serialized_output(&mut result_json, result); add_costs(&mut result_json, costs, cost); (0, Some(result_json)) @@ -1289,7 +1307,8 @@ pub fn invoke_command(invoked_by: &str, args: &[String]) -> (i32, Option (i32, Option (i32, Option (i32, Option (i32, Option(fd: &mut R) -> Result, Error> { - read_next_at_most::(fd, u32::max_value()) + read_next_at_most::(fd, u32::MAX) } } diff --git a/src/core/mempool.rs b/src/core/mempool.rs index bfe8340e4..da5afa51e 100644 --- a/src/core/mempool.rs +++ b/src/core/mempool.rs @@ -816,7 +816,7 @@ impl MemPoolDB { consensus_hash, block_hash, 0, - (i64::max_value() - 1) as u64, + (i64::MAX - 1) as u64, ) .unwrap_or(vec![]) .into_iter() diff --git a/src/core/mod.rs b/src/core/mod.rs index 04af5ffb2..c6e224cee 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -70,8 +70,7 @@ pub const STACKS_2_0_LAST_BLOCK_TO_PROCESS: u64 = 700_000; pub const MAINNET_2_0_GENESIS_ROOT_HASH: &str = "9653c92b1ad726e2dc17862a3786f7438ab9239c16dd8e7aaba8b0b5c34b52af"; -// first burnchain block hash -// TODO: update once we know the true first burnchain block +/// This is the "dummy" parent to the actual first burnchain block that we process. pub const FIRST_BURNCHAIN_CONSENSUS_HASH: ConsensusHash = ConsensusHash([0u8; 20]); // TODO: TO BE SET BY STACKS_V1_MINER_THRESHOLD @@ -81,10 +80,10 @@ pub const BITCOIN_MAINNET_FIRST_BLOCK_HASH: &str = "0000000000000000000ab248c8e35c574514d052a83dbc12669e19bc43df486e"; pub const BITCOIN_MAINNET_INITIAL_REWARD_START_BLOCK: u64 = 651389; -pub const BITCOIN_TESTNET_FIRST_BLOCK_HEIGHT: u64 = 1931620; -pub const BITCOIN_TESTNET_FIRST_BLOCK_TIMESTAMP: u32 = 1612282029; +pub const BITCOIN_TESTNET_FIRST_BLOCK_HEIGHT: u64 = 2000000; +pub const BITCOIN_TESTNET_FIRST_BLOCK_TIMESTAMP: u32 = 1622691840; pub const BITCOIN_TESTNET_FIRST_BLOCK_HASH: &str = - "00000000000000b8275ac9907d4d8f3b862f93d6f986ba628a2784748e56e51b"; + "000000000000010dd0863ec3d7a0bae17c1957ae1de9cbcdae8e77aad33e3b8c"; pub const BITCOIN_REGTEST_FIRST_BLOCK_HEIGHT: u64 = 0; pub const BITCOIN_REGTEST_FIRST_BLOCK_TIMESTAMP: u32 = 0; @@ -145,32 +144,3 @@ pub fn check_fault_injection(fault_name: &str) -> bool { env::var(fault_name) == Ok("1".to_string()) } - -/// Synchronize burn transactions from the Bitcoin blockchain -pub fn sync_burnchain_bitcoin( - working_dir: &String, - network_name: &String, -) -> Result { - use burnchains::bitcoin::indexer::BitcoinIndexer; - let channels = CoordinatorCommunication::instantiate(); - - let mut burnchain = - Burnchain::new(working_dir, &"bitcoin".to_string(), network_name).map_err(|e| { - error!( - "Failed to instantiate burn chain driver for {}: {:?}", - network_name, e - ); - e - })?; - - let new_height_res = burnchain.sync::(&channels.1, None, None); - let new_height = new_height_res.map_err(|e| { - error!( - "Failed to synchronize Bitcoin chain state for {} in {}", - network_name, working_dir - ); - e - })?; - - Ok(new_height) -} diff --git a/src/deps/bitcoin/network/encodable.rs b/src/deps/bitcoin/network/encodable.rs index c816a71e8..e5db0dbe0 100644 --- a/src/deps/bitcoin/network/encodable.rs +++ b/src/deps/bitcoin/network/encodable.rs @@ -538,27 +538,27 @@ mod tests { fn deserialize_nonminimal_vec() { match deserialize::>(&[0xfd, 0x00, 0x00]) { Err(Error::ParseFailed("non-minimal varint")) => {} - x => panic!(x), + x => std::panic::panic_any(x), } match deserialize::>(&[0xfd, 0xfc, 0x00]) { Err(Error::ParseFailed("non-minimal varint")) => {} - x => panic!(x), + x => std::panic::panic_any(x), } match deserialize::>(&[0xfe, 0xff, 0x00, 0x00, 0x00]) { Err(Error::ParseFailed("non-minimal varint")) => {} - x => panic!(x), + x => std::panic::panic_any(x), } match deserialize::>(&[0xfe, 0xff, 0xff, 0x00, 0x00]) { Err(Error::ParseFailed("non-minimal varint")) => {} - x => panic!(x), + x => std::panic::panic_any(x), } match deserialize::>(&[0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) { Err(Error::ParseFailed("non-minimal varint")) => {} - x => panic!(x), + x => std::panic::panic_any(x), } match deserialize::>(&[0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00]) { Err(Error::ParseFailed("non-minimal varint")) => {} - x => panic!(x), + x => std::panic::panic_any(x), } let mut vec_256 = vec![0; 259]; diff --git a/src/lib.rs b/src/lib.rs index dbbe9e078..0e77b65f5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,7 +28,6 @@ extern crate rand_chacha; extern crate rusqlite; extern crate secp256k1; extern crate serde; -extern crate tini; #[macro_use] extern crate lazy_static; extern crate integer_sqrt; diff --git a/src/main.rs b/src/main.rs index 9cebb248b..2851cf7ab 100644 --- a/src/main.rs +++ b/src/main.rs @@ -719,25 +719,25 @@ simulating a miner. // initial argon balances -- see testnet/stacks-node/conf/testnet-follower-conf.toml let initial_balances = vec![ ( - StacksAddress::from_string("STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6") + StacksAddress::from_string("ST2QKZ4FKHAH1NQKYKYAYZPY440FEPK7GZ1R5HBP2") .unwrap() .to_account_principal(), 10000000000000000, ), ( - StacksAddress::from_string("ST11NJTTKGVT6D1HY4NJRVQWMQM7TVAR091EJ8P2Y") + StacksAddress::from_string("ST319CF5WV77KYR1H3GT0GZ7B8Q4AQPY42ETP1VPF") .unwrap() .to_account_principal(), 10000000000000000, ), ( - StacksAddress::from_string("ST1HB1T8WRNBYB0Y3T7WXZS38NKKPTBR3EG9EPJKR") + StacksAddress::from_string("ST221Z6TDTC5E0BYR2V624Q2ST6R0Q71T78WTAX6H") .unwrap() .to_account_principal(), 10000000000000000, ), ( - StacksAddress::from_string("STRYYQQ9M8KAF4NS7WNZQYY59X93XEKR31JP64CP") + StacksAddress::from_string("ST2TFVBMRPS5SSNP98DQKQ5JNB2B6NZM91C4K3P7B") .unwrap() .to_account_principal(), 10000000000000000, @@ -755,8 +755,13 @@ simulating a miner. let burnchain = Burnchain::regtest(&burnchain_db_path); let first_burnchain_block_height = burnchain.first_block_height; let first_burnchain_block_hash = burnchain.first_block_hash; - let indexer: BitcoinIndexer = burnchain.make_indexer().unwrap(); - let (mut new_sortition_db, _) = burnchain.connect_db(&indexer, true).unwrap(); + let (mut new_sortition_db, _) = burnchain + .connect_db( + true, + first_burnchain_block_hash, + BITCOIN_REGTEST_FIRST_BLOCK_TIMESTAMP.into(), + ) + .unwrap(); let old_burnchaindb = BurnchainDB::connect( &old_burnchaindb_path, @@ -839,7 +844,13 @@ simulating a miner. let mut known_stacks_blocks = HashSet::new(); let mut next_arrival = 0; - let (p2p_new_sortition_db, _) = burnchain.connect_db(&indexer, true).unwrap(); + let (p2p_new_sortition_db, _) = burnchain + .connect_db( + true, + first_burnchain_block_hash, + BITCOIN_REGTEST_FIRST_BLOCK_TIMESTAMP.into(), + ) + .unwrap(); let (mut p2p_chainstate, _) = StacksChainState::open_with_block_limit( false, 0x80000000, diff --git a/src/net/atlas/download.rs b/src/net/atlas/download.rs index 35b017bc7..0fec7a721 100644 --- a/src/net/atlas/download.rs +++ b/src/net/atlas/download.rs @@ -816,54 +816,56 @@ impl BatchedRequestsState state.remaining.len() ); - for (event_id, request) in state.remaining.drain() { - match network.http.get_conversation(event_id) { - None => { - if network.http.is_connecting(event_id) { - debug!( - "Atlas: Request {} (event_id: {}) is still connecting", - request, event_id - ); - pending_requests.insert(event_id, request); - } else { - debug!( - "Atlas: Request {} (event_id: {}) failed to connect. Temporarily blocking URL", - request, - event_id - ); - let peer_url = request.get_url().clone(); - state.faulty_peers.insert(event_id, peer_url); - } - } - Some(ref mut convo) => { - match convo.try_get_response() { - None => { - // still waiting + PeerNetwork::with_http(network, |_, ref mut http| { + for (event_id, request) in state.remaining.drain() { + match http.get_conversation(event_id) { + None => { + if http.is_connecting(event_id) { debug!( - "Atlas: Request {} (event_id: {}) is still waiting for a response", + "Atlas: Request {} (event_id: {}) is still connecting", + request, event_id + ); + pending_requests.insert(event_id, request); + } else { + debug!( + "Atlas: Request {} (event_id: {}) failed to connect. Temporarily blocking URL", request, event_id ); - pending_requests.insert(event_id, request); - continue; - } - Some(response) => { let peer_url = request.get_url().clone(); - - if let HttpResponseType::NotFound(_, _) = response { - state.faulty_peers.insert(event_id, peer_url); + state.faulty_peers.insert(event_id, peer_url); + } + } + Some(ref mut convo) => { + match convo.try_get_response() { + None => { + // still waiting + debug!( + "Atlas: Request {} (event_id: {}) is still waiting for a response", + request, + event_id + ); + pending_requests.insert(event_id, request); continue; } - debug!( - "Atlas: Request {} (event_id: {}) received response {:?}", - request, event_id, response - ); - state.succeeded.insert(request, Some(response)); + Some(response) => { + let peer_url = request.get_url().clone(); + + if let HttpResponseType::NotFound(_, _) = response { + state.faulty_peers.insert(event_id, peer_url); + continue; + } + debug!( + "Atlas: Request {} (event_id: {}) received response {:?}", + request, event_id, response + ); + state.succeeded.insert(request, Some(response)); + } } } } } - } + }); if pending_requests.len() > 0 { // We need to keep polling diff --git a/src/net/chat.rs b/src/net/chat.rs index 6ca9b9aa8..cda196f4d 100644 --- a/src/net/chat.rs +++ b/src/net/chat.rs @@ -1334,7 +1334,7 @@ impl ConversationP2P { ); match res { Ok(hashes) => Ok(hashes), - Err(db_error::NotFoundError) => { + Err(db_error::NotFoundError) | Err(db_error::InvalidPoxSortition) => { debug!( "{:?}: Failed to load ancestor hashes from {}", &_local_peer, &tip_snapshot.consensus_hash diff --git a/src/net/db.rs b/src/net/db.rs index 07b8d5d94..1876696d9 100644 --- a/src/net/db.rs +++ b/src/net/db.rs @@ -478,7 +478,7 @@ impl PeerDB { let deny_cidrs = PeerDB::get_denied_cidrs(tx)?; for (prefix, mask) in deny_cidrs.into_iter() { debug!("Refresh deny {}/{}", &prefix, mask); - PeerDB::apply_cidr_filter(tx, &prefix, mask, "denied", i64::max_value())?; + PeerDB::apply_cidr_filter(tx, &prefix, mask, "denied", i64::MAX)?; } Ok(()) } @@ -488,7 +488,7 @@ impl PeerDB { let allow_cidrs = PeerDB::get_allowed_cidrs(tx)?; for (prefix, mask) in allow_cidrs.into_iter() { debug!("Refresh allow {}/{}", &prefix, mask); - PeerDB::apply_cidr_filter(tx, &prefix, mask, "allowed", i64::max_value())?; + PeerDB::apply_cidr_filter(tx, &prefix, mask, "allowed", i64::MAX)?; } Ok(()) } @@ -845,6 +845,13 @@ impl PeerDB { Ok(allow_rows) } + /// Get the bootstrap peers + pub fn get_bootstrap_peers(conn: &DBConn, network_id: u32) -> Result, db_error> { + let sql = "SELECT * FROM frontier WHERE initial = 1 AND network_id = ?1 ORDER BY RANDOM()"; + let allow_rows = query_rows::(conn, sql, &[&network_id])?; + Ok(allow_rows) + } + /// Insert or replace a neighbor into a given slot pub fn insert_or_replace_peer<'a>( tx: &mut Transaction<'a>, @@ -1228,7 +1235,7 @@ impl PeerDB { PeerDB::add_cidr_prefix(tx, "denied_prefixes", prefix, mask)?; debug!("Apply deny {}/{}", &prefix, mask); - PeerDB::apply_cidr_filter(tx, prefix, mask, "denied", i64::max_value())?; + PeerDB::apply_cidr_filter(tx, prefix, mask, "denied", i64::MAX)?; Ok(()) } @@ -2004,7 +2011,7 @@ mod test { .unwrap() .unwrap(); assert_eq!(n1.allowed, 12345); - assert_eq!(n1.denied, i64::max_value()); + assert_eq!(n1.denied, i64::MAX); assert_eq!(n2.allowed, 12345); assert_eq!(n2.denied, 67890); @@ -2041,7 +2048,7 @@ mod test { .unwrap(); assert_eq!(n1.allowed, -1); - assert_eq!(n1.denied, i64::max_value()); + assert_eq!(n1.denied, i64::MAX); assert_eq!(n2.allowed, 12345); assert_eq!(n2.denied, 67890); } @@ -2161,7 +2168,7 @@ mod test { .unwrap() .unwrap(); - assert_eq!(n1.denied, i64::max_value()); + assert_eq!(n1.denied, i64::MAX); assert_eq!(n2.denied, 0); // refreshed; no longer denied assert_eq!(n1.allowed, -1); diff --git a/src/net/download.rs b/src/net/download.rs index cfa6cd661..cd4ac02bf 100644 --- a/src/net/download.rs +++ b/src/net/download.rs @@ -441,102 +441,104 @@ impl BlockDownloader { // requests that are still pending let mut pending_block_requests = HashMap::new(); - for (block_key, event_id) in self.getblock_requests.drain() { - match network.http.get_conversation(event_id) { - None => { - if network.http.is_connecting(event_id) { - debug!( - "Event {} ({:?}, {:?} for block {} is not connected yet", - event_id, - &block_key.neighbor, - &block_key.data_url, - &block_key.index_block_hash - ); - pending_block_requests.insert(block_key, event_id); - } else { - self.dead_peers.push(event_id); - - let is_always_allowed = match PeerDB::get_peer( - &network.peerdb.conn(), - block_key.neighbor.network_id, - &block_key.neighbor.addrbytes, - block_key.neighbor.port, - ) { - Ok(Some(neighbor)) => neighbor.is_always_allowed(), - _ => false, - }; - - if !is_always_allowed { - debug!("Event {} ({:?}, {:?}) for block {} failed to connect. Temporarily blocking URL", event_id, &block_key.neighbor, &block_key.data_url, &block_key.index_block_hash); - - // don't try this again for a while - self.blocked_urls.insert( - block_key.data_url, - get_epoch_time_secs() + BLOCK_DOWNLOAD_BAN_URL, + PeerNetwork::with_http(network, |ref mut network, ref mut http| { + for (block_key, event_id) in self.getblock_requests.drain() { + match http.get_conversation(event_id) { + None => { + if http.is_connecting(event_id) { + debug!( + "Event {} ({:?}, {:?} for block {} is not connected yet", + event_id, + &block_key.neighbor, + &block_key.data_url, + &block_key.index_block_hash ); + pending_block_requests.insert(block_key, event_id); } else { - debug!("Event {} ({:?}, {:?}, always-allowed) for block {} failed to connect", event_id, &block_key.neighbor, &block_key.data_url, &block_key.index_block_hash); + self.dead_peers.push(event_id); - if cfg!(test) { - // just mark that we would have blocked it - self.blocked_urls - .insert(block_key.data_url, get_epoch_time_secs() + 10); + let is_always_allowed = match PeerDB::get_peer( + &network.peerdb.conn(), + block_key.neighbor.network_id, + &block_key.neighbor.addrbytes, + block_key.neighbor.port, + ) { + Ok(Some(neighbor)) => neighbor.is_always_allowed(), + _ => false, + }; + + if !is_always_allowed { + debug!("Event {} ({:?}, {:?}) for block {} failed to connect. Temporarily blocking URL", event_id, &block_key.neighbor, &block_key.data_url, &block_key.index_block_hash); + + // don't try this again for a while + self.blocked_urls.insert( + block_key.data_url, + get_epoch_time_secs() + BLOCK_DOWNLOAD_BAN_URL, + ); + } else { + debug!("Event {} ({:?}, {:?}, always-allowed) for block {} failed to connect", event_id, &block_key.neighbor, &block_key.data_url, &block_key.index_block_hash); + + if cfg!(test) { + // just mark that we would have blocked it + self.blocked_urls + .insert(block_key.data_url, get_epoch_time_secs() + 10); + } } } } - } - Some(ref mut convo) => { - match convo.try_get_response() { - None => { - // still waiting - debug!("Event {} ({:?}, {:?} for block {}) is still waiting for a response", event_id, &block_key.neighbor, &block_key.data_url, &block_key.index_block_hash); - pending_block_requests.insert(block_key, event_id); - } - Some(http_response) => match http_response { - HttpResponseType::Block(_md, block) => { - if StacksBlockHeader::make_index_block_hash( - &block_key.consensus_hash, - &block.block_hash(), - ) != block_key.index_block_hash - { - info!("Invalid block from {:?} ({:?}): did not ask for block {}/{}", &block_key.neighbor, &block_key.data_url, block_key.consensus_hash, block.block_hash()); + Some(ref mut convo) => { + match convo.try_get_response() { + None => { + // still waiting + debug!("Event {} ({:?}, {:?} for block {}) is still waiting for a response", event_id, &block_key.neighbor, &block_key.data_url, &block_key.index_block_hash); + pending_block_requests.insert(block_key, event_id); + } + Some(http_response) => match http_response { + HttpResponseType::Block(_md, block) => { + if StacksBlockHeader::make_index_block_hash( + &block_key.consensus_hash, + &block.block_hash(), + ) != block_key.index_block_hash + { + info!("Invalid block from {:?} ({:?}): did not ask for block {}/{}", &block_key.neighbor, &block_key.data_url, block_key.consensus_hash, block.block_hash()); + self.broken_peers.push(event_id); + self.broken_neighbors.push(block_key.neighbor.clone()); + } else { + // got the block + debug!( + "Got block {}: {}/{}", + &block_key.sortition_height, + &block_key.consensus_hash, + block.block_hash() + ); + self.blocks.insert(block_key, block); + } + } + // TODO: redirect? + HttpResponseType::NotFound(_, _) => { + // remote peer didn't have the block + info!("Remote neighbor {:?} ({:?}) does not actually have block {} indexed at {} ({})", &block_key.neighbor, &block_key.data_url, block_key.sortition_height, &block_key.index_block_hash, &block_key.consensus_hash); + + // the fact that we asked this peer means that it's block inv indicated + // it was present, so the absence is the mark of a broken peer self.broken_peers.push(event_id); self.broken_neighbors.push(block_key.neighbor.clone()); - } else { - // got the block - debug!( - "Got block {}: {}/{}", - &block_key.sortition_height, - &block_key.consensus_hash, - block.block_hash() - ); - self.blocks.insert(block_key, block); } - } - // TODO: redirect? - HttpResponseType::NotFound(_, _) => { - // remote peer didn't have the block - info!("Remote neighbor {:?} ({:?}) does not actually have block {} indexed at {} ({})", &block_key.neighbor, &block_key.data_url, block_key.sortition_height, &block_key.index_block_hash, &block_key.consensus_hash); - - // the fact that we asked this peer means that it's block inv indicated - // it was present, so the absence is the mark of a broken peer - self.broken_peers.push(event_id); - self.broken_neighbors.push(block_key.neighbor.clone()); - } - _ => { - // wrong message response - info!( - "Got bad HTTP response from {:?}: {:?}", - &block_key.data_url, &http_response - ); - self.broken_peers.push(event_id); - self.broken_neighbors.push(block_key.neighbor.clone()); - } - }, + _ => { + // wrong message response + info!( + "Got bad HTTP response from {:?}: {:?}", + &block_key.data_url, &http_response + ); + self.broken_peers.push(event_id); + self.broken_neighbors.push(block_key.neighbor.clone()); + } + }, + } } } } - } + }); // are we done? if pending_block_requests.len() == 0 { @@ -568,93 +570,95 @@ impl BlockDownloader { // requests that are still pending let mut pending_microblock_requests = HashMap::new(); - for (block_key, event_id) in self.getmicroblocks_requests.drain() { - let rh_block_key = block_key.clone(); - match network.http.get_conversation(event_id) { - None => { - if network.http.is_connecting(event_id) { - debug!("Event {} ({:?}, {:?} for microblocks built by ({}) is not connected yet", &block_key.neighbor, &block_key.data_url, &block_key.index_block_hash, event_id); - pending_microblock_requests.insert(block_key, event_id); - } else { - self.dead_peers.push(event_id); + PeerNetwork::with_http(network, |ref mut network, ref mut http| { + for (block_key, event_id) in self.getmicroblocks_requests.drain() { + let rh_block_key = block_key.clone(); + match http.get_conversation(event_id) { + None => { + if http.is_connecting(event_id) { + debug!("Event {} ({:?}, {:?} for microblocks built by ({}) is not connected yet", &block_key.neighbor, &block_key.data_url, &block_key.index_block_hash, event_id); + pending_microblock_requests.insert(block_key, event_id); + } else { + self.dead_peers.push(event_id); - let is_always_allowed = match PeerDB::get_peer( - &network.peerdb.conn(), - block_key.neighbor.network_id, - &block_key.neighbor.addrbytes, - block_key.neighbor.port, - ) { - Ok(Some(neighbor)) => neighbor.is_always_allowed(), - _ => false, - }; + let is_always_allowed = match PeerDB::get_peer( + &network.peerdb.conn(), + block_key.neighbor.network_id, + &block_key.neighbor.addrbytes, + block_key.neighbor.port, + ) { + Ok(Some(neighbor)) => neighbor.is_always_allowed(), + _ => false, + }; - if !is_always_allowed { - debug!( - "Event {} ({:?}, {:?} for microblocks built by ({}) failed to connect. Temporarily blocking URL.", - event_id, - &block_key.neighbor, - &block_key.data_url, - &block_key.index_block_hash, - ); + if !is_always_allowed { + debug!( + "Event {} ({:?}, {:?} for microblocks built by ({}) failed to connect. Temporarily blocking URL.", + event_id, + &block_key.neighbor, + &block_key.data_url, + &block_key.index_block_hash, + ); - // don't try this again for a while - self.blocked_urls.insert( - block_key.data_url, - get_epoch_time_secs() + BLOCK_DOWNLOAD_BAN_URL, - ); + // don't try this again for a while + self.blocked_urls.insert( + block_key.data_url, + get_epoch_time_secs() + BLOCK_DOWNLOAD_BAN_URL, + ); + } } } - } - Some(ref mut convo) => { - match convo.try_get_response() { - None => { - // still waiting - debug!("Event {} ({:?}, {:?} for microblocks built by {:?}) is still waiting for a response", &block_key.neighbor, &block_key.data_url, &block_key.index_block_hash, event_id); - pending_microblock_requests.insert(rh_block_key, event_id); - } - Some(http_response) => match http_response { - HttpResponseType::Microblocks(_md, microblocks) => { - if microblocks.len() == 0 { - // we wouldn't have asked for a 0-length stream - info!("Got unexpected zero-length microblock stream from {:?} ({:?})", &block_key.neighbor, &block_key.data_url); + Some(ref mut convo) => { + match convo.try_get_response() { + None => { + // still waiting + debug!("Event {} ({:?}, {:?} for microblocks built by {:?}) is still waiting for a response", &block_key.neighbor, &block_key.data_url, &block_key.index_block_hash, event_id); + pending_microblock_requests.insert(rh_block_key, event_id); + } + Some(http_response) => match http_response { + HttpResponseType::Microblocks(_md, microblocks) => { + if microblocks.len() == 0 { + // we wouldn't have asked for a 0-length stream + info!("Got unexpected zero-length microblock stream from {:?} ({:?})", &block_key.neighbor, &block_key.data_url); + self.broken_peers.push(event_id); + self.broken_neighbors.push(block_key.neighbor.clone()); + } else { + // have microblocks (but we don't know yet if they're well-formed) + debug!( + "Got (tentative) microblocks {}: {}/{}-{}", + block_key.sortition_height, + &block_key.consensus_hash, + &block_key.index_block_hash, + microblocks[0].block_hash() + ); + self.microblocks.insert(block_key, microblocks); + } + } + // TODO: redirect? + HttpResponseType::NotFound(_, _) => { + // remote peer didn't have the microblock, even though their blockinv said + // they did. + info!("Remote neighbor {:?} ({:?}) does not have microblock stream indexed at {}", &block_key.neighbor, &block_key.data_url, &block_key.index_block_hash); + + // the fact that we asked this peer means that it's block inv indicated + // it was present, so the absence is the mark of a broken peer. + // HOWEVER, there has been some bugs recently about nodes reporting + // invalid microblock streams as present, even though they are + // truly absent. Don't punish these peers with a ban; just don't + // talk to them for a while. + } + _ => { + // wrong message response + info!("Got bad HTTP response from {:?}", &block_key.data_url); self.broken_peers.push(event_id); self.broken_neighbors.push(block_key.neighbor.clone()); - } else { - // have microblocks (but we don't know yet if they're well-formed) - debug!( - "Got (tentative) microblocks {}: {}/{}-{}", - block_key.sortition_height, - &block_key.consensus_hash, - &block_key.index_block_hash, - microblocks[0].block_hash() - ); - self.microblocks.insert(block_key, microblocks); } - } - // TODO: redirect? - HttpResponseType::NotFound(_, _) => { - // remote peer didn't have the microblock, even though their blockinv said - // they did. - info!("Remote neighbor {:?} ({:?}) does not have microblock stream indexed at {}", &block_key.neighbor, &block_key.data_url, &block_key.index_block_hash); - - // the fact that we asked this peer means that it's block inv indicated - // it was present, so the absence is the mark of a broken peer. - // HOWEVER, there has been some bugs recently about nodes reporting - // invalid microblock streams as present, even though they are - // truly absent. Don't punish these peers with a ban; just don't - // talk to them for a while. - } - _ => { - // wrong message response - info!("Got bad HTTP response from {:?}", &block_key.data_url); - self.broken_peers.push(event_id); - self.broken_neighbors.push(block_key.neighbor.clone()); - } - }, + }, + } } } } - } + }); // are we done? if pending_microblock_requests.len() == 0 { @@ -729,11 +733,19 @@ impl BlockDownloader { last_ancestor.block_height, &last_ancestor.consensus_hash ); - let local_blocks = ic.get_stacks_header_hashes( - sortition_height_end - sortition_height_start, - &last_ancestor.consensus_hash, - header_cache, - )?; + let local_blocks = ic + .get_stacks_header_hashes( + sortition_height_end - sortition_height_start, + &last_ancestor.consensus_hash, + header_cache, + ) + .map_err(|e| { + if let db_error::InvalidPoxSortition = e { + net_error::Transient("Invalid PoX sortition; try again".to_string()) + } else { + net_error::DBError(e) + } + })?; for (_i, (_consensus_hash, _block_hash_opt)) in local_blocks.iter().enumerate() { test_debug!( @@ -773,7 +785,8 @@ impl BlockDownloader { } } test_debug!( - "at sortition height {} (block bit {}): {:?}/{:?} blocks available from {:?}", + "{:?}: At sortition height {} (block bit {}): {:?}/{:?} blocks available from {:?}", + _local_peer, sortition_bit - 1, sortition_bit + first_block_height, &consensus_hash, @@ -785,7 +798,8 @@ impl BlockDownloader { None => { // no sortition test_debug!( - "at sortition height {} (block bit {}): {:?}/(no sortition)", + "{:?}: At sortition height {} (block bit {}): {:?}/(no sortition)", + _local_peer, sortition_bit - 1, sortition_bit + first_block_height, &consensus_hash @@ -795,8 +809,9 @@ impl BlockDownloader { if cfg!(test) { for (_nk, stats) in inv_state.block_stats.iter() { if stats.inv.has_ith_block(sortition_bit + first_block_height) { - test_debug!( - "BUT! Neighbor {:?} has block bit {} set!: {:?}", + debug!( + "{:?}: BUT! Neighbor {:?} has block bit {} set!: {:?}", + _local_peer, &_nk, sortition_bit + first_block_height, &stats @@ -814,6 +829,7 @@ impl BlockDownloader { /// Find out which neighbors can serve a confirmed microblock stream, given the /// burn/block-header-hashes of the sortition that _produced_ them. fn get_microblock_stream_availability( + _local_peer: &LocalPeer, inv_state: &InvState, sortdb: &SortitionDB, consensus_hash: &ConsensusHash, @@ -837,7 +853,8 @@ impl BlockDownloader { let mut neighbors = vec![]; for (nk, stats) in inv_state.block_stats.iter() { test_debug!( - "stats for {:?}: {:?}; testing block {}", + "{:?}: stats for {:?}: {:?}; testing block {}", + _local_peer, &nk, &stats, block_height @@ -846,8 +863,9 @@ impl BlockDownloader { neighbors.push(nk.clone()); } } - test_debug!( - "at sortition height {} (block {}): {:?}/{:?} microblocks available from {:?}", + debug!( + "{:?}: At sortition height {} (block {}): {:?}/{:?} microblocks available from {:?}", + _local_peer, block_height - sortdb.first_block_height + 1, block_height, consensus_hash, @@ -872,9 +890,14 @@ impl BlockDownloader { /// Set a hint that a block is now available from a remote peer, if we're idling or we're ahead /// of the given height. - pub fn hint_block_sortition_height_available(&mut self, block_sortition_height: u64) -> () { - if self.empty_block_download_passes > 0 - || block_sortition_height < self.block_sortition_height + 1 + pub fn hint_block_sortition_height_available( + &mut self, + block_sortition_height: u64, + ibd: bool, + ) -> () { + if (ibd && self.state == BlockDownloaderState::DNSLookupBegin) + || (self.empty_block_download_passes > 0 + || block_sortition_height < self.block_sortition_height + 1) { // idling on new blocks to fetch self.empty_block_download_passes = 0; @@ -887,6 +910,14 @@ impl BlockDownloader { block_sortition_height.saturating_sub(1) ); } + if ibd && self.state != BlockDownloaderState::DNSLookupBegin { + debug!( + "Will NOT awaken downloader to start scanning at block sortiton height {}, because it is busy at {} in state {:?}", + block_sortition_height.saturating_sub(1), + self.block_sortition_height, + self.state + ); + } } /// Set a hint that a confirmed microblock stream is now available from a remote peer, if we're idling or we're ahead @@ -894,9 +925,11 @@ impl BlockDownloader { pub fn hint_microblock_sortition_height_available( &mut self, mblock_sortition_height: u64, + ibd: bool, ) -> () { - if self.empty_microblock_download_passes > 0 - || mblock_sortition_height < self.microblock_sortition_height + 1 + if (ibd && self.state == BlockDownloaderState::DNSLookupBegin) + || (self.empty_microblock_download_passes > 0 + || mblock_sortition_height < self.microblock_sortition_height + 1) { // idling on new blocks to fetch self.empty_microblock_download_passes = 0; @@ -908,24 +941,20 @@ impl BlockDownloader { mblock_sortition_height.saturating_sub(1) ); } + if ibd && self.state != BlockDownloaderState::DNSLookupBegin { + debug!( + "Will NOT awaken downloader to start scanning at microblock sortiton height {}, because it is busy at {} in state {:?}", + mblock_sortition_height.saturating_sub(1), + self.microblock_sortition_height, + self.state + ); + } } /// Set a hint that we should re-scan for blocks - pub fn hint_download_rescan(&mut self, target_height: u64) -> () { - if self.empty_block_download_passes > 0 { - self.empty_block_download_passes = 0; - self.next_block_sortition_height = target_height; - } - - if self.empty_microblock_download_passes > 0 { - self.empty_microblock_download_passes = 0; - self.next_microblock_sortition_height = target_height; - } - - debug!( - "Awaken downloader to restart scanning at sortition height {}", - target_height - ); + pub fn hint_download_rescan(&mut self, target_sortition_height: u64, ibd: bool) -> () { + self.hint_block_sortition_height_available(target_sortition_height, ibd); + self.hint_microblock_sortition_height_available(target_sortition_height, ibd); } // are we doing the initial block download? @@ -999,9 +1028,9 @@ impl PeerNetwork { } /// Pass a hint to the downloader to re-scan - pub fn hint_download_rescan(&mut self, target_height: u64) -> () { + pub fn hint_download_rescan(&mut self, target_height: u64, ibd: bool) -> () { match self.block_downloader { - Some(ref mut dl) => dl.hint_download_rescan(target_height), + Some(ref mut dl) => dl.hint_download_rescan(target_height, ibd), None => {} } } @@ -1240,12 +1269,9 @@ impl PeerNetwork { continue; } - test_debug!( + debug!( "{:?}: Do not have anchored block {}/{} ({})", - &self.local_peer, - &consensus_hash, - &block_hash, - &index_block_hash + &self.local_peer, &consensus_hash, &block_hash, &index_block_hash ); (consensus_hash, block_hash) @@ -1339,6 +1365,7 @@ impl PeerNetwork { // microblocks built off of this block's _parent_) let mut microblock_stream_neighbors = match self.inv_state { Some(ref inv_state) => BlockDownloader::get_microblock_stream_availability( + &self.local_peer, inv_state, sortdb, &consensus_hash, @@ -1381,9 +1408,20 @@ impl PeerNetwork { &target_block_hash, ); - // don't request the same data from the same data url, in case multiple peers report the - // same data url (e.g. two peers sharing a Gaia hub). - let block_urls: HashSet = HashSet::new(); + debug!( + "{:?}: Consider {} sortition {} {}/{} from {} neighbors", + &self.local_peer, + if microblocks { + "microblock stream" + } else { + "anchored block" + }, + start_sortition_height + (i as u64), + &target_consensus_hash, + &target_block_hash, + neighbors.len() + ); + (&mut neighbors[..]).shuffle(&mut thread_rng()); let mut requests = VecDeque::new(); @@ -1391,15 +1429,20 @@ impl PeerNetwork { let data_url = match self.get_data_url(&nk) { Some(url) => url, None => { + debug!( + "{:?}: Unable to request {} from {}: no data URL", + &self.local_peer, &target_index_block_hash, &nk + ); continue; } }; if data_url.len() == 0 { // peer doesn't yet know its public IP address, and isn't given a data URL // directly - continue; - } - if block_urls.contains(&data_url) { + debug!( + "{:?}: Unable to request {} from {}: no data URL", + &self.local_peer, &target_index_block_hash, &nk + ); continue; } @@ -1428,7 +1471,7 @@ impl PeerNetwork { continue; } - test_debug!( + debug!( "{:?}: Make request for {} at sortition height {} to {:?}: {:?}/{:?}", &self.local_peer, if microblocks { @@ -1851,33 +1894,35 @@ impl PeerNetwork { request: HttpRequestType, chainstate: &mut StacksChainState, ) -> Result { - PeerNetwork::with_network_state( - self, - |ref mut network, ref mut network_state| match network.http.connect_http( - network_state, - data_url.clone(), - addr.clone(), - Some(request.clone()), - ) { - Ok(event_id) => Ok(event_id), - Err(net_error::AlreadyConnected(event_id, _)) => { - match network.http.get_conversation_and_socket(event_id) { - (Some(ref mut convo), Some(ref mut socket)) => { - convo.send_request(request)?; - HttpPeer::saturate_http_socket(socket, convo, chainstate)?; - Ok(event_id) - } - (_, _) => { - debug!("HTTP failed to connect to {:?}, {:?}", &data_url, &addr); - Err(net_error::PeerNotConnected) + PeerNetwork::with_network_state(self, |ref mut network, ref mut network_state| { + PeerNetwork::with_http(network, |ref mut network, ref mut http| { + match http.connect_http( + network_state, + network, + data_url.clone(), + addr.clone(), + Some(request.clone()), + ) { + Ok(event_id) => Ok(event_id), + Err(net_error::AlreadyConnected(event_id, _)) => { + match http.get_conversation_and_socket(event_id) { + (Some(ref mut convo), Some(ref mut socket)) => { + convo.send_request(request)?; + HttpPeer::saturate_http_socket(socket, convo, chainstate)?; + Ok(event_id) + } + (_, _) => { + debug!("HTTP failed to connect to {:?}, {:?}", &data_url, &addr); + Err(net_error::PeerNotConnected) + } } } + Err(e) => { + return Err(e); + } } - Err(e) => { - return Err(e); - } - }, - ) + }) + }) } /// Start a request, given the list of request keys to consider. Use the given request_factory to @@ -2254,7 +2299,8 @@ impl PeerNetwork { for (height, requests) in downloader.blocks_to_try.iter() { assert!( requests.len() > 0, - format!("Empty block requests at height {}", height) + "Empty block requests at height {}", + height ); debug!( " Height {}: anchored block {} available from {} peers: {:?}", @@ -2270,7 +2316,8 @@ impl PeerNetwork { for (height, requests) in downloader.microblocks_to_try.iter() { assert!( requests.len() > 0, - format!("Empty microblock requests at height {}", height) + "Empty microblock requests at height {}", + height ); debug!( " Height {}: microblocks {} available from {} peers: {:?}", @@ -2320,6 +2367,7 @@ impl PeerNetwork { sortdb: &SortitionDB, chainstate: &mut StacksChainState, dns_client: &mut DNSClient, + ibd: bool, ) -> Result< ( bool, @@ -2332,8 +2380,16 @@ impl PeerNetwork { ), net_error, > { - if self.inv_state.is_none() { - test_debug!("{:?}: Inv state not initialized yet", &self.local_peer); + if let Some(ref inv_state) = self.inv_state { + if !inv_state.has_inv_data_for_downloader(ibd) { + debug!( + "{:?}: No inventory state tracked, so no download actions to take (ibd={})", + &self.local_peer, ibd + ); + return Err(net_error::NotConnected); + } + } else { + debug!("{:?}: Inv state not initialized yet", &self.local_peer); return Err(net_error::NotConnected); } @@ -2343,15 +2399,22 @@ impl PeerNetwork { let mut last_inv_update_at = 0; let mut inv_start_sortition = 0; + let mut num_inv_states = 0; if let Some(ref inv_state) = self.inv_state { last_inv_update_at = inv_state.last_change_at; inv_start_sortition = inv_state.block_sortition_start; + num_inv_states = inv_state.block_stats.len(); } match self.block_downloader { Some(ref mut downloader) => { + debug!("{:?}: Have {} inventory state(s) tracked, so take download actions starting from ({},{}, next {},{}) (ibd={})", + &self.local_peer, num_inv_states, downloader.block_sortition_height, downloader.microblock_sortition_height, + downloader.next_block_sortition_height, downloader.next_microblock_sortition_height, ibd); + if downloader.empty_block_download_passes > 0 && downloader.empty_microblock_download_passes > 0 + && !ibd { if downloader.last_inv_update_at == last_inv_update_at && downloader.finished_scan_at + downloader.download_interval diff --git a/src/net/http.rs b/src/net/http.rs index 2bb12d2b4..b53b0ddac 100644 --- a/src/net/http.rs +++ b/src/net/http.rs @@ -296,7 +296,7 @@ impl HttpChunkedTransferReaderState { chunk_read: 0, max_size: max_size, total_size: 0, - last_chunk_size: u64::max_value(), // if this ever becomes 0, then we should expect chunk boundary '0\r\n\r\n' and EOF + last_chunk_size: u64::MAX, // if this ever becomes 0, then we should expect chunk boundary '0\r\n\r\n' and EOF chunk_buffer: [0u8; 18], i: 0, } @@ -4808,11 +4808,11 @@ mod test { for (data, request) in tests.iter() { let req = HttpRequestPreamble::consensus_deserialize(&mut data.as_bytes()); - assert!(req.is_ok(), format!("{:?}", &req)); + assert!(req.is_ok(), "{:?}", &req); assert_eq!(req.unwrap(), *request); let sreq = StacksHttpPreamble::consensus_deserialize(&mut data.as_bytes()); - assert!(sreq.is_ok(), format!("{:?}", &sreq)); + assert!(sreq.is_ok(), "{:?}", &sreq); assert_eq!( sreq.unwrap(), StacksHttpPreamble::Request((*request).clone()) @@ -4850,11 +4850,11 @@ mod test { for (data, request) in tests.iter() { let req = HttpRequestPreamble::consensus_deserialize(&mut data.as_bytes()); - assert!(req.is_ok(), format!("{:?}", &req)); + assert!(req.is_ok(), "{:?}", &req); assert_eq!(req.unwrap(), *request); let sreq = StacksHttpPreamble::consensus_deserialize(&mut data.as_bytes()); - assert!(sreq.is_ok(), format!("{:?}", &sreq)); + assert!(sreq.is_ok(), "{:?}", &sreq); assert_eq!( sreq.unwrap(), StacksHttpPreamble::Request((*request).clone()) @@ -4894,11 +4894,11 @@ mod test { for (data, errstr) in tests.iter() { let res = HttpRequestPreamble::consensus_deserialize(&mut data.as_bytes()); test_debug!("Expect '{}'", errstr); - let expected_errstr = format!("{:?}", &res); - assert!(res.is_err(), expected_errstr); + assert!(res.is_err(), "{:?}", &res); assert!( - res.unwrap_err().to_string().find(errstr).is_some(), - expected_errstr + res.as_ref().unwrap_err().to_string().find(errstr).is_some(), + "{:?}", + &res ); } } @@ -4946,12 +4946,16 @@ mod test { for (data, errstr) in tests.iter() { let sres = StacksHttpPreamble::consensus_deserialize(&mut data.as_bytes()); - let expected_serrstr = format!("{:?}", &sres); test_debug!("Expect '{}'", errstr); - assert!(sres.is_err(), expected_serrstr); + assert!(sres.is_err(), "{:?}", &sres); assert!( - sres.unwrap_err().to_string().find(errstr).is_some(), - expected_serrstr + sres.as_ref() + .unwrap_err() + .to_string() + .find(errstr) + .is_some(), + "{:?}", + &sres ); } } @@ -5059,11 +5063,11 @@ mod test { for (data, response) in tests.iter() { test_debug!("Try parsing:\n{}\n", data); let res = HttpResponsePreamble::consensus_deserialize(&mut data.as_bytes()); - assert!(res.is_ok(), format!("{:?}", &res)); + assert!(res.is_ok(), "{:?}", &res); assert_eq!(res.unwrap(), *response); let sres = StacksHttpPreamble::consensus_deserialize(&mut data.as_bytes()); - assert!(sres.is_ok(), format!("{:?}", &sres)); + assert!(sres.is_ok(), "{:?}", &sres); assert_eq!( sres.unwrap(), StacksHttpPreamble::Response((*response).clone()) @@ -5087,11 +5091,11 @@ mod test { for (data, response) in tests.iter() { test_debug!("Try parsing:\n{}\n", data); let res = HttpResponsePreamble::consensus_deserialize(&mut data.as_bytes()); - assert!(res.is_ok(), format!("{:?}", &res)); + assert!(res.is_ok(), "{:?}", &res); assert_eq!(res.unwrap(), *response); let sres = StacksHttpPreamble::consensus_deserialize(&mut data.as_bytes()); - assert!(sres.is_ok(), format!("{:?}", &sres)); + assert!(sres.is_ok(), "{:?}", &sres); assert_eq!( sres.unwrap(), StacksHttpPreamble::Response((*response).clone()) @@ -5181,7 +5185,7 @@ mod test { for (data, errstr) in tests.iter() { let res = HttpResponsePreamble::consensus_deserialize(&mut data.as_bytes()); test_debug!("Expect '{}', got: {:?}", errstr, &res); - assert!(res.is_err(), format!("{:?}", &res)); + assert!(res.is_err(), "{:?}", &res); assert!(res.unwrap_err().to_string().find(errstr).is_some()); } } @@ -5211,12 +5215,16 @@ mod test { for (data, errstr) in tests.iter() { let sres = StacksHttpPreamble::consensus_deserialize(&mut data.as_bytes()); - let expected_serrstr = format!("{:?}", &sres); test_debug!("Expect '{}', got: {:?}", errstr, &sres); - assert!(sres.is_err(), expected_serrstr); + assert!(sres.is_err(), "{:?}", &sres); assert!( - sres.unwrap_err().to_string().find(errstr).is_some(), - expected_serrstr + sres.as_ref() + .unwrap_err() + .to_string() + .find(errstr) + .is_some(), + "{:?}", + &sres ); } } @@ -5462,15 +5470,16 @@ mod test { let mut http = StacksHttp::new("127.0.0.1:20443".parse().unwrap()); let (preamble, offset) = http.read_preamble(bad_content_length.as_bytes()).unwrap(); let e = http.read_payload(&preamble, &bad_content_length.as_bytes()[offset..]); - let estr = format!("{:?}", &e); - assert!(e.is_err(), estr); + assert!(e.is_err(), "{:?}", &e); assert!( - e.unwrap_err() + e.as_ref() + .unwrap_err() .to_string() .find("-length body for") .is_some(), - estr + "{:?}", + &e ); } @@ -5977,6 +5986,7 @@ mod test { assert!(e.is_err()); assert!( e.unwrap_err().to_string().find(expected_error).is_some(), + "{}", errstr ); } @@ -6351,17 +6361,15 @@ mod test { for live_header in live_headers { let res = HttpRequestPreamble::consensus_deserialize(&mut live_header.as_bytes()); - assert!( - res.is_ok(), - format!("headers: {}\nerror: {:?}", live_header, &res) - ); + assert!(res.is_ok(), "headers: {}\nerror: {:?}", live_header, &res); } for bad_live_header in bad_live_headers { let res = HttpRequestPreamble::consensus_deserialize(&mut bad_live_header.as_bytes()); assert!( res.is_err(), - format!("headers: {}\nshould not have parsed", bad_live_header) + "headers: {}\nshould not have parsed", + bad_live_header ); } } diff --git a/src/net/inv.rs b/src/net/inv.rs index 0c09654d5..891c6d21f 100644 --- a/src/net/inv.rs +++ b/src/net/inv.rs @@ -430,7 +430,7 @@ impl PeerBlocksInv { pub fn num_blocks(&self) -> u64 { let mut total = 0; for i in 0..self.num_sortitions { - if self.has_ith_block(i) { + if self.has_ith_block(i + self.first_block_height) { total += 1; } } @@ -441,7 +441,7 @@ impl PeerBlocksInv { pub fn num_microblock_streams(&self) -> u64 { let mut total = 0; for i in 0..self.num_sortitions { - if self.has_ith_microblock_stream(i) { + if self.has_ith_microblock_stream(i + self.first_block_height) { total += 1; } } @@ -552,14 +552,20 @@ pub struct NeighborBlockStats { pub done: bool, /// Did we learn anything new? pub learned_data: bool, + /// What height do we learn at? + pub learned_data_height: u64, /// How many times have we hit up this neighbor? pub scans: u64, - /// Do we need a full rescan? - pub need_full_rescan: bool, + /// Is this an always-allowed peer? + pub is_bootstrap_peer: bool, } impl NeighborBlockStats { - pub fn new(nk: NeighborKey, first_block_height: u64) -> NeighborBlockStats { + pub fn new( + nk: NeighborKey, + first_block_height: u64, + is_bootstrap_peer: bool, + ) -> NeighborBlockStats { NeighborBlockStats { nk: nk, inv: PeerBlocksInv::empty(first_block_height), @@ -576,8 +582,9 @@ impl NeighborBlockStats { last_rescan_timestamp: 0, done: false, learned_data: false, + learned_data_height: u64::MAX, scans: 0, - need_full_rescan: false, + is_bootstrap_peer: is_bootstrap_peer, } } @@ -590,7 +597,6 @@ impl NeighborBlockStats { self.request = None; self.pox_inv = None; self.blocks_inv = None; - self.need_full_rescan = false; self.state = InvWorkState::GetPoxInvBegin; debug!( @@ -604,11 +610,10 @@ impl NeighborBlockStats { self.request = None; self.pox_inv = None; self.blocks_inv = None; - self.need_full_rescan = false; self.state = InvWorkState::GetBlocksInvBegin; debug!( - "Reset {:?} block scan height to {}", + "Reset {:?} block scan height to reward cycle {}", &self.nk, self.block_reward_cycle ); } @@ -622,7 +627,7 @@ impl NeighborBlockStats { preamble_burn_stable_block_height: u64, preamble_burn_block_hash: &BurnchainHeaderHash, preamble_burn_stable_block_hash: &BurnchainHeaderHash, - always_allowed: bool, + is_bootstrap_peer: bool, ) -> NodeStatus { let mut diverged = false; let mut unstable = false; @@ -663,7 +668,7 @@ impl NeighborBlockStats { } else { // if this peer is always allowed, then this isn't a "broken" condition -- it's // a diverged condition. we trust that it has the correct PoX view. - if always_allowed { + if is_bootstrap_peer { debug!("Remote always-allowed neighbor {:?} NACKed us because it does not recognize our consensus hash. Treating as Diverged.", _nk); diverged = true; } else { @@ -701,7 +706,7 @@ impl NeighborBlockStats { chain_view: &BurnchainView, preamble: &Preamble, nack_data: NackData, - always_allowed: bool, + is_bootstrap_peer: bool, ) { let preamble_burn_block_height = preamble.burn_block_height; let preamble_burn_stable_block_height = preamble.burn_stable_block_height; @@ -716,7 +721,7 @@ impl NeighborBlockStats { preamble_burn_stable_block_height, preamble_burn_block_hash, preamble_burn_stable_block_hash, - always_allowed, + is_bootstrap_peer, ); } @@ -727,6 +732,7 @@ impl NeighborBlockStats { self.request = Some(request); self.pox_inv = None; self.target_pox_reward_cycle = target_pox_reward_cycle; + self.learned_data = false; self.state = InvWorkState::GetPoxInvFinish; } @@ -741,7 +747,7 @@ impl NeighborBlockStats { let mut bit = target_pox_reward_cycle; while bit < (network.pox_id.len() as u64) - 1 && (bit - target_pox_reward_cycle) < poxinv_data.bitlen as u64 - && (bit - target_pox_reward_cycle) < u16::max_value() as u64 + && (bit - target_pox_reward_cycle) < u16::MAX as u64 { if network.pox_id.has_ith_anchor_block(bit as usize) && !poxinv_data.has_ith_reward_cycle((bit - target_pox_reward_cycle) as u16) @@ -765,7 +771,7 @@ impl NeighborBlockStats { let mut bit = target_pox_reward_cycle; while bit < (network.pox_id.len() as u64) - 1 && (bit - target_pox_reward_cycle) < poxinv_data.bitlen as u64 - && (bit - target_pox_reward_cycle) < u16::max_value() as u64 + && (bit - target_pox_reward_cycle) < u16::MAX as u64 { if !network.pox_id.has_ith_anchor_block(bit as usize) && poxinv_data.has_ith_reward_cycle((bit - target_pox_reward_cycle) as u16) @@ -808,7 +814,7 @@ impl NeighborBlockStats { } StacksMessageType::Nack(nack_data) => { debug!("Remote neighbor {:?} nack'ed our GetPoxInv at reward cycle {}: NACK code {}", &self.nk, self.target_pox_reward_cycle, nack_data.error_code); - let always_allowed = PeerDB::is_peer_always_allowed( + let is_bootstrap_peer = PeerDB::is_initial_peer( &network.peerdb.conn(), self.nk.network_id, &self.nk.addrbytes, @@ -819,7 +825,7 @@ impl NeighborBlockStats { &network.chain_view, &message.preamble, nack_data, - always_allowed, + is_bootstrap_peer, ); } _ => { @@ -908,7 +914,7 @@ impl NeighborBlockStats { } StacksMessageType::Nack(nack_data) => { debug!("Remote neighbor {:?} nack'ed our GetBlocksInv at reward cycle {}: NACK code {}", &self.nk, self.target_block_reward_cycle, nack_data.error_code); - let always_allowed = PeerDB::is_peer_always_allowed( + let is_bootstrap_peer = PeerDB::is_initial_peer( &network.peerdb.conn(), self.nk.network_id, &self.nk.addrbytes, @@ -919,7 +925,7 @@ impl NeighborBlockStats { &network.chain_view, &message.preamble, nack_data, - always_allowed, + is_bootstrap_peer, ); } _ => { @@ -952,6 +958,7 @@ impl NeighborBlockStats { self.request = Some(next_request); Ok(false) } else { + debug!("Finished inventory scan for {:?}", &self.nk); self.state = InvWorkState::Done; self.scans += 1; Ok(true) @@ -985,7 +992,7 @@ pub struct InvState { last_rescanned_at: u64, /// Should we do a full rescan? hint_do_full_rescan: bool, - /// last time a full rescan was completed + /// last time a full rescan was completed, in seconds last_full_rescanned_at: u64, /// How many passes -- short and full -- have we done? @@ -1008,9 +1015,9 @@ impl InvState { sync_interval: sync_interval, hint_learned_data: false, - hint_learned_data_height: 0, + hint_learned_data_height: u64::MAX, hint_do_rescan: true, - hint_do_full_rescan: true, + hint_do_full_rescan: false, last_rescanned_at: 0, last_full_rescanned_at: 0, @@ -1021,23 +1028,36 @@ impl InvState { } } - pub fn reset_sync_peers(&mut self, peers: HashSet, max_neighbors: usize) -> () { - for (_, stats) in self.block_stats.iter_mut() { + fn reset_sync_peers( + &mut self, + peers: HashSet, + bootstrap_peers: &HashSet, + max_neighbors: usize, + ) -> () { + for (nk, stats) in self.block_stats.iter_mut() { if stats.status != NodeStatus::Online { stats.status = NodeStatus::Online; } stats.done = false; stats.learned_data = false; + stats.learned_data_height = u64::MAX; + + stats.is_bootstrap_peer = bootstrap_peers.contains(nk); } let mut added = 0; for peer in peers.iter() { if let Some(stats) = self.block_stats.get_mut(peer) { stats.reset_pox_scan(0); + stats.is_bootstrap_peer = bootstrap_peers.contains(&peer); } else if self.block_stats.len() < max_neighbors { self.block_stats.insert( peer.clone(), - NeighborBlockStats::new(peer.clone(), self.first_block_height), + NeighborBlockStats::new( + peer.clone(), + self.first_block_height, + bootstrap_peers.contains(&peer), + ), ); added += 1; } @@ -1153,10 +1173,10 @@ impl InvState { } #[cfg(test)] - pub fn add_peer(&mut self, nk: NeighborKey) -> () { + pub fn add_peer(&mut self, nk: NeighborKey, is_bootstrap_peer: bool) -> () { self.block_stats.insert( nk.clone(), - NeighborBlockStats::new(nk, self.first_block_height), + NeighborBlockStats::new(nk, self.first_block_height, is_bootstrap_peer), ); } @@ -1164,6 +1184,18 @@ impl InvState { self.block_stats.remove(&nk); } + /// Is there any downloader-actionable data available? + pub fn has_inv_data_for_downloader(&self, ibd: bool) -> bool { + let mut ret = false; + for (nk, stats) in self.block_stats.iter() { + if stats.scans > 0 && (!ibd || stats.is_bootstrap_peer) { + debug!("Have inv data for downloader from {:?} (ibd={}, is_bootstrap_peer={}, scans={}))", nk, ibd, stats.is_bootstrap_peer, stats.scans); + ret = true; + } + } + ret + } + /// Set a block or confirmed microblock stream as available, given the burn header hash and consensus hash. /// Used when processing a BlocksAvailable or MicroblocksAvailable message. /// Drops if the message refers to a block height @@ -1489,7 +1521,7 @@ impl PeerNetwork { convo: &ConversationP2P, ) -> Result { if target_block_reward_cycle >= (self.pox_id.num_inventory_reward_cycles() as u64) { - test_debug!( + debug!( "{:?}: target reward cycle {} >= our max reward cycle {}", &self.local_peer, target_block_reward_cycle, @@ -1501,7 +1533,7 @@ impl PeerNetwork { // does the peer agree with our PoX view up to this reward cycle? match stats.inv.pox_inv_cmp(&self.pox_id) { Some((disagreed, _, _)) => { - if disagreed <= target_block_reward_cycle { + if disagreed < target_block_reward_cycle { // can't proceed debug!("{:?}: remote neighbor {:?} disagrees with our PoX inventory at reward cycle {} (asked for {})", &self.local_peer, nk, disagreed, target_block_reward_cycle); return Ok(0); @@ -1622,6 +1654,7 @@ impl PeerNetwork { )? { 0 => { // cannot ask this peer for any blocks in this reward cycle + debug!("{:?}: no blocks available from {}", &self.local_peer, nk); return Ok(None); } x => x, @@ -1734,11 +1767,37 @@ impl PeerNetwork { } /// Determine at which reward cycle to begin scanning inventories - fn get_block_scan_start(&self, highest_remote_reward_cycle: u64, full_rescan: bool) -> u64 { + fn get_block_scan_start( + &self, + sortdb: &SortitionDB, + highest_remote_reward_cycle: u64, + full_rescan: bool, + ) -> u64 { + let (consensus_hash, _) = SortitionDB::get_canonical_stacks_chain_tip_hash(sortdb.conn()) + .unwrap_or((ConsensusHash::empty(), BlockHeaderHash([0u8; 32]))); + + let stacks_tip_burn_block_height = + match SortitionDB::get_block_snapshot_consensus(sortdb.conn(), &consensus_hash) { + Err(_) => self.burnchain.first_block_height, + Ok(x) => x + .map(|sn| sn.block_height) + .unwrap_or(self.burnchain.first_block_height), + }; + + let stacks_tip_rc = self + .burnchain + .block_height_to_reward_cycle(stacks_tip_burn_block_height) + .unwrap_or(0); + + let start_reward_cycle = cmp::min( + stacks_tip_rc, + highest_remote_reward_cycle.saturating_sub(self.connection_opts.inv_reward_cycles), + ); + if full_rescan { 0 } else { - highest_remote_reward_cycle.saturating_sub(self.connection_opts.inv_reward_cycles) + start_reward_cycle } } @@ -1757,9 +1816,10 @@ impl PeerNetwork { Some(x) => x, None => { // proceed to block scan - let scan_start = self.get_block_scan_start(stats.inv.get_pox_height(), full_rescan); - debug!("{:?}: cannot make any more GetPoxInv requests for {:?}; proceeding to block inventory scan at reward cycle {}", &self.local_peer, nk, scan_start); - stats.reset_block_scan(scan_start); + let scan_start_rc = + self.get_block_scan_start(sortdb, stats.inv.get_pox_height(), full_rescan); + debug!("{:?}: cannot make any more GetPoxInv requests for {:?}; proceeding to block inventory scan at reward cycle {}", &self.local_peer, nk, scan_start_rc); + stats.reset_block_scan(scan_start_rc); return Ok(()); } }; @@ -1781,6 +1841,7 @@ impl PeerNetwork { /// Return true if done. fn inv_getpoxinv_try_finish( &mut self, + sortdb: &SortitionDB, nk: &NeighborKey, stats: &mut NeighborBlockStats, full_rescan: bool, @@ -1813,15 +1874,24 @@ impl PeerNetwork { // proceed with block scan. // If we're in IBD, then this is an always-allowed peer and we should // react to divergences by deepening our rescan. - let scan_start = - self.get_block_scan_start(stats.inv.get_pox_height(), ibd || full_rescan); + let scan_start_rc = self.get_block_scan_start( + sortdb, + stats + .target_pox_reward_cycle + .saturating_sub(INV_REWARD_CYCLES), + full_rescan, + ); debug!( "{:?}: proceeding to block inventory scan for {:?} (diverged) at reward cycle {} (ibd={}, full={})", - &self.local_peer, nk, scan_start, ibd, full_rescan + &self.local_peer, nk, scan_start_rc, ibd, full_rescan ); - stats.reset_block_scan(scan_start); + + stats.learned_data = true; + stats.learned_data_height = + self.burnchain.reward_cycle_to_block_height(scan_start_rc); + stats.reset_block_scan(scan_start_rc); } - // done + // done with pox inv sync return Ok(true); } @@ -1853,6 +1923,11 @@ impl PeerNetwork { ); stats.learned_data = true; + stats.learned_data_height = cmp::min( + stats.learned_data_height, + self.burnchain + .reward_cycle_to_block_height(*lowest_learned_reward_cycle), + ); } else { debug!( "{:?}: have {} total reward cycles for {:?}", @@ -1906,7 +1981,8 @@ impl PeerNetwork { } // proceed to block scan. - let scan_start = self.get_block_scan_start(stats.inv.get_pox_height(), full_rescan); + let scan_start = + self.get_block_scan_start(sortdb, stats.inv.get_pox_height(), full_rescan); debug!( "{:?}: proceeding to block inventory scan for {:?} at reward cycle {}", &self.local_peer, nk, scan_start @@ -1933,6 +2009,10 @@ impl PeerNetwork { match self.make_next_getblocksinv(sortdb, nk, stats)? { Some(x) => x, None => { + debug!( + "{:?}: finished inv sync with {}: could not make new GetBlocksInv", + &self.local_peer, &nk + ); stats.done = true; return Ok(()); } @@ -1953,7 +2033,7 @@ impl PeerNetwork { } /// Finish receiving the next batch of block inventories. - /// Indicate whether or not we're done + /// Indicate whether or not we're done (true=continue, false=stop) fn inv_getblocksinv_try_finish( &mut self, nk: &NeighborKey, @@ -1971,10 +2051,24 @@ impl PeerNetwork { if ibd && stats.status == NodeStatus::Diverged { // we were in the initial block download, and we diverged. // we should try and deepen the scan. - debug!("{:?}: In initial block download and diverged from always-allowed peer -- schedule a full inventory sync for next time.", &self.local_peer); - stats.need_full_rescan = true; + stats.block_reward_cycle = + stats.block_reward_cycle.saturating_sub(INV_REWARD_CYCLES); + let learned_data_height = self + .burnchain + .reward_cycle_to_block_height(stats.block_reward_cycle); + + debug!("{:?}: In initial block download and diverged from always-allowed peer -- schedule an ibd inventory sync for next time, starting at reward cycle {} ({}).", &self.local_peer, stats.block_reward_cycle, learned_data_height); + stats.reset_block_scan(stats.block_reward_cycle); + + stats.learned_data = true; + stats.learned_data_height = learned_data_height; } - return Ok(true); + debug!( + "{:?}: Node {} is diverged; done with inv sync", + &self.local_peer, nk + ); + stats.done = true; + return Ok(false); } // if we get a blocksinv, then it means the remote peer still agrees with us on PoX state @@ -2004,6 +2098,7 @@ impl PeerNetwork { if new_blocks > 0 || new_microblocks > 0 { stats.learned_data = true; + stats.learned_data_height = cmp::min(target_block_height, stats.learned_data_height); } assert_eq!(stats.state, InvWorkState::Done); @@ -2017,6 +2112,10 @@ impl PeerNetwork { } else { // we're done scanning! proceed to rescan stats.last_rescan_timestamp = get_epoch_time_secs(); + debug!( + "{:?}: finished inv sync with {}: reached remote chain tip", + &self.local_peer, &nk + ); stats.done = true; } @@ -2045,7 +2144,7 @@ impl PeerNetwork { .inv_getpoxinv_begin(sortdb, nk, stats, request_timeout, full_rescan) .and_then(|_| Ok(true))?, InvWorkState::GetPoxInvFinish => { - self.inv_getpoxinv_try_finish(nk, stats, full_rescan, ibd)? + self.inv_getpoxinv_try_finish(sortdb, nk, stats, full_rescan, ibd)? } InvWorkState::GetBlocksInvBegin => self .inv_getblocksinv_begin(sortdb, nk, stats, request_timeout) @@ -2066,7 +2165,8 @@ impl PeerNetwork { } /// Refresh our cached PoX bitvector, and invalidate any PoX state if we have since learned - /// about a new reward cycle + /// about a new reward cycle. + /// Call right after PeerNetwork::refresh_burnchain_view() pub fn refresh_sortition_view(&mut self, sortdb: &SortitionDB) -> Result<(), net_error> { if self.inv_state.is_none() { self.init_inv_sync(sortdb); @@ -2077,45 +2177,57 @@ impl PeerNetwork { .as_mut() .expect("Unreachable: inv state not initialized"); - let (new_tip_sort_id, new_pox_id) = { - let ic = sortdb.index_conn(); - let tip_sort_id = SortitionDB::get_canonical_sortition_tip(sortdb.conn())?; - let sortdb_reader = SortitionHandleConn::open_reader(&ic, &tip_sort_id)?; - (tip_sort_id, sortdb_reader.get_pox_id()?) + let (new_tip_sort_id, new_pox_id, reloaded) = { + if self.burnchain_tip.sortition_id != self.tip_sort_id { + // reloaded burnchain tip disagrees with our last-considered sortition tip + let ic = sortdb.index_conn(); + let sortdb_reader = + SortitionHandleConn::open_reader(&ic, &self.burnchain_tip.sortition_id)?; + ( + self.burnchain_tip.sortition_id.clone(), + sortdb_reader.get_pox_id()?, + true, + ) + } else { + (self.tip_sort_id.clone(), self.pox_id.clone(), false) + } }; - // find the lowest reward cycle whose bit has since changed from a 0 to a 1. - let num_reward_cycles = cmp::min( - new_pox_id.num_inventory_reward_cycles(), - self.pox_id.num_inventory_reward_cycles(), - ); - for i in 0..num_reward_cycles { - if !self.pox_id.has_ith_anchor_block(i) && new_pox_id.has_ith_anchor_block(i) { - // we learned of a new anchor block intermittently. Invalidate all cached state at and after this reward cycle. - inv_state.invalidate_block_inventories(&self.burnchain, i as u64); + if reloaded { + // find the lowest reward cycle whose bit has since changed from a 0 to a 1. + let num_reward_cycles = cmp::min( + new_pox_id.num_inventory_reward_cycles(), + self.pox_id.num_inventory_reward_cycles(), + ); + for i in 0..num_reward_cycles { + if !self.pox_id.has_ith_anchor_block(i) && new_pox_id.has_ith_anchor_block(i) { + // we learned of a new anchor block intermittently. Invalidate all cached state at and after this reward cycle. + inv_state.invalidate_block_inventories(&self.burnchain, i as u64); - // also clear block header cache (TODO: this is pessimistic -- only invalidated - // entries need to be cleared) - debug!( - "{:?}: invalidating block header cache in response to PoX bit flip", - &self.local_peer - ); - self.header_cache.clear(); - break; + // also clear block header cache (TODO: this is pessimistic -- only invalidated + // entries need to be cleared) + debug!( + "{:?}: invalidating block header cache in response to PoX bit flip", + &self.local_peer + ); + self.header_cache.clear(); + break; + } } - } - // if the PoX bitvector shrinks, then invalidate block inventories that are no longer represented - if new_pox_id.num_inventory_reward_cycles() < self.pox_id.num_inventory_reward_cycles() { - inv_state.invalidate_block_inventories(&self.burnchain, self.pox_id.len() as u64); - } + // if the PoX bitvector shrinks, then invalidate block inventories that are no longer represented + if new_pox_id.num_inventory_reward_cycles() < self.pox_id.num_inventory_reward_cycles() + { + inv_state.invalidate_block_inventories(&self.burnchain, new_pox_id.len() as u64); + } - self.tip_sort_id = new_tip_sort_id; - self.pox_id = new_pox_id; + self.tip_sort_id = new_tip_sort_id; + self.pox_id = new_pox_id; + } debug!( - "{:?}: PoX bit vector is {:?}", - &self.local_peer, &self.pox_id + "{:?}: PoX bit vector is {:?} (reloaded={})", + &self.local_peer, &self.pox_id, reloaded ); Ok(()) @@ -2136,10 +2248,18 @@ impl PeerNetwork { ); let mut all_done = true; - let mut do_full_rescan = false; let mut fully_synced_peers = HashSet::new(); + let mut ibd_diverged_height: Option = None; - if !inv_state.hint_do_rescan + let bootstrap_peers: HashSet<_> = + PeerDB::get_bootstrap_peers(&network.peerdb.conn(), network.local_peer.network_id) + .unwrap_or(vec![]) + .into_iter() + .map(|neighbor| neighbor.addr) + .collect(); + + if !ibd + && !inv_state.hint_do_rescan && !inv_state.hint_learned_data && inv_state.last_rescanned_at + inv_state.sync_interval >= get_epoch_time_secs() { @@ -2199,32 +2319,31 @@ impl PeerNetwork { } }; - if stats.need_full_rescan { - debug!( - "{:?}: remote neighbor {:?} requests full rescan", - &network.local_peer, &nk - ); - } - all_done = all_done && stats.done; - do_full_rescan = do_full_rescan || stats.need_full_rescan; - if stats.learned_data { - // update hints - debug!( - "{:?}: learned something new from {:?}", - &network.local_peer, &nk - ); - inv_state.hint_learned_data = - inv_state.hint_learned_data || stats.learned_data; + // if this node diverged from us, and we're in ibd, and this is an + // always-allowed peer, then start scanning here (or lower) + if ibd + && bootstrap_peers.contains(&nk) + && stats.status == NodeStatus::Diverged + { + inv_state.last_change_at = get_epoch_time_secs(); + inv_state.hint_learned_data = true; + inv_state.hint_learned_data_height = cmp::min( + inv_state.hint_learned_data_height, + stats.learned_data_height, + ); - inv_state.hint_learned_data_height = cmp::min( - inv_state.hint_learned_data_height, - network.burnchain.reward_cycle_to_block_height( - stats.target_block_reward_cycle.saturating_sub(1), - ), - ); - inv_state.last_change_at = get_epoch_time_secs(); + // this will be where sortitions must begin + ibd_diverged_height = Some(inv_state.hint_learned_data_height); + + debug!("{:?}: remote neighbor {:?} diverged (at {}), so try re-scanning at height {}", &network.local_peer, &nk, stats.learned_data_height, inv_state.hint_learned_data_height); + } else { + debug!( + "{:?}: learned something new from {:?} at height {}", + &network.local_peer, &nk, stats.learned_data_height + ); + } } if stats.done @@ -2249,34 +2368,36 @@ impl PeerNetwork { let broken_peers = inv_state.get_broken_peers(); let dead_peers = inv_state.get_dead_peers(); - // hint to downloader as to where to begin scanning - inv_state.block_sortition_start = network - .burnchain - .reward_cycle_to_block_height(network.get_block_scan_start( - network.pox_id.num_inventory_reward_cycles() as u64, - inv_state.hint_do_full_rescan, + // hint to downloader as to where to begin scanning next time + inv_state.block_sortition_start = ibd_diverged_height + .unwrap_or(network.burnchain.reward_cycle_to_block_height( + network.get_block_scan_start( + sortdb, + network.pox_id.num_inventory_reward_cycles() as u64, + inv_state.hint_do_full_rescan, + ), )) .saturating_sub(sortdb.first_block_height); + debug!( + "{:?}: inventory sync finished; sortition start is {} (do rescan? {})", + &network.local_peer, + inv_state.block_sortition_start, + inv_state.hint_do_full_rescan + ); + let was_full = inv_state.hint_do_full_rescan; if was_full { - let synced_with_always_allowed = if ibd { - // make sure we've sync'ed with at least one always-allowed peer before + let synced_with_bootstrap_peer = if ibd { + // make sure we've sync'ed with at least one bootstrap peer before // clearing the hint_do_full_rescan flag - let always_allowed: HashSet<_> = PeerDB::get_always_allowed_peers( - &network.peerdb.conn(), - network.local_peer.network_id, - ) - .unwrap_or(vec![]) - .into_iter() - .map(|neighbor| neighbor.addr) - .collect(); - - let synced = !always_allowed.is_disjoint(&fully_synced_peers); + let synced = bootstrap_peers.len() == 0 + || !bootstrap_peers.is_disjoint(&fully_synced_peers); if synced { debug!( - "{:?}: finished full inventory rescan in initial block download", - &network.local_peer + "{:?}: finished full inventory rescan in initial block download with {} always-allowed peer(s)", + &network.local_peer, + bootstrap_peers.len() ); } else { debug!("{:?}: did NOT finish full inventory rescan in initial block download", &network.local_peer); @@ -2291,7 +2412,7 @@ impl PeerNetwork { true }; - if synced_with_always_allowed { + if synced_with_bootstrap_peer { inv_state.last_full_rescanned_at = get_epoch_time_secs(); inv_state.hint_do_full_rescan = false; inv_state.num_full_inv_syncs += 1; @@ -2332,12 +2453,10 @@ impl PeerNetwork { ); } - if do_full_rescan - || inv_state.last_full_rescanned_at - + network.connection_opts.full_inv_sync_interval - < get_epoch_time_secs() + if inv_state.last_full_rescanned_at + network.connection_opts.full_inv_sync_interval + < get_epoch_time_secs() { - if !inv_state.hint_do_full_rescan { + if !ibd && !inv_state.hint_do_full_rescan { debug!("{:?}: schedule full inventory sync", &network.local_peer); inv_state.hint_do_full_rescan = true; } @@ -2359,13 +2478,7 @@ impl PeerNetwork { let mut good_sync_peers_set = HashSet::new(); let mut random_sync_peers_list = vec![]; for nk in random_neighbor_list.into_iter() { - if PeerDB::is_peer_always_allowed( - &network.peerdb.conn(), - nk.network_id, - &nk.addrbytes, - nk.port, - ) - .unwrap_or(false) + if bootstrap_peers.contains(&nk) && good_sync_peers_set.len() < (network.connection_opts.num_neighbors as usize) { @@ -2394,6 +2507,7 @@ impl PeerNetwork { inv_state.reset_sync_peers( good_sync_peers_set, + &bootstrap_peers, network.connection_opts.num_neighbors as usize, ); @@ -2447,7 +2561,10 @@ impl PeerNetwork { pub fn hint_sync_invs(&mut self, target_height: u64) { match self.inv_state { Some(ref mut inv_state) => { - debug!("Awaken inv sync to re-scan peer block inventories"); + debug!( + "Awaken inv sync to re-scan peer block inventories at height {}", + target_height + ); inv_state.hint_learned_data = true; inv_state.hint_do_rescan = true; inv_state.hint_learned_data_height = target_height; @@ -3004,8 +3121,7 @@ mod test { #[test] fn test_inv_merge_pox_inv() { let mut burnchain = Burnchain::regtest("unused"); - burnchain.pox_constants = - PoxConstants::new(5, 3, 3, 25, 5, u64::max_value(), u64::max_value()); + burnchain.pox_constants = PoxConstants::new(5, 3, 3, 25, 5, u64::MAX, u64::MAX); let mut peer_inv = PeerBlocksInv::new(vec![0x01], vec![0x01], vec![0x01], 1, 1, 0); for i in 0..32 { @@ -3023,8 +3139,7 @@ mod test { #[test] fn test_inv_truncate_pox_inv() { let mut burnchain = Burnchain::regtest("unused"); - burnchain.pox_constants = - PoxConstants::new(5, 3, 3, 25, 5, u64::max_value(), u64::max_value()); + burnchain.pox_constants = PoxConstants::new(5, 3, 3, 25, 5, u64::MAX, u64::MAX); let mut peer_inv = PeerBlocksInv::new(vec![0x01], vec![0x01], vec![0x01], 1, 1, 0); for i in 0..5 { @@ -3120,7 +3235,7 @@ mod test { peer_1.network.init_inv_sync(&sortdb); match peer_1.network.inv_state { Some(ref mut inv) => { - inv.add_peer(nk.clone()); + inv.add_peer(nk.clone(), true); } None => { panic!("No inv state"); @@ -3919,7 +4034,9 @@ mod test { for i in 0..num_blocks { assert!( peer_2_inv.has_ith_block(i + first_stacks_block_height), - format!("Missing block {} (+ {})", i, first_stacks_block_height) + "Missing block {} (+ {})", + i, + first_stacks_block_height ); } @@ -3927,7 +4044,9 @@ mod test { for i in 1..(num_blocks - 1) { assert!( peer_2_inv.has_ith_microblock_stream(i + first_stacks_block_height), - format!("Missing microblock {} (+ {})", i, first_stacks_block_height) + "Missing microblock {} (+ {})", + i, + first_stacks_block_height ); } @@ -3949,7 +4068,9 @@ mod test { for i in 0..num_blocks { assert!( peer_1_inv.has_ith_block(i + first_stacks_block_height), - format!("Missing block {} (+ {})", i, first_stacks_block_height) + "Missing block {} (+ {})", + i, + first_stacks_block_height ); } }) @@ -4128,7 +4249,9 @@ mod test { for i in 0..num_blocks { assert!( peer_2_inv.has_ith_block(i + first_stacks_block_height), - format!("Missing block {} (+ {})", i, first_stacks_block_height) + "Missing block {} (+ {})", + i, + first_stacks_block_height ); } @@ -4136,7 +4259,9 @@ mod test { for i in 1..(num_blocks - 1) { assert!( peer_2_inv.has_ith_microblock_stream(i + first_stacks_block_height), - format!("Missing microblock {} (+ {})", i, first_stacks_block_height) + "Missing microblock {} (+ {})", + i, + first_stacks_block_height ); } @@ -4158,7 +4283,9 @@ mod test { for i in 0..num_blocks { assert!( peer_1_inv.has_ith_block(i + first_stacks_block_height), - format!("Missing block {} (+ {})", i, first_stacks_block_height) + "Missing block {} (+ {})", + i, + first_stacks_block_height ); } }) diff --git a/src/net/mod.rs b/src/net/mod.rs index d44ede099..a68cf7d3a 100644 --- a/src/net/mod.rs +++ b/src/net/mod.rs @@ -207,6 +207,8 @@ pub enum Error { ConnectionCycle, /// Requested data not found NotFoundError, + /// Transient error (akin to EAGAIN) + Transient(String), } impl From for Error { @@ -303,6 +305,7 @@ impl fmt::Display for Error { Error::StaleView => write!(f, "State view is stale"), Error::ConnectionCycle => write!(f, "Tried to connect to myself"), Error::NotFoundError => write!(f, "Requested data not found"), + Error::Transient(ref s) => write!(f, "Transient network error: {}", s), } } } @@ -361,6 +364,7 @@ impl error::Error for Error { Error::StaleView => None, Error::ConnectionCycle => None, Error::NotFoundError => None, + Error::Transient(ref _s) => None, } } } @@ -969,7 +973,7 @@ pub struct RPCPeerInfoData { pub parent_network_id: u32, pub stacks_tip_height: u64, pub stacks_tip: BlockHeaderHash, - pub stacks_tip_consensus_hash: String, + pub stacks_tip_consensus_hash: ConsensusHash, pub genesis_chainstate_hash: Sha256Sum, pub unanchored_tip: StacksBlockId, pub unanchored_seq: u16, @@ -1492,7 +1496,7 @@ pub trait ProtocolFamily { pub struct StacksP2P {} // an array in our protocol can't exceed this many items -pub const ARRAY_MAX_LEN: u32 = u32::max_value(); +pub const ARRAY_MAX_LEN: u32 = u32::MAX; // maximum number of neighbors in a NeighborsData pub const MAX_NEIGHBORS_DATA_LEN: u32 = 128; @@ -2109,8 +2113,7 @@ pub mod test { ) .unwrap(), ); - burnchain.pox_constants = - PoxConstants::new(5, 3, 3, 25, 5, u64::max_value(), u64::max_value()); + burnchain.pox_constants = PoxConstants::new(5, 3, 3, 25, 5, u64::MAX, u64::MAX); let mut spending_account = TestMinerFactory::new().next_miner( &burnchain, diff --git a/src/net/p2p.rs b/src/net/p2p.rs index 252b34269..274ac26bf 100644 --- a/src/net/p2p.rs +++ b/src/net/p2p.rs @@ -39,6 +39,7 @@ use burnchains::Burnchain; use burnchains::BurnchainView; use burnchains::PublicKey; use chainstate::burn::db::sortdb::{BlockHeaderCache, SortitionDB}; +use chainstate::burn::BlockSnapshot; use chainstate::stacks::db::StacksChainState; use chainstate::stacks::{MAX_BLOCK_LEN, MAX_TRANSACTION_LEN}; use monitoring::{update_inbound_neighbors, update_outbound_neighbors}; @@ -194,7 +195,8 @@ pub struct PeerNetwork { pub local_peer: LocalPeer, pub peer_version: u32, pub chain_view: BurnchainView, - pub last_burnchain_tip: BurnchainHeaderHash, + pub burnchain_tip: BlockSnapshot, + pub chain_view_stable_consensus_hash: ConsensusHash, pub peerdb: PeerDB, pub atlasdb: AtlasDB, @@ -262,7 +264,7 @@ pub struct PeerNetwork { pub prune_inbound_counts: HashMap, // http endpoint, used for driving HTTP conversations (some of which we initiate) - pub http: HttpPeer, + pub http: Option, // our own neighbor address that we bind on bind_nk: NeighborKey, @@ -311,13 +313,7 @@ impl PeerNetwork { chain_view: BurnchainView, connection_opts: ConnectionOptions, ) -> PeerNetwork { - let http = HttpPeer::new( - local_peer.network_id, - burnchain.clone(), - chain_view.clone(), - connection_opts.clone(), - 0, - ); + let http = HttpPeer::new(connection_opts.clone(), 0); let pub_ip = connection_opts.public_ip_address.clone(); let pub_ip_learned = pub_ip.is_none(); local_peer.public_ip_address = pub_ip.clone(); @@ -329,11 +325,20 @@ impl PeerNetwork { debug!("{:?}: disable inbound neighbor walks", &local_peer); } + let first_block_height = burnchain.first_block_height; + let first_burn_header_hash = burnchain.first_block_hash.clone(); + let first_burn_header_ts = burnchain.first_block_timestamp; + let mut network = PeerNetwork { local_peer: local_peer, peer_version: peer_version, chain_view: chain_view, - last_burnchain_tip: BurnchainHeaderHash([0u8; 32]), + chain_view_stable_consensus_hash: ConsensusHash([0u8; 20]), + burnchain_tip: BlockSnapshot::initial( + first_block_height, + &first_burn_header_hash, + first_burn_header_ts as u64, + ), peerdb: peerdb, atlasdb: atlasdb, @@ -378,7 +383,7 @@ impl PeerNetwork { prune_outbound_counts: HashMap::new(), prune_inbound_counts: HashMap::new(), - http: http, + http: Some(http), bind_nk: NeighborKey { network_id: 0, peer_version: 0, @@ -407,10 +412,28 @@ impl PeerNetwork { fault_last_disconnect: 0, }; + network.init_block_downloader(); network.init_attachments_downloader(vec![]); + network } + /// Do something with the HTTP peer. + /// NOTE: the HTTP peer is *always* instantiated; it's just an Option<..> so its methods can + /// receive a ref to the PeerNetwork that contains it. + pub fn with_http(network: &mut PeerNetwork, to_do: F) -> R + where + F: FnOnce(&mut PeerNetwork, &mut HttpPeer) -> R, + { + let mut http = network + .http + .take() + .expect("BUG: HTTP peer is not instantiated"); + let res = to_do(network, &mut http); + network.http = Some(http); + res + } + /// start serving. pub fn bind(&mut self, my_addr: &SocketAddr, http_addr: &SocketAddr) -> Result<(), net_error> { let mut net = NetworkState::new(self.connection_opts.max_sockets)?; @@ -429,7 +452,9 @@ impl PeerNetwork { self.p2p_network_handle = p2p_handle; self.http_network_handle = http_handle; - self.http.set_server_handle(http_handle); + PeerNetwork::with_http(self, |_, ref mut http| { + http.set_server_handle(http_handle); + }); self.bind_nk = NeighborKey { network_id: self.local_peer.network_id, @@ -2375,6 +2400,7 @@ impl PeerNetwork { sortdb: &SortitionDB, chainstate: &mut StacksChainState, dns_client: &mut DNSClient, + ibd: bool, network_result: &mut NetworkResult, ) -> Result { if self.connection_opts.disable_block_download { @@ -2394,7 +2420,29 @@ impl PeerNetwork { mut microblocks, mut broken_http_peers, mut broken_p2p_peers, - ) = self.download_blocks(sortdb, chainstate, dns_client)?; + ) = match self.download_blocks(sortdb, chainstate, dns_client, ibd) { + Ok(x) => x, + Err(net_error::NotConnected) => { + // there was simply nothing to do + debug!( + "{:?}: no progress can be made on the block downloader -- not connected", + &self.local_peer + ); + return Ok(true); + } + Err(net_error::Transient(s)) => { + // not fatal, but just skip and try again + info!("Transient network error while downloading blocks: {}", &s); + return Ok(true); + } + Err(e) => { + warn!( + "{:?}: Failed to download blocks: {:?}", + &self.local_peer, &e + ); + return Err(e); + } + }; network_result.download_pox_id = old_pox_id; network_result.blocks.append(&mut blocks); @@ -2429,7 +2477,9 @@ impl PeerNetwork { "{:?}: De-register dead/broken HTTP connection {}", &network.local_peer, dead_event ); - network.http.deregister_http(network_state, dead_event); + PeerNetwork::with_http(network, |_, http| { + http.deregister_http(network_state, dead_event); + }); } Ok(()) }); @@ -2717,7 +2767,7 @@ impl PeerNetwork { let mut microblocks_to_broadcast = HashMap::new(); let start_block_height = self.burnchain.reward_cycle_to_block_height(reward_cycle); - let highest_snapshot = SortitionDB::get_canonical_burn_chain_tip(sortdb.conn())?; + let highest_snapshot = self.burnchain_tip.clone(); for nk in neighbor_keys.iter() { if total_blocks_to_broadcast >= self.connection_opts.max_block_push && total_microblocks_to_broadcast >= self.connection_opts.max_microblock_push @@ -3007,19 +3057,6 @@ impl PeerNetwork { self.work_state = PeerNetworkWorkState::Prune; } - // pass along hints - if let Some(ref inv_sync) = self.inv_state { - if inv_sync.hint_learned_data { - // tell the downloader to wake up - if let Some(ref mut downloader) = self.block_downloader { - downloader.hint_download_rescan(cmp::min( - self.chain_view.burn_block_height, - inv_sync.hint_learned_data_height, - )); - } - } - } - if !inv_throttled { // only count an inv_sync as passing if there's an always-allowed node // in our inv state @@ -3069,28 +3106,63 @@ impl PeerNetwork { // hint to the downloader to start scanning at the sortition // height we just synchronized - let start_download_sortition = - if let Some(ref inv_state) = self.inv_state { - debug!( - "{:?}: Begin downloader synchronization at sortition height {}", - &self.local_peer, inv_state.block_sortition_start + let start_download_sortition = if let Some(ref inv_state) = + self.inv_state + { + let (consensus_hash, _) = + SortitionDB::get_canonical_stacks_chain_tip_hash( + sortdb.conn(), + )?; + + let stacks_tip_sortition_height = + SortitionDB::get_block_snapshot_consensus( + sortdb.conn(), + &consensus_hash, + )? + .map(|sn| sn.block_height) + .unwrap_or(self.burnchain.first_block_height) + .saturating_sub(self.burnchain.first_block_height); + + let sortition_height_start = cmp::min( + stacks_tip_sortition_height, + inv_state.block_sortition_start, ); - inv_state.block_sortition_start - } else { - // really unreachable, but why tempt fate? - warn!( - "{:?}: Inventory state machine not yet initialized", - &self.local_peer + + debug!( + "{:?}: Begin downloader synchronization at sortition height {} min({},{})", + &self.local_peer, + sortition_height_start, + inv_state.block_sortition_start, + stacks_tip_sortition_height ); - 0 - }; + + sortition_height_start + } else { + // really unreachable, but why tempt fate? + warn!( + "{:?}: Inventory state machine not yet initialized", + &self.local_peer + ); + 0 + }; if let Some(ref mut downloader) = self.block_downloader { + debug!( + "{:?}: wake up downloader at sortition height {}", + &self.local_peer, start_download_sortition + ); downloader.hint_block_sortition_height_available( start_download_sortition, + ibd, ); downloader.hint_microblock_sortition_height_available( start_download_sortition, + ibd, + ); + } else { + warn!( + "{:?}: Block downloader not yet initialized", + &self.local_peer ); } } @@ -3104,6 +3176,7 @@ impl PeerNetwork { sortdb, chainstate, *dns_client, + ibd, network_result, )? { // advance work state @@ -3211,7 +3284,9 @@ impl PeerNetwork { "Atlas: Deregistering faulty connection (event_id: {})", event_id ); - network.http.deregister_http(network_state, event_id); + PeerNetwork::with_http(network, |_, http| { + http.deregister_http(network_state, event_id); + }); } Ok(()) }, @@ -3556,7 +3631,7 @@ impl PeerNetwork { // have the downloader request this block if it's new match self.block_downloader { Some(ref mut downloader) => { - downloader.hint_block_sortition_height_available(block_sortition_height); + downloader.hint_block_sortition_height_available(block_sortition_height, false); } None => {} } @@ -3623,7 +3698,8 @@ impl PeerNetwork { // have the downloader request this block if it's new match self.block_downloader { Some(ref mut downloader) => { - downloader.hint_microblock_sortition_height_available(mblock_sortition_height); + downloader + .hint_microblock_sortition_height_available(mblock_sortition_height, false); } None => {} } @@ -4124,18 +4200,40 @@ impl PeerNetwork { let new_chain_view = SortitionDB::get_burnchain_view(&sortdb.conn(), &self.burnchain, &sn)?; + let new_chain_view_stable_consensus_hash = { + let ic = sortdb.index_conn(); + let ancestor_sn = SortitionDB::get_ancestor_snapshot( + &ic, + new_chain_view.burn_stable_block_height, + &sn.sortition_id, + )? + .unwrap_or(SortitionDB::get_first_block_snapshot(sortdb.conn())?); + ancestor_sn.consensus_hash + }; + // wake up the inv-sync and downloader -- we have potentially more sortitions self.hint_sync_invs(self.chain_view.burn_stable_block_height); - self.hint_download_rescan(self.chain_view.burn_stable_block_height); + self.hint_download_rescan( + self.chain_view + .burn_stable_block_height + .saturating_sub(self.burnchain.first_block_height), + false, + ); + + // update cached burnchain view for /v2/info self.chain_view = new_chain_view; + self.chain_view_stable_consensus_hash = new_chain_view_stable_consensus_hash; } - if sn.burn_header_hash != self.last_burnchain_tip { + if sn.burn_header_hash != self.burnchain_tip.burn_header_hash { // try processing previously-buffered messages (best-effort) - self.last_burnchain_tip = sn.burn_header_hash; let buffered_messages = mem::replace(&mut self.pending_messages, HashMap::new()); ret = self.handle_unsolicited_messages(sortdb, chainstate, buffered_messages, false)?; } + + // update cached stacks chain view for /v2/info + self.burnchain_tip = sn; + Ok(ret) } @@ -4159,16 +4257,6 @@ impl PeerNetwork { return Err(net_error::NotConnected); } - // update local-peer state - self.refresh_local_peer()?; - - // update burnchain view - let unsolicited_buffered_messages = self.refresh_burnchain_view(sortdb, chainstate)?; - network_result.consume_unsolicited(unsolicited_buffered_messages); - - // update PoX view - self.refresh_sortition_view(sortdb)?; - // set up new inbound conversations self.process_new_sockets(&mut poll_state)?; @@ -4400,6 +4488,16 @@ impl PeerNetwork { self.num_downloader_passes, ); + // update local-peer state + self.refresh_local_peer()?; + + // update burnchain view, before handling any HTTP connections + let unsolicited_buffered_messages = self.refresh_burnchain_view(sortdb, chainstate)?; + network_result.consume_unsolicited(unsolicited_buffered_messages); + + // update PoX view, before handling any HTTP connections + self.refresh_sortition_view(sortdb)?; + // This operation needs to be performed before any early return: // Events are being parsed and dispatched here once and we want to // enqueue them. @@ -4419,18 +4517,17 @@ impl PeerNetwork { } PeerNetwork::with_network_state(self, |ref mut network, ref mut network_state| { - let http_stacks_msgs = network.http.run( - network_state, - network.chain_view.clone(), - &network.peers, - sortdb, - &network.peerdb, - &mut network.atlasdb, - chainstate, - mempool, - http_poll_state, - handler_args, - )?; + let http_stacks_msgs = PeerNetwork::with_http(network, |ref mut net, ref mut http| { + http.run( + network_state, + net, + sortdb, + chainstate, + mempool, + http_poll_state, + handler_args, + ) + })?; network_result.consume_http_uploads(http_stacks_msgs); Ok(()) })?; diff --git a/src/net/poll.rs b/src/net/poll.rs index ccb60462b..bad104760 100644 --- a/src/net/poll.rs +++ b/src/net/poll.rs @@ -211,12 +211,10 @@ impl NetworkState { assert!( self.event_map.len() <= self.event_capacity + self.servers.len(), - format!( - "BUG: event map exceeded event capacity ({} > {} + {})", - self.event_map.len(), - self.event_capacity, - self.servers.len() - ) + "BUG: event map exceeded event capacity ({} > {} + {})", + self.event_map.len(), + self.event_capacity, + self.servers.len() ); self.poll diff --git a/src/net/relay.rs b/src/net/relay.rs index cffe73393..b71188735 100644 --- a/src/net/relay.rs +++ b/src/net/relay.rs @@ -2092,7 +2092,7 @@ mod test { Some(ref mut inv_state) => { if inv_state.get_stats(&peer_1_nk).is_none() { test_debug!("initialize inv statistics for peer 1 in peer 2"); - inv_state.add_peer(peer_1_nk.clone()); + inv_state.add_peer(peer_1_nk.clone(), true); inv_state .get_stats_mut(&peer_1_nk) @@ -2569,7 +2569,7 @@ mod test { Some(ref mut inv_state) => { if inv_state.get_stats(&peer_0_nk).is_none() { test_debug!("initialize inv statistics for peer 0 in peer 1"); - inv_state.add_peer(peer_0_nk); + inv_state.add_peer(peer_0_nk, true); } else { test_debug!("peer 1 has inv state for peer 0"); } @@ -3104,7 +3104,7 @@ mod test { Some(ref mut inv_state) => { if inv_state.get_stats(&peer_0_nk).is_none() { test_debug!("initialize inv statistics for peer 0 in peer 1"); - inv_state.add_peer(peer_0_nk); + inv_state.add_peer(peer_0_nk, true); } else { test_debug!("peer 1 has inv state for peer 0"); } @@ -3448,7 +3448,8 @@ mod test { |ref mut peers| { for peer in peers.iter_mut() { // force peers to keep trying to process buffered data - peer.network.last_burnchain_tip = BurnchainHeaderHash([0u8; 32]); + peer.network.burnchain_tip.burn_header_hash = + BurnchainHeaderHash([0u8; 32]); } let done_flag = *done.borrow(); @@ -3779,7 +3780,8 @@ mod test { |ref mut peers| { for peer in peers.iter_mut() { // force peers to keep trying to process buffered data - peer.network.last_burnchain_tip = BurnchainHeaderHash([0u8; 32]); + peer.network.burnchain_tip.burn_header_hash = + BurnchainHeaderHash([0u8; 32]); } let tip_opt = peers[1] @@ -3901,7 +3903,8 @@ mod test { |ref mut peers| { for peer in peers.iter_mut() { // force peers to keep trying to process buffered data - peer.network.last_burnchain_tip = BurnchainHeaderHash([0u8; 32]); + peer.network.burnchain_tip.burn_header_hash = + BurnchainHeaderHash([0u8; 32]); } let mut i = idx.borrow_mut(); diff --git a/src/net/rpc.rs b/src/net/rpc.rs index 08a27f36e..343dd3874 100644 --- a/src/net/rpc.rs +++ b/src/net/rpc.rs @@ -123,14 +123,12 @@ pub struct RPCHandlerArgs<'a> { } pub struct ConversationHttp { - network_id: u32, connection: ConnectionHttp, conn_id: usize, timeout: u64, peer_host: PeerHost, outbound_url: Option, peer_addr: SocketAddr, - burnchain: Burnchain, keep_alive: bool, total_request_count: u64, // number of messages taken from the inbox total_reply_count: u64, // number of messages responsed to @@ -174,37 +172,18 @@ impl fmt::Debug for ConversationHttp { } impl RPCPeerInfoData { - pub fn from_db( - burnchain: &Burnchain, - sortdb: &SortitionDB, + pub fn from_network( + network: &PeerNetwork, chainstate: &StacksChainState, - peerdb: &PeerDB, exit_at_block_height: &Option<&u64>, genesis_chainstate_hash: &Sha256Sum, - ) -> Result { - let burnchain_tip = SortitionDB::get_canonical_burn_chain_tip(sortdb.conn())?; - let local_peer = PeerDB::get_local_peer(peerdb.conn())?; - let stable_burnchain_tip = { - let ic = sortdb.index_conn(); - let stable_height = - if burnchain_tip.block_height < burnchain.stable_confirmations as u64 { - 0 - } else { - burnchain_tip.block_height - (burnchain.stable_confirmations as u64) - }; - SortitionDB::get_ancestor_snapshot(&ic, stable_height, &burnchain_tip.sortition_id)? - .ok_or_else(|| net_error::DBError(db_error::NotFoundError))? - }; - + ) -> RPCPeerInfoData { let server_version = version_string( "stacks-node", option_env!("STACKS_NODE_VERSION") .or(option_env!("CARGO_PKG_VERSION")) .unwrap_or("0.0.0.0"), ); - let stacks_tip_consensus_hash = burnchain_tip.canonical_stacks_tip_consensus_hash; - let stacks_tip = burnchain_tip.canonical_stacks_tip_hash; - let stacks_tip_height = burnchain_tip.canonical_stacks_tip_height; let (unconfirmed_tip, unconfirmed_seq) = match chainstate.unconfirmed_state { Some(ref unconfirmed) => { if unconfirmed.num_mined_txs() > 0 { @@ -219,23 +198,26 @@ impl RPCPeerInfoData { None => (StacksBlockId([0x00; 32]), 0), }; - Ok(RPCPeerInfoData { - peer_version: burnchain.peer_version, - pox_consensus: burnchain_tip.consensus_hash, - burn_block_height: burnchain_tip.block_height, - stable_pox_consensus: stable_burnchain_tip.consensus_hash, - stable_burn_block_height: stable_burnchain_tip.block_height, + RPCPeerInfoData { + peer_version: network.burnchain.peer_version, + pox_consensus: network.burnchain_tip.consensus_hash.clone(), + burn_block_height: network.chain_view.burn_block_height, + stable_pox_consensus: network.chain_view_stable_consensus_hash.clone(), + stable_burn_block_height: network.chain_view.burn_stable_block_height, server_version, - network_id: local_peer.network_id, - parent_network_id: local_peer.parent_network_id, - stacks_tip_height, - stacks_tip, - stacks_tip_consensus_hash: stacks_tip_consensus_hash.to_hex(), + network_id: network.local_peer.network_id, + parent_network_id: network.local_peer.parent_network_id, + stacks_tip_height: network.burnchain_tip.canonical_stacks_tip_height, + stacks_tip: network.burnchain_tip.canonical_stacks_tip_hash.clone(), + stacks_tip_consensus_hash: network + .burnchain_tip + .canonical_stacks_tip_consensus_hash + .clone(), unanchored_tip: unconfirmed_tip, unanchored_seq: unconfirmed_seq, exit_at_block_height: exit_at_block_height.cloned(), genesis_chainstate_hash: genesis_chainstate_hash.clone(), - }) + } } } @@ -473,8 +455,6 @@ impl RPCNeighborsInfo { impl ConversationHttp { pub fn new( - network_id: u32, - burnchain: &Burnchain, peer_addr: SocketAddr, outbound_url: Option, peer_host: PeerHost, @@ -484,7 +464,6 @@ impl ConversationHttp { let mut stacks_http = StacksHttp::new(peer_addr.clone()); stacks_http.maximum_call_argument_size = conn_opts.maximum_call_argument_size; ConversationHttp { - network_id: network_id, connection: ConnectionHttp::new(stacks_http, conn_opts, None), conn_id: conn_id, timeout: conn_opts.timeout, @@ -492,7 +471,6 @@ impl ConversationHttp { peer_addr: peer_addr, outbound_url: outbound_url, peer_host: peer_host, - burnchain: burnchain.clone(), pending_request: None, pending_response: None, pending_error_response: None, @@ -607,36 +585,20 @@ impl ConversationHttp { http: &mut StacksHttp, fd: &mut W, req: &HttpRequestType, - burnchain: &Burnchain, - sortdb: &SortitionDB, + network: &PeerNetwork, chainstate: &StacksChainState, - peerdb: &PeerDB, handler_args: &RPCHandlerArgs, ) -> Result<(), net_error> { let response_metadata = HttpResponseMetadata::from(req); - match RPCPeerInfoData::from_db( - burnchain, - sortdb, + let pi = RPCPeerInfoData::from_network( + network, chainstate, - peerdb, &handler_args.exit_at_block_height, &handler_args.genesis_chainstate_hash, - ) { - Ok(pi) => { - let response = HttpResponseType::PeerInfo(response_metadata, pi); - // timer.observe_duration(); - response.send(http, fd) - } - Err(e) => { - warn!("Failed to get peer info {:?}: {:?}", req, &e); - let response = HttpResponseType::ServerError( - response_metadata, - "Failed to query peer info".to_string(), - ); - // timer.observe_duration(); - response.send(http, fd) - } - } + ); + let response = HttpResponseType::PeerInfo(response_metadata, pi); + // timer.observe_duration(); + response.send(http, fd) } /// Handle a GET pox info. @@ -772,13 +734,15 @@ impl ConversationHttp { http: &mut StacksHttp, fd: &mut W, req: &HttpRequestType, - network_id: u32, - chain_view: &BurnchainView, - peers: &PeerMap, - peerdb: &PeerDB, + network: &PeerNetwork, ) -> Result<(), net_error> { let response_metadata = HttpResponseMetadata::from(req); - let neighbor_data = RPCNeighborsInfo::from_p2p(network_id, peers, chain_view, peerdb)?; + let neighbor_data = RPCNeighborsInfo::from_p2p( + network.local_peer.network_id, + &network.peers, + &network.chain_view, + &network.peerdb, + )?; let response = HttpResponseType::Neighbors(response_metadata, neighbor_data); response.send(http, fd) } @@ -1848,11 +1812,8 @@ impl ConversationHttp { pub fn handle_request( &mut self, req: HttpRequestType, - chain_view: &BurnchainView, - peers: &PeerMap, + network: &mut PeerNetwork, sortdb: &SortitionDB, - peerdb: &PeerDB, - atlasdb: &mut AtlasDB, chainstate: &mut StacksChainState, mempool: &mut MemPoolDB, handler_opts: &RPCHandlerArgs, @@ -1867,10 +1828,8 @@ impl ConversationHttp { &mut self.connection.protocol, &mut reply, &req, - &self.burnchain, - sortdb, + network, chainstate, - peerdb, handler_opts, )?; None @@ -1891,7 +1850,7 @@ impl ConversationHttp { sortdb, chainstate, &tip, - &self.burnchain, + &network.burnchain, )?; } None @@ -1901,10 +1860,7 @@ impl ConversationHttp { &mut self.connection.protocol, &mut reply, &req, - self.network_id, - chain_view, - peers, - peerdb, + network, )?; None } @@ -2123,7 +2079,7 @@ impl ConversationHttp { tip.anchored_block_hash, mempool, tx.clone(), - atlasdb, + &mut network.atlasdb, attachment.clone(), handler_opts.event_observer.as_deref(), )?; @@ -2149,7 +2105,7 @@ impl ConversationHttp { &mut self.connection.protocol, &mut reply, &req, - atlasdb, + &mut network.atlasdb, content_hash.clone(), )?; None @@ -2163,7 +2119,7 @@ impl ConversationHttp { &mut self.connection.protocol, &mut reply, &req, - atlasdb, + &mut network.atlasdb, &index_block_hash, pages_indexes, &self.connection.options, @@ -2517,11 +2473,8 @@ impl ConversationHttp { /// Returns the list of transactions we'll need to forward to the peer network pub fn chat( &mut self, - chain_view: &BurnchainView, - peers: &PeerMap, + network: &mut PeerNetwork, sortdb: &SortitionDB, - peerdb: &PeerDB, - atlasdb: &mut AtlasDB, chainstate: &mut StacksChainState, mempool: &mut MemPoolDB, handler_args: &RPCHandlerArgs, @@ -2550,17 +2503,7 @@ impl ConversationHttp { self.total_request_count += 1; self.last_request_timestamp = get_epoch_time_secs(); let msg_opt = monitoring::instrument_http_request_handler(req, |req| { - self.handle_request( - req, - chain_view, - peers, - sortdb, - peerdb, - atlasdb, - chainstate, - mempool, - handler_args, - ) + self.handle_request(req, network, sortdb, chainstate, mempool, handler_args) })?; if let Some(msg) = msg_opt { ret.push(msg); @@ -3209,8 +3152,6 @@ mod test { let view_2 = peer_2.get_burnchain_view().unwrap(); let mut convo_1 = ConversationHttp::new( - peer_1.config.network_id, - &peer_1.config.burnchain, format!("127.0.0.1:{}", peer_1_http) .parse::() .unwrap(), @@ -3221,8 +3162,6 @@ mod test { ); let mut convo_2 = ConversationHttp::new( - peer_2.config.network_id, - &peer_2.config.burnchain, format!("127.0.0.1:{}", peer_2_http) .parse::() .unwrap(), @@ -3254,11 +3193,8 @@ mod test { convo_1 .chat( - &view_1, - &PeerMap::new(), + &mut peer_1.network, &mut peer_1_sortdb, - &peer_1.network.peerdb, - &mut peer_1.network.atlasdb, &mut peer_1_stacks_node.chainstate, &mut peer_1_mempool, &RPCHandlerArgs::default(), @@ -3281,11 +3217,8 @@ mod test { convo_2 .chat( - &view_2, - &PeerMap::new(), + &mut peer_2.network, &mut peer_2_sortdb, - &peer_2.network.peerdb, - &mut peer_2.network.atlasdb, &mut peer_2_stacks_node.chainstate, &mut peer_2_mempool, &RPCHandlerArgs::default(), @@ -3322,11 +3255,8 @@ mod test { convo_1 .chat( - &view_1, - &PeerMap::new(), + &mut peer_1.network, &mut peer_1_sortdb, - &peer_1.network.peerdb, - &mut peer_1.network.atlasdb, &mut peer_1_stacks_node.chainstate, &mut peer_1_mempool, &RPCHandlerArgs::default(), @@ -3361,15 +3291,12 @@ mod test { ref mut convo_client, ref mut peer_server, ref mut convo_server| { - let peer_info = RPCPeerInfoData::from_db( - &peer_server.config.burnchain, - peer_server.sortdb.as_mut().unwrap(), + let peer_info = RPCPeerInfoData::from_network( + &peer_server.network, &peer_server.stacks_node.as_ref().unwrap().chainstate, - &peer_server.network.peerdb, &None, &Sha256Sum::zero(), - ) - .unwrap(); + ); *peer_server_info.borrow_mut() = Some(peer_info); diff --git a/src/net/server.rs b/src/net/server.rs index d0874440b..cb219869f 100644 --- a/src/net/server.rs +++ b/src/net/server.rs @@ -35,7 +35,7 @@ use net::atlas::AtlasDB; use net::connection::*; use net::db::*; use net::http::*; -use net::p2p::PeerMap; +use net::p2p::{PeerMap, PeerNetwork}; use net::poll::*; use net::rpc::*; use net::Error as net_error; @@ -55,9 +55,6 @@ use core::mempool::*; #[derive(Debug)] pub struct HttpPeer { - pub network_id: u32, - pub chain_view: BurnchainView, - // ongoing http conversations (either they reached out to us, or we to them) pub peers: HashMap, pub sockets: HashMap, @@ -76,31 +73,19 @@ pub struct HttpPeer { // server network handle pub http_server_handle: usize, - // info on the burn chain we're tracking - pub burnchain: Burnchain, - // connection options pub connection_opts: ConnectionOptions, } impl HttpPeer { - pub fn new( - network_id: u32, - burnchain: Burnchain, - chain_view: BurnchainView, - conn_opts: ConnectionOptions, - server_handle: usize, - ) -> HttpPeer { + pub fn new(conn_opts: ConnectionOptions, server_handle: usize) -> HttpPeer { HttpPeer { - network_id: network_id, - chain_view: chain_view, peers: HashMap::new(), sockets: HashMap::new(), connecting: HashMap::new(), http_server_handle: server_handle, - burnchain: burnchain, connection_opts: conn_opts, } } @@ -147,14 +132,15 @@ impl HttpPeer { pub fn connect_http( &mut self, network_state: &mut NetworkState, + network: &PeerNetwork, data_url: UrlString, addr: SocketAddr, request: Option, ) -> Result { if let Some(event_id) = self.find_free_conversation(&data_url) { let http_nk = NeighborKey { - peer_version: self.burnchain.peer_version, - network_id: self.network_id, + peer_version: network.burnchain.peer_version, + network_id: network.local_peer.network_id, addrbytes: PeerAddress::from_socketaddr(&addr), port: addr.port(), }; @@ -260,8 +246,6 @@ impl HttpPeer { }; let mut new_convo = ConversationHttp::new( - self.network_id, - &self.burnchain, client_addr.clone(), outbound_url.clone(), peer_host, @@ -434,11 +418,8 @@ impl HttpPeer { /// Returns whether or not the convo is still alive, as well as any message(s) that need to be /// forwarded to the peer network. fn process_http_conversation( - chain_view: &BurnchainView, - peers: &PeerMap, + network: &mut PeerNetwork, sortdb: &SortitionDB, - peerdb: &PeerDB, - atlasdb: &mut AtlasDB, chainstate: &mut StacksChainState, mempool: &mut MemPoolDB, event_id: usize, @@ -508,16 +489,7 @@ impl HttpPeer { // react to inbound messages -- do we need to send something out, or fulfill requests // to other threads? Try to chat even if the recv() failed, since we'll want to at // least drain the conversation inbox. - let msgs = match convo.chat( - chain_view, - peers, - sortdb, - peerdb, - atlasdb, - chainstate, - mempool, - handler_args, - ) { + let msgs = match convo.chat(network, sortdb, chainstate, mempool, handler_args) { Ok(msgs) => msgs, Err(e) => { debug!( @@ -589,10 +561,8 @@ impl HttpPeer { fn process_ready_sockets( &mut self, poll_state: &mut NetworkPollState, - peers: &PeerMap, + network: &mut PeerNetwork, sortdb: &SortitionDB, - peerdb: &PeerDB, - atlasdb: &mut AtlasDB, chainstate: &mut StacksChainState, mempool: &mut MemPoolDB, handler_args: &RPCHandlerArgs, @@ -619,11 +589,8 @@ impl HttpPeer { // activity on a http socket test_debug!("Process HTTP data from {:?}", convo); match HttpPeer::process_http_conversation( - &self.chain_view, - peers, + network, sortdb, - peerdb, - atlasdb, chainstate, mempool, *event_id, @@ -687,19 +654,13 @@ impl HttpPeer { pub fn run( &mut self, network_state: &mut NetworkState, - new_chain_view: BurnchainView, - p2p_peers: &PeerMap, + network: &mut PeerNetwork, sortdb: &SortitionDB, - peerdb: &PeerDB, - atlasdb: &mut AtlasDB, chainstate: &mut StacksChainState, mempool: &mut MemPoolDB, mut poll_state: NetworkPollState, handler_args: &RPCHandlerArgs, ) -> Result, net_error> { - // update burnchain snapshot - self.chain_view = new_chain_view; - // set up new inbound conversations self.process_new_sockets(network_state, chainstate, &mut poll_state)?; @@ -709,10 +670,8 @@ impl HttpPeer { // run existing conversations, clear out broken ones, and get back messages forwarded to us let (stacks_msgs, error_events) = self.process_ready_sockets( &mut poll_state, - p2p_peers, + network, sortdb, - peerdb, - atlasdb, chainstate, mempool, handler_args, diff --git a/src/types/proof.rs b/src/types/proof.rs index 0247e55aa..07b09aba8 100644 --- a/src/types/proof.rs +++ b/src/types/proof.rs @@ -20,7 +20,7 @@ pub trait ClarityMarfTrieId: { fn as_bytes(&self) -> &[u8]; fn to_bytes(self) -> [u8; 32]; - fn from_bytes([u8; 32]) -> Self; + fn from_bytes(from: [u8; 32]) -> Self; fn sentinel() -> Self; } diff --git a/src/util/db.rs b/src/util/db.rs index e19387725..48ba060f8 100644 --- a/src/util/db.rs +++ b/src/util/db.rs @@ -204,7 +204,7 @@ impl FromColumn for QualifiedContractIdentifier { } pub fn u64_to_sql(x: u64) -> Result { - if x > (i64::max_value() as u64) { + if x > (i64::MAX as u64) { return Err(Error::ParseError); } Ok(x as i64) @@ -518,7 +518,7 @@ pub fn get_ancestor_block_hash( block_height: u64, tip_block_hash: &T, ) -> Result, Error> { - assert!(block_height < u32::max_value() as u64); + assert!(block_height < u32::MAX as u64); let mut read_only = index.reopen_readonly()?; let bh = read_only.get_block_at_height(block_height as u32, tip_block_hash)?; Ok(bh) diff --git a/src/util/retry.rs b/src/util/retry.rs index a382f6e77..915de7a83 100644 --- a/src/util/retry.rs +++ b/src/util/retry.rs @@ -168,13 +168,13 @@ mod test { let mut tmp_buf = [0u8; 3]; let e = retry_reader.read_exact(&mut tmp_buf); - let e_str = format!("{:?}", &e); - assert!(e.is_err(), e_str); + assert!(e.is_err(), "{:?}", &e); assert!( - format!("{:?}", &e.unwrap_err()) + format!("{:?}", &e.as_ref().unwrap_err()) .find("failed to fill whole buffer") .is_some(), - e_str + "{:?}", + &e ); let res = retry_reader.read(&mut tmp_buf); diff --git a/src/util/uint.rs b/src/util/uint.rs index 776b69dfe..a35422ba3 100644 --- a/src/util/uint.rs +++ b/src/util/uint.rs @@ -487,7 +487,7 @@ mod tests { "0x00000000000000000000000000000000000000000000000000000000deadbeef" ); assert_eq!( - format!("{}", Uint256::from_u64(u64::max_value())), + format!("{}", Uint256::from_u64(u64::MAX)), "0x000000000000000000000000000000000000000000000000ffffffffffffffff" ); diff --git a/src/vm/analysis/errors.rs b/src/vm/analysis/errors.rs index 2eda5a7d0..56d0d7e09 100644 --- a/src/vm/analysis/errors.rs +++ b/src/vm/analysis/errors.rs @@ -370,7 +370,7 @@ impl DiagnosableError for CheckErrors { CheckErrors::NonFunctionApplication => format!("expecting expression of type function"), CheckErrors::ExpectedListApplication => format!("expecting expression of type list"), CheckErrors::ExpectedSequence(found_type) => format!("expecting expression of type 'list', 'buff', 'string-ascii' or 'string-utf8' - found '{}'", found_type), - CheckErrors::MaxLengthOverflow => format!("expecting a value <= {}", u32::max_value()), + CheckErrors::MaxLengthOverflow => format!("expecting a value <= {}", u32::MAX), CheckErrors::BadLetSyntax => format!("invalid syntax of 'let'"), CheckErrors::CircularReference(function_names) => format!("detected interdependent functions ({})", function_names.join(", ")), CheckErrors::BadSyntaxBinding => format!("invalid syntax binding"), diff --git a/src/vm/analysis/type_checker/natives/mod.rs b/src/vm/analysis/type_checker/natives/mod.rs index 84b636341..d5885a9f1 100644 --- a/src/vm/analysis/type_checker/natives/mod.rs +++ b/src/vm/analysis/type_checker/natives/mod.rs @@ -482,7 +482,7 @@ fn check_secp256k1_recover( check_argument_count(2, args)?; checker.type_check_expects(&args[0], context, &BUFF_32)?; checker.type_check_expects(&args[1], context, &BUFF_65)?; - Ok(TypeSignature::new_response(BUFF_33, TypeSignature::UIntType).unwrap()) + Ok(TypeSignature::new_response(BUFF_33.clone(), TypeSignature::UIntType).unwrap()) } fn check_secp256k1_verify( diff --git a/src/vm/contexts.rs b/src/vm/contexts.rs index e047430ae..2c489e4c2 100644 --- a/src/vm/contexts.rs +++ b/src/vm/contexts.rs @@ -52,6 +52,8 @@ use crate::types::chainstate::StacksMicroblockHeader; use serde::Serialize; use vm::costs::cost_functions::ClarityCostFunction; +use vm::coverage::CoverageReporter; + pub const MAX_CONTEXT_DEPTH: u16 = 256; // TODO: @@ -191,6 +193,7 @@ pub struct GlobalContext<'a> { read_only: Vec, pub cost_track: LimitedCostTracker, pub mainnet: bool, + pub coverage_reporting: Option, } #[derive(Serialize, Deserialize, Clone)] @@ -545,6 +548,14 @@ impl<'a> OwnedEnvironment<'a> { } } + pub fn set_coverage_reporter(&mut self, reporter: CoverageReporter) { + self.context.coverage_reporting = Some(reporter) + } + + pub fn take_coverage_reporter(&mut self) -> Option { + self.context.coverage_reporting.take() + } + pub fn new_free(mainnet: bool, database: ClarityDatabase<'a>) -> OwnedEnvironment<'a> { OwnedEnvironment { context: GlobalContext::new(mainnet, database, LimitedCostTracker::new_free()), @@ -1349,6 +1360,7 @@ impl<'a> GlobalContext<'a> { asset_maps: Vec::new(), event_batches: Vec::new(), mainnet, + coverage_reporting: None, } } @@ -1699,8 +1711,7 @@ mod test { let mut am2 = AssetMap::new(); am1.add_token_transfer(&p1, t1.clone(), 1).unwrap(); - am1.add_token_transfer(&p2, t1.clone(), u128::max_value()) - .unwrap(); + am1.add_token_transfer(&p2, t1.clone(), u128::MAX).unwrap(); am2.add_token_transfer(&p1, t1.clone(), 1).unwrap(); am2.add_token_transfer(&p2, t1.clone(), 1).unwrap(); @@ -1708,7 +1719,7 @@ mod test { let table = am1.to_table(); - assert_eq!(table[&p2][&t1], AssetMapEntry::Token(u128::max_value())); + assert_eq!(table[&p2][&t1], AssetMapEntry::Token(u128::MAX)); assert_eq!(table[&p1][&t1], AssetMapEntry::Token(1)); } diff --git a/src/vm/costs/mod.rs b/src/vm/costs/mod.rs index 10b06624e..38d8e60c0 100644 --- a/src/vm/costs/mod.rs +++ b/src/vm/costs/mod.rs @@ -999,11 +999,11 @@ impl ExecutionCost { pub fn max_value() -> ExecutionCost { Self { - runtime: u64::max_value(), - write_length: u64::max_value(), - read_count: u64::max_value(), - write_count: u64::max_value(), - read_length: u64::max_value(), + runtime: u64::MAX, + write_length: u64::MAX, + read_count: u64::MAX, + write_count: u64::MAX, + read_length: u64::MAX, } } @@ -1087,14 +1087,8 @@ mod unit_tests { #[test] fn test_simple_overflows() { - assert_eq!( - u64::max_value().cost_overflow_add(1), - Err(CostErrors::CostOverflow) - ); - assert_eq!( - u64::max_value().cost_overflow_mul(2), - Err(CostErrors::CostOverflow) - ); + assert_eq!(u64::MAX.cost_overflow_add(1), Err(CostErrors::CostOverflow)); + assert_eq!(u64::MAX.cost_overflow_mul(2), Err(CostErrors::CostOverflow)); } #[test] @@ -1117,7 +1111,7 @@ mod unit_tests { 64, 128, 2_u64.pow(63), - u64::max_value(), + u64::MAX, ]; let expected = [0, 1, 2, 3, 4, 5, 5, 6, 6, 6, 7, 63, 64]; for (input, expected) in inputs.iter().zip(expected.iter()) { diff --git a/src/vm/coverage.rs b/src/vm/coverage.rs new file mode 100644 index 000000000..9a532852d --- /dev/null +++ b/src/vm/coverage.rs @@ -0,0 +1,224 @@ +use std::{ + collections::{BTreeMap, HashMap, HashSet}, + fs::File, + io::Write, +}; + +use serde_json::Value as JsonValue; +use vm::types::QualifiedContractIdentifier; +use vm::SymbolicExpression; + +use super::functions::define::DefineFunctionsParsed; + +pub struct CoverageReporter { + executed_lines: HashMap>, +} + +#[derive(Serialize, Deserialize)] +struct ContractFileInfo { + contract: String, + src_file: String, + executable_lines: Vec, +} + +#[derive(Serialize, Deserialize)] +struct CoverageFileInfo { + coverage: HashMap>, +} + +impl CoverageReporter { + pub fn new() -> CoverageReporter { + CoverageReporter { + executed_lines: HashMap::new(), + } + } + + #[cfg(not(feature = "developer-mode"))] + pub fn report_eval( + &mut self, + _expr: &SymbolicExpression, + _contract: &QualifiedContractIdentifier, + ) { + } + + #[cfg(feature = "developer-mode")] + pub fn report_eval( + &mut self, + expr: &SymbolicExpression, + contract: &QualifiedContractIdentifier, + ) { + if expr.match_list().is_some() { + // don't count the whole list expression: wait until we've eval'ed the + // list components + return; + } + + // other sexps can only span 1 line + let line_executed = expr.span.start_line; + + if let Some(execution_map_contract) = self.executed_lines.get_mut(contract) { + if let Some(execution_count) = execution_map_contract.get_mut(&line_executed) { + *execution_count += 1; + } else { + execution_map_contract.insert(line_executed, 1); + } + } else { + let mut execution_map_contract = HashMap::new(); + execution_map_contract.insert(line_executed, 1); + self.executed_lines + .insert(contract.clone(), execution_map_contract); + } + } + + pub fn to_file + Copy>(&self, filename: P) -> std::io::Result<()> { + let f = File::create(filename)?; + let mut coverage = HashMap::new(); + for (contract, execution_map) in self.executed_lines.iter() { + let mut executed_lines = vec![]; + for (line, count) in execution_map.iter() { + executed_lines.push((*line, *count)); + } + executed_lines.sort_by_key(|f| f.0); + + coverage.insert(contract.to_string(), executed_lines); + } + + let out = CoverageFileInfo { coverage }; + if let Err(e) = serde_json::to_writer(f, &out) { + error!( + "Failed to serialize JSON to coverage file {}: {}", + filename.as_ref().display(), + e + ); + return Err(e.into()); + } + + Ok(()) + } + + fn executable_lines(exprs: &[SymbolicExpression]) -> Vec { + let mut lines = vec![]; + let mut lines_seen = HashSet::new(); + for expression in exprs.iter() { + let mut frontier = vec![expression]; + while let Some(cur_expr) = frontier.pop() { + // handle defines: the `define-` atom is non executable, and neither are any of the type arguments, + // but the bodies of functions, the value of a constant, initial values for variables, and the + // max supply of FTs + if let Some(define_expr) = DefineFunctionsParsed::try_parse(cur_expr).ok().flatten() + { + match define_expr { + DefineFunctionsParsed::Constant { name: _, value } => { + frontier.push(value); + } + DefineFunctionsParsed::PrivateFunction { signature: _, body } + | DefineFunctionsParsed::PublicFunction { signature: _, body } + | DefineFunctionsParsed::ReadOnlyFunction { signature: _, body } => { + frontier.push(body); + } + DefineFunctionsParsed::BoundedFungibleToken { + name: _, + max_supply, + } => { + frontier.push(max_supply); + } + DefineFunctionsParsed::PersistedVariable { + name: _, + data_type: _, + initial, + } => { + frontier.push(initial); + } + DefineFunctionsParsed::NonFungibleToken { .. } => {} + DefineFunctionsParsed::UnboundedFungibleToken { .. } => {} + DefineFunctionsParsed::Map { .. } => {} + DefineFunctionsParsed::Trait { .. } => {} + DefineFunctionsParsed::UseTrait { .. } => {} + DefineFunctionsParsed::ImplTrait { .. } => {} + } + + continue; + } + + if let Some(children) = cur_expr.match_list() { + // don't count list expressions as a whole, just their children + frontier.extend(children); + } else { + let line = cur_expr.span.start_line; + if !lines_seen.contains(&line) { + lines_seen.insert(line); + lines.push(line); + } + } + } + } + + lines.sort(); + lines + } + + pub fn register_src_file + Copy>( + contract: &QualifiedContractIdentifier, + src_file_name: &str, + ast: &[SymbolicExpression], + filename: P, + ) -> std::io::Result<()> { + let f = File::create(filename)?; + + let executable_lines = CoverageReporter::executable_lines(ast); + + let json = ContractFileInfo { + contract: contract.to_string(), + src_file: src_file_name.to_string(), + executable_lines, + }; + + if let Err(e) = serde_json::to_writer(f, &json) { + error!( + "Failed to serialize JSON to coverage file {}: {}", + filename.as_ref().display(), + e + ); + return Err(e.into()); + } + Ok(()) + } + + pub fn produce_lcov>( + out_filename: &str, + register_files: &[P], + coverage_files: &[P], + ) -> std::io::Result<()> { + let mut out = File::create(out_filename)?; + + for contract_filename in register_files.iter() { + let reader = File::open(contract_filename)?; + let info: ContractFileInfo = serde_json::from_reader(reader)?; + let mut summed_coverage = BTreeMap::new(); + for coverage_filename in coverage_files.iter() { + let cov_reader = File::open(coverage_filename)?; + let coverage: CoverageFileInfo = serde_json::from_reader(cov_reader)?; + if let Some(contract_coverage) = coverage.coverage.get(&info.contract) { + for (line, count) in contract_coverage.iter() { + if let Some(line_count) = summed_coverage.get_mut(line) { + *line_count += *count; + } else { + summed_coverage.insert(*line, *count); + } + } + } + } + writeln!(out, "TN:{}", &info.contract)?; + writeln!(out, "SF:{}", &info.src_file)?; + for line in info.executable_lines.iter() { + let count = summed_coverage.get(line).cloned().unwrap_or(0); + writeln!(out, "DA:{},{}", line, count)?; + } + writeln!(out, "LH:{}", summed_coverage.len())?; + writeln!(out, "LF:{}", &info.executable_lines.len())?; + writeln!(out, "end_of_record")?; + } + + Ok(()) + } +} diff --git a/src/vm/database/sqlite.rs b/src/vm/database/sqlite.rs index 009c18aeb..e06772aaa 100644 --- a/src/vm/database/sqlite.rs +++ b/src/vm/database/sqlite.rs @@ -44,7 +44,7 @@ fn sqlite_put(conn: &Connection, key: &str, value: &str) { Ok(_) => {} Err(e) => { error!("Failed to insert/replace ({},{}): {:?}", key, value, &e); - panic!(SQL_FAIL_MESSAGE); + panic!("{}", SQL_FAIL_MESSAGE); } }; } @@ -63,7 +63,7 @@ fn sqlite_get(conn: &Connection, key: &str) -> Option { Ok(x) => x, Err(e) => { error!("Failed to query '{}': {:?}", key, &e); - panic!(SQL_FAIL_MESSAGE); + panic!("{}", SQL_FAIL_MESSAGE); } }; @@ -105,7 +105,7 @@ impl SqliteConnection { &value.to_string(), &e ); - panic!(SQL_FAIL_MESSAGE); + panic!("{}", SQL_FAIL_MESSAGE); } } @@ -116,14 +116,14 @@ impl SqliteConnection { ¶ms, ) { error!("Failed to update {} to {}: {:?}", &from, &to, &e); - panic!(SQL_FAIL_MESSAGE); + panic!("{}", SQL_FAIL_MESSAGE); } } pub fn drop_metadata(conn: &Connection, from: &StacksBlockId) { if let Err(e) = conn.execute("DELETE FROM metadata_table WHERE blockhash = ?", &[from]) { error!("Failed to drop metadata from {}: {:?}", &from, &e); - panic!(SQL_FAIL_MESSAGE); + panic!("{}", SQL_FAIL_MESSAGE); } } @@ -147,7 +147,7 @@ impl SqliteConnection { Ok(x) => x, Err(e) => { error!("Failed to query ({},{}): {:?}", &bhh, &key, &e); - panic!(SQL_FAIL_MESSAGE); + panic!("{}", SQL_FAIL_MESSAGE); } } } diff --git a/src/vm/docs/mod.rs b/src/vm/docs/mod.rs index 01964c2df..63ed03824 100644 --- a/src/vm/docs/mod.rs +++ b/src/vm/docs/mod.rs @@ -463,50 +463,89 @@ inputted value. The function always returns `true`.", }; const MAP_API: SpecialAPI = SpecialAPI { - input_type: "Function(A, B, ..., N) -> X, (list A1 A2 ... Am), (list B1 B2 ... Bm), ..., (list N1 N2 ... Nm)", + input_type: "Function(A, B, ..., N) -> X, sequence_A, sequence_B, ..., sequence_N", output_type: "(list X)", - signature: "(map func list-A list-B ... list-N)", - description: "The `map` function applies the input function `func` to each element of the -input lists, and outputs a list containing the _outputs_ from those function applications.", - example: " + signature: "(map func sequence_A sequence_B ... sequence_N)", + description: "The `map` function applies the function `func` to each corresponding element of the input sequences, +and outputs a _list_ of the same type containing the outputs from those function applications. +Applicable sequence types are `(list A)`, `buff`, `string-ascii` and `string-utf8`, +for which the corresponding element types are, respectively, `A`, `(buff 1)`, `(string-ascii 1)` and `(string-utf8 1)`. +The `func` argument must be a literal function name. +Also, note that, no matter what kind of sequences the inputs are, the output is always a list.", + example: r#" (map not (list true false true false)) ;; Returns (false true false true) -(map + (list 1 2 3) (list 1 2 3) (list 1 2 3)) ;; Returns (3 6 9)", +(map + (list 1 2 3) (list 1 2 3) (list 1 2 3)) ;; Returns (3 6 9) +(define-private (a-or-b (char (string-utf8 1))) (if (is-eq char u"a") u"a" u"b")) +(map a-or-b u"aca") ;; Returns (u"a" u"b" u"a") +(define-private (zero-or-one (char (buff 1))) (if (is-eq char 0x00) 0x00 0x01)) +(map zero-or-one 0x000102) ;; Returns (0x00 0x01 0x01) +"#, }; const FILTER_API: SpecialAPI = SpecialAPI { - input_type: "Function(A) -> bool, (list A)", - output_type: "(list A)", - signature: "(filter func list)", + input_type: "Function(A) -> bool, sequence_A", + output_type: "sequence_A", + signature: "(filter func sequence)", description: "The `filter` function applies the input function `func` to each element of the -input list, and returns the same list with any elements removed for which the `func` returned `false`.", - example: "(filter not (list true false true false)) ;; Returns (false false)" +input sequence, and returns the same sequence with any elements removed for which `func` returned `false`. +Applicable sequence types are `(list A)`, `buff`, `string-ascii` and `string-utf8`, +for which the corresponding element types are, respectively, `A`, `(buff 1)`, `(string-ascii 1)` and `(string-utf8 1)`. +The `func` argument must be a literal function name. +", + example: r#" +(filter not (list true false true false)) ;; Returns (false false) +(define-private (is-a (char (string-utf8 1))) (is-eq char u"a")) +(filter is-a u"acabd") ;; Returns u"aa" +(define-private (is-zero (char (buff 1))) (is-eq char 0x00)) +(filter is-zero 0x00010002) ;; Returns 0x0000 +"#, }; const FOLD_API: SpecialAPI = SpecialAPI { - input_type: "Function(A, B) -> B, (list A), B", + input_type: "Function(A, B) -> B, sequence_A, B", output_type: "B", - signature: "(fold func list initial-value)", - description: "The `fold` special form applies the input function `func` to each element of the -input list _and_ the output of the previous application of the `fold` function. When invoked on -the first list element, it uses the `initial-value` as the second input. `fold` returns the last -value returned by the successive applications. Note that the first argument is not evaluated thus -has to be a literal function name.", - example: "(fold * (list 2 2 2) 1) ;; Returns 8 + signature: "(fold func sequence_A initial_B)", + description: "The `fold` function condenses `sequence_A` into a value of type +`B` by recursively applies the function `func` to each element of the +input sequence _and_ the output of a previous application of `func`. + +`fold` uses `initial_B` in the initial application of `func`, along with the +first element of `sequence_A`. The resulting value of type `B` is used for the +next application of `func`, along with the next element of `sequence_A` and so +on. `fold` returns the last value of type `B` returned by these successive +applications `func`. + +Applicable sequence types are `(list A)`, `buff`, `string-ascii` and `string-utf8`, +for which the corresponding element types are, respectively, `A`, `(buff 1)`, `(string-ascii 1)` and `(string-utf8 1)`. +The `func` argument must be a literal function name. +", + example: r#" +(fold * (list 2 2 2) 1) ;; Returns 8 (fold * (list 2 2 2) 0) ;; Returns 0 ;; calculates (- 11 (- 7 (- 3 2))) (fold - (list 3 7 11) 2) ;; Returns 5 (define-private (concat-string (a (string-ascii 20)) (b (string-ascii 20))) (unwrap-panic (as-max-len? (concat a b) u20))) -(fold concat-string \"cdef\" \"ab\") ;; Returns \"fedcab\" -(fold concat-string (list \"cd\" \"ef\") \"ab\") ;; Returns \"efcdab\"", +(fold concat-string "cdef" "ab") ;; Returns "fedcab" +(fold concat-string (list "cd" "ef") "ab") ;; Returns "efcdab" +(define-private (concat-buff (a (buff 20)) (b (buff 20))) (unwrap-panic (as-max-len? (concat a b) u20))) +(fold concat-buff 0x03040506 0x0102) ;; Returns 0x060504030102 +"#, }; const CONCAT_API: SpecialAPI = SpecialAPI { - input_type: "(buff, buff)|(list, list)", - output_type: "buff|list", - signature: "(concat buff-a buff-b)", - description: "The `concat` function takes two buffers or two lists with the same entry type, -and returns a concatenated buffer or list of the same entry type, with max_len = max_len_a + max_len_b.", - example: "(concat \"hello \" \"world\") ;; Returns \"hello world\"" + input_type: "sequence_A, sequence_A", + output_type: "sequence_A", + signature: "(concat sequence1 sequence2)", + description: "The `concat` function takes two sequences of the same type, +and returns a concatenated sequence of the same type, with the resulting +sequence_len = sequence1_len + sequence2_len. +Applicable sequence types are `(list A)`, `buff`, `string-ascii` and `string-utf8`. +", + example: r#" +(concat (list 1 2) (list 3 4)) ;; Returns (1 2 3 4) +(concat "hello " "world") ;; Returns "hello world" +(concat 0x0102 0x0304) ;; Returns 0x01020304 +"#, }; const APPEND_API: SpecialAPI = SpecialAPI { @@ -519,59 +558,72 @@ and outputs a list of the same type with max_len += 1.", }; const ASSERTS_MAX_LEN_API: SpecialAPI = SpecialAPI { - input_type: "buff|list, uint", - output_type: "(optional buff|list)", - signature: "(as-max-len? buffer u10)", - description: "The `as-max-len?` function takes a length N (must be a literal) and a buffer or list argument, which must be typed as a list -or buffer of length M and outputs that same list or buffer, but typed with max length N. - -This function returns an optional type with the resulting sequence. If the input sequence is less than -or equal to the supplied max-len, it returns `(some )`, otherwise it returns `none`.", - example: "(as-max-len? (list 2 2 2) u3) ;; Returns (some (2 2 2)) -(as-max-len? (list 1 2 3) u2) ;; Returns none" + input_type: "sequence_A, uint", + output_type: "sequence_A", + signature: "(as-max-len? sequence max_length)", + description: + "The `as-max-len?` function takes a sequence argument and a uint-valued, literal length argument. +The function returns an optional type. If the input sequence length is less than +or equal to the supplied max_length, this returns `(some sequence)`, otherwise it returns `none`. +Applicable sequence types are `(list A)`, `buff`, `string-ascii` and `string-utf8`. +", + example: r#" +(as-max-len? (list 2 2 2) u3) ;; Returns (some (2 2 2)) +(as-max-len? (list 1 2 3) u2) ;; Returns none +(as-max-len? "hello" u10) ;; Returns (some "hello") +(as-max-len? 0x010203 u10) ;; Returns (some 0x010203) +"#, }; const LEN_API: SpecialAPI = SpecialAPI { - input_type: "buff|list", + input_type: "sequence_A", output_type: "uint", - signature: "(len buffer)", - description: "The `len` function returns the length of a given buffer or list.", - example: "(len \"blockstack\") ;; Returns u10 + signature: "(len sequence)", + description: "The `len` function returns the length of a given sequence. +Applicable sequence types are `(list A)`, `buff`, `string-ascii` and `string-utf8`. + ", + example: r#" +(len "blockstack") ;; Returns u10 (len (list 1 2 3 4 5)) ;; Returns u5 -", +(len 0x010203) ;; Returns u3 +"#, }; const ELEMENT_AT_API: SpecialAPI = SpecialAPI { - input_type: "buff|list A, uint", - output_type: "(optional buff|A)", + input_type: "sequence_A, uint", + output_type: "(optional A)", signature: "(element-at sequence index)", - description: - "The `element-at` function returns the element at `index` in the provided sequence. -If `index` is greater than or equal to `(len sequence)`, this function returns `none`. -For strings and buffers, this function will return 1-length strings or buffers.", - example: "(element-at \"blockstack\" u5) ;; Returns (some \"s\") + description: "The `element-at` function returns the element at `index` in the provided sequence. +Applicable sequence types are `(list A)`, `buff`, `string-ascii` and `string-utf8`, +for which the corresponding element types are, respectively, `A`, `(buff 1)`, `(string-ascii 1)` and `(string-utf8 1)`. +", + example: r#" +(element-at "blockstack" u5) ;; Returns (some "s") (element-at (list 1 2 3 4 5) u5) ;; Returns none (element-at (list 1 2 3 4 5) (+ u1 u2)) ;; Returns (some 4) -(element-at \"abcd\" u1) ;; Returns (some \"b\") +(element-at "abcd" u1) ;; Returns (some "b") (element-at 0xfb01 u1) ;; Returns (some 0x01) -", +"#, }; const INDEX_OF_API: SpecialAPI = SpecialAPI { - input_type: "buff|list A, buff|A", + input_type: "sequence_A, A", output_type: "(optional uint)", signature: "(index-of sequence item)", description: "The `index-of` function returns the first index at which `item` can be -found in the provided sequence (using `is-eq` checks). - -If this item is not found in the sequence (or an empty string/buffer is supplied), this -function returns `none`.", - example: "(index-of \"blockstack\" \"b\") ;; Returns (some u0) -(index-of \"blockstack\" \"k\") ;; Returns (some u4) -(index-of \"blockstack\" \"\") ;; Returns none +found, using `is-eq` checks, in the provided sequence. +Applicable sequence types are `(list A)`, `buff`, `string-ascii` and `string-utf8`, +for which the corresponding element types are, respectively, `A`, `(buff 1)`, `(string-ascii 1)` and `(string-utf8 1)`. +If the target item is not found in the sequence (or if an empty string or buffer is +supplied), this function returns `none`. +", + example: r#" +(index-of "blockstack" "b") ;; Returns (some u0) +(index-of "blockstack" "k") ;; Returns (some u4) +(index-of "blockstack" "") ;; Returns none (index-of (list 1 2 3 4 5) 6) ;; Returns none (index-of 0xfb01 0x01) ;; Returns (some u1) -", +"#, }; const LIST_API: SpecialAPI = SpecialAPI { diff --git a/src/vm/functions/arithmetic.rs b/src/vm/functions/arithmetic.rs index 320c81ef7..0090ecbc6 100644 --- a/src/vm/functions/arithmetic.rs +++ b/src/vm/functions/arithmetic.rs @@ -201,7 +201,7 @@ macro_rules! make_arithmetic_ops { return Self::make_value(base); } - if power < 0 || power > (u32::max_value() as $type) { + if power < 0 || power > (u32::MAX as $type) { return Err(RuntimeErrorType::Arithmetic( "Power argument to (pow ...) must be a u32 integer".to_string(), ) diff --git a/src/vm/functions/crypto.rs b/src/vm/functions/crypto.rs index 0e78bfded..27737b043 100644 --- a/src/vm/functions/crypto.rs +++ b/src/vm/functions/crypto.rs @@ -80,11 +80,11 @@ pub fn special_principal_of( let pub_key = match param0 { Value::Sequence(SequenceData::Buffer(BuffData { ref data })) => { if data.len() != 33 { - return Err(CheckErrors::TypeValueError(BUFF_33, param0).into()); + return Err(CheckErrors::TypeValueError(BUFF_33.clone(), param0).into()); } data } - _ => return Err(CheckErrors::TypeValueError(BUFF_33, param0).into()), + _ => return Err(CheckErrors::TypeValueError(BUFF_33.clone(), param0).into()), }; if let Ok(pub_key) = Secp256k1PublicKey::from_slice(&pub_key) { @@ -117,25 +117,25 @@ pub fn special_secp256k1_recover( let message = match param0 { Value::Sequence(SequenceData::Buffer(BuffData { ref data })) => { if data.len() != 32 { - return Err(CheckErrors::TypeValueError(BUFF_32, param0).into()); + return Err(CheckErrors::TypeValueError(BUFF_32.clone(), param0).into()); } data } - _ => return Err(CheckErrors::TypeValueError(BUFF_32, param0).into()), + _ => return Err(CheckErrors::TypeValueError(BUFF_32.clone(), param0).into()), }; let param1 = eval(&args[1], env, context)?; let signature = match param1 { Value::Sequence(SequenceData::Buffer(BuffData { ref data })) => { if data.len() > 65 { - return Err(CheckErrors::TypeValueError(BUFF_65, param1).into()); + return Err(CheckErrors::TypeValueError(BUFF_65.clone(), param1).into()); } if data.len() < 65 || data[64] > 3 { return Ok(Value::err_uint(2)); } data } - _ => return Err(CheckErrors::TypeValueError(BUFF_65, param1).into()), + _ => return Err(CheckErrors::TypeValueError(BUFF_65.clone(), param1).into()), }; match secp256k1_recover(&message, &signature).map_err(|_| CheckErrors::InvalidSecp65k1Signature) @@ -160,18 +160,18 @@ pub fn special_secp256k1_verify( let message = match param0 { Value::Sequence(SequenceData::Buffer(BuffData { ref data })) => { if data.len() != 32 { - return Err(CheckErrors::TypeValueError(BUFF_32, param0).into()); + return Err(CheckErrors::TypeValueError(BUFF_32.clone(), param0).into()); } data } - _ => return Err(CheckErrors::TypeValueError(BUFF_32, param0).into()), + _ => return Err(CheckErrors::TypeValueError(BUFF_32.clone(), param0).into()), }; let param1 = eval(&args[1], env, context)?; let signature = match param1 { Value::Sequence(SequenceData::Buffer(BuffData { ref data })) => { if data.len() > 65 { - return Err(CheckErrors::TypeValueError(BUFF_65, param1).into()); + return Err(CheckErrors::TypeValueError(BUFF_65.clone(), param1).into()); } if data.len() < 64 { return Ok(Value::Bool(false)); @@ -181,18 +181,18 @@ pub fn special_secp256k1_verify( } data } - _ => return Err(CheckErrors::TypeValueError(BUFF_65, param1).into()), + _ => return Err(CheckErrors::TypeValueError(BUFF_65.clone(), param1).into()), }; let param2 = eval(&args[2], env, context)?; let pubkey = match param2 { Value::Sequence(SequenceData::Buffer(BuffData { ref data })) => { if data.len() != 33 { - return Err(CheckErrors::TypeValueError(BUFF_33, param2).into()); + return Err(CheckErrors::TypeValueError(BUFF_33.clone(), param2).into()); } data } - _ => return Err(CheckErrors::TypeValueError(BUFF_33, param2).into()), + _ => return Err(CheckErrors::TypeValueError(BUFF_33.clone(), param2).into()), }; Ok(Value::Bool( diff --git a/src/vm/mod.rs b/src/vm/mod.rs index 31daf888f..95ceb7f67 100644 --- a/src/vm/mod.rs +++ b/src/vm/mod.rs @@ -38,6 +38,8 @@ mod variables; pub mod analysis; pub mod docs; +pub mod coverage; + #[cfg(test)] pub mod tests; @@ -206,6 +208,10 @@ pub fn eval<'a>( Atom, AtomValue, Field, List, LiteralValue, TraitReference, }; + if let Some(ref mut coverage_tracker) = env.global_context.coverage_reporting { + coverage_tracker.report_eval(exp, &env.contract_context.contract_identifier); + } + match exp.expr { AtomValue(ref value) | LiteralValue(ref value) => Ok(value.clone()), Atom(ref value) => lookup_variable(&value, context, env), @@ -213,6 +219,14 @@ pub fn eval<'a>( let (function_variable, rest) = children .split_first() .ok_or(CheckErrors::NonFunctionApplication)?; + + if let Some(ref mut coverage_tracker) = env.global_context.coverage_reporting { + coverage_tracker.report_eval( + &function_variable, + &env.contract_context.contract_identifier, + ); + } + let function_name = function_variable .match_atom() .ok_or(CheckErrors::BadFunctionName)?; @@ -236,7 +250,7 @@ pub fn is_reserved(name: &str) -> bool { /* This function evaluates a list of expressions, sharing a global context. * It returns the final evaluated result. */ -fn eval_all( +pub fn eval_all( expressions: &[SymbolicExpression], contract_context: &mut ContractContext, global_context: &mut GlobalContext, diff --git a/src/vm/tests/assets.rs b/src/vm/tests/assets.rs index 8540e3f85..681706f9a 100644 --- a/src/vm/tests/assets.rs +++ b/src/vm/tests/assets.rs @@ -183,7 +183,7 @@ fn test_native_stx_ops(owned_env: &mut OwnedEnvironment) { .initialize_contract(second_contract_id.clone(), contract_second) .unwrap(); - owned_env.stx_faucet(&(p1_principal), u128::max_value() - 1500); + owned_env.stx_faucet(&(p1_principal), u128::MAX - 1500); owned_env.stx_faucet(&p2_principal, 1000); // test 1: send 0 diff --git a/src/vm/tests/simple_apply_eval.rs b/src/vm/tests/simple_apply_eval.rs index 13980b959..2a127d30f 100644 --- a/src/vm/tests/simple_apply_eval.rs +++ b/src/vm/tests/simple_apply_eval.rs @@ -27,7 +27,7 @@ use vm::contexts::OwnedEnvironment; use vm::costs::LimitedCostTracker; use vm::errors::{CheckErrors, Error, RuntimeErrorType, ShortReturnType}; use vm::tests::execute; -use vm::types::signatures::BufferLength; +use vm::types::signatures::*; use vm::types::{BuffData, QualifiedContractIdentifier, TypeSignature}; use vm::types::{PrincipalData, ResponseData, SequenceData, SequenceSubtype}; use vm::{eval, execute as vm_execute}; @@ -281,14 +281,14 @@ fn test_secp256k1_errors() { ]; let expectations: &[Error] = &[ - CheckErrors::TypeValueError(TypeSignature::SequenceType(SequenceSubtype::BufferType(BufferLength(32))), Value::Sequence(SequenceData::Buffer(BuffData { data: hex_bytes("de5b9eb9e7c5592930eb2e30a01369c36586d872082ed8181ee83d2a0ec20f").unwrap() }))).into(), - CheckErrors::TypeValueError(TypeSignature::SequenceType(SequenceSubtype::BufferType(BufferLength(65))), Value::Sequence(SequenceData::Buffer(BuffData { data: hex_bytes("8738487ebe69b93d8e51583be8eee50bb4213fc49c767d329632730cc193b873554428fc936ca3569afc15f1c9365f6591d6251a89fee9c9ac661116824d3a130100").unwrap() }))).into(), + CheckErrors::TypeValueError(BUFF_32.clone(), Value::Sequence(SequenceData::Buffer(BuffData { data: hex_bytes("de5b9eb9e7c5592930eb2e30a01369c36586d872082ed8181ee83d2a0ec20f").unwrap() }))).into(), + CheckErrors::TypeValueError(BUFF_65.clone(), Value::Sequence(SequenceData::Buffer(BuffData { data: hex_bytes("8738487ebe69b93d8e51583be8eee50bb4213fc49c767d329632730cc193b873554428fc936ca3569afc15f1c9365f6591d6251a89fee9c9ac661116824d3a130100").unwrap() }))).into(), CheckErrors::IncorrectArgumentCount(2, 1).into(), CheckErrors::IncorrectArgumentCount(2, 3).into(), - CheckErrors::TypeValueError(TypeSignature::SequenceType(SequenceSubtype::BufferType(BufferLength(32))), Value::Sequence(SequenceData::Buffer(BuffData { data: hex_bytes("de5b9eb9e7c5592930eb2e30a01369c36586d872082ed8181ee83d2a0ec20f").unwrap() }))).into(), - CheckErrors::TypeValueError(TypeSignature::SequenceType(SequenceSubtype::BufferType(BufferLength(65))), Value::Sequence(SequenceData::Buffer(BuffData { data: hex_bytes("8738487ebe69b93d8e51583be8eee50bb4213fc49c767d329632730cc193b873554428fc936ca3569afc15f1c9365f6591d6251a89fee9c9ac661116824d3a130111").unwrap() }))).into(), - CheckErrors::TypeValueError(TypeSignature::SequenceType(SequenceSubtype::BufferType(BufferLength(33))), Value::Sequence(SequenceData::Buffer(BuffData { data: hex_bytes("03adb8de4bfb65db2cfd6120d55c6526ae9c52e675db7e47308636534ba7").unwrap() }))).into(), + CheckErrors::TypeValueError(BUFF_32.clone(), Value::Sequence(SequenceData::Buffer(BuffData { data: hex_bytes("de5b9eb9e7c5592930eb2e30a01369c36586d872082ed8181ee83d2a0ec20f").unwrap() }))).into(), + CheckErrors::TypeValueError(BUFF_65.clone(), Value::Sequence(SequenceData::Buffer(BuffData { data: hex_bytes("8738487ebe69b93d8e51583be8eee50bb4213fc49c767d329632730cc193b873554428fc936ca3569afc15f1c9365f6591d6251a89fee9c9ac661116824d3a130111").unwrap() }))).into(), + CheckErrors::TypeValueError(BUFF_33.clone(), Value::Sequence(SequenceData::Buffer(BuffData { data: hex_bytes("03adb8de4bfb65db2cfd6120d55c6526ae9c52e675db7e47308636534ba7").unwrap() }))).into(), CheckErrors::IncorrectArgumentCount(3, 2).into(), CheckErrors::IncorrectArgumentCount(1, 2).into(), @@ -507,7 +507,7 @@ fn test_simple_arithmetic_functions() { Value::Bool(true), Value::Bool(true), Value::Int(65536), - Value::Int(u32::max_value() as i128 + 1), + Value::Int(u32::MAX as i128 + 1), Value::Int(1), Value::Int(170_141_183_460_469_231_731_687_303_715_884_105_727), Value::UInt(340_282_366_920_938_463_463_374_607_431_768_211_455), @@ -525,10 +525,10 @@ fn test_simple_arithmetic_functions() { Value::Int(3), Value::Int(126), Value::UInt(127), - Value::UInt(u128::max_value()), + Value::UInt(u128::MAX), Value::UInt(137), - Value::Int(i128::max_value()), - Value::Int(-1 * (u32::max_value() as i128 + 1)), + Value::Int(i128::MAX), + Value::Int(-1 * (u32::MAX as i128 + 1)), ]; tests diff --git a/src/vm/types/mod.rs b/src/vm/types/mod.rs index ea2f91e51..1261423ec 100644 --- a/src/vm/types/mod.rs +++ b/src/vm/types/mod.rs @@ -1356,9 +1356,9 @@ mod test { } // on 32-bit archs, this error cannot even happen, so don't test (and cause an overflow panic) - if (u32::max_value() as usize) < usize::max_value() { + if (u32::MAX as usize) < usize::MAX { assert_eq!( - Value::buff_from(vec![0; (u32::max_value() as usize) + 10]), + Value::buff_from(vec![0; (u32::MAX as usize) + 10]), Err(CheckErrors::ValueTooLarge.into()) ); } diff --git a/src/vm/types/serialization.rs b/src/vm/types/serialization.rs index fe5d582bc..9442b18ff 100644 --- a/src/vm/types/serialization.rs +++ b/src/vm/types/serialization.rs @@ -785,9 +785,9 @@ mod tests { Err(eres) => match eres { SerializationError::IOError(ioe) => match ioe.err.kind() { std::io::ErrorKind::UnexpectedEof => {} - _ => assert!(false, format!("Invalid I/O error: {:?}", &ioe)), + _ => assert!(false, "Invalid I/O error: {:?}", &ioe), }, - _ => assert!(false, format!("Invalid deserialize error: {:?}", &eres)), + _ => assert!(false, "Invalid deserialize error: {:?}", &eres), }, } } @@ -806,8 +806,8 @@ mod tests { test_deser_ser(Value::Int(0)); test_deser_ser(Value::Int(1)); test_deser_ser(Value::Int(-1)); - test_deser_ser(Value::Int(i128::max_value())); - test_deser_ser(Value::Int(i128::min_value())); + test_deser_ser(Value::Int(i128::MAX)); + test_deser_ser(Value::Int(i128::MIN)); test_bad_expectation(Value::Int(1), TypeSignature::UIntType); } @@ -816,8 +816,8 @@ mod tests { fn test_uints() { test_deser_ser(Value::UInt(0)); test_deser_ser(Value::UInt(1)); - test_deser_ser(Value::UInt(u128::max_value())); - test_deser_ser(Value::UInt(u128::min_value())); + test_deser_ser(Value::UInt(u128::MAX)); + test_deser_ser(Value::UInt(u128::MIN)); test_bad_expectation(Value::UInt(1), TypeSignature::IntType); } diff --git a/src/vm/types/signatures.rs b/src/vm/types/signatures.rs index ae6f56199..5cd67fcd7 100644 --- a/src/vm/types/signatures.rs +++ b/src/vm/types/signatures.rs @@ -68,7 +68,7 @@ pub struct TupleTypeSignature { } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct BufferLength(pub u32); +pub struct BufferLength(u32); #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct StringUTF8Length(u32); @@ -125,12 +125,29 @@ use self::TypeSignature::{ TraitReferenceType, TupleType, UIntType, }; -pub const BUFF_64: TypeSignature = SequenceType(SequenceSubtype::BufferType(BufferLength(64))); -pub const BUFF_65: TypeSignature = SequenceType(SequenceSubtype::BufferType(BufferLength(65))); -pub const BUFF_32: TypeSignature = SequenceType(SequenceSubtype::BufferType(BufferLength(32))); -pub const BUFF_33: TypeSignature = SequenceType(SequenceSubtype::BufferType(BufferLength(33))); -pub const BUFF_20: TypeSignature = SequenceType(SequenceSubtype::BufferType(BufferLength(20))); -pub const BUFF_1: TypeSignature = SequenceType(SequenceSubtype::BufferType(BufferLength(1))); +lazy_static! { + pub static ref BUFF_64: TypeSignature = SequenceType(SequenceSubtype::BufferType( + BufferLength::try_from(64u32).expect("BUG: Legal Clarity buffer length marked invalid") + )); + pub static ref BUFF_65: TypeSignature = SequenceType(SequenceSubtype::BufferType( + BufferLength::try_from(65u32).expect("BUG: Legal Clarity buffer length marked invalid") + )); + pub static ref BUFF_32: TypeSignature = SequenceType(SequenceSubtype::BufferType( + BufferLength::try_from(32u32).expect("BUG: Legal Clarity buffer length marked invalid") + )); + pub static ref BUFF_33: TypeSignature = SequenceType(SequenceSubtype::BufferType( + BufferLength::try_from(33u32).expect("BUG: Legal Clarity buffer length marked invalid") + )); + pub static ref BUFF_20: TypeSignature = SequenceType(SequenceSubtype::BufferType( + BufferLength::try_from(20u32).expect("BUG: Legal Clarity buffer length marked invalid") + )); + pub static ref BUFF_1: TypeSignature = SequenceType(SequenceSubtype::BufferType( + BufferLength::try_from(1u32).expect("BUG: Legal Clarity buffer length marked invalid") + )); + pub static ref BUFF_16: TypeSignature = SequenceType(SequenceSubtype::BufferType( + BufferLength::try_from(16u32).expect("BUG: Legal Clarity buffer length marked invalid") + )); +} #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ListTypeData { @@ -638,10 +655,10 @@ impl TypeSignature { } pub fn max_buffer() -> TypeSignature { - SequenceType(SequenceSubtype::BufferType(BufferLength( - u32::try_from(MAX_VALUE_SIZE) + SequenceType(SequenceSubtype::BufferType( + BufferLength::try_from(MAX_VALUE_SIZE) .expect("FAIL: Max Clarity Value Size is no longer realizable in Buffer Type"), - ))) + )) } /// If one of the types is a NoType, return Ok(the other type), otherwise return least_supertype(a, b) diff --git a/testnet/stacks-node/conf/regtest-follower-conf.toml b/testnet/stacks-node/conf/regtest-follower-conf.toml index 628e56e47..a2a71c8ac 100644 --- a/testnet/stacks-node/conf/regtest-follower-conf.toml +++ b/testnet/stacks-node/conf/regtest-follower-conf.toml @@ -21,17 +21,17 @@ peer_port = 18444 # events_keys = ["*"] [[ustx_balance]] -address = "STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6" +address = "ST2QKZ4FKHAH1NQKYKYAYZPY440FEPK7GZ1R5HBP2" amount = 10000000000000000 [[ustx_balance]] -address = "ST11NJTTKGVT6D1HY4NJRVQWMQM7TVAR091EJ8P2Y" +address = "ST319CF5WV77KYR1H3GT0GZ7B8Q4AQPY42ETP1VPF" amount = 10000000000000000 [[ustx_balance]] -address = "ST1HB1T8WRNBYB0Y3T7WXZS38NKKPTBR3EG9EPJKR" +address = "ST221Z6TDTC5E0BYR2V624Q2ST6R0Q71T78WTAX6H" amount = 10000000000000000 [[ustx_balance]] -address = "STRYYQQ9M8KAF4NS7WNZQYY59X93XEKR31JP64CP" +address = "ST2TFVBMRPS5SSNP98DQKQ5JNB2B6NZM91C4K3P7B" amount = 10000000000000000 diff --git a/testnet/stacks-node/conf/testnet-follower-conf.toml b/testnet/stacks-node/conf/testnet-follower-conf.toml index f8c3fd4ba..e1563d333 100644 --- a/testnet/stacks-node/conf/testnet-follower-conf.toml +++ b/testnet/stacks-node/conf/testnet-follower-conf.toml @@ -21,17 +21,17 @@ peer_port = 18333 # events_keys = ["*"] [[ustx_balance]] -address = "STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6" +address = "ST2QKZ4FKHAH1NQKYKYAYZPY440FEPK7GZ1R5HBP2" amount = 10000000000000000 [[ustx_balance]] -address = "ST11NJTTKGVT6D1HY4NJRVQWMQM7TVAR091EJ8P2Y" +address = "ST319CF5WV77KYR1H3GT0GZ7B8Q4AQPY42ETP1VPF" amount = 10000000000000000 [[ustx_balance]] -address = "ST1HB1T8WRNBYB0Y3T7WXZS38NKKPTBR3EG9EPJKR" +address = "ST221Z6TDTC5E0BYR2V624Q2ST6R0Q71T78WTAX6H" amount = 10000000000000000 [[ustx_balance]] -address = "STRYYQQ9M8KAF4NS7WNZQYY59X93XEKR31JP64CP" +address = "ST2TFVBMRPS5SSNP98DQKQ5JNB2B6NZM91C4K3P7B" amount = 10000000000000000 diff --git a/testnet/stacks-node/conf/testnet-miner-conf.toml b/testnet/stacks-node/conf/testnet-miner-conf.toml index 1cbcf5db0..3b1b0013e 100644 --- a/testnet/stacks-node/conf/testnet-miner-conf.toml +++ b/testnet/stacks-node/conf/testnet-miner-conf.toml @@ -18,17 +18,17 @@ rpc_port = 18332 peer_port = 18333 [[ustx_balance]] -address = "STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6" +address = "ST2QKZ4FKHAH1NQKYKYAYZPY440FEPK7GZ1R5HBP2" amount = 10000000000000000 [[ustx_balance]] -address = "ST11NJTTKGVT6D1HY4NJRVQWMQM7TVAR091EJ8P2Y" +address = "ST319CF5WV77KYR1H3GT0GZ7B8Q4AQPY42ETP1VPF" amount = 10000000000000000 [[ustx_balance]] -address = "ST1HB1T8WRNBYB0Y3T7WXZS38NKKPTBR3EG9EPJKR" +address = "ST221Z6TDTC5E0BYR2V624Q2ST6R0Q71T78WTAX6H" amount = 10000000000000000 [[ustx_balance]] -address = "STRYYQQ9M8KAF4NS7WNZQYY59X93XEKR31JP64CP" +address = "ST2TFVBMRPS5SSNP98DQKQ5JNB2B6NZM91C4K3P7B" amount = 10000000000000000 diff --git a/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs b/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs index 5eaf4a26b..c2923312d 100644 --- a/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs +++ b/testnet/stacks-node/src/burnchains/bitcoin_regtest_controller.rs @@ -1043,18 +1043,6 @@ impl BitcoinRegtestController { } } - // Stop as soon as the fee_rate is ${self.config.burnchain.max_rbf} percent higher, stop RBF - if ongoing_op.fees.fee_rate - > (self.config.burnchain.satoshis_per_byte * self.config.burnchain.max_rbf / 100) - { - warn!( - "RBF'd block commits reached {}% satoshi per byte fee rate, not resubmitting", - self.config.burnchain.max_rbf - ); - self.ongoing_block_commit = Some(ongoing_op); - return None; - } - // Did a re-org occurred since we fetched our UTXOs, or are the UTXOs so stale that they should be abandoned? let mut traversal_depth = 0; let mut burn_chain_tip = burnchain_db.get_canonical_chain_tip().ok()?; @@ -1081,6 +1069,18 @@ impl BitcoinRegtestController { return res; } + // Stop as soon as the fee_rate is ${self.config.burnchain.max_rbf} percent higher, stop RBF + if ongoing_op.fees.fee_rate + > (self.config.burnchain.satoshis_per_byte * self.config.burnchain.max_rbf / 100) + { + warn!( + "RBF'd block commits reached {}% satoshi per byte fee rate, not resubmitting", + self.config.burnchain.max_rbf + ); + self.ongoing_block_commit = Some(ongoing_op); + return None; + } + // An ongoing operation is in the mempool and we received a new block. The desired behaviour is the following: // 1) If the ongoing and the incoming operation are **strictly** identical, we will be idempotent and discard the incoming. // 2) If the 2 operations are different, we will try to avoid wasting UTXOs, and attempt to RBF the outgoing transaction: diff --git a/testnet/stacks-node/src/config.rs b/testnet/stacks-node/src/config.rs index aeee97eb3..0979c1b42 100644 --- a/testnet/stacks-node/src/config.rs +++ b/testnet/stacks-node/src/config.rs @@ -50,19 +50,19 @@ mod tests { let config = ConfigFile::from_str( r#" [[ustx_balance]] - address = "STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6" + address = "ST2QKZ4FKHAH1NQKYKYAYZPY440FEPK7GZ1R5HBP2" amount = 10000000000000000 [[ustx_balance]] - address = "ST11NJTTKGVT6D1HY4NJRVQWMQM7TVAR091EJ8P2Y" + address = "ST319CF5WV77KYR1H3GT0GZ7B8Q4AQPY42ETP1VPF" amount = 10000000000000000 [[mstx_balance]] # legacy property name - address = "ST1HB1T8WRNBYB0Y3T7WXZS38NKKPTBR3EG9EPJKR" + address = "ST221Z6TDTC5E0BYR2V624Q2ST6R0Q71T78WTAX6H" amount = 10000000000000000 [[mstx_balance]] # legacy property name - address = "STRYYQQ9M8KAF4NS7WNZQYY59X93XEKR31JP64CP" + address = "ST2TFVBMRPS5SSNP98DQKQ5JNB2B6NZM91C4K3P7B" amount = 10000000000000000 "#, ); @@ -73,19 +73,19 @@ mod tests { assert_eq!(balances.len(), 4); assert_eq!( balances[0].address, - "STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6" + "ST2QKZ4FKHAH1NQKYKYAYZPY440FEPK7GZ1R5HBP2" ); assert_eq!( balances[1].address, - "ST11NJTTKGVT6D1HY4NJRVQWMQM7TVAR091EJ8P2Y" + "ST319CF5WV77KYR1H3GT0GZ7B8Q4AQPY42ETP1VPF" ); assert_eq!( balances[2].address, - "ST1HB1T8WRNBYB0Y3T7WXZS38NKKPTBR3EG9EPJKR" + "ST221Z6TDTC5E0BYR2V624Q2ST6R0Q71T78WTAX6H" ); assert_eq!( balances[3].address, - "STRYYQQ9M8KAF4NS7WNZQYY59X93XEKR31JP64CP" + "ST2TFVBMRPS5SSNP98DQKQ5JNB2B6NZM91C4K3P7B" ); } } @@ -109,141 +109,13 @@ impl ConfigFile { config } - pub fn neon() -> ConfigFile { - let burnchain = BurnchainConfigFile { - mode: Some("neon".to_string()), - rpc_port: Some(18443), - peer_port: Some(18444), - peer_host: Some("neon.blockstack.org".to_string()), - ..BurnchainConfigFile::default() - }; - - let node = NodeConfigFile { - bootstrap_node: Some("038dd4f26101715853533dee005f0915375854fd5be73405f679c1917a5d4d16aa@neon.blockstack.org:20444".to_string()), - miner: Some(false), - ..NodeConfigFile::default() - }; - - let balances = vec![ - InitialBalanceFile { - address: "STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6".to_string(), - amount: 10000000000000000, - }, - InitialBalanceFile { - address: "ST11NJTTKGVT6D1HY4NJRVQWMQM7TVAR091EJ8P2Y".to_string(), - amount: 10000000000000000, - }, - InitialBalanceFile { - address: "ST1HB1T8WRNBYB0Y3T7WXZS38NKKPTBR3EG9EPJKR".to_string(), - amount: 10000000000000000, - }, - InitialBalanceFile { - address: "STRYYQQ9M8KAF4NS7WNZQYY59X93XEKR31JP64CP".to_string(), - amount: 10000000000000000, - }, - ]; - - ConfigFile { - burnchain: Some(burnchain), - node: Some(node), - ustx_balance: Some(balances), - ..ConfigFile::default() - } - } - - pub fn argon() -> ConfigFile { - let burnchain = BurnchainConfigFile { - mode: Some("argon".to_string()), - rpc_port: Some(18443), - peer_port: Some(18444), - peer_host: Some("argon.blockstack.org".to_string()), - process_exit_at_block_height: Some(28160), // 1 block every 30s, 24 hours * 8 + 300 blocks initially mined for seeding faucet / miner - ..BurnchainConfigFile::default() - }; - - let node = NodeConfigFile { - bootstrap_node: Some("048dd4f26101715853533dee005f0915375854fd5be73405f679c1917a5d4d16aaaf3c4c0d7a9c132a36b8c5fe1287f07dad8c910174d789eb24bdfb5ae26f5f27@argon.blockstack.org:20444".to_string()), - miner: Some(false), - ..NodeConfigFile::default() - }; - - let balances = vec![ - InitialBalanceFile { - address: "STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6".to_string(), - amount: 10000000000000000, - }, - InitialBalanceFile { - address: "ST11NJTTKGVT6D1HY4NJRVQWMQM7TVAR091EJ8P2Y".to_string(), - amount: 10000000000000000, - }, - InitialBalanceFile { - address: "ST1HB1T8WRNBYB0Y3T7WXZS38NKKPTBR3EG9EPJKR".to_string(), - amount: 10000000000000000, - }, - InitialBalanceFile { - address: "STRYYQQ9M8KAF4NS7WNZQYY59X93XEKR31JP64CP".to_string(), - amount: 10000000000000000, - }, - ]; - - ConfigFile { - burnchain: Some(burnchain), - node: Some(node), - ustx_balance: Some(balances), - ..ConfigFile::default() - } - } - - pub fn krypton() -> ConfigFile { - let burnchain = BurnchainConfigFile { - mode: Some("krypton".to_string()), - rpc_port: Some(18443), - peer_port: Some(18444), - peer_host: Some("bitcoind.krypton.blockstack.org".to_string()), - process_exit_at_block_height: Some(5130), // 1 block every 2m, 24 hours * 7 + 300 blocks initially mined for seeding faucet / miner - ..BurnchainConfigFile::default() - }; - - let node = NodeConfigFile { - bootstrap_node: Some("048dd4f26101715853533dee005f0915375854fd5be73405f679c1917a5d4d16aaaf3c4c0d7a9c132a36b8c5fe1287f07dad8c910174d789eb24bdfb5ae26f5f27@krypton.blockstack.org:20444".to_string()), - miner: Some(false), - ..NodeConfigFile::default() - }; - - let balances = vec![ - InitialBalanceFile { - address: "STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6".to_string(), - amount: 10000000000000000, - }, - InitialBalanceFile { - address: "ST11NJTTKGVT6D1HY4NJRVQWMQM7TVAR091EJ8P2Y".to_string(), - amount: 10000000000000000, - }, - InitialBalanceFile { - address: "ST1HB1T8WRNBYB0Y3T7WXZS38NKKPTBR3EG9EPJKR".to_string(), - amount: 10000000000000000, - }, - InitialBalanceFile { - address: "STRYYQQ9M8KAF4NS7WNZQYY59X93XEKR31JP64CP".to_string(), - amount: 10000000000000000, - }, - ]; - - ConfigFile { - burnchain: Some(burnchain), - node: Some(node), - ustx_balance: Some(balances), - ..ConfigFile::default() - } - } - pub fn xenon() -> ConfigFile { let burnchain = BurnchainConfigFile { mode: Some("xenon".to_string()), rpc_port: Some(18332), peer_port: Some(18333), peer_host: Some("bitcoind.xenon.blockstack.org".to_string()), - magic_bytes: Some("X6".into()), + magic_bytes: Some("T2".into()), ..BurnchainConfigFile::default() }; @@ -255,19 +127,19 @@ impl ConfigFile { let balances = vec![ InitialBalanceFile { - address: "STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6".to_string(), + address: "ST2QKZ4FKHAH1NQKYKYAYZPY440FEPK7GZ1R5HBP2".to_string(), amount: 10000000000000000, }, InitialBalanceFile { - address: "ST11NJTTKGVT6D1HY4NJRVQWMQM7TVAR091EJ8P2Y".to_string(), + address: "ST319CF5WV77KYR1H3GT0GZ7B8Q4AQPY42ETP1VPF".to_string(), amount: 10000000000000000, }, InitialBalanceFile { - address: "ST1HB1T8WRNBYB0Y3T7WXZS38NKKPTBR3EG9EPJKR".to_string(), + address: "ST221Z6TDTC5E0BYR2V624Q2ST6R0Q71T78WTAX6H".to_string(), amount: 10000000000000000, }, InitialBalanceFile { - address: "STRYYQQ9M8KAF4NS7WNZQYY59X93XEKR31JP64CP".to_string(), + address: "ST2TFVBMRPS5SSNP98DQKQ5JNB2B6NZM91C4K3P7B".to_string(), amount: 10000000000000000, }, ]; diff --git a/testnet/stacks-node/src/event_dispatcher.rs b/testnet/stacks-node/src/event_dispatcher.rs index c85bacc78..2a4656c92 100644 --- a/testnet/stacks-node/src/event_dispatcher.rs +++ b/testnet/stacks-node/src/event_dispatcher.rs @@ -217,7 +217,7 @@ impl EventObserver { } } - /// Returns json payload to send for new block event + /// Returns json payload to send for new block or microblock event fn make_new_block_txs_payload( receipt: &StacksTransactionReceipt, tx_index: u32, @@ -232,26 +232,9 @@ impl EventObserver { "raw_tx": format!("0x{}", &receipt_payload_info.raw_tx), "contract_abi": receipt_payload_info.contract_interface_json, "execution_cost": receipt.execution_cost, - }) - } - - /// Returns json payload to send for new microblock event - fn make_new_microblock_txs_payload( - receipt: &StacksTransactionReceipt, - tx_index: u32, - sequence: u16, - ) -> serde_json::Value { - let receipt_payload_info = EventObserver::generate_payload_info_for_receipt(receipt); - - json!({ - "txid": format!("0x{}", &receipt_payload_info.txid), - "tx_index": tx_index, - "status": receipt_payload_info.success, - "raw_result": format!("0x{}", &receipt_payload_info.raw_result), - "raw_tx": format!("0x{}", &receipt_payload_info.raw_tx), - "contract_abi": receipt_payload_info.contract_interface_json, - "execution_cost": receipt.execution_cost, - "sequence": sequence, + "microblock_sequence": receipt.microblock_header.as_ref().map(|x| x.sequence), + "microblock_hash": receipt.microblock_header.as_ref().map(|x| format!("0x{}", x.block_hash())), + "microblock_parent_hash": receipt.microblock_header.as_ref().map(|x| format!("0x{}", x.prev_block)), }) } @@ -284,6 +267,9 @@ impl EventObserver { parent_index_block_hash: StacksBlockId, filtered_events: Vec<(usize, &(bool, Txid, &StacksTransactionEvent))>, serialized_txs: &Vec, + burn_block_hash: BurnchainHeaderHash, + burn_block_height: u32, + burn_block_timestamp: u64, ) { // Serialize events to JSON let serialized_events: Vec = filtered_events @@ -297,6 +283,9 @@ impl EventObserver { "parent_index_block_hash": format!("0x{}", parent_index_block_hash), "events": serialized_events, "transactions": serialized_txs, + "burn_block_hash": format!("0x{}", burn_block_hash), + "burn_block_height": burn_block_height, + "burn_block_timestamp": burn_block_timestamp, }); self.send_payload(&payload, PATH_MICROBLOCK_SUBMIT); @@ -318,6 +307,9 @@ impl EventObserver { boot_receipts: &Vec, winner_txid: &Txid, mature_rewards: &serde_json::Value, + parent_burn_block_hash: BurnchainHeaderHash, + parent_burn_block_height: u32, + parent_burn_block_timestamp: u64, ) { // Serialize events to JSON let serialized_events: Vec = filtered_events @@ -348,9 +340,13 @@ impl EventObserver { "parent_block_hash": format!("0x{}", chain_tip.block.header.parent_block), "parent_index_block_hash": format!("0x{}", parent_index_hash), "parent_microblock": format!("0x{}", chain_tip.block.header.parent_microblock), + "parent_microblock_sequence": chain_tip.block.header.parent_microblock_sequence, "matured_miner_rewards": mature_rewards.clone(), "events": serialized_events, "transactions": serialized_txs, + "parent_burn_block_hash": format!("0x{}", parent_burn_block_hash), + "parent_burn_block_height": parent_burn_block_height, + "parent_burn_block_timestamp": parent_burn_block_timestamp, }); // Send payload @@ -389,6 +385,9 @@ impl BlockEventDispatcher for EventDispatcher { winner_txid: Txid, mature_rewards: Vec, mature_rewards_info: Option, + parent_burn_block_hash: BurnchainHeaderHash, + parent_burn_block_height: u32, + parent_burn_block_timestamp: u64, ) { let chain_tip = ChainTip { metadata, @@ -401,6 +400,9 @@ impl BlockEventDispatcher for EventDispatcher { winner_txid, mature_rewards, mature_rewards_info, + parent_burn_block_hash, + parent_burn_block_height, + parent_burn_block_timestamp, ) } @@ -582,6 +584,9 @@ impl EventDispatcher { winner_txid: Txid, mature_rewards: Vec, mature_rewards_info: Option, + parent_burn_block_hash: BurnchainHeaderHash, + parent_burn_block_height: u32, + parent_burn_block_timestamp: u64, ) { let boot_receipts = if chain_tip.metadata.block_height == 1 { let mut boot_receipts_result = self @@ -641,6 +646,9 @@ impl EventDispatcher { &boot_receipts, &winner_txid, &mature_rewards, + parent_burn_block_hash, + parent_burn_block_height, + parent_burn_block_timestamp, ); } } @@ -670,7 +678,7 @@ impl EventDispatcher { let flattened_receipts = processed_unconfirmed_state .receipts .iter() - .flat_map(|(_, r)| r.clone()) + .flat_map(|(_, _, r)| r.clone()) .collect(); let (dispatch_matrix, events) = self.create_dispatch_matrix_and_event_vector(&flattened_receipts); @@ -679,14 +687,10 @@ impl EventDispatcher { let mut tx_index; let mut serialized_txs = Vec::new(); - for (curr_sequence_number, receipts) in processed_unconfirmed_state.receipts.iter() { + for (_, _, receipts) in processed_unconfirmed_state.receipts.iter() { tx_index = 0; for receipt in receipts.iter() { - let payload = EventObserver::make_new_microblock_txs_payload( - receipt, - tx_index, - *curr_sequence_number, - ); + let payload = EventObserver::make_new_block_txs_payload(receipt, tx_index); serialized_txs.push(payload); tx_index += 1; } @@ -703,6 +707,9 @@ impl EventDispatcher { parent_index_block_hash, filtered_events, &serialized_txs, + processed_unconfirmed_state.burn_block_hash, + processed_unconfirmed_state.burn_block_height, + processed_unconfirmed_state.burn_block_timestamp, ); } } diff --git a/testnet/stacks-node/src/main.rs b/testnet/stacks-node/src/main.rs index 376f31260..3c1a8583d 100644 --- a/testnet/stacks-node/src/main.rs +++ b/testnet/stacks-node/src/main.rs @@ -100,19 +100,7 @@ fn main() { args.finish().unwrap(); ConfigFile::helium() } - "neon" => { - args.finish().unwrap(); - ConfigFile::neon() - } - "argon" => { - args.finish().unwrap(); - ConfigFile::argon() - } - "krypton" => { - args.finish().unwrap(); - ConfigFile::krypton() - } - "xenon" => { + "testnet" => { args.finish().unwrap(); ConfigFile::xenon() } @@ -223,7 +211,7 @@ helium\t\tStart a node based on a local setup relying on a local instance of bit \t\t rpcuser=helium \t\t rpcpassword=helium -xenon\t\tStart a node that will join and stream blocks from the public xenon testnet, decentralized. +testnet\t\tStart a node that will join and stream blocks from the public testnet, relying on Bitcoin Testnet. start\t\tStart a node with a config of your own. Can be used for joining a network, starting new chain, etc. \t\tArguments: diff --git a/testnet/stacks-node/src/neon_node.rs b/testnet/stacks-node/src/neon_node.rs index c8ea488ee..549f79142 100644 --- a/testnet/stacks-node/src/neon_node.rs +++ b/testnet/stacks-node/src/neon_node.rs @@ -11,7 +11,6 @@ use std::sync::{ }; use std::{thread, thread::JoinHandle}; -use stacks::burnchains::BurnchainSigner; use stacks::burnchains::{Burnchain, BurnchainParameters, Txid}; use stacks::chainstate::burn::db::sortdb::SortitionDB; use stacks::chainstate::burn::operations::{ @@ -57,6 +56,7 @@ use stacks::util::sleep_ms; use stacks::util::strings::{UrlString, VecDisplay}; use stacks::util::vrf::VRFPublicKey; use stacks::vm::costs::ExecutionCost; +use stacks::{burnchains::BurnchainSigner, chainstate::stacks::db::StacksHeaderInfo}; use crate::burnchains::bitcoin_regtest_controller::BitcoinRegtestController; use crate::run_loop::RegisteredKey; @@ -136,6 +136,25 @@ fn set_processed_counter(blocks_processed: &BlocksProcessedCounter, value: u64) #[cfg(not(test))] fn set_processed_counter(_blocks_processed: &BlocksProcessedCounter, _value: u64) {} +enum Error { + HeaderNotFoundForChainTip, + WinningVtxNotFoundForChainTip, + SnapshotNotFoundForChainTip, + BurnchainTipChanged, +} + +struct MiningTenureInformation { + stacks_parent_header: StacksHeaderInfo, + /// the consensus hash of the sortition that selected the Stacks block parent + parent_consensus_hash: ConsensusHash, + /// the burn block height of the sortition that selected the Stacks block parent + parent_block_burn_height: u64, + /// the total amount burned in the sortition that selected the Stacks block parent + parent_block_total_burn: u64, + parent_winning_vtxindex: u16, + coinbase_nonce: u64, +} + /// Process artifacts from the tenure. /// At this point, we're modifying the chainstate, and merging the artifacts from the previous tenure. fn inner_process_tenure( @@ -480,6 +499,7 @@ fn run_microblock_tenure( relayer: &mut Relayer, miner_tip: (ConsensusHash, BlockHeaderHash, Secp256k1PrivateKey), microblocks_processed: BlocksProcessedCounter, + event_dispatcher: &EventDispatcher, ) { // TODO: this is sensitive to poll latency -- can we call this on a fixed // schedule, regardless of network activity? @@ -512,7 +532,7 @@ fn run_microblock_tenure( // apply it let microblock_hash = next_microblock.block_hash(); - Relayer::refresh_unconfirmed(chainstate, sortdb); + let processed_unconfirmed_state = Relayer::refresh_unconfirmed(chainstate, sortdb); let num_mblocks = chainstate .unconfirmed_state .as_ref() @@ -525,6 +545,11 @@ fn run_microblock_tenure( ); set_processed_counter(µblocks_processed, num_mblocks); + let parent_index_block_hash = + StacksBlockHeader::make_index_block_hash(parent_consensus_hash, parent_block_hash); + event_dispatcher + .process_new_microblocks(parent_index_block_hash, processed_unconfirmed_state); + // send it off if let Err(e) = relayer.broadcast_microblock(parent_consensus_hash, parent_block_hash, next_microblock) @@ -647,7 +672,7 @@ fn spawn_peer( download_backpressure, this.has_more_downloads() ); - 100 + 1 } else { cmp::min(poll_timeout, config.node.microblock_frequency) }; @@ -1056,7 +1081,8 @@ fn spawn_miner_relayer( &mem_pool, &mut relayer, (ch, bh, mblock_pkey), - microblocks_processed.clone() + microblocks_processed.clone(), + &event_dispatcher ); // synchronize unconfirmed tx index to p2p thread @@ -1398,6 +1424,101 @@ impl InitializedNeonNode { true } + fn get_mining_tenure_information( + chain_state: &mut StacksChainState, + burn_db: &mut SortitionDB, + check_burn_block: &BlockSnapshot, + miner_address: StacksAddress, + mine_tip_ch: &ConsensusHash, + mine_tip_bh: &BlockHeaderHash, + ) -> Result { + let stacks_tip_header = StacksChainState::get_anchored_block_header_info( + chain_state.db(), + &mine_tip_ch, + &mine_tip_bh, + ) + .unwrap() + .ok_or_else(|| { + error!( + "Could not mine new tenure, since could not find header for known chain tip."; + "tip_consensus_hash" => %mine_tip_ch, + "tip_stacks_block_hash" => %mine_tip_bh + ); + Error::HeaderNotFoundForChainTip + })?; + + // the stacks block I'm mining off of's burn header hash and vtxindex: + let parent_snapshot = + SortitionDB::get_block_snapshot_consensus(burn_db.conn(), mine_tip_ch) + .expect("Failed to look up block's parent snapshot") + .expect("Failed to look up block's parent snapshot"); + + let parent_sortition_id = &parent_snapshot.sortition_id; + let parent_winning_vtxindex = + SortitionDB::get_block_winning_vtxindex(burn_db.conn(), parent_sortition_id) + .expect("SortitionDB failure.") + .ok_or_else(|| { + error!( + "Failed to find winning vtx index for the parent sortition"; + "parent_sortition_id" => %parent_sortition_id + ); + Error::WinningVtxNotFoundForChainTip + })?; + + let parent_block = SortitionDB::get_block_snapshot(burn_db.conn(), parent_sortition_id) + .expect("SortitionDB failure.") + .ok_or_else(|| { + error!( + "Failed to find block snapshot for the parent sortition"; + "parent_sortition_id" => %parent_sortition_id + ); + Error::SnapshotNotFoundForChainTip + })?; + + // don't mine off of an old burnchain block + let burn_chain_tip = SortitionDB::get_canonical_burn_chain_tip(burn_db.conn()) + .expect("FATAL: failed to query sortition DB for canonical burn chain tip"); + + if burn_chain_tip.consensus_hash != check_burn_block.consensus_hash { + info!( + "New canonical burn chain tip detected. Will not try to mine."; + "new_consensus_hash" => %burn_chain_tip.consensus_hash, + "old_consensus_hash" => %check_burn_block.consensus_hash, + "new_burn_height" => burn_chain_tip.block_height, + "old_burn_height" => check_burn_block.block_height + ); + return Err(Error::BurnchainTipChanged); + } + + debug!("Mining tenure's last consensus hash: {} (height {} hash {}), stacks tip consensus hash: {} (height {} hash {})", + &check_burn_block.consensus_hash, check_burn_block.block_height, &check_burn_block.burn_header_hash, + mine_tip_ch, parent_snapshot.block_height, &parent_snapshot.burn_header_hash); + + let coinbase_nonce = { + let principal = miner_address.into(); + let account = chain_state + .with_read_only_clarity_tx( + &burn_db.index_conn(), + &StacksBlockHeader::make_index_block_hash(mine_tip_ch, mine_tip_bh), + |conn| StacksChainState::get_account(conn, &principal), + ) + .expect(&format!( + "BUG: stacks tip block {}/{} no longer exists after we queried it", + mine_tip_ch, mine_tip_bh + )); + account.nonce + }; + + Ok(MiningTenureInformation { + stacks_parent_header: stacks_tip_header, + parent_consensus_hash: mine_tip_ch.clone(), + parent_block_burn_height: parent_block.block_height, + parent_block_total_burn: parent_block.total_burn, + parent_winning_vtxindex, + coinbase_nonce, + }) + } + // return stack's parent's burn header hash, // the anchored block, // the burn header hash of the burnchain tip @@ -1415,107 +1536,24 @@ impl InitializedNeonNode { last_mined_blocks: &Vec<&AssembledAnchorBlock>, event_observer: &EventDispatcher, ) -> Option<(AssembledAnchorBlock, Secp256k1PrivateKey)> { - let ( + let MiningTenureInformation { mut stacks_parent_header, parent_consensus_hash, parent_block_burn_height, parent_block_total_burn, parent_winning_vtxindex, coinbase_nonce, - ) = if let Some(stacks_tip) = chain_state.get_stacks_chain_tip(burn_db).unwrap() { - let stacks_tip_header = match StacksChainState::get_anchored_block_header_info( - chain_state.db(), + } = if let Some(stacks_tip) = chain_state.get_stacks_chain_tip(burn_db).unwrap() { + let miner_address = keychain.origin_address(config.is_mainnet()).unwrap(); + Self::get_mining_tenure_information( + chain_state, + burn_db, + &burn_block, + miner_address, &stacks_tip.consensus_hash, &stacks_tip.anchored_block_hash, ) - .unwrap() - { - Some(x) => x, - None => { - error!("Could not mine new tenure, since could not find header for known chain tip."); - return None; - } - }; - - // the consensus hash of my Stacks block parent - let parent_consensus_hash = stacks_tip.consensus_hash.clone(); - - // the stacks block I'm mining off of's burn header hash and vtxindex: - let parent_snapshot = SortitionDB::get_block_snapshot_consensus( - burn_db.conn(), - &stacks_tip.consensus_hash, - ) - .expect("Failed to look up block's parent snapshot") - .expect("Failed to look up block's parent snapshot"); - - let parent_sortition_id = &parent_snapshot.sortition_id; - let parent_winning_vtxindex = - match SortitionDB::get_block_winning_vtxindex(burn_db.conn(), parent_sortition_id) - .expect("SortitionDB failure.") - { - Some(x) => x, - None => { - warn!( - "Failed to find winning vtx index for the parent sortition {}", - parent_sortition_id - ); - return None; - } - }; - - let parent_block = - match SortitionDB::get_block_snapshot(burn_db.conn(), parent_sortition_id) - .expect("SortitionDB failure.") - { - Some(x) => x, - None => { - warn!( - "Failed to find block snapshot for the parent sortition {}", - parent_sortition_id - ); - return None; - } - }; - - // don't mine off of an old burnchain block - let burn_chain_tip = SortitionDB::get_canonical_burn_chain_tip(burn_db.conn()) - .expect("FATAL: failed to query sortition DB for canonical burn chain tip"); - - if burn_chain_tip.consensus_hash != burn_block.consensus_hash { - debug!("New canonical burn chain tip detected: {} ({}) > {} ({}). Will not try to mine.", burn_chain_tip.consensus_hash, burn_chain_tip.block_height, &burn_block.consensus_hash, &burn_block.block_height); - return None; - } - - debug!("Mining tenure's last consensus hash: {} (height {} hash {}), stacks tip consensus hash: {} (height {} hash {})", - &burn_block.consensus_hash, burn_block.block_height, &burn_block.burn_header_hash, - &stacks_tip.consensus_hash, parent_snapshot.block_height, &parent_snapshot.burn_header_hash); - - let coinbase_nonce = { - let principal = keychain.origin_address(config.is_mainnet()).unwrap().into(); - let account = chain_state - .with_read_only_clarity_tx( - &burn_db.index_conn(), - &StacksBlockHeader::make_index_block_hash( - &stacks_tip.consensus_hash, - &stacks_tip.anchored_block_hash, - ), - |conn| StacksChainState::get_account(conn, &principal), - ) - .expect(&format!( - "BUG: stacks tip block {}/{} no longer exists after we queried it", - &stacks_tip.consensus_hash, &stacks_tip.anchored_block_hash - )); - account.nonce - }; - - ( - stacks_tip_header, - parent_consensus_hash, - parent_block.block_height, - parent_block.total_burn, - parent_winning_vtxindex, - coinbase_nonce, - ) + .ok()? } else { debug!("No Stacks chain tip known, will return a genesis block"); let (network, _) = config.burnchain.get_bitcoin_network(); @@ -1529,14 +1567,14 @@ impl InitializedNeonNode { burnchain_params.first_block_timestamp.into(), ); - ( - chain_tip.metadata, - FIRST_BURNCHAIN_CONSENSUS_HASH.clone(), - 0, - 0, - 0, - 0, - ) + MiningTenureInformation { + stacks_parent_header: chain_tip.metadata, + parent_consensus_hash: FIRST_BURNCHAIN_CONSENSUS_HASH.clone(), + parent_block_burn_height: 0, + parent_block_total_burn: 0, + parent_winning_vtxindex: 0, + coinbase_nonce: 0, + } }; // has the tip changed from our previously-mined block for this epoch? diff --git a/testnet/stacks-node/src/node.rs b/testnet/stacks-node/src/node.rs index 34d4128d7..2c00d56dc 100644 --- a/testnet/stacks-node/src/node.rs +++ b/testnet/stacks-node/src/node.rs @@ -769,6 +769,31 @@ impl Node { parent_consensus_hash }; + + // get previous burn block stats + let (parent_burn_block_hash, parent_burn_block_height, parent_burn_block_timestamp) = + if anchored_block.is_first_mined() { + (BurnchainHeaderHash([0; 32]), 0, 0) + } else { + match SortitionDB::get_block_snapshot_consensus(db.conn(), &parent_consensus_hash) + .unwrap() + { + Some(sn) => ( + sn.burn_header_hash, + sn.block_height as u32, + sn.burn_header_timestamp, + ), + None => { + // shouldn't happen + warn!( + "CORRUPTION: block {}/{} does not correspond to a burn block", + &parent_consensus_hash, &anchored_block.header.parent_block + ); + (BurnchainHeaderHash([0; 32]), 0, 0) + } + } + }; + let atlas_config = AtlasConfig::default(false); let mut processed_blocks = vec![]; loop { @@ -841,6 +866,9 @@ impl Node { Txid([0; 32]), vec![], None, + parent_burn_block_hash, + parent_burn_block_height, + parent_burn_block_timestamp, ); self.chain_tip = Some(chain_tip.clone()); @@ -919,14 +947,7 @@ impl Node { burnchain_tip: &BurnchainTip, vrf_seed: VRFSeed, ) -> BlockstackOperationType { - let winning_tx_vtindex = match ( - burnchain_tip.get_winning_tx_index(), - burnchain_tip.block_snapshot.total_burn, - ) { - (Some(winning_tx_id), _) => winning_tx_id, - (None, 0) => 0, - _ => unreachable!(), - }; + let winning_tx_vtindex = burnchain_tip.get_winning_tx_index().unwrap_or(0); let (parent_block_ptr, parent_vtxindex) = match self.bootstraping_chain { true => (0, 0), // parent_block_ptr and parent_vtxindex should both be 0 on block #1 diff --git a/testnet/stacks-node/src/run_loop/neon.rs b/testnet/stacks-node/src/run_loop/neon.rs index 0dc7a9596..dbecf22ba 100644 --- a/testnet/stacks-node/src/run_loop/neon.rs +++ b/testnet/stacks-node/src/run_loop/neon.rs @@ -14,9 +14,8 @@ use stacks::chainstate::coordinator::comm::{CoordinatorChannels, CoordinatorRece use stacks::chainstate::coordinator::{ BlockEventDispatcher, ChainsCoordinator, CoordinatorCommunication, }; -use stacks::chainstate::stacks::db::{ChainStateBootData, ClarityTx, StacksChainState}; +use stacks::chainstate::stacks::db::{ChainStateBootData, StacksChainState}; use stacks::net::atlas::{AtlasConfig, Attachment}; -use stacks::vm::types::{PrincipalData, Value}; use stx_genesis::GenesisData; use crate::monitoring::start_serving_monitoring_metrics; @@ -24,7 +23,7 @@ use crate::node::use_test_genesis_chainstate; use crate::syncctl::PoxSyncWatchdog; use crate::{ node::{get_account_balances, get_account_lockups, get_names, get_namespaces}, - util, BitcoinRegtestController, BurnchainController, Config, EventDispatcher, Keychain, + BitcoinRegtestController, BurnchainController, Config, EventDispatcher, Keychain, NeonGenesisNode, }; @@ -146,6 +145,11 @@ impl RunLoop { .unwrap(); info!("Miner node: checking UTXOs at address: {}", btc_addr); + match burnchain.create_wallet_if_dne() { + Err(e) => warn!("Error when creating wallet: {:?}", e), + _ => {} + } + let utxos = burnchain.get_utxos(&keychain.generate_op_signer().get_public_key(), 1, None, 0); if utxos.is_none() { @@ -310,7 +314,27 @@ impl RunLoop { }; // TODO (hack) instantiate the sortdb in the burnchain - let _ = burnchain.sortdb_mut(); + let sortdb = burnchain.sortdb_mut(); + let mut block_height = { + let (stacks_ch, _) = SortitionDB::get_canonical_stacks_chain_tip_hash(sortdb.conn()) + .expect("BUG: failed to load canonical stacks chain tip hash"); + + match SortitionDB::get_block_snapshot_consensus(sortdb.conn(), &stacks_ch) + .expect("BUG: failed to query sortition DB") + { + Some(sn) => burnchain_config.reward_cycle_to_block_height( + burnchain_config + .block_height_to_reward_cycle(sn.block_height) + .expect("BUG: snapshot preceeds first reward cycle"), + ), + None => { + let sn = SortitionDB::get_first_block_snapshot(&sortdb.conn()) + .expect("BUG: failed to get first-ever block snapshot"); + + sn.block_height + } + } + }; // Start the runloop trace!("Begin run loop"); @@ -326,8 +350,6 @@ impl RunLoop { .unwrap(); } - let mut block_height = 1.max(burnchain_config.first_block_height); - let mut burnchain_height = block_height; let mut num_sortitions_in_last_cycle = 1; let mut learned_burnchain_height = false; @@ -335,6 +357,11 @@ impl RunLoop { // prepare to fetch the first reward cycle! target_burnchain_block_height = burnchain_height + pox_constants.reward_cycle_length as u64; + debug!( + "Begin main runloop starting a burnchain block {}", + block_height + ); + loop { // Orchestrating graceful termination if !should_keep_running.load(Ordering::SeqCst) { diff --git a/testnet/stacks-node/src/syncctl.rs b/testnet/stacks-node/src/syncctl.rs index 3d11e07e0..e0e65254a 100644 --- a/testnet/stacks-node/src/syncctl.rs +++ b/testnet/stacks-node/src/syncctl.rs @@ -73,7 +73,7 @@ impl PoxSyncWatchdogComms { return Ok(false); } self.interruptable_sleep(1)?; - std::sync::atomic::spin_loop_hint(); + std::hint::spin_loop(); } return Ok(true); } @@ -99,7 +99,7 @@ impl PoxSyncWatchdogComms { return Ok(false); } self.interruptable_sleep(1)?; - std::sync::atomic::spin_loop_hint(); + std::hint::spin_loop(); } return Ok(true); } @@ -298,8 +298,8 @@ impl PoxSyncWatchdog { /// low and high pass filter average -- take average without the smallest and largest values fn hilo_filter_avg(samples: &Vec) -> f64 { // take average with low and high pass - let mut min = i64::max_value(); - let mut max = i64::min_value(); + let mut min = i64::MAX; + let mut max = i64::MIN; for s in samples.iter() { if *s < 0 { // nonsensical result (e.g. due to clock drift?) diff --git a/testnet/stacks-node/src/tests/neon_integrations.rs b/testnet/stacks-node/src/tests/neon_integrations.rs index 308dd8f4a..80cbbf2b5 100644 --- a/testnet/stacks-node/src/tests/neon_integrations.rs +++ b/testnet/stacks-node/src/tests/neon_integrations.rs @@ -94,7 +94,7 @@ fn neon_integration_test_conf() -> (Config, StacksAddress) { let magic_bytes = Config::from_config_file(ConfigFile::xenon()) .burnchain .magic_bytes; - assert_eq!(magic_bytes.as_bytes(), &['X' as u8, '6' as u8]); + assert_eq!(magic_bytes.as_bytes(), &['T' as u8, '2' as u8]); conf.burnchain.magic_bytes = magic_bytes; conf.burnchain.poll_time_secs = 1; conf.node.pox_sync_sample_secs = 0; @@ -310,12 +310,12 @@ fn wait_for_runloop(blocks_processed: &Arc) { fn wait_for_microblocks(microblocks_processed: &Arc, timeout: u64) -> bool { let mut current = microblocks_processed.load(Ordering::SeqCst); let start = Instant::now(); - + info!("Waiting for next microblock"); loop { let now = microblocks_processed.load(Ordering::SeqCst); if now == 0 && current != 0 { // wrapped around -- a new epoch started - debug!( + info!( "New microblock epoch started while waiting (originally {})", current ); @@ -376,8 +376,7 @@ fn get_tip_anchored_block(conf: &Config) -> (ConsensusHash, StacksBlock) { .json::() .unwrap(); let stacks_tip = tip_info.stacks_tip; - let stacks_tip_consensus_hash = - ConsensusHash::from_hex(&tip_info.stacks_tip_consensus_hash).unwrap(); + let stacks_tip_consensus_hash = tip_info.stacks_tip_consensus_hash; let stacks_id_tip = StacksBlockHeader::make_index_block_hash(&stacks_tip_consensus_hash, &stacks_tip); @@ -1308,6 +1307,97 @@ fn bitcoind_forking_test() { // but we're able to keep on mining assert_eq!(account.nonce, 3); + // Let's create another fork, deeper + let burn_header_hash_to_fork = btc_regtest_controller.get_block_hash(206); + btc_regtest_controller.invalidate_block(&burn_header_hash_to_fork); + btc_regtest_controller.build_next_block(10); + + thread::sleep(Duration::from_secs(5)); + next_block_and_wait(&mut btc_regtest_controller, &blocks_processed); + + let account = get_account(&http_origin, &miner_account); + assert_eq!(account.balance, 0); + assert_eq!(account.nonce, 3); + + next_block_and_wait(&mut btc_regtest_controller, &blocks_processed); + + let account = get_account(&http_origin, &miner_account); + assert_eq!(account.balance, 0); + // but we're able to keep on mining + assert_eq!(account.nonce, 3); + + channel.stop_chains_coordinator(); +} + +#[test] +#[ignore] +fn should_fix_2771() { + if env::var("BITCOIND_TEST") != Ok("1".into()) { + return; + } + + let (conf, miner_account) = neon_integration_test_conf(); + + let mut btcd_controller = BitcoinCoreController::new(conf.clone()); + btcd_controller + .start_bitcoind() + .map_err(|_e| ()) + .expect("Failed starting bitcoind"); + + let mut btc_regtest_controller = BitcoinRegtestController::new(conf.clone(), None); + + btc_regtest_controller.bootstrap_chain(201); + + eprintln!("Chain bootstrapped..."); + + let mut run_loop = neon::RunLoop::new(conf); + let blocks_processed = run_loop.get_blocks_processed_arc(); + + let channel = run_loop.get_coordinator_channel().unwrap(); + + thread::spawn(move || run_loop.start(None, 0)); + + // give the run loop some time to start up! + wait_for_runloop(&blocks_processed); + + // first block wakes up the run loop + next_block_and_wait(&mut btc_regtest_controller, &blocks_processed); + + // first block will hold our VRF registration + next_block_and_wait(&mut btc_regtest_controller, &blocks_processed); + + let mut sort_height = channel.get_sortitions_processed(); + eprintln!("Sort height: {}", sort_height); + + while sort_height < 210 { + next_block_and_wait(&mut btc_regtest_controller, &blocks_processed); + sort_height = channel.get_sortitions_processed(); + eprintln!("Sort height: {}", sort_height); + } + + // okay, let's figure out the burn block we want to fork away. + let reorg_height = 208; + warn!("Will trigger re-org at block {}", reorg_height); + let burn_header_hash_to_fork = btc_regtest_controller.get_block_hash(reorg_height); + btc_regtest_controller.invalidate_block(&burn_header_hash_to_fork); + btc_regtest_controller.build_next_block(1); + thread::sleep(Duration::from_secs(5)); + + // The test here consists in producing a canonical chain with 210 blocks. + // Once done, we invalidate the block 208, and instead of rebuilding directly + // a longer fork with N blocks (as done in the bitcoind_forking_test) + // we slowly add some more blocks. + // Without the patch, this behavior ends up crashing the node with errors like: + // WARN [1626791307.078061] [src/chainstate/coordinator/mod.rs:535] [chains-coordinator] ChainsCoordinator: could not retrieve block burnhash=40bdbf0dda349642bdf4dd30dd31af4f0c9979ce12a7c17485245d0a6ddd970b + // WARN [1626791307.078098] [src/chainstate/coordinator/mod.rs:308] [chains-coordinator] Error processing new burn block: NonContiguousBurnchainBlock(UnknownBlock(40bdbf0dda349642bdf4dd30dd31af4f0c9979ce12a7c17485245d0a6ddd970b)) + // And the burnchain db ends up in the same state we ended up while investigating 2771. + // With this patch, the node is able to entirely register this new canonical fork, and then able to make progress and finish successfully. + while sort_height < 213 { + next_block_and_wait(&mut btc_regtest_controller, &blocks_processed); + sort_height = channel.get_sortitions_processed(); + eprintln!("Sort height: {}", sort_height); + } + channel.stop_chains_coordinator(); } @@ -1363,8 +1453,8 @@ fn microblock_integration_test() { }); conf.node.mine_microblocks = true; - conf.node.wait_time_for_microblocks = 30000; - conf.node.microblock_frequency = 5_000; + conf.node.wait_time_for_microblocks = 10_000; + conf.node.microblock_frequency = 1_000; test_observer::spawn(); @@ -1463,7 +1553,7 @@ fn microblock_integration_test() { let _ = btc_regtest_controller.sortdb_mut(); // put each into a microblock - let (microblock, second_microblock) = { + let (first_microblock, second_microblock) = { let path = format!("{}/v2/info", &http_origin); let tip_info = client .get(&path) @@ -1502,7 +1592,7 @@ fn microblock_integration_test() { }; let mut microblock_bytes = vec![]; - microblock + first_microblock .consensus_serialize(&mut microblock_bytes) .unwrap(); @@ -1517,9 +1607,9 @@ fn microblock_integration_test() { .json() .unwrap(); - assert_eq!(res, format!("{}", µblock.block_hash())); + assert_eq!(res, format!("{}", &first_microblock.block_hash())); - eprintln!("\n\nBegin testing\nmicroblock: {:?}\n\n", µblock); + eprintln!("\n\nBegin testing\nmicroblock: {:?}\n\n", &first_microblock); let account = get_account(&http_origin, &spender_addr); assert_eq!(account.nonce, 1); @@ -1543,49 +1633,69 @@ fn microblock_integration_test() { assert_eq!(res, format!("{}", &second_microblock.block_hash())); - let path = format!("{}/v2/info", &http_origin); - let tip_info = client - .get(&path) - .send() - .unwrap() - .json::() - .unwrap(); - assert!(tip_info.stacks_tip_height >= 3); - let stacks_tip = tip_info.stacks_tip; - let stacks_tip_consensus_hash = - ConsensusHash::from_hex(&tip_info.stacks_tip_consensus_hash).unwrap(); - let stacks_id_tip = - StacksBlockHeader::make_index_block_hash(&stacks_tip_consensus_hash, &stacks_tip); + sleep_ms(5_000); - eprintln!( - "{:#?}", - client + let path = format!("{}/v2/info", &http_origin); + let mut iter_count = 0; + let tip_info = loop { + let tip_info = client .get(&path) .send() .unwrap() - .json::() - .unwrap() - ); + .json::() + .unwrap(); + eprintln!("{:#?}", tip_info); + if tip_info.unanchored_tip == StacksBlockId([0; 32]) { + iter_count += 1; + assert!( + iter_count < 10, + "Hit retry count while waiting for net module to process pushed microblock" + ); + sleep_ms(5_000); + continue; + } else { + break tip_info; + } + }; + + assert!(tip_info.stacks_tip_height >= 3); + let stacks_tip = tip_info.stacks_tip; + let stacks_tip_consensus_hash = tip_info.stacks_tip_consensus_hash; + let stacks_id_tip = + StacksBlockHeader::make_index_block_hash(&stacks_tip_consensus_hash, &stacks_tip); // todo - pipe in the PoxSyncWatchdog to the RunLoop struct to avoid flakiness here // wait at least two p2p refreshes so it can produce the microblock for i in 0..30 { - debug!( + info!( "wait {} more seconds for microblock miner to find our transaction...", 30 - i ); sleep_ms(1000); } - // check event observer for new microblock event (expect 2) + // check event observer for new microblock event (expect 4) let mut microblock_events = test_observer::get_microblocks(); - assert_eq!(microblock_events.len(), 2); + assert_eq!(microblock_events.len(), 4); // this microblock should correspond to `second_microblock` let microblock = microblock_events.pop().unwrap(); let transactions = microblock.get("transactions").unwrap().as_array().unwrap(); assert_eq!(transactions.len(), 1); - let tx_sequence = transactions[0].get("sequence").unwrap().as_u64().unwrap(); + let tx_sequence = transactions[0] + .get("microblock_sequence") + .unwrap() + .as_u64() + .unwrap(); assert_eq!(tx_sequence, 1); + let microblock_hash = transactions[0] + .get("microblock_hash") + .unwrap() + .as_str() + .unwrap(); + assert_eq!( + microblock_hash[2..], + format!("{}", second_microblock.header.block_hash()) + ); let microblock_associated_hash = microblock .get("parent_index_block_hash") .unwrap() @@ -1596,11 +1706,28 @@ fn microblock_integration_test() { StacksBlockId::from_vec(&index_block_hash_bytes), Some(stacks_id_tip) ); + // make sure we have stats for the burn block + let _burn_block_hash = microblock.get("burn_block_hash").unwrap().as_str().unwrap(); + let _burn_block_height = microblock + .get("burn_block_height") + .unwrap() + .as_u64() + .unwrap(); + let _burn_block_timestamp = microblock + .get("burn_block_timestamp") + .unwrap() + .as_u64() + .unwrap(); + // this microblock should correspond to the first microblock that was posted let microblock = microblock_events.pop().unwrap(); let transactions = microblock.get("transactions").unwrap().as_array().unwrap(); assert_eq!(transactions.len(), 1); - let tx_sequence = transactions[0].get("sequence").unwrap().as_u64().unwrap(); + let tx_sequence = transactions[0] + .get("microblock_sequence") + .unwrap() + .as_u64() + .unwrap(); assert_eq!(tx_sequence, 0); // check mempool tx events @@ -1658,6 +1785,25 @@ fn microblock_integration_test() { let _miner_txid = block.get("miner_txid").unwrap().as_str().unwrap(); + // make sure we have stats for the previous burn block + let _parent_burn_block_hash = block + .get("parent_burn_block_hash") + .unwrap() + .as_str() + .unwrap(); + + let _parent_burn_block_height = block + .get("parent_burn_block_height") + .unwrap() + .as_u64() + .unwrap(); + + let _parent_burn_block_timestamp = block + .get("parent_burn_block_timestamp") + .unwrap() + .as_u64() + .unwrap(); + prior = Some(my_index_hash); } @@ -1666,27 +1812,28 @@ fn microblock_integration_test() { "{}/v2/accounts/{}?proof=0&tip={}", &http_origin, &spender_addr, &tip_info.unanchored_tip ); + eprintln!("{:?}", &path); + let mut iter_count = 0; let res = loop { - let res = match client - .get(&path) - .send() - .unwrap() - .json::() - { - Ok(x) => x, - Err(_) => { - eprintln!("Failed to query {}; will try again", &path); + let http_resp = client.get(&path).send().unwrap(); + + info!("{:?}", http_resp); + + match http_resp.json::() { + Ok(x) => break x, + Err(e) => { + warn!("Failed to query {}; will try again. Err = {:?}", &path, e); + iter_count += 1; + assert!(iter_count < 10, "Retry limit reached querying account"); sleep_ms(1000); continue; } }; - - break res; }; - eprintln!("{:#?}", res); + info!("Account Response = {:#?}", res); assert_eq!(res.nonce, 2); assert_eq!(u128::from_str_radix(&res.balance[2..], 16).unwrap(), 96300); @@ -1742,6 +1889,7 @@ fn microblock_integration_test() { "{}/v2/accounts/{}?proof=0&tip={}", &http_origin, &spender_addr, &tip_info.unanchored_tip ); + let res_text = client.get(&path).send().unwrap().text().unwrap(); eprintln!("text of {}\n{}", &path, &res_text); @@ -1820,7 +1968,7 @@ fn size_check_integration_test() { conf.node.mine_microblocks = true; conf.node.wait_time_for_microblocks = 5000; - conf.node.microblock_frequency = 15000; + conf.node.microblock_frequency = 1000; let mut btcd_controller = BitcoinCoreController::new(conf.clone()); btcd_controller @@ -1939,7 +2087,7 @@ fn size_overflow_unconfirmed_microblocks_integration_test() { small_contract.push_str(" "); } - let spender_sks: Vec<_> = (0..10) + let spender_sks: Vec<_> = (0..5) .into_iter() .map(|_| StacksPrivateKey::new()) .collect(); @@ -1985,8 +2133,8 @@ fn size_overflow_unconfirmed_microblocks_integration_test() { } conf.node.mine_microblocks = true; - conf.node.wait_time_for_microblocks = 5000; - conf.node.microblock_frequency = 15000; + conf.node.wait_time_for_microblocks = 5_000; + conf.node.microblock_frequency = 5_000; test_observer::spawn(); conf.events_observers.push(EventObserverConfig { @@ -2009,6 +2157,7 @@ fn size_overflow_unconfirmed_microblocks_integration_test() { let mut run_loop = neon::RunLoop::new(conf); let blocks_processed = run_loop.get_blocks_processed_arc(); + let microblocks_processed = run_loop.get_microblocks_processed_arc(); let channel = run_loop.get_coordinator_channel().unwrap(); @@ -2045,7 +2194,9 @@ fn size_overflow_unconfirmed_microblocks_integration_test() { } } - sleep_ms(150_000); + while wait_for_microblocks(µblocks_processed, 30) { + info!("Waiting for microblocks to no longer be processed"); + } // now let's mine a couple blocks, and then check the sender's nonce. // at the end of mining three blocks, there should be _two_ transactions from the microblock @@ -2057,7 +2208,10 @@ fn size_overflow_unconfirmed_microblocks_integration_test() { next_block_and_wait(&mut btc_regtest_controller, &blocks_processed); // this one will contain the sortition from above anchor block, // which *should* have also confirmed the microblock. - sleep_ms(150_000); + + while wait_for_microblocks(µblocks_processed, 30) { + info!("Waiting for microblocks to no longer be processed"); + } next_block_and_wait(&mut btc_regtest_controller, &blocks_processed); @@ -2137,7 +2291,7 @@ fn size_overflow_unconfirmed_stream_microblocks_integration_test() { small_contract.push_str(" "); } - let spender_sks: Vec<_> = (0..25) + let spender_sks: Vec<_> = (0..20) .into_iter() .map(|_| StacksPrivateKey::new()) .collect(); @@ -2231,7 +2385,7 @@ fn size_overflow_unconfirmed_stream_microblocks_integration_test() { let mut ctr = 0; while ctr < flat_txs.len() { submit_tx(&http_origin, &flat_txs[ctr]); - if !wait_for_microblocks(µblocks_processed, 240) { + if !wait_for_microblocks(µblocks_processed, 60) { break; } ctr += 1; @@ -2249,7 +2403,7 @@ fn size_overflow_unconfirmed_stream_microblocks_integration_test() { while ctr < flat_txs.len() { submit_tx(&http_origin, &flat_txs[ctr]); - if !wait_for_microblocks(µblocks_processed, 240) { + if !wait_for_microblocks(µblocks_processed, 60) { break; } ctr += 1; @@ -2367,8 +2521,8 @@ fn size_overflow_unconfirmed_invalid_stream_microblocks_integration_test() { } conf.node.mine_microblocks = true; - conf.node.wait_time_for_microblocks = 15000; - conf.node.microblock_frequency = 1000; + conf.node.wait_time_for_microblocks = 5_000; + conf.node.microblock_frequency = 1_000; conf.node.max_microblocks = 65536; conf.burnchain.max_rbf = 1000000; conf.block_limit = BLOCK_LIMIT_MAINNET.clone(); @@ -2426,7 +2580,7 @@ fn size_overflow_unconfirmed_invalid_stream_microblocks_integration_test() { let mut ctr = 0; for _i in 0..6 { submit_tx(&http_origin, &flat_txs[ctr]); - if !wait_for_microblocks(µblocks_processed, 240) { + if !wait_for_microblocks(µblocks_processed, 60) { break; } ctr += 1; @@ -3451,7 +3605,7 @@ fn pox_integration_test() { assert_eq!(pox_info.reward_slots as u32, pox_constants.reward_slots()); assert_eq!(pox_info.next_cycle.reward_phase_start_block_height, 210); assert_eq!(pox_info.next_cycle.prepare_phase_start_block_height, 205); - assert_eq!(pox_info.next_cycle.min_increment_ustx, 20845173515333); + assert_eq!(pox_info.next_cycle.min_increment_ustx, 1250710410920); assert_eq!( pox_info.prepare_cycle_length as u32, pox_constants.prepare_length @@ -5296,10 +5450,9 @@ fn atlas_stress_integration_test() { // requests should take no more than 20ms assert!( total_time < attempts * 50, - format!( - "Atlas inventory request is too slow: {} >= {} * 50", - total_time, attempts - ) + "Atlas inventory request is too slow: {} >= {} * 50", + total_time, + attempts ); } @@ -5338,10 +5491,9 @@ fn atlas_stress_integration_test() { // requests should take no more than 40ms assert!( total_time < attempts * 50, - format!( - "Atlas chunk request is too slow: {} >= {} * 50", - total_time, attempts - ) + "Atlas chunk request is too slow: {} >= {} * 50", + total_time, + attempts ); } }