Add waitNext() to BlockTemplate interface #31283

pull Sjors wants to merge 2 commits into bitcoin:master from Sjors:2024/11/wait-next changing 8 files +251 −11
  1. Sjors commented at 12:30 pm on November 13, 2024: member

    This PR introduces waitNext(). It waits for either the tip to update or for fees at the top of the mempool to rise sufficiently. It then returns a new template, with which the caller can rinse and repeat.

    On testnet3 and testnet4 the difficulty drops after 20 minutes, so the second ensures that a new template is returned in that case.

    Alternative approach to #31003, suggested in #31109 (comment)

  2. DrahtBot commented at 12:30 pm on November 13, 2024: contributor

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/31283.

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    ACK ryanofsky, ismaelsadeeq, vasild

    If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #31564 (Add checkblock RPC and checkBlock() to Mining interface by Sjors)
    • #29415 (Broadcast own transactions only via short-lived Tor or I2P connections by vasild)
    • #26812 (test: add end-to-end tests for CConnman and PeerManager by vasild)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

  3. in src/node/interfaces.cpp:1012 in f955b8e406 outdated
    993+        }
    994+
    995+        return block_template;
    996+    }
    997+
    998+    const CScript m_script_pub_key;
    


    Sjors commented at 12:34 pm on November 13, 2024:
    Separate from this PR, I’m looking into whether we can avoid passing script_pub_key around. Afaik it’s only used by tests and for solo CPU mining.

    Sjors commented at 3:33 pm on November 18, 2024:
    #31318 drops this argument. It’s only slightly easier to merge that first, so I’ll leave this PR open.
  4. in src/node/interfaces.cpp:943 in f955b8e406 outdated
    937@@ -938,9 +938,71 @@ class BlockTemplateImpl : public BlockTemplate
    938         return chainman().ProcessNewBlock(block_ptr, /*force_processing=*/true, /*min_pow_checked=*/true, /*new_block=*/nullptr);
    939     }
    940 
    941+    std::unique_ptr<BlockTemplate> waitNext(CAmount fee_threshold, MillisecondsDouble timeout) override
    942+    {
    943+        if (timeout > std::chrono::years{100}) timeout = std::chrono::years{100}; // Upper bound to avoid UB in std::chrono
    


    Sjors commented at 12:37 pm on November 13, 2024:
    I still need to study #31003 (review) in more detail to see if this code isn’t making similar mistakes.

    Sjors commented at 1:30 pm on November 14, 2024:

    I dropped this line.

    Additionally this PR does not make use of last_mempool_update, since it’s not very useful in a typical production setting where something in the mempool updates all the time.

    I think the race condition mentioned there is not an issue here because we don’t pass in the current fees via argument, but instead derive them from the original template.

    I did forget to update now at the end of the loop, which I fixed.

  5. Sjors commented at 7:46 pm on November 13, 2024: member
    I pushed a commit refactoring miner_tests to use the Mining interface. I plan to expand those tests to cover waitNext().
  6. in src/test/miner_tests.cpp:68 in a316ba58cb outdated
    71-BlockAssembler MinerTestingSetup::AssemblerForTest(CTxMemPool& tx_mempool)
    72-{
    73-    BlockAssembler::Options options;
    74-
    75-    options.nBlockMaxWeight = MAX_BLOCK_WEIGHT;
    76-    options.blockMinFeeRate = blockMinFeeRate;
    


    Sjors commented at 7:47 pm on November 13, 2024:
    a316ba58cba5df8aadb05737959b97eeb6a350b4: I think dropping this doesn’t matter?
  7. in src/test/miner_tests.cpp:662 in a316ba58cb outdated
    684         }
    685-        std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(*pblock);
    686-        BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlock(shared_pblock, true, true, nullptr));
    687-        pblock->hashPrevBlock = pblock->GetHash();
    688+        std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(block);
    689+        BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(shared_pblock, /*force_processing=*/true, /*min_pow_checked=*/true, nullptr));
    


    Sjors commented at 7:51 pm on November 13, 2024:
    a316ba58cba5df8aadb05737959b97eeb6a350b4: I’m tempted to use miner.submitSolution() here.

    Sjors commented at 12:16 pm on November 14, 2024:
    Done
  8. DrahtBot added the label CI failed on Nov 13, 2024
  9. DrahtBot commented at 8:24 pm on November 13, 2024: contributor

    🚧 At least one of the CI tasks failed. Debug: https://github.com/bitcoin/bitcoin/runs/32946689392

    Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

  10. Sjors force-pushed on Nov 13, 2024
  11. DrahtBot removed the label CI failed on Nov 13, 2024
  12. Sjors force-pushed on Nov 14, 2024
  13. Sjors commented at 12:17 pm on November 14, 2024: member
    I made the test code alternate calls between Chainman’s ProcessNewBlock and submitSolution via the Mining interface. The latter approach is much simpler, but both are used in production code, so should be covered.
  14. Sjors force-pushed on Nov 14, 2024
  15. Sjors force-pushed on Nov 14, 2024
  16. Sjors force-pushed on Nov 14, 2024
  17. Sjors marked this as ready for review on Nov 14, 2024
  18. Sjors commented at 1:05 pm on November 14, 2024: member
    I added test coverage for waitNext().
  19. Sjors force-pushed on Nov 14, 2024
  20. Sjors force-pushed on Nov 14, 2024
  21. TheCharlatan commented at 1:18 pm on November 14, 2024: contributor

    In commit f9200f6c65b5a97a4842cc1ec34e688ec13bffcd

    “Use createNewBlock via the interface instead of calling Chainman’s CreateNewBlock.”

    Do you mean “Use createNewBlock via the interface instead of calling CreateNewBlock through the BlockAssembler directly”? There is no CreateNewBlock in the chainman.

  22. Sjors force-pushed on Nov 14, 2024
  23. Sjors commented at 1:31 pm on November 14, 2024: member
    Made some adjustments based on #31283 (review)
  24. Sjors force-pushed on Nov 14, 2024
  25. Sjors commented at 1:33 pm on November 14, 2024: member
    @TheCharlatan I adjusted the commit message
  26. in src/node/interfaces.cpp:1011 in e5a9b73210 outdated
    987+                CAmount new_fees = 0;
    988+                for (CAmount fee : block_template->m_block_template->vTxFees) {
    989+                    // Skip coinbase
    990+                    if (fee < 0) continue;
    991+                    new_fees += fee;
    992+                    if (new_fees >= current_fees + fee_threshold) return block_template;
    


    Sjors commented at 10:55 am on November 15, 2024:
    A fee_threshold of 0 combined with a timeout of 0 will immediately return a new template. This is useful for the Template Provider, because it needs to unconditionally send new templates to all connected clients if fees increased sufficiently for one its connected clients. (at least until cluster mempool makes the fee calculation trivially cheap)

    ryanofsky commented at 3:56 pm on January 22, 2025:

    re: #31283 (review)

    In commit “Add waitNext() to BlockTemplate interface” (956b449a51223cfa620d6c76d7ec37db5de86c3b)

    A fee_threshold of 0 combined with a timeout of 0 will immediately return a new template. This is useful for the Template Provider, because it needs to unconditionally send new templates to all connected clients if fees increased sufficiently for one its connected clients. (at least until cluster mempool makes the fee calculation trivially cheap)

    I don’t think I understand this special case. Wouldn’t it be easier for callers to just call createNewBlock() if they need a new template unconditionally? Dropping this would allow getting rid of the !tip_changed && timeout == MillisecondsDouble(0) && fee_threshold == 0 code which complicates things here.


    Sjors commented at 5:22 pm on January 22, 2025:
    That may indeed be a way to kill two birds with one stone: #31283 (review)
  27. Sjors force-pushed on Nov 15, 2024
  28. Sjors commented at 5:37 pm on November 15, 2024: member
    Added early return logic. Similar to #31297 this would be less tedious if notifications().m_tip_block was set to the tip earlier.
  29. Sjors commented at 1:33 pm on November 19, 2024: member
    Marking this draft, let’s review the early return and m_tip_block block changes in #31297 first.
  30. Sjors marked this as a draft on Nov 19, 2024
  31. Sjors commented at 7:11 pm on November 19, 2024: member

    #31297 is on hold, as it’s probably unnecessary.

    Please review #31325 first, so I can decide whether to build this PR on top of it.

  32. DrahtBot added the label Needs rebase on Nov 20, 2024
  33. ryanofsky referenced this in commit 22ef95dbe3 on Nov 20, 2024
  34. Sjors commented at 1:35 pm on November 21, 2024: member

    Rebased after #31288 and #31122.

    This PR is now based on #31325 since that has enough support. Keeping this draft until that’s merged (or dropped).

    The test changes will probably cause a merge conflict with #31318, but it should be trivial. Ditto for the waitNext() implementation since that would no longer need to pump script_pub_key around.

    I dropped the early return in waitNext() because wait_until should already take care of that. I also made the while loop use <= instead of < so this actually works: #31283 (review) Added a test for that.

  35. Sjors force-pushed on Nov 21, 2024
  36. Sjors force-pushed on Nov 21, 2024
  37. DrahtBot removed the label Needs rebase on Nov 21, 2024
  38. Sjors force-pushed on Nov 22, 2024
  39. Sjors commented at 1:50 pm on November 22, 2024: member
    Fixed a bug found while testing https://github.com/Sjors/bitcoin/pull/49, where it was spuriously making new block templates every tick even if fees didn’t rise. diff
  40. in src/node/interfaces.cpp:963 in 5d294d86f4 outdated
    958+                WAIT_LOCK(notifications().m_tip_block_mutex, lock);
    959+                notifications().m_tip_block_cv.wait_until(lock, std::min(now + tick, deadline), [&]() EXCLUSIVE_LOCKS_REQUIRED(notifications().m_tip_block_mutex) {
    960+                    // Although C++17 allows safe comparison of optional<T> with
    961+                    // another T, we need to wait for m_tip_block to be set AND
    962+                    // for the value to be different than the current_tip value.
    963+                    tip_changed = notifications().m_tip_block && notifications().m_tip_block != m_block_template->block.hashPrevBlock;
    


    Sjors commented at 7:15 pm on November 22, 2024:

    If #31346 lands first, notifications().m_tip_block could be an Assert and the comment removed, see #31346 (comment)

    Though I have to make sure there’s no race condition where you call createNewBlock() followed by waitNext() right at node startup, without first calling waitTipChanged(ZERO).

  41. Sjors referenced this in commit a8da99a25e on Nov 29, 2024
  42. Sjors referenced this in commit 9634b8ae00 on Dec 2, 2024
  43. Sjors referenced this in commit 392967375b on Dec 2, 2024
  44. DrahtBot added the label Needs rebase on Dec 3, 2024
  45. Sjors force-pushed on Dec 4, 2024
  46. Sjors commented at 5:44 am on December 4, 2024: member
    Rebased after #31112.
  47. Sjors force-pushed on Dec 4, 2024
  48. DrahtBot removed the label Needs rebase on Dec 4, 2024
  49. DrahtBot added the label Needs rebase on Dec 13, 2024
  50. Sjors force-pushed on Dec 14, 2024
  51. Sjors commented at 4:38 am on December 14, 2024: member
    Rebased after #31346.
  52. DrahtBot removed the label Needs rebase on Dec 14, 2024
  53. Sjors referenced this in commit a263cda285 on Dec 16, 2024
  54. DrahtBot added the label Needs rebase on Dec 17, 2024
  55. Sjors force-pushed on Dec 18, 2024
  56. Sjors commented at 2:51 am on December 18, 2024: member
    Rebased after #31318 and #31197.
  57. DrahtBot commented at 3:15 am on December 18, 2024: contributor

    🚧 At least one of the CI tasks failed. Debug: https://github.com/bitcoin/bitcoin/runs/34571636339

    Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

  58. DrahtBot added the label CI failed on Dec 18, 2024
  59. DrahtBot removed the label Needs rebase on Dec 18, 2024
  60. Sjors force-pushed on Dec 18, 2024
  61. Sjors commented at 3:44 am on December 18, 2024: member
    Fixed tidy error.
  62. DrahtBot removed the label CI failed on Dec 18, 2024
  63. Sjors force-pushed on Dec 19, 2024
  64. Sjors commented at 1:32 am on December 19, 2024: member
    The changes here don’t conflict with the merged #31196, but I rebased just in case.
  65. Sjors force-pushed on Dec 20, 2024
  66. Sjors marked this as ready for review on Dec 20, 2024
  67. Sjors commented at 1:44 am on December 20, 2024: member
    #31325 landed so this is ready for review.
  68. Sjors force-pushed on Dec 24, 2024
  69. Sjors commented at 4:37 pm on December 30, 2024: member
    I extracted the first commit into its own PR #31581. Review on the second commit is still welcome here, since I don’t expect it to change. Marking draft.
  70. Sjors marked this as a draft on Dec 30, 2024
  71. Sjors force-pushed on Jan 6, 2025
  72. Sjors commented at 5:25 pm on January 6, 2025: member

    Rebased on the latest #31581.

    I added a commit that on testnet3 and testnet4 returns a new template after 20 minutes, to take advantage of the min difficulty rule.

    Since miner_tests.cpp uses mainnet, I created testnet4_miner_tests.cpp to cover this behaviour in the unit tests. A functional test could be added later using the testnet4 blocks added by #31583. This could check if the difficulty is correct.

    I also switched to using NodeClock::now() to be able to use mock time in tests.

  73. Sjors force-pushed on Jan 6, 2025
  74. DrahtBot added the label CI failed on Jan 6, 2025
  75. DrahtBot commented at 5:45 pm on January 6, 2025: contributor

    🚧 At least one of the CI tasks failed. Debug: https://github.com/bitcoin/bitcoin/runs/35210885796

    Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

  76. DrahtBot removed the label CI failed on Jan 6, 2025
  77. achow101 referenced this in commit c506f2cee7 on Jan 6, 2025
  78. Sjors force-pushed on Jan 7, 2025
  79. Sjors commented at 7:31 am on January 7, 2025: member

    Rebased after #31581 landed, marking ready for review again.

    If I need to retouch I’ll fix the typo mentioned in #31581 (review) and maybe add a helper to the test suggested here #31581 (review).

  80. Sjors marked this as ready for review on Jan 7, 2025
  81. in src/node/interfaces.cpp:1032 in 956b449a51 outdated
    1001+                    if (new_fees >= current_fees + fee_threshold) return block_template;
    1002+                }
    1003+            }
    1004+
    1005+            // Break out of while when using mock time
    1006+            if (now == deadline) break;
    


    ryanofsky commented at 3:34 pm on January 22, 2025:

    In commit “Add waitNext() to BlockTemplate interface” (956b449a51223cfa620d6c76d7ec37db5de86c3b)

    This seems hacky, Why not change now <= deadline to now < deadline at the top of the while loop, or alternately call setmocktime in tests with time + 1?


    Sjors commented at 10:52 am on January 24, 2025:
    Even after dropping the fee_threshold==0 && timeout==0 special case, I still need this. Added a comment to explain why (unit tests can’t easily move mock time during a blocking call).

    vasild commented at 1:52 pm on January 31, 2025:
    Just stumbled on this and posted a comment to the PR elsewhere… Having this only to make unit tests easier seems not enough justification to me.

    vasild commented at 2:09 pm on January 31, 2025:

    unit tests can’t easily move mock time during a blocking call

    Consider the following pattern to do that. I think it would be better to have that in the tests instead of this if (now == deadline) in the main (production) code.

     0std::atomic_int now = 0;
     1
     2void waitNext(int timeout)
     3{
     4    const int deadline = now + timeout;
     5    while (now < deadline) {
     6        std::cout << "waitNext() now=" << now << "\n";
     7        std::this_thread::sleep_for(500ms);
     8    }
     9}
    10
    11int main(int, char**)
    12{
    13    std::atomic_bool stop_advancing_mocktime{false};
    14    std::thread advance_mocktime{[&stop_advancing_mocktime]() {
    15        while (!stop_advancing_mocktime) {
    16            ++now;
    17            std::this_thread::sleep_for(500ms);
    18        }
    19    }};
    20    waitNext(5);
    21    stop_advancing_mocktime = true;
    22    advance_mocktime.join();
    23    return 0;
    24}
    

    ryanofsky commented at 2:32 pm on January 31, 2025:

    I think it would be better to have that in the tests instead of this if (now == deadline) in the main (production) code.

    Do we actually think that behavior is bad to have in production code? It seems like it’s perfectly reasonable for production code to return either at the deadline or immediately after, and the choice is basically arbitrary for normal code but important for tests, so we are choosing a behavior that makes it more convenient to write tests and documenting that fact. This doesn’t seem l like a bad thing to me, and it might be preferable to introducing another thread and risking making the tests nondeterministic. Just my thoughts, no strong opinion though.


    vasild commented at 3:50 pm on January 31, 2025:
    Functionality-wise I think the behavior of the production code is ok. I am concerned about its readability and maintainability. It just looks confusing (to me).
  82. in src/node/interfaces.cpp:939 in 956b449a51 outdated
    935@@ -936,9 +936,87 @@ class BlockTemplateImpl : public BlockTemplate
    936         return chainman().ProcessNewBlock(block_ptr, /*force_processing=*/true, /*min_pow_checked=*/true, /*new_block=*/nullptr);
    937     }
    938 
    939+    std::unique_ptr<BlockTemplate> waitNext(CAmount fee_threshold, MillisecondsDouble timeout) override
    


    ryanofsky commented at 3:54 pm on January 22, 2025:

    In commit “Add waitNext() to BlockTemplate interface” (956b449a51223cfa620d6c76d7ec37db5de86c3b)

    It might sense to move arguments to this function into a BlockWaitOptions struct similar to the existing BlockCreateOptions struct. Could make calling code clearer since it will specify need to specify arguments by name instead of passing two numbers, and provide more backwards source compatibility (require source code to change less if we want to drop existing arguments or add new ones in the future).

  83. ryanofsky approved
  84. ryanofsky commented at 4:38 pm on January 22, 2025: contributor
    Code review ACK 8d54cfdb1f368bd4486bbc4b285f4712a0f260fa, but I didn’t look at the tests very closely and could review them more. The code seems to have gotten simpler since the last version in #31003, so that is very nice
  85. Sjors force-pushed on Jan 24, 2025
  86. Sjors commented at 10:52 am on January 24, 2025: member

    I dropped the special case for fee_threshold of 0 combined with a timeout of 0. I’ll adjust https://github.com/Sjors/bitcoin/pull/49.

    Added a comment why the mock time workaround is still needed.

    I also switched to using a new BlockWaitOptions struct.

    Also rebased.

  87. ryanofsky approved
  88. ryanofsky commented at 2:44 pm on January 24, 2025: contributor

    Code review ACK 6bf77e5a376804c0681897fa032537ba16e9a99a. Code looks great, but I still would like to spend more time reviewing tests in detail.

    Main changes since last review were just adding options struct, dropping special case, adding comment explaining the now == deadline logic and other documentation. Code is pretty clear and simple now without the mempool GetTransactionsUpdated complexity, without the previous special case, and with the updated documentation.

  89. in src/interfaces/mining.h:64 in a61fb2fd2d outdated
    55@@ -56,6 +56,16 @@ class BlockTemplate
    56      * @returns if the block was processed, independent of block validity
    57      */
    58     virtual bool submitSolution(uint32_t version, uint32_t timestamp, uint32_t nonce, CTransactionRef coinbase) = 0;
    59+
    60+    /**
    61+     * Waits for fees in the next block to rise, a new tip or the timeout.
    62+     *
    63+     * @param[in] options   Control the timeout (default forever) and by how much total fees
    64+     *                      for the next block should rise (default infinite).
    


    ismaelsadeeq commented at 8:04 pm on January 30, 2025:

    In “Add waitNext() to BlockTemplate interface” a61fb2fd2d6b338c08c619e4605521a41bd3edd9

    We already have the comments about the default in node types BlockWaitOptions docstring, writing it here is duplicating same information, when we update the default we have to update both places.

    I think it will be better to just move the fee threshold description comment to node types BlockWaitOptions docstring and delete the duplicate here.


    Sjors commented at 8:32 pm on January 30, 2025:
    I tried to make it a quick TL&DR so someone can understand the most important behaviour, without having to go and read the entire struct doc (which might grow, and not all code let you hover and see the full thing in a popup).

    Sjors commented at 7:34 am on January 31, 2025:
    (and these two defaults are very unlikely to change).
  90. in src/node/interfaces.cpp:998 in a61fb2fd2d outdated
    970+             * way to determine how much (approximate) fees for the next block increased.
    971+             *
    972+             * We'll also create a new template if the tip changed during the last tick.
    973+             */
    974+            if (options.fee_threshold < MAX_MONEY || tip_changed) {
    975+                auto block_template{std::make_unique<BlockTemplateImpl>(m_assemble_options, BlockAssembler{chainman().ActiveChainstate(), context()->mempool.get(), m_assemble_options}.CreateNewBlock(), m_node)};
    


    ismaelsadeeq commented at 8:28 pm on January 30, 2025:

    In “Add waitNext() to BlockTemplate interface” a61fb2fd2d6b338c08c619e4605521a41bd3edd9

    I think we are not just creating a block template after ticks but on each iteration of this loop, which continues infinitely in the default case. Since we are holding cs_main continuously, wouldn’t this cause resource contention and delay other components of the node?

    My concern is that, regardless of whether the mempool improves, we keep creating block templates.

    Currently there is no reliable way to determine if the fee rate diagram of the mempool has increased, I think this will become easier after #30289. When RBF occurs, we can be certain that the fee rate diagram of the mempool has improved. We could even determine the fee additions after those RBFs and compare them with fee_threshold the pool want; if the threshold is exceeded, we should trigger a block template update.

    Since that is not possible and #30289 is a WIP I think we should only build a block template after some time has passed. It seems like GBT waits for a minute maybe you could do the same?


    ryanofsky commented at 8:38 pm on January 30, 2025:

    re: #31283 (review)

    In “Add waitNext() to BlockTemplate interface” a61fb2f

    I think we are not just creating a block template after ticks but on each iteration of this loop, which continues infinitely in the default case. Since we are holding cs_main continuously, wouldn’t this cause resource contention and delay other components of the node?

    The m_tip_block_cv.wait_until(lock, std::min(now + tick, deadline)) call on line 957 is what is supposed to prevent this. Each time that line is reached the thread should sleep for one tick. It should only wake up before the tick if the tip changes or the call is timing out or the node is shutting down.


    Sjors commented at 8:40 pm on January 30, 2025:

    cs_main is only held briefly, after each 1 second wait, once per loop.

    This PR currently makes 1 template per second to get the fees. Once we have cluster mempool, I’m hoping there will be a lighter weight method to see fees in the top 4 mega-weight units have risen. cc @sipa / @sdaftuar

    One of the stratum v2 goals is to push new templates faster, so I’d rather keep the tick at 1 second unless it causes problems on slow hardware.


    ismaelsadeeq commented at 8:46 pm on January 30, 2025:

    The m_tip_block_cv.wait_until(lock, std::min(now + tick, deadline)) call on line 957 is what is supposed to prevent this.

    cs_main is only held briefly, after each 1 second wait, once per loop.

    I missed this in first pass. Thanks for the clarifications my concern is addressed.

    I imagine still there will be redundant work in doing this each second, we won’t be certain that much has changed in a second, but you could make the same argument for 30 seconds or 1 minute as well.

  91. in src/node/interfaces.cpp:1031 in a61fb2fd2d outdated
    993+                for (CAmount fee : block_template->m_block_template->vTxFees) {
    994+                    // Skip coinbase
    995+                    if (fee < 0) continue;
    996+                    new_fees += fee;
    997+                    if (new_fees >= current_fees + options.fee_threshold) return block_template;
    998+                }
    


    ismaelsadeeq commented at 9:05 pm on January 30, 2025:
    In “Add waitNext() to BlockTemplate interface” a61fb2fd2d6b338c08c619e4605521a41bd3edd9 @Sjors IIRC block assembler has a total fees field nFees that also skips coinbase transaction, why aren’t we using that? could expose it and use it, this will prevent the iterating through all the block transactions to compute the total fees during each tick?

    Sjors commented at 7:45 am on January 31, 2025:

    Good point. BlockAssembler::CreateNewBlock() uses nFees internally. It’s updated in AddToBlock. I could expose it via CBlockTemplate.

    However this code will hopefully go away soon(tm) with Cluster Mempool, and I suspect it’s extremely fast (compared to block assembly). So it’s perhaps not worth refactoring. In any case it could be done in a followup without changing the interface.

  92. in src/interfaces/mining.h:64 in 6bf77e5a37 outdated
    63@@ -64,6 +64,9 @@ class BlockTemplate
    64      *                      for the next block should rise (default infinite).
    


    ismaelsadeeq commented at 9:25 pm on January 30, 2025:

    In “On testnet create min diff template after 20 mins” 6bf77e5a376804c0681897fa032537ba16e9a99a

    nit: @Sjors should this commit message and comments be updated to reflect the actual change something like

    “miner: update waitNext to return a new block if 20 minutes have elapsed since the tip”

    I think the above discription is exactly what this commit does and does not enable the creation of the min diff template, it’s done internally by the block assembler?


    Sjors commented at 4:20 pm on January 31, 2025:
    Changed the commit message.
  93. ismaelsadeeq commented at 9:31 pm on January 30, 2025: member

    Code Review 6bf77e5a376804c0681897fa032537ba16e9a99a

    Just a question and a nit

  94. in src/interfaces/mining.h:66 in 6bf77e5a37 outdated
    61+     * Waits for fees in the next block to rise, a new tip or the timeout.
    62+     *
    63+     * @param[in] options   Control the timeout (default forever) and by how much total fees
    64+     *                      for the next block should rise (default infinite).
    65+     *
    66+     * @returns a new BlockTemplate or nullptr if the timeout occurs.
    


    vasild commented at 8:13 am on January 31, 2025:
    nit: strictly speaking it does not return nullptr but an empty std::unique_ptr (which owns nullptr).

    Sjors commented at 4:21 pm on January 31, 2025:
    Changed to “nothing”
  95. in src/node/interfaces.cpp:958 in 6bf77e5a37 outdated
    953+        while (now <= deadline) {
    954+            bool tip_changed{false};
    955+            {
    956+                WAIT_LOCK(notifications().m_tip_block_mutex, lock);
    957+                notifications().m_tip_block_cv.wait_until(lock, std::min(now + tick, deadline), [&]() EXCLUSIVE_LOCKS_REQUIRED(notifications().m_tip_block_mutex) {
    958+                    Assert(notifications().TipBlock());
    


    vasild commented at 11:23 am on January 31, 2025:
    What is the reasoning to abort the entire program if TipBlock() returns an empty optional? Looks a bit too aggressive to me. TipBlock()’s doc says “Might be unset during an early shutdown”. Is it not better to return an empty unique_ptr from waitNext() if this happens?

    Sjors commented at 4:28 pm on January 31, 2025:

    I turned Assert into Assume. When comparing an std::optional<uint256> with a uint256 it will be considered unequal if the optional is empty. https://en.cppreference.com/w/cpp/utility/optional/operator_cmp (24)

    This would trigger a spurious tip_changed. But since it can only happen during shutdown, it’s not a problem. And not fatal in any case.

  96. in src/node/interfaces.cpp:959 in 6bf77e5a37 outdated
    944+        CAmount current_fees = -1;
    945+
    946+        // Alternate waiting for a new tip and checking if fees have risen.
    947+        // The latter check is expensive so we only run it once per second.
    948+        auto now{NodeClock::now()};
    949+        const auto deadline = now + options.timeout;
    


    vasild commented at 12:03 pm on January 31, 2025:
    When options.timeout is MillisecondsDouble::max() it looks odd to add to it. This seems to work because of the underlying double type which ends up being inf and adding to inf results in inf. But if that is changed to an integer type this will break (overflow). Maybe at least comment on BlockWaitOptions::timeout that it must be of double type because of this, or to have BlockWaitOptions::timeout be of type std::optional with an empty optional meaning “no timeout”. Then deadline would be optional as well.

    ryanofsky commented at 2:54 pm on January 31, 2025:

    But if that is changed to an integer type this will break (overflow).

    I don’t think there is anything unusual about that. If you change a floating point variable to an integer, you should expect code using that variable to need to be updated to handle overflows correctly, because integer and floating point numbers overflow differently. This code could work correctly if it were switched to an integer. It would just need to use saturating integer operations like the ones in util/overflow.h.

    I also think an nullopt value is more semantically ambiguous. Specifying nullopt or “no timeout” could mean either return right away or wait forever. But specifying 1.7 x 10^308 seconds more clearly corresponds to waiting forever.

  97. in src/node/interfaces.cpp:1032 in 6bf77e5a37 outdated
    1014+             * unit tests would need to spawn a thread to achieve this.
    1015+             * Making the while loop use now < deadline won't work either, because
    1016+             * then there's no wait to test its internals, e.g. the  20 minute
    1017+             * testnet exception.
    1018+             */
    1019+            if (now == deadline) break;
    


    vasild commented at 12:12 pm on January 31, 2025:
    I think the loop should be while (now < deadline) (instead of <=) and this should not exist. But then I do not understand what this means: “because then there’s no wait to test its internals, e.g. the 20 minute testnet exception”. Can you elaborate?

    vasild commented at 1:54 pm on January 31, 2025:
    Lets continue the discussion here #31283 (review) which is a previous comment about the same thing.

    Sjors commented at 1:58 pm on January 31, 2025:

    If the loop uses while (now < deadline) then it will never generate a template, so all the BOOST_REQUIRE(block_template) checks will fail.

    So it has to use <=, but without the break here the test will freeze, because time doesn’t move.


    vasild commented at 3:45 pm on January 31, 2025:

    You mean “never generate a template” when run from the tests, right? Why is that? The default timeout is inf so now < deadline should be true?

    So it has to use <=, but without the break here the test will freeze, because time doesn’t move.

    Right, but it is not too difficult to advance the time in unit tests.


    Sjors commented at 7:33 pm on January 31, 2025:

    Yes, when run from the tests.

    It would require a second thread, because the main thread of the unit test is waiting on waitNext().


    vasild commented at 5:43 am on February 1, 2025:

    Ok, it will never generate a template because it will never exit from waitNext() (when run from unit tests with mocktime which never advances). “BOOST_REQUIRE(block_template) checks will fail” – they will never be reached in that case.

    What about this (unit tests will use timeout=0):

    0now = now()
    1deadline = now + timeout;
    2do {
    3    stuff;
    4    now = now();
    5} while (now < deadline);
    

    it will execute the body of the loop exactly once when given timeout=0 regardless of whether the clock advances or not.


    Sjors commented at 8:48 am on February 1, 2025:

    I think it’s better explicitly mention the test issue:

    1. Less likely for someone to accidentally break it and be confused
    2. Maybe someone adds a nice macro one day RUN_COMMAND_DO_OTHER_THING_IN_NEW_THREAD_AFTER(command, delay) (or 3. have an option to make mock time move)

    vasild commented at 9:29 am on February 3, 2025:

    then there’s no wait to test its internals

    Should that be “no way”?


    Sjors commented at 9:38 am on February 3, 2025:

    Will push fix if I need to change anything else.

    Fixed


    vasild commented at 9:41 am on February 3, 2025:

    Consider the following. It avoids the hackish part and achieves the same purpose:

     0diff --git i/src/node/interfaces.cpp w/src/node/interfaces.cpp
     1index 9fe4984cd0..8022b47b5e 100644
     2--- i/src/node/interfaces.cpp
     3+++ w/src/node/interfaces.cpp
     4@@ -960,13 +960,13 @@ public:
     5             // could have been generated before a tip exists.
     6             Assume(tip_block);
     7             tip_changed = tip_block != m_block_template->block.hashPrevBlock;
     8             return tip_changed;
     9         };
    10 
    11-        while (now <= deadline) {
    12+        do {
    13             {
    14                 WAIT_LOCK(notifications().m_tip_block_mutex, lock);
    15                 // Note that wait_until() checks the predicate before waiting
    16                 notifications().m_tip_block_cv.wait_until(lock, std::min(now + tick, deadline), [&]() EXCLUSIVE_LOCKS_REQUIRED(notifications().m_tip_block_mutex) {
    17                     return check_tip_changed() || chainman().m_interrupt;
    18                 });
    19@@ -1018,23 +1018,14 @@ public:
    20                     new_fees += fee;
    21                     Assume(options.fee_threshold != MAX_MONEY);
    22                     if (new_fees >= current_fees + options.fee_threshold) return block_template;
    23                 }
    24             }
    25 
    26-            /**
    27-             * Break out of while when using mock time. While functional tests
    28-             * can call setmocktime to move the clock and exit this loop, a
    29-             * unit tests would need to spawn a thread to achieve this.
    30-             * Making the while loop use now < deadline won't work either, because
    31-             * then there's no wait to test its internals, e.g. the  20 minute
    32-             * testnet exception.
    33-             */
    34-            if (now == deadline) break;
    35             now = NodeClock::now();
    36-        }
    37+        } while (now < deadline);
    38 
    39         return nullptr;
    40     }
    41 
    42     const BlockAssembler::Options m_assemble_options;
    43 
    

    Sjors commented at 9:55 am on February 3, 2025:
    Taken
  98. in src/node/types.h:85 in 6bf77e5a37 outdated
    74+    /**
    75+     * Wait until total fees in the new template exceed fees in the origal
    76+     * template by at least this amount (in sats). The default is to ignore
    77+     * fee increases and only wait for a tip change.
    78+     */
    79+    CAmount fee_threshold{MAX_MONEY};
    


    vasild commented at 12:15 pm on January 31, 2025:
    Consider using std::optional with an empty optional meaning “no value” instead of the magic values MillisecondsDouble::max() and MAX_MONEY.

    Sjors commented at 2:29 pm on January 31, 2025:
    IIRC I used magic values back when these were passed in as arguments, but in a struct using an std::optional is not much friction. I’ll look into that.

    ryanofsky commented at 2:41 pm on January 31, 2025:

    IIRC I used magic values back when these were passed in as arguments, but in a struct using an std::optional is not much friction. I’ll look into that.

    IMO, MAX_MONEY is a magic value that would be good to replace with nullopt, but MillisecondsDouble::max() is not a magic value and the code would be simpler if it were kept.

    A magic value (imo) is a value that is handled specially and adds a special case, so replacing CAmount fee_threshold with std::optional` is good because the code actually has a contains a special case for not checking for any fee threshold, and so it is better to make that special case more explicit and directly controllable.

    But the code does not contain any special case for MillisecondsDouble::max(), it is just treated correctly as a really large value, so changing that to use std::optional would only add a special case that wasn’t previously there and make the code more complicated and fragile. So it would be better to keep the way it is.


    vasild commented at 3:53 pm on January 31, 2025:
    Agreed about MillisecondsDouble::max(). Note here that we rely to have such a max value that when we add to it the result is the same max value (not overflow or cause unexpected things).

    Sjors commented at 4:12 pm on January 31, 2025:
    @ryanofsky do I understand correctly that for the .capnp there’s no difference when making something an std::optional?

    ryanofsky commented at 5:05 pm on January 31, 2025:

    @ryanofsky do I understand correctly that for the .capnp there’s no difference when making something an std::optional?

    It depends on the type. In capnproto schemas primitive fields (numbers, bools, enums, unions) are not optional and are always present. If these fields are not assigned values they are just initialized to 0. So to represent a std::optional<T> value where T is a primitive you have to change the capnproto schema to be able to represent std::nullopt differently somehow, unless you are ok with std::nullopt being represented as 0.

    Capnproto does allow non-primitive fields (data, text, struct, list fields) to be missing so you don’t need to change the capnproto schema to use std::optional for these. For these fields a std::nullopt field is just serialized with the field unset, and this is not ambiguous.

    The difference between primitive/non-primitve fields is explained at https://capnproto.org/faq.html#how-do-i-make-a-field-optional and the faq recommends using a union with Void to represent optional primitive fields(*). Unfortunately though, I didn’t know about that trick and I don’t think libmultiprocess currently supports that, although it could. Another natural way to represent an optional value might just to use a List() type and only populate it with 0 or 1 elements.

    But actually the way I’ve supported optional primitive fields in libmultiprocess so far has just been to accompany them with a boolean “has” field. So for example the Chain::getHeight method which returns std::optional<int> in c++ returns (result :Int32, hasResult :Bool) in capnproto. I know this works for parameters and returns values, though I’m not sure if it works for struct fields. You could just declare the struct to have a hasFeeThreshold: Bool value and I could fix this in libmultiprocess if it isn’t working.

    Or if we think maybe List(Int64) or Union(Void, Int64) would be better ways to represent std::optional<Int64> fields we could use one of those representations and I can extend libmultiprocess to support them.

    (*) I think the faq might be a little buggy, though, because I believe the Void field needs to come before the Int32 field in its example for Void to be the default value instead of 0.. EDIT: NVM faq is actually fine. I forgot the schema language allows setting default values so as long as the default value is void this is fine


    vasild commented at 8:16 am on February 3, 2025:
    Ok, I think using MAX_MONEY as magic value to mean “no threshold” would be nice to avoid but is not a blocker for this PR. Accompanying boolean “has” field is fine too.
  99. in src/node/interfaces.cpp:1022 in 6bf77e5a37 outdated
     995+                    for (CAmount fee : m_block_template->vTxFees) {
     996+                        // Skip coinbase
     997+                        if (fee < 0) continue;
     998+                        current_fees += fee;
     999+                    }
    1000+                }
    


    vasild commented at 1:19 pm on January 31, 2025:

    Looking at

    https://github.com/bitcoin/bitcoin/blob/8fa10edcd1706a1f0dc9d8c3adbc8efa3c7755bf/src/node/miner.cpp#L157

    isn’t this equivalent to just current_fees = m_block_template->vTxFees[0];?


    Sjors commented at 2:05 pm on January 31, 2025:

    That might work actually, with an extra minus. But it seems brittle, because the line pblocktemplate->vTxFees[0] = -nFees; doesn’t do anything afaik and might one day get deleted, causing this code to think fees are 0.


    I just verified that deleting pblocktemplate->vTxFees[0] = -nFees; doesn’t break any test.

    Additionally I expect this to go away in favor of something more efficient sooner rather than later, see #31283 (review)


    vasild commented at 11:13 am on February 3, 2025:
    Can there be a test that enforces pblocktemplate->vTxFees[0] = -nFees? Ie the test to fail if that line is removed?

    Sjors commented at 12:21 pm on February 3, 2025:

    I’d rather remove the line, it might have had a use before that no longer applies.

    If we want to get fees via the CBLockTemplate then it should just get a field for that.

    But I don’t think it’s useful to do any of that, because this (duplicate) code will likely go away soon: #31283 (review)

    The real slowness is in constructing the CBLockTemplate, compared to that this loop should be negligible. And when we have a more efficient method to figure out the fee increase at the top of the mempool, we probably won’t use the CBLockTemplate struct at all.


    ryanofsky commented at 6:49 pm on February 12, 2025:

    re: #31283 (review)

    This discussion is interesting but I don’t understand the line

    https://github.com/bitcoin/bitcoin/blob/8fa10edcd1706a1f0dc9d8c3adbc8efa3c7755bf/src/node/miner.cpp#L157

    at all. Why does it even make sense?


    Sjors commented at 3:38 pm on February 18, 2025:
    I opened #31897 to drop this -nFees dummy.
  100. in src/node/interfaces.cpp:983 in 6bf77e5a37 outdated
    967+            LOCK(::cs_main);
    968+
    969+            // On test networks return a minimum difficulty block after 20 minutes
    970+            if (!tip_changed && allow_min_difficulty) {
    971+                const NodeClock::time_point tip_time{std::chrono::seconds{chainman().ActiveChain().Tip()->GetBlockTime()}};
    972+                if (now > tip_time + std::chrono::seconds(20 * 60)) {
    


    vasild commented at 1:35 pm on January 31, 2025:

    nit:

    0                if (now > tip_time + 20min) {
    

    vasild commented at 9:42 am on February 3, 2025:
    This is marked as resolved, but the code is not changed.

    Sjors commented at 9:54 am on February 3, 2025:
    I think I lost it in a rebase, taken again.
  101. vasild commented at 1:38 pm on January 31, 2025: contributor
    Approach ACK 6bf77e5a37
  102. in src/node/interfaces.cpp:969 in a61fb2fd2d outdated
    964+
    965+            // Must release m_tip_block_mutex before locking cs_main, to avoid deadlocks.
    966+            LOCK(::cs_main);
    967+            /**
    968+             * The only way to determine if fees increased compared to the previous template,
    969+             * is to generate a fresh template. Cluster Mempool may allow for a more efficient
    


    sipa commented at 3:00 pm on January 31, 2025:

    This is not the case, or at least, this is not dependent on cluster mempool. EDIT: maybe it can actually be done much faster with cluster mempool, see below.

    Cluster mempool will make the block-building algorithm faster (or, possibly, have the option of producing a better-quality one), but operationally nothing changes: you need to effectively run the block-building algorithm to know what fees can be gathered.

    There are a number of options if we just want an approximate “how many fees” faster, but they could be implemented today just as easily:

    • We could have a variant of the block-building code that does not actually remember the template, and only keeps track of the fees.
    • We could have a variant of the block-building code that avoids spending (as much) time in the bin-packing aspect to fill the last few kWU of the block.

    I think eventually, the correct approach is to have a background thread that continuously creates blocks for the block weight(s) requested, and store the result in a variable that’s not under cs_main; the code here could then wait on it.


    Sjors commented at 3:27 pm on January 31, 2025:

    I was hoping we could simply have a variable that tracks the total fees for the top 1 vMB, which is constantly updated as things are inserted and removed from the mempool.

    (or the ability to register multiple such variables which the code here can then monitor)


    sipa commented at 3:34 pm on January 31, 2025:
    It’s probably fast enough that that can be done as an add-on, updated after every mempool change, but limited to every N milliseconds or so.

    ismaelsadeeq commented at 3:36 pm on January 31, 2025:

    @sipa, I imagine that an RBF over any transaction in the last built block template be an indicator that the previously built template improve.

    And since we would be using fee rate diagram for RBF’s after cluster mempool, it could also be an indicator? But higher mining score txs can also get it, so this is not that reliable.


    sipa commented at 4:09 pm on January 31, 2025:

    Just discussed this with @ismaelsadeeq a bit more.

    The hard part is not tracking improvements to the mempool (that is ultimately what all of cluster mempool is, making this explicit), but knowing which improvements affect the top 1 MvB of the mempool and to what extent. My thinking was that we can’t really maintain an index for that, as doing so would be less efficient than just recomputing when it’s needed.

    But maybe that is not the case. We could keep track at all times of (a) the sum of the fees F and sizes S in all chunks that (fully) fit in the top 1 MvB of a block, and (b) the first chunk C that does not (fully) fit. Any time a chunk is added whose feerate exceeds C’s feerate, its fee/size is added to F & S. Any time such a chunk is removed, its fee is removed from F & S. Any time one wants to query the top block’s fee, C may need to be walked up or down, so to make S <= 1000000 and S+size(C) > 1000000, but that can be a pretty fast operation, which only scales with the number of chunks affected in between, not with the total number of chunks. When a new block comes in it may be faster to recompute F/S/C from scratch than trying to keep it updated though.

    This would give a super fast way of estimating top block fee, though will ignore binpacking effects entirely.

    This needs #30535, though.


    Sjors commented at 4:30 pm on January 31, 2025:
    I adjusted the comment.
  103. Sjors force-pushed on Jan 31, 2025
  104. Sjors commented at 4:32 pm on January 31, 2025: member
    Rebased and addressed new feedback.
  105. in src/node/interfaces.cpp:957 in ae787b1b41 outdated
    952+        while (now <= deadline) {
    953+            bool tip_changed{false};
    954+            {
    955+                WAIT_LOCK(notifications().m_tip_block_mutex, lock);
    956+                notifications().m_tip_block_cv.wait_until(lock, std::min(now + tick, deadline), [&]() EXCLUSIVE_LOCKS_REQUIRED(notifications().m_tip_block_mutex) {
    957+                    Assume(notifications().TipBlock());
    


    sipa commented at 4:44 pm on January 31, 2025:
    The earlier discussion is unclear to me. Is it possible that TipBlock() returns std::nullopt under non-error conditions (including shutdown)? If so, there should not be an Assume() here. Those are for conditions we believe are not possible if the code is correct.

    Sjors commented at 6:50 pm on January 31, 2025:

    The assume is correct, but the assert was a bit overzealous. waitNext() is a method on BlockTemplate. You can’t generate a template before there’s a tip, so it’s impossible for this fail.

    It’s possible that the node is shut down when TipBlock() still returns std::nullopt, but in that scenario there can’t be a BlockTemplate. The value doesn’t get unset during shutdown.


    sipa commented at 6:57 pm on January 31, 2025:
    Got it.

    Sjors commented at 7:32 pm on January 31, 2025:
    I added a comment to remember this.

    vasild commented at 11:42 am on February 3, 2025:

    but in that scenario there can’t be a BlockTemplate

    What about

    1. A BlockTemplate is generated
    2. Shutdown is initiated
    3. This code is reached and TipBlock() returns an empty optional, triggering the Assume()

    Can’t happen?


    Sjors commented at 12:24 pm on February 3, 2025:
    Step (3) doesn’t happen. A shutdown doesn’t null out m_tip_block.


    Sjors commented at 2:46 pm on February 3, 2025:

    @vasild it’s possible to start the node and quickly shut it down. If that happens then m_tip_block remains null.

    If you call createNewBlock() during that time I’m not sure what happens. I think it’s best to add an extra guard there. The Template Provider calls waitTipChanged before calling createNewBlock(), but not every interface user might do that.

    With that in place you can’t get here.


    Sjors commented at 2:57 pm on February 3, 2025:
    Opened #31785 for this check, though I could include it here.

    Sjors commented at 5:24 pm on February 3, 2025:

    though I could include it here.

    It turned into a bigger change than I expected, so better to keep it separate from this. I don’t think it’s blocking.


    vasild commented at 5:32 pm on February 4, 2025:

    Or this, idea from discussion from another thread - return nullptr if TipBlock() returns no block:

     0diff --git i/src/node/interfaces.cpp w/src/node/interfaces.cpp
     1index 1eb66d81c0..9af5e65308 100644
     2--- i/src/node/interfaces.cpp
     3+++ w/src/node/interfaces.cpp
     4@@ -947,35 +947,37 @@ public:
     5         // The latter check is expensive so we only run it once per second.
     6         auto now{NodeClock::now()};
     7         const auto deadline = now + options.timeout;
     8         const MillisecondsDouble tick{1000};
     9         const bool allow_min_difficulty{chainman().GetParams().GetConsensus().fPowAllowMinDifficultyBlocks};
    10 
    11+        std::optional<uint256> tip_hash;
    12         bool tip_changed{false};
    13         // Helper to check if the tip has changed, and also update tip_changed.
    14-        auto check_tip_changed = [this, &tip_changed]() EXCLUSIVE_LOCKS_REQUIRED(notifications().m_tip_block_mutex) {
    15+        auto check_tip_changed = [this, &tip_hash, &tip_changed]() EXCLUSIVE_LOCKS_REQUIRED(notifications().m_tip_block_mutex) {
    16             AssertLockHeld(notifications().m_tip_block_mutex);
    17             if (tip_changed) return true;
    18-            const auto tip_block{notifications().TipBlock()};
    19-            // This is an instance method on BlockTemplate and no template
    20-            // could have been generated before a tip exists.
    21-            Assume(tip_block);
    22-            tip_changed = tip_block != m_block_template->block.hashPrevBlock;
    23+            tip_hash = notifications().TipBlock();
    24+            if (tip_hash.has_value()) {
    25+                tip_changed = tip_hash.value() != m_block_template->block.hashPrevBlock;
    26+            } else {
    27+                tip_changed = true;
    28+            }
    29             return tip_changed;
    30         };
    31 
    32         do {
    33             {
    34                 WAIT_LOCK(notifications().m_tip_block_mutex, lock);
    35                 // Note that wait_until() checks the predicate before waiting
    36                 notifications().m_tip_block_cv.wait_until(lock, std::min(now + tick, deadline), [&]() EXCLUSIVE_LOCKS_REQUIRED(notifications().m_tip_block_mutex) {
    37                     return check_tip_changed() || chainman().m_interrupt;
    38                 });
    39             }
    40 
    41-            if (chainman().m_interrupt) return nullptr;
    42+            if (chainman().m_interrupt || !tip_hash.has_value()) return nullptr;
    43 
    44             // Must release m_tip_block_mutex before locking cs_main, to avoid deadlocks.
    45             LOCK(::cs_main);
    46 
    47             // On test networks return a minimum difficulty block after 20 minutes
    48             if (!tip_changed && allow_min_difficulty) {
    

    Does that look sensible?


    Sjors commented at 6:05 pm on February 4, 2025:
    I think #31785 is a more thorough solution that avoids having to deal with this edge case all over the place.

    vasild commented at 2:41 pm on February 11, 2025:

    it’s possible to start the node and quickly shut it down. If that happens then m_tip_block remains null. If you call createNewBlock() during that time I’m not sure what happens

    The RPC is not yet serving requests when m_tip_block is assigned a value, but IPC is started before that. I put a long sleep just before assigning to m_tip_block but I am not sure how to call createNewBlock() from the IPC. Just reading the code, I think this assert in BlockAssembler::CreateNewBlock() will be triggered:

    https://github.com/bitcoin/bitcoin/blob/86528937e5c4da2e12c46085fc41e87ed759258e/src/node/miner.cpp#L133-L134


    Sjors commented at 2:56 pm on February 11, 2025:

    it’s possible

    In theory, I don’t think this is easy to hit in practice. And #31785 should fix it entirely. You could try modifying bitcoin-mine in #30437.


    vasild commented at 11:33 am on February 12, 2025:
    Ok, I decided to spend time reviewing #31785 instead of trying to trigger this assert. Now, should merging #31785 be a blocker for this PR? If not, then I should try to trigger this assert and confirm that it is not triggered (if this is to be merged without #31785).
  106. in src/node/interfaces.cpp:958 in ae787b1b41 outdated
    953+            bool tip_changed{false};
    954+            {
    955+                WAIT_LOCK(notifications().m_tip_block_mutex, lock);
    956+                notifications().m_tip_block_cv.wait_until(lock, std::min(now + tick, deadline), [&]() EXCLUSIVE_LOCKS_REQUIRED(notifications().m_tip_block_mutex) {
    957+                    Assume(notifications().TipBlock());
    958+                    tip_changed = notifications().TipBlock() != m_block_template->block.hashPrevBlock;
    


    sipa commented at 4:47 pm on January 31, 2025:

    (In case the Assume() above is correct, see my other comment).

    It seems wasteful to call TipBlock() twice here. I’d suggest:

    0auto tip_block = notifications().TipBlock();
    1Assume(tip_block);
    2tip_changed = m_block_template->block.hashPrevBlock;
    3return tip_changed || chainman().m_interrupt;
    

    Sjors commented at 6:55 pm on January 31, 2025:
    Taken (with some changes)
  107. in src/ipc/capnp/mining.capnp:43 in 22cf6cdb41 outdated
    39@@ -39,6 +40,11 @@ struct BlockCreateOptions $Proxy.wrap("node::BlockCreateOptions") {
    40     coinbaseOutputMaxAdditionalSigops @2 :UInt64 $Proxy.name("coinbase_output_max_additional_sigops");
    41 }
    42 
    43+struct BlockWaitOptions $Proxy.wrap("node::BlockWaitOptions") {
    


    ryanofsky commented at 5:25 pm on January 31, 2025:

    In commit “miner: have waitNext return after 20 min on testnet” (22cf6cdb4190482b486752dae4e9dc3b44cfcb1e):

    Note for a possible followup. I think this representation of the options struct should work fine for now as long as the client initializing the BlockWaitOptions struct is a c++ client using the libmultiprocess library.

    However, this representation of the options struct might not be the most convenient for other clients, like a possible rust client, because by default these fields will both be set to 0 and interpreted by the server as 0, so the rust client will have to manually remember to set these fields to max values if they want default behavior.

    This problem could be avoided by adding additional hasTimeout and hasFeeThreshold bool fields which will both default to false, and which should cause the c++ server code code to automatically leave the default c++ struct field values in place and not overwrite them with 0. Or other representations like the union(Void, T) representation mentioned #31283 (review) could also work. Any representation that results in default behavior when a capnproto client leaves these fields unset would work.

    This can be a followup though, since default libmultiprocess c++ client will initialize both these fields from a source C++ struct which has the correct default values.


    ryanofsky commented at 5:57 pm on January 31, 2025:

    re: #31283 (review)

    Actually never mind, there is a better solution here. I forgot capnproto does support setting default values, using syntax shown https://capnproto.org/language.html#structs, but for some reason I had thought it did not support them. It supports default values using a xor trick described https://capnproto.org/encoding.html#default-values).

    So recommendation here could be to set default values in the capnproto struct to match default values in the c++ struct. But again this could be a followup and it only affects other potential clients like rust clients, not c++ client using libmultiprocess since they will just be serializing a c++ struct which already has the defaults set.


    Sjors commented at 6:34 pm on January 31, 2025:
    I’d really like this to be easy to use for a Rust client as early as possible. It seems better to revert to the magic value for now.

    ryanofsky commented at 7:10 pm on February 13, 2025:

    re: #31283 (review)

    I’d really like this to be easy to use for a Rust client as early as possible. It seems better to revert to the magic value for now.

    To make this easy for rust (or other) cilents using this interface you can assign these fields default values. Maybe define MAX_MONEY and MAX_TIMEOUT constants using syntax shown https://capnproto.org/language.html#constants and assign them as defaults using syntax shown https://capnproto.org/language.html#structs. These changes could be a followup though and shouldn’t be necessary for this PR.

  108. in src/node/interfaces.cpp:968 in ae787b1b41 outdated
    951+
    952+        while (now <= deadline) {
    953+            bool tip_changed{false};
    954+            {
    955+                WAIT_LOCK(notifications().m_tip_block_mutex, lock);
    956+                notifications().m_tip_block_cv.wait_until(lock, std::min(now + tick, deadline), [&]() EXCLUSIVE_LOCKS_REQUIRED(notifications().m_tip_block_mutex) {
    


    sipa commented at 6:18 pm on January 31, 2025:
    Is it possible that notifications.TipBlock() has changed already, before the m_tip_block_mutex lock is grabbed? If so, we’d wait up to a full second before noticing?

    Sjors commented at 7:26 pm on January 31, 2025:
    I added check before we start the wait.

    Sjors commented at 9:08 am on February 3, 2025:
    Actually, I forgot that wait_until already checks the predicate once before waiting. See #31283 (review)
  109. in src/node/interfaces.cpp:1001 in ae787b1b41 outdated
    975+             */
    976+            if (options.fee_threshold || tip_changed) {
    977+                auto block_template{std::make_unique<BlockTemplateImpl>(m_assemble_options, BlockAssembler{chainman().ActiveChainstate(), context()->mempool.get(), m_assemble_options}.CreateNewBlock(), m_node)};
    978+
    979+                // If the tip changed, return the new template regardless of its fees.
    980+                if (tip_changed) {
    


    sipa commented at 6:20 pm on January 31, 2025:
    Similarly here, is it possible the tip block has changed after the 1s wait above expired, but before cs_main is grabbed above? This could be easily detect by tip_changed || block_template->block.hashPrevBlock != m_block_template->block.hashPrevBlock here (the tip_changed || is perhaps not even necessary?).

    Sjors commented at 7:26 pm on January 31, 2025:
    If that happens we’ll catch it at the start of the next loop, with the above change.

    Sjors commented at 7:28 pm on January 31, 2025:
    m_tip_block_mutex and cs_main locks don’t jive well, so I limited the places where we actually check
  110. Sjors force-pushed on Jan 31, 2025
  111. Sjors commented at 7:26 pm on January 31, 2025: member
    I went back to using the MAX_MONEY magic value (with comment to explain why) and added a helper check_tip_changed() to make sure we check for a tip update before waiting.
  112. Sjors force-pushed on Jan 31, 2025
  113. in src/node/interfaces.cpp:954 in 5dd3575ae1 outdated
    949+        const auto deadline = now + options.timeout;
    950+        const MillisecondsDouble tick{1000};
    951+        const bool allow_min_difficulty{chainman().GetParams().GetConsensus().fPowAllowMinDifficultyBlocks};
    952+
    953+        bool tip_changed{false};
    954+        // Helper to check if the tip has has changed, and also update tip_changed.
    


    vasild commented at 8:21 am on February 3, 2025:

    nit:

    0        // Helper to check if the tip has changed, and also update tip_changed.
    
  114. in src/node/interfaces.cpp:976 in 5dd3575ae1 outdated
    967+            {
    968+                WAIT_LOCK(notifications().m_tip_block_mutex, lock);
    969+                // First make sure the tip hasn't already changed:
    970+                check_tip_changed() || notifications().m_tip_block_cv.wait_until(lock, std::min(now + tick, deadline), [&]() EXCLUSIVE_LOCKS_REQUIRED(notifications().m_tip_block_mutex) {
    971+                    return check_tip_changed() || chainman().m_interrupt;
    972+                });
    


    vasild commented at 8:30 am on February 3, 2025:

    wait_until() will first call the predicate, so check_tip_changed() || is not needed. The above code is equivalent to:

    0                notifications().m_tip_block_cv.wait_until(lock, std::min(now + tick, deadline), [&]() EXCLUSIVE_LOCKS_REQUIRED(notifications().m_tip_block_mutex) {
    1                    return check_tip_changed() || chainman().m_interrupt;
    2                });
    

    Sjors commented at 8:54 am on February 3, 2025:
    Ah oops, we’re going in circles, I forgot about this as well. Will add a comment.

    Sjors commented at 9:07 am on February 3, 2025:
    @ryanofsky pointed out the same mistake in a different (now closed) PR: #31297 (review)
  115. Sjors force-pushed on Feb 3, 2025
  116. Sjors commented at 9:10 am on February 3, 2025: member
    Dropped unnecessary check_tip_changed() check before wait_until.
  117. Sjors force-pushed on Feb 3, 2025
  118. Sjors force-pushed on Feb 3, 2025
  119. Sjors commented at 9:55 am on February 3, 2025: member
    Applied @vasild’s suggestion: #31283 (review)
  120. vasild approved
  121. vasild commented at 11:47 am on February 3, 2025: contributor

    ACK 276ce2eea38865154017047f3761225c2b504cf6

    Two things that would be nice to figure out before merge:

    • There could be a low-hanging fruit to avoid iterating over all transactions and use -nFee which should be significantly faster: #31283 (review)

    • Just to confirm that the Assume() can’t happen in normal circumstances: #31283 (review)

  122. DrahtBot requested review from ryanofsky on Feb 3, 2025
  123. Sjors force-pushed on Feb 10, 2025
  124. Sjors commented at 2:35 pm on February 10, 2025: member
    Rebased after #31384.
  125. in src/node/types.h:85 in 7f6f58bad8 outdated
    80+     * libmultiprocess maps missing values to 0.
    81+     * https://github.com/bitcoin/bitcoin/pull/31283#discussion_r1937622086
    82+     *
    83+     * Use a magic value instead.
    84+     */
    85+    CAmount fee_threshold{MAX_MONEY};
    


    ryanofsky commented at 6:24 pm on February 12, 2025:

    In commit “Add waitNext() to BlockTemplate interface” (7f6f58bad8e56c9a25ffbc0d105bed37e9958fef)

    This may all be true, but I don’t think it clearly explains the behavior when MAX_MONEY is specified. Would suggest a simpler comment:

    • The wait method will not return a new template unless it has fees at least fee_threshold sats higher than the current template, or unless the chain tip changes and the previous template is no longer valid. Determining whether fee_threshold is reached is expensive, so as an optimization, when fee_threshold is set to MAX_MONEY or higher, the implementation is able to be much more efficient, skipping expensive checks and only returning new templates when the chain tip changes.

    Sjors commented at 3:59 pm on February 13, 2025:
    I incorporated part of this comment. Note that some callers may just not be interested in fee changes, so I mentioned that as well.

    ryanofsky commented at 6:25 pm on February 13, 2025:

    re: #31283 (review)

    In commit “Add waitNext() to BlockTemplate interface” (86449088b703d2110edaacbd6d1f1386916a3773)

    I incorporated part of this comment. Note that some callers may just not be interested in fee changes, so I mentioned that as well.

    Thanks, I do have two more suggestions:

    • In “A caller may not be interested in templates with higher fees, but determining whether fee_threshold is reached is also expensive,” can you replace “but” with “and”. Both of these are just different reasons to specify MAX_MONEY.
    • Can we drop the “This is not an std::optional […]” section? I think as long as the MAX_MONEY case is just an optimization (we could drop rid of the options.fee_threshold < MAX_MONEY check in the code and it would be less efficient internally but not have an observable difference from the callers perspective), it doesn’t make actually make sense to use std::optional here. Using std::optional would just make the interface more confusing and the implementation more complicated.
  126. in src/node/interfaces.cpp:960 in 7f6f58bad8 outdated
    955+        const auto deadline = now + options.timeout;
    956+        const MillisecondsDouble tick{1000};
    957+
    958+        bool tip_changed{false};
    959+        // Helper to check if the tip has changed, and also update tip_changed.
    960+        auto check_tip_changed = [this, &tip_changed]() EXCLUSIVE_LOCKS_REQUIRED(notifications().m_tip_block_mutex) {
    


    ryanofsky commented at 6:33 pm on February 12, 2025:

    In commit “Add waitNext() to BlockTemplate interface” (7f6f58bad8e56c9a25ffbc0d105bed37e9958fef)

    IMO it is confusing to define an extra lambda here that is only called in one place (inside another lambda). It would be less confusing to just inline check_tip_changed in the place where it is used, to remove a level of indirection and make the order code is written match the order that it executes. Would suggest

     0--- a/src/node/interfaces.cpp
     1+++ b/src/node/interfaces.cpp
     2@@ -956,24 +956,19 @@ public:
     3         const MillisecondsDouble tick{1000};
     4 
     5         bool tip_changed{false};
     6-        // Helper to check if the tip has changed, and also update tip_changed.
     7-        auto check_tip_changed = [this, &tip_changed]() EXCLUSIVE_LOCKS_REQUIRED(notifications().m_tip_block_mutex) {
     8-            AssertLockHeld(notifications().m_tip_block_mutex);
     9-            if (tip_changed) return true;
    10-            const auto tip_block{notifications().TipBlock()};
    11-            // This is an instance method on BlockTemplate and no template
    12-            // could have been generated before a tip exists.
    13-            Assume(tip_block);
    14-            tip_changed = tip_block != m_block_template->block.hashPrevBlock;
    15-            return tip_changed;
    16-        };
    17-
    18         do {
    19             {
    20                 WAIT_LOCK(notifications().m_tip_block_mutex, lock);
    21                 // Note that wait_until() checks the predicate before waiting
    22                 notifications().m_tip_block_cv.wait_until(lock, std::min(now + tick, deadline), [&]() EXCLUSIVE_LOCKS_REQUIRED(notifications().m_tip_block_mutex) {
    23-                    return check_tip_changed() || chainman().m_interrupt;
    24+                    AssertLockHeld(notifications().m_tip_block_mutex);
    25+                    if (tip_changed || chainman().m_interrupt) return true;
    26+                    const auto tip_block{notifications().TipBlock()};
    27+                    // This is an instance method on BlockTemplate and no template
    28+                    // could have been generated before a tip exists.
    29+                    Assume(tip_block);
    30+                    tip_changed = tip_block != m_block_template->block.hashPrevBlock;
    31+                    return tip_changed;
    32                 });
    33             }
    34 
    

    vasild commented at 5:20 am on February 13, 2025:
    Before it was like what you suggest, then the check_tip_changed() lambda was introduced because that code had to be called in two places, then, following suggestions, it was changed to be called in just one place. I agree it would be better to remove it now, given that it is called in just one place.

    Sjors commented at 3:34 pm on February 13, 2025:
    The reason I kept it separate is because it’s verbose. But happy to move it back.
  127. in src/node/interfaces.cpp:993 in 7f6f58bad8 outdated
    988+             * (approximate) fees for the next block increased, perhaps more so after
    989+             * Cluster Mempool.
    990+             *
    991+             * We'll also create a new template if the tip changed during the last tick.
    992+             */
    993+            if (options.fee_threshold != MAX_MONEY || tip_changed) {
    


    ryanofsky commented at 6:40 pm on February 12, 2025:

    In commit “Add waitNext() to BlockTemplate interface” (7f6f58bad8e56c9a25ffbc0d105bed37e9958fef)

    IMO would be slightly better to write this as options.fee_threshold < MAX_MONEY || tip_changed, so MAX_MONEY is not a magic value, just an optimization cutoff for the point where we stop checking if fee_threshold has been reached because we know it is too high to be reached.

  128. in src/node/interfaces.cpp:960 in 7f6f58bad8 outdated
    951+
    952+        // Alternate waiting for a new tip and checking if fees have risen.
    953+        // The latter check is expensive so we only run it once per second.
    954+        auto now{NodeClock::now()};
    955+        const auto deadline = now + options.timeout;
    956+        const MillisecondsDouble tick{1000};
    


    ryanofsky commented at 7:00 pm on February 12, 2025:

    In commit “Add waitNext() to BlockTemplate interface” (7f6f58bad8e56c9a25ffbc0d105bed37e9958fef)

    Any thoughts on whether it makes sense for this polling interval to be configurable? I could imagine a miner not really caring how expensive this check is and just wanting it to be as fast as possible?


    Sjors commented at 3:40 pm on February 13, 2025:

    A miner might naively set it so low that our p2p processing is impacted, potentially getting new blocks later and interfering with transaction relay.

    Also, I hope to get rid of this soon(tm) if cluster mempool gives us a more efficient method. It’s nice if we don’t have to drop a configuration option again.

    This is already 60x faster than the long poll behavior of getblocktemplate, which nobody complains about. And typical stratum (v1) jobs are refreshed every 30 seconds. https://b10c.me/blog/014-mining-pool-behavior-during-forks/


    Sjors commented at 3:42 pm on February 13, 2025:
    Additionally there’s memory management to worry about, if we really start pushing fresh template at every mempool increment, potentially multiple times per second. I haven’t gotten around to implementing support for that.
  129. in src/test/miner_tests.cpp:683 in 7f6f58bad8 outdated
    676@@ -672,8 +677,10 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
    677         const int current_height{mining->getTip()->height};
    678 
    679         // Simple block creation, nothing special yet:
    680-        block_template = mining->createNewBlock(options);
    681-        BOOST_REQUIRE(block_template);
    682+        if (current_height % 2 == 0) {
    683+            block_template = mining->createNewBlock(options);
    684+            BOOST_REQUIRE(block_template);
    685+        } // for odd heights the next template is created at the end of this loop
    


    ryanofsky commented at 7:04 pm on February 12, 2025:

    In commit “Add waitNext() to BlockTemplate interface” (7f6f58bad8e56c9a25ffbc0d105bed37e9958fef)

    I found this comment pretty confusing and would suggest making it more specific like “// if current_height is odd, block_template will have already been set at the end of the previous loop.”

  130. in src/test/miner_tests.cpp:719 in 7f6f58bad8 outdated
    716@@ -710,7 +717,12 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
    717             BOOST_REQUIRE_EQUAL(maybe_new_tip->GetBlockHash(), block.GetHash());
    718         }
    719         // This just adds coverage
    


    ryanofsky commented at 7:06 pm on February 12, 2025:

    In commit “Add waitNext() to BlockTemplate interface” (7f6f58bad8e56c9a25ffbc0d105bed37e9958fef)

    The “// This just adds coverage” comment should be moved above the waitTipChanged call since it does not apply to the waitNext call, which is needed to set block_template for the next loop iteration.

  131. in src/node/interfaces.cpp:967 in 7f6f58bad8 outdated
    962+            if (tip_changed) return true;
    963+            const auto tip_block{notifications().TipBlock()};
    964+            // This is an instance method on BlockTemplate and no template
    965+            // could have been generated before a tip exists.
    966+            Assume(tip_block);
    967+            tip_changed = tip_block != m_block_template->block.hashPrevBlock;
    


    ryanofsky commented at 7:26 pm on February 12, 2025:

    In commit “Add waitNext() to BlockTemplate interface” (7f6f58bad8e56c9a25ffbc0d105bed37e9958fef)

    Seem like it would be more correct (and more compact) to write this as:

    tip_changed = Assume(tip_block) && tip_block != m_block_template->block.hashPrevBlock;

    Since if tip_block is null the right thing to do is just wait for it to be non-null, not set tip_changed to true and try to return a new block template below.


    vasild commented at 5:23 am on February 13, 2025:

    Assume(tip_block) … if tip_block is null the right thing to do is just wait …

    Hmm, if tip_block is null then Assume() will not wait but stop the program?


    Sjors commented at 3:58 pm on February 13, 2025:

    Taken

    Assume(x) is x in production, so in the event of a bug tip_changed will be false. So the function will just wait for the deadline and return nothing.

  132. ryanofsky approved
  133. ryanofsky commented at 7:36 pm on February 12, 2025: contributor
    Code review ACK ce9c2e17d5d4ef99cba82521f2515e34130a02a1. I don’t think this needs to depend on the other PR #31785, because I don’t see how the assume check #31283 (review) could fail. Left some minor comments below though, including one about how assume check could be handled better.
  134. DrahtBot requested review from vasild on Feb 12, 2025
  135. Sjors commented at 4:29 pm on February 13, 2025: member
    (there seems to be a delay in Github processing my latest push)
  136. ryanofsky approved
  137. ryanofsky commented at 6:26 pm on February 13, 2025: contributor

    Code review ACK 16b18f845313f25bbcdea122baa753b570add7d0. Just various suggested changes since last review.

    (there seems to be a delay in Github processing my latest push)

    Can confirm I see “Processing Updates” “Recent push is still being processed, and will show up in a bit” at the top of this PR.

  138. Sjors force-pushed on Feb 13, 2025
  139. Sjors commented at 6:49 pm on February 13, 2025: member
    It seems Github never got around to my last push. The new one was picked up immediately. It incorporates the two suggestions.
  140. ryanofsky approved
  141. ryanofsky commented at 7:02 pm on February 13, 2025: contributor
    Code review ACK 2d65225c4696dcc8312d546eb67f4eae11b9b8fe, just updating fee_threshold comment since last review
  142. in src/node/interfaces.cpp:881 in dcb75cbae6 outdated
    877@@ -877,7 +878,7 @@ class ChainImpl : public Chain
    878 class BlockTemplateImpl : public BlockTemplate
    879 {
    880 public:
    881-    explicit BlockTemplateImpl(std::unique_ptr<CBlockTemplate> block_template, NodeContext& node) : m_block_template(std::move(block_template)), m_node(node)
    882+    explicit BlockTemplateImpl(BlockAssembler::Options assemble_options, std::unique_ptr<CBlockTemplate> block_template, NodeContext& node) : m_assemble_options(std::move(assemble_options)), m_block_template(std::move(block_template)), m_node(node)
    


    ismaelsadeeq commented at 7:33 pm on February 18, 2025:
    style nit: Break line to be more readable according to https://github.com/bitcoin/bitcoin/blob/master/doc/developer-notes.md#coding-style-c

    Sjors commented at 3:59 pm on February 19, 2025:
    clang-format doesn’t seem to touch this. However once I added some line breaks myself, it suddenly had an opinion. So I’ll commit that.

    vasild commented at 5:26 pm on February 20, 2025:

    clang-format will not break long lines itself because src/.clang-format contains ColumnLimit: 0 (unlimited). However, if you break the line yourself it will do the alignment according to the other rulez.

    I have a clang-format wrapper shell script that replaces ColumnLimit: 0 with ColumnLimit: 110 in src/.clang-format, runs the actual clang-format and then restores the file (so that I do not commit it accidentally).

  143. in src/node/interfaces.cpp:965 in dcb75cbae6 outdated
    960+            {
    961+                WAIT_LOCK(notifications().m_tip_block_mutex, lock);
    962+                // Note that wait_until() checks the predicate before waiting
    963+                notifications().m_tip_block_cv.wait_until(lock, std::min(now + tick, deadline), [&]() EXCLUSIVE_LOCKS_REQUIRED(notifications().m_tip_block_mutex) {
    964+                    AssertLockHeld(notifications().m_tip_block_mutex);
    965+                    if (tip_changed) return true;
    


    ismaelsadeeq commented at 9:52 pm on February 18, 2025:

    @Sjors , does tip_changed ever become true at line 956 and we return from there? I think it will always be false hence can be deleted.

    1. When we first enter this scope, the predicate is executed before blocking. If tip_changed is updated to true, it is returned. And when the predicate returned value is true, we do not wait; instead, we proceed to build and return a block template.

    2. In subsequent executions of the predicate tip_changed will be false and we will check if the tip has changed, if yes tip_changed is updated to true, and returned, causing the wait to end. We then proceed to build and return a block template.


    ryanofsky commented at 10:00 pm on February 18, 2025:

    re: https://github.com/bitcoin/bitcoin/pull/31283/files#r1960644463

    Agree it looks like this line can be deleted. Also scope of tip_changed can be reduced so it is inside the do..while loop.


    Sjors commented at 4:11 pm on February 19, 2025:
    Done both.
  144. in src/node/interfaces.cpp:986 in dcb75cbae6 outdated
    981+             * We determine if fees increased compared to the previous template by generating
    982+             * a fresh template. There may be more efficient ways to determine how much
    983+             * (approximate) fees for the next block increased, perhaps more so after
    984+             * Cluster Mempool.
    985+             *
    986+             * We'll also create a new template if the tip changed during the last tick.
    


    ismaelsadeeq commented at 9:56 pm on February 18, 2025:
    We can generate a template before a tick

    Sjors commented at 4:13 pm on February 19, 2025:
    I’ll make it “during this iteration”.
  145. in src/node/interfaces.cpp:972 in 2d65225c46 outdated
    967+                    const auto tip_block{notifications().TipBlock()};
    968+                    // We assume tip_block is set, because this is an instance
    969+                    // method on BlockTemplate and no template could have been
    970+                    // generated before a tip exists.
    971+                    tip_changed = Assume(tip_block) && tip_block != m_block_template->block.hashPrevBlock;
    972+                    return tip_changed;
    


    vasild commented at 2:11 pm on February 19, 2025:

    This was return check_tip_changed() || chainman().m_interrupt; when the lambda check_tip_changed() was used. Now that the lambda is expanded here, the chainman().m_interrupt condition was lost. I think it should be:

    0                    return tip_changed || chainman().m_interrupt;
    

    Sjors commented at 4:18 pm on February 19, 2025:
    Fixed
  146. vasild approved
  147. vasild commented at 2:17 pm on February 19, 2025: contributor
    ACK 2d65225c4696dcc8312d546eb67f4eae11b9b8fe modulo the comment below and #31283 (review)
  148. Add waitNext() to BlockTemplate interface d4020f502a
  149. miner: have waitNext return after 20 min on testnet
    On testnet we need to create a min diff template after 20 min.
    cadbd4137d
  150. Sjors force-pushed on Feb 19, 2025
  151. Sjors commented at 4:23 pm on February 19, 2025: member

    Rebased and addressed feedback. I also reformatted the BlockAssembler{ call to make it a bit less wide.

     0--- a/src/node/interfaces.cpp
     1+++ b/src/node/interfaces.cpp
     2@@ -878,7 +878,11 @@ public:
     3 class BlockTemplateImpl : public BlockTemplate
     4 {
     5 public:
     6-    explicit BlockTemplateImpl(BlockAssembler::Options assemble_options, std::unique_ptr<CBlockTemplate> block_template, NodeContext& node) : m_assemble_options(std::move(assemble_options)), m_block_template(std::move(block_template)), m_node(node)
     7+    explicit BlockTemplateImpl(BlockAssembler::Options assemble_options,
     8+                               std::unique_ptr<CBlockTemplate> block_template,
     9+                               NodeContext& node) : m_assemble_options(std::move(assemble_options)),
    10+                                                    m_block_template(std::move(block_template)),
    11+                                                    m_node(node)
    12     {
    13         assert(m_block_template);
    14     }
    15@@ -956,24 +960,25 @@ public:
    16         const MillisecondsDouble tick{1000};
    17         const bool allow_min_difficulty{chainman().GetParams().GetConsensus().fPowAllowMinDifficultyBlocks};
    18 
    19-        bool tip_changed{false};
    20         do {
    21+            bool tip_changed{false};
    22             {
    23                 WAIT_LOCK(notifications().m_tip_block_mutex, lock);
    24                 // Note that wait_until() checks the predicate before waiting
    25                 notifications().m_tip_block_cv.wait_until(lock, std::min(now + tick, deadline), [&]() EXCLUSIVE_LOCKS_REQUIRED(notifications().m_tip_block_mutex) {
    26                     AssertLockHeld(notifications().m_tip_block_mutex);
    27-                    if (tip_changed) return true;
    28                     const auto tip_block{notifications().TipBlock()};
    29                     // We assume tip_block is set, because this is an instance
    30                     // method on BlockTemplate and no template could have been
    31                     // generated before a tip exists.
    32                     tip_changed = Assume(tip_block) && tip_block != m_block_template->block.hashPrevBlock;
    33-                    return tip_changed;
    34+                    return tip_changed || chainman().m_interrupt;
    35                 });
    36             }
    37 
    38             if (chainman().m_interrupt) return nullptr;
    39+            // At this point the tip changed, a full tick went by or we reached
    40+            // the deadline.
    41 
    42             // Must release m_tip_block_mutex before locking cs_main, to avoid deadlocks.
    43             LOCK(::cs_main);
    44@@ -992,15 +997,19 @@ public:
    45              * (approximate) fees for the next block increased, perhaps more so after
    46              * Cluster Mempool.
    47              *
    48-             * We'll also create a new template if the tip changed during the last tick.
    49+             * We'll also create a new template if the tip changed during this iteration.
    50              */
    51             if (options.fee_threshold < MAX_MONEY || tip_changed) {
    52-                auto block_template{std::make_unique<BlockTemplateImpl>(m_assemble_options, BlockAssembler{chainman().ActiveChainstate(), context()->mempool.get(), m_assemble_options}.CreateNewBlock(), m_node)};
    53+                auto tmpl{std::make_unique<BlockTemplateImpl>(m_assemble_options,
    54+                                                              BlockAssembler{
    55+                                                                  chainman().ActiveChainstate(),
    56+                                                                  context()->mempool.get(),
    57+                                                                  m_assemble_options}
    58+                                                                  .CreateNewBlock(),
    59+                                                              m_node)};
    60 
    61                 // If the tip changed, return the new template regardless of its fees.
    62-                if (tip_changed) {
    63-                    return block_template;
    64-                }
    65+                if (tip_changed) return tmpl;
    66 
    67                 // Calculate the original template total fees if we haven't already
    68                 if (current_fees == -1) {
    69@@ -1013,12 +1022,12 @@ public:
    70                 }
    71 
    72                 CAmount new_fees = 0;
    73-                for (CAmount fee : block_template->m_block_template->vTxFees) {
    74+                for (CAmount fee : tmpl->m_block_template->vTxFees) {
    75                     // Skip coinbase
    76                     if (fee < 0) continue;
    77                     new_fees += fee;
    78                     Assume(options.fee_threshold != MAX_MONEY);
    79-                    if (new_fees >= current_fees + options.fee_threshold) return block_template;
    80+                    if (new_fees >= current_fees + options.fee_threshold) return tmpl;
    81                 }
    82             }
    
  152. in src/node/interfaces.cpp:957 in d4020f502a outdated
    952+        // Delay calculating the current template fees, just in case a new block
    953+        // comes in before the next tick.
    954+        CAmount current_fees = -1;
    955+
    956+        // Alternate waiting for a new tip and checking if fees have risen.
    957+        // The latter check is expensive so we only run it once per second.
    


    ismaelsadeeq commented at 5:56 pm on February 19, 2025:
    We run both checks in each tick.

    Sjors commented at 10:21 am on February 20, 2025:
    The phrasing is a bit unclear, but “alternate” refers to what happens within each tick: we first check if the tip changed, then check if the fees went up.
  153. ryanofsky approved
  154. ryanofsky commented at 5:58 pm on February 19, 2025: contributor
    Code review ACK cadbd4137d84b71be26effd6a2ae177d5031345e. Main change since last review is adding back a missing m_interrupt check in the waitNext loop. Also made various code cleanups in both commits.
  155. DrahtBot requested review from vasild on Feb 19, 2025
  156. in src/node/interfaces.cpp:950 in d4020f502a outdated
    946@@ -942,9 +947,94 @@ class BlockTemplateImpl : public BlockTemplate
    947         return chainman().ProcessNewBlock(block_ptr, /*force_processing=*/true, /*min_pow_checked=*/true, /*new_block=*/nullptr);
    948     }
    949 
    950+    std::unique_ptr<BlockTemplate> waitNext(BlockWaitOptions options) override
    


    sipa commented at 7:23 pm on February 19, 2025:
    What is the rationale for this creating and returning a new BlockTemplate object, as opposed to replacing the current (*this) one? As far as I can see the current one becomes useless, as its m_block_template will be outdated, so future calls to waitNext for it will return instantly anyway.

    ryanofsky commented at 8:04 pm on February 19, 2025:

    As far as I can see the current one becomes useless, as its m_block_template will be outdated

    This should not be the case. This returns a pointer to a new block template so miners are able to mine with the old one while calling this.

    so future calls to waitNext for it will return instantly anyway.

    They will need to call waitNext on the next template, but they can still call submitSolution on this one.


    sipa commented at 8:06 pm on February 19, 2025:
    Sure, while calling waitNext, but after it returns, isn’t it the case that the current BlockTemplate becomes useless?

    ryanofsky commented at 8:21 pm on February 19, 2025:

    Sure, while calling waitNext, but after it returns, isn’t it the case that the current BlockTemplate becomes useless?

    It’s not useless if submitSolution can still be called. This should be the case as long as the tip hasn’t changed and miners are still mining with this template. If the tip has changed or miners are no longer mining with this template, the client can discard it.


    sipa commented at 8:52 pm on February 19, 2025:
    Oh, great, that answers my question.

    Sjors commented at 10:15 am on February 20, 2025:

    Indeed there can be quite a delay in when miners submit jobs. From the brief testing with ASIC machines I’ve seen, you might give it a new job and then several seconds later it’s still sending shares for the old job.

    And it’s also possible that not every new template we create is actually deployed on every ASIC, as pool software might filter them (not the case at the moment afaik).

  157. ismaelsadeeq approved
  158. ismaelsadeeq commented at 9:19 pm on February 19, 2025: member
    Code review ACK cadbd4137d84b71be26effd6a2ae177d5031345e
  159. vasild approved
  160. vasild commented at 5:32 pm on February 20, 2025: contributor
    ACK cadbd4137d84b71be26effd6a2ae177d5031345e

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2025-02-22 15:12 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me