Skip to content

Commit

Permalink
Merge pull request AleoNet#2521 from ProvableHQ/fix/transmission-chec…
Browse files Browse the repository at this point in the history
…ksum

[Fix] Add checksum to `TransmissionID`
  • Loading branch information
zosorock authored Jul 29, 2024
2 parents ef547d9 + a6d175a commit 68f4f31
Show file tree
Hide file tree
Showing 22 changed files with 339 additions and 67 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions console/network/src/canary_v0.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ impl Network for CanaryV0 {
type TransactionID = AleoID<Field<Self>, { hrp2!(TRANSACTION_PREFIX) }>;
/// The transition ID type.
type TransitionID = AleoID<Field<Self>, { hrp2!("au") }>;
/// The transmission checksum type.
type TransmissionChecksum = u128;

/// The network edition.
const EDITION: u16 = 0;
Expand Down
2 changes: 2 additions & 0 deletions console/network/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,8 @@ pub trait Network:
type TransactionID: Bech32ID<Field<Self>>;
/// The transition ID type.
type TransitionID: Bech32ID<Field<Self>>;
/// The transmission checksum type.
type TransmissionChecksum: IntegerType;

/// Returns the genesis block bytes.
fn genesis_bytes() -> &'static [u8];
Expand Down
2 changes: 2 additions & 0 deletions console/network/src/mainnet_v0.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ impl Network for MainnetV0 {
type TransactionID = AleoID<Field<Self>, { hrp2!(TRANSACTION_PREFIX) }>;
/// The transition ID type.
type TransitionID = AleoID<Field<Self>, { hrp2!("au") }>;
/// The transmission checksum type.
type TransmissionChecksum = u128;

/// The network edition.
const EDITION: u16 = 0;
Expand Down
2 changes: 2 additions & 0 deletions console/network/src/testnet_v0.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ impl Network for TestnetV0 {
type TransactionID = AleoID<Field<Self>, { hrp2!(TRANSACTION_PREFIX) }>;
/// The transition ID type.
type TransitionID = AleoID<Field<Self>, { hrp2!("au") }>;
/// The transmission checksum type.
type TransmissionChecksum = u128;

/// The network edition.
const EDITION: u16 = 0;
Expand Down
5 changes: 5 additions & 0 deletions ledger/block/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ package = "snarkvm-ledger-narwhal-batch-header"
path = "../narwhal/batch-header"
version = "=0.16.19"

[dependencies.ledger-narwhal-data]
package = "snarkvm-ledger-narwhal-data"
path = "../narwhal/data"
version = "=0.16.19"

[dependencies.ledger-narwhal-subdag]
package = "snarkvm-ledger-narwhal-subdag"
path = "../narwhal/subdag"
Expand Down
1 change: 1 addition & 0 deletions ledger/block/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ use console::{
};
use ledger_authority::Authority;
use ledger_committee::Committee;
use ledger_narwhal_data::Data;
use ledger_narwhal_subdag::Subdag;
use ledger_narwhal_transmission_id::TransmissionID;
use ledger_puzzle::{PuzzleSolutions, Solution, SolutionID};
Expand Down
58 changes: 41 additions & 17 deletions ledger/block/src/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -539,14 +539,15 @@ impl<N: Network> Block<N> {
) -> Result<(Vec<SolutionID<N>>, Vec<N::TransactionID>)> {
// Prepare an iterator over the solution IDs.
let mut solutions = solutions.as_ref().map(|s| s.deref()).into_iter().flatten().peekable();
// Prepare an iterator over the unconfirmed transaction IDs.
let unconfirmed_transaction_ids = cfg_iter!(transactions)
.map(|confirmed| confirmed.to_unconfirmed_transaction_id())
// Prepare an iterator over the unconfirmed transactions.
let unconfirmed_transactions = cfg_iter!(transactions)
.map(|confirmed| confirmed.to_unconfirmed_transaction())
.collect::<Result<Vec<_>>>()?;
let mut unconfirmed_transaction_ids = unconfirmed_transaction_ids.iter().peekable();
let mut unconfirmed_transactions = unconfirmed_transactions.iter().peekable();

// Initialize a set of already seen transmission IDs.
let mut seen_transmission_ids = HashSet::new();
// Initialize a set of already seen transaction and solution IDs.
let mut seen_transaction_ids = HashSet::new();
let mut seen_solution_ids = HashSet::new();

// Initialize a set of aborted or already-existing solution IDs.
let mut aborted_or_existing_solution_ids = HashSet::new();
Expand All @@ -555,18 +556,36 @@ impl<N: Network> Block<N> {

// Iterate over the transmission IDs.
for transmission_id in subdag.transmission_ids() {
// If the transmission ID has already been seen, then continue.
if !seen_transmission_ids.insert(transmission_id) {
continue;
// If the transaction or solution ID has already been seen, then continue.
// Note: This is done instead of checking `TransmissionID` directly, because we need to
// ensure that each transaction or solution ID is unique. The `TransmissionID` is guaranteed
// to be unique, however the transaction/solution ID may not be due to malleability concerns.
match transmission_id {
TransmissionID::Ratification => {}
TransmissionID::Solution(solution_id, _) => {
if !seen_solution_ids.insert(solution_id) {
continue;
}
}
TransmissionID::Transaction(transaction_id, _) => {
if !seen_transaction_ids.insert(transaction_id) {
continue;
}
}
}

// Process the transmission ID.
match transmission_id {
TransmissionID::Ratification => {}
TransmissionID::Solution(solution_id) => {
TransmissionID::Solution(solution_id, checksum) => {
match solutions.peek() {
// Check the next solution matches the expected solution ID.
Some((_, solution)) if solution.id() == *solution_id => {
Some((_, solution))
if solution.id() == *solution_id
&& Data::<Solution<N>>::Buffer(solution.to_bytes_le()?.into())
.to_checksum::<N>()?
== *checksum =>
{
// Increment the solution iterator.
solutions.next();
}
Expand All @@ -578,12 +597,17 @@ impl<N: Network> Block<N> {
}
}
}
TransmissionID::Transaction(transaction_id) => {
match unconfirmed_transaction_ids.peek() {
TransmissionID::Transaction(transaction_id, checksum) => {
match unconfirmed_transactions.peek() {
// Check the next transaction matches the expected transaction.
Some(expected_id) if transaction_id == *expected_id => {
// Increment the unconfirmed transaction ID iterator.
unconfirmed_transaction_ids.next();
Some(transaction)
if transaction.id() == *transaction_id
&& Data::<Transaction<N>>::Buffer(transaction.to_bytes_le()?.into())
.to_checksum::<N>()?
== *checksum =>
{
// Increment the unconfirmed transaction iterator.
unconfirmed_transactions.next();
}
// Otherwise, add the transaction ID to the aborted or existing list.
_ => {
Expand All @@ -599,7 +623,7 @@ impl<N: Network> Block<N> {
// Ensure there are no more solutions in the block.
ensure!(solutions.next().is_none(), "There exists more solutions than expected.");
// Ensure there are no more transactions in the block.
ensure!(unconfirmed_transaction_ids.next().is_none(), "There exists more transactions than expected.");
ensure!(unconfirmed_transactions.next().is_none(), "There exists more transactions than expected.");

// Ensure the aborted solution IDs match.
for aborted_solution_id in aborted_solution_ids {
Expand Down
14 changes: 14 additions & 0 deletions ledger/narwhal/data/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,20 @@ pub enum Data<T: FromBytes + ToBytes + Send + 'static> {
}

impl<T: FromBytes + ToBytes + Send + 'static> Data<T> {
pub fn to_checksum<N: Network>(&self) -> Result<N::TransmissionChecksum> {
// Convert to bits.
let preimage = match self {
Self::Object(object) => object.to_bytes_le()?.to_bits_le(),
Self::Buffer(bytes) => bytes.deref().to_bits_le(),
};
// Hash the preimage bits.
let hash = N::hash_sha3_256(&preimage)?;
// Select the number of bits needed to parse the checksum.
let num_bits = usize::try_from(N::TransmissionChecksum::BITS).map_err(error)?;
// Return the checksum.
N::TransmissionChecksum::from_bits_le(&hash[0..num_bits])
}

pub fn into<T2: From<Data<T>> + From<T> + FromBytes + ToBytes + Send + 'static>(self) -> Data<T2> {
match self {
Self::Object(x) => Data::Object(x.into()),
Expand Down
14 changes: 8 additions & 6 deletions ledger/narwhal/transmission-id/src/bytes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ impl<N: Network> FromBytes for TransmissionID<N> {
// Match the variant.
match variant {
0 => Ok(Self::Ratification),
1 => Ok(Self::Solution(FromBytes::read_le(&mut reader)?)),
2 => Ok(Self::Transaction(FromBytes::read_le(&mut reader)?)),
1 => Ok(Self::Solution(FromBytes::read_le(&mut reader)?, FromBytes::read_le(&mut reader)?)),
2 => Ok(Self::Transaction(FromBytes::read_le(&mut reader)?, FromBytes::read_le(&mut reader)?)),
3.. => Err(error("Invalid worker transmission ID variant")),
}
}
Expand All @@ -35,13 +35,15 @@ impl<N: Network> ToBytes for TransmissionID<N> {
// Write the transmission.
match self {
Self::Ratification => 0u8.write_le(&mut writer),
Self::Solution(id) => {
Self::Solution(id, checksum) => {
1u8.write_le(&mut writer)?;
id.write_le(&mut writer)
id.write_le(&mut writer)?;
checksum.write_le(&mut writer)
}
Self::Transaction(id) => {
Self::Transaction(id, checksum) => {
2u8.write_le(&mut writer)?;
id.write_le(&mut writer)
id.write_le(&mut writer)?;
checksum.write_le(&mut writer)
}
}
}
Expand Down
43 changes: 29 additions & 14 deletions ledger/narwhal/transmission-id/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,41 +27,50 @@ pub enum TransmissionID<N: Network> {
/// A ratification.
Ratification,
/// A solution.
Solution(SolutionID<N>),
Solution(SolutionID<N>, N::TransmissionChecksum),
/// A transaction.
Transaction(N::TransactionID),
Transaction(N::TransactionID, N::TransmissionChecksum),
}

impl<N: Network> From<SolutionID<N>> for TransmissionID<N> {
/// Converts the solution ID into a transmission ID.
fn from(solution_id: SolutionID<N>) -> Self {
Self::Solution(solution_id)
impl<N: Network> From<(SolutionID<N>, N::TransmissionChecksum)> for TransmissionID<N> {
/// Converts the solution ID and checksum into a transmission ID.
fn from((solution_id, checksum): (SolutionID<N>, N::TransmissionChecksum)) -> Self {
Self::Solution(solution_id, checksum)
}
}

impl<N: Network> From<&N::TransactionID> for TransmissionID<N> {
/// Converts the transaction ID into a transmission ID.
fn from(transaction_id: &N::TransactionID) -> Self {
Self::Transaction(*transaction_id)
impl<N: Network> From<(&N::TransactionID, &N::TransmissionChecksum)> for TransmissionID<N> {
/// Converts the transaction ID and checksum into a transmission ID.
fn from((transaction_id, checksum): (&N::TransactionID, &N::TransmissionChecksum)) -> Self {
Self::Transaction(*transaction_id, *checksum)
}
}

impl<N: Network> TransmissionID<N> {
/// Returns the solution ID if the transmission is a solution.
pub fn solution(&self) -> Option<SolutionID<N>> {
match self {
Self::Solution(solution_id) => Some(*solution_id),
Self::Solution(solution_id, _) => Some(*solution_id),
_ => None,
}
}

/// Returns the transaction ID if the transmission is a transaction.
pub fn transaction(&self) -> Option<N::TransactionID> {
match self {
Self::Transaction(transaction_id) => Some(*transaction_id),
Self::Transaction(transaction_id, _) => Some(*transaction_id),
_ => None,
}
}

/// Returns the checksum if the transmission is a solution or a transaction.
pub fn checksum(&self) -> Option<N::TransmissionChecksum> {
match self {
Self::Ratification => None,
Self::Solution(_, checksum) => Some(*checksum),
Self::Transaction(_, checksum) => Some(*checksum),
}
}
}

#[cfg(any(test, feature = "test-helpers"))]
Expand All @@ -81,11 +90,17 @@ pub mod test_helpers {
let mut sample = Vec::with_capacity(10);
// Append sample solution IDs.
for _ in 0..5 {
sample.push(TransmissionID::Solution(SolutionID::from(rng.gen::<u64>())));
sample.push(TransmissionID::Solution(
SolutionID::from(rng.gen::<u64>()),
<CurrentNetwork as Network>::TransmissionChecksum::from(rng.gen::<u128>()),
));
}
// Append sample transaction IDs.
for _ in 0..5 {
let id = TransmissionID::Transaction(<CurrentNetwork as Network>::TransactionID::from(Field::rand(rng)));
let id = TransmissionID::Transaction(
<CurrentNetwork as Network>::TransactionID::from(Field::rand(rng)),
<CurrentNetwork as Network>::TransmissionChecksum::from(rng.gen::<u128>()),
);
sample.push(id);
}
// Return the sample vector.
Expand Down
21 changes: 15 additions & 6 deletions ledger/narwhal/transmission-id/src/string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,20 @@ impl<N: Network> FromStr for TransmissionID<N> {

/// Initializes the transmission ID from a string.
fn from_str(input: &str) -> Result<Self, Self::Err> {
if input.starts_with(SOLUTION_ID_PREFIX) {
Ok(Self::Solution(SolutionID::from_str(input)?))
} else if input.starts_with(TRANSACTION_PREFIX) {
// Split the id and checksum.
let (id, checksum) = input.split_once('.').ok_or_else(|| anyhow!("Invalid transmission ID: {input}"))?;
// Parse the string.
if id.starts_with(SOLUTION_ID_PREFIX) {
Ok(Self::Solution(
SolutionID::from_str(id)?,
N::TransmissionChecksum::from_str(checksum)
.map_err(|_| anyhow!("Failed to parse checksum: {checksum}"))?,
))
} else if id.starts_with(TRANSACTION_PREFIX) {
Ok(Self::Transaction(
N::TransactionID::from_str(input).map_err(|_| anyhow!("Failed to parse transaction ID: {input}"))?,
N::TransactionID::from_str(id).map_err(|_| anyhow!("Failed to parse transaction ID: {id}"))?,
N::TransmissionChecksum::from_str(checksum)
.map_err(|_| anyhow!("Failed to parse checksum: {checksum}"))?,
))
} else {
bail!("Invalid transmission ID: {input}")
Expand All @@ -43,8 +52,8 @@ impl<N: Network> Display for TransmissionID<N> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::Ratification => write!(f, "ratification"),
Self::Solution(id) => write!(f, "{}", id),
Self::Transaction(id) => write!(f, "{}", id),
Self::Solution(id, checksum) => write!(f, "{}.{}", id, checksum),
Self::Transaction(id, checksum) => write!(f, "{}.{}", id, checksum),
}
}
}
Expand Down
11 changes: 11 additions & 0 deletions ledger/narwhal/transmission/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,17 @@ impl<N: Network> From<Data<Transaction<N>>> for Transmission<N> {
}
}

impl<N: Network> Transmission<N> {
/// Returns the checksum of the transmission.
pub fn to_checksum(&self) -> Result<Option<N::TransmissionChecksum>> {
match self {
Self::Ratification => Ok(None),
Self::Solution(solution) => solution.to_checksum::<N>().map(Some),
Self::Transaction(transaction) => transaction.to_checksum::<N>().map(Some),
}
}
}

#[cfg(any(test, feature = "test-helpers"))]
pub mod test_helpers {
use super::*;
Expand Down
28 changes: 28 additions & 0 deletions ledger/puzzle/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,34 @@ impl<N: Network> Puzzle<N> {
Ok(())
}

/// ATTENTION: This function will update the target if the solution target is different from the calculated one.
/// Returns `Ok(())` if the solution is valid.
pub fn check_solution_mut(
&self,
solution: &mut Solution<N>,
expected_epoch_hash: N::BlockHash,
expected_proof_target: u64,
) -> Result<()> {
// Ensure the epoch hash matches.
if solution.epoch_hash() != expected_epoch_hash {
bail!(
"Solution does not match the expected epoch hash (found '{}', expected '{expected_epoch_hash}')",
solution.epoch_hash()
)
}
// Calculate the proof target of the solution.
let proof_target = self.get_proof_target_unchecked(solution)?;

// Set the target with the newly calculated proof target value.
solution.target = proof_target;

// Ensure the solution is greater than or equal to the expected proof target.
if proof_target < expected_proof_target {
bail!("Solution does not meet the proof target requirement ({proof_target} < {expected_proof_target})")
}
Ok(())
}

/// Returns `Ok(())` if the solutions are valid.
pub fn check_solutions(
&self,
Expand Down
Loading

0 comments on commit 68f4f31

Please sign in to comment.