Skip to content
This repository has been archived by the owner on Dec 17, 2024. It is now read-only.

Commit

Permalink
Add transaction validation (#36)
Browse files Browse the repository at this point in the history
This is the first step towards having validation for transactions.

It verifies that transaction signature is correct and that current
workflow restrictions are checked.

The transaction is validated on entering mempool, which covers
currently all the key use cases in the program.
  • Loading branch information
tuommaki authored Jan 21, 2024
1 parent 906fae2 commit 3936d2d
Show file tree
Hide file tree
Showing 10 changed files with 248 additions and 97 deletions.
23 changes: 8 additions & 15 deletions crates/cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,15 +123,12 @@ pub async fn run_exec_command(
.map(|t| t.try_into())
.collect::<Result<Vec<_>, _>>()?;

let mut tx = Transaction {
payload: Payload::Run {
let tx = Transaction::new(
Payload::Run {
workflow: Workflow { steps },
},
// NOTE: In the devnet `nonce` is just a placeholder for the future.
nonce: 42,
..Default::default()
};
tx.sign(&key);
&key,
);

client.send_transaction(&tx).await?;
Ok(tx.hash.to_string())
Expand Down Expand Up @@ -204,18 +201,14 @@ pub async fn run_deploy_command(
let prover_hash = prover_data.hash.to_string();
let verifier_hash = verifier_data.hash.to_string();

let mut tx = Transaction {
payload: Payload::Deploy {
let tx = Transaction::new(
Payload::Deploy {
name,
prover: prover_data,
verifier: verifier_data,
},
nonce: 42,
..Default::default()
};

// Transaction hash gets computed during this as well.
tx.sign(&key);
&key,
);

client
.send_transaction(&tx)
Expand Down
3 changes: 3 additions & 0 deletions crates/node/src/mempool/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ impl Mempool {
}

pub async fn add(&mut self, tx: Transaction) -> Result<()> {
// First validate transaction.
tx.validate()?;

let mut tx = tx;
self.storage.set(&tx).await?;

Expand Down
14 changes: 3 additions & 11 deletions crates/node/src/networking/p2p.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,9 +151,9 @@ impl Writing for P2P {
mod tests {
use super::*;
use eyre::Result;
use gevulot_node::types::{transaction::Payload, Hash, Transaction};
use gevulot_node::types::{transaction::Payload, Transaction};
use libsecp256k1::SecretKey;
use rand::{rngs::StdRng, RngCore, SeedableRng};
use rand::{rngs::StdRng, SeedableRng};
use tokio::sync::mpsc::{self, Sender};
use tracing::level_filters::LevelFilter;
use tracing_subscriber::EnvFilter;
Expand Down Expand Up @@ -222,16 +222,8 @@ mod tests {

fn new_tx() -> Transaction {
let rng = &mut StdRng::from_entropy();
let mut tx = Transaction {
hash: Hash::random(rng),
payload: Payload::Empty,
nonce: rng.next_u64(),
..Default::default()
};

let key = SecretKey::random(rng);
tx.sign(&key);
tx
Transaction::new(Payload::Empty, &key)
}

fn start_logger(default_level: LevelFilter) {
Expand Down
28 changes: 10 additions & 18 deletions crates/node/src/scheduler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::vmm::{vm_server::TaskManager, VMId};
use crate::workflow::{WorkflowEngine, WorkflowError};
use async_trait::async_trait;
use gevulot_node::types::transaction::Payload;
use gevulot_node::types::{Signature, TaskKind, Transaction};
use gevulot_node::types::{TaskKind, Transaction};
use libsecp256k1::SecretKey;
pub use program_manager::ProgramManager;
use rand::RngCore;
Expand Down Expand Up @@ -294,29 +294,23 @@ impl TaskManager for Scheduler {
);

let nonce = rand::thread_rng().next_u64();
let mut tx = match running_task.task.kind {
TaskKind::Proof => Transaction {
hash: Hash::default(),
payload: Payload::Proof {
let tx = match running_task.task.kind {
TaskKind::Proof => Transaction::new(
Payload::Proof {
parent: running_task.task.tx,
prover: program,
proof: result.data,
},
nonce,
signature: Signature::default(),
propagated: false,
},
TaskKind::Verification => Transaction {
hash: Hash::default(),
payload: Payload::Verification {
&self.node_key,
),
TaskKind::Verification => Transaction::new(
Payload::Verification {
parent: running_task.task.tx,
verifier: program,
verification: result.data,
},
nonce,
signature: Signature::default(),
propagated: false,
},
&self.node_key,
),
TaskKind::PoW => {
todo!("proof of work tasks not implemented yet");
}
Expand All @@ -328,8 +322,6 @@ impl TaskManager for Scheduler {
}
};

tx.sign(&self.node_key);

let mut mempool = self.mempool.write().await;
if let Err(err) = mempool.add(tx.clone()).await {
tracing::error!("failed to add transaction to mempool: {}", err);
Expand Down
2 changes: 2 additions & 0 deletions crates/node/src/storage/database/entity/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
pub mod payload;
pub mod public_key;
pub mod transaction;

pub use public_key::PublicKey;
pub use transaction::Transaction;
89 changes: 89 additions & 0 deletions crates/node/src/storage/database/entity/public_key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
use std::fmt;

use serde::{Deserialize, Serialize};
use sqlx::{Decode, Encode, Postgres, Type};
use thiserror::Error;

#[allow(clippy::enum_variant_names)]
#[derive(Error, Debug)]
pub enum PublicKeyError {
#[error("public key parse error: {0}")]
ParseError(String),
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct PublicKey(pub libsecp256k1::PublicKey);

impl Default for PublicKey {
fn default() -> Self {
PublicKey(libsecp256k1::PublicKey::from_secret_key(
&libsecp256k1::SecretKey::default(),
))
}
}

impl fmt::Display for PublicKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", hex::encode(self.0.serialize()))
}
}
impl From<PublicKey> for libsecp256k1::PublicKey {
fn from(value: PublicKey) -> Self {
value.0
}
}

impl From<String> for PublicKey {
fn from(value: String) -> Self {
Self::try_from(value.as_str()).expect("from string")
}
}

impl TryFrom<&str> for PublicKey {
type Error = PublicKeyError;

fn try_from(value: &str) -> Result<Self, Self::Error> {
let bs = hex::decode(value).map_err(|e| PublicKeyError::ParseError(e.to_string()))?;
let bs: [u8; 65] = bs
.try_into()
.map_err(|_| PublicKeyError::ParseError("byte array length".to_string()))?;
let pub_key = libsecp256k1::PublicKey::parse(&bs)
.map_err(|e| PublicKeyError::ParseError(e.to_string()))?;
Ok(PublicKey(pub_key))
}
}

impl PublicKey {
pub fn from_secret_key(secret_key: &libsecp256k1::SecretKey) -> PublicKey {
PublicKey(libsecp256k1::PublicKey::from_secret_key(secret_key))
}
}

impl<'q> Decode<'q, Postgres> for PublicKey {
fn decode(
value: <Postgres as sqlx::database::HasValueRef<'q>>::ValueRef,
) -> Result<Self, sqlx::error::BoxDynError> {
let str_value = <String as Decode<Postgres>>::decode(value)?;
Ok(PublicKey::from(str_value))
}
}

impl<'q> Encode<'q, Postgres> for PublicKey {
fn encode_by_ref(
&self,
buf: &mut <Postgres as sqlx::database::HasArguments<'q>>::ArgumentBuffer,
) -> sqlx::encode::IsNull {
let str_value = self.to_string();
<String as Encode<Postgres>>::encode(str_value, buf)
}
}

impl Type<Postgres> for PublicKey {
fn type_info() -> <Postgres as sqlx::Database>::TypeInfo {
<String as Type<Postgres>>::type_info()
}

fn compatible(ty: &<Postgres as sqlx::Database>::TypeInfo) -> bool {
<String as Type<Postgres>>::compatible(ty)
}
}
4 changes: 4 additions & 0 deletions crates/node/src/storage/database/entity/transaction.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::storage::database::entity;
use crate::types::{self, transaction, Hash, Signature};
use serde::{Deserialize, Serialize};

Expand All @@ -19,6 +20,7 @@ pub enum Kind {

#[derive(Clone, Debug, Default, Deserialize, Serialize, sqlx::FromRow)]
pub struct Transaction {
pub author: entity::PublicKey,
pub hash: Hash,
pub kind: Kind,
pub nonce: sqlx::types::Decimal,
Expand All @@ -42,6 +44,7 @@ impl From<&types::Transaction> for Transaction {
};

Transaction {
author: entity::PublicKey(value.author),
hash: value.hash,
kind,
nonce: value.nonce.into(),
Expand All @@ -54,6 +57,7 @@ impl From<&types::Transaction> for Transaction {
impl From<Transaction> for types::Transaction {
fn from(value: Transaction) -> types::Transaction {
types::Transaction {
author: value.author.into(),
hash: value.hash,
// This field is complemented separately.
payload: transaction::Payload::Empty,
Expand Down
3 changes: 3 additions & 0 deletions crates/node/src/storage/database/postgres.rs
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,8 @@ impl Database {

#[cfg(test)]
mod tests {
use libsecp256k1::{PublicKey, SecretKey};

use crate::types::{
transaction::{Payload, ProgramMetadata},
Signature, Transaction,
Expand All @@ -532,6 +534,7 @@ mod tests {
.expect("failed to connect to db");

let tx = Transaction {
author: PublicKey::from_secret_key(&SecretKey::default()),
hash: Hash::default(),
payload: Payload::Deploy {
name: "test deployment".to_string(),
Expand Down
Loading

0 comments on commit 3936d2d

Please sign in to comment.