Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement Hermes IPFS runtime extension functionality #272

Merged
merged 61 commits into from
Jul 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
b2ac4ce
feat(wit): Add bindings for hermes-ipfs
saibatizoku Jun 20, 2024
de585f1
feat(wit): add exports_hermes_ipfs_event_on_topic to stub-module.c
saibatizoku Jun 21, 2024
8f16429
feat(wip): add stubbed ipfs::api::Host implementation and on-topic event
saibatizoku Jun 21, 2024
6dced77
fix: update integration test modules
saibatizoku Jun 21, 2024
f3e813d
feat: add ipfs runtime context
saibatizoku Jun 21, 2024
75ebb5b
fix: lints
saibatizoku Jun 21, 2024
4cd6153
fix: update rust integration test modules
saibatizoku Jun 21, 2024
34631bd
chore: update project dictionary
saibatizoku Jun 21, 2024
435a960
fix: pubsub-message includes topic
saibatizoku Jun 21, 2024
d43b7af
Merge branch 'main' into feat/hermes-ipfs-wasi
stevenj Jun 27, 2024
ef69e0f
fix: update IPFS WIT bindings
saibatizoku Jul 1, 2024
fcb4f39
Merge remote-tracking branch 'origin/main' into feat/hermes-ipfs-wasi
saibatizoku Jul 1, 2024
4f06722
fix: add errors and types, peer-evict function
saibatizoku Jul 1, 2024
0a3935f
fix: typo
saibatizoku Jul 1, 2024
5ce22fe
chore: format code
saibatizoku Jul 1, 2024
52b11bc
chore: add hermes-ipfs to deps
saibatizoku Jul 1, 2024
3ae88b1
chore: add docs to show how to start ipfs node
saibatizoku Jul 1, 2024
a7ae63c
feat: add methods to publish/get/pin files
saibatizoku Jul 1, 2024
2afb295
feat: add unsubscribe method, return friendlier types
saibatizoku Jul 1, 2024
78bc21b
feat(wip): implement ipfs methods
saibatizoku Jul 1, 2024
8a9f831
chore: format code and remove unused dep
saibatizoku Jul 1, 2024
9815a59
fix: add peer_evict method to Host implementation
saibatizoku Jul 1, 2024
4d7ba12
Merge remote-tracking branch 'origin/feat/hermes-ipfs-wasi' into feat…
saibatizoku Jul 1, 2024
0502d01
chore: format code
saibatizoku Jul 1, 2024
ed82e81
fix: remove dup function
saibatizoku Jul 1, 2024
5f997b1
feat: keep track of ipfs files belonging to apps
saibatizoku Jul 2, 2024
af0f85f
Merge remote-tracking branch 'origin/main' into feat/hermes-ipfs-wasi
saibatizoku Jul 2, 2024
e6c7f70
Merge branch 'feat/hermes-ipfs-wasi' into feat/hermes-ipfs-rte
saibatizoku Jul 2, 2024
3b64188
fix: remove unused import, update event.wit
saibatizoku Jul 2, 2024
f114309
Merge remote-tracking branch 'origin/feat/hermes-ipfs-wasi' into feat…
saibatizoku Jul 2, 2024
0449597
fix: remove unused import, update event.wit
saibatizoku Jul 2, 2024
f46f6bc
Merge branch 'feat/hermes-ipfs-wasi' into feat/hermes-ipfs-rte
saibatizoku Jul 2, 2024
bb22302
Merge remote-tracking branch 'origin/main' into feat/hermes-ipfs-rte
saibatizoku Jul 3, 2024
9d75ead
fix: update IPFS WIT bindings
saibatizoku Jul 4, 2024
8117b1e
feat: implement IPFS api
saibatizoku Jul 4, 2024
0a40940
feat: add pubsub-publish to IPFS wasi definitions
saibatizoku Jul 9, 2024
fb9d5cd
feat: add integration testing for IPFS runtime extension
saibatizoku Jul 9, 2024
c171e37
fix: update rte and add ipfs tests to earthly target
saibatizoku Jul 9, 2024
9697b45
fix: update hermes-ipfs example to handle content already pinned
saibatizoku Jul 9, 2024
01666d7
Merge remote-tracking branch 'origin/main' into feat/hermes-ipfs-rte
saibatizoku Jul 9, 2024
58f7f3a
fix: remove re-exports
saibatizoku Jul 9, 2024
71cf59b
fix: type_lenght_limit to build doctests
saibatizoku Jul 9, 2024
2c5b383
chore: refactor 'mod task' out of state
saibatizoku Jul 9, 2024
9e42e4e
fix: types for pubsub-publishing
saibatizoku Jul 10, 2024
3c8bb57
fix: add publishing implementation
saibatizoku Jul 10, 2024
52ec9ca
chore: update docs
saibatizoku Jul 10, 2024
0304261
wip: publish
saibatizoku Jul 10, 2024
98f88ba
fix: cleanup code
saibatizoku Jul 10, 2024
05f24b8
fix: refactor ipfs api into its own module
saibatizoku Jul 10, 2024
6344863
fix: simplify type usage
saibatizoku Jul 10, 2024
4edddea
fix: use new type for gossip message id
saibatizoku Jul 11, 2024
94ed756
fix: spelling
saibatizoku Jul 11, 2024
8be50f1
fix: use HashSet instead of DashSet
saibatizoku Jul 11, 2024
c6c9f92
fix: broken integration tests
saibatizoku Jul 11, 2024
9423fa4
Merge remote-tracking branch 'origin/main' into feat/hermes-ipfs-rte
saibatizoku Jul 11, 2024
89b9bc2
fix: Update hermes/bin/src/runtime_extensions/hermes/ipfs/state/api.rs
saibatizoku Jul 11, 2024
26da0d8
chore: update TODO comments with issue url
saibatizoku Jul 11, 2024
7ddf778
fix: Update wasm/integration-test/ipfs/src/lib.rs
saibatizoku Jul 11, 2024
6837061
chore: minor fix to trigger ci
saibatizoku Jul 11, 2024
c9b846a
fix: earthly integration tests in Rust use --keep-ts flag
saibatizoku Jul 12, 2024
362d591
Merge remote-tracking branch 'origin/main' into feat/hermes-ipfs-rte
saibatizoku Jul 12, 2024
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
1 change: 1 addition & 0 deletions .config/dictionaries/project.dic
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ genhtml
GETFL
getres
gmtime
gossipsub
happ
hardano
hasher
Expand Down
3 changes: 3 additions & 0 deletions hermes/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ pallas-hardano = { git = "https://github.com/input-output-hk/catalyst-pallas.git

cardano-chain-follower = { path = "crates/cardano-chain-follower", version = "0.0.1" }

hermes-ipfs = { path = "crates/hermes-ipfs", version = "0.0.1" }

wasmtime = "20.0.2"
rusty_ulid = "2.0.0"
anyhow = "1.0.71"
Expand Down Expand Up @@ -103,5 +105,6 @@ ed25519-dalek = "2.1.1"
x509-cert = "0.2.5"
coset = "0.3.7"
libipld = "0.16.0"
libp2p = "0.53.2"
rust-ipfs = "0.11.19"
rustyline-async = "0.4.2"
1 change: 1 addition & 0 deletions hermes/Earthfile
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ test-wasm-integration:
COPY ../wasm/integration-test/crypto+build/crypto.wasm ../wasm/test-components/
COPY ../wasm/integration-test/cardano+build/cardano.wasm ../wasm/test-components/
COPY ../wasm/integration-test/hashing+build/hashing.wasm ../wasm/test-components/
COPY ../wasm/integration-test/ipfs+build/ipfs.wasm ../wasm/test-components/
COPY ../wasm/integration-test/localtime+build/localtime.wasm ../wasm/test-components/
COPY ../wasm/integration-test/logger+build/logger.wasm ../wasm/test-components/
COPY ../wasm/integration-test/sqlite+build/sqlite.wasm ../wasm/test-components/
Expand Down
1 change: 1 addition & 0 deletions hermes/bin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ sha2 = { workspace = true }
ed25519-dalek = { workspace = true, features = ["pem"] }
x509-cert = { workspace = true, features = ["pem"] }
coset = { workspace = true }
hermes-ipfs = { workspace = true }

[build-dependencies]
build-info-build = { workspace = true }
Expand Down
1 change: 1 addition & 0 deletions hermes/bin/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Intentionally empty
//! This file exists, so that doc tests can be used inside binary crates.
#![type_length_limit = "45079293105"]

pub mod app;
#[allow(dead_code)]
Expand Down
9 changes: 3 additions & 6 deletions hermes/bin/src/runtime_extensions/hermes/ipfs/event.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
//! Hermes IPFS runtime extension event handler implementation.
use crate::{
event::HermesEventPayload,
runtime_extensions::bindings::hermes::ipfs::api::{PubsubMessage, PubsubTopic},
event::HermesEventPayload, runtime_extensions::bindings::hermes::ipfs::api::PubsubMessage,
};

/// Event handler for the `on-topic` event.
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub(crate) struct OnTopicEvent {
/// Topic
pub(crate) topic: PubsubTopic,
/// Message
/// Topic message received.
pub(crate) message: PubsubMessage,
}

Expand Down
52 changes: 35 additions & 17 deletions hermes/bin/src/runtime_extensions/hermes/ipfs/host.rs
Original file line number Diff line number Diff line change
@@ -1,40 +1,58 @@
//! IPFS host implementation for WASM runtime.

use super::state::{
hermes_ipfs_add_file, hermes_ipfs_content_validate, hermes_ipfs_evict_peer,
hermes_ipfs_get_dht_value, hermes_ipfs_get_file, hermes_ipfs_pin_file, hermes_ipfs_publish,
hermes_ipfs_put_dht_value, hermes_ipfs_subscribe,
};
use crate::{
runtime_context::HermesRuntimeContext,
runtime_extensions::bindings::hermes::ipfs::api::{
DhtKey, DhtValue, Errno, Host, IpfsContent, IpfsPath, PeerId, PubsubTopic,
DhtKey, DhtValue, Errno, Host, IpfsContent, IpfsFile, IpfsPath, MessageData, MessageId,
PeerId, PubsubTopic,
},
};

impl Host for HermesRuntimeContext {
fn file_add(&mut self, _contents: IpfsContent) -> wasmtime::Result<Result<IpfsPath, Errno>> {
todo!();
fn file_add(&mut self, contents: IpfsFile) -> wasmtime::Result<Result<IpfsPath, Errno>> {
let path: IpfsPath = hermes_ipfs_add_file(self.app_name(), contents)?.to_string();
Ok(Ok(path))
}

fn file_get(&mut self, _path: IpfsPath) -> wasmtime::Result<Result<IpfsContent, Errno>> {
todo!();
fn file_get(&mut self, path: IpfsPath) -> wasmtime::Result<Result<IpfsFile, Errno>> {
let contents = hermes_ipfs_get_file(self.app_name(), &path)?;
Ok(Ok(contents))
}

fn file_pin(&mut self, _ipfs_path: IpfsPath) -> wasmtime::Result<Result<bool, Errno>> {
todo!();
fn file_pin(&mut self, ipfs_path: IpfsPath) -> wasmtime::Result<Result<bool, Errno>> {
Ok(hermes_ipfs_pin_file(self.app_name(), ipfs_path))
}

fn dht_put(
&mut self, _key: DhtKey, _contents: IpfsContent,
) -> wasmtime::Result<Result<bool, Errno>> {
todo!();
fn dht_put(&mut self, key: DhtKey, value: DhtValue) -> wasmtime::Result<Result<bool, Errno>> {
Ok(hermes_ipfs_put_dht_value(self.app_name(), key, value))
}

fn dht_get(&mut self, key: DhtKey) -> wasmtime::Result<Result<DhtValue, Errno>> {
Ok(hermes_ipfs_get_dht_value(self.app_name(), key))
}

fn dht_get(&mut self, _key: DhtKey) -> wasmtime::Result<Result<DhtValue, Errno>> {
todo!();
fn pubsub_publish(
&mut self, topic: PubsubTopic, message: MessageData,
) -> wasmtime::Result<Result<MessageId, Errno>> {
Ok(hermes_ipfs_publish(self.app_name(), &topic, message))
}

fn pubsub_subscribe(&mut self, _topic: PubsubTopic) -> wasmtime::Result<Result<bool, Errno>> {
todo!();
fn pubsub_subscribe(&mut self, topic: PubsubTopic) -> wasmtime::Result<Result<bool, Errno>> {
Ok(hermes_ipfs_subscribe(self.app_name(), topic))
}

fn ipfs_content_validate(
&mut self, content: IpfsContent,
) -> wasmtime::Result<Result<bool, Errno>> {
Ok(Ok(hermes_ipfs_content_validate(self.app_name(), &content)))
}

fn peer_evict(&mut self, _peer: PeerId) -> wasmtime::Result<Result<bool, Errno>> {
todo!();
fn peer_evict(&mut self, peer: PeerId) -> wasmtime::Result<Result<bool, Errno>> {
Ok(hermes_ipfs_evict_peer(self.app_name(), peer))
}
}
1 change: 1 addition & 0 deletions hermes/bin/src/runtime_extensions/hermes/ipfs/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Hermes IPFS runtime extension.
mod event;
mod host;
mod state;

/// Advise Runtime Extensions of a new context
pub(crate) fn new_context(_ctx: &crate::runtime_context::HermesRuntimeContext) {}
125 changes: 125 additions & 0 deletions hermes/bin/src/runtime_extensions/hermes/ipfs/state/api.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
//! Hermes IPFS State API
use super::{is_valid_dht_content, is_valid_pubsub_content, HERMES_IPFS_STATE};
use crate::{
app::HermesAppName,
runtime_extensions::bindings::hermes::ipfs::api::{
DhtKey, DhtValue, Errno, IpfsContent, IpfsFile, IpfsPath, MessageData, MessageId, PeerId,
PubsubTopic,
},
};

/// Add File to IPFS
pub(crate) fn hermes_ipfs_add_file(
app_name: &HermesAppName, contents: IpfsFile,
) -> Result<IpfsPath, Errno> {
tracing::debug!(app_name = %app_name, "adding IPFS file");
let ipfs_path = HERMES_IPFS_STATE.file_add(contents)?;
tracing::debug!(app_name = %app_name, path = %ipfs_path, "added IPFS file");
HERMES_IPFS_STATE
.apps
.added_file(app_name.clone(), ipfs_path.clone());
Ok(ipfs_path)
}

/// Validate IPFS Content from DHT or `PubSub`
pub(crate) fn hermes_ipfs_content_validate(
app_name: &HermesAppName, content: &IpfsContent,
) -> bool {
match content {
IpfsContent::Dht((k, v)) => {
let key_str = format!("{k:x?}");
let is_valid = is_valid_dht_content(k, v);
tracing::debug!(app_name = %app_name, dht_key = %key_str, is_valid = %is_valid, "DHT value validation");
is_valid
},
IpfsContent::Pubsub((topic, message)) => {
let is_valid = is_valid_pubsub_content(topic, message);
tracing::debug!(app_name = %app_name, topic = %topic, is_valid = %is_valid, "PubSub message validation");
is_valid
},
}
}

/// Get File from Ipfs
pub(crate) fn hermes_ipfs_get_file(
app_name: &HermesAppName, path: &IpfsPath,
) -> Result<IpfsFile, Errno> {
tracing::debug!(app_name = %app_name, path = %path, "get IPFS file");
let content = HERMES_IPFS_STATE.file_get(path)?;
tracing::debug!(app_name = %app_name, path = %path, "got IPFS file");
Ok(content)
}

/// Pin IPFS File
pub(crate) fn hermes_ipfs_pin_file(
app_name: &HermesAppName, path: IpfsPath,
) -> Result<bool, Errno> {
tracing::debug!(app_name = %app_name, path = %path, "pin IPFS file");
let status = HERMES_IPFS_STATE.file_pin(&path)?;
tracing::debug!(app_name = %app_name, path = %path, "pinned IPFS file");
HERMES_IPFS_STATE.apps.pinned_file(app_name.clone(), path);
Ok(status)
}

/// Get DHT Value
pub(crate) fn hermes_ipfs_get_dht_value(
app_name: &HermesAppName, key: DhtKey,
) -> Result<DhtValue, Errno> {
let key_str = format!("{key:x?}");
tracing::debug!(app_name = %app_name, dht_key = %key_str, "get DHT value");
let value = HERMES_IPFS_STATE.dht_get(key)?;
tracing::debug!(app_name = %app_name, dht_key = %key_str, "got DHT value");
Ok(value)
}

/// Put DHT Value
pub(crate) fn hermes_ipfs_put_dht_value(
app_name: &HermesAppName, key: DhtKey, value: DhtValue,
) -> Result<bool, Errno> {
let key_str = format!("{key:x?}");
tracing::debug!(app_name = %app_name, dht_key = %key_str, "putting DHT value");
let status = HERMES_IPFS_STATE.dht_put(key.clone(), value)?;
tracing::debug!(app_name = %app_name, dht_key = %key_str, "have put DHT value");
HERMES_IPFS_STATE.apps.added_dht_key(app_name.clone(), key);
Ok(status)
}

/// Subscribe to a topic
pub(crate) fn hermes_ipfs_subscribe(
app_name: &HermesAppName, topic: PubsubTopic,
) -> Result<bool, Errno> {
tracing::debug!(app_name = %app_name, pubsub_topic = %topic, "subscribing to PubSub topic");
if HERMES_IPFS_STATE.apps.topic_subscriptions_contains(&topic) {
tracing::debug!(app_name = %app_name, pubsub_topic = %topic, "topic subscription stream already exists");
} else {
let handle = HERMES_IPFS_STATE.pubsub_subscribe(&topic)?;
HERMES_IPFS_STATE
.apps
.added_topic_stream(topic.clone(), handle);
tracing::debug!(app_name = %app_name, pubsub_topic = %topic, "added subscription topic stream");
}
HERMES_IPFS_STATE
.apps
.added_app_topic_subscription(app_name.clone(), topic);
Ok(true)
}

/// Publish message to a topic
pub(crate) fn hermes_ipfs_publish(
_app_name: &HermesAppName, topic: &PubsubTopic, message: MessageData,
) -> Result<MessageId, Errno> {
HERMES_IPFS_STATE
.pubsub_publish(topic.to_string(), message)
.map(|m| m.0 .0)
}

/// Evict Peer from node
pub(crate) fn hermes_ipfs_evict_peer(
app_name: &HermesAppName, peer: PeerId,
) -> Result<bool, Errno> {
tracing::debug!(app_name = %app_name, peer_id = %peer, "evicting peer");
let status = HERMES_IPFS_STATE.peer_evict(&peer.to_string())?;
tracing::debug!(app_name = %app_name, peer_id = %peer, "evicted peer");
HERMES_IPFS_STATE.apps.evicted_peer(app_name.clone(), peer);
Ok(status)
}
Loading
Loading