blockchain: parameterise magic number 2016 as CHUNK_SIZE
This commit is contained in:
@@ -38,6 +38,7 @@ if TYPE_CHECKING:
|
|||||||
_logger = get_logger(__name__)
|
_logger = get_logger(__name__)
|
||||||
|
|
||||||
HEADER_SIZE = 80 # bytes
|
HEADER_SIZE = 80 # bytes
|
||||||
|
CHUNK_SIZE = 2016 # num headers in a difficulty retarget period
|
||||||
|
|
||||||
# see https://github.com/bitcoin/bitcoin/blob/feedb9c84e72e4fff489810a2bbeec09bcda5763/src/chainparams.cpp#L76
|
# see https://github.com/bitcoin/bitcoin/blob/feedb9c84e72e4fff489810a2bbeec09bcda5763/src/chainparams.cpp#L76
|
||||||
MAX_TARGET = 0x00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff # compact: 0x1d00ffff
|
MAX_TARGET = 0x00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff # compact: 0x1d00ffff
|
||||||
@@ -175,7 +176,7 @@ _CHAINWORK_CACHE = {
|
|||||||
def init_headers_file_for_best_chain():
|
def init_headers_file_for_best_chain():
|
||||||
b = get_best_chain()
|
b = get_best_chain()
|
||||||
filename = b.path()
|
filename = b.path()
|
||||||
length = HEADER_SIZE * len(constants.net.CHECKPOINTS) * 2016
|
length = HEADER_SIZE * len(constants.net.CHECKPOINTS) * CHUNK_SIZE
|
||||||
if not os.path.exists(filename) or os.path.getsize(filename) < length:
|
if not os.path.exists(filename) or os.path.getsize(filename) < length:
|
||||||
with open(filename, 'wb') as f:
|
with open(filename, 'wb') as f:
|
||||||
if length > 0:
|
if length > 0:
|
||||||
@@ -321,7 +322,7 @@ class Blockchain(Logger):
|
|||||||
|
|
||||||
def verify_chunk(self, index: int, data: bytes) -> None:
|
def verify_chunk(self, index: int, data: bytes) -> None:
|
||||||
num = len(data) // HEADER_SIZE
|
num = len(data) // HEADER_SIZE
|
||||||
start_height = index * 2016
|
start_height = index * CHUNK_SIZE
|
||||||
prev_hash = self.get_hash(start_height - 1)
|
prev_hash = self.get_hash(start_height - 1)
|
||||||
target = self.get_target(index-1)
|
target = self.get_target(index-1)
|
||||||
for i in range(num):
|
for i in range(num):
|
||||||
@@ -331,7 +332,7 @@ class Blockchain(Logger):
|
|||||||
except MissingHeader:
|
except MissingHeader:
|
||||||
expected_header_hash = None
|
expected_header_hash = None
|
||||||
raw_header = data[i*HEADER_SIZE : (i+1)*HEADER_SIZE]
|
raw_header = data[i*HEADER_SIZE : (i+1)*HEADER_SIZE]
|
||||||
header = deserialize_header(raw_header, index*2016 + i)
|
header = deserialize_header(raw_header, index*CHUNK_SIZE + i)
|
||||||
self.verify_header(header, prev_hash, target, expected_header_hash)
|
self.verify_header(header, prev_hash, target, expected_header_hash)
|
||||||
prev_hash = hash_header(header)
|
prev_hash = hash_header(header)
|
||||||
|
|
||||||
@@ -358,7 +359,7 @@ class Blockchain(Logger):
|
|||||||
main_chain.save_chunk(index, chunk)
|
main_chain.save_chunk(index, chunk)
|
||||||
return
|
return
|
||||||
|
|
||||||
delta_height = (index * 2016 - self.forkpoint)
|
delta_height = (index * CHUNK_SIZE - self.forkpoint)
|
||||||
delta_bytes = delta_height * HEADER_SIZE
|
delta_bytes = delta_height * HEADER_SIZE
|
||||||
# if this chunk contains our forkpoint, only save the part after forkpoint
|
# if this chunk contains our forkpoint, only save the part after forkpoint
|
||||||
# (the part before is the responsibility of the parent)
|
# (the part before is the responsibility of the parent)
|
||||||
@@ -509,7 +510,7 @@ class Blockchain(Logger):
|
|||||||
def get_hash(self, height: int) -> str:
|
def get_hash(self, height: int) -> str:
|
||||||
def is_height_checkpoint():
|
def is_height_checkpoint():
|
||||||
within_cp_range = height <= constants.net.max_checkpoint()
|
within_cp_range = height <= constants.net.max_checkpoint()
|
||||||
at_chunk_boundary = (height+1) % 2016 == 0
|
at_chunk_boundary = (height+1) % CHUNK_SIZE == 0
|
||||||
return within_cp_range and at_chunk_boundary
|
return within_cp_range and at_chunk_boundary
|
||||||
|
|
||||||
if height == -1:
|
if height == -1:
|
||||||
@@ -517,7 +518,7 @@ class Blockchain(Logger):
|
|||||||
elif height == 0:
|
elif height == 0:
|
||||||
return constants.net.GENESIS
|
return constants.net.GENESIS
|
||||||
elif is_height_checkpoint():
|
elif is_height_checkpoint():
|
||||||
index = height // 2016
|
index = height // CHUNK_SIZE
|
||||||
h, t = self.checkpoints[index]
|
h, t = self.checkpoints[index]
|
||||||
return h
|
return h
|
||||||
else:
|
else:
|
||||||
@@ -536,8 +537,8 @@ class Blockchain(Logger):
|
|||||||
h, t = self.checkpoints[index]
|
h, t = self.checkpoints[index]
|
||||||
return t
|
return t
|
||||||
# new target
|
# new target
|
||||||
first = self.read_header(index * 2016)
|
first = self.read_header(index * CHUNK_SIZE)
|
||||||
last = self.read_header(index * 2016 + 2015)
|
last = self.read_header((index+1) * CHUNK_SIZE - 1)
|
||||||
if not first or not last:
|
if not first or not last:
|
||||||
raise MissingHeader()
|
raise MissingHeader()
|
||||||
bits = last.get('bits')
|
bits = last.get('bits')
|
||||||
@@ -591,7 +592,7 @@ class Blockchain(Logger):
|
|||||||
|
|
||||||
def chainwork_of_header_at_height(self, height: int) -> int:
|
def chainwork_of_header_at_height(self, height: int) -> int:
|
||||||
"""work done by single header at given height"""
|
"""work done by single header at given height"""
|
||||||
chunk_idx = height // 2016 - 1
|
chunk_idx = height // CHUNK_SIZE - 1
|
||||||
target = self.get_target(chunk_idx)
|
target = self.get_target(chunk_idx)
|
||||||
work = ((2 ** 256 - target - 1) // (target + 1)) + 1
|
work = ((2 ** 256 - target - 1) // (target + 1)) + 1
|
||||||
return work
|
return work
|
||||||
@@ -604,23 +605,23 @@ class Blockchain(Logger):
|
|||||||
# On testnet/regtest, difficulty works somewhat different.
|
# On testnet/regtest, difficulty works somewhat different.
|
||||||
# It's out of scope to properly implement that.
|
# It's out of scope to properly implement that.
|
||||||
return height
|
return height
|
||||||
last_retarget = height // 2016 * 2016 - 1
|
last_retarget = height // CHUNK_SIZE * CHUNK_SIZE - 1
|
||||||
cached_height = last_retarget
|
cached_height = last_retarget
|
||||||
while _CHAINWORK_CACHE.get(self.get_hash(cached_height)) is None:
|
while _CHAINWORK_CACHE.get(self.get_hash(cached_height)) is None:
|
||||||
if cached_height <= -1:
|
if cached_height <= -1:
|
||||||
break
|
break
|
||||||
cached_height -= 2016
|
cached_height -= CHUNK_SIZE
|
||||||
assert cached_height >= -1, cached_height
|
assert cached_height >= -1, cached_height
|
||||||
running_total = _CHAINWORK_CACHE[self.get_hash(cached_height)]
|
running_total = _CHAINWORK_CACHE[self.get_hash(cached_height)]
|
||||||
while cached_height < last_retarget:
|
while cached_height < last_retarget:
|
||||||
cached_height += 2016
|
cached_height += CHUNK_SIZE
|
||||||
work_in_single_header = self.chainwork_of_header_at_height(cached_height)
|
work_in_single_header = self.chainwork_of_header_at_height(cached_height)
|
||||||
work_in_chunk = 2016 * work_in_single_header
|
work_in_chunk = CHUNK_SIZE * work_in_single_header
|
||||||
running_total += work_in_chunk
|
running_total += work_in_chunk
|
||||||
_CHAINWORK_CACHE[self.get_hash(cached_height)] = running_total
|
_CHAINWORK_CACHE[self.get_hash(cached_height)] = running_total
|
||||||
cached_height += 2016
|
cached_height += CHUNK_SIZE
|
||||||
work_in_single_header = self.chainwork_of_header_at_height(cached_height)
|
work_in_single_header = self.chainwork_of_header_at_height(cached_height)
|
||||||
work_in_last_partial_chunk = (height % 2016 + 1) * work_in_single_header
|
work_in_last_partial_chunk = (height % CHUNK_SIZE + 1) * work_in_single_header
|
||||||
return running_total + work_in_last_partial_chunk
|
return running_total + work_in_last_partial_chunk
|
||||||
|
|
||||||
def can_connect(self, header: dict, check_height: bool=True) -> bool:
|
def can_connect(self, header: dict, check_height: bool=True) -> bool:
|
||||||
@@ -638,7 +639,7 @@ class Blockchain(Logger):
|
|||||||
if prev_hash != header.get('prev_block_hash'):
|
if prev_hash != header.get('prev_block_hash'):
|
||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
target = self.get_target(height // 2016 - 1)
|
target = self.get_target(height // CHUNK_SIZE - 1)
|
||||||
except MissingHeader:
|
except MissingHeader:
|
||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
@@ -661,9 +662,9 @@ class Blockchain(Logger):
|
|||||||
def get_checkpoints(self):
|
def get_checkpoints(self):
|
||||||
# for each chunk, store the hash of the last block and the target after the chunk
|
# for each chunk, store the hash of the last block and the target after the chunk
|
||||||
cp = []
|
cp = []
|
||||||
n = self.height() // 2016
|
n = self.height() // CHUNK_SIZE
|
||||||
for index in range(n):
|
for index in range(n):
|
||||||
h = self.get_hash((index+1) * 2016 -1)
|
h = self.get_hash((index+1) * CHUNK_SIZE -1)
|
||||||
target = self.get_target(index)
|
target = self.get_target(index)
|
||||||
cp.append((h, target))
|
cp.append((h, target))
|
||||||
return cp
|
return cp
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ from . import x509
|
|||||||
from . import pem
|
from . import pem
|
||||||
from . import version
|
from . import version
|
||||||
from . import blockchain
|
from . import blockchain
|
||||||
from .blockchain import Blockchain, HEADER_SIZE
|
from .blockchain import Blockchain, HEADER_SIZE, CHUNK_SIZE
|
||||||
from . import bitcoin
|
from . import bitcoin
|
||||||
from . import constants
|
from . import constants
|
||||||
from .i18n import _
|
from .i18n import _
|
||||||
@@ -771,17 +771,17 @@ class Interface(Logger):
|
|||||||
) -> Optional[Tuple[bool, int]]:
|
) -> Optional[Tuple[bool, int]]:
|
||||||
if not is_non_negative_integer(height):
|
if not is_non_negative_integer(height):
|
||||||
raise Exception(f"{repr(height)} is not a block height")
|
raise Exception(f"{repr(height)} is not a block height")
|
||||||
index = height // 2016
|
index = height // CHUNK_SIZE
|
||||||
if can_return_early and index in self._requested_chunks:
|
if can_return_early and index in self._requested_chunks:
|
||||||
return None
|
return None
|
||||||
self.logger.info(f"requesting chunk from height {height}")
|
self.logger.info(f"requesting chunk from height {height}")
|
||||||
size = 2016
|
size = CHUNK_SIZE
|
||||||
if tip is not None:
|
if tip is not None:
|
||||||
size = min(size, tip - index * 2016 + 1)
|
size = min(size, tip - index * CHUNK_SIZE + 1)
|
||||||
size = max(size, 0)
|
size = max(size, 0)
|
||||||
try:
|
try:
|
||||||
self._requested_chunks.add(index)
|
self._requested_chunks.add(index)
|
||||||
res = await self.session.send_request('blockchain.block.headers', [index * 2016, size])
|
res = await self.session.send_request('blockchain.block.headers', [index * CHUNK_SIZE, size])
|
||||||
finally:
|
finally:
|
||||||
self._requested_chunks.discard(index)
|
self._requested_chunks.discard(index)
|
||||||
assert_dict_contains_field(res, field_name='count')
|
assert_dict_contains_field(res, field_name='count')
|
||||||
@@ -792,9 +792,9 @@ class Interface(Logger):
|
|||||||
assert_hex_str(res['hex'])
|
assert_hex_str(res['hex'])
|
||||||
if len(res['hex']) != HEADER_SIZE * 2 * res['count']:
|
if len(res['hex']) != HEADER_SIZE * 2 * res['count']:
|
||||||
raise RequestCorrupted('inconsistent chunk hex and count')
|
raise RequestCorrupted('inconsistent chunk hex and count')
|
||||||
# we never request more than 2016 headers, but we enforce those fit in a single response
|
# we never request more than CHUNK_SIZE headers, but we enforce those fit in a single response
|
||||||
if res['max'] < 2016:
|
if res['max'] < CHUNK_SIZE:
|
||||||
raise RequestCorrupted(f"server uses too low 'max' count for block.headers: {res['max']} < 2016")
|
raise RequestCorrupted(f"server uses too low 'max' count for block.headers: {res['max']} < {CHUNK_SIZE}")
|
||||||
if res['count'] != size:
|
if res['count'] != size:
|
||||||
raise RequestCorrupted(f"expected {size} headers but only got {res['count']}")
|
raise RequestCorrupted(f"expected {size} headers but only got {res['count']}")
|
||||||
conn = self.blockchain.connect_chunk(index, res['hex'])
|
conn = self.blockchain.connect_chunk(index, res['hex'])
|
||||||
@@ -965,7 +965,7 @@ class Interface(Logger):
|
|||||||
continue
|
continue
|
||||||
util.trigger_callback('blockchain_updated')
|
util.trigger_callback('blockchain_updated')
|
||||||
util.trigger_callback('network_updated')
|
util.trigger_callback('network_updated')
|
||||||
height = (height // 2016 * 2016) + num_headers
|
height = (height // CHUNK_SIZE * CHUNK_SIZE) + num_headers
|
||||||
assert height <= next_height+1, (height, self.tip)
|
assert height <= next_height+1, (height, self.tip)
|
||||||
last = ChainResolutionMode.CATCHUP
|
last = ChainResolutionMode.CATCHUP
|
||||||
else:
|
else:
|
||||||
|
|||||||
Reference in New Issue
Block a user