Skip to content
Open
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
5119e87
feat: fix create_bucket_with_storage extrinsic
danielbui12 May 27, 2026
8b2b806
feat: update pallet-storage-provider
danielbui12 May 28, 2026
c9d0edd
feat: add establish_storage_agreement with provider-signed terms
danielbui12 May 28, 2026
43eeb2e
feat: migrate request_agreement to establish_replica_agreement
danielbui12 May 28, 2026
8ab1797
test: update pallet tests for new agreement flow
danielbui12 May 28, 2026
583e1af
feat: update pallet storage provider benchmarks
danielbui12 May 28, 2026
2e10aac
feat: update pallet s3 registry
danielbui12 May 28, 2026
fd0bbef
feat: rewire pallet-drive-registry to signed-terms create_drive
danielbui12 May 28, 2026
a3fbf60
chore: update weights
danielbui12 May 28, 2026
82b5919
feat: rewire client SDK to signed-terms establish_storage_agreement
danielbui12 May 29, 2026
430f3ea
chore: update runtime weights
danielbui12 May 29, 2026
50f2394
feat: add negotiate endpoint and persistent nonce counter to provider…
danielbui12 May 29, 2026
52e3d59
feat: update full-flow papi example
danielbui12 May 29, 2026
4134f39
chore: fmt
danielbui12 May 29, 2026
00408be
chore: fmt
danielbui12 May 29, 2026
7aae4eb
Merge branch 'dev' into tung/create_bucket_with_storage_fixes
danielbui12 May 29, 2026
e36601c
feat: adopt storage-interfaces/file-system to new agreement flow
danielbui12 Jun 1, 2026
ebe02b7
feat: adopt storage-interfaces/s3 to new agreement flow
danielbui12 Jun 1, 2026
4726b1b
fix(bot): install missing `frame-omni-bencher` in CI for bench (#106)
ilchu May 29, 2026
07afc24
chore: fmt
danielbui12 Jun 1, 2026
c5e0c65
chore: fix tests
danielbui12 Jun 1, 2026
1440bba
feat: update demo lifecycle of s3 & drive registry
danielbui12 Jun 1, 2026
cd27041
MMerge remote-tracking branch 'origin/dev' into tung/create_bucket_wi…
danielbui12 Jun 1, 2026
ef2f8ae
feat: adpt examples to new agreement flow
danielbui12 Jun 1, 2026
522c53c
refactor(provider-node): drop disk-backed nonce file, bootstrap from …
danielbui12 Jun 3, 2026
daa524f
Merge remote-tracking branch 'origin/dev' into tung/create_bucket_wit…
danielbui12 Jun 3, 2026
bf8fb7b
refactor: rename replay-window anchor hwm -> hsn (high sequence nonce)
danielbui12 Jun 3, 2026
ab9cec5
Merge remote-tracking branch 'origin/dev' into tung/create_bucket_wit…
danielbui12 Jun 3, 2026
9dc759a
feat(precompiles): move bucket/drive creation to provider-signed term…
danielbui12 Jun 3, 2026
a119080
Rewire user-interface following new agreement flow changes (#111)
danielbui12 Jun 3, 2026
7195874
test(provider-node): derive expected /info provider id from signing seed
danielbui12 Jun 3, 2026
eadd3c4
chore(weights): regenerate weights for the signed-terms extrinsic sur…
danielbui12 Jun 3, 2026
48a0a66
feat(examples/papi): port smart-contract demos to the signed-terms flow
danielbui12 Jun 3, 2026
1266734
chore: make all tests passed
danielbui12 Jun 3, 2026
1e7da05
Merge remote-tracking branch 'origin/dev' into tung/create_bucket_wit…
danielbui12 Jun 3, 2026
bfef55c
chore: resolve user interface build
danielbui12 Jun 3, 2026
75b8f52
fix: widen replay window to 1024 bits
danielbui12 Jun 4, 2026
5f8ef54
feat: bind provider-signed agreement terms to a bucket id
danielbui12 Jun 4, 2026
4eb7cd2
feat: validate negotiate requests against on-chain provider settings
danielbui12 Jun 4, 2026
a198c5b
fix: return 503 with defined errors when /negotiate prerequisites are…
danielbui12 Jun 4, 2026
0603508
Merge remote-tracking branch 'origin/dev' into tung/create_bucket_wit…
danielbui12 Jun 4, 2026
bd659ef
feat: domain-separate primary and replica term signatures
danielbui12 Jun 4, 2026
17528eb
fix: send bigint terms fields as strings in /negotiate requests
danielbui12 Jun 4, 2026
1feb033
ci: register providers on-chain before starting provider nodes
danielbui12 Jun 4, 2026
2fc19cd
feat: update storage interface benchmarks
danielbui12 Jun 4, 2026
972d9c4
fix: examples smart contract ensure price_per_byte greater than provi…
danielbui12 Jun 4, 2026
2abee61
fix: update sc-api to ensure negotiate request body
danielbui12 Jun 4, 2026
a6f6a99
fix: update ci examples
danielbui12 Jun 4, 2026
86917bb
ci: register provider on-chain before starting provider node in ui-e2e
danielbui12 Jun 4, 2026
e7ef4a0
fix: using runtime api to query matching provider & retrieving bucket…
danielbui12 Jun 5, 2026
5310a22
Merge remote-tracking branch 'origin/dev' into tung/create_bucket_wit…
danielbui12 Jun 5, 2026
6aaeb27
fix: resolve conflicts
danielbui12 Jun 5, 2026
ebf16d5
fix: CI tests
danielbui12 Jun 5, 2026
ead45c0
fix: adopt outdated e2e test coverage
danielbui12 Jun 6, 2026
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
2 changes: 0 additions & 2 deletions .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,6 @@ jobs:
nohup ./target/release/storage-provider-node \
--keyfile /tmp/alice-key --storage-mode inmemory \
--bind-addr 0.0.0.0:3333 --chain-rpc ws://127.0.0.1:2222 \
--enable-agreement-coordinator --enable-checkpoint-coordinator \
> /tmp/provider.log 2>&1 &
echo "Waiting for provider HTTP server..."
for i in $(seq 1 60); do
Expand All @@ -167,7 +166,6 @@ jobs:
nohup ./target/release/storage-provider-node \
--keyfile /tmp/charlie-key --storage-mode disk --storage-path /tmp/provider-data \
--bind-addr 0.0.0.0:3334 --chain-rpc ws://127.0.0.1:2222 \
--enable-agreement-coordinator --enable-checkpoint-coordinator \
> /tmp/provider-disk.log 2>&1 &
echo "Waiting for disk provider HTTP server..."
for i in $(seq 1 60); do
Expand Down
6 changes: 6 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ description = "Client library for scalable Web3 storage"

[dependencies]
storage-primitives = { workspace = true, features = ["serde", "std"] }
codec = { workspace = true, features = ["std"] }
reqwest = { workspace = true }
serde = { workspace = true, features = ["std"] }
serde_json = { workspace = true }
Expand Down
289 changes: 92 additions & 197 deletions client/examples/complete_workflow.rs
Original file line number Diff line number Diff line change
@@ -1,216 +1,111 @@
//! Complete workflow example demonstrating all client types.
//!
//! This example shows:
//! 1. Provider registration
//! 2. Bucket creation by admin
//! 3. Agreement request and acceptance
//! 4. Data upload by storage user
//! 5. Checkpoint creation
//! 6. Challenge by third party
//! 7. Challenge response by provider

//! 1. Start the chain: just start-chain
//! 2. Start the provider: just start-provider (defaults to the //Alice key)
//! 3. Register the provider: cargo run --example register_provider
//!
//! With those running, this example exercises only the user-facing flow against
//! the live chain + provider node:
//! 1. negotiate storage terms with the provider node (HTTP)
//! 2. establish a storage agreement on-chain (creates a bucket)
//! 3. upload data to the provider
//! 4. download it back and verify integrity
//!
//! Usage: cargo run --example complete_workflow [chain_ws] [provider_url] [user_seed]
//!
//! Arguments:
//! chain_ws - WebSocket URL for parachain (default: ws://127.0.0.1:2222)
//! provider_url - HTTP URL for the provider node (default: http://127.0.0.1:3333)
//! user_seed - Seed of the paying user (default: //Bob)

use sp_core::crypto::Ss58Codec;
use sp_runtime::AccountId32;
use std::env;
use storage_client::{
AdminClient, ChallengerClient, ChunkingStrategy, ClientConfig, ProviderClient,
AdminClient, ChunkingStrategy, ClientConfig, NegotiateRequest, ProviderClient, SignedTerms,
StorageUserClient,
};
use subxt_signer::{sr25519::Keypair, SecretUri};

const DEFAULT_CHAIN_WS: &str = "ws://127.0.0.1:2222";
const DEFAULT_PROVIDER_URL: &str = "http://127.0.0.1:3333";
const DEFAULT_USER_SEED: &str = "//Bob";

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize tracing for logs
tracing_subscriber::fmt::init();

println!("=== Scalable Web3 Storage Complete Workflow ===\n");
let args: Vec<String> = env::args().collect();
let chain_ws = args.get(1).map(String::as_str).unwrap_or(DEFAULT_CHAIN_WS);
let provider_url = args
.get(2)
.map(String::as_str)
.unwrap_or(DEFAULT_PROVIDER_URL);
let user_seed = args.get(3).map(String::as_str).unwrap_or(DEFAULT_USER_SEED);

let user_keypair = Keypair::from_uri(&user_seed.parse::<SecretUri>()?)?;
let user_account = AccountId32::from(user_keypair.public_key().0);
let user_ss58 = user_account.to_ss58check();

let provider_ss58 = ProviderClient::fetch_provider_id(provider_url)
.await?
.to_ss58check();

println!("=== Complete Storage Workflow ===");
println!("Chain WebSocket: {chain_ws}");
println!("Provider URL: {provider_url}");
println!("Provider (SS58): {provider_ss58}");
println!("User (SS58): {user_ss58}");
println!();

// Configuration
let config = ClientConfig {
chain_ws_url: "ws://localhost:2222".to_string(),
provider_urls: vec!["http://localhost:3333".to_string()],
timeout_secs: 30,
enable_retries: true,
let chain_config = ClientConfig {
chain_ws_url: chain_ws.to_string(),
..Default::default()
};

// ═════════════════════════════════════════════════════════════════════════
// Step 1: Provider Registration
// ═════════════════════════════════════════════════════════════════════════
println!("📦 Step 1: Provider Registration");

let provider_account = "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty";
let provider_client = ProviderClient::new(config.clone(), provider_account.to_string())?;

println!(" Registering provider {provider_account}...");
provider_client
.register(
"/ip4/203.0.113.1/tcp/3333".to_string(),
vec![0u8; 32], // Mock public key
10_000_000_000_000, // 10 tokens stake
)
.await?;
println!(" ✓ Provider registered with 10 tokens stake\n");

// ═════════════════════════════════════════════════════════════════════════
// Step 2: Bucket Creation by Admin
// ═════════════════════════════════════════════════════════════════════════
println!("🗂️ Step 2: Bucket Creation");

let admin_account = "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY";
let admin_client = AdminClient::new(config.clone(), admin_account.to_string())?;

println!(" Creating bucket with min_providers=1...");
let bucket_id = admin_client.create_bucket(1).await?;
println!(" ✓ Bucket created with ID: {bucket_id}\n");

// ═════════════════════════════════════════════════════════════════════════
// Step 3: Agreement Request and Acceptance
// ═════════════════════════════════════════════════════════════════════════
println!("🤝 Step 3: Storage Agreement");

println!(" Admin requesting storage agreement...");
admin_client
.request_agreement(
bucket_id,
provider_account.to_string(),
10 * 1024 * 1024 * 1024, // 10 GB
100_000, // ~2 weeks at 6 sec blocks
5_000_000_000_000, // 5 tokens payment
None, // Primary (not replica)
)
.await?;
println!(" ✓ Agreement requested: 10 GB for 100,000 blocks");

println!(" Provider accepting agreement...");
provider_client.accept_agreement(bucket_id).await?;
println!(" ✓ Agreement accepted!\n");

// ═════════════════════════════════════════════════════════════════════════
// Step 4: Data Upload by Storage User
// ═════════════════════════════════════════════════════════════════════════
println!("⬆️ Step 4: Data Upload");

let user_client = StorageUserClient::new(config.clone())?;

let data = b"Hello, decentralized world! This is my important data that \
needs to be stored reliably across multiple providers. \
It's content-addressed and cryptographically verified!";

println!(" Uploading {} bytes...", data.len());
let data_root = user_client
.upload(bucket_id, data, ChunkingStrategy::default())
// 1. Negotiate terms with the running provider node.
println!("Negotiating storage terms with provider...");
let signed: SignedTerms = ProviderClient::negotiate_terms(
provider_url,
&NegotiateRequest {
owner: user_account.clone(),
max_bytes: 1_000_000,
duration: 100,
price_per_byte: 1,
replica_params: None,
},
)
.await?;
assert!(signed.terms.nonce > 0, "provider should allocate a nonce");
println!(" Terms signed (nonce={})", signed.terms.nonce);

// 2. Redeem the signed terms on-chain to open a bucket + primary agreement.
println!("Establishing storage agreement on-chain...");
let mut admin = AdminClient::new(chain_config.clone(), user_ss58.clone())?;
admin.connect().await?;
admin.set_signer(user_keypair)?;
let bucket_id = admin
.establish_storage_agreement(provider_ss58, signed.terms, signed.signature)
.await?;
println!(
" ✓ Data uploaded with root: 0x{}",
hex::encode(data_root.as_bytes())
);

println!(" Committing to chain...");
let commitment = user_client.commit(bucket_id, vec![data_root]).await?;
println!(" ✓ Committed with MMR root: {}\n", commitment.mmr_root);

// ═════════════════════════════════════════════════════════════════════════
// Step 5: Data Verification
// ═════════════════════════════════════════════════════════════════════════
println!("✅ Step 5: Data Verification");

println!(" Downloading data...");
let retrieved_data = user_client
.download(&data_root, 0, data.len() as u64)
println!(" Bucket #{bucket_id} created");

// 3. Upload + download against the provider node, verifying integrity.
println!("Uploading and downloading data...");
let user_config = ClientConfig {
chain_ws_url: chain_ws.to_string(),
provider_urls: vec![provider_url.to_string()],
..Default::default()
};
let user = StorageUserClient::new(user_config)?;
let data = b"hello e2e".to_vec();
let data_root = user
.upload(bucket_id, &data, ChunkingStrategy::default())
.await?;
println!(" ✓ Downloaded {} bytes", retrieved_data.len());
let downloaded = user.download(&data_root, 0, data.len() as u64).await?;
assert_eq!(downloaded, data, "downloaded bytes must match upload");
println!(" Uploaded {} bytes and verified download", data.len());

if retrieved_data == data {
println!(" ✓ Data integrity verified!");
} else {
println!(" ✗ Data mismatch!");
}
println!();

// ═════════════════════════════════════════════════════════════════════════
// Step 6: Challenge by Third Party
// ═════════════════════════════════════════════════════════════════════════
println!("🎯 Step 6: Data Integrity Challenge");

let challenger_account = "5DAAnrj7VHTznn2AWBemMuyBwZWs6FNFjdyVXUeYum3PTXFy";
let challenger_client = ChallengerClient::new(config.clone(), challenger_account.to_string())?;

println!(" Challenger analyzing provider...");
let analysis = challenger_client
.analyze_provider(bucket_id, provider_account.to_string())
.await?;
println!(" Provider reputation: {}", analysis.reputation);
println!(" Recommendation: {:?}", analysis.recommendation);

println!(" Creating challenge...");
let challenge_id = challenger_client
.challenge_checkpoint(
bucket_id,
provider_account.to_string(),
0, // leaf_index
0, // chunk_index
)
.await?;
println!(
" ✓ Challenge created: deadline={}, index={}",
challenge_id.deadline, challenge_id.index
);
println!(" Provider must respond within challenge timeout!\n");

// ═════════════════════════════════════════════════════════════════════════
// Step 7: Challenge Response by Provider
// ═════════════════════════════════════════════════════════════════════════
println!("🛡️ Step 7: Provider Response");

println!(" Provider fetching challenged data from local storage...");
// In a real scenario, provider would:
// 1. Load chunk from local storage
// 2. Generate Merkle proof
// 3. Generate MMR proof
// 4. Submit response on-chain

println!(" ✓ Provider would respond with proofs\n");

// ═════════════════════════════════════════════════════════════════════════
// Step 8: Monitoring & Analytics
// ═════════════════════════════════════════════════════════════════════════
println!("📊 Step 8: Monitoring & Analytics");

println!(" Provider statistics:");
let provider_stats = provider_client.get_stats().await?;
println!(
" - Total agreements: {}",
provider_stats.agreements_total
);
println!(
" - Challenges received: {}",
provider_stats.challenges_received
);
println!(" - Reputation: {}/100", provider_stats.reputation);

println!("\n Challenger statistics:");
let challenge_stats = challenger_client.get_challenge_stats().await?;
println!(
" - Total challenges: {}",
challenge_stats.total_challenges
);
println!(
" - Success rate: {:.1}%",
if challenge_stats.total_challenges > 0 {
(challenge_stats.successful_challenges as f64 / challenge_stats.total_challenges as f64)
* 100.0
} else {
0.0
}
);
println!(
" - Total earnings: {} tokens",
challenge_stats.total_earnings
);

println!("\n=== Workflow Complete ===");
println!("\n💡 Key Takeaways:");
println!(" • Providers stake collateral and offer storage");
println!(" • Admins create buckets and manage agreements");
println!(" • Users upload data with cryptographic guarantees");
println!(" • Challengers enforce accountability");
println!(" • All operations are verifiable on-chain");

println!("=== Done ===");
Ok(())
}
Loading
Loading