Skip to content

Commit

Permalink
Merge pull request #3948 from greymistcube/refactor/remove-fork
Browse files Browse the repository at this point in the history
🧹 Remove `Fork()` and `Swap()`
  • Loading branch information
greymistcube authored Sep 6, 2024
2 parents f8e7c93 + 38e8381 commit 3b939e7
Show file tree
Hide file tree
Showing 9 changed files with 68 additions and 1,268 deletions.
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ To be released.
[[#3942]]
- (Libplanet.Net) Changed `BlockHashDownloadState` and `BlockDownloadState`
to be `Obsolete`. [[#3943]]
- Removed `Fork()` method from `BlockChain`. [[#3948]]

### Backward-incompatible network protocol changes

Expand Down Expand Up @@ -55,6 +56,7 @@ To be released.
[#3934]: https://github.com/planetarium/libplanet/pull/3934
[#3942]: https://github.com/planetarium/libplanet/pull/3942
[#3943]: https://github.com/planetarium/libplanet/pull/3943
[#3948]: https://github.com/planetarium/libplanet/pull/3948


Version 5.2.2
Expand Down
214 changes: 29 additions & 185 deletions src/Libplanet.Net/Swarm.BlockCandidate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,6 @@ private bool BlockCandidateProcess(
IProgress<BlockSyncState> progress,
CancellationToken cancellationToken)
{
BlockChain synced = null;
System.Action renderSwap = () => { };
try
{
FillBlocksAsyncStarted.Set();
Expand All @@ -74,17 +72,19 @@ private bool BlockCandidateProcess(
nameof(BlockCandidateProcess),
BlockChain.Tip.Index,
BlockChain.Tip.Hash);
synced = AppendPreviousBlocks(
AppendBranch(
blockChain: BlockChain,
candidate: candidate,
render: render,
progress: progress);
progress: progress,
cancellationToken: cancellationToken);
ProcessFillBlocksFinished.Set();
_logger.Debug(
"{MethodName}() finished appending blocks; synced tip is #{Index} {Hash}",
"{MethodName}() finished appending blocks; current tip is #{Index} {Hash}",
nameof(BlockCandidateProcess),
synced.Tip.Index,
synced.Tip.Hash);
BlockChain.Tip.Index,
BlockChain.Tip.Hash);
return true;
}
catch (Exception e)
{
Expand All @@ -95,107 +95,24 @@ private bool BlockCandidateProcess(
FillBlocksAsyncFailed.Set();
return false;
}

try
{
// Although highly unlikely, current block chain's tip can change.
if (synced is { } syncedB
&& !syncedB.Id.Equals(BlockChain?.Id)
&& BlockChain.Tip.Index < syncedB.Tip.Index)
{
_logger.Debug(
"Swapping chain {ChainIdA} with chain {ChainIdB}...",
BlockChain.Id,
synced.Id
);
renderSwap = BlockChain.Swap(
synced,
render: render);
_logger.Debug(
"Swapped chain {ChainIdA} with chain {ChainIdB}",
BlockChain.Id,
synced.Id
);

renderSwap();
BroadcastBlock(BlockChain.Tip);
return true;
}
else
{
return false;
}
}
catch (Exception e)
{
_logger.Error(
e,
"{MethodName}() has failed to swap chain {ChainIdA} with chain {ChainIdB}",
nameof(BlockCandidateProcess),
BlockChain.Id,
synced.Id);
return false;
}
}

private BlockChain AppendPreviousBlocks(
private void AppendBranch(
BlockChain blockChain,
Branch candidate,
bool render,
IProgress<BlockSyncState> progress)
IProgress<BlockSyncState> progress,
CancellationToken cancellationToken = default)
{
BlockChain workspace;
List<Guid> scope = new List<Guid>();
bool forked = false;

Block oldTip = blockChain.Tip;
List<(Block, BlockCommit)> blocks = candidate.Blocks.ToList();
Block branchpoint = FindBranchpoint(
oldTip,
blocks.Select(pair => pair.Item1).ToList());
Block branchpoint = oldTip;
List<(Block, BlockCommit)> blocks = ExtractBlocksToAppend(branchpoint, candidate);

if (branchpoint.Equals(oldTip))
if (!blocks.Any())
{
_logger.Debug(
"No need to fork. at {MethodName}()",
nameof(AppendPreviousBlocks));
workspace = blockChain;
}
else if (!blockChain.ContainsBlock(branchpoint.Hash))
{
// FIXME: This behavior can unexpectedly terminate the swarm (and the game
// app) if it encounters a peer having a different blockchain, and therefore
// can be exploited to remotely shut down other nodes as well.
// Since the intention of this behavior is to prevent mistakes to try to
// connect incorrect seeds (by a user), this behavior should be limited for
// only seed peers.
var msg =
$"Since the genesis block is fixed to {BlockChain.Genesis} " +
"protocol-wise, the blockchain which does not share " +
"any mutual block is not acceptable.";
throw new InvalidGenesisBlockException(
msg,
branchpoint.Hash,
blockChain.Genesis.Hash);
}
else
{
_logger.Debug(
"Trying to fork... at {MethodName}()",
nameof(AppendPreviousBlocks)
);
workspace = blockChain.Fork(branchpoint.Hash);
forked = true;
scope.Add(workspace.Id);
_logger.Debug(
"Fork finished. at {MethodName}()",
nameof(AppendPreviousBlocks)
);
}

if (!workspace.Tip.Hash.Equals(blocks.First().Item1.PreviousHash))
{
blocks = blocks.Skip(1).ToList();
"There are no blocks to append to block {BlockHash}",
branchpoint.Hash);
}

try
Expand All @@ -204,14 +121,14 @@ private BlockChain AppendPreviousBlocks(

foreach (var (block, commit) in blocks)
{
cancellationToken.ThrowIfCancellationRequested();
if (block.ProtocolVersion < BlockMetadata.SlothProtocolVersion)
{
workspace.AppendStateRootHashPreceded(
block, commit, render: render && !forked);
blockChain.AppendStateRootHashPreceded(block, commit, render: render);
}
else
{
workspace.Append(block, commit, render: render && !forked);
blockChain.Append(block, commit, render: render);
}

verifiedBlockCount++;
Expand All @@ -233,102 +150,29 @@ private BlockChain AppendPreviousBlocks(
}
catch (Exception e)
{
const string dbgMsg =
"An exception occurred while appending a block " +
"to a workspace chain; deleting workspace chain {ChainId}";
_logger.Debug(e, dbgMsg, workspace.Id);

if (workspace?.Id is Guid workspaceId && scope.Contains(workspaceId))
{
_store.DeleteChainId(workspaceId);
}

const string dbgMsg = "An exception occurred while appending a block";
_logger.Error(e, dbgMsg);
throw;
}
finally
{
foreach (var id in scope.Where(guid => guid != workspace?.Id))
{
_store.DeleteChainId(id);
}

_logger.Debug(
"Completed (chain ID: {ChainId}, tip: #{TipIndex} {TipHash}). " +
"at {MethodName}()",
workspace?.Id,
workspace?.Tip?.Index,
workspace?.Tip?.Hash,
nameof(AppendPreviousBlocks)
);
}

return workspace;
}

private Block FindBranchpoint(Block oldTip, List<Block> newBlocks)
private List<(Block, BlockCommit)> ExtractBlocksToAppend(Block branchpoint, Branch branch)
{
var newTip = newBlocks.Last();
while (oldTip.Index > newTip.Index &&
oldTip.PreviousHash is { } aPrev)
{
oldTip = BlockChain[aPrev];
}

while (newTip.Index > oldTip.Index &&
newTip.PreviousHash is { } bPrev)
{
try
{
newTip = newBlocks.Single(x => x.Hash.Equals(bPrev));
}
catch (ArgumentNullException)
{
newTip = BlockChain[bPrev];
}
catch (InvalidOperationException)
{
newTip = BlockChain[bPrev];
}
}

if (oldTip.Index != newTip.Index)
var trimmed = new List<(Block, BlockCommit)>();
bool matchFound = false;
foreach (var pair in branch.Blocks)
{
throw new ArgumentException(
"Some previous blocks of two blocks are orphan.",
nameof(oldTip)
);
}

while (oldTip.Index >= 0)
{
if (oldTip.Equals(newTip))
if (matchFound)
{
return oldTip;
trimmed.Add(pair);
}

if (oldTip.PreviousHash is { } aPrev &&
newTip.PreviousHash is { } bPrev)
else
{
oldTip = BlockChain[aPrev];
try
{
newTip = newBlocks.Single(x => x.Hash.Equals(bPrev));
}
catch (ArgumentNullException)
{
newTip = BlockChain[bPrev];
}

continue;
matchFound = branchpoint.Hash.Equals(pair.Item1.Hash);
}

break;
}

throw new ArgumentException(
"Two blocks do not have any ancestors in common.",
nameof(oldTip)
);
return trimmed;
}

private async Task<bool> ProcessBlockDemandAsync(
Expand Down
Loading

0 comments on commit 3b939e7

Please sign in to comment.