Skip to content

Commit a848927

Browse files
authored
feat: Add S3 implementation for object_store (matter-labs#3664)
## What ❔ Add S3 implementation for object_store. <!-- What are the changes this PR brings about? --> <!-- Example: This PR adds a PR template to the repo. --> <!-- (For bigger PRs adding more context is appreciated) --> ## Why ❔ To remove hard dependency on GCS, add possibility to use any S3-like storage for snapshots and provers. <!-- Why are these changes done? What goal do they contribute to? What are the principles behind them? --> <!-- The `Why` has to be clear to non-Matter Labs entities running their own ZK Chain --> <!-- Example: PR templates ensure PR reviewers, observers, and future iterators are in context about the evolution of repos. --> ## Is this a breaking change? - [ ] Yes - [x] No ## Operational changes <!-- Any config changes? Any new flags? Any changes to any scripts? --> <!-- Please add anything that non-Matter Labs entities running their own ZK Chain may need to know --> ## Checklist <!-- Check your PR fulfills the following items. --> <!-- For draft PRs check the boxes as you complete them. --> - [x] PR title corresponds to the body of PR (we generate changelog entries from PRs). - [x] Tests for the changes have been added / updated. - [x] Documentation comments have been added / updated. - [x] Code has been formatted via `zkstack dev fmt` and `zkstack dev lint`. ref ZKD-2415
1 parent 5986708 commit a848927

File tree

15 files changed

+1487
-26
lines changed

15 files changed

+1487
-26
lines changed

core/Cargo.lock

Lines changed: 486 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

core/Cargo.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,11 @@ anyhow = "1"
108108
assert_matches = "1.5"
109109
async-trait = "0.1"
110110
async-recursion = "1"
111+
aws-config = { version = "1.1.7", default-features = false, features = [
112+
"behavior-version-latest",
113+
] }
114+
aws-runtime = "1.5.5"
115+
aws-sdk-s3 = "1.76.0"
111116
axum = "0.7.5"
112117
backon = "0.4.4"
113118
bigdecimal = "0.4.5"
@@ -192,7 +197,7 @@ tower-http = "0.5.2"
192197
tracing = "0.1"
193198
tracing-subscriber = "0.3"
194199
tracing-opentelemetry = "0.25.0"
195-
time = "0.3.36" # Has to be same as used by `tracing-subscriber`
200+
time = "0.3.36" # Has to be same as used by `tracing-subscriber`
196201
url = "2"
197202
web3 = "0.19.0"
198203
yab = "0.1.0"

core/lib/config/src/configs/object_store.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,17 @@ pub enum ObjectStoreMode {
3737
bucket_base_url: String,
3838
gcs_credential_file_path: String,
3939
},
40+
S3AnonymousReadOnly {
41+
bucket_base_url: String,
42+
endpoint: Option<String>,
43+
region: Option<String>,
44+
},
45+
S3WithCredentialFile {
46+
bucket_base_url: String,
47+
s3_credential_file_path: String,
48+
endpoint: Option<String>,
49+
region: Option<String>,
50+
},
4051
FileBacked {
4152
file_backed_base_path: String,
4253
},

core/lib/object_store/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,13 @@ tokio = { workspace = true, features = ["full"] }
2828
tracing.workspace = true
2929
prost.workspace = true
3030
reqwest.workspace = true
31+
aws-config.workspace = true
32+
aws-runtime.workspace = true
33+
aws-sdk-s3.workspace = true
3134

3235
[dev-dependencies]
3336
assert_matches.workspace = true
3437
tempfile.workspace = true
38+
clap = { workspace = true, features = ["derive"] }
39+
tracing-subscriber = { workspace = true, features = ["env-filter"] }
40+
zksync_core_leftovers.workspace = true

core/lib/object_store/README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,25 @@ Besides the lower-level storage abstraction, the crate provides high-level types
1515
objects. Prefer using these methods whenever possible.
1616

1717
[configuration]: ../config
18+
19+
## S3
20+
21+
S3 implementation can be used to access different storages. Here is list of recommended values.
22+
23+
### GCS
24+
25+
See [details](https://cloud.google.com/storage/docs/authentication/managing-hmackeys)
26+
27+
- Endpoint: `https://storage.googleapis.com`
28+
- Region: `us` or `auto`
29+
- Access Key ID: Access key
30+
- Secret Access Key: Corresponding secret
31+
32+
### R2
33+
34+
See [details](https://developers.cloudflare.com/r2/api/s3/tokens/)
35+
36+
- Endpoint: `https://<ACCOUNT_ID>.r2.cloudflarestorage.com`
37+
- Region: `auto` or `us-east-1`
38+
- Access Key ID: The id of the API token
39+
- Secret Access Key: The SHA-256 hash of the API token value
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
core_object_store:
2+
s3_with_credential_file:
3+
bucket_base_url: some-bucket
4+
s3_credential_file_path: credentials.example
5+
region: us
6+
endpoint: https://storage.googleapis.com
7+
max_retries: 2
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[default]
2+
aws_access_key_id = <Your access key>
3+
aws_secret_access_key = <Your secret key>
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
//! Object store manual connection test.
2+
3+
use anyhow::Context as _;
4+
use clap::Parser;
5+
use tracing::level_filters::LevelFilter;
6+
use tracing_subscriber::EnvFilter;
7+
use zksync_core_leftovers::temp_config_store::load_general_config;
8+
use zksync_object_store::ObjectStoreFactory;
9+
use zksync_types::{
10+
snapshots::{SnapshotStorageLogsChunk, SnapshotStorageLogsStorageKey},
11+
L1BatchNumber, H256,
12+
};
13+
14+
/// CLI for testing Object Storage using core_object_store config field.
15+
#[derive(Debug, Parser)]
16+
struct Cli {
17+
/// Number of updates to perform.
18+
#[arg(name = "config", alias = "c")]
19+
config_path: Option<std::path::PathBuf>,
20+
}
21+
22+
impl Cli {
23+
fn init_logging() {
24+
tracing_subscriber::fmt()
25+
.pretty()
26+
.with_env_filter(
27+
EnvFilter::builder()
28+
.with_default_directive(LevelFilter::INFO.into())
29+
.from_env_lossy()
30+
.add_directive("zksync_object_store=trace".parse().unwrap()),
31+
)
32+
.init();
33+
}
34+
35+
#[tokio::main]
36+
async fn run(self) -> anyhow::Result<()> {
37+
Self::init_logging();
38+
tracing::info!("Launched with options: {self:?}");
39+
let general_config = load_general_config(self.config_path).context("general config")?;
40+
41+
let object_store_config = general_config
42+
.core_object_store
43+
.context("core object store config")?;
44+
let object_store = ObjectStoreFactory::new(object_store_config)
45+
.create_store()
46+
.await
47+
.context("failed to create object store")?;
48+
49+
let key = SnapshotStorageLogsStorageKey {
50+
l1_batch_number: L1BatchNumber(123456),
51+
chunk_id: 4321,
52+
};
53+
let snapshot_chunk = SnapshotStorageLogsChunk::<H256> {
54+
storage_logs: vec![],
55+
};
56+
57+
object_store
58+
.put::<SnapshotStorageLogsChunk>(key, &snapshot_chunk)
59+
.await?;
60+
let result = object_store.get::<SnapshotStorageLogsChunk>(key).await?;
61+
tracing::info!("Result: {result:?}");
62+
object_store.remove::<SnapshotStorageLogsChunk>(key).await?;
63+
Ok(())
64+
}
65+
}
66+
67+
fn main() -> anyhow::Result<()> {
68+
Cli::parse().run()
69+
}

core/lib/object_store/src/factory.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use crate::{
1010
mirror::MirroringObjectStore,
1111
raw::{ObjectStore, ObjectStoreError},
1212
retries::StoreWithRetries,
13+
s3::{S3Store, S3StoreAuthMode},
1314
};
1415

1516
/// Factory of [`ObjectStore`]s that caches the store instance once it's created. Used mainly for legacy reasons.
@@ -98,6 +99,42 @@ impl ObjectStoreFactory {
9899
Self::wrap_mirroring(store, config.local_mirror_path.as_ref()).await
99100
}
100101

102+
ObjectStoreMode::S3WithCredentialFile {
103+
bucket_base_url,
104+
s3_credential_file_path,
105+
endpoint,
106+
region,
107+
} => {
108+
let store = StoreWithRetries::try_new(config.max_retries, || {
109+
S3Store::new(
110+
S3StoreAuthMode::AuthenticatedWithCredentialFile(
111+
s3_credential_file_path.clone(),
112+
),
113+
bucket_base_url.clone(),
114+
endpoint.clone(),
115+
region.clone(),
116+
)
117+
})
118+
.await?;
119+
Self::wrap_mirroring(store, config.local_mirror_path.as_ref()).await
120+
}
121+
ObjectStoreMode::S3AnonymousReadOnly {
122+
bucket_base_url,
123+
endpoint,
124+
region,
125+
} => {
126+
let store = StoreWithRetries::try_new(config.max_retries, || {
127+
S3Store::new(
128+
S3StoreAuthMode::Anonymous,
129+
bucket_base_url.clone(),
130+
endpoint.clone(),
131+
region.clone(),
132+
)
133+
})
134+
.await?;
135+
Self::wrap_mirroring(store, config.local_mirror_path.as_ref()).await
136+
}
137+
101138
ObjectStoreMode::FileBacked {
102139
file_backed_base_path,
103140
} => {

core/lib/object_store/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ mod mock;
3232
mod objects;
3333
mod raw;
3434
mod retries;
35+
mod s3;
3536

3637
// Re-export `bincode` crate so that client binaries can conveniently use it.
3738
pub use bincode;

0 commit comments

Comments
 (0)