Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

target/
target-docker/
cache/
out/

# Dependencies that are explicitly vendored for offline builds.
# .vendor/ stores a snapshot of crates.io deps; re-generate via:
Expand Down
1 change: 1 addition & 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 Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ rusqlite = { version = "0.31", features = ["bundled"] }
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
mpp = { version = "0.10.4", default-features = false, features = ["client", "tempo", "reqwest-rustls-tls"] }
url = "2"
humantime = "2"
humantime-serde = "1"
tempfile = "3"
futures = "0.3"
Expand Down
8 changes: 8 additions & 0 deletions crates/bloom-daemon/src/ipc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::UNIX_EPOCH;

use base64::Engine as _;
use base64::engine::general_purpose::STANDARD as B64;
Expand Down Expand Up @@ -1334,12 +1335,19 @@ fn entry_to_json(e: &Entry) -> Value {
EntryKind::File => "file",
EntryKind::Symlink => "symlink",
};
let modified_ms = e.modified.map(|modified| {
modified
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis()
});
json!({
"name": e.name,
"kind": kind,
"size": e.size,
"mode": e.mode,
"link_target": e.link_target,
"modified_ms": modified_ms,
})
}

Expand Down
19 changes: 19 additions & 0 deletions crates/bloom-evm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -331,10 +331,29 @@ impl ChainClient {
Ok(self.primary.get_transaction_by_hash(hash).await?)
}

pub async fn raw_tx_by_hash(
&self,
hash: B256,
) -> Result<Option<serde_json::Value>, ChainError> {
Ok(self
.primary
.client()
.request("eth_getTransactionByHash", (format!("{hash:#x}"),))
.await?)
}

pub async fn receipt(&self, hash: B256) -> Result<Option<TransactionReceipt>, ChainError> {
Ok(self.primary.get_transaction_receipt(hash).await?)
}

pub async fn raw_receipt(&self, hash: B256) -> Result<Option<serde_json::Value>, ChainError> {
Ok(self
.primary
.client()
.request("eth_getTransactionReceipt", (format!("{hash:#x}"),))
.await?)
}

/// Re-execute a *reverted* transaction via `eth_call` at the block it
/// was mined in, to capture revert returndata. Returns:
///
Expand Down
32 changes: 31 additions & 1 deletion crates/bloom-mount/src/adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
use std::collections::{BTreeMap, HashMap};
use std::num::NonZeroUsize;
use std::sync::Arc;
use std::time::{Duration, Instant, UNIX_EPOCH};
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};

use async_trait::async_trait;
use bytes::Bytes;
Expand Down Expand Up @@ -239,6 +239,15 @@ fn epoch_ts() -> Timestamp {
}
}

/// Build a `Timestamp` from a system time, falling back to the Unix epoch.
fn system_time_to_ts(time: SystemTime) -> Timestamp {
let dur = time.duration_since(UNIX_EPOCH).unwrap_or_default();
Timestamp {
seconds: dur.as_secs() as i64,
nanos: dur.subsec_nanos(),
}
}

fn stable_attrs(object_type: ObjectType, fileid: u64) -> Attrs {
let mut attrs = Attrs::new(object_type, fileid);
let ts = epoch_ts();
Expand Down Expand Up @@ -278,6 +287,10 @@ fn entry_to_attrs(path: &VfsPath, e: &Entry, size: u64) -> Attrs {
a.size = size;
a.space_used = size;
a.mode = e.mode;
let ts = system_time_to_ts(e.modified.unwrap_or_else(SystemTime::now));
a.mtime = ts;
a.atime = ts;
a.ctime = ts;
if matches!(e.kind, EntryKind::File | EntryKind::Symlink) {
a.change = file_change_now();
}
Expand Down Expand Up @@ -750,6 +763,10 @@ impl FileSystem for BloomFs {
BloomHandle::Root => {
let mut a = stable_attrs(ObjectType::Directory, fileid_for(&VfsPath::root()));
a.mode = 0o755;
let ts = system_time_to_ts(SystemTime::now());
a.mtime = ts;
a.atime = ts;
a.ctime = ts;
a.change = self.dir_change(&VfsPath::root()).await;
Ok(a)
}
Expand Down Expand Up @@ -2160,6 +2177,19 @@ mod tests {
assert_eq!(attrs.mode & 0o777, 0o555);
}

#[test]
fn entry_to_attrs_uses_entry_modified_time() {
let modified = SystemTime::UNIX_EPOCH + Duration::from_secs(1_700_000_000);
let entry = Entry::file("artifact.json").with_modified(modified);
let path = VfsPath::parse("/requests/pending/req_1/artifact.json").unwrap();

let attrs = entry_to_attrs(&path, &entry, 0);

assert_eq!(attrs.mtime.seconds, 1_700_000_000);
assert_eq!(attrs.ctime.seconds, 1_700_000_000);
assert_eq!(attrs.atime.seconds, 1_700_000_000);
}

/// Bug #5: ACCESS strips MODIFY/EXTEND/DELETE for a read-only path
/// so the kernel doesn't cache a false-positive write capability.
#[tokio::test]
Expand Down
104 changes: 103 additions & 1 deletion crates/bloom-vfs/src/handler.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
//! The Handler trait — every top-level subtree implements it.

use std::time::Duration;
use std::fs;
use std::path::Path;
use std::time::{Duration, SystemTime};

use async_trait::async_trait;
use thiserror::Error;
Expand All @@ -25,6 +27,9 @@ pub struct Entry {
pub mode: u32,
/// For symlinks, the target.
pub link_target: Option<String>,
/// Optional artifact modification time. Mount adapters surface this as
/// mtime/ctime/atime when a handler knows the backing artifact time.
pub modified: Option<SystemTime>,
}

impl Entry {
Expand All @@ -35,6 +40,7 @@ impl Entry {
size: 0,
mode: 0o755,
link_target: None,
modified: None,
}
}
/// Build a read-only file entry (mode 0o444). This is the right
Expand All @@ -57,6 +63,7 @@ impl Entry {
size: 0,
mode: 0o444,
link_target: None,
modified: None,
}
}
/// Build a writable file entry (mode 0o644). Use for the small set
Expand All @@ -68,6 +75,7 @@ impl Entry {
size: 0,
mode: 0o644,
link_target: None,
modified: None,
}
}
/// Build an executable read-only file entry (mode 0o555). This is used
Expand All @@ -80,6 +88,7 @@ impl Entry {
size: 0,
mode: 0o555,
link_target: None,
modified: None,
}
}
pub fn symlink(name: &str, target: &str) -> Self {
Expand All @@ -89,8 +98,66 @@ impl Entry {
size: 0,
mode: 0o777,
link_target: Some(target.into()),
modified: None,
}
}

pub fn with_modified(mut self, modified: SystemTime) -> Self {
self.modified = Some(modified);
self
}

pub fn with_modified_ms(self, modified_ms: u128) -> Self {
if modified_ms > u64::MAX as u128 {
return self;
}
self.with_modified(SystemTime::UNIX_EPOCH + Duration::from_millis(modified_ms as u64))
}

pub fn with_fs_metadata(mut self, metadata: &fs::Metadata) -> Self {
if self.kind == EntryKind::File {
self.size = metadata.len();
}
if let Ok(modified) = metadata.modified() {
self.modified = Some(modified);
}
self
}
}

pub fn entry_from_fs_metadata(name: &str, kind: EntryKind, metadata: &fs::Metadata) -> Entry {
match kind {
EntryKind::Dir => Entry::dir(name),
EntryKind::File => Entry::file(name),
EntryKind::Symlink => Entry::symlink(name, ""),
}
.with_fs_metadata(metadata)
}

pub fn entry_for_fs_path(
path: impl AsRef<Path>,
name: &str,
kind: EntryKind,
) -> Result<Entry, HandlerError> {
let metadata = fs::metadata(path)?;
Ok(entry_from_fs_metadata(name, kind, &metadata))
}

pub fn entry_from_fs_dir_entry(
dir_entry: &fs::DirEntry,
name: &str,
kind: EntryKind,
) -> Result<Entry, HandlerError> {
let metadata = dir_entry.metadata()?;
Ok(entry_from_fs_metadata(name, kind, &metadata))
}

pub fn fs_path_modified(path: impl AsRef<Path>) -> Result<Option<SystemTime>, HandlerError> {
match fs::metadata(path) {
Ok(metadata) => Ok(metadata.modified().ok()),
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(None),
Err(err) => Err(err.into()),
}
}

#[derive(Debug, Error)]
Expand Down Expand Up @@ -207,6 +274,7 @@ mod tests {
assert_eq!(e.size, 0);
assert_eq!(e.mode, 0o755);
assert!(e.link_target.is_none());
assert!(e.modified.is_none());
}

#[test]
Expand All @@ -218,6 +286,7 @@ mod tests {
assert_eq!(a.mode, b.mode);
assert_eq!(a.size, b.size);
assert_eq!(a.link_target, b.link_target);
assert_eq!(a.modified, b.modified);
}

#[test]
Expand All @@ -228,6 +297,7 @@ mod tests {
assert_eq!(e.size, 0);
assert_eq!(e.mode, 0o444);
assert!(e.link_target.is_none());
assert!(e.modified.is_none());
}

#[test]
Expand All @@ -238,6 +308,7 @@ mod tests {
assert_eq!(e.size, 0);
assert_eq!(e.mode, 0o644);
assert!(e.link_target.is_none());
assert!(e.modified.is_none());
}

#[test]
Expand All @@ -248,6 +319,7 @@ mod tests {
assert_eq!(e.size, 0);
assert_eq!(e.mode, 0o555);
assert!(e.link_target.is_none());
assert!(e.modified.is_none());
}

#[test]
Expand All @@ -258,6 +330,36 @@ mod tests {
assert_eq!(e.size, 0);
assert_eq!(e.mode, 0o777);
assert_eq!(e.link_target.as_deref(), Some("../ethereum"));
assert!(e.modified.is_none());
}

#[test]
fn with_modified_records_artifact_time() {
let modified = SystemTime::UNIX_EPOCH + Duration::from_secs(123);
let e = Entry::file("artifact.json").with_modified(modified);
assert_eq!(e.modified, Some(modified));
}

#[test]
fn with_modified_ms_records_epoch_milliseconds() {
let e = Entry::dir("req").with_modified_ms(1_700_000_000_123);
assert_eq!(
e.modified,
Some(SystemTime::UNIX_EPOCH + Duration::from_millis(1_700_000_000_123))
);
}

#[test]
fn with_fs_metadata_records_file_size_and_modified_time() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("artifact.json");
std::fs::write(&path, b"hello").unwrap();
let metadata = std::fs::metadata(&path).unwrap();

let entry = Entry::file("artifact.json").with_fs_metadata(&metadata);

assert_eq!(entry.size, 5);
assert_eq!(entry.modified, metadata.modified().ok());
}

#[test]
Expand Down
Loading
Loading