forked from zack-bitcoin/basiccoin
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathblockchain.py
259 lines (217 loc) · 8.05 KB
/
blockchain.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
""" This file explains how we talk to the database. It explains the rules for
adding blocks and transactions.
"""
import time
import copy
import custom
import tools
import networking
import transactions
def db_get(n, DB):
n = str(n)
try:
return tools.unpackage(DB['db'].Get(n))
except:
db_put(n, {'count': 0, 'amount': 0}, DB) # Everyone defaults with
# having zero money, and having broadcast zero transcations.
return db_get(n, DB)
def db_put(key, dic, DB):
return DB['db'].Put(str(key), tools.package(dic))
def db_delete(key, DB):
return DB['db'].Delete(str(key))
def count(address, DB):
# Returns the number of transactions that pubkey has broadcast.
def zeroth_confirmation_txs(address, DB):
def func(t):
address == tools.make_address(t['pubkeys'], len(t['signatures']))
return len(filter(func, DB['txs']))
current = db_get(address, DB)['count']
return current+zeroth_confirmation_txs(address, DB)
def add_tx(tx, DB):
# Attempt to add a new transaction into the pool.
address = tools.make_address(tx['pubkeys'], len(tx['signatures']))
def verify_count(tx, txs):
return tx['count'] != count(address, DB)
def tx_type_check(tx, txs):
return not isinstance(tx, dict)
def type_check(tx, txs):
if 'type' not in tx:
return True
if tx['type'] == 'mint':
return True
return tx['type'] not in transactions.tx_check
def too_big_block(tx, txs):
return len(tools.package(txs+[tx])) > networking.MAX_MESSAGE_SIZE - 5000
def verify_tx(tx, txs):
if type_check(tx, txs):
return False
if tx in txs:
return False
if verify_count(tx, txs):
return False
if too_big_block(tx, txs):
return False
if 'start' in tx and DB['length'] < tx['start']:
return False
if 'end' in tx and DB['length'] > tx['end']:
return False
return transactions.tx_check[tx['type']](tx, txs, DB)
if verify_tx(tx, DB['txs']):
DB['txs'].append(tx)
targets = {}
times = {} # Stores blocktimes
def recent_blockthings(key, DB, size, length=0):
# Grabs info from old blocks.
if key == 'time':
storage = times
if key == 'target':
storage = targets
def get_val(length):
leng = str(length)
if not leng in storage:
storage[leng] = db_get(leng, DB)[key]
return storage[leng]
if length == 0:
length = DB['length']
start = (length-size) if (length-size) >= 0 else 0
return map(get_val, range(start, length))
def hexSum(a, b):
# Sum of numbers expressed as hexidecimal strings
return tools.buffer_(str(hex(int(a, 16)+int(b, 16)))[2: -1], 64)
def hexInvert(n):
# Use double-size for division, to reduce information leakage.
return tools.buffer_(str(hex(int('f' * 128, 16) / int(n, 16)))[2: -1], 64)
def target(DB, length=0):
# Returns the target difficulty at a paticular blocklength.
if length == 0:
length = DB['length']
if length < 4:
return '0' * 4 + 'f' * 60 # Use same difficulty for first few blocks.
if length <= DB['length']:
return targets[str(length)] # Memoized
def targetTimesFloat(target, number):
a = int(str(target), 16)
b = int(a * number)
return tools.buffer_(str(hex(b))[2: -1], 64)
def weights(length):
return [custom.inflection ** (length-i) for i in range(length)]
def estimate_target(DB):
# We are actually interested in the average number of hashes requred
# to mine a block. number of hashes required is inversely proportional
# to target. So we average over inverse-targets, and inverse the final
# answer.
def sumTargets(l):
if len(l) < 1:
return 0
while len(l) > 1:
l = [hexSum(l[0], l[1])] + l[2:]
return l[0]
targets = recent_blockthings('target', DB, custom.history_length)
w = weights(len(targets))
tw = sum(w)
targets = map(hexInvert, targets)
def weighted_multiply(i):
return targetTimesFloat(targets[i], w[i]/tw)
weighted_targets = [weighted_multiply(i) for i in range(len(targets))]
return hexInvert(sumTargets(weighted_targets))
def estimate_time(DB):
times = recent_blockthings('time', DB, custom.history_length)
blocklengths = [times[i] - times[i - 1] for i in range(1, len(times))]
w = weights(len(blocklengths)) # Geometric weighting
tw = sum(w) # Normalization constant
return sum([w[i] * blocklengths[i] / tw for i in range(len(blocklengths))])
retarget = estimate_time(DB) / custom.blocktime(length)
return targetTimesFloat(estimate_target(DB), retarget)
def add_block(block, DB):
# Attempts adding a new block to the blockchain.
# Median is good for weeding out liars, so long as
def median(mylist): # the liars don't have 51% hashpower.
if len(mylist) < 1:
return 0
return sorted(mylist)[len(mylist) / 2]
def block_check(block, DB):
def tx_check(txs):
start = copy.deepcopy(txs)
out = []
start_copy = []
while start != start_copy:
if start == []:
return False # Block passes this test
start_copy = copy.deepcopy(start)
if transactions.tx_check[start[-1]['type']](start[-1], out, DB):
out.append(start.pop())
else:
return True # Block is invalid
return True # Block is invalid
if not isinstance(block, dict):
return False
if 'error' in block:
return False
if 'length' not in block:
return False
length = DB['length']
if int(block['length']) != int(length) + 1:
return False
if block['diffLength'] != hexSum(DB['diffLength'],
hexInvert(block['target'])):
return False
if length >= 0:
if tools.det_hash(db_get(length, DB)) != block['prevHash']:
return False
a = copy.deepcopy(block)
a.pop('nonce')
if u'target' not in block.keys():
return False
half_way = {u'nonce': block['nonce'], u'halfHash': tools.det_hash(a)}
if tools.det_hash(half_way) > block['target']:
return False
if block['target'] != target(DB, block['length']):
return False
earliest = median(recent_blockthings('time', DB, custom.mmm))
if 'time' not in block:
return False
if block['time'] > time.time():
return False
if block['time'] < earliest:
return False
if tx_check(block['txs']):
return False
return True
if block_check(block, DB):
print('add_block: ' + str(block))
db_put(block['length'], block, DB)
DB['length'] = block['length']
DB['diffLength'] = block['diffLength']
orphans = copy.deepcopy(DB['txs'])
DB['txs'] = []
for tx in block['txs']:
transactions.add_block[tx['type']](tx, DB)
for tx in orphans:
add_tx(tx, DB)
def delete_block(DB):
# Removes the most recent block from the blockchain.
if DB['length'] < 0:
return
try:
targets.pop(str(DB['length']))
except:
pass
try:
times.pop(str(DB['length']))
except:
pass
block = db_get(DB['length'], DB)
orphans = copy.deepcopy(DB['txs'])
DB['txs'] = []
for tx in block['txs']:
orphans.append(tx)
transactions.delete_block[tx['type']](tx, DB)
db_delete(DB['length'], DB)
DB['length'] -= 1
if DB['length'] == -1:
DB['diffLength'] = '0'
else:
block = db_get(DB['length'], DB)
DB['diffLength'] = block['diffLength']
for orphan in sorted(orphans, key=lambda x: x['count']):
add_tx(orphan, DB)