Repository: monero-project/monero (2026-07-04T20:16:45Z)

Issue:
Get_block_template Fails with reserve_size > 127 (Expected Max 255)
When calling the get_block_template RPC with a reserve_size greater than 127, the daemon fails with:
{
  "code": -5,
  "message": "Internal error: failed to create block template"
}

Error is actually in add_extra_nonce_to_tx_extra. This line:

tx_extra[start_pos] = static_cast<uint8_t>(extra_nonce.size());

This silently truncates lengths >127 or causes misinterpretation during block template construction.


Discussion:

Patch:
diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp
index 7fff16fe13f..2b124637ee1 100644
--- a/src/rpc/core_rpc_server.cpp
+++ b/src/rpc/core_rpc_server.cpp
@@ -40,6 +40,7 @@ using namespace epee;
 #include "common/updates.h"
 #include "common/download.h"
 #include "common/util.h"
+#include "common/varint.h"
 #include "common/perf_timer.h"
 #include "int-util.h"
 #include "cryptonote_basic/cryptonote_format_utils.h"
@@ -1802,7 +1803,7 @@ namespace cryptonote
       LOG_ERROR("Failed to find tx pub key in blockblob");
       return false;
     }
-    reserved_offset += sizeof(tx_pub_key) + 2; //2 bytes: tag for TX_EXTRA_NONCE(1 byte), counter in TX_EXTRA_NONCE(1 byte)
+    reserved_offset += sizeof(tx_pub_key) + 1 + tools::get_varint_byte_size(extra_nonce.size());
     if(reserved_offset + extra_nonce.size() > block_blob.size())
     {
       error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR;
diff --git a/utils/python-rpc/framework/daemon.py b/utils/python-rpc/framework/daemon.py
index adab4204ccc..88c231bb31a 100644
--- a/utils/python-rpc/framework/daemon.py
+++ b/utils/python-rpc/framework/daemon.py
@@ -40,13 +40,13 @@ def __init__(self, protocol='http', host='127.0.0.1', port=0, idx=0, restricted_
         self.rpc = JSONRPC('{protocol}://{host}:{port}'.format(protocol=protocol, host=host, port=self.port),
             username, password)
 
-    def getblocktemplate(self, address, prev_block = "", client = ""):
+    def getblocktemplate(self, address, prev_block = "", client = "", reserve_size = 1):
         getblocktemplate = {
             'method': 'getblocktemplate',
             'params': {
                 'client': client,
                 'wallet_address': address,
-                'reserve_size' : 1,
+                'reserve_size' : reserve_size,
                 'prev_block' : prev_block,
             },
             'jsonrpc': '2.0', 


Test patch:
diff --git a/tests/functional_tests/blockchain.py b/tests/functional_tests/blockchain.py
index 2504899187e..a296ecdf398 100755
--- a/tests/functional_tests/blockchain.py
+++ b/tests/functional_tests/blockchain.py
@@ -57,12 +57,20 @@ def reset(self):
         daemon.pop_blocks(res.height - 1)
         daemon.flush_txpool()
 
+    def _check_blocktemplate_reserved_offset(self, daemon, address, reserve_size):
+        res = daemon.getblocktemplate(address, reserve_size = reserve_size)
+        assert res.reserved_offset > 0
+        block_blob = bytes.fromhex(res.blocktemplate_blob)
+        reserved_bytes = block_blob[res.reserved_offset:res.reserved_offset + reserve_size]
+        assert reserved_bytes == bytes(reserve_size)
+
     def _test_generateblocks(self, blocks):
         assert blocks >= 2
 
         print("Test generating", blocks, 'blocks')
 
         daemon = Daemon()
+        address = '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm'
 
         # check info/height before generating blocks
         res_info = daemon.get_info()
@@ -86,7 +94,7 @@ def _test_generateblocks(self, blocks):
         assert res.fee <= 1200000
 
         # generate blocks
-        res_generateblocks = daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', blocks)
+        res_generateblocks = daemon.generateblocks(address, blocks)
 
         # check info/height after generateblocks blocks
         assert res_generateblocks.height == height + blocks - 1
@@ -131,7 +139,7 @@ def _test_generateblocks(self, blocks):
         assert res_getblockheaderbyheight.block_header == block_header
 
         # getting a block template after that should have the right height, etc
-        res_getblocktemplate = daemon.getblocktemplate('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm')
+        res_getblocktemplate = daemon.getblocktemplate(address)
         assert res_getblocktemplate.height == height + blocks
         assert res_getblocktemplate.reserved_offset > 0
         assert res_getblocktemplate.prev_hash == res_info.top_block_hash
@@ -141,6 +149,10 @@ def _test_generateblocks(self, blocks):
         assert len(res_getblocktemplate.blockhashing_blob) > 0
         assert int(res_getblocktemplate.wide_difficulty, 16) == (res_getblocktemplate.difficulty_top64 << 64) + res_getblocktemplate.difficulty
 
+        # 255 matches TX_EXTRA_NONCE_MAX_COUNT in the C++ code.
+        for reserve_size in (1, 127, 128, 255):
+            self._check_blocktemplate_reserved_offset(daemon, address, reserve_size)
+
         # diff etc should be the same
         assert res_getblocktemplate.prev_hash == res_info.top_block_hash