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

Use ExtensiblePayload in consensus #2202

Merged
merged 9 commits into from
Jan 10, 2021
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 110 additions & 37 deletions src/neo/Consensus/ConsensusContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using static Neo.Consensus.RecoveryMessage;

namespace Neo.Consensus
{
Expand All @@ -29,10 +30,10 @@ public class ConsensusContext : IDisposable, ISerializable
public int MyIndex;
public UInt256[] TransactionHashes;
public Dictionary<UInt256, Transaction> Transactions;
public ConsensusPayload[] PreparationPayloads;
public ConsensusPayload[] CommitPayloads;
public ConsensusPayload[] ChangeViewPayloads;
public ConsensusPayload[] LastChangeViewPayloads;
public ExtensiblePayload[] PreparationPayloads;
public ExtensiblePayload[] CommitPayloads;
public ExtensiblePayload[] ChangeViewPayloads;
public ExtensiblePayload[] LastChangeViewPayloads;
// LastSeenMessage array stores the height of the last seen message, for each validator.
// if this node never heard from validator i, LastSeenMessage[i] will be -1.
public Dictionary<ECPoint, uint> LastSeenMessage { get; private set; }
Expand All @@ -47,6 +48,7 @@ public class ConsensusContext : IDisposable, ISerializable
private int _witnessSize;
private readonly Wallet wallet;
private readonly IStore store;
private Dictionary<UInt256, ConsensusMessage> cachedMessages;

public int F => (Validators.Length - 1) / 3;
public int M => Validators.Length - F;
Expand Down Expand Up @@ -79,7 +81,7 @@ public bool ValidatorsChanged
public bool ResponseSent => !WatchOnly && PreparationPayloads[MyIndex] != null;
public bool CommitSent => !WatchOnly && CommitPayloads[MyIndex] != null;
public bool BlockSent => Block.Transactions != null;
public bool ViewChanging => !WatchOnly && ChangeViewPayloads[MyIndex]?.GetDeserializedMessage<ChangeView>().NewViewNumber > ViewNumber;
public bool ViewChanging => !WatchOnly && GetMessage<ChangeView>(ChangeViewPayloads[MyIndex])?.NewViewNumber > ViewNumber;
public bool NotAcceptingPayloadsDueToViewChanging => ViewChanging && !MoreThanFNodesCommittedOrLost;
// 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
Expand All @@ -104,15 +106,34 @@ public Block CreateBlock()
ContractParametersContext sc = new ContractParametersContext(Block);
for (int i = 0, j = 0; i < Validators.Length && j < M; i++)
{
if (CommitPayloads[i]?.ConsensusMessage.ViewNumber != ViewNumber) continue;
sc.AddSignature(contract, Validators[i], CommitPayloads[i].GetDeserializedMessage<Commit>().Signature);
if (GetMessage(CommitPayloads[i])?.ViewNumber != ViewNumber) continue;
sc.AddSignature(contract, Validators[i], GetMessage<Commit>(CommitPayloads[i]).Signature);
j++;
}
Block.Witness = sc.GetWitnesses()[0];
Block.Transactions = TransactionHashes.Select(p => Transactions[p]).ToArray();
return Block;
}

public ExtensiblePayload CreatePayload(ConsensusMessage message, byte[] invocationScript = null)
{
ExtensiblePayload payload = new ExtensiblePayload
{
Category = "Consensus",
ValidBlockStart = 0,
ValidBlockEnd = message.BlockIndex,
Sender = GetSender(message.ValidatorIndex),
Data = message.ToArray(),
Witness = invocationScript is null ? null : new Witness
{
InvocationScript = invocationScript,
VerificationScript = Contract.CreateSignatureRedeemScript(Validators[message.ValidatorIndex])
}
};
cachedMessages.TryAdd(payload.Hash, message);
return payload;
}

public void Deserialize(BinaryReader reader)
{
Reset(0);
Expand All @@ -126,10 +147,10 @@ public void Deserialize(BinaryReader reader)
ViewNumber = reader.ReadByte();
TransactionHashes = reader.ReadSerializableArray<UInt256>();
Transaction[] transactions = reader.ReadSerializableArray<Transaction>(Block.MaxTransactionsPerBlock);
PreparationPayloads = reader.ReadNullableArray<ConsensusPayload>(ProtocolSettings.Default.ValidatorsCount);
CommitPayloads = reader.ReadNullableArray<ConsensusPayload>(ProtocolSettings.Default.ValidatorsCount);
ChangeViewPayloads = reader.ReadNullableArray<ConsensusPayload>(ProtocolSettings.Default.ValidatorsCount);
LastChangeViewPayloads = reader.ReadNullableArray<ConsensusPayload>(ProtocolSettings.Default.ValidatorsCount);
PreparationPayloads = reader.ReadNullableArray<ExtensiblePayload>(ProtocolSettings.Default.ValidatorsCount);
CommitPayloads = reader.ReadNullableArray<ExtensiblePayload>(ProtocolSettings.Default.ValidatorsCount);
ChangeViewPayloads = reader.ReadNullableArray<ExtensiblePayload>(ProtocolSettings.Default.ValidatorsCount);
LastChangeViewPayloads = reader.ReadNullableArray<ExtensiblePayload>(ProtocolSettings.Default.ValidatorsCount);
if (TransactionHashes.Length == 0 && !RequestSentOrReceived)
TransactionHashes = null;
Transactions = transactions.Length == 0 && !RequestSentOrReceived ? null : transactions.ToDictionary(p => p.Hash);
Expand All @@ -154,13 +175,64 @@ public Block EnsureHeader()
return Block;
}

public ConsensusMessage GetMessage(ExtensiblePayload payload)
{
if (payload is null) return null;
if (!cachedMessages.TryGetValue(payload.Hash, out ConsensusMessage message))
cachedMessages.Add(payload.Hash, message = ConsensusMessage.DeserializeFrom(payload.Data));
return message;
}

public T GetMessage<T>(ExtensiblePayload payload) where T : ConsensusMessage
{
return (T)GetMessage(payload);
}

private ChangeViewPayloadCompact GetChangeViewPayloadCompact(ExtensiblePayload payload)
{
ChangeView message = GetMessage<ChangeView>(payload);
return new ChangeViewPayloadCompact
{
ValidatorIndex = message.ValidatorIndex,
OriginalViewNumber = message.ViewNumber,
Timestamp = message.Timestamp,
InvocationScript = payload.Witness.InvocationScript
};
}

private CommitPayloadCompact GetCommitPayloadCompact(ExtensiblePayload payload)
{
Commit message = GetMessage<Commit>(payload);
return new CommitPayloadCompact
{
ViewNumber = message.ViewNumber,
ValidatorIndex = message.ValidatorIndex,
Signature = message.Signature,
InvocationScript = payload.Witness.InvocationScript
};
}

private PreparationPayloadCompact GetPreparationPayloadCompact(ExtensiblePayload payload)
{
return new PreparationPayloadCompact
{
ValidatorIndex = GetMessage(payload).ValidatorIndex,
InvocationScript = payload.Witness.InvocationScript
};
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte GetPrimaryIndex(byte viewNumber)
{
int p = ((int)Block.Index - viewNumber) % Validators.Length;
return p >= 0 ? (byte)p : (byte)(p + Validators.Length);
}

public UInt160 GetSender(int index)
{
return Contract.CreateSignatureRedeemScript(Validators[index]).ToScriptHash();
}

public bool Load()
{
byte[] data = store.TryGet(ConsensusStatePrefix, null);
Expand All @@ -180,7 +252,7 @@ public bool Load()
}
}

public ConsensusPayload MakeChangeView(ChangeViewReason reason)
public ExtensiblePayload MakeChangeView(ChangeViewReason reason)
{
return ChangeViewPayloads[MyIndex] = MakeSignedPayload(new ChangeView
{
Expand All @@ -189,30 +261,25 @@ public ConsensusPayload MakeChangeView(ChangeViewReason reason)
});
}

public ConsensusPayload MakeCommit()
public ExtensiblePayload MakeCommit()
{
return CommitPayloads[MyIndex] ?? (CommitPayloads[MyIndex] = MakeSignedPayload(new Commit
{
Signature = EnsureHeader().Sign(keyPair)
}));
}

private ConsensusPayload MakeSignedPayload(ConsensusMessage message)
private ExtensiblePayload MakeSignedPayload(ConsensusMessage message)
{
message.BlockIndex = Block.Index;
message.ValidatorIndex = (byte)MyIndex;
message.ViewNumber = ViewNumber;
ConsensusPayload payload = new ConsensusPayload
{
Version = Block.Version,
PrevHash = Block.PrevHash,
BlockIndex = Block.Index,
ValidatorIndex = (byte)MyIndex,
ConsensusMessage = message
};
ExtensiblePayload payload = CreatePayload(message, null);
SignPayload(payload);
return payload;
}

private void SignPayload(ConsensusPayload payload)
private void SignPayload(ExtensiblePayload payload)
{
ContractParametersContext sc;
try
Expand Down Expand Up @@ -308,7 +375,7 @@ internal void EnsureMaxBlockLimitation(IEnumerable<Transaction> txs)
TransactionHashes = hashes.ToArray();
}

public ConsensusPayload MakePrepareRequest()
public ExtensiblePayload MakePrepareRequest()
{
var random = new Random();
Span<byte> buffer = stackalloc byte[sizeof(ulong)];
Expand All @@ -319,47 +386,52 @@ public ConsensusPayload MakePrepareRequest()

return PreparationPayloads[MyIndex] = MakeSignedPayload(new PrepareRequest
{
Version = Block.Version,
PrevHash = Block.PrevHash,
Timestamp = Block.Timestamp,
Nonce = Block.ConsensusData.Nonce,
TransactionHashes = TransactionHashes
});
}

public ConsensusPayload MakeRecoveryRequest()
public ExtensiblePayload MakeRecoveryRequest()
{
return MakeSignedPayload(new RecoveryRequest
{
Timestamp = TimeProvider.Current.UtcNow.ToTimestampMS()
});
}

public ConsensusPayload MakeRecoveryMessage()
public ExtensiblePayload MakeRecoveryMessage()
{
PrepareRequest prepareRequestMessage = null;
if (TransactionHashes != null)
{
prepareRequestMessage = new PrepareRequest
{
Version = Block.Version,
PrevHash = Block.PrevHash,
ViewNumber = ViewNumber,
Timestamp = Block.Timestamp,
BlockIndex = Block.Index,
Nonce = Block.ConsensusData.Nonce,
TransactionHashes = TransactionHashes
};
}
return MakeSignedPayload(new RecoveryMessage()
{
ChangeViewMessages = LastChangeViewPayloads.Where(p => p != null).Select(p => RecoveryMessage.ChangeViewPayloadCompact.FromPayload(p)).Take(M).ToDictionary(p => (int)p.ValidatorIndex),
ChangeViewMessages = LastChangeViewPayloads.Where(p => p != null).Select(p => GetChangeViewPayloadCompact(p)).Take(M).ToDictionary(p => (int)p.ValidatorIndex),
PrepareRequestMessage = prepareRequestMessage,
// We only need a PreparationHash set if we don't have the PrepareRequest information.
PreparationHash = TransactionHashes == null ? PreparationPayloads.Where(p => p != null).GroupBy(p => p.GetDeserializedMessage<PrepareResponse>().PreparationHash, (k, g) => new { Hash = k, Count = g.Count() }).OrderByDescending(p => p.Count).Select(p => p.Hash).FirstOrDefault() : null,
PreparationMessages = PreparationPayloads.Where(p => p != null).Select(p => RecoveryMessage.PreparationPayloadCompact.FromPayload(p)).ToDictionary(p => (int)p.ValidatorIndex),
PreparationHash = TransactionHashes == null ? PreparationPayloads.Where(p => p != null).GroupBy(p => GetMessage<PrepareResponse>(p).PreparationHash, (k, g) => new { Hash = k, Count = g.Count() }).OrderByDescending(p => p.Count).Select(p => p.Hash).FirstOrDefault() : null,
PreparationMessages = PreparationPayloads.Where(p => p != null).Select(p => GetPreparationPayloadCompact(p)).ToDictionary(p => (int)p.ValidatorIndex),
CommitMessages = CommitSent
? CommitPayloads.Where(p => p != null).Select(p => RecoveryMessage.CommitPayloadCompact.FromPayload(p)).ToDictionary(p => (int)p.ValidatorIndex)
: new Dictionary<int, RecoveryMessage.CommitPayloadCompact>()
? CommitPayloads.Where(p => p != null).Select(p => GetCommitPayloadCompact(p)).ToDictionary(p => (int)p.ValidatorIndex)
: new Dictionary<int, CommitPayloadCompact>()
});
}

public ConsensusPayload MakePrepareResponse()
public ExtensiblePayload MakePrepareResponse()
{
return PreparationPayloads[MyIndex] = MakeSignedPayload(new PrepareResponse
{
Expand Down Expand Up @@ -401,9 +473,9 @@ public void Reset(byte viewNumber)
}
}
MyIndex = -1;
ChangeViewPayloads = new ConsensusPayload[Validators.Length];
LastChangeViewPayloads = new ConsensusPayload[Validators.Length];
CommitPayloads = new ConsensusPayload[Validators.Length];
ChangeViewPayloads = new ExtensiblePayload[Validators.Length];
LastChangeViewPayloads = new ExtensiblePayload[Validators.Length];
CommitPayloads = new ExtensiblePayload[Validators.Length];
if (ValidatorsChanged || LastSeenMessage is null)
{
var previous_last_seen_message = LastSeenMessage;
Expand All @@ -425,11 +497,12 @@ public void Reset(byte viewNumber)
keyPair = account.GetKey();
break;
}
cachedMessages = new Dictionary<UInt256, ConsensusMessage>();
}
else
{
for (int i = 0; i < LastChangeViewPayloads.Length; i++)
if (ChangeViewPayloads[i]?.GetDeserializedMessage<ChangeView>().NewViewNumber >= viewNumber)
if (GetMessage<ChangeView>(ChangeViewPayloads[i])?.NewViewNumber >= viewNumber)
LastChangeViewPayloads[i] = ChangeViewPayloads[i];
else
LastChangeViewPayloads[i] = null;
Expand All @@ -443,7 +516,7 @@ public void Reset(byte viewNumber)
Block.Timestamp = 0;
Block.Transactions = null;
TransactionHashes = null;
PreparationPayloads = new ConsensusPayload[Validators.Length];
PreparationPayloads = new ExtensiblePayload[Validators.Length];
if (MyIndex >= 0) LastSeenMessage[Validators[MyIndex]] = Block.Index;
}

Expand Down
14 changes: 13 additions & 1 deletion src/neo/Consensus/ConsensusMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,15 @@ namespace Neo.Consensus
public abstract class ConsensusMessage : ISerializable
{
public readonly ConsensusMessageType Type;
public uint BlockIndex;
public byte ValidatorIndex;
public byte ViewNumber;

public virtual int Size => sizeof(ConsensusMessageType) + sizeof(byte);
public virtual int Size =>
sizeof(ConsensusMessageType) + //Type
sizeof(uint) + //BlockIndex
sizeof(byte) + //ValidatorIndex
sizeof(byte); //ViewNumber

protected ConsensusMessage(ConsensusMessageType type)
{
Expand All @@ -23,6 +29,10 @@ public virtual void Deserialize(BinaryReader reader)
{
if (Type != (ConsensusMessageType)reader.ReadByte())
throw new FormatException();
BlockIndex = reader.ReadUInt32();
ValidatorIndex = reader.ReadByte();
if (ValidatorIndex >= ProtocolSettings.Default.ValidatorsCount)
throw new FormatException();
ViewNumber = reader.ReadByte();
}

Expand All @@ -37,6 +47,8 @@ public static ConsensusMessage DeserializeFrom(byte[] data)
public virtual void Serialize(BinaryWriter writer)
{
writer.Write((byte)Type);
writer.Write(BlockIndex);
writer.Write(ValidatorIndex);
writer.Write(ViewNumber);
}
}
Expand Down
Loading