Merge 'Enable encryption option in Whopper ' from Avinash Sajjanshetty

If encryption is arg is passed, then we run whopper tests with
encryptions. We randomly generate cipher and key, and run whopper. They
are also printed for debugging and analysis.
Also, updated the corresponding scripts. So now you can do:
```bash
./whopper/bin/run --enable-encryption

or 

./whopper/bin/explore --enable-encryption
```

Closes #3183
This commit is contained in:
Pekka Enberg
2025-09-18 10:03:13 +03:00
committed by GitHub
7 changed files with 108 additions and 13 deletions

View File

@@ -248,7 +248,7 @@ Note that exploration uses the `chaos` mode so if you need to reproduce a run, u
SEED=1234 ./whopper/bin/run --mode chaos
```
Both `explore` and `run` accept `--enable-checksums` flag to enable per page checksums.
Both `explore` and `run` accept the `--enable-checksums` and `--enable-encryption` flags for per page checksums and encryption respectively.
## Python Bindings

1
Cargo.lock generated
View File

@@ -4338,6 +4338,7 @@ version = "0.2.0-pre.3"
dependencies = [
"anyhow",
"clap",
"hex",
"memmap2",
"rand 0.9.2",
"rand_chacha 0.9.0",

View File

@@ -40,7 +40,6 @@ pub mod numeric;
mod numeric;
use crate::storage::checksum::CHECKSUM_REQUIRED_RESERVED_BYTES;
use crate::storage::encryption::CipherMode;
use crate::translate::pragma::TURSO_CDC_DEFAULT_TABLE_NAME;
#[cfg(all(feature = "fs", feature = "conn_raw_api"))]
use crate::types::{WalFrameInfo, WalState};
@@ -79,7 +78,7 @@ use std::{
#[cfg(feature = "fs")]
use storage::database::DatabaseFile;
pub use storage::database::IOContext;
pub use storage::encryption::{EncryptionContext, EncryptionKey};
pub use storage::encryption::{CipherMode, EncryptionContext, EncryptionKey};
use storage::page_cache::PageCache;
use storage::pager::{AtomicDbState, DbState};
use storage::sqlite3_ondisk::PageSize;

View File

@@ -25,6 +25,8 @@ tracing = { workspace = true }
tracing-subscriber = { workspace = true, features = ["env-filter"] }
turso_core = { workspace = true, features = ["simulator"]}
turso_parser = { workspace = true }
hex = "0.4.3"
[features]
checksum = ["turso_core/checksum"]
checksum = ["turso_core/checksum"]
encryption = ["turso_core/encryption"]

View File

@@ -1,18 +1,34 @@
#!/bin/bash
# Usage: ./explore.sh [--enable-checksums|--enable-encryption] [other-args...]
set -e
# Check for special build time flags (e.g. `--enable-checksums`)
# Check for special build time flags (e.g. `--enable-checksums`, `--enable-encryption`)
FEATURES=""
ARGS=()
CHECKSUM_ENABLED=false
ENCRYPTION_ENABLED=false
for arg in "$@"; do
if [[ "$arg" == "--enable-checksums" ]]; then
FEATURES="--features checksum"
CHECKSUM_ENABLED=true
elif [[ "$arg" == "--enable-encryption" ]]; then
FEATURES="--features encryption"
ENCRYPTION_ENABLED=true
ARGS+=("$arg")
else
ARGS+=("$arg")
fi
done
# check for incompatible options
if [[ "$CHECKSUM_ENABLED" == true && "$ENCRYPTION_ENABLED" == true ]]; then
echo "Error: --enable-checksums and --enable-encryption are not compatible with each other"
exit 1
fi
cargo build $FEATURES -p turso_whopper
echo "Running Whopper in an infinite loop in 'chaos' mode..."

View File

@@ -1,18 +1,34 @@
#!/bin/bash
# Usage: ./run.sh [--enable-checksums|--enable-encryption] [other-args...]
set -e
# Check for special build time flags (e.g. `--enable-checksums`)
# Check for special build time flags (e.g. `--enable-checksums`, `--enable-encryption`)
FEATURES=""
ARGS=()
CHECKSUM_ENABLED=false
ENCRYPTION_ENABLED=false
for arg in "$@"; do
if [[ "$arg" == "--enable-checksums" ]]; then
FEATURES="--features checksum"
CHECKSUM_ENABLED=true
elif [[ "$arg" == "--enable-encryption" ]]; then
FEATURES="--features encryption"
ENCRYPTION_ENABLED=true
ARGS+=("$arg")
else
ARGS+=("$arg")
fi
done
# check for incompatible options
if [[ "$CHECKSUM_ENABLED" == true && "$ENCRYPTION_ENABLED" == true ]]; then
echo "Error: --enable-checksums and --enable-encryption are not compatible with each other"
exit 1
fi
cargo build $FEATURES -p turso_whopper
time RUST_BACKTRACE=full ./target/debug/turso_whopper "${ARGS[@]}"

View File

@@ -14,7 +14,9 @@ use std::cell::RefCell;
use std::sync::Arc;
use tracing::trace;
use tracing_subscriber::{EnvFilter, layer::SubscriberExt, util::SubscriberInitExt};
use turso_core::{Connection, Database, IO, Statement};
use turso_core::{
CipherMode, Connection, Database, DatabaseOpts, EncryptionOpts, IO, OpenFlags, Statement,
};
use turso_parser::ast::SortOrder;
mod io;
@@ -36,6 +38,9 @@ struct Args {
/// Enable MVCC (Multi-Version Concurrency Control)
#[arg(long)]
enable_mvcc: bool,
/// Enable database encryption
#[arg(long)]
enable_encryption: bool,
}
struct SimulatorConfig {
@@ -74,6 +79,17 @@ struct Stats {
integrity_checks: usize,
}
fn may_be_set_encryption(
conn: Arc<Connection>,
opts: &Option<EncryptionOpts>,
) -> anyhow::Result<Arc<Connection>> {
if let Some(opts) = opts {
conn.pragma_update("cipher", format!("'{}'", opts.cipher.clone()))?;
conn.pragma_update("hexkey", format!("'{}'", opts.hexkey.clone()))?;
}
Ok(conn)
}
fn main() -> anyhow::Result<()> {
init_logger();
@@ -109,14 +125,35 @@ fn main() -> anyhow::Result<()> {
let db_path = format!("whopper-{}-{}.db", seed, std::process::id());
let db = match Database::open_file(io.clone(), &db_path, args.enable_mvcc, true) {
Ok(db) => db,
Err(e) => {
return Err(anyhow::anyhow!("Database open failed: {}", e));
let encryption_opts = if args.enable_encryption {
let opts = random_encryption_config(&mut rng);
println!("cipher = {}, key = {}", opts.cipher, opts.hexkey);
Some(opts)
} else {
None
};
let db = {
let opts = DatabaseOpts::new()
.with_mvcc(args.enable_mvcc)
.with_indexes(true);
match Database::open_file_with_flags(
io.clone(),
&db_path,
OpenFlags::default(),
opts,
encryption_opts.clone(),
) {
Ok(db) => db,
Err(e) => {
return Err(anyhow::anyhow!("Database open failed: {}", e));
}
}
};
let boostrap_conn = match db.connect() {
Ok(conn) => conn,
Ok(conn) => may_be_set_encryption(conn, &encryption_opts)?,
Err(e) => {
return Err(anyhow::anyhow!("Connection failed: {}", e));
}
@@ -146,7 +183,7 @@ fn main() -> anyhow::Result<()> {
let mut fibers = Vec::new();
for i in 0..config.max_connections {
let conn = match db.connect() {
Ok(conn) => conn,
Ok(conn) => may_be_set_encryption(conn, &encryption_opts)?,
Err(e) => {
return Err(anyhow::anyhow!(
"Failed to create fiber connection {}: {}",
@@ -323,6 +360,30 @@ fn create_initial_schema(rng: &mut ChaCha8Rng) -> Vec<Create> {
schema
}
fn random_encryption_config(rng: &mut ChaCha8Rng) -> EncryptionOpts {
let cipher_modes = [
CipherMode::Aes128Gcm,
CipherMode::Aes256Gcm,
CipherMode::Aegis256,
CipherMode::Aegis128L,
CipherMode::Aegis128X2,
CipherMode::Aegis128X4,
CipherMode::Aegis256X2,
CipherMode::Aegis256X4,
];
let cipher_mode = cipher_modes[rng.random_range(0..cipher_modes.len())];
let key_size = cipher_mode.required_key_size();
let mut key = vec![0u8; key_size];
rng.fill_bytes(&mut key);
EncryptionOpts {
cipher: cipher_mode.to_string(),
hexkey: hex::encode(&key),
}
}
fn perform_work(
fiber_idx: usize,
rng: &mut ChaCha8Rng,