Conversation
|
@claude review |
|
Claude finished @ak88's task in 2m 57s —— View job Review: Refactor sign tx
The refactor moves signing-transaction logic out of the Critical
High
Medium
Low
What's good
|
|
Claude finished @ak88's task in 2m 7s —— View job Review: Refactor sign tx
The refactor moves signing-transaction logic out of High
Medium
The registration was changed to
After this refactor, nothing outside
Low
Repo style requires Verdict: not yet mergeable. The High finding (silent exception + cache poisoning on sign failure) and the dead-code Medium findings should be addressed first. |
| if (spec == null) | ||
| return; | ||
|
|
||
| if (xdcHeader.Number % spec.MergeSignRange == 0) |
There was a problem hiding this comment.
Critical: inverted signing condition
The old code in XdcHotStuff signed when Number % MergeSignRange == 0:
if (_highestSignTxNumber < roundParent.Number && (roundParent.Number % spec.MergeSignRange == 0))
{ // sign }PenaltyHandler and XdcRewardCalculator both confirm the semantic: actions occur at multiples of MergeSignRange. The new code does the opposite — it returns early (skips) at multiples and signs on every other block, which is a complete behavioral inversion.
| if (xdcHeader.Number % spec.MergeSignRange == 0) | |
| if (xdcHeader.Number % spec.MergeSignRange != 0) | |
| return; |
| if (IsMasternode(snapshot, _signer.Address)) | ||
| { | ||
| _alreadySigned.Set(xdcHeader.Hash); | ||
| _ = SubmitTransactionSign(xdcHeader, spec); |
There was a problem hiding this comment.
High: fire-and-forget swallows async exceptions
_ = SubmitTransactionSign(...) discards the Task, so any exception thrown inside SubmitTransactionSign (signer failure, tx-pool error, etc.) is silently lost. Per robustness.md: "A missing await on an async call silently discards the result. Only omit await when fire-and-forget is the documented intent."
This handler fires on an event callback so it cannot be async directly — the conventional fix is a helper that logs on failure:
| _ = SubmitTransactionSign(xdcHeader, spec); | |
| _ = SubmitTransactionSign(xdcHeader, spec).ContinueWith( | |
| t => _logger.Error("Failed to submit sign transaction", t.Exception), | |
| System.Threading.Tasks.TaskContinuationOptions.OnlyOnFaulted); |
| if (_alreadySigned.Contains(xdcHeader.Hash)) | ||
| return; | ||
|
|
||
| ulong round = xdcHeader.ExtraConsensusData.BlockRound; |
There was a problem hiding this comment.
Medium: potential NullReferenceException
ExtraConsensusData is not null-checked before accessing .BlockRound. XdcHotStuff.OnNewHeadBlock throws explicitly when it's null; this event handler would throw an unhandled exception from the blockchain core instead.
| ulong round = xdcHeader.ExtraConsensusData.BlockRound; | |
| if (xdcHeader.ExtraConsensusData is null) | |
| return; | |
| ulong round = xdcHeader.ExtraConsensusData.BlockRound; |
There was a problem hiding this comment.
It is expected to be a xdc header
|
|
||
| ulong round = xdcHeader.ExtraConsensusData.BlockRound; | ||
| IXdcReleaseSpec spec = _specProvider.GetXdcSpec(xdcHeader, round); | ||
| if (spec == null) |
There was a problem hiding this comment.
Medium: use is null instead of == null
Per coding-style.md: "Use is null and is not null instead of == null and != null".
| if (spec == null) | |
| if (spec is null) | |
| return; |
| private readonly IBlockTree _blockTree; | ||
| private readonly ISnapshotManager _snapshotManager; | ||
| private readonly ISpecProvider _specProvider; | ||
| private AssociativeKeyCache<ValueHash256> _alreadySigned = new (128); |
There was a problem hiding this comment.
Low: field should be readonly
_alreadySigned is assigned once in the constructor and never reassigned; it should be readonly.
| private AssociativeKeyCache<ValueHash256> _alreadySigned = new (128); | |
| private readonly AssociativeKeyCache<ValueHash256> _alreadySigned = new(128); |
|
|
||
| private ISnapshotManager CreateSnapshotManager([KeyFilter(XdcRocksDbConfigFactory.XdcSnapshotDbName)] IDb db, IBlockTree blockTree, IMasternodeVotingContract votingContract, ISpecProvider specProvider) => new SnapshotManager(db, blockTree, votingContract, specProvider); | ||
| private ISignTransactionManager CreateSignTransactionManager(ISigner signer, ITxPool txPool, ILogManager logManager) => new SignTransactionManager(signer, txPool, logManager.GetClassLogger<SignTransactionManager>()); | ||
| private ISignTransactionManager CreateSignTransactionManager(ISigner signer, ITxPool txPool, IBlockTree blockTree, ISnapshotManager snapshotManager, ISpecProvider specProvider, ILogManager logManager) => new SignTransactionManager(signer, txPool, blockTree, snapshotManager, specProvider, logManager.GetClassLogger<SignTransactionManager>()); |
There was a problem hiding this comment.
Low: dead code
CreateSignTransactionManager is no longer called — the registration changed to .AddSingleton<ISignTransactionManager, SignTransactionManager>() which lets Autofac resolve the constructor directly. This method can be deleted.
| if (IsMasternode(snapshot, _signer.Address)) | ||
| { | ||
| _alreadySigned.Set(xdcHeader.Hash); | ||
| _ = SubmitTransactionSign(xdcHeader, spec); |
There was a problem hiding this comment.
High — fire-and-forget swallows exceptions; failed sign is silently skipped forever
_alreadySigned.Set is called before the async sign attempt. If SubmitTransactionSign throws (signer unavailable, key error, etc.), the exception is swallowed and the hash remains in the cache — so this block can never be retried.
Because OnBlockAddedToMain is a synchronous event handler you cannot await here directly, but you should at minimum log the failure and avoid poisoning the cache:
| _ = SubmitTransactionSign(xdcHeader, spec); | |
| if (IsMasternode(snapshot, _signer.Address)) | |
| { | |
| _alreadySigned.Set(xdcHeader.Hash); | |
| _ = SubmitTransactionSign(xdcHeader, spec).ContinueWith(t => | |
| { | |
| if (t.IsFaulted) | |
| { | |
| _alreadySigned.Delete(xdcHeader.Hash); // allow retry on next event | |
| _logger.Error($"SubmitTransactionSign failed for block {xdcHeader.Hash}", t.Exception!.InnerException); | |
| } | |
| }, TaskContinuationOptions.OnlyOnFaulted); | |
| } |
(Or factor the fire-and-forget into a small async Task SignAsync(...) method with a try/catch that handles removal and logging, which avoids the lambda capturing overhead.)
There was a problem hiding this comment.
if we fail to submit our transaction it is fine, just ignore it. Dont want to retry it.
|
|
||
| ulong round = xdcHeader.ExtraConsensusData.BlockRound; | ||
| IXdcReleaseSpec spec = _specProvider.GetXdcSpec(xdcHeader, round); | ||
| if (spec == null) |
There was a problem hiding this comment.
Low — style: use is null not == null
Per the repo coding style (coding-style.md): "Use is null and is not null instead of == null and != null".
| if (spec == null) | |
| if (spec is null) |
|
|
||
| private ISnapshotManager CreateSnapshotManager([KeyFilter(XdcRocksDbConfigFactory.XdcSnapshotDbName)] IDb db, IBlockTree blockTree, IMasternodeVotingContract votingContract, ISpecProvider specProvider) => new SnapshotManager(db, blockTree, votingContract, specProvider); | ||
| private ISignTransactionManager CreateSignTransactionManager(ISigner signer, ITxPool txPool, ILogManager logManager) => new SignTransactionManager(signer, txPool, logManager.GetClassLogger<SignTransactionManager>()); | ||
| private ISignTransactionManager CreateSignTransactionManager(ISigner signer, ITxPool txPool, IBlockTree blockTree, ISnapshotManager snapshotManager, ISpecProvider specProvider, ILogManager logManager) => new SignTransactionManager(signer, txPool, blockTree, snapshotManager, specProvider, logManager.GetClassLogger<SignTransactionManager>()); |
There was a problem hiding this comment.
Medium — dead code: this factory method is no longer wired to any DI registration
After the refactor, line 104 registers SignTransactionManager directly via Autofac constructor injection:
.AddSingleton<ISignTransactionManager, SignTransactionManager>()This CreateSignTransactionManager factory is never called. Delete it.
Similarly, ISignTransactionManager is now only used internally within SignTransactionManager itself — nothing external resolves it from the container. Consider whether the interface (and the DI registration) still serves a purpose, or if SignTransactionManager can simply be registered as a concrete IDisposable singleton activated by the container. Fix this →
|
@claude again |
|
Claude finished @ak88's task in 3m 19s —— View job Re-review: Refactor sign tx
The previous review issues have been addressed — the inverted condition is fixed, However, a silent regression slipped in during the refactor of Critical: missing
|
Refactor sign tx out of the consensus module and instead just listen for event inside the manager.