acceptHeader method

Future<HeaderAcceptResult> acceptHeader(
  1. BlockHeader header, {
  2. int? expectedHeight,
})

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');
  }
}