Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all 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
6 changes: 3 additions & 3 deletions node/messages/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ use polkadot_primitives::parachain::{
SignedAvailabilityBitfield, SigningContext, ValidatorId, ValidationCode, ValidatorIndex,
};
use polkadot_node_primitives::{
MisbehaviorReport, SignedStatement,
MisbehaviorReport, SignedFullStatement,
};

/// Signals sent by an overseer to a subsystem.
Expand Down Expand Up @@ -68,7 +68,7 @@ pub enum CandidateBackingMessage {
Second(Hash, AbridgedCandidateReceipt),
/// Note a validator's statement about a particular candidate. Disagreements about validity must be escalated
/// to a broader check by Misbehavior Arbitration. Agreements are simply tallied until a quorum is reached.
Statement(Hash, SignedStatement),
Statement(Hash, SignedFullStatement),
}

/// Blanket error for validation failing.
Expand Down Expand Up @@ -180,7 +180,7 @@ pub enum RuntimeApiMessage {
pub enum StatementDistributionMessage {
/// We have originated a signed statement in the context of
/// given relay-parent hash and it should be distributed to other validators.
Share(Hash, SignedStatement),
Share(Hash, SignedFullStatement),
}

/// This data becomes intrinsics or extrinsics which should be included in a future relay chain block.
Expand Down
70 changes: 21 additions & 49 deletions node/primitives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@
//! not shared between the node and the runtime. This crate builds on top of the primitives defined
//! there.

use runtime_primitives::traits::AppVerify;
use polkadot_primitives::Hash;
use polkadot_primitives::parachain::{
AbridgedCandidateReceipt, CandidateReceipt, SigningContext, ValidatorSignature,
ValidatorIndex, ValidatorId,
use parity_scale_codec::{Decode, Encode};
use polkadot_primitives::{Hash,
parachain::{
AbridgedCandidateReceipt, CandidateReceipt, CompactStatement,
EncodeAs, Signed,
}
};
use parity_scale_codec::{Encode, Decode};

/// A statement, where the candidate receipt is included in the `Seconded` variant.
#[derive(Debug, Clone, PartialEq, Encode, Decode)]
Expand All @@ -42,54 +42,26 @@ pub enum Statement {
Invalid(Hash),
}

impl Statement {
/// Get the signing payload of the statement.
pub fn signing_payload(&self, context: &SigningContext) -> Vec<u8> {
// convert to fully hash-based payload.
impl EncodeAs<CompactStatement> for Statement {
fn encode_as(&self) -> Vec<u8> {
let statement = match *self {
Statement::Seconded(ref c) => polkadot_primitives::parachain::Statement::Candidate(c.hash()),
Statement::Valid(hash) => polkadot_primitives::parachain::Statement::Valid(hash),
Statement::Invalid(hash) => polkadot_primitives::parachain::Statement::Invalid(hash),
Statement::Seconded(ref c) => {
polkadot_primitives::parachain::CompactStatement::Candidate(c.hash())
}
Statement::Valid(hash) => polkadot_primitives::parachain::CompactStatement::Valid(hash),
Statement::Invalid(hash) => polkadot_primitives::parachain::CompactStatement::Invalid(hash),
};

statement.signing_payload(context)
statement.encode()
}
}

/// A statement, the corresponding signature, and the index of the sender.
///
/// Signing context and validator set should be apparent from context.
#[derive(Debug, Clone, PartialEq, Encode, Decode)]
pub struct SignedStatement {
/// The statement signed.
pub statement: Statement,
/// The signature of the validator.
pub signature: ValidatorSignature,
/// The index in the validator set of the signing validator. Which validator set should
/// be apparent from context.
pub sender: ValidatorIndex,
}

impl SignedStatement {
/// Check the signature on a statement. Provide a list of validators to index into
/// and the context in which the statement is presumably signed.
///
/// Returns an error if out of bounds or the signature is invalid. Otherwise, returns Ok.
pub fn check_signature(
&self,
validators: &[ValidatorId],
signing_context: &SigningContext,
) -> Result<(), ()> {
let validator = validators.get(self.sender as usize).ok_or(())?;
let payload = self.statement.signing_payload(signing_context);

if self.signature.verify(&payload[..], validator) {
Ok(())
} else {
Err(())
}
}
}
///
/// This statement is "full" in the sense that the `Seconded` variant includes the candidate receipt.
/// Only the compact `SignedStatement` is suitable for submission to the chain.
pub type SignedFullStatement = Signed<Statement, CompactStatement>;

/// A misbehaviour report.
pub enum MisbehaviorReport {
Expand All @@ -101,9 +73,9 @@ pub enum MisbehaviorReport {
/// this message should be dispatched with all of them, in arbitrary order.
///
/// This variant is also used when our own validity checks disagree with others'.
CandidateValidityDisagreement(CandidateReceipt, Vec<SignedStatement>),
CandidateValidityDisagreement(CandidateReceipt, Vec<SignedFullStatement>),
/// I've noticed a peer contradicting itself about a particular candidate
SelfContradiction(CandidateReceipt, SignedStatement, SignedStatement),
SelfContradiction(CandidateReceipt, SignedFullStatement, SignedFullStatement),
/// This peer has seconded more than one parachain candidate for this relay parent head
DoubleVote(CandidateReceipt, SignedStatement, SignedStatement),
DoubleVote(CandidateReceipt, SignedFullStatement, SignedFullStatement),
}
189 changes: 115 additions & 74 deletions primitives/src/parachain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ use super::{Hash, Balance, BlockNumber};
use serde::{Serialize, Deserialize};

#[cfg(feature = "std")]
use primitives::bytes;
use primitives::{bytes, crypto::Pair};
use primitives::RuntimeDebug;
use runtime_primitives::traits::Block as BlockT;
use runtime_primitives::traits::{AppVerify, Block as BlockT};
use inherents::InherentIdentifier;
use application_crypto::KeyTypeId;

Expand Down Expand Up @@ -245,8 +245,6 @@ fn check_collator_signature<H: AsRef<[u8]>>(
collator: &CollatorId,
signature: &CollatorSignature,
) -> Result<(),()> {
use runtime_primitives::traits::AppVerify;

let payload = collator_signature_payload(relay_parent, parachain_index, pov_block_hash);
if signature.verify(&payload[..], collator) {
Ok(())
Expand Down Expand Up @@ -594,7 +592,7 @@ pub struct Activity(#[cfg_attr(feature = "std", serde(with="bytes"))] pub Vec<u8
/// actual values that are signed.
#[derive(Clone, PartialEq, Eq, Encode, Decode)]
#[cfg_attr(feature = "std", derive(Debug))]
pub enum Statement {
pub enum CompactStatement {
/// Proposal of a parachain candidate.
#[codec(index = "1")]
Candidate(Hash),
Expand All @@ -606,14 +604,8 @@ pub enum Statement {
Invalid(Hash),
}

impl Statement {
/// Produce a payload on this statement that is used for signing.
///
/// It includes the context provided.
pub fn signing_payload(&self, context: &SigningContext) -> Vec<u8> {
(self, context).encode()
}
}
/// A signed compact statement, suitable to be sent to the chain.
pub type SignedStatement = Signed<CompactStatement>;

/// An either implicit or explicit attestation to the validity of a parachain
/// candidate.
Expand Down Expand Up @@ -647,11 +639,11 @@ impl ValidityAttestation {
) -> Vec<u8> {
match *self {
ValidityAttestation::Implicit(_) => (
Statement::Candidate(candidate_hash),
CompactStatement::Candidate(candidate_hash),
signing_context,
).encode(),
ValidityAttestation::Explicit(_) => (
Statement::Valid(candidate_hash),
CompactStatement::Valid(candidate_hash),
signing_context,
).encode(),
}
Expand Down Expand Up @@ -723,64 +715,8 @@ impl From<BitVec<bitvec::order::Lsb0, u8>> for AvailabilityBitfield {
}
}

impl AvailabilityBitfield {
/// Encodes the signing payload into the given buffer.
pub fn encode_signing_payload_into<H: Encode>(
&self,
signing_context: &SigningContext<H>,
buf: &mut Vec<u8>,
) {
self.0.encode_to(buf);
signing_context.encode_to(buf);
}

/// Encodes the signing payload into a fresh byte-vector.
pub fn encode_signing_payload<H: Encode>(
&self,
signing_context: &SigningContext<H>,
) -> Vec<u8> {
let mut v = Vec::new();
self.encode_signing_payload_into(signing_context, &mut v);
v
}
}

/// A bitfield signed by a particular validator about the availability of pending candidates.
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)]
pub struct SignedAvailabilityBitfield {
/// The index of the validator in the current set.
pub validator_index: ValidatorIndex,
/// The bitfield itself, with one bit per core. Only occupied cores may have the `1` bit set.
pub bitfield: AvailabilityBitfield,
/// The signature by the validator on the bitfield's signing payload. The context of the signature
/// should be apparent when checking the signature.
pub signature: ValidatorSignature,
}

/// Check a signature on an availability bitfield. Provide the bitfield, the validator who signed it,
/// the signature, the signing context, and an optional buffer in which to encode.
///
/// If the buffer is provided, it is assumed to be empty.
pub fn check_availability_bitfield_signature<H: Encode>(
bitfield: &AvailabilityBitfield,
validator: &ValidatorId,
signature: &ValidatorSignature,
signing_context: &SigningContext<H>,
payload_encode_buf: Option<&mut Vec<u8>>,
) -> Result<(),()> {
use runtime_primitives::traits::AppVerify;

let mut v = Vec::new();
let payload_encode_buf = payload_encode_buf.unwrap_or(&mut v);

bitfield.encode_signing_payload_into(signing_context, payload_encode_buf);

if signature.verify(&payload_encode_buf[..], validator) {
Ok(())
} else {
Err(())
}
}
pub type SignedAvailabilityBitfield = Signed<AvailabilityBitfield>;

/// A set of signed availability bitfields. Should be sorted by validator index, ascending.
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)]
Expand Down Expand Up @@ -816,8 +752,6 @@ pub fn check_candidate_backing<H: AsRef<[u8]> + Encode>(
group_len: usize,
validator_lookup: impl Fn(usize) -> Option<ValidatorId>,
) -> Result<usize, ()> {
use runtime_primitives::traits::AppVerify;

if backed.validator_indices.len() != group_len {
return Err(())
}
Expand Down Expand Up @@ -885,6 +819,113 @@ pub mod id {
pub const PARACHAIN_HOST: ApiId = *b"parahost";
}

/// This helper trait ensures that we can encode Statement as CompactStatement,
/// and anything as itself.
///
/// This resembles `parity_scale_codec::EncodeLike`, but it's distinct:
/// EncodeLike is a marker trait which asserts at the typesystem level that
/// one type's encoding is a valid encoding for another type. It doesn't
/// perform any type conversion when encoding.
///
/// This trait, on the other hand, provides a method which can be used to
/// simultaneously convert and encode one type as another.
pub trait EncodeAs<T> {
/// Convert Self into T, then encode T.
///
/// This is useful when T is a subset of Self, reducing encoding costs;
/// its signature also means that we do not need to clone Self in order
/// to retain ownership, as we would if we were to do
/// `self.clone().into().encode()`.
fn encode_as(&self) -> Vec<u8>;
}

impl<T: Encode> EncodeAs<T> for T {
fn encode_as(&self) -> Vec<u8> {
self.encode()
}
}

/// A signed type which encapsulates the common desire to sign some data and validate a signature.
///
/// Note that the internal fields are not public; they are all accessable by immutable getters.
/// This reduces the chance that they are accidentally mutated, invalidating the signature.
#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)]
pub struct Signed<Payload, RealPayload = Payload> {
/// The payload is part of the signed data. The rest is the signing context,
/// which is known both at signing and at validation.
payload: Payload,
/// The index of the validator signing this statement.
validator_index: ValidatorIndex,
/// The signature by the validator of the signed payload.
signature: ValidatorSignature,
/// This ensures the real payload is tracked at the typesystem level.
real_payload: sp_std::marker::PhantomData<RealPayload>,
}

// We can't bound this on `Payload: Into<RealPayload>` beacuse that conversion consumes
// the payload, and we don't want that. We can't bound it on `Payload: AsRef<RealPayload>`
// because there's no blanket impl of `AsRef<T> for T`. In the end, we just invent our
// own trait which does what we need: EncodeAs.
impl<Payload: EncodeAs<RealPayload>, RealPayload: Encode> Signed<Payload, RealPayload> {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It seems like the EncodeAs machinery could be replaced with a simple From bound, but actually not, because you'd have to do some cloning internally for types like AvailabilityBitfield.

Playground to avoid this: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=f734b13e93d6a7baa8b19dab78df345f

But it gets a bit messy

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You have to use the trait because you would need HKT to do this with pure closures.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Exactly right on the reason we're using EncodeAs instead of From/Into.

I think we may have been overthinking the solution, though; my new attempt is in 234eca5.

fn payload_data<H: Encode>(payload: &Payload, context: &SigningContext<H>) -> Vec<u8> {
// equivalent to (real_payload, context).encode()
let mut out = payload.encode_as();
out.extend(context.encode());
out
}

/// Sign this payload with the given context and key, storing the validator index.
#[cfg(feature = "std")]
pub fn sign<H: Encode>(
payload: Payload,
context: &SigningContext<H>,
validator_index: ValidatorIndex,
key: &ValidatorPair,
) -> Self {
let data = Self::payload_data(&payload, context);
let signature = key.sign(&data);
Self {
payload,
validator_index,
signature,
real_payload: std::marker::PhantomData,
}
}

/// Validate the payload given the context and public key.
pub fn check_signature<H: Encode>(&self, context: &SigningContext<H>, key: &ValidatorId) -> Result<(), ()> {
let data = Self::payload_data(&self.payload, context);
if self.signature.verify(data.as_slice(), key) { Ok(()) } else { Err(()) }
}

/// Immutably access the payload.
#[inline]
pub fn payload(&self) -> &Payload {
&self.payload
}

/// Immutably access the validator index.
#[inline]
pub fn validator_index(&self) -> ValidatorIndex {
self.validator_index
}

/// Immutably access the signature.
#[inline]
pub fn signature(&self) -> &ValidatorSignature {
&self.signature
}

/// Discard signing data, get the payload
// Note: can't `impl<P, R> From<Signed<P, R>> for P` because the orphan rule exception doesn't
// handle this case yet. Likewise can't `impl<P, R> Into<P> for Signed<P, R>` because it might
// potentially conflict with the global blanket impl, even though it currently doesn't.
#[inline]
pub fn into_payload(self) -> Payload {
self.payload
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@ The Statement Distribution subsystem sends statements to peer nodes and detects

## Peer Receipt State Machine

There is a very simple state machine which governs which messages we are willing to receive from peers. Not depicted in the state machine: on initial receipt of any [`SignedStatement`](../../types/backing.md#signed-statement-type), validate that the provided signature does in fact sign the included data. Note that each individual parablock candidate gets its own instance of this state machine; it is perfectly legal to receive a `Valid(X)` before a `Seconded(Y)`, as long as a `Seconded(X)` has been received.
There is a very simple state machine which governs which messages we are willing to receive from peers. Not depicted in the state machine: on initial receipt of any [`SignedFullStatement`](../../types/backing.md#signed-statement-type), validate that the provided signature does in fact sign the included data. Note that each individual parablock candidate gets its own instance of this state machine; it is perfectly legal to receive a `Valid(X)` before a `Seconded(Y)`, as long as a `Seconded(X)` has been received.

A: Initial State. Receive `SignedStatement(Statement::Second)`: extract `Statement`, forward to Candidate Backing, proceed to B. Receive any other `SignedStatement` variant: drop it.
A: Initial State. Receive `SignedFullStatement(Statement::Second)`: extract `Statement`, forward to Candidate Backing, proceed to B. Receive any other `SignedFullStatement` variant: drop it.

B: Receive any `SignedStatement`: check signature, forward to Candidate Backing. Receive `OverseerMessage::StopWork`: proceed to C.
B: Receive any `SignedFullStatement`: check signature, forward to Candidate Backing. Receive `OverseerMessage::StopWork`: proceed to C.

C: Receive any message for this block: drop it.

Expand Down
Loading