From e2468dba07fb1d4360ad04c815d99b858f3a2f7c Mon Sep 17 00:00:00 2001 From: Roberto Saltini <38655434+saltiniroberto@users.noreply.github.com> Date: Thu, 25 Oct 2018 17:41:31 +1100 Subject: [PATCH] [EC-183] Add iBFT Prepare and Round Change RLP encoding and decoding (#84) --- .../pantheon/consensus/ibft/IbftMessages.java | 43 +++++++ .../ibft/ibftmessage/AbstractIbftMessage.java | 40 +++++++ .../ibftmessage/IbftPrePrepareMessage.java | 64 +++++++++++ .../ibft/ibftmessage/IbftPrepareMessage.java | 64 +++++++++++ .../ibftmessage/IbftRoundChangeMessage.java | 64 +++++++++++ .../consensus/ibft/ibftmessage/IbftV2.java | 23 ++++ ...bstractIbftUnsignedInRoundMessageData.java | 28 +++++ .../AbstractIbftUnsignedMessageData.java | 37 ++++++ .../ibftmessagedata/IbftMessageFactory.java | 72 ++++++++++++ .../IbftPreparedCertificate.java | 53 +++++++++ .../IbftSignedMessageData.java | 108 ++++++++++++++++++ .../IbftUnsignedPrePrepareMessageData.java | 44 +++++++ .../IbftUnsignedPrepareMessageData.java | 59 ++++++++++ .../IbftUnsignedRoundChangeMessageData.java | 84 ++++++++++++++ .../ibft/protocol/IbftSubProtocol.java | 49 ++------ 15 files changed, 794 insertions(+), 38 deletions(-) create mode 100644 consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftMessages.java create mode 100644 consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessage/AbstractIbftMessage.java create mode 100644 consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessage/IbftPrePrepareMessage.java create mode 100644 consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessage/IbftPrepareMessage.java create mode 100644 consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessage/IbftRoundChangeMessage.java create mode 100644 consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessage/IbftV2.java create mode 100644 consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessagedata/AbstractIbftUnsignedInRoundMessageData.java create mode 100644 consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessagedata/AbstractIbftUnsignedMessageData.java create mode 100644 consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessagedata/IbftMessageFactory.java create mode 100644 consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessagedata/IbftPreparedCertificate.java create mode 100644 consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessagedata/IbftSignedMessageData.java create mode 100644 consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessagedata/IbftUnsignedPrePrepareMessageData.java create mode 100644 consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessagedata/IbftUnsignedPrepareMessageData.java create mode 100644 consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessagedata/IbftUnsignedRoundChangeMessageData.java diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftMessages.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftMessages.java new file mode 100644 index 0000000000..8ae7b285df --- /dev/null +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/IbftMessages.java @@ -0,0 +1,43 @@ +/* + * Copyright 2018 ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package tech.pegasys.pantheon.consensus.ibft; + +import tech.pegasys.pantheon.consensus.ibft.ibftmessage.IbftPrePrepareMessage; +import tech.pegasys.pantheon.consensus.ibft.ibftmessage.IbftPrepareMessage; +import tech.pegasys.pantheon.consensus.ibft.ibftmessage.IbftRoundChangeMessage; +import tech.pegasys.pantheon.consensus.ibft.ibftmessage.IbftV2; +import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.IbftSignedMessageData; +import tech.pegasys.pantheon.ethereum.p2p.api.Message; +import tech.pegasys.pantheon.ethereum.p2p.api.MessageData; + +public class IbftMessages { + + public static IbftSignedMessageData fromMessage(final Message message) { + final MessageData messageData = message.getData(); + + switch (messageData.getCode()) { + case IbftV2.PRE_PREPARE: + return IbftPrePrepareMessage.fromMessage(messageData).decode(); + + case IbftV2.PREPARE: + return IbftPrepareMessage.fromMessage(messageData).decode(); + + case IbftV2.ROUND_CHANGE: + return IbftRoundChangeMessage.fromMessage(messageData).decode(); + + default: + throw new IllegalArgumentException( + "Received message does not conform to any recognised IBFT message structure."); + } + } +} diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessage/AbstractIbftMessage.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessage/AbstractIbftMessage.java new file mode 100644 index 0000000000..7095862695 --- /dev/null +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessage/AbstractIbftMessage.java @@ -0,0 +1,40 @@ +/* + * Copyright 2018 ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package tech.pegasys.pantheon.consensus.ibft.ibftmessage; + +import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.IbftSignedMessageData; +import tech.pegasys.pantheon.ethereum.p2p.NetworkMemoryPool; +import tech.pegasys.pantheon.ethereum.p2p.wire.AbstractMessageData; +import tech.pegasys.pantheon.ethereum.rlp.BytesValueRLPOutput; + +import io.netty.buffer.ByteBuf; + +public abstract class AbstractIbftMessage extends AbstractMessageData { + protected AbstractIbftMessage(final ByteBuf data) { + super(data); + } + + public abstract IbftSignedMessageData decode(); + + protected static ByteBuf writeMessageToByteBuf( + final IbftSignedMessageData ibftSignedMessageData) { + + BytesValueRLPOutput rlpEncode = new BytesValueRLPOutput(); + ibftSignedMessageData.writeTo(rlpEncode); + + final ByteBuf data = NetworkMemoryPool.allocate(rlpEncode.encodedSize()); + data.writeBytes(rlpEncode.encoded().extractArray()); + + return data; + } +} diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessage/IbftPrePrepareMessage.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessage/IbftPrePrepareMessage.java new file mode 100644 index 0000000000..70f8fbad3d --- /dev/null +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessage/IbftPrePrepareMessage.java @@ -0,0 +1,64 @@ +/* + * Copyright 2018 ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package tech.pegasys.pantheon.consensus.ibft.ibftmessage; + +import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.IbftSignedMessageData; +import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.IbftUnsignedPrePrepareMessageData; +import tech.pegasys.pantheon.ethereum.p2p.NetworkMemoryPool; +import tech.pegasys.pantheon.ethereum.p2p.api.MessageData; +import tech.pegasys.pantheon.ethereum.rlp.RLP; +import tech.pegasys.pantheon.util.bytes.BytesValue; + +import io.netty.buffer.ByteBuf; + +public class IbftPrePrepareMessage extends AbstractIbftMessage { + + private static final int MESSAGE_CODE = IbftV2.PRE_PREPARE; + + private IbftPrePrepareMessage(final ByteBuf data) { + super(data); + } + + public static IbftPrePrepareMessage fromMessage(final MessageData message) { + if (message instanceof IbftPrePrepareMessage) { + message.retain(); + return (IbftPrePrepareMessage) message; + } + final int code = message.getCode(); + if (code != MESSAGE_CODE) { + throw new IllegalArgumentException( + String.format("Message has code %d and thus is not a PrePrepareMessage", code)); + } + + final ByteBuf data = NetworkMemoryPool.allocate(message.getSize()); + message.writeTo(data); + return new IbftPrePrepareMessage(data); + } + + @Override + public IbftSignedMessageData decode() { + return IbftSignedMessageData.readIbftSignedPrePrepareMessageDataFrom( + RLP.input(BytesValue.wrapBuffer(data))); + } + + public static IbftPrePrepareMessage create( + final IbftSignedMessageData ibftPrepareMessageDecoded) { + + return new IbftPrePrepareMessage(writeMessageToByteBuf(ibftPrepareMessageDecoded)); + } + + @Override + public int getCode() { + return MESSAGE_CODE; + } +} diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessage/IbftPrepareMessage.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessage/IbftPrepareMessage.java new file mode 100644 index 0000000000..b1d77b3556 --- /dev/null +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessage/IbftPrepareMessage.java @@ -0,0 +1,64 @@ +/* + * Copyright 2018 ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package tech.pegasys.pantheon.consensus.ibft.ibftmessage; + +import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.IbftSignedMessageData; +import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.IbftUnsignedPrepareMessageData; +import tech.pegasys.pantheon.ethereum.p2p.NetworkMemoryPool; +import tech.pegasys.pantheon.ethereum.p2p.api.MessageData; +import tech.pegasys.pantheon.ethereum.rlp.RLP; +import tech.pegasys.pantheon.util.bytes.BytesValue; + +import io.netty.buffer.ByteBuf; + +public class IbftPrepareMessage extends AbstractIbftMessage { + + private static final int MESSAGE_CODE = IbftV2.PREPARE; + + private IbftPrepareMessage(final ByteBuf data) { + super(data); + } + + public static IbftPrepareMessage fromMessage(final MessageData message) { + if (message instanceof IbftPrepareMessage) { + message.retain(); + return (IbftPrepareMessage) message; + } + final int code = message.getCode(); + if (code != MESSAGE_CODE) { + throw new IllegalArgumentException( + String.format("Message has code %d and thus is not a PrepareMessage", code)); + } + + final ByteBuf data = NetworkMemoryPool.allocate(message.getSize()); + message.writeTo(data); + return new IbftPrepareMessage(data); + } + + @Override + public IbftSignedMessageData decode() { + return IbftSignedMessageData.readIbftSignedPrepareMessageDataFrom( + RLP.input(BytesValue.wrapBuffer(data))); + } + + public static IbftPrepareMessage create( + final IbftSignedMessageData ibftPrepareMessageDecoded) { + + return new IbftPrepareMessage(writeMessageToByteBuf(ibftPrepareMessageDecoded)); + } + + @Override + public int getCode() { + return MESSAGE_CODE; + } +} diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessage/IbftRoundChangeMessage.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessage/IbftRoundChangeMessage.java new file mode 100644 index 0000000000..bfdd00e495 --- /dev/null +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessage/IbftRoundChangeMessage.java @@ -0,0 +1,64 @@ +/* + * Copyright 2018 ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package tech.pegasys.pantheon.consensus.ibft.ibftmessage; + +import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.IbftSignedMessageData; +import tech.pegasys.pantheon.consensus.ibft.ibftmessagedata.IbftUnsignedRoundChangeMessageData; +import tech.pegasys.pantheon.ethereum.p2p.NetworkMemoryPool; +import tech.pegasys.pantheon.ethereum.p2p.api.MessageData; +import tech.pegasys.pantheon.ethereum.rlp.RLP; +import tech.pegasys.pantheon.util.bytes.BytesValue; + +import io.netty.buffer.ByteBuf; + +public class IbftRoundChangeMessage extends AbstractIbftMessage { + + private static final int MESSAGE_CODE = IbftV2.ROUND_CHANGE; + + private IbftRoundChangeMessage(final ByteBuf data) { + super(data); + } + + public static IbftRoundChangeMessage fromMessage(final MessageData message) { + if (message instanceof IbftRoundChangeMessage) { + message.retain(); + return (IbftRoundChangeMessage) message; + } + final int code = message.getCode(); + if (code != MESSAGE_CODE) { + throw new IllegalArgumentException( + String.format("Message has code %d and thus is not a RoundChangeMessage", code)); + } + + final ByteBuf data = NetworkMemoryPool.allocate(message.getSize()); + message.writeTo(data); + return new IbftRoundChangeMessage(data); + } + + @Override + public IbftSignedMessageData decode() { + return IbftSignedMessageData.readIbftSignedRoundChangeMessageDataFrom( + RLP.input(BytesValue.wrapBuffer(data))); + } + + public static IbftRoundChangeMessage create( + final IbftSignedMessageData ibftPrepareMessageDecoded) { + + return new IbftRoundChangeMessage(writeMessageToByteBuf(ibftPrepareMessageDecoded)); + } + + @Override + public int getCode() { + return MESSAGE_CODE; + } +} diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessage/IbftV2.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessage/IbftV2.java new file mode 100644 index 0000000000..3f7e81647e --- /dev/null +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessage/IbftV2.java @@ -0,0 +1,23 @@ +/* + * Copyright 2018 ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package tech.pegasys.pantheon.consensus.ibft.ibftmessage; + +/** Message codes for iBFT v2 messages */ +public class IbftV2 { + public static final int PRE_PREPARE = 0; + public static final int PREPARE = 1; + public static final int COMMIT = 2; + public static final int ROUND_CHANGE = 3; + + public static final int MESSAGE_SPACE = 4; +} diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessagedata/AbstractIbftUnsignedInRoundMessageData.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessagedata/AbstractIbftUnsignedInRoundMessageData.java new file mode 100644 index 0000000000..de4a8f8007 --- /dev/null +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessagedata/AbstractIbftUnsignedInRoundMessageData.java @@ -0,0 +1,28 @@ +/* + * Copyright 2018 ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package tech.pegasys.pantheon.consensus.ibft.ibftmessagedata; + +import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier; + +public abstract class AbstractIbftUnsignedInRoundMessageData + extends AbstractIbftUnsignedMessageData { + protected final ConsensusRoundIdentifier roundIdentifier; + + protected AbstractIbftUnsignedInRoundMessageData(final ConsensusRoundIdentifier roundIdentifier) { + this.roundIdentifier = roundIdentifier; + } + + public ConsensusRoundIdentifier getRoundIdentifier() { + return roundIdentifier; + } +} diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessagedata/AbstractIbftUnsignedMessageData.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessagedata/AbstractIbftUnsignedMessageData.java new file mode 100644 index 0000000000..d178643bfd --- /dev/null +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessagedata/AbstractIbftUnsignedMessageData.java @@ -0,0 +1,37 @@ +/* + * Copyright 2018 ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package tech.pegasys.pantheon.consensus.ibft.ibftmessagedata; + +import tech.pegasys.pantheon.ethereum.core.Hash; +import tech.pegasys.pantheon.ethereum.rlp.BytesValueRLPOutput; +import tech.pegasys.pantheon.ethereum.rlp.RLPInput; +import tech.pegasys.pantheon.ethereum.rlp.RLPOutput; +import tech.pegasys.pantheon.util.bytes.BytesValue; + +public abstract class AbstractIbftUnsignedMessageData { + + public abstract void writeTo(final RLPOutput rlpOutput); + + public BytesValue encoded() { + BytesValueRLPOutput rlpOutput = new BytesValueRLPOutput(); + writeTo(rlpOutput); + + return rlpOutput.encoded(); + } + + public abstract int getMessageType(); + + protected static Hash readDigest(final RLPInput ibftMessageData) { + return Hash.wrap(ibftMessageData.readBytes32()); + } +} diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessagedata/IbftMessageFactory.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessagedata/IbftMessageFactory.java new file mode 100644 index 0000000000..451368874a --- /dev/null +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessagedata/IbftMessageFactory.java @@ -0,0 +1,72 @@ +/* + * Copyright 2018 ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package tech.pegasys.pantheon.consensus.ibft.ibftmessagedata; + +import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier; +import tech.pegasys.pantheon.crypto.SECP256K1; +import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair; +import tech.pegasys.pantheon.crypto.SECP256K1.Signature; +import tech.pegasys.pantheon.ethereum.core.Hash; +import tech.pegasys.pantheon.ethereum.core.Util; +import tech.pegasys.pantheon.util.bytes.BytesValues; + +import java.util.Optional; + +public class IbftMessageFactory { + private final KeyPair validatorKeyPair; + + public IbftMessageFactory(final KeyPair validatorKeyPair) { + this.validatorKeyPair = validatorKeyPair; + } + + public IbftSignedMessageData createIbftSignedPrepareMessageData( + final ConsensusRoundIdentifier roundIdentifier, final Hash digest) { + + IbftUnsignedPrepareMessageData prepareUnsignedMessageData = + new IbftUnsignedPrepareMessageData(roundIdentifier, digest); + + return createSignedMessage(prepareUnsignedMessageData); + } + + public IbftSignedMessageData + createIbftSignedRoundChangeMessageData( + final ConsensusRoundIdentifier roundIdentifier, + final Optional preparedCertificate) { + + IbftUnsignedRoundChangeMessageData prepareUnsignedMessageData = + new IbftUnsignedRoundChangeMessageData(roundIdentifier, preparedCertificate); + + return createSignedMessage(prepareUnsignedMessageData); + } + + private IbftSignedMessageData createSignedMessage( + final M ibftUnsignedMessage) { + final Signature signature = sign(ibftUnsignedMessage, validatorKeyPair); + + return new IbftSignedMessageData<>( + ibftUnsignedMessage, Util.publicKeyToAddress(validatorKeyPair.getPublicKey()), signature); + } + + static Hash hashForSignature(final AbstractIbftUnsignedMessageData unsignedMessageData) { + return Hash.hash( + BytesValues.concatenate( + BytesValues.ofUnsignedByte(unsignedMessageData.getMessageType()), + unsignedMessageData.encoded())); + } + + private static Signature sign( + final AbstractIbftUnsignedMessageData unsignedMessageData, final KeyPair nodeKeys) { + + return SECP256K1.sign(hashForSignature(unsignedMessageData), nodeKeys); + } +} diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessagedata/IbftPreparedCertificate.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessagedata/IbftPreparedCertificate.java new file mode 100644 index 0000000000..56270b4554 --- /dev/null +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessagedata/IbftPreparedCertificate.java @@ -0,0 +1,53 @@ +/* + * Copyright 2018 ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package tech.pegasys.pantheon.consensus.ibft.ibftmessagedata; + +import tech.pegasys.pantheon.ethereum.rlp.RLPInput; +import tech.pegasys.pantheon.ethereum.rlp.RLPOutput; + +import java.util.Collection; + +public class IbftPreparedCertificate { + + private final IbftSignedMessageData ibftPrePrepareMessage; + private final Collection> + ibftPrepareMessages; + + public IbftPreparedCertificate( + final IbftSignedMessageData ibftPrePrepareMessage, + final Collection> ibftPrepareMessages) { + this.ibftPrePrepareMessage = ibftPrePrepareMessage; + this.ibftPrepareMessages = ibftPrepareMessages; + } + + public static IbftPreparedCertificate readFrom(final RLPInput rlpInput) { + final IbftSignedMessageData ibftPrePreparedMessage; + final Collection> ibftPrepareMessages; + + rlpInput.enterList(); + ibftPrePreparedMessage = + IbftSignedMessageData.readIbftSignedPrePrepareMessageDataFrom(rlpInput); + ibftPrepareMessages = + rlpInput.readList(IbftSignedMessageData::readIbftSignedPrepareMessageDataFrom); + rlpInput.leaveList(); + + return new IbftPreparedCertificate(ibftPrePreparedMessage, ibftPrepareMessages); + } + + public void writeTo(final RLPOutput rlpOutput) { + rlpOutput.startList(); + ibftPrePrepareMessage.writeTo(rlpOutput); + rlpOutput.writeList(ibftPrepareMessages, IbftSignedMessageData::writeTo); + rlpOutput.endList(); + } +} diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessagedata/IbftSignedMessageData.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessagedata/IbftSignedMessageData.java new file mode 100644 index 0000000000..1cfec1e0b6 --- /dev/null +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessagedata/IbftSignedMessageData.java @@ -0,0 +1,108 @@ +/* + * Copyright 2018 ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package tech.pegasys.pantheon.consensus.ibft.ibftmessagedata; + +import tech.pegasys.pantheon.crypto.SECP256K1.Signature; +import tech.pegasys.pantheon.ethereum.core.Address; +import tech.pegasys.pantheon.ethereum.core.Util; +import tech.pegasys.pantheon.ethereum.rlp.RLPInput; +import tech.pegasys.pantheon.ethereum.rlp.RLPOutput; + +public class IbftSignedMessageData { + + protected final Address sender; + protected final Signature signature; + protected final M ibftUnsignedMessageData; + + public IbftSignedMessageData( + final M ibftUnsignedMessageData, final Address sender, final Signature signature) { + this.ibftUnsignedMessageData = ibftUnsignedMessageData; + this.sender = sender; + this.signature = signature; + } + + public Address getSender() { + return sender; + } + + public Signature getSignature() { + return signature; + } + + public M getUnsignedMessageData() { + return ibftUnsignedMessageData; + } + + public void writeTo(final RLPOutput output) { + + output.startList(); + ibftUnsignedMessageData.writeTo(output); + output.writeBytesValue(getSignature().encodedBytes()); + output.endList(); + } + + public static IbftSignedMessageData + readIbftSignedPrePrepareMessageDataFrom(final RLPInput rlpInput) { + + rlpInput.enterList(); + final IbftUnsignedPrePrepareMessageData unsignedMessageData = + IbftUnsignedPrePrepareMessageData.readFrom(rlpInput); + final Signature signature = readSignature(rlpInput); + rlpInput.leaveList(); + + return from(unsignedMessageData, signature); + } + + public static IbftSignedMessageData + readIbftSignedPrepareMessageDataFrom(final RLPInput rlpInput) { + + rlpInput.enterList(); + final IbftUnsignedPrepareMessageData unsignedMessageData = + IbftUnsignedPrepareMessageData.readFrom(rlpInput); + final Signature signature = readSignature(rlpInput); + rlpInput.leaveList(); + + return from(unsignedMessageData, signature); + } + + public static IbftSignedMessageData + readIbftSignedRoundChangeMessageDataFrom(final RLPInput rlpInput) { + + rlpInput.enterList(); + final IbftUnsignedRoundChangeMessageData unsignedMessageData = + IbftUnsignedRoundChangeMessageData.readFrom(rlpInput); + final Signature signature = readSignature(rlpInput); + rlpInput.leaveList(); + + return from(unsignedMessageData, signature); + } + + protected static IbftSignedMessageData from( + final M unsignedMessageData, final Signature signature) { + + final Address sender = recoverSender(unsignedMessageData, signature); + + return new IbftSignedMessageData<>(unsignedMessageData, sender, signature); + } + + protected static Signature readSignature(final RLPInput signedMessage) { + return signedMessage.readBytesValue(Signature::decode); + } + + protected static Address recoverSender( + final AbstractIbftUnsignedMessageData unsignedMessageData, final Signature signature) { + + return Util.signatureToAddress( + signature, IbftMessageFactory.hashForSignature(unsignedMessageData)); + } +} diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessagedata/IbftUnsignedPrePrepareMessageData.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessagedata/IbftUnsignedPrePrepareMessageData.java new file mode 100644 index 0000000000..ad43c6ae69 --- /dev/null +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessagedata/IbftUnsignedPrePrepareMessageData.java @@ -0,0 +1,44 @@ +/* + * Copyright 2018 ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package tech.pegasys.pantheon.consensus.ibft.ibftmessagedata; + +import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier; +import tech.pegasys.pantheon.ethereum.core.Block; +import tech.pegasys.pantheon.ethereum.rlp.RLPInput; +import tech.pegasys.pantheon.ethereum.rlp.RLPOutput; + +// NOTE: Implementation of all methods of this class is still pending. This class was added to show +// how a PreparedCertificate is encoded and decoded inside a RoundChange message +public class IbftUnsignedPrePrepareMessageData extends AbstractIbftUnsignedInRoundMessageData { + + public IbftUnsignedPrePrepareMessageData( + final ConsensusRoundIdentifier roundIdentifier, final Block block) { + super(roundIdentifier); + } + + public Block getBlock() { + return null; + } + + @Override + public void writeTo(final RLPOutput rlpOutput) {} + + @Override + public int getMessageType() { + return 0; + } + + public static IbftUnsignedPrePrepareMessageData readFrom(final RLPInput rlpInput) { + return null; + } +} diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessagedata/IbftUnsignedPrepareMessageData.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessagedata/IbftUnsignedPrepareMessageData.java new file mode 100644 index 0000000000..ef32262f4e --- /dev/null +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessagedata/IbftUnsignedPrepareMessageData.java @@ -0,0 +1,59 @@ +/* + * Copyright 2018 ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package tech.pegasys.pantheon.consensus.ibft.ibftmessagedata; + +import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier; +import tech.pegasys.pantheon.consensus.ibft.ibftmessage.IbftV2; +import tech.pegasys.pantheon.ethereum.core.Hash; +import tech.pegasys.pantheon.ethereum.rlp.RLPInput; +import tech.pegasys.pantheon.ethereum.rlp.RLPOutput; + +public class IbftUnsignedPrepareMessageData extends AbstractIbftUnsignedInRoundMessageData { + private static final int TYPE = IbftV2.PREPARE; + private final Hash digest; + + /** Constructor used when a validator wants to send a message */ + public IbftUnsignedPrepareMessageData( + final ConsensusRoundIdentifier roundIdentifier, final Hash digest) { + super(roundIdentifier); + this.digest = digest; + } + + public static IbftUnsignedPrepareMessageData readFrom(final RLPInput rlpInput) { + + rlpInput.enterList(); + final ConsensusRoundIdentifier roundIdentifier = ConsensusRoundIdentifier.readFrom(rlpInput); + final Hash digest = readDigest(rlpInput); + rlpInput.leaveList(); + + return new IbftUnsignedPrepareMessageData(roundIdentifier, digest); + } + + @Override + public void writeTo(final RLPOutput rlpOutput) { + + rlpOutput.startList(); + roundIdentifier.writeTo(rlpOutput); + rlpOutput.writeBytesValue(digest); + rlpOutput.endList(); + } + + @Override + public int getMessageType() { + return TYPE; + } + + public Hash getDigest() { + return digest; + } +} diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessagedata/IbftUnsignedRoundChangeMessageData.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessagedata/IbftUnsignedRoundChangeMessageData.java new file mode 100644 index 0000000000..d132dc83e0 --- /dev/null +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/ibftmessagedata/IbftUnsignedRoundChangeMessageData.java @@ -0,0 +1,84 @@ +/* + * Copyright 2018 ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package tech.pegasys.pantheon.consensus.ibft.ibftmessagedata; + +import tech.pegasys.pantheon.consensus.ibft.ConsensusRoundIdentifier; +import tech.pegasys.pantheon.consensus.ibft.ibftmessage.IbftV2; +import tech.pegasys.pantheon.ethereum.rlp.BytesValueRLPOutput; +import tech.pegasys.pantheon.ethereum.rlp.RLPInput; +import tech.pegasys.pantheon.ethereum.rlp.RLPOutput; + +import java.util.Optional; + +public class IbftUnsignedRoundChangeMessageData extends AbstractIbftUnsignedMessageData { + + private static final int TYPE = IbftV2.PREPARE; + + private final ConsensusRoundIdentifier roundChangeIdentifier; + + // The validator may not hae any prepared certificate + private final Optional preparedCertificate; + + /** Constructor used only by the {@link #readFrom(RLPInput)} method */ + public IbftUnsignedRoundChangeMessageData( + final ConsensusRoundIdentifier roundIdentifier, + final Optional preparedCertificate) { + this.roundChangeIdentifier = roundIdentifier; + this.preparedCertificate = preparedCertificate; + } + + public ConsensusRoundIdentifier getRoundChangeIdentifier() { + return roundChangeIdentifier; + } + + public Optional getPreparedCertificate() { + return preparedCertificate; + } + + @Override + public void writeTo(final RLPOutput rlpOutput) { + // RLP encode of the message data content (round identifier and prepared certificate) + BytesValueRLPOutput ibftMessage = new BytesValueRLPOutput(); + ibftMessage.startList(); + roundChangeIdentifier.writeTo(ibftMessage); + + if (preparedCertificate.isPresent()) { + preparedCertificate.get().writeTo(ibftMessage); + } else { + ibftMessage.writeNull(); + } + ibftMessage.endList(); + } + + public static IbftUnsignedRoundChangeMessageData readFrom(final RLPInput rlpInput) { + rlpInput.enterList(); + final ConsensusRoundIdentifier roundIdentifier = ConsensusRoundIdentifier.readFrom(rlpInput); + + final Optional preparedCertificate; + + if (rlpInput.nextIsNull()) { + rlpInput.skipNext(); + preparedCertificate = Optional.empty(); + } else { + preparedCertificate = Optional.of(IbftPreparedCertificate.readFrom(rlpInput)); + } + rlpInput.leaveList(); + + return new IbftUnsignedRoundChangeMessageData(roundIdentifier, preparedCertificate); + } + + @Override + public int getMessageType() { + return TYPE; + } +} diff --git a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/protocol/IbftSubProtocol.java b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/protocol/IbftSubProtocol.java index d4d1f96620..a4a25ab448 100644 --- a/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/protocol/IbftSubProtocol.java +++ b/consensus/ibft/src/main/java/tech/pegasys/pantheon/consensus/ibft/protocol/IbftSubProtocol.java @@ -12,16 +12,10 @@ */ package tech.pegasys.pantheon.consensus.ibft.protocol; +import tech.pegasys.pantheon.consensus.ibft.ibftmessage.IbftV2; import tech.pegasys.pantheon.ethereum.p2p.wire.Capability; import tech.pegasys.pantheon.ethereum.p2p.wire.SubProtocol; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Optional; -import java.util.stream.Stream; - public class IbftSubProtocol implements SubProtocol { public static String NAME = "IBF"; @@ -40,41 +34,20 @@ public class IbftSubProtocol implements SubProtocol { @Override public int messageSpace(final int protocolVersion) { - return NotificationType.getMax() + 1; + return IbftV2.MESSAGE_SPACE; } @Override public boolean isValidMessageCode(final int protocolVersion, final int code) { - return NotificationType.fromValue(code).isPresent(); - } - - public enum NotificationType { - PREPREPARE(0), - PREPARE(1), - COMMIT(2), - ROUND_CHANGE(3); - - private final int value; - - NotificationType(final int value) { - this.value = value; - } - - public final int getValue() { - return value; - } - - public static final int getMax() { - return Collections.max( - Arrays.asList(NotificationType.values()), - Comparator.comparing(NotificationType::getValue)) - .getValue(); - } - - public static final Optional fromValue(final int i) { - final List notifications = Arrays.asList(NotificationType.values()); - - return Stream.of(NotificationType.values()).filter(n -> n.getValue() == i).findFirst(); + switch (code) { + case IbftV2.PRE_PREPARE: + case IbftV2.PREPARE: + case IbftV2.COMMIT: + case IbftV2.ROUND_CHANGE: + return true; + + default: + return false; } } }