From c74fb4a6061fb06f037e009967cd5fdc60c3a978 Mon Sep 17 00:00:00 2001
From: Sjors Provoost <sjors@sprovoost.nl>
Date: Tue, 20 Aug 2024 17:43:17 +0200
Subject: [PATCH] mining: add -coinbaselocktime

To prepare for a potential soft fork that would mandate nLockTime of the coinbase to be set to the block height, allow miners to opt-in to doing this sooner.

By using this setting, miners can check if there is any ancillary software they're relying on that is incompatible with such a proposed change.
---
 src/init.cpp                    |  1 +
 src/node/miner.cpp              |  4 ++++
 src/node/miner.h                |  2 ++
 src/policy/policy.h             |  2 ++
 test/functional/mining_basic.py | 14 ++++++++++++--
 5 files changed, 21 insertions(+), 2 deletions(-)

diff --git a/src/init.cpp b/src/init.cpp
index faaf3353d070d4..6a9b32dbee3bd4 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -659,6 +659,7 @@ void SetupServerArgs(ArgsManager& argsman)
     argsman.AddArg("-blockmaxweight=<n>", strprintf("Set maximum BIP141 block weight (default: %d)", DEFAULT_BLOCK_MAX_WEIGHT), ArgsManager::ALLOW_ANY, OptionsCategory::BLOCK_CREATION);
     argsman.AddArg("-blockmintxfee=<amt>", strprintf("Set lowest fee rate (in %s/kvB) for transactions to be included in block creation. (default: %s)", CURRENCY_UNIT, FormatMoney(DEFAULT_BLOCK_MIN_TX_FEE)), ArgsManager::ALLOW_ANY, OptionsCategory::BLOCK_CREATION);
     argsman.AddArg("-blockversion=<n>", "Override block version to test forking scenarios", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::BLOCK_CREATION);
+    argsman.AddArg("-coinbaselocktime", strprintf("Set maximum BIP141 block weight (default: %d)", DEFAULT_COINBASE_LOCKTIME), ArgsManager::ALLOW_ANY, OptionsCategory::BLOCK_CREATION);
 
     argsman.AddArg("-rest", strprintf("Accept public REST requests (default: %u)", DEFAULT_REST_ENABLE), ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
     argsman.AddArg("-rpcallowip=<ip>", "Allow JSON-RPC connections from specified source. Valid values for <ip> are a single IP (e.g. 1.2.3.4), a network/netmask (e.g. 1.2.3.4/255.255.255.0), a network/CIDR (e.g. 1.2.3.4/24), all ipv4 (0.0.0.0/0), or all ipv6 (::/0). This option can be specified multiple times", ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
diff --git a/src/node/miner.cpp b/src/node/miner.cpp
index fa2d979b86a637..dcc5baaca86edc 100644
--- a/src/node/miner.cpp
+++ b/src/node/miner.cpp
@@ -83,6 +83,7 @@ void ApplyArgsManOptions(const ArgsManager& args, BlockAssembler::Options& optio
         if (const auto parsed{ParseMoney(*blockmintxfee)}) options.blockMinFeeRate = CFeeRate{*parsed};
     }
     options.print_modified_fee = args.GetBoolArg("-printpriority", options.print_modified_fee);
+    options.coinbase_locktime = args.GetBoolArg("-coinbaselocktime", DEFAULT_COINBASE_LOCKTIME);
 }
 
 void BlockAssembler::resetBlock()
@@ -151,6 +152,9 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& sc
     coinbaseTx.vout[0].scriptPubKey = scriptPubKeyIn;
     coinbaseTx.vout[0].nValue = nFees + GetBlockSubsidy(nHeight, chainparams.GetConsensus());
     coinbaseTx.vin[0].scriptSig = CScript() << nHeight << OP_0;
+    if (m_options.coinbase_locktime) {
+        coinbaseTx.nLockTime = nHeight;
+    }
     pblock->vtx[0] = MakeTransactionRef(std::move(coinbaseTx));
     pblocktemplate->vchCoinbaseCommitment = m_chainstate.m_chainman.GenerateCoinbaseCommitment(*pblock, pindexPrev);
     pblocktemplate->vTxFees[0] = -nFees;
diff --git a/src/node/miner.h b/src/node/miner.h
index 1b8294376692e4..a5b77bdbb6238e 100644
--- a/src/node/miner.h
+++ b/src/node/miner.h
@@ -162,6 +162,8 @@ class BlockAssembler
         // Configuration parameters for the block size
         size_t nBlockMaxWeight{DEFAULT_BLOCK_MAX_WEIGHT};
         CFeeRate blockMinFeeRate{DEFAULT_BLOCK_MIN_TX_FEE};
+        // Whether to set nLockTime to the current height
+        bool coinbase_locktime{DEFAULT_COINBASE_LOCKTIME};
         // Whether to call TestBlockValidity() at the end of CreateNewBlock().
         bool test_block_validity{true};
         bool print_modified_fee{DEFAULT_PRINT_MODIFIED_FEE};
diff --git a/src/policy/policy.h b/src/policy/policy.h
index a82488a28c9589..06113279dcb072 100644
--- a/src/policy/policy.h
+++ b/src/policy/policy.h
@@ -23,6 +23,8 @@ class CScript;
 static constexpr unsigned int DEFAULT_BLOCK_MAX_WEIGHT{MAX_BLOCK_WEIGHT - 4000};
 /** Default for -blockmintxfee, which sets the minimum feerate for a transaction in blocks created by mining code **/
 static constexpr unsigned int DEFAULT_BLOCK_MIN_TX_FEE{1000};
+/** Default for -coinbaselocktime, which sets the coinbase nLockTime in blocks created by mining code **/
+static constexpr bool DEFAULT_COINBASE_LOCKTIME{false};
 /** The maximum weight for transactions we're willing to relay/mine */
 static constexpr int32_t MAX_STANDARD_TX_WEIGHT{400000};
 /** The minimum non-witness size for transactions we're willing to relay/mine: one larger than 64  */
diff --git a/test/functional/mining_basic.py b/test/functional/mining_basic.py
index 6a364a481599e9..c7b147e7b8bebf 100755
--- a/test/functional/mining_basic.py
+++ b/test/functional/mining_basic.py
@@ -75,6 +75,7 @@ def mine_chain(self):
         assert_equal(VERSIONBITS_TOP_BITS + (1 << VERSIONBITS_DEPLOYMENT_TESTDUMMY_BIT), self.nodes[0].getblocktemplate(NORMAL_GBT_REQUEST_PARAMS)['version'])
         self.restart_node(0)
         self.connect_nodes(0, 1)
+        return t
 
     def test_blockmintxfee_parameter(self):
         self.log.info("Test -blockmintxfee setting")
@@ -118,7 +119,7 @@ def test_blockmintxfee_parameter(self):
     def run_test(self):
         node = self.nodes[0]
         self.wallet = MiniWallet(node)
-        self.mine_chain()
+        t = self.mine_chain()
 
         def assert_submitblock(block, result_str_1, result_str_2=None):
             block.solve()
@@ -136,6 +137,15 @@ def assert_submitblock(block, result_str_1, result_str_2=None):
         assert_equal(mining_info['networkhashps'], Decimal('0.003333333333333334'))
         assert_equal(mining_info['pooledtx'], 0)
 
+        self.log.info('check coinbase transaction')
+        coinbase = node.getblock(node.getbestblockhash(), verbosity=3)['tx'][0]
+        assert_equal(coinbase['locktime'], 0)
+        self.restart_node(0, extra_args=[f'-mocktime={t}', '-coinbaselocktime'])
+        self.connect_nodes(0, 1)
+        self.generate(self.wallet, 1, sync_fun=self.no_op)
+        coinbase = node.getblock(node.getbestblockhash(), verbosity=3)['tx'][0]
+        assert_equal(coinbase['locktime'], 201)
+
         self.log.info("getblocktemplate: Test default witness commitment")
         txid = int(self.wallet.send_self_transfer(from_node=node)['wtxid'], 16)
         tmpl = node.getblocktemplate(NORMAL_GBT_REQUEST_PARAMS)
@@ -265,7 +275,7 @@ def assert_submitblock(block, result_str_1, result_str_2=None):
         block.solve()
 
         def chain_tip(b_hash, *, status='headers-only', branchlen=1):
-            return {'hash': b_hash, 'height': 202, 'branchlen': branchlen, 'status': status}
+            return {'hash': b_hash, 'height': 203, 'branchlen': branchlen, 'status': status}
 
         assert chain_tip(block.hash) not in node.getchaintips()
         node.submitheader(hexdata=block.serialize().hex())