Skip to content

Commit

Permalink
Refactor cp_height checkpoints' usage of header parsing
Browse files Browse the repository at this point in the history
This makes it easier for Electrum forks to support alternate header
formats such as AuxPoW.
  • Loading branch information
JeremyRand committed Jul 5, 2019
1 parent cdfab1a commit 2d2adb7
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 36 deletions.
63 changes: 35 additions & 28 deletions electrum/blockchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,33 @@ class MissingHeader(Exception):
class InvalidHeader(Exception):
pass

class HeaderChunk:
def __init__(self, base_height, data):
self.base_height = base_height
self.data = data

def __repr__(self):
return "HeaderChunk(base_height={}, data_count={})".format(self.base_height, len(self.data))

def contains_height(self, height):
return height >= self.base_height and height < self.base_height + self.get_header_count()

def get_header_at_height(self, height):
return self.get_header_at_index(height - self.base_height)

def get_header_at_index(self, index):
header_offset = index * HEADER_SIZE
return self.data[header_offset:header_offset + HEADER_SIZE]

def get_deserialized_header_at_height(self, height):
return deserialize_header(self.get_header_at_height(height), height)

def get_deserialized_header_at_index(self, index):
return deserialize_header(self.get_header_at_index(index), self.base_height + index)

def get_header_count(self):
return len(self.data) // HEADER_SIZE

def serialize_header(header_dict: dict) -> str:
s = int_to_hex(header_dict['version'], 4) \
+ rev_hex(header_dict['prev_block_hash']) \
Expand Down Expand Up @@ -295,10 +322,9 @@ def verify_header(cls, header: dict, prev_hash: str, target: int, expected_heade
if block_hash_as_num > target:
raise Exception(f"insufficient proof of work: {block_hash_as_num} vs target {target}")

def verify_chunk(self, index: int, data: bytes) -> None:
num = len(data) // HEADER_SIZE
def verify_chunk(self, index: int, chunk: HeaderChunk) -> None:
num = chunk.get_header_count()
start_height = index * 2016
chunk = HeaderChunk(start_height, data)
prev_hash = self.get_hash(start_height - 1)
target = self.get_target(index-1)
for i in range(num):
Expand All @@ -307,8 +333,7 @@ def verify_chunk(self, index: int, data: bytes) -> None:
expected_header_hash = self.get_hash(height)
except MissingHeader:
expected_header_hash = None
raw_header = chunk.get_header_at_index(i)
header = deserialize_header(raw_header, start_height + i)
header = chunk.get_deserialized_header_at_index(i)
self.verify_header(header, prev_hash, target, expected_header_hash)
prev_hash = hash_header(header)

Expand Down Expand Up @@ -598,8 +623,10 @@ def connect_chunk(self, idx: int, hexdata: str, proof_was_provided: bool=False)
assert idx >= 0, idx
try:
data = bfh(hexdata)
start_height = idx * 2016
chunk = HeaderChunk(start_height, data)
if not proof_was_provided:
self.verify_chunk(idx, data)
self.verify_chunk(idx, chunk)
self.save_chunk(idx, data)
return True
except BaseException as e:
Expand Down Expand Up @@ -627,12 +654,11 @@ def can_connect(header: dict, proof_was_provided: bool=False) -> Optional[Blockc
def verify_proven_chunk(chunk_base_height, chunk_data):
chunk = HeaderChunk(chunk_base_height, chunk_data)

header_count = len(chunk_data) // HEADER_SIZE
header_count = chunk.get_header_count()
prev_header = None
prev_header_hash = None
for i in range(header_count):
raw_header = chunk.get_header_at_index(i)
header = deserialize_header(raw_header, chunk_base_height + i)
header = chunk.get_deserialized_header_at_index(i)
# Check the chain of hashes for all headers preceding the proven one.
this_header_hash = hash_header(header)
if i > 0:
Expand All @@ -652,22 +678,3 @@ def root_from_proof(hash, branch, index):
if index:
raise ValueError('index out of range for branch')
return hash

class HeaderChunk:
def __init__(self, base_height, data):
self.base_height = base_height
self.data = data

def __repr__(self):
return "HeaderChunk(base_height={}, data_count={})".format(self.base_height, len(self.data))

def contains_height(self, height):
header_count = len(self.data) // HEADER_SIZE
return height >= self.base_height and height < self.base_height + header_count

def get_header_at_height(self, height):
return self.get_header_at_index(height - self.base_height)

def get_header_at_index(self, index):
header_offset = index * HEADER_SIZE
return self.data[header_offset:header_offset + HEADER_SIZE]
17 changes: 9 additions & 8 deletions electrum/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,14 @@
from aiorpcx.rawsocket import RSClient
import certifi

from .util import ignore_exceptions, log_exceptions, bfh, SilentTaskGroup
from .util import ignore_exceptions, log_exceptions, bfh, bh2u, SilentTaskGroup
from . import util
from .crypto import sha256d
from . import x509
from . import pem
from . import version
from . import blockchain
from .blockchain import Blockchain
from .blockchain import Blockchain, HeaderChunk
from . import constants
from .i18n import _
from .logging import Logger
Expand Down Expand Up @@ -493,9 +493,10 @@ async def request_headers(self, height, count):

res = await self.session.send_request('blockchain.block.headers', [height, count, cp_height])

header_hexsize = 80 * 2
hexdata = res['hex']
actual_header_count = len(hexdata) // header_hexsize
data = bfh(hexdata)
chunk = HeaderChunk(height, data)
actual_header_count = chunk.get_header_count()
# We accept less headers than we asked for, to cover the case where the distance to the tip was unknown.
if actual_header_count > count:
raise Exception("chunk data size incorrect expected_size={} actual_size={}".format(count * header_hexsize, len(hexdata)))
Expand All @@ -506,11 +507,11 @@ async def request_headers(self, height, count):
raise Exception("Received checkpoint validation data even though it wasn't requested.")

header_height = height + actual_header_count - 1
header_offset = (actual_header_count - 1) * header_hexsize
header = hexdata[header_offset : header_offset + header_hexsize]
self.validate_checkpoint_result(res["root"], res["branch"], header, header_height)
header = chunk.get_header_at_height(header_height)
hexheader = bh2u(header)

self.validate_checkpoint_result(res["root"], res["branch"], hexheader, header_height)

data = bfh(hexdata)
blockchain.verify_proven_chunk(height, data)

proof_was_provided = True
Expand Down

0 comments on commit 2d2adb7

Please sign in to comment.