Skip to content

Commit

Permalink
feat!: remove blobs from state
Browse files Browse the repository at this point in the history
This commit removes all blobs from state and makes use of protocol
input data semantics.
This approach lowers storage costs by ~100x.
  • Loading branch information
dndll committed Sep 21, 2023
1 parent 7d6d037 commit 04b5d08
Show file tree
Hide file tree
Showing 12 changed files with 251 additions and 516 deletions.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ authors = ["Pagoda <[email protected]>"]
edition = "2021"

[workspace]
members = ["crates/*", "contracts/*", "bin/*", "op-stack/optimism-rs"]
members = ["crates/*", "contracts/*", "bin/*" ]
# TODO: ignored for now "op-stack/optimism-rs"
resolver = "2"

[workspace.dependencies]
Expand Down
7 changes: 1 addition & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,4 @@ devnet-down:
devnet-da-logs:
docker compose -f op-stack/optimism/ops-bedrock/docker-compose-devnet.yml logs op-batcher | grep NEAR
docker compose -f op-stack/optimism/ops-bedrock/docker-compose-devnet.yml logs op-node | grep NEAR

clear-namespace:
near contract call-function as-transaction $$NEAR_CONTRACT clear json-args '{"ns":[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 8, 229, 246, 121, 191, 113, 22, 203, 216, 166, 155, 132, 9, 0, 73, 156, 212, 167, 93, 119, 8, 0, 81, 0, 0, 0, 0, 0, 0, 0]]}' \
prepaid-gas '100.000 TeraGas' \
attached-deposit '0 NEAR' \
sign-as $$NEAR_CONTRACT network-config testnet sign-with-keychain send

2 changes: 1 addition & 1 deletion bin/light-client
161 changes: 10 additions & 151 deletions contracts/blob-store/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,182 +1,41 @@
use near_da_primitives::{Blob as ExternalBlob, Namespace, ShareVersion};
use near_sdk::{
borsh::{self, BorshDeserialize, BorshSerialize},
store::{LookupMap, UnorderedMap},
BlockHeight,
};
use near_da_primitives::Blob;
use near_sdk::{borsh, borsh::BorshDeserialize, borsh::BorshSerialize, BlockHeight};
use near_sdk::{env, near_bindgen};
use near_sdk_contract_tools::owner::OwnerExternal;
use near_sdk_contract_tools::{owner::Owner, Owner};
use std::default;
use std::vec::Vec;
use std::{collections::HashMap, default};

type Commitment = [u8; 32];
#[allow(unused_imports)] // Justification: Proc macro needs this
use near_sdk_contract_tools::owner::OwnerExternal;

#[near_bindgen]
#[derive(Owner, BorshDeserialize, BorshSerialize)]
pub struct Contract {
pub blobs: UnorderedMap<Commitment, Blob>,
pub indices: LookupMap<Namespace, HashMap<BlockHeight, Commitment>>,
}
pub struct Contract {}

impl default::Default for Contract {
fn default() -> Self {
let mut contract = Self {
indices: LookupMap::new(b"i".to_vec()),
blobs: UnorderedMap::new(b"b"),
};
let mut contract = Self {};
Owner::init(&mut contract, &near_sdk::env::predecessor_account_id());

contract
}
}

#[derive(BorshDeserialize, BorshSerialize, Clone)]
pub struct Blob {
data: Vec<u8>,
// Keep track of these to make sure there are no breaking changes which
// might bork the store
share_version: ShareVersion,
}

impl TryFrom<ExternalBlob> for Blob {
type Error = ();
fn try_from(value: ExternalBlob) -> Result<Self, Self::Error> {
Ok(Blob {
data: value.data.try_to_vec().map_err(|_| ())?,
share_version: value.share_version,
})
}
}

#[near_bindgen]
impl Contract {
pub fn submit(&mut self, blobs: Vec<ExternalBlob>) -> BlockHeight {
pub fn submit(&mut self, blobs: Vec<Blob>) -> BlockHeight {
Self::require_owner();

let height = env::block_height();

for blob in blobs {
let map = self
.indices
.entry(blob.namespace.clone())
.or_insert(HashMap::default());
let commitment = blob.commitment;
map.insert(height, blob.commitment);
let blob: Result<Blob, _> = blob.try_into();
if let Ok(blob) = blob {
self.blobs.insert(commitment, blob);
} else {
return 0;
}
}
height
}
pub fn clear(&mut self, ns: Vec<Namespace>) {
Self::require_owner();

for ns in ns {
let inner = self.indices.get_mut(&ns);
if let Some(inner) = inner {
inner.iter().for_each(|(_, commitment)| {
self.blobs.remove(commitment);
});
}
self.indices.remove(&ns);
}
}

pub fn get(&self, namespace: Namespace, height: BlockHeight) -> Option<ExternalBlob> {
self.indices
.get(&namespace)
.and_then(|x| x.get(&height))
.and_then(|commitment| self.blobs.get(commitment).map(|x| (commitment, x)))
.and_then(|(commitment, inner)| {
Some(ExternalBlob {
namespace,
data: BorshDeserialize::try_from_slice(&inner.data).ok()?,
share_version: inner.share_version,
commitment: commitment.clone(),
})
})
.clone()
}

pub fn get_all(&self, namespace: Namespace) -> Vec<(BlockHeight, ExternalBlob)> {
self.indices
.get(&namespace)
.map(|height_map| {
height_map
.iter()
.flat_map(|(height, commitment)| {
self.blobs.get(commitment).map(|x| (height, commitment, x))
})
.flat_map(|(height, commitment, inner)| {
Some((
*height,
ExternalBlob {
namespace,
data: BorshDeserialize::try_from_slice(&inner.data).ok()?,
share_version: inner.share_version,
commitment: commitment.clone(),
},
))
})
.collect::<Vec<_>>()
})
.unwrap_or_default()
}

// Shortcut read if you already know the namespace of the commitment
pub fn fast_get(&self, commitment: Commitment) -> Option<ExternalBlob> {
self.blobs.get(&commitment).and_then(|inner| {
Some(ExternalBlob {
namespace: Namespace::default(),
data: BorshDeserialize::try_from_slice(&inner.data).ok()?,
share_version: inner.share_version,
commitment: commitment.clone(),
})
})
near_sdk::env::log_str(&format!("submitting {} blobs", blobs.len()));
env::block_height()
}
}

#[cfg(test)]
mod tests {
use super::*;

fn dummy_blob() -> ExternalBlob {
ExternalBlob {
namespace: Namespace::new(1, 1),
data: "fake".try_to_vec().unwrap(),
share_version: 1,
commitment: [2_u8; 32],
}
}

#[test]
fn initializes() {
let _ = Contract::default();
}

#[test]
fn test_submit_indices() {
let mut contract = Contract::default();
let blobs = vec![dummy_blob()];
let height = contract.submit(blobs.clone());
assert_eq!(height, 0);
assert_eq!(contract.blobs.len(), 1);
let height_commitment = contract.indices.get(&blobs[0].namespace).unwrap();
assert_eq!(height_commitment.len(), 1);
assert_eq!(height_commitment.get(&0).unwrap(), &[2_u8; 32]);
}

#[test]
fn test_remove() {
let mut contract = Contract::default();
let blobs = vec![dummy_blob()];
contract.submit(blobs.clone());
contract.clear(vec![blobs[0].namespace]);
assert_eq!(contract.blobs.len(), 0);
assert!(contract.indices.get(&blobs[0].namespace).is_none());
}
}
6 changes: 3 additions & 3 deletions crates/op-rpc-sys/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ fn main() {
let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let crate_name = env::var("CARGO_PKG_NAME").unwrap();
let output_file = target_dir()
.join(&format!("lib{crate_name}.h"))
.join(format!("lib{crate_name}.h"))
.display()
.to_string();

Expand All @@ -23,9 +23,9 @@ fn main() {
"near-da-op-rpc".to_string(),
]);
config.sys_includes = vec!["math.h".to_string(), "stdio.h".to_string()];
cbindgen::generate_with_config(&crate_dir, config)
cbindgen::generate_with_config(crate_dir, config)
.expect("Unable to generate bindings")
.write_to_file(&output_file);
.write_to_file(output_file);
}

/// Find the location of the `target/` directory. Note that this may be
Expand Down
Loading

0 comments on commit 04b5d08

Please sign in to comment.