diff --git a/CHANGES.md b/CHANGES.md
index 469b35369d1..7a821052d79 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -34,6 +34,39 @@ To be released.
[#3999]: https://github.com/planetarium/libplanet/pull/3999
+Version 5.4.2
+-------------
+
+Released on December 13, 2024.
+
+### Backward-incompatible API changes
+
+ - Removed `ContextTimeoutOption` class. Instead, added `ContextOption` class.
+ The unit of the time-related options in `ContextOption` is millisecond,
+ whereas `ContextTimeoutOption` was second. [[#4007]]
+ - Removed `ConsensusReactorOption.ContextTimeoutOptions` property.
+ Instead, added `ConsensusReactorOption.ContextOption` property. [[#4007]]
+ - `ConsensusReactor` constructor requires `ContextOption` parameter
+ instead of the `ContextTimeoutOption` parameter. [[#4007]]
+
+### Behavioral changes
+
+ - `Gossip.RebuildTableAsync()` now bootstrap peers from the seed peers.
+ [[#4007]]
+
+[#4007]: https://github.com/planetarium/libplanet/pull/4007
+
+
+Version 5.4.1
+-------------
+
+Released on November 22, 2024.
+
+ - Ported changes from [Libplanet 5.3.2] release. [[#3973]]
+
+[Libplanet 5.4.1]: https://www.nuget.org/packages/Libplanet/5.4.1
+
+
Version 5.4.0
-------------
diff --git a/src/Libplanet.Net/Consensus/ConsensusContext.cs b/src/Libplanet.Net/Consensus/ConsensusContext.cs
index 38339996e65..b72323d0b5c 100644
--- a/src/Libplanet.Net/Consensus/ConsensusContext.cs
+++ b/src/Libplanet.Net/Consensus/ConsensusContext.cs
@@ -32,7 +32,7 @@ namespace Libplanet.Net.Consensus
public partial class ConsensusContext : IDisposable
{
private readonly object _contextLock;
- private readonly ContextTimeoutOption _contextTimeoutOption;
+ private readonly ContextOption _contextOption;
private readonly IConsensusMessageCommunicator _consensusMessageCommunicator;
private readonly BlockChain _blockChain;
private readonly PrivateKey _privateKey;
@@ -58,14 +58,14 @@ private readonly EvidenceExceptionCollector _evidenceCollector
/// A time delay in starting the consensus for the next height
/// block.
///
- /// A for
- /// configuring a timeout for each .
+ /// A for
+ /// configuring a timeout or delay for each .
public ConsensusContext(
IConsensusMessageCommunicator consensusMessageCommunicator,
BlockChain blockChain,
PrivateKey privateKey,
TimeSpan newHeightDelay,
- ContextTimeoutOption contextTimeoutOption)
+ ContextOption contextOption)
{
_consensusMessageCommunicator = consensusMessageCommunicator;
_blockChain = blockChain;
@@ -73,7 +73,7 @@ public ConsensusContext(
Running = false;
_newHeightDelay = newHeightDelay;
- _contextTimeoutOption = contextTimeoutOption;
+ _contextOption = contextOption;
_currentContext = CreateContext(
_blockChain.Tip.Index + 1,
_blockChain.GetBlockCommit(_blockChain.Tip.Index));
@@ -498,7 +498,7 @@ private Context CreateContext(long height, BlockCommit? lastCommit)
lastCommit,
_privateKey,
validatorSet,
- contextTimeoutOptions: _contextTimeoutOption);
+ contextOption: _contextOption);
return context;
}
diff --git a/src/Libplanet.Net/Consensus/ConsensusReactor.cs b/src/Libplanet.Net/Consensus/ConsensusReactor.cs
index 8cad857799a..4fd97d8f370 100644
--- a/src/Libplanet.Net/Consensus/ConsensusReactor.cs
+++ b/src/Libplanet.Net/Consensus/ConsensusReactor.cs
@@ -43,7 +43,7 @@ public class ConsensusReactor : IReactor
/// A time delay in starting the consensus for the next height
/// block.
///
- /// A for
+ /// A for
/// configuring a timeout for each .
public ConsensusReactor(
ITransport consensusTransport,
@@ -52,7 +52,7 @@ public ConsensusReactor(
ImmutableList validatorPeers,
ImmutableList seedPeers,
TimeSpan newHeightDelay,
- ContextTimeoutOption contextTimeoutOption)
+ ContextOption contextOption)
{
validatorPeers ??= ImmutableList.Empty;
seedPeers ??= ImmutableList.Empty;
@@ -71,7 +71,7 @@ public ConsensusReactor(
blockChain,
privateKey,
newHeightDelay,
- contextTimeoutOption);
+ contextOption);
_logger = Log
.ForContext("Tag", "Consensus")
diff --git a/src/Libplanet.Net/Consensus/ConsensusReactorOption.cs b/src/Libplanet.Net/Consensus/ConsensusReactorOption.cs
index f39aa792e34..1b4440463e8 100644
--- a/src/Libplanet.Net/Consensus/ConsensusReactorOption.cs
+++ b/src/Libplanet.Net/Consensus/ConsensusReactorOption.cs
@@ -7,7 +7,7 @@
namespace Libplanet.Net.Consensus
{
///
- /// A option struct for initializing .
+ /// An option struct for initializing .
///
public struct ConsensusReactorOption
{
@@ -42,8 +42,8 @@ public struct ConsensusReactorOption
public TimeSpan TargetBlockInterval { get; set; }
///
- /// A timeout second and multiplier value for used in .
+ /// A timeout and delay value for used in in milliseconds.
///
- public ContextTimeoutOption ContextTimeoutOptions { get; set; }
+ public ContextOption ContextOption { get; set; }
}
}
diff --git a/src/Libplanet.Net/Consensus/Context.Async.cs b/src/Libplanet.Net/Consensus/Context.Async.cs
index 790339c3355..a3d73b2974b 100644
--- a/src/Libplanet.Net/Consensus/Context.Async.cs
+++ b/src/Libplanet.Net/Consensus/Context.Async.cs
@@ -189,6 +189,40 @@ private void AppendBlock(Block block)
_ = Task.Run(() => _blockChain.Append(block, GetBlockCommit()));
}
+ private async Task EnterPreCommitWait(int round, BlockHash hash)
+ {
+ if (!_preCommitWaitFlags.Add(round))
+ {
+ return;
+ }
+
+ if (_contextOption.EnterPreCommitDelay > 0)
+ {
+ await Task.Delay(
+ _contextOption.EnterPreCommitDelay,
+ _cancellationTokenSource.Token);
+ }
+
+ ProduceMutation(() => EnterPreCommit(round, hash));
+ }
+
+ private async Task EnterEndCommitWait(int round)
+ {
+ if (!_endCommitWaitFlags.Add(round))
+ {
+ return;
+ }
+
+ if (_contextOption.EnterEndCommitDelay > 0)
+ {
+ await Task.Delay(
+ _contextOption.EnterEndCommitDelay,
+ _cancellationTokenSource.Token);
+ }
+
+ ProduceMutation(() => EnterEndCommit(round));
+ }
+
///
/// Schedules to be queued after
/// amount of time.
@@ -212,7 +246,18 @@ private async Task OnTimeoutPropose(int round)
/// A round that the timeout task is scheduled for.
private async Task OnTimeoutPreVote(int round)
{
+ if (_preCommitTimeoutFlags.Contains(round) || !_preVoteTimeoutFlags.Add(round))
+ {
+ return;
+ }
+
TimeSpan timeout = TimeoutPreVote(round);
+ _logger.Debug(
+ "PreVote step in round {Round} is scheduled to be timed out after {Timeout} " +
+ "because 2/3+ PreVotes are collected for the round. (context: {Context})",
+ round,
+ timeout,
+ ToString());
await Task.Delay(timeout, _cancellationTokenSource.Token);
_logger.Information(
"TimeoutPreVote has occurred in {Timeout}. {Info}",
@@ -228,7 +273,18 @@ private async Task OnTimeoutPreVote(int round)
/// The round that the timeout task is scheduled for.
private async Task OnTimeoutPreCommit(int round)
{
+ if (!_preCommitTimeoutFlags.Add(round))
+ {
+ return;
+ }
+
TimeSpan timeout = TimeoutPreCommit(round);
+ _logger.Debug(
+ "PreCommit step in round {Round} is scheduled to be timed out in {Timeout} " +
+ "because 2/3+ PreCommits are collected for the round. (context: {Context})",
+ round,
+ timeout,
+ ToString());
await Task.Delay(timeout, _cancellationTokenSource.Token);
_logger.Information(
"TimeoutPreCommit has occurred in {Timeout}. {Info}",
diff --git a/src/Libplanet.Net/Consensus/Context.Mutate.cs b/src/Libplanet.Net/Consensus/Context.Mutate.cs
index 8b71cfee34b..6417befbe8f 100644
--- a/src/Libplanet.Net/Consensus/Context.Mutate.cs
+++ b/src/Libplanet.Net/Consensus/Context.Mutate.cs
@@ -245,17 +245,14 @@ private void ProcessGenericUponRules()
"Entering PreVote step due to proposal message with " +
"valid round -1. (context: {Context})",
ToString());
- Step = ConsensusStep.PreVote;
if (IsValid(p1.Block) && (_lockedRound == -1 || _lockedValue == p1.Block))
{
- PublishMessage(
- new ConsensusPreVoteMsg(MakeVote(Round, p1.Block.Hash, VoteFlag.PreVote)));
+ EnterPreVote(Round, p1.Block.Hash);
}
else
{
- PublishMessage(
- new ConsensusPreVoteMsg(MakeVote(Round, default, VoteFlag.PreVote)));
+ EnterPreVote(Round, default);
}
}
@@ -271,31 +268,20 @@ private void ProcessGenericUponRules()
"2/3+ PreVote for valid round {ValidRound}. (context: {Context})",
p2.ValidRound,
ToString());
- Step = ConsensusStep.PreVote;
if (IsValid(p2.Block) &&
(_lockedRound <= p2.ValidRound || _lockedValue == p2.Block))
{
- PublishMessage(
- new ConsensusPreVoteMsg(MakeVote(Round, p2.Block.Hash, VoteFlag.PreVote)));
+ EnterPreVote(Round, p2.Block.Hash);
}
else
{
- PublishMessage(
- new ConsensusPreVoteMsg(MakeVote(Round, default, VoteFlag.PreVote)));
+ EnterPreVote(Round, default);
}
}
- if (_heightVoteSet.PreVotes(Round).HasTwoThirdsAny() &&
- Step == ConsensusStep.PreVote &&
- !_preVoteTimeoutFlags.Contains(Round))
+ if (_heightVoteSet.PreVotes(Round).HasTwoThirdsAny() && Step == ConsensusStep.PreVote)
{
- _logger.Debug(
- "PreVote step in round {Round} is scheduled to be timed out because " +
- "2/3+ PreVotes are collected for the round. (context: {Context})",
- Round,
- ToString());
- _preVoteTimeoutFlags.Add(Round);
_ = OnTimeoutPreVote(Round);
}
@@ -315,16 +301,13 @@ private void ProcessGenericUponRules()
if (Step == ConsensusStep.PreVote)
{
_logger.Debug(
- "Entering PreCommit step due to proposal message and have collected " +
- "2/3+ PreVote for current round {Round}. (context: {Context})",
+ "Schedule to enter PreCommit step due to proposal message and have " +
+ "collected 2/3+ PreVote for current round {Round}. (context: {Context})",
Round,
ToString());
- Step = ConsensusStep.PreCommit;
_lockedValue = p3.Block;
_lockedRound = Round;
- PublishMessage(
- new ConsensusPreCommitMsg(
- MakeVote(Round, p3.Block.Hash, VoteFlag.PreCommit)));
+ _ = EnterPreCommitWait(Round, p3.Block.Hash);
// Maybe need to broadcast periodically?
PublishMessage(
@@ -346,9 +329,7 @@ private void ProcessGenericUponRules()
"were collected. (context: {Context})",
Round,
ToString());
- Step = ConsensusStep.PreCommit;
- PublishMessage(
- new ConsensusPreCommitMsg(MakeVote(Round, default, VoteFlag.PreCommit)));
+ _ = EnterPreCommitWait(Round, default);
}
else if (Proposal is { } proposal && !proposal.BlockHash.Equals(hash3))
{
@@ -372,15 +353,8 @@ private void ProcessGenericUponRules()
}
}
- if (_heightVoteSet.PreCommits(Round).HasTwoThirdsAny() &&
- !_preCommitTimeoutFlags.Contains(Round))
+ if (_heightVoteSet.PreCommits(Round).HasTwoThirdsAny())
{
- _logger.Debug(
- "PreCommit step in round {Round} is scheduled to be timed out because " +
- "2/3+ PreCommits are collected for the round. (context: {Context})",
- Round,
- ToString());
- _preCommitTimeoutFlags.Add(Round);
_ = OnTimeoutPreCommit(Round);
}
}
@@ -406,7 +380,6 @@ private void ProcessHeightOrRoundUponRules(ConsensusMsg message)
block4.Hash.Equals(hash) &&
IsValid(block4))
{
- Step = ConsensusStep.EndCommit;
_decision = block4;
_committedRound = round;
@@ -414,39 +387,12 @@ private void ProcessHeightOrRoundUponRules(ConsensusMsg message)
PublishMessage(
new ConsensusMaj23Msg(
MakeMaj23(round, block4.Hash, VoteFlag.PreCommit)));
-
- try
- {
- _logger.Information(
- "Committing block #{Index} {Hash} (context: {Context})",
- block4.Index,
- block4.Hash,
- ToString());
-
- IsValid(block4);
-
- AppendBlock(block4);
- }
- catch (Exception e)
- {
- _logger.Error(
- e,
- "Failed to commit block #{Index} {Hash}",
- block4.Index,
- block4.Hash);
- ExceptionOccurred?.Invoke(this, e);
- return;
- }
-
- _logger.Information(
- "Committed block #{Index} {Hash}",
- block4.Index,
- block4.Hash);
+ _ = EnterEndCommitWait(Round);
return;
}
// NOTE: +1/3 prevote received, skip round
- // FIXME: Tendermint uses +2/3, should fixed?
+ // FIXME: Tendermint uses +2/3, should be fixed?
if (round > Round &&
_heightVoteSet.PreVotes(round).HasOneThirdsAny())
{
@@ -461,6 +407,81 @@ private void ProcessHeightOrRoundUponRules(ConsensusMsg message)
}
}
+ private void EnterPreVote(int round, BlockHash hash)
+ {
+ if (Round != round || Step >= ConsensusStep.PreVote)
+ {
+ // Round and step mismatch
+ return;
+ }
+
+ Step = ConsensusStep.PreVote;
+ PublishMessage(
+ new ConsensusPreVoteMsg(MakeVote(round, hash, VoteFlag.PreVote)));
+ }
+
+ private void EnterPreCommit(int round, BlockHash hash)
+ {
+ if (Round != round || Step >= ConsensusStep.PreCommit)
+ {
+ // Round and step mismatch
+ return;
+ }
+
+ _logger.Debug(
+ "Entering PreCommit step current round {Round}. (context: {Context})",
+ Round,
+ ToString());
+ Step = ConsensusStep.PreCommit;
+ PublishMessage(
+ new ConsensusPreCommitMsg(MakeVote(round, hash, VoteFlag.PreCommit)));
+ }
+
+ private void EnterEndCommit(int round)
+ {
+ if (Round != round ||
+ Step == ConsensusStep.Default ||
+ Step == ConsensusStep.EndCommit)
+ {
+ // Round and step mismatch
+ return;
+ }
+
+ Step = ConsensusStep.EndCommit;
+ if (_decision is not { } block)
+ {
+ StartRound(Round + 1);
+ return;
+ }
+
+ try
+ {
+ _logger.Information(
+ "Committing block #{Index} {Hash} (context: {Context})",
+ block.Index,
+ block.Hash,
+ ToString());
+
+ IsValid(block);
+ AppendBlock(block);
+ }
+ catch (Exception e)
+ {
+ _logger.Error(
+ e,
+ "Failed to commit block #{Index} {Hash}",
+ block.Index,
+ block.Hash);
+ ExceptionOccurred?.Invoke(this, e);
+ return;
+ }
+
+ _logger.Information(
+ "Committed block #{Index} {Hash}",
+ block.Index,
+ block.Hash);
+ }
+
///
/// A timeout mutation to run if no is received in
/// and is still in step.
@@ -470,9 +491,7 @@ private void ProcessTimeoutPropose(int round)
{
if (round == Round && Step == ConsensusStep.Propose)
{
- PublishMessage(
- new ConsensusPreVoteMsg(MakeVote(Round, default, VoteFlag.PreVote)));
- Step = ConsensusStep.PreVote;
+ EnterPreVote(round, default);
TimeoutProcessed?.Invoke(this, (round, ConsensusStep.Propose));
}
}
@@ -487,9 +506,7 @@ private void ProcessTimeoutPreVote(int round)
{
if (round == Round && Step == ConsensusStep.PreVote)
{
- PublishMessage(
- new ConsensusPreCommitMsg(MakeVote(Round, default, VoteFlag.PreCommit)));
- Step = ConsensusStep.PreCommit;
+ EnterPreCommit(round, default);
TimeoutProcessed?.Invoke(this, (round, ConsensusStep.PreVote));
}
}
@@ -510,7 +527,7 @@ private void ProcessTimeoutPreCommit(int round)
if (round == Round)
{
- StartRound(Round + 1);
+ EnterEndCommit(round);
TimeoutProcessed?.Invoke(this, (round, ConsensusStep.PreCommit));
}
}
diff --git a/src/Libplanet.Net/Consensus/Context.cs b/src/Libplanet.Net/Consensus/Context.cs
index 69453b615fb..daa7f0063eb 100644
--- a/src/Libplanet.Net/Consensus/Context.cs
+++ b/src/Libplanet.Net/Consensus/Context.cs
@@ -78,7 +78,7 @@ namespace Libplanet.Net.Consensus
///
public partial class Context : IDisposable
{
- private readonly ContextTimeoutOption _contextTimeoutOption;
+ private readonly ContextOption _contextOption;
private readonly BlockChain _blockChain;
private readonly Codec _codec;
@@ -87,9 +87,11 @@ public partial class Context : IDisposable
private readonly Channel _mutationRequests;
private readonly HeightVoteSet _heightVoteSet;
private readonly PrivateKey _privateKey;
- private readonly HashSet _preVoteTimeoutFlags;
private readonly HashSet _hasTwoThirdsPreVoteFlags;
+ private readonly HashSet _preVoteTimeoutFlags;
private readonly HashSet _preCommitTimeoutFlags;
+ private readonly HashSet _preCommitWaitFlags;
+ private readonly HashSet _endCommitWaitFlags;
private readonly EvidenceExceptionCollector _evidenceCollector
= new EvidenceExceptionCollector();
@@ -98,6 +100,8 @@ private readonly EvidenceExceptionCollector _evidenceCollector
private readonly ILogger _logger;
private readonly LRUCache _blockValidationCache;
+ private Proposal? _proposal;
+ private Block? _proposalBlock;
private Block? _lockedValue;
private int _lockedRound;
private Block? _validValue;
@@ -122,15 +126,15 @@ private readonly EvidenceExceptionCollector _evidenceCollector
///
/// The for
/// given .
- /// A for
- /// configuring a timeout for each .
+ /// A for
+ /// configuring a timeout and delay for each .
public Context(
BlockChain blockChain,
long height,
BlockCommit? lastCommit,
PrivateKey privateKey,
ValidatorSet validators,
- ContextTimeoutOption contextTimeoutOptions)
+ ContextOption contextOption)
: this(
blockChain,
height,
@@ -140,7 +144,7 @@ public Context(
ConsensusStep.Default,
-1,
128,
- contextTimeoutOptions)
+ contextOption)
{
}
@@ -153,7 +157,7 @@ private Context(
ConsensusStep consensusStep,
int round = -1,
int cacheSize = 128,
- ContextTimeoutOption? contextTimeoutOptions = null)
+ ContextOption? contextOption = null)
{
if (height < 1)
{
@@ -183,15 +187,17 @@ private Context(
_messageRequests = Channel.CreateUnbounded();
_mutationRequests = Channel.CreateUnbounded();
_heightVoteSet = new HeightVoteSet(height, validators);
- _preVoteTimeoutFlags = new HashSet();
_hasTwoThirdsPreVoteFlags = new HashSet();
+ _preVoteTimeoutFlags = new HashSet();
_preCommitTimeoutFlags = new HashSet();
+ _preCommitWaitFlags = new HashSet();
+ _endCommitWaitFlags = new HashSet();
_validatorSet = validators;
_cancellationTokenSource = new CancellationTokenSource();
_blockValidationCache =
new LRUCache(cacheSize, Math.Max(cacheSize / 64, 8));
- _contextTimeoutOption = contextTimeoutOptions ?? new ContextTimeoutOption();
+ _contextOption = contextOption ?? new ContextOption();
_logger.Information(
"Created Context for height #{Height}, round #{Round}",
@@ -214,7 +220,24 @@ private Context(
///
public ConsensusStep Step { get; private set; }
- public Proposal? Proposal { get; private set; }
+ public Proposal? Proposal
+ {
+ get => _proposal;
+ private set
+ {
+ if (value is { } p)
+ {
+ _proposal = p;
+ _proposalBlock =
+ BlockMarshaler.UnmarshalBlock((Dictionary)_codec.Decode(p.MarshaledBlock));
+ }
+ else
+ {
+ _proposal = null;
+ _proposalBlock = null;
+ }
+ }
+ }
///
public void Dispose()
@@ -379,9 +402,9 @@ public override string ToString()
/// A duration in .
private TimeSpan TimeoutPreVote(long round)
{
- return TimeSpan.FromSeconds(
- _contextTimeoutOption.PreVoteSecondBase +
- round * _contextTimeoutOption.PreVoteMultiplier);
+ return TimeSpan.FromMilliseconds(
+ _contextOption.PreVoteTimeoutBase +
+ round * _contextOption.PreVoteTimeoutDelta);
}
///
@@ -392,9 +415,9 @@ private TimeSpan TimeoutPreVote(long round)
/// A duration in .
private TimeSpan TimeoutPreCommit(long round)
{
- return TimeSpan.FromSeconds(
- _contextTimeoutOption.PreCommitSecondBase +
- round * _contextTimeoutOption.PreCommitMultiplier);
+ return TimeSpan.FromMilliseconds(
+ _contextOption.PreCommitTimeoutBase +
+ round * _contextOption.PreCommitTimeoutDelta);
}
///
@@ -405,9 +428,9 @@ private TimeSpan TimeoutPreCommit(long round)
/// A duration in .
private TimeSpan TimeoutPropose(long round)
{
- return TimeSpan.FromSeconds(
- _contextTimeoutOption.ProposeSecondBase +
- round * _contextTimeoutOption.ProposeMultiplier);
+ return TimeSpan.FromMilliseconds(
+ _contextOption.ProposeTimeoutBase +
+ round * _contextOption.ProposeTimeoutDelta);
}
///
@@ -590,16 +613,7 @@ private Maj23 MakeMaj23(int round, BlockHash hash, VoteFlag flag)
/// Returns a tuple of proposer and valid round. If proposal for the round
/// does not exist, returns instead.
///
- private (Block, int)? GetProposal()
- {
- if (Proposal is { } p)
- {
- var block = BlockMarshaler.UnmarshalBlock(
- (Dictionary)_codec.Decode(p.MarshaledBlock));
- return (block, p.ValidRound);
- }
-
- return null;
- }
+ private (Block, int)? GetProposal() =>
+ Proposal is { } p && _proposalBlock is { } b ? (b, p.ValidRound) : null;
}
}
diff --git a/src/Libplanet.Net/Consensus/ContextOption.cs b/src/Libplanet.Net/Consensus/ContextOption.cs
new file mode 100644
index 00000000000..95ef6022cfb
--- /dev/null
+++ b/src/Libplanet.Net/Consensus/ContextOption.cs
@@ -0,0 +1,122 @@
+using System;
+
+namespace Libplanet.Net.Consensus
+{
+ ///
+ /// An options class to configure timeout and delay
+ /// for each in milliseconds.
+ ///
+ public class ContextOption
+ {
+ public ContextOption(
+ int proposeTimeoutBase = 8_000,
+ int preVoteTimeoutBase = 1_000,
+ int preCommitTimeoutBase = 1_000,
+ int proposeTimeoutDelta = 4_000,
+ int preVoteTimeoutDelta = 500,
+ int preCommitTimeoutDelta = 500,
+ int enterPreVoteDelay = 0,
+ int enterPreCommitDelay = 0,
+ int enterEndCommitDelay = 0)
+ {
+ if (proposeTimeoutBase <= 0)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(proposeTimeoutBase),
+ "ProposeTimeoutBase must be greater than 0.");
+ }
+
+ ProposeTimeoutBase = proposeTimeoutBase;
+
+ if (preVoteTimeoutBase <= 0)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(preVoteTimeoutBase),
+ "PreVoteTimeoutBase must be greater than 0.");
+ }
+
+ PreVoteTimeoutBase = preVoteTimeoutBase;
+
+ if (preCommitTimeoutBase <= 0)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(preCommitTimeoutBase),
+ "PreCommitTimeoutBase must be greater than 0.");
+ }
+
+ PreCommitTimeoutBase = preCommitTimeoutBase;
+
+ if (proposeTimeoutDelta <= 0)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(proposeTimeoutDelta),
+ "ProposeTimeoutDelta must be greater than 0.");
+ }
+
+ ProposeTimeoutDelta = proposeTimeoutDelta;
+
+ if (preVoteTimeoutDelta <= 0)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(preVoteTimeoutDelta),
+ "PreVoteTimeoutDelta must be greater than 0.");
+ }
+
+ PreVoteTimeoutDelta = preVoteTimeoutDelta;
+
+ if (preCommitTimeoutDelta <= 0)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(preCommitTimeoutDelta),
+ "PreCommitTimeoutDelta must be greater than 0.");
+ }
+
+ PreCommitTimeoutDelta = preCommitTimeoutDelta;
+
+ if (enterPreVoteDelay < 0)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(enterPreVoteDelay),
+ "EnterPreVoteDelay must be greater than or equal to 0.");
+ }
+
+ EnterPreVoteDelay = enterPreVoteDelay;
+
+ if (enterPreCommitDelay < 0)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(enterPreCommitDelay),
+ "EnterPreCommitDelay must be greater than or equal to 0.");
+ }
+
+ EnterPreCommitDelay = enterPreCommitDelay;
+
+ if (enterEndCommitDelay < 0)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(enterEndCommitDelay),
+ "EnterEndCommitDelay must be greater than or equal to 0.");
+ }
+
+ EnterEndCommitDelay = enterEndCommitDelay;
+ }
+
+ public int ProposeTimeoutBase { get; }
+
+ public int PreVoteTimeoutBase { get; }
+
+ public int PreCommitTimeoutBase { get; }
+
+ public int ProposeTimeoutDelta { get; }
+
+ public int PreVoteTimeoutDelta { get; }
+
+ public int PreCommitTimeoutDelta { get; }
+
+ public int EnterPreVoteDelay { get; }
+
+ public int EnterPreCommitDelay { get; }
+
+ public int EnterEndCommitDelay { get; }
+ }
+}
diff --git a/src/Libplanet.Net/Consensus/ContextTimeoutOption.cs b/src/Libplanet.Net/Consensus/ContextTimeoutOption.cs
deleted file mode 100644
index 78a5515ffd2..00000000000
--- a/src/Libplanet.Net/Consensus/ContextTimeoutOption.cs
+++ /dev/null
@@ -1,86 +0,0 @@
-using System;
-
-namespace Libplanet.Net.Consensus
-{
- ///
- /// A options class to configure timeout
- /// for each .
- ///
- public class ContextTimeoutOption
- {
- public ContextTimeoutOption(
- int proposeSecondBase = 8,
- int preVoteSecondBase = 4,
- int preCommitSecondBase = 4,
- int proposeMultiplier = 4,
- int preVoteMultiplier = 2,
- int preCommitMultiplier = 2)
- {
- if (proposeSecondBase <= 0)
- {
- throw new ArgumentOutOfRangeException(
- nameof(proposeSecondBase),
- "ProposeSecondBase must be greater than 0.");
- }
-
- ProposeSecondBase = proposeSecondBase;
-
- if (preVoteSecondBase <= 0)
- {
- throw new ArgumentOutOfRangeException(
- nameof(preVoteSecondBase),
- "PreVoteSecondBase must be greater than 0.");
- }
-
- PreVoteSecondBase = preVoteSecondBase;
-
- if (preCommitSecondBase <= 0)
- {
- throw new ArgumentOutOfRangeException(
- nameof(preCommitSecondBase),
- "PreCommitSecondBase must be greater than 0.");
- }
-
- PreCommitSecondBase = preCommitSecondBase;
-
- if (proposeMultiplier <= 0)
- {
- throw new ArgumentOutOfRangeException(
- nameof(proposeMultiplier),
- "ProposeMultiplier must be greater than 0.");
- }
-
- ProposeMultiplier = proposeMultiplier;
-
- if (preVoteMultiplier <= 0)
- {
- throw new ArgumentOutOfRangeException(
- nameof(preVoteMultiplier),
- "PreVoteMultiplier must be greater than 0.");
- }
-
- PreVoteMultiplier = preVoteMultiplier;
-
- if (preCommitMultiplier <= 0)
- {
- throw new ArgumentOutOfRangeException(
- nameof(preCommitMultiplier),
- "PreCommitMultiplier must be greater than 0.");
- }
-
- PreCommitMultiplier = preCommitMultiplier;
- }
-
- public int ProposeSecondBase { get; }
-
- public int PreVoteSecondBase { get; }
-
- public int PreCommitSecondBase { get; }
-
- public int ProposeMultiplier { get; }
-
- public int PreVoteMultiplier { get; }
-
- public int PreCommitMultiplier { get; }
- }
-}
diff --git a/src/Libplanet.Net/Consensus/Gossip.cs b/src/Libplanet.Net/Consensus/Gossip.cs
index 3e0a0031eb0..9ec93da2d74 100644
--- a/src/Libplanet.Net/Consensus/Gossip.cs
+++ b/src/Libplanet.Net/Consensus/Gossip.cs
@@ -542,7 +542,21 @@ private async Task RebuildTableAsync(CancellationToken ctx)
"{FName}: Updating peer table from seed(s) {Seeds}...",
nameof(RebuildTableAsync),
_seeds.Select(s => s.Address.ToHex()));
- await _protocol.RebuildConnectionAsync(Kademlia.MaxDepth, ctx);
+ try
+ {
+ await _protocol.BootstrapAsync(
+ _seeds,
+ TimeSpan.FromSeconds(1),
+ Kademlia.MaxDepth,
+ ctx);
+ }
+ catch (Exception e)
+ {
+ _logger.Error(
+ e,
+ "Peer discovery exception occurred during {FName}.",
+ nameof(RebuildTableAsync));
+ }
}
}
diff --git a/src/Libplanet.Net/Swarm.cs b/src/Libplanet.Net/Swarm.cs
index ca4949609ec..aab456222fb 100644
--- a/src/Libplanet.Net/Swarm.cs
+++ b/src/Libplanet.Net/Swarm.cs
@@ -124,7 +124,7 @@ public Swarm(
consensusReactorOption.ConsensusPeers,
consensusReactorOption.SeedPeers,
consensusReactorOption.TargetBlockInterval,
- consensusReactorOption.ContextTimeoutOptions);
+ consensusReactorOption.ContextOption);
}
}
diff --git a/test/Libplanet.Explorer.Tests/GraphTypes/BlockTypeTest.cs b/test/Libplanet.Explorer.Tests/GraphTypes/BlockTypeTest.cs
index 1c47d06289f..772c4cfc066 100644
--- a/test/Libplanet.Explorer.Tests/GraphTypes/BlockTypeTest.cs
+++ b/test/Libplanet.Explorer.Tests/GraphTypes/BlockTypeTest.cs
@@ -122,7 +122,7 @@ public async void Query()
{ "blockHash", lastVotes[0].BlockHash.ToString() },
{ "timestamp", new DateTimeOffsetGraphType().Serialize(lastVotes[0].Timestamp) },
{ "validatorPublicKey", lastVotes[0].ValidatorPublicKey.ToString() },
- { "validatorPower", lastVotes[0].ValidatorPower },
+ { "validatorPower", lastVotes[0].ValidatorPower?.ToString() },
{ "flag", lastVotes[0].Flag.ToString() },
{ "signature", ByteUtil.Hex(lastVotes[0].Signature) },
}
diff --git a/test/Libplanet.Explorer.Tests/GraphTypes/VoteTypeTest.cs b/test/Libplanet.Explorer.Tests/GraphTypes/VoteTypeTest.cs
index 475928c556f..16968d0f8ca 100644
--- a/test/Libplanet.Explorer.Tests/GraphTypes/VoteTypeTest.cs
+++ b/test/Libplanet.Explorer.Tests/GraphTypes/VoteTypeTest.cs
@@ -57,7 +57,7 @@ public async void Query()
Assert.Equal(vote.BlockHash.ToString(), resultData["blockHash"]);
Assert.Equal(new DateTimeOffsetGraphType().Serialize(vote.Timestamp), resultData["timestamp"]);
Assert.Equal(vote.ValidatorPublicKey.ToString(), resultData["validatorPublicKey"]);
- Assert.Equal(vote.ValidatorPower, resultData["validatorPower"]);
+ Assert.Equal(vote.ValidatorPower?.ToString(), resultData["validatorPower"]);
Assert.Equal(vote.Flag.ToString(), resultData["flag"]);
Assert.Equal(ByteUtil.Hex(vote.Signature), resultData["signature"]);
}
diff --git a/test/Libplanet.Net.Tests/Consensus/ContextNonProposerTest.cs b/test/Libplanet.Net.Tests/Consensus/ContextNonProposerTest.cs
index 9c4e0824ad2..9de309e2db6 100644
--- a/test/Libplanet.Net.Tests/Consensus/ContextNonProposerTest.cs
+++ b/test/Libplanet.Net.Tests/Consensus/ContextNonProposerTest.cs
@@ -531,7 +531,7 @@ public async void TimeoutPropose()
var (_, context) = TestUtils.CreateDummyContext(
privateKey: TestUtils.PrivateKeys[0],
- contextTimeoutOptions: new ContextTimeoutOption(proposeSecondBase: 1));
+ contextOption: new ContextOption(proposeTimeoutBase: 1_000));
context.StateChanged += (_, eventArgs) =>
{
@@ -560,9 +560,9 @@ public async Task UponRulesCheckAfterTimeout()
{
var (blockChain, context) = TestUtils.CreateDummyContext(
privateKey: TestUtils.PrivateKeys[0],
- contextTimeoutOptions: new ContextTimeoutOption(
- preVoteSecondBase: 1,
- preCommitSecondBase: 1));
+ contextOption: new ContextOption(
+ preVoteTimeoutBase: 1_000,
+ preCommitTimeoutBase: 1_000));
var block1 = blockChain.ProposeBlock(TestUtils.PrivateKeys[1]);
var block2 = blockChain.ProposeBlock(TestUtils.PrivateKeys[2]);
@@ -642,7 +642,7 @@ public async Task TimeoutPreVote()
{
var (blockChain, context) = TestUtils.CreateDummyContext(
privateKey: TestUtils.PrivateKeys[0],
- contextTimeoutOptions: new ContextTimeoutOption(preVoteSecondBase: 1));
+ contextOption: new ContextOption(preVoteTimeoutBase: 1_000));
var block = blockChain.ProposeBlock(TestUtils.PrivateKeys[1]);
var timeoutProcessed = new AsyncAutoResetEvent();
@@ -693,7 +693,7 @@ public async Task TimeoutPreCommit()
{
var (blockChain, context) = TestUtils.CreateDummyContext(
privateKey: TestUtils.PrivateKeys[0],
- contextTimeoutOptions: new ContextTimeoutOption(preCommitSecondBase: 1));
+ contextOption: new ContextOption(preCommitTimeoutBase: 1_000));
var block = blockChain.ProposeBlock(TestUtils.PrivateKeys[1]);
var timeoutProcessed = new AsyncAutoResetEvent();
diff --git a/test/Libplanet.Net.Tests/Consensus/ContextTest.cs b/test/Libplanet.Net.Tests/Consensus/ContextTest.cs
index 926d0622ebd..703d5e2b60d 100644
--- a/test/Libplanet.Net.Tests/Consensus/ContextTest.cs
+++ b/test/Libplanet.Net.Tests/Consensus/ContextTest.cs
@@ -28,6 +28,7 @@
using Serilog;
using Xunit;
using Xunit.Abstractions;
+using Xunit.Sdk;
namespace Libplanet.Net.Tests.Consensus
{
@@ -340,7 +341,7 @@ public async Task CanPreCommitOnEndCommit()
blockChain
.GetNextWorldState(0L)
.GetValidatorSet(),
- contextTimeoutOptions: new ContextTimeoutOption());
+ contextOption: new ContextOption());
context.MessageToPublish += (sender, message) => context.ProduceMessage(message);
context.StateChanged += (_, eventArgs) =>
@@ -639,7 +640,7 @@ public async Task CanCreateContextWithLastingEvaluation()
blockChain,
TestUtils.PrivateKeys[0],
newHeightDelay,
- new ContextTimeoutOption());
+ new ContextOption());
Context context = consensusContext.CurrentContext;
context.MessageToPublish += (sender, message) => context.ProduceMessage(message);
@@ -715,6 +716,91 @@ public async Task CanCreateContextWithLastingEvaluation()
Assert.Equal(2, consensusContext.Height);
}
+ [Theory(Timeout = Timeout)]
+ [InlineData(0)]
+ [InlineData(100)]
+ [InlineData(500)]
+ public async Task CanCollectPreVoteAfterMajority(int delay)
+ {
+ var stepChangedToPreVote = new AsyncAutoResetEvent();
+ var stepChangedToPreCommit = new AsyncAutoResetEvent();
+ Block? proposedBlock = null;
+ int numPreVotes = 0;
+ var (_, context) = TestUtils.CreateDummyContext(
+ contextOption: new ContextOption(
+ enterPreCommitDelay: delay));
+ context.StateChanged += (_, eventArgs) =>
+ {
+ if (eventArgs.Step == ConsensusStep.PreVote)
+ {
+ stepChangedToPreVote.Set();
+ }
+ else if (eventArgs.Step == ConsensusStep.PreCommit)
+ {
+ stepChangedToPreCommit.Set();
+ }
+ };
+ context.MessageToPublish += (_, message) =>
+ {
+ if (message is ConsensusProposalMsg proposalMsg)
+ {
+ proposedBlock = BlockMarshaler.UnmarshalBlock(
+ (Dictionary)new Codec().Decode(proposalMsg!.Proposal.MarshaledBlock));
+ }
+ };
+ context.VoteSetModified += (_, tuple) =>
+ {
+ if (tuple.Flag == VoteFlag.PreVote)
+ {
+ numPreVotes = tuple.Votes.Count();
+ }
+ };
+ context.Start();
+ await stepChangedToPreVote.WaitAsync();
+ Assert.Equal(ConsensusStep.PreVote, context.Step);
+ if (proposedBlock is not { } block)
+ {
+ throw new XunitException("No proposal is made");
+ }
+
+ for (int i = 0; i < 3; i++)
+ {
+ context.ProduceMessage(
+ new ConsensusPreVoteMsg(
+ new VoteMetadata(
+ block.Index,
+ 0,
+ block.Hash,
+ DateTimeOffset.UtcNow,
+ TestUtils.PrivateKeys[i].PublicKey,
+ TestUtils.ValidatorSet[i].Power,
+ VoteFlag.PreVote).Sign(TestUtils.PrivateKeys[i])));
+ }
+
+ // Send delayed PreVote message after sending preCommit message
+ var cts = new CancellationTokenSource();
+ const int preVoteDelay = 300;
+ _ = Task.Run(
+ async () =>
+ {
+ await Task.Delay(preVoteDelay, cts.Token);
+ context.ProduceMessage(
+ new ConsensusPreVoteMsg(
+ new VoteMetadata(
+ block.Index,
+ 0,
+ block.Hash,
+ DateTimeOffset.UtcNow,
+ TestUtils.PrivateKeys[3].PublicKey,
+ TestUtils.ValidatorSet[3].Power,
+ VoteFlag.PreVote).Sign(TestUtils.PrivateKeys[3])));
+ }, cts.Token);
+
+ await stepChangedToPreCommit.WaitAsync();
+ cts.Cancel();
+ Assert.Equal(delay < preVoteDelay ? 3 : 4, numPreVotes);
+ }
+
public struct ContextJson
{
#pragma warning disable SA1300
diff --git a/test/Libplanet.Net.Tests/SwarmTest.Evidence.cs b/test/Libplanet.Net.Tests/SwarmTest.Evidence.cs
index 828e44f54ec..9914e9fc432 100644
--- a/test/Libplanet.Net.Tests/SwarmTest.Evidence.cs
+++ b/test/Libplanet.Net.Tests/SwarmTest.Evidence.cs
@@ -41,7 +41,7 @@ public async Task DuplicateVote_Test()
ConsensusPrivateKey = privateKeys[i],
ConsensusWorkers = 100,
TargetBlockInterval = TimeSpan.FromSeconds(4),
- ContextTimeoutOptions = new ContextTimeoutOption(),
+ ContextOption = new ContextOption(),
}).ToList();
var swarmTasks = privateKeys.Select(
diff --git a/test/Libplanet.Net.Tests/SwarmTest.cs b/test/Libplanet.Net.Tests/SwarmTest.cs
index bd41a0e8fa9..83bbf8ce0ec 100644
--- a/test/Libplanet.Net.Tests/SwarmTest.cs
+++ b/test/Libplanet.Net.Tests/SwarmTest.cs
@@ -411,7 +411,7 @@ public async Task BootstrapContext()
ConsensusPrivateKey = TestUtils.PrivateKeys[i],
ConsensusWorkers = 100,
TargetBlockInterval = TimeSpan.FromSeconds(10),
- ContextTimeoutOptions = new ContextTimeoutOption(),
+ ContextOption = new ContextOption(),
}).ToList();
var swarms = new List();
for (int i = 0; i < 4; i++)
diff --git a/test/Libplanet.Net.Tests/TestUtils.cs b/test/Libplanet.Net.Tests/TestUtils.cs
index c73486a3b1e..1942d0280c0 100644
--- a/test/Libplanet.Net.Tests/TestUtils.cs
+++ b/test/Libplanet.Net.Tests/TestUtils.cs
@@ -252,7 +252,7 @@ public static (BlockChain BlockChain, ConsensusContext ConsensusContext)
IBlockPolicy? policy = null,
IActionLoader? actionLoader = null,
PrivateKey? privateKey = null,
- ContextTimeoutOption? contextTimeoutOptions = null)
+ ContextOption? contextOption = null)
{
policy ??= Policy;
var blockChain = CreateDummyBlockChain(policy, actionLoader);
@@ -272,7 +272,7 @@ void BroadcastMessage(ConsensusMsg message) =>
blockChain,
privateKey,
newHeightDelay,
- contextTimeoutOptions ?? new ContextTimeoutOption());
+ contextOption ?? new ContextOption());
return (blockChain, consensusContext);
}
@@ -282,7 +282,7 @@ public static Context CreateDummyContext(
long height = 1,
BlockCommit? lastCommit = null,
PrivateKey? privateKey = null,
- ContextTimeoutOption? contextTimeoutOptions = null,
+ ContextOption? contextOption = null,
ValidatorSet? validatorSet = null)
{
Context? context = null;
@@ -295,7 +295,7 @@ public static Context CreateDummyContext(
validatorSet ?? blockChain
.GetNextWorldState(height - 1)
.GetValidatorSet(),
- contextTimeoutOptions: contextTimeoutOptions ?? new ContextTimeoutOption());
+ contextOption: contextOption ?? new ContextOption());
context.MessageToPublish += (sender, message) => context.ProduceMessage(message);
return context;
}
@@ -307,7 +307,7 @@ public static (BlockChain BlockChain, Context Context)
IBlockPolicy? policy = null,
IActionLoader? actionLoader = null,
PrivateKey? privateKey = null,
- ContextTimeoutOption? contextTimeoutOptions = null,
+ ContextOption? contextOption = null,
ValidatorSet? validatorSet = null)
{
Context? context = null;
@@ -323,7 +323,7 @@ public static (BlockChain BlockChain, Context Context)
validatorSet ?? blockChain
.GetNextWorldState(height - 1)
.GetValidatorSet(),
- contextTimeoutOptions: contextTimeoutOptions ?? new ContextTimeoutOption());
+ contextOption: contextOption ?? new ContextOption());
context.MessageToPublish += (sender, message) => context.ProduceMessage(message);
return (blockChain, context);
@@ -336,7 +336,7 @@ public static ConsensusReactor CreateDummyConsensusReactor(
int consensusPort = 5101,
List? validatorPeers = null,
int newHeightDelayMilliseconds = 10_000,
- ContextTimeoutOption? contextTimeoutOptions = null)
+ ContextOption? contextOption = null)
{
key ??= PrivateKeys[1];
validatorPeers ??= Peers;
@@ -356,7 +356,7 @@ public static ConsensusReactor CreateDummyConsensusReactor(
validatorPeers.ToImmutableList(),
new List().ToImmutableList(),
TimeSpan.FromMilliseconds(newHeightDelayMilliseconds),
- contextTimeoutOption: contextTimeoutOptions ?? new ContextTimeoutOption());
+ contextOption: contextOption ?? new ContextOption());
}
public static byte[] GetRandomBytes(int size)
diff --git a/tools/Libplanet.Explorer/GraphTypes/VoteType.cs b/tools/Libplanet.Explorer/GraphTypes/VoteType.cs
index 2171c4717c6..04975126b9c 100644
--- a/tools/Libplanet.Explorer/GraphTypes/VoteType.cs
+++ b/tools/Libplanet.Explorer/GraphTypes/VoteType.cs
@@ -30,10 +30,10 @@ public VoteType()
"ValidatorPublicKey",
description: "Public key of the validator which is subject of the vote.",
resolve: ctx => ctx.Source.ValidatorPublicKey);
- Field(
+ Field(
"ValidatorPower",
description: "Power of the validator which is subject of the vote.",
- resolve: ctx => ctx.Source.ValidatorPower);
+ resolve: ctx => ctx.Source.ValidatorPower?.ToString());
Field>(
"Flag",
description: "Flag of the vote",