acceptHeader method
Validate header, place it on the branch its parent belongs to, and
move the tip if that branch now carries the most work.
Implementation
Future<HeaderAcceptResult> acceptHeader(BlockHeader header, {int? expectedHeight}) async {
try {
final hash = header.blockHash().toString();
// Already active?
final knownHeight = await _activeHeightOf(hash);
if (knownHeight != null) {
if (expectedHeight != null && expectedHeight != knownHeight) {
return HeaderAcceptResult.rejected(HeaderRejectReason.heightMismatch,
'header $hash is at height $knownHeight, caller said $expectedHeight',
height: knownHeight);
}
return HeaderAcceptResult._(accepted: true, height: knownHeight, alreadyKnown: true,
isNewTip: hash == _chainTip?.blockHash().toString());
}
final knownSide = _sideHeaders[hash];
if (knownSide != null) {
return HeaderAcceptResult._(accepted: true, height: knownSide.height, alreadyKnown: true);
}
// Empty chain: only the anchor, or a direct child of it, can start it.
if (_chainTip == null) {
return _acceptFirstHeader(header, hash, expectedHeight);
}
final parentHash = header.prevBlock.toString();
final parentActiveHeight = await _activeHeightOf(parentHash);
final parentSide = parentActiveHeight == null ? _sideHeaders[parentHash] : null;
if (parentActiveHeight == null && parentSide == null) {
return HeaderAcceptResult.rejected(
HeaderRejectReason.unknownParent, 'parent $parentHash of $hash is not known');
}
final height = (parentActiveHeight ?? parentSide!.height) + 1;
if (expectedHeight != null && expectedHeight != height) {
return HeaderAcceptResult.rejected(HeaderRejectReason.heightMismatch,
'parent of $hash is at height ${height - 1}, caller said $expectedHeight',
height: height);
}
final ancestry = await _ancestryFor(parentHash, parentActiveHeight, parentSide);
final prev = (await ancestry.headerAt(height - 1))!;
final rejection = await _validate(header, hash, height, prev, ancestry);
if (rejection != null) return rejection;
// Extends the tip: the common case.
if (parentActiveHeight != null && parentActiveHeight == _bestHeight) {
await _storeActive(header, height);
_logger.fine('Stored header at height $height: ${hash.substring(0, 16)}...');
return HeaderAcceptResult._(accepted: true, height: height, isNewTip: true);
}
// Side branch.
final forkHeight = parentSide?.forkHeight ?? parentActiveHeight!;
final work = (parentSide?.workAboveFork ?? BigInt.zero) + DifficultyRules.blockWork(header.bits);
final side = _SideHeader(
header: header,
height: height,
hash: hash,
parentHash: parentHash,
forkHeight: forkHeight,
workAboveFork: work,
);
_sideHeaders[hash] = side;
_boundSideHeaders();
final activeWork = await _activeWorkAbove(forkHeight);
if (work <= activeWork) {
_logger.info('Header $hash at height $height stored on a side branch forking at '
'$forkHeight (work $work vs active $activeWork)');
return HeaderAcceptResult._(accepted: true, height: height);
}
final orphaned = await _reorganizeTo(side);
return HeaderAcceptResult._(
accepted: true,
height: height,
isNewTip: true,
reorganized: true,
forkHeight: forkHeight,
orphaned: orphaned,
);
} catch (e, st) {
_logger.warning('Failed to validate/store header: $e\n$st');
return HeaderAcceptResult.rejected(HeaderRejectReason.storage, '$e');
}
}