private void OnChangeViewReceived(ConsensusPayload payload, ChangeView message) { // We keep track of the payload hashes received in this block, and don't respond with recovery // in response to the same payload that we already responded to previously. // ChangeView messages include a Timestamp when the change view is sent, thus if a node restarts // and issues a change view for the same view, it will have a different hash and will correctly respond // again; however replay attacks of the ChangeView message from arbitrary nodes will not trigger an // additonal recovery message response. if (!knownHashes.Add(payload.Hash)) { return; } if (message.NewViewNumber <= context.ViewNumber) { bool shouldSendRecovery = false; // Limit recovery to sending from `f` nodes when the request is from a lower view number. int allowedRecoveryNodeCount = context.F(); for (int i = 0; i < allowedRecoveryNodeCount; i++) { var eligibleResponders = context.Validators.Length - 1; var chosenIndex = (payload.ValidatorIndex + i + message.NewViewNumber) % eligibleResponders; if (chosenIndex >= payload.ValidatorIndex) { chosenIndex++; } if (chosenIndex != context.MyIndex) { continue; } shouldSendRecovery = true; break; } if (!shouldSendRecovery) { return; } Log($"send recovery from view: {message.ViewNumber} to view: {context.ViewNumber}"); localNode.Tell(new LocalNode.SendDirectly { Inventory = context.MakeRecoveryMessage() }); } if (context.CommitSent()) { return; } var expectedView = GetLastExpectedView(payload.ValidatorIndex); if (message.NewViewNumber <= expectedView) { return; } Log($"{nameof(OnChangeViewReceived)}: height={payload.BlockIndex} view={message.ViewNumber} index={payload.ValidatorIndex} nv={message.NewViewNumber}"); context.ChangeViewPayloads[payload.ValidatorIndex] = payload; CheckExpectedView(message.NewViewNumber); }
// A possible attack can happen if the last node to commit is malicious and either sends change view after his // commit to stall nodes in a higher view, or if he refuses to send recovery messages. In addition, if a node // asking change views loses network or crashes and comes back when nodes are committed in more than one higher // numbered view, it is possible for the node accepting recovery and commit in any of the higher views, thus // potentially splitting nodes among views and stalling the network. public static bool MoreThanFNodesCommitted(this IConsensusContext context) => context.CommitPayloads.Count(p => p != null) > context.F();
public static int M(this IConsensusContext context) => context.Validators.Length - context.F();
// A possible attack can happen if the last node to commit is malicious and either sends change view after his // commit to stall nodes in a higher view, or if he refuses to send recovery messages. In addition, if a node // asking change views loses network or crashes and comes back when nodes are committed in more than one higher // numbered view, it is possible for the node accepting recovery to commit in any of the higher views, thus // potentially splitting nodes among views and stalling the network. public static bool MoreThanFNodesCommittedOrLost(this IConsensusContext context) => (context.CountCommitted() + context.CountFailed()) > context.F();