Skip to content

Commit

Permalink
Replace initial headers download with hardcoded checkpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
ecdsa committed Dec 12, 2017
1 parent b4e4375 commit d1b8a6f
Show file tree
Hide file tree
Showing 4 changed files with 1,118 additions and 78 deletions.
108 changes: 65 additions & 43 deletions lib/blockchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ def hash_header(header):

blockchains = {}

def read_blockchains(config):
blockchains[0] = Blockchain(config, 0, None)
def read_blockchains(config, checkpoints):
blockchains[0] = Blockchain(config, checkpoints, 0, None)
fdir = os.path.join(util.get_headers_dir(config), 'forks')
if not os.path.exists(fdir):
os.mkdir(fdir)
Expand All @@ -70,7 +70,7 @@ def read_blockchains(config):
for filename in l:
checkpoint = int(filename.split('_')[2])
parent_id = int(filename.split('_')[1])
b = Blockchain(config, checkpoint, parent_id)
b = Blockchain(config, checkpoints, checkpoint, parent_id)
blockchains[b.checkpoint] = b
return blockchains

Expand All @@ -94,10 +94,11 @@ class Blockchain(util.PrintError):
Manages blockchain headers and their verification
"""

def __init__(self, config, checkpoint, parent_id):
def __init__(self, config, checkpoints, checkpoint, parent_id):
self.config = config
self.catch_up = None # interface catching up
self.checkpoint = checkpoint
self.checkpoints = checkpoints
self.parent_id = parent_id
self.lock = threading.Lock()
with self.lock:
Expand Down Expand Up @@ -127,7 +128,7 @@ def check_header(self, header):

def fork(parent, header):
checkpoint = header.get('block_height')
self = Blockchain(parent.config, checkpoint, parent.checkpoint)
self = Blockchain(parent.config, parent.checkpoints, checkpoint, parent.checkpoint)
open(self.path(), 'w+').close()
self.save_header(header)
return self
Expand All @@ -143,29 +144,27 @@ def update_size(self):
p = self.path()
self._size = os.path.getsize(p)//80 if os.path.exists(p) else 0

def verify_header(self, header, prev_header, bits, target):
prev_hash = hash_header(prev_header)
def verify_header(self, header, prev_hash, target):
_hash = hash_header(header)
if prev_hash != header.get('prev_block_hash'):
raise BaseException("prev hash mismatch: %s vs %s" % (prev_hash, header.get('prev_block_hash')))
if bitcoin.NetworkConstants.TESTNET:
return
bits = self.target_to_bits(target)
if bits != header.get('bits'):
raise BaseException("bits mismatch: %s vs %s" % (bits, header.get('bits')))
if int('0x' + _hash, 16) > target:
raise BaseException("insufficient proof of work: %s vs target %s" % (int('0x' + _hash, 16), target))

def verify_chunk(self, index, data):
num = len(data) // 80
prev_header = None
if index != 0:
prev_header = self.read_header(index * 2016 - 1)
bits, target = self.get_target(index)
prev_hash = self.get_hash(index * 2016 - 1)
target = self.get_target(index-1)
for i in range(num):
raw_header = data[i*80:(i+1) * 80]
header = deserialize_header(raw_header, index*2016 + i)
self.verify_header(header, prev_header, bits, target)
prev_header = header
self.verify_header(header, prev_hash, target)
prev_hash = hash_header(header)

def path(self):
d = util.get_headers_dir(self.config)
Expand Down Expand Up @@ -250,68 +249,81 @@ def read_header(self, height):
with open(name, 'rb') as f:
f.seek(delta * 80)
h = f.read(80)
if h == bytes([0])*80:
return None
return deserialize_header(h, height)

def get_hash(self, height):
return hash_header(self.read_header(height))

def BIP9(self, height, flag):
v = self.read_header(height)['version']
return ((v & 0xE0000000) == 0x20000000) and ((v & flag) == flag)

def segwit_support(self, N=144):
h = self.local_height
return sum([self.BIP9(h-i, 2) for i in range(N)])*10000/N/100.
if height == -1:
return '0000000000000000000000000000000000000000000000000000000000000000'
elif height == 0:
return bitcoin.NetworkConstants.GENESIS
elif height < len(self.checkpoints) * 2016:
assert (height+1) % 2016 == 0
index = height // 2016
h, t = self.checkpoints[index]
return h
else:
return hash_header(self.read_header(height))

def get_target(self, index):
# compute target from chunk x, used in chunk x+1
if bitcoin.NetworkConstants.TESTNET:
return 0, 0
if index == 0:
if index == -1:
return 0x1d00ffff, MAX_TARGET
first = self.read_header((index-1) * 2016)
last = self.read_header(index*2016 - 1)
# bits to target
if index < len(self.checkpoints):
h, t = self.checkpoints[index]
return t
# new target
first = self.read_header(index * 2016)
last = self.read_header(index * 2016 + 2015)
bits = last.get('bits')
target = self.bits_to_target(bits)
nActualTimespan = last.get('timestamp') - first.get('timestamp')
nTargetTimespan = 14 * 24 * 60 * 60
nActualTimespan = max(nActualTimespan, nTargetTimespan // 4)
nActualTimespan = min(nActualTimespan, nTargetTimespan * 4)
new_target = min(MAX_TARGET, (target * nActualTimespan) // nTargetTimespan)
return new_target

def bits_to_target(self, bits):
bitsN = (bits >> 24) & 0xff
if not (bitsN >= 0x03 and bitsN <= 0x1d):
raise BaseException("First part of bits should be in [0x03, 0x1d]")
bitsBase = bits & 0xffffff
if not (bitsBase >= 0x8000 and bitsBase <= 0x7fffff):
raise BaseException("Second part of bits should be in [0x8000, 0x7fffff]")
target = bitsBase << (8 * (bitsN-3))
# new target
nActualTimespan = last.get('timestamp') - first.get('timestamp')
nTargetTimespan = 14 * 24 * 60 * 60
nActualTimespan = max(nActualTimespan, nTargetTimespan // 4)
nActualTimespan = min(nActualTimespan, nTargetTimespan * 4)
new_target = min(MAX_TARGET, (target * nActualTimespan) // nTargetTimespan)
# convert new target to bits
c = ("%064x" % new_target)[2:]
return bitsBase << (8 * (bitsN-3))

def target_to_bits(self, target):
c = ("%064x" % target)[2:]
while c[:2] == '00' and len(c) > 6:
c = c[2:]
bitsN, bitsBase = len(c) // 2, int('0x' + c[:6], 16)
if bitsBase >= 0x800000:
bitsN += 1
bitsBase >>= 8
new_bits = bitsN << 24 | bitsBase
return new_bits, bitsBase << (8 * (bitsN - 3))
return bitsN << 24 | bitsBase

def can_connect(self, header, check_height=True):
height = header['block_height']
if check_height and self.height() != height - 1:
#self.print_error("cannot connect at height", height)
return False
if height == 0:
return hash_header(header) == bitcoin.NetworkConstants.GENESIS
previous_header = self.read_header(height -1)
if not previous_header:
try:
prev_hash = self.get_hash(height - 1)
except:
return False
prev_hash = hash_header(previous_header)
if prev_hash != header.get('prev_block_hash'):
self.print_error("bad hash", height, prev_hash, header.get('prev_block_hash'))
return False
bits, target = self.get_target(height // 2016)
target = self.get_target(height // 2016 - 1)
try:
self.verify_header(header, previous_header, bits, target)
except:
self.verify_header(header, prev_hash, target)
except BaseException as e:
return False
return True

Expand All @@ -325,3 +337,13 @@ def connect_chunk(self, idx, hexdata):
except BaseException as e:
self.print_error('verify_chunk failed', str(e))
return False

def get_checkpoints(self):
# for each chunk, store the hash of the last block and the target after the chunk
cp = []
n = self.height() // 2016
for index in range(n):
h = self.get_hash((index+1) * 2016 -1)
target = self.get_target(index)
cp.append((h, target))
return cp
Loading

0 comments on commit d1b8a6f

Please sign in to comment.