-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.py
297 lines (250 loc) · 10.6 KB
/
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
"""
Clarifications:
- do we need to check whether ancestors are included and double spends, RBF?
- do we have to maximize the fee?
- are the filenames the txids?
"""
"""
Approach
- select transactions
- in each iteration add the current transaction and its ancestors if not been added and are valid
- construct the coinbase transaction
- create block header
"""
import json
import time
import hashlib
def main():
# to calculate compact size
def cmptSz(data:bytes)->bytes:
val = len(data) # c'mmon it's compact SIZE for a reason
if (val<=252):
return val.to_bytes(1,"little",signed=False)
elif val>252 and val<=65535:
return bytes.fromhex("fd") + val.to_bytes(2,"little",signed=False)
elif val>65535 and val<=4294967295:
return bytes.fromhex("fe") + val.to_bytes(4,"little",signed=False)
elif val>4294967295 and val<=18446744073709551615:
return bytes.fromhex("ff") + val.to_bytes(8,"little",signed=False)
# to find out pushbytes opcode
def pushbytes(data:bytes)->bytes:
sz = len(data)
if (sz<=76):
return sz.to_bytes(1,byteorder="little",signed=False)
elif (sz<=255):
return bytes.fromhex("4c") + sz.to_bytes(1,byteorder="little",signed=False)
elif (sz<=520):
return bytes.fromhex("4d") + sz.to_bytes(2,byteorder="little",signed=False)
def hash256(data:bytes)->bytes:
return hashlib.sha256(hashlib.sha256(data).digest()).digest()
def difficulty_to_bits(difficulty:str)->bytes:
exp = 0
for i in range(len(difficulty)):
if difficulty[i]!='0':
break
if (i%2!=0):
i-=1
if (int(difficulty[i:i+2],base=16)>=80):
i-=2
exp = (len(difficulty)-(i))//2
return bytes.fromhex(difficulty[i+4:i+6]+difficulty[i+2:i+4]+difficulty[i:i+2]) + exp.to_bytes(1,byteorder="little",signed=False)
# selecting transactions (need to take care of block wt)
files = []
f = open("./mempool/mempool.json","r")
files = json.load(f)
f.close()
def validate(file):
"""
Check if the sum of outputs is less than sum of inputs
"""
input_sum = 0
output_sum = 0
f = open(f"./mempool/{file}.json","r")
data = json.load(f)
f.close()
for input in data["vin"]:
input_sum += input["prevout"]["value"]
for output in data["vout"]:
output_sum += output["value"]
return input_sum >= output_sum
def add(txids,added,file,vout):
"""
if it does not exist in mempool assume it is valid
if it has already been added but with different vout then return
if already been added but with same vout this is a case of double spend, do not include it or its descendants
if not been included then first include parent if valid, now do validity check
- if valid then add to txids and added
- if not valid then do not add it or its descendants
"""
if not file in files:
return txids,added,True
if file in txids and added[file]!=vout:
return txids,added,True
elif file in txids:
return txids,added,False
else:
f = open(f"./mempool/{file}.json","r")
data = json.load(f)
f.close()
for input in data["vin"]:
txids,added,is_added = add(txids,added,input["txid"],input["vout"])
if not is_added:
return txids,added,False
if not validate(file):
return txids,added,False
txids.append(file)
added[file]=vout
return txids,added,True
bits = difficulty_to_bits("0000ffff00000000000000000000000000000000000000000000000000000000")
def find_target(bits):
bits = bits[::-1]
bits = bits.hex()
exp = int(bits[:2],base=16)
base = int(bits[2:],base=16)<<(8*(exp-3))
base = base.to_bytes(32,"big",signed=False)
return base
target = find_target(bits)
def find_wtxids(txids):
wtxids = []
for txid in txids:
f = open(f"./mempool/{txid}.json","r")
data = json.load(f)
f.close()
wtxid = hash256(bytes.fromhex(data["hex"]))[::-1]
wtxids.append(wtxid)
return wtxids
def find_root(txids):
level = [txid[::-1] for txid in txids]
while (len(level)>=2):
next_level = []
for i in range(0,len(level),2):
if (i+1 == len(level)):
next_level.append(hash256(level[i]+level[i]))
else:
next_level.append(hash256(level[i]+level[i+1]))
level = next_level
return level[0]
def create_coinbase(witness_root_hash):
version = bytes.fromhex("01000000")
marker = bytes.fromhex("00")
flag = bytes.fromhex("01")
input_ct = bytes.fromhex("01")
txid_to_spend = bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000000")
idx_to_spend = bytes.fromhex("ffffffff")
block_ht = bytes.fromhex("97040d")
script_sig = pushbytes(block_ht) + block_ht
sequence = bytes.fromhex("ffffffff")
inputs = (
txid_to_spend +
idx_to_spend +
cmptSz(script_sig) +
script_sig +
sequence
)
witness_reserved_val = bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000000")
witness = (
bytes.fromhex("01") +
pushbytes(witness_reserved_val) +
witness_reserved_val
)
output_ct = bytes.fromhex("02")
output1_amt_sats = (50*(10**8)).to_bytes(8,byteorder="little",signed=True)
output1_spk = bytes.fromhex("76a914") + bytes.fromhex("3bc28d6d92d9073fb5e3adf481795eaf446bceed") + bytes.fromhex("88ac")
output2_amt_sats = bytes.fromhex("0000000000000000")
output2_spk = bytes.fromhex("6a") + bytes.fromhex("24") + bytes.fromhex("aa21a9ed") + hash256(witness_root_hash + witness_reserved_val)
outputs = (
output1_amt_sats +
cmptSz(output1_spk) +
output1_spk +
output2_amt_sats +
cmptSz(output2_spk) +
output2_spk
)
locktime = bytes.fromhex("00000000")
coinbase = (
version +
marker +
flag +
input_ct +
inputs +
output_ct +
outputs +
witness +
locktime
)
coinbase_txid = hash256(
version +
input_ct +
inputs +
output_ct +
outputs +
locktime
)[::-1] # remember to reverse to get the txids
return [coinbase, coinbase_txid]
# find magic nonce
def find_nonce(header_without_nonce, target):
for nonce in range(4294967295+1):
print(nonce)
if (hash256(header_without_nonce + nonce.to_bytes(4,"little",signed=False))[::-1] <= target ):
return [True, nonce]
return [False, nonce]
def create_block(merkle_root, bits, target):
block_version = bytes.fromhex("00010000")
previousBlockHash = bytes.fromhex("0000fffe00000000000000000000000000000000000000000000000000000000")[::-1]
# timestamp
timestamp = int(time.time()).to_bytes(4,byteorder="little",signed=False)
header_without_nonce = (
block_version +
previousBlockHash +
merkle_root +
timestamp +
bits
)
found, nonce = find_nonce(header_without_nonce,target)
return [found,header_without_nonce+nonce.to_bytes(4,"little",signed=False)]
# mine
found = False
txids = []
added = {}
idx = 0
coinbase_txid = bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000000")
while ((found == False or len(txids)<1) and idx<len(files)):
is_added = False
while(idx<len(files) and files[idx] in txids):
idx+=1
if (idx<len(files)):
txids, added, is_added= add(txids,added,files[idx],-1)
idx+=1
# some kind of add transaction function
wtxids = [bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000000")]
wtxids = wtxids + find_wtxids(txids)
witness_root_hash = find_root(wtxids)
coinbase, coinbase_txid = create_coinbase(witness_root_hash)
tmp_txids = [coinbase_txid] + [bytes.fromhex(txid) for txid in txids]
merkle_root = find_root(tmp_txids)
found, block_header = create_block(merkle_root,bits, target)
# output
f = open("out.txt","w")
txids = [coinbase_txid.hex()] + txids
txids = '\n'.join(txids)
f.write(f"{block_header.hex()}\n{coinbase.hex()}\n{txids}")
f.close()
if __name__ == "__main__":
main()
"""
References:
- block header: learnmeabitcoin.com => block header: version + prev_block_hash + merkle-root + time + bits + nonce (remember to reverse)
- mining simulator: learnmeabitcoin.com
- "All the descendants of a conflicting memory pool transaction will be removed at the same time": learnmeabitcoin.com
- steps to construct block : https://learnmeabitcoin.com/technical/mining/candidate-block/#construction
- if a transaction has ancestors that are currently in the mempool, those ancestors must be included above it in the candidate block. : learnmeabitcoin
- calculating weight of block : https://learnmeabitcoin.com/technical/transaction/size/#:~:text=The%20size%20of%20a%20transaction%20in%20bytes%20mostly%20depends%20on,or%20226%20bytes%20(most%20common)
- blockheader is a fixed size: 320 wu
- version 2 is not required for segwit
- getting the unix epoch time : https://stackoverflow.com/questions/16755394/what-is-the-easiest-way-to-get-current-gmt-time-in-unix-timestamp-format
- target to bits : https://learnmeabitcoin.com/technical/block/bits/#target-to-bits
- minimum block version: https://learnmeabitcoin.com/technical/block/version/#version-numbers
- "The TXIDs should be input in reverse byte order (as they appear on blockchain explorers), but they are converted to natural byte order before the merkle root is calculated.": learmeabitcoin
- finding merkle root : ../test/helper.ts
- bits to target: https://learnmeabitcoin.com/technical/block/bits/#bits-to-target
"""