Skip to content

Commit 6116cb0

Browse files
committed
Added example to mine a block
1 parent db367d8 commit 6116cb0

2 files changed

Lines changed: 321 additions & 0 deletions

File tree

examples/mempool/test_tx.json

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
{
2+
"txid": "00000a2d1a9e29116b539b85b6e893213b1ed95a08b7526a8d59a4b088fc6571",
3+
"version": 1,
4+
"locktime": 0,
5+
"vin": [
6+
{
7+
"txid": "2e4843d552ca9487efd9e69c0359f05375b7de5449eb49510d17a25bb5b15ec0",
8+
"vout": 1,
9+
"prevout": {
10+
"scriptpubkey": "512065fd3d423ea46a70505248db989e7302bfbbdd64ee4193dd9a59f69894f0de48",
11+
"scriptpubkey_asm": "OP_PUSHNUM_1 OP_PUSHBYTES_32 65fd3d423ea46a70505248db989e7302bfbbdd64ee4193dd9a59f69894f0de48",
12+
"scriptpubkey_type": "v1_p2tr",
13+
"scriptpubkey_address": "bc1pvh7n6s375348q5zjfrde38nnq2lmhhtyaeqe8hv6t8mf398smeyqnug47s",
14+
"value": 13413
15+
},
16+
"scriptsig": "",
17+
"scriptsig_asm": "",
18+
"witness": [
19+
"29783b151d376d5178451ce14f62b091059021680bff36aec2814e33ecacf130e8aa92d6da23f35be7a8c2245b8f910261d4e6a5169f79d6ff7a3f412981f486"
20+
],
21+
"is_coinbase": false,
22+
"sequence": 1610616404
23+
}
24+
],
25+
"vout": [
26+
{
27+
"scriptpubkey": "51204b918d31f22461021ed54e354ac9dcbbe94b98edcfd3615b76c068b08222a87f",
28+
"scriptpubkey_asm": "OP_PUSHNUM_1 OP_PUSHBYTES_32 4b918d31f22461021ed54e354ac9dcbbe94b98edcfd3615b76c068b08222a87f",
29+
"scriptpubkey_type": "v1_p2tr",
30+
"scriptpubkey_address": "bc1pfwgc6v0jy3ssy8k4fc654jwuh055hx8delfkzkmkcp5tpq3z4pls7tx8q3",
31+
"value": 2908
32+
},
33+
{
34+
"scriptpubkey": "512065fd3d423ea46a70505248db989e7302bfbbdd64ee4193dd9a59f69894f0de48",
35+
"scriptpubkey_asm": "OP_PUSHNUM_1 OP_PUSHBYTES_32 65fd3d423ea46a70505248db989e7302bfbbdd64ee4193dd9a59f69894f0de48",
36+
"scriptpubkey_type": "v1_p2tr",
37+
"scriptpubkey_address": "bc1pvh7n6s375348q5zjfrde38nnq2lmhhtyaeqe8hv6t8mf398smeyqnug47s",
38+
"value": 8503
39+
}
40+
],
41+
"size": 205,
42+
"weight": 616,
43+
"fee": 2002,
44+
"status": {
45+
"confirmed": true,
46+
"block_height": 834552,
47+
"block_hash": "00000000000000000001dd0468a70c94f619251d286585cff57aeb4bd9ede330",
48+
"block_time": 1710355598
49+
},
50+
"hex": "01000000000101c05eb1b55ba2170d5149eb4954deb77553f059039ce6d9ef8794ca52d543482e0100000000540e0060025c0b0000000000002251204b918d31f22461021ed54e354ac9dcbbe94b98edcfd3615b76c068b08222a87f372100000000000022512065fd3d423ea46a70505248db989e7302bfbbdd64ee4193dd9a59f69894f0de48014029783b151d376d5178451ce14f62b091059021680bff36aec2814e33ecacf130e8aa92d6da23f35be7a8c2245b8f910261d4e6a5169f79d6ff7a3f412981f48600000000"
51+
}

examples/mine_block.py

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
#!/usr/bin/env python3
2+
3+
import json
4+
import time
5+
from bitcoinutils.setup import setup
6+
from bitcoinutils.transactions import Transaction, TxInput, TxOutput, TxWitnessInput
7+
from bitcoinutils.script import Script
8+
from bitcoinutils.keys import P2trAddress
9+
from bitcoinutils.utils import to_satoshis
10+
from bitcoinutils.block import Block, BlockHeader
11+
import hashlib
12+
13+
14+
tx_details = ""
15+
with open(
16+
"./examples/mempool/test_tx.json",
17+
"r",
18+
) as file:
19+
tx_details = json.load(file)
20+
21+
setup("mainnet")
22+
23+
24+
from_addr = "0000000000000000000000000000000000000000000000000000000000000000"
25+
to_addr = "bc1pvh7n6s375348q5zjfrde38nnq2lmhhtyaeqe8hv6t8mf398smeyqnug47s"
26+
to_addr = P2trAddress(to_addr)
27+
28+
29+
def calculate_wtxid(tx_hex):
30+
tx_binary = bytes.fromhex(tx_hex)
31+
hash_once = hashlib.sha256(tx_binary).digest()
32+
hash_twice = hashlib.sha256(hash_once).digest()
33+
return hash_twice[::-1].hex()
34+
35+
36+
def calculate_merkle_root(txid_list):
37+
"""
38+
Calculates merkel root by hashing pairwise txids till only 1 is left.
39+
40+
Args:
41+
txid_list: List of the transaction ids in the block
42+
43+
Returns:
44+
(bytes): The merkle root of the block.
45+
"""
46+
if len(txid_list) == 1:
47+
return bytes.fromhex(txid_list[0])
48+
49+
# Convert each txid from big-endian hex to little-endian bytes.
50+
current_layer = [bytes.fromhex(txid)[::-1] for txid in txid_list]
51+
52+
while len(current_layer) > 1:
53+
next_layer = []
54+
for i in range(0, len(current_layer), 2):
55+
# If there's an odd number, duplicate the last one.
56+
if i + 1 < len(current_layer):
57+
pair = current_layer[i] + current_layer[i + 1]
58+
else:
59+
pair = current_layer[i] + current_layer[i]
60+
# Perform double SHA-256 on the concatenated pair.
61+
next_layer.append(hashlib.sha256(hashlib.sha256(pair).digest()).digest())
62+
current_layer = next_layer
63+
64+
# Reverse back to big-endian and return the bytes.
65+
return current_layer[0][::-1]
66+
67+
68+
def calculate_witness_root_hash(wtxid_list):
69+
"""
70+
Calculates witness root hash by hashing pairwise wtxids till only 1 is left.
71+
72+
Args:
73+
wtxid_list: List of the witness transaction ids in the block
74+
75+
Returns:
76+
(bytes): The witness root hash of the block.
77+
"""
78+
if len(wtxid_list) == 1:
79+
# For a single txid, return its bytes (big-endian) representation.
80+
return bytes.fromhex(wtxid_list[0])
81+
82+
# Convert each txid from big-endian hex to little-endian bytes.
83+
current_layer = [bytes.fromhex(wtxid)[::-1] for wtxid in wtxid_list]
84+
85+
while len(current_layer) > 1:
86+
next_layer = []
87+
for i in range(0, len(current_layer), 2):
88+
# If there's an odd number, duplicate the last one.
89+
if i + 1 < len(current_layer):
90+
pair = current_layer[i] + current_layer[i + 1]
91+
else:
92+
pair = current_layer[i] + current_layer[i]
93+
# Perform double SHA-256 on the concatenated pair.
94+
next_layer.append(hashlib.sha256(hashlib.sha256(pair).digest()).digest())
95+
current_layer = next_layer
96+
97+
return current_layer[0]
98+
99+
100+
def calculate_witness_commitment(witness_root_hash, witness_reserved_value):
101+
"""
102+
Calculates the witness commitment by performing a double SHA-256
103+
on the concatenation of the witness_root_hash and witness_reserved_value.
104+
105+
Args:
106+
witness_root_hash(hex): witness root hash of the block
107+
witness_reserved_value(hex): the reserved value stored in the witness.
108+
109+
Returns:
110+
(hex): hex of the witness commitment
111+
112+
"""
113+
# Convert both hex strings to bytes and concatenate them
114+
combined = bytes.fromhex(witness_root_hash + witness_reserved_value)
115+
116+
# Perform double SHA256 hashing
117+
hash1 = hashlib.sha256(combined).digest()
118+
hash2 = hashlib.sha256(hash1).digest()
119+
return hash2.hex()
120+
121+
122+
def create_block(coinbase_tx, tx1):
123+
# Note: Here I have for simplicity taken just the coinbase tx and another transaction (the test_tx the mempool),
124+
# more generally we just take the list of tx.
125+
126+
txid_list = [coinbase_tx.get_txid(), tx1.get_txid()]
127+
merkle_root = calculate_merkle_root(txid_list)
128+
prev_block_hash = bytes.fromhex(
129+
"0000000000000000000000000000000000000000000000000000000000000000"
130+
)
131+
132+
version = "20000000"
133+
version = int.from_bytes(bytes.fromhex(version), byteorder="little")
134+
timestamp = int(time.time())
135+
# resource: learn me a bitcoin's target to bit converter.
136+
bits = "1f00ffff"
137+
bits = int.from_bytes(bytes.fromhex(bits), byteorder="big")
138+
139+
nonce = 0
140+
141+
block_header = BlockHeader(
142+
version,
143+
previous_block_hash=prev_block_hash,
144+
merkle_root=merkle_root,
145+
timestamp=timestamp,
146+
target_bits=bits,
147+
nonce=nonce,
148+
)
149+
magic = bytes.fromhex("f9beb4d9")
150+
block_size = None
151+
transaction_count = len([coinbase_tx, tx1])
152+
block = Block(
153+
magic=magic,
154+
block_size=block_size,
155+
header=block_header,
156+
transactions=[coinbase_tx, tx1],
157+
transaction_count=transaction_count,
158+
)
159+
return block
160+
161+
162+
def mine_block(block_header_bytes, target_hex):
163+
"""
164+
Mine a block by iterating through nonce values.
165+
166+
Args:
167+
block_header_bytes (bytes): The 80-byte block header with a placeholder nonce.
168+
target_hex (str): The difficulty target as a hex string (256-bit number).
169+
170+
Returns:
171+
(int, bytes): The nonce value and the corresponding block header hash that meets the target.
172+
"""
173+
target_int = int(target_hex, 16)
174+
print("Target (int):", target_int)
175+
176+
for nonce in range(2**32):
177+
# Replace the last 4 bytes of the header with the current nonce in little-endian format.
178+
header_with_nonce = block_header_bytes[:-4] + nonce.to_bytes(
179+
4, byteorder="little"
180+
)
181+
182+
hash_once = hashlib.sha256(header_with_nonce).digest()
183+
hash_twice = hashlib.sha256(hash_once).digest()
184+
185+
# Bitcoin displays the block hash in big-endian order.
186+
block_hash = hash_twice[::-1]
187+
block_hash_int = int.from_bytes(block_hash, byteorder="big")
188+
189+
if block_hash_int < target_int:
190+
print("Success! Nonce found:", nonce)
191+
return nonce, block_hash
192+
193+
# Optional: print progress every so often
194+
if nonce % 1000000 == 0:
195+
print("Tried nonce:", nonce)
196+
197+
return None, None
198+
199+
200+
def main():
201+
# create a coinbase transaction
202+
witness_reserved_value = (
203+
"0000000000000000000000000000000000000000000000000000000000000000"
204+
)
205+
witness_root_hash = ""
206+
207+
# The commitment is constructed as:
208+
# commitment = "6a24aa21a9ed" + doublesha256(witness_root_hash, witness_reserved_value)
209+
tx1 = Transaction()
210+
tx1 = tx1.from_raw(tx_details["hex"])
211+
txinp = TxInput(
212+
txid=from_addr,
213+
txout_index=0,
214+
script_sig=Script([witness_reserved_value]),
215+
)
216+
witness_stack = [witness_reserved_value]
217+
218+
wtxid = calculate_wtxid(tx_details["hex"])
219+
print("Wtx id :", wtxid)
220+
coinbase_wtxid = "0" * 64
221+
222+
witness_root_hash = calculate_witness_root_hash([coinbase_wtxid, wtxid])
223+
witness_root_hash = witness_root_hash.hex()
224+
print("Witness root hash ", witness_root_hash)
225+
226+
# Calculating the witness commitment hash (double SHA256 of witness_root_hash and witness_reserved_value)
227+
witness_commitment_hash = calculate_witness_commitment(
228+
witness_root_hash, witness_reserved_value
229+
)
230+
231+
# Prepending the witness commitment header
232+
commitment = "6a24aa21a9ed" + witness_commitment_hash
233+
234+
print("Witness Commitment :", commitment)
235+
# note: to_addr defined at the top. Taken from the first transaction
236+
txout1 = TxOutput(to_satoshis(0.001), to_addr.to_script_pub_key())
237+
witness_commitment_script = Script([])
238+
witness_commitment_script = witness_commitment_script.from_raw(commitment)
239+
print("witness commitment script : ", witness_commitment_script)
240+
txout2 = TxOutput(to_satoshis(0), witness_commitment_script)
241+
coinbase_tx = Transaction(
242+
[txinp],
243+
[txout1, txout2],
244+
has_segwit=True,
245+
witnesses=[TxWitnessInput(witness_stack)],
246+
)
247+
# example difficulty target
248+
difficulty_target = (
249+
"0000ffff00000000000000000000000000000000000000000000000000000000"
250+
)
251+
# Creating a block that includes the coinbase transaction and tx1.
252+
block = create_block(coinbase_tx, tx1)
253+
print("block header: ", block.header)
254+
# getting the block header bytes
255+
256+
serialized_header = block.header.serialize_header()
257+
print("Serialized header : ", serialized_header)
258+
259+
# mining the block
260+
nonce, mined_hash = mine_block(serialized_header, difficulty_target)
261+
print("NONCE: ", nonce)
262+
serialized_header = serialized_header[:-4] + nonce.to_bytes(4, byteorder="little")
263+
264+
print("hex of serialized header ", serialized_header.hex())
265+
print("coinbase transaction :", coinbase_tx.to_hex())
266+
print("Block: ", block)
267+
268+
269+
if __name__ == "__main__":
270+
main()

0 commit comments

Comments
 (0)