Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stage 3 of dBFT (Commit) #320

Closed
wants to merge 16 commits into from
32 changes: 32 additions & 0 deletions neo/Consensus/CommitAgreement.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System.IO;
using Neo.IO;

namespace Neo.Consensus
{
internal class CommitAgreement : ConsensusMessage
{
/// <summary>
/// Block hash of the signature
/// </summary>
public UInt256 BlockHash;

/// <summary>
/// Constructors
/// </summary>
public CommitAgreement() : base(ConsensusMessageType.CommitAgreement) { }

public override void Deserialize(BinaryReader reader)
{
base.Deserialize(reader);

BlockHash = reader.ReadSerializable<UInt256>();
}

public override void Serialize(BinaryWriter writer)
{
base.Serialize(writer);

writer.Write(BlockHash);
}
}
}
48 changes: 44 additions & 4 deletions neo/Consensus/ConsensusContext.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
using Neo.Core;
using System.Collections.Generic;
using System.Linq;
using Neo.Core;
using Neo.Cryptography;
using Neo.Cryptography.ECC;
using Neo.IO;
using Neo.Network.Payloads;
using Neo.Wallets;
using System.Collections.Generic;
using System.Linq;

namespace Neo.Consensus
{
Expand All @@ -28,21 +28,46 @@ internal class ConsensusContext
public byte[] ExpectedView;
public KeyPair KeyPair;

private UInt256[] Commits;
private Block _header = null;

public UInt256 CommitHash => _header?.Hash;
public int M => Validators.Length - (Validators.Length - 1) / 3;

public bool TryToCommit(ConsensusPayload payload, CommitAgreement message)
{
// Already received

if (Commits[payload.ValidatorIndex] != null) return false;

// Store received block hash

Commits[payload.ValidatorIndex] = message.BlockHash;

// Check count

return _header != null && Commits.Where(u => u != null && u == _header.Hash).Count() >= M;
}

public void ChangeView(byte view_number)
{
int p = ((int)BlockIndex - view_number) % Validators.Length;
State &= ConsensusState.SignatureSent;
ViewNumber = view_number;
PrimaryIndex = p >= 0 ? (uint)p : (uint)(p + Validators.Length);

if (State == ConsensusState.Initial)
{
TransactionHashes = null;
Signatures = new byte[Validators.Length][];
Commits = new UInt256[Validators.Length];
}

if (MyIndex >= 0)
{
ExpectedView[MyIndex] = view_number;
}

_header = null;
}

Expand All @@ -54,10 +79,10 @@ public ConsensusPayload MakeChangeView()
});
}

private Block _header = null;
public Block MakeHeader()
{
if (TransactionHashes == null) return null;

if (_header == null)
{
_header = new Block
Expand All @@ -71,10 +96,23 @@ public Block MakeHeader()
NextConsensus = NextConsensus,
Transactions = new Transaction[0]
};

Commits[MyIndex] = _header.Hash;
}

return _header;
}

public ConsensusPayload MakeCommitAgreement()
{
if (_header == null) return null;

return MakePayload(new CommitAgreement()
{
BlockHash = _header.Hash
});
}

private ConsensusPayload MakePayload(ConsensusMessage message)
{
message.ViewNumber = ViewNumber;
Expand Down Expand Up @@ -122,6 +160,8 @@ public void Reset(Wallet wallet)
Signatures = new byte[Validators.Length][];
ExpectedView = new byte[Validators.Length];
KeyPair = null;
Commits = new UInt256[Validators.Length];

for (int i = 0; i < Validators.Length; i++)
{
WalletAccount account = wallet.GetAccount(Validators[i]);
Expand Down
4 changes: 3 additions & 1 deletion neo/Consensus/ConsensusMessageType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,7 @@ internal enum ConsensusMessageType : byte
PrepareRequest = 0x20,
[ReflectionCache(typeof(PrepareResponse))]
PrepareResponse = 0x21,
[ReflectionCache(typeof(CommitAgreement))]
CommitAgreement = 0x022,
}
}
}
123 changes: 77 additions & 46 deletions neo/Consensus/ConsensusService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,17 @@ private bool CheckPolicy(Transaction tx)
return true;
}

private void CheckSignatures()
private void OnCommitAgreement(ConsensusPayload payload, CommitAgreement message)
{
Log($"{nameof(OnCommitAgreement)}: height={payload.BlockIndex} hash={message.BlockHash.ToString()} view={message.ViewNumber} index={payload.ValidatorIndex}");

if (context.State.HasFlag(ConsensusState.BlockSent) ||
!context.TryToCommit(payload, message)) return;

if (context.Signatures.Count(p => p != null) >= context.M && context.TransactionHashes.All(p => context.Transactions.ContainsKey(p)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add context.State.HasFlag(ConsensusState.CommitSent) here in this last verification before relay.

Copy link
Member

@vncoelho vncoelho Aug 29, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean line 95:

if (context.Signatures.Count(p => p != null) >= context.M && context.TransactionHashes.All(p => context.Transactions.ContainsKey(p)))

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But this method is called when receive a commit, no when you sent your commit, so if you receive 6 commits, and you don't send your commit, you never relay the block.

{
context.State |= ConsensusState.BlockSent;

Contract contract = Contract.CreateMultiSigContract(context.M, context.Validators);
Block block = context.MakeHeader();
ContractParametersContext sc = new ContractParametersContext(block);
Expand All @@ -101,9 +108,26 @@ private void CheckSignatures()
sc.Verifiable.Scripts = sc.GetScripts();
block.Transactions = context.TransactionHashes.Select(p => context.Transactions[p]).ToArray();
Log($"relay block: {block.Hash}");

if (!localNode.Relay(block))
{
Log($"reject block: {block.Hash}");
context.State |= ConsensusState.BlockSent;
}
}
}

private void CheckSignatures()
{
if (!context.State.HasFlag(ConsensusState.CommitSent) &&
context.Signatures.Count(p => p != null) >= context.M &&
context.TransactionHashes.All(p => context.Transactions.ContainsKey(p)))
{
// Send my commit

context.State |= ConsensusState.CommitSent;
SignAndRelay(context.MakeCommitAgreement());

Log($"Commit sent: height={context.BlockIndex} hash={context.CommitHash} state={context.State}");
}
}

Expand Down Expand Up @@ -206,61 +230,62 @@ private void InitializeConsensus(byte view_number)

private void LocalNode_InventoryReceived(object sender, IInventory inventory)
{
ConsensusPayload payload = inventory as ConsensusPayload;
if (payload != null)
if (inventory == null || !(inventory is ConsensusPayload payload)) return;

lock (context)
{
lock (context)
if (payload.ValidatorIndex == context.MyIndex ||
Copy link
Member

@vncoelho vncoelho Aug 29, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not see the ConsensusPayload payload = inventory as ConsensusPayload;, where is it now @shargon ?

Is this (inventory is ConsensusPayload payload)) creating the object locally in the scope of this function?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#233 :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But I think that we can remove inventory==null because the is check nulls

payload.ValidatorIndex >= context.Validators.Length ||
payload.Version != ConsensusContext.Version) return;

if (payload.PrevHash != context.PrevHash || payload.BlockIndex != context.BlockIndex)
{
if (payload.ValidatorIndex == context.MyIndex) return;
// Request blocks

if (payload.Version != ConsensusContext.Version)
return;
if (payload.PrevHash != context.PrevHash || payload.BlockIndex != context.BlockIndex)
if (Blockchain.Default?.Height + 1 < payload.BlockIndex)
{
// Request blocks
Log($"chain sync: expected={payload.BlockIndex} current: {Blockchain.Default?.Height} nodes={localNode.RemoteNodeCount}");

if (Blockchain.Default?.Height + 1 < payload.BlockIndex)
{
Log($"chain sync: expected={payload.BlockIndex} current: {Blockchain.Default?.Height} nodes={localNode.RemoteNodeCount}");
localNode.RequestGetBlocks();
}

localNode.RequestGetBlocks();
}
return;
}

return;
}
ConsensusMessage message;
try
{
message = ConsensusMessage.DeserializeFrom(payload.Data);
}
catch
{
return;
}

if (payload.ValidatorIndex >= context.Validators.Length) return;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check if payload validator Index could had changed on RequestGetBlocks()

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ConsensusMessage message;
try
{
message = ConsensusMessage.DeserializeFrom(payload.Data);
}
catch
{
return;
}
if (message.ViewNumber != context.ViewNumber && message.Type != ConsensusMessageType.ChangeView)
return;
switch (message.Type)
{
case ConsensusMessageType.ChangeView:
OnChangeViewReceived(payload, (ChangeView)message);
break;
case ConsensusMessageType.PrepareRequest:
OnPrepareRequestReceived(payload, (PrepareRequest)message);
break;
case ConsensusMessageType.PrepareResponse:
OnPrepareResponseReceived(payload, (PrepareResponse)message);
break;
}
if (message.ViewNumber != context.ViewNumber && message.Type != ConsensusMessageType.ChangeView)
return;

switch (message.Type)
{
case ConsensusMessageType.ChangeView:
OnChangeViewReceived(payload, (ChangeView)message);
break;
case ConsensusMessageType.PrepareRequest:
OnPrepareRequestReceived(payload, (PrepareRequest)message);
break;
case ConsensusMessageType.PrepareResponse:
OnPrepareResponseReceived(payload, (PrepareResponse)message);
break;
case ConsensusMessageType.CommitAgreement:
OnCommitAgreement(payload, (CommitAgreement)message);
break;
}
}
}

private void LocalNode_InventoryReceiving(object sender, InventoryReceivingEventArgs e)
{
Transaction tx = e.Inventory as Transaction;
if (tx != null)
if (e.Inventory is Transaction tx)
{
lock (context)
{
Expand All @@ -274,22 +299,23 @@ private void LocalNode_InventoryReceiving(object sender, InventoryReceivingEvent
}
}

protected virtual void Log(string message)
{
}
protected virtual void Log(string message) { }

private void OnChangeViewReceived(ConsensusPayload payload, ChangeView message)
{
Log($"{nameof(OnChangeViewReceived)}: height={payload.BlockIndex} view={message.ViewNumber} index={payload.ValidatorIndex} nv={message.NewViewNumber}");

if (message.NewViewNumber <= context.ExpectedView[payload.ValidatorIndex])
return;

context.ExpectedView[payload.ValidatorIndex] = message.NewViewNumber;
CheckExpectedView(message.NewViewNumber);
}

private void OnPrepareRequestReceived(ConsensusPayload payload, PrepareRequest message)
{
Log($"{nameof(OnPrepareRequestReceived)}: height={payload.BlockIndex} view={message.ViewNumber} index={payload.ValidatorIndex} tx={message.TransactionHashes.Length}");

if (!context.State.HasFlag(ConsensusState.Backup) || context.State.HasFlag(ConsensusState.RequestReceived))
return;
if (payload.ValidatorIndex != context.PrimaryIndex) return;
Expand All @@ -298,6 +324,7 @@ private void OnPrepareRequestReceived(ConsensusPayload payload, PrepareRequest m
Log($"Timestamp incorrect: {payload.Timestamp}");
return;
}

context.State |= ConsensusState.RequestReceived;
context.Timestamp = payload.Timestamp;
context.Nonce = message.Nonce;
Expand Down Expand Up @@ -328,8 +355,10 @@ private void OnPrepareRequestReceived(ConsensusPayload payload, PrepareRequest m
private void OnPrepareResponseReceived(ConsensusPayload payload, PrepareResponse message)
{
Log($"{nameof(OnPrepareResponseReceived)}: height={payload.BlockIndex} view={message.ViewNumber} index={payload.ValidatorIndex}");

if (context.State.HasFlag(ConsensusState.BlockSent)) return;
if (context.Signatures[payload.ValidatorIndex] != null) return;

Block header = context.MakeHeader();
if (header == null || !Crypto.Default.VerifySignature(header.GetHashData(), message.Signature, context.Validators[payload.ValidatorIndex].EncodePoint(false))) return;
context.Signatures[payload.ValidatorIndex] = message.Signature;
Expand Down Expand Up @@ -373,6 +402,8 @@ private void RequestChangeView()

private void SignAndRelay(ConsensusPayload payload)
{
if (payload == null) return;
Copy link
Member

@vncoelho vncoelho Aug 29, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this just for the commit case or it is a general case that has been happening?


ContractParametersContext sc;
try
{
Expand Down
1 change: 1 addition & 0 deletions neo/Consensus/ConsensusState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ internal enum ConsensusState : byte
SignatureSent = 0x10,
BlockSent = 0x20,
ViewChanging = 0x40,
CommitSent = 0x80,
}
}