diff --git a/crates/bindings/src/impls.rs b/crates/bindings/src/impls.rs index 703f92fb60..88ce0de63f 100644 --- a/crates/bindings/src/impls.rs +++ b/crates/bindings/src/impls.rs @@ -1,7 +1,4 @@ -use spacetimedb_lib::{ - name::{UniqueDomainName, UniqueTld}, - Address, DataKey, Hash, Identity, -}; +use spacetimedb_lib::{Address, DataKey, Hash, Identity}; use super::PrimaryKey; use crate::{FilterableValue, UniqueValue}; @@ -64,17 +61,3 @@ impl UniqueValue for Address { todo!() } } - -impl FilterableValue for UniqueDomainName {} -impl UniqueValue for UniqueDomainName { - fn into_primarykey(self) -> PrimaryKey { - todo!() - } -} - -impl FilterableValue for UniqueTld {} -impl UniqueValue for UniqueTld { - fn into_primarykey(self) -> PrimaryKey { - todo!() - } -} diff --git a/crates/client-api/src/lib.rs b/crates/client-api/src/lib.rs index 23a6b0d246..b1c3a27e57 100644 --- a/crates/client-api/src/lib.rs +++ b/crates/client-api/src/lib.rs @@ -7,11 +7,10 @@ use spacetimedb::address::Address; use spacetimedb::auth::identity::{DecodingKey, EncodingKey}; use spacetimedb::client::ClientActorIndex; use spacetimedb::database_instance_context_controller::DatabaseInstanceContextController; -use spacetimedb::hash::Hash; use spacetimedb::host::UpdateDatabaseResult; -use spacetimedb::host::{EnergyQuanta, HostController, UpdateDatabaseResult}; +use spacetimedb::host::{EnergyQuanta, HostController}; use spacetimedb::identity::Identity; -use spacetimedb::messages::control_db::{Database, DatabaseInstance, EnergyBalance, IdentityEmail, Node}; +use spacetimedb::messages::control_db::{Database, DatabaseInstance, IdentityEmail, Node}; use spacetimedb::messages::worker_db::DatabaseInstanceState; use spacetimedb::module_host_context::ModuleHostContext; use spacetimedb::sendgrid_controller::SendGridController; @@ -112,7 +111,7 @@ pub trait ControlStateReadAccess { fn get_recovery_codes(&self, email: &str) -> spacetimedb::control_db::Result>; // Energy - fn get_energy_balance(&self, identity: &Identity) -> spacetimedb::control_db::Result>; + fn get_energy_balance(&self, identity: &Identity) -> spacetimedb::control_db::Result>; // DNS fn lookup_address(&self, domain: &DomainName) -> spacetimedb::control_db::Result>; @@ -152,7 +151,7 @@ pub trait ControlStateWriteAccess: Send + Sync { ) -> spacetimedb::control_db::Result<()>; // Energy - async fn add_energy(&self, identity: &Identity, quanta: u64) -> spacetimedb::control_db::Result<()>; + async fn add_energy(&self, identity: &Identity, amount: EnergyQuanta) -> spacetimedb::control_db::Result<()>; async fn withdraw_energy(&self, identity: &Identity, amount: EnergyQuanta) -> spacetimedb::control_db::Result<()>; // DNS @@ -221,7 +220,7 @@ impl ControlStateReadAccess for ArcEnv { } // Energy - fn get_energy_balance(&self, identity: &Identity) -> spacetimedb::control_db::Result> { + fn get_energy_balance(&self, identity: &Identity) -> spacetimedb::control_db::Result> { self.0.get_energy_balance(identity) } @@ -270,8 +269,8 @@ impl ControlStateWriteAccess for ArcEnv self.0.insert_recovery_code(identity, email, code).await } - async fn add_energy(&self, identity: &Identity, quanta: u64) -> spacetimedb::control_db::Result<()> { - self.0.add_energy(identity, quanta).await + async fn add_energy(&self, identity: &Identity, amount: EnergyQuanta) -> spacetimedb::control_db::Result<()> { + self.0.add_energy(identity, amount).await } async fn withdraw_energy(&self, identity: &Identity, amount: EnergyQuanta) -> spacetimedb::control_db::Result<()> { self.0.withdraw_energy(identity, amount).await @@ -375,7 +374,7 @@ impl ControlStateReadAccess for Arc { } // Energy - fn get_energy_balance(&self, identity: &Identity) -> spacetimedb::control_db::Result> { + fn get_energy_balance(&self, identity: &Identity) -> spacetimedb::control_db::Result> { (**self).get_energy_balance(identity) } @@ -424,8 +423,11 @@ impl ControlStateWriteAccess for Arc { (**self).insert_recovery_code(identity, email, code).await } - async fn add_energy(&self, identity: &Identity, quanta: u64) -> spacetimedb::control_db::Result<()> { - (**self).add_energy(identity, quanta).await + async fn add_energy(&self, identity: &Identity, amount: EnergyQuanta) -> spacetimedb::control_db::Result<()> { + (**self).add_energy(identity, amount).await + } + async fn withdraw_energy(&self, identity: &Identity, amount: EnergyQuanta) -> spacetimedb::control_db::Result<()> { + (**self).withdraw_energy(identity, amount).await } async fn register_tld(&self, identity: &Identity, tld: Tld) -> spacetimedb::control_db::Result { diff --git a/crates/client-api/src/routes/database.rs b/crates/client-api/src/routes/database.rs index 797c9066e9..ad52a4788a 100644 --- a/crates/client-api/src/routes/database.rs +++ b/crates/client-api/src/routes/database.rs @@ -68,8 +68,8 @@ pub async fn call( let args = ReducerArgs::Json(body); - let address = name_or_address.resolve(&*worker_ctx).await?.into(); - let database = worker_ctx_find_database(&*worker_ctx, &address).await?.ok_or_else(|| { + let address = name_or_address.resolve(&worker_ctx).await?.into(); + let database = worker_ctx_find_database(&worker_ctx, &address).await?.ok_or_else(|| { log::error!("Could not find database: {}", address.to_hex()); (StatusCode::NOT_FOUND, "No such database.") })?; @@ -157,7 +157,7 @@ use rand::Rng; use spacetimedb::auth::identity::encode_token; use spacetimedb::sql::execute::execute; use spacetimedb_lib::identity::AuthCtx; -use spacetimedb_lib::name::{DnsLookupResponse, PublishOp, PublishResult}; +use spacetimedb_lib::name::{DnsLookupResponse, PublishResult}; use spacetimedb_lib::recovery::{RecoveryCode, RecoveryCodeResponse}; use std::convert::From; @@ -255,8 +255,8 @@ pub async fn describe( where S: ControlStateDelegate + NodeDelegate, { - let address = name_or_address.resolve(&*worker_ctx).await?.into(); - let database = worker_ctx_find_database(&*worker_ctx, &address) + let address = name_or_address.resolve(&worker_ctx).await?.into(); + let database = worker_ctx_find_database(&worker_ctx, &address) .await? .ok_or((StatusCode::NOT_FOUND, "No such database."))?; @@ -312,8 +312,8 @@ pub async fn catalog( where S: ControlStateDelegate + NodeDelegate, { - let address = name_or_address.resolve(&*worker_ctx).await?.into(); - let database = worker_ctx_find_database(&*worker_ctx, &address) + let address = name_or_address.resolve(&worker_ctx).await?.into(); + let database = worker_ctx_find_database(&worker_ctx, &address) .await? .ok_or((StatusCode::NOT_FOUND, "No such database."))?; @@ -358,8 +358,8 @@ pub async fn info( State(worker_ctx): State, Path(InfoParams { name_or_address }): Path, ) -> axum::response::Result { - let address = name_or_address.resolve(&*worker_ctx).await?.into(); - let database = worker_ctx_find_database(&*worker_ctx, &address) + let address = name_or_address.resolve(&worker_ctx).await?.into(); + let database = worker_ctx_find_database(&worker_ctx, &address) .await? .ok_or((StatusCode::NOT_FOUND, "No such database."))?; @@ -410,8 +410,8 @@ where // Should all the others change? let auth = auth_or_unauth(auth)?; - let address = name_or_address.resolve(&*worker_ctx).await?.into(); - let database = worker_ctx_find_database(&*worker_ctx, &address) + let address = name_or_address.resolve(&worker_ctx).await?.into(); + let database = worker_ctx_find_database(&worker_ctx, &address) .await? .ok_or((StatusCode::NOT_FOUND, "No such database."))?; @@ -484,10 +484,10 @@ fn mime_ndjson() -> mime::Mime { } async fn worker_ctx_find_database( - worker_ctx: &dyn WorkerCtx, + worker_ctx: &(impl ControlStateDelegate + ?Sized), address: &Address, ) -> Result, StatusCode> { - worker_ctx.get_database_by_address(address).await.map_err(log_and_500) + worker_ctx.get_database_by_address(address).map_err(log_and_500) } #[derive(Deserialize)] @@ -512,8 +512,8 @@ where // which queries this identity is allowed to execute against the database. let auth = auth.get().ok_or((StatusCode::UNAUTHORIZED, "Invalid credentials."))?; - let address = name_or_address.resolve(&*worker_ctx).await?.into(); - let database = worker_ctx_find_database(&*worker_ctx, &address) + let address = name_or_address.resolve(&worker_ctx).await?.into(); + let database = worker_ctx_find_database(&worker_ctx, &address) .await? .ok_or((StatusCode::NOT_FOUND, "No such database."))?; @@ -631,11 +631,7 @@ pub async fn register_tld( let auth = auth_or_bad_request(auth)?; let tld = tld.parse::().map_err(DomainParsingRejection)?.into(); - let result = ctx - .control_db() - .spacetime_register_tld(tld, auth.identity) - .await - .map_err(log_and_500)?; + let result = ctx.register_tld(&auth.identity, tld).await.map_err(log_and_500)?; Ok(axum::Json(result)) } @@ -746,13 +742,6 @@ pub async fn confirm_recovery_code( Ok(axum::Json(result)) } -async fn control_ctx_find_database(ctx: &dyn ControlCtx, address: &Address) -> Result, StatusCode> { - ctx.control_db() - .get_database_by_address(address) - .await - .map_err(log_and_500) -} - #[derive(Deserialize)] pub struct PublishDatabaseParams {} @@ -793,7 +782,7 @@ pub async fn publish( let (db_addr, db_name) = match name_or_address { Some(noa) => match noa.try_resolve(&ctx).await? { - Ok((addr, maybe_domain)) => (addr, maybe_domain), + Ok(resolved) => resolved.into(), Err(domain) => { // `name_or_address` was a `NameOrAddress::Name`, but no record // exists yet. Create it now with a fresh address. @@ -923,10 +912,7 @@ pub async fn set_name( /// This API call is just designed to allow clients to determine whether or not they can /// establish a connection to SpacetimeDB. This API call doesn't actually do anything. -pub async fn ping( - State(_ctx): State>, - _auth: SpacetimeAuthHeader, -) -> axum::response::Result { +pub async fn ping(State(_ctx): State, _auth: SpacetimeAuthHeader) -> axum::response::Result { Ok(()) } @@ -939,7 +925,7 @@ where .route("/dns/:database_name", get(dns::)) .route("/reverse_dns/:database_address", get(reverse_dns::)) .route("/set_name", get(set_name::)) - .route("/ping", get(ping)) + .route("/ping", get(ping::)) .route("/register_tld", get(register_tld::)) .route("/request_recovery_code", get(request_recovery_code::)) .route("/confirm_recovery_code", get(confirm_recovery_code::)) diff --git a/crates/client-api/src/routes/energy.rs b/crates/client-api/src/routes/energy.rs index 93ec24fa96..c3d3517190 100644 --- a/crates/client-api/src/routes/energy.rs +++ b/crates/client-api/src/routes/energy.rs @@ -27,30 +27,53 @@ pub async fn get_energy_balance( #[derive(Deserialize)] pub struct AddEnergyQueryParams { - quanta: Option, + amount: Option, } pub async fn add_energy( State(ctx): State, - Path(IdentityParams { identity }): Path, - Query(AddEnergyQueryParams { quanta }): Query, + Query(AddEnergyQueryParams { amount }): Query, + auth: SpacetimeAuthHeader, ) -> axum::response::Result { - // TODO: we need to do authorization here. For now, just short-circuit. GOD MODE. - - if let Some(satoshi) = quanta { - ctx.add_energy(&identity, satoshi).await.map_err(log_and_500)?; + let Some(auth) = auth.auth else { + return Err(StatusCode::UNAUTHORIZED.into()); + }; + // Nb.: Negative amount withdraws + let amount = amount.map(|s| s.parse::()).transpose().map_err(|e| { + log::error!("Failed to parse amount: {e:?}"); + StatusCode::BAD_REQUEST + })?; + + let mut balance = ctx + .get_energy_balance(&auth.identity) + .map_err(log_and_500)? + .map(|quanta| quanta.0) + .unwrap_or(0); + + if let Some(satoshi) = amount { + ctx.add_energy(&auth.identity, EnergyQuanta(satoshi)) + .await + .map_err(log_and_500)?; + balance += satoshi; } - get_budget_inner(ctx, &identity) + + let response_json = json!({ + // Note: balance must be returned as a string to avoid truncation. + "balance": balance.to_string(), + }); + + Ok(axum::Json(response_json)) } fn get_budget_inner(ctx: impl ControlStateDelegate, identity: &Identity) -> axum::response::Result { - let budget = ctx + let balance = ctx .get_energy_balance(identity) .map_err(log_and_500)? - .unwrap_or(EnergyQuanta(0)); + .map(|quanta| quanta.0) + .unwrap_or(0); let response_json = json!({ // Note: balance must be returned as a string to avoid truncation. - "balance": balance.0.to_string(), + "balance": balance.to_string(), }); Ok(axum::Json(response_json)) @@ -60,8 +83,8 @@ fn get_budget_inner(ctx: impl ControlStateDelegate, identity: &Identity) -> axum pub struct SetEnergyBalanceQueryParams { balance: Option, } -pub async fn set_energy_balance( - State(ctx): State>, +pub async fn set_energy_balance( + State(ctx): State, Path(IdentityParams { identity }): Path, Query(SetEnergyBalanceQueryParams { balance }): Query, auth: SpacetimeAuthHeader, @@ -80,23 +103,37 @@ pub async fn set_energy_balance( let identity = Identity::from(identity); - let balance = balance + let desired_balance = balance .map(|balance| balance.parse::()) .transpose() .map_err(|err| { log::error!("Failed to parse balance: {:?}", err); StatusCode::BAD_REQUEST - })?; - let balance = EnergyQuanta(balance.unwrap_or(0)); - - ctx.control_db() - .set_energy_balance(identity, balance) - .await - .map_err(log_and_500)?; + })? + .unwrap_or(0); + let current_balance = ctx + .get_energy_balance(&identity) + .map_err(log_and_500)? + .map(|quanta| quanta.0) + .unwrap_or(0); + + let balance: i128 = if desired_balance > current_balance { + let delta = desired_balance - current_balance; + ctx.add_energy(&identity, EnergyQuanta(delta)) + .await + .map_err(log_and_500)?; + delta + } else { + let delta = current_balance - desired_balance; + ctx.withdraw_energy(&identity, EnergyQuanta(delta)) + .await + .map_err(log_and_500)?; + delta + }; let response_json = json!({ // Note: balance must be returned as a string to avoid truncation. - "balance": balance.0.to_string(), + "balance": balance.to_string(), }); Ok(axum::Json(response_json)) @@ -106,7 +143,7 @@ pub fn router() -> axum::Router where S: NodeDelegate + ControlStateDelegate + Clone + 'static, { - use axum::routing::{get, post}; + use axum::routing::{get, post, put}; axum::Router::new() .route("/:identity", get(get_energy_balance::)) .route("/:identity", post(set_energy_balance::)) diff --git a/crates/client-api/src/routes/subscribe.rs b/crates/client-api/src/routes/subscribe.rs index 1a5995fe20..a1cf1aec7c 100644 --- a/crates/client-api/src/routes/subscribe.rs +++ b/crates/client-api/src/routes/subscribe.rs @@ -43,7 +43,7 @@ where { let auth = auth.get_or_create(&ctx).await?; - let address = name_or_address.resolve(&*worker_ctx).await?.into(); + let address = name_or_address.resolve(&ctx).await?.into(); let (res, ws_upgrade, protocol) = ws.select_protocol([(BIN_PROTOCOL, Protocol::Binary), (TEXT_PROTOCOL, Protocol::Text)]); diff --git a/crates/client-api/src/util.rs b/crates/client-api/src/util.rs index aad2c02c06..a11d1e37b2 100644 --- a/crates/client-api/src/util.rs +++ b/crates/client-api/src/util.rs @@ -173,3 +173,9 @@ impl From for Address { value.address } } + +impl From for (Address, Option) { + fn from(ResolvedAddress { address, domain }: ResolvedAddress) -> Self { + (address, domain) + } +} diff --git a/crates/core/src/control_db.rs b/crates/core/src/control_db.rs index 8ab3c71599..9ac0508bc2 100644 --- a/crates/core/src/control_db.rs +++ b/crates/core/src/control_db.rs @@ -118,8 +118,8 @@ impl ControlDb { if self.spacetime_dns(&domain)?.is_some() { return Err(Error::RecordAlreadyExists(domain)); } - let tld = domain.as_tld(); - match self.spacetime_lookup_tld(&tld)? { + let tld = domain.tld(); + match self.spacetime_lookup_tld(tld)? { Some(owner) => { if owner != owner_identity { return Ok(InsertDomainResult::PermissionDenied { domain }); @@ -128,7 +128,7 @@ impl ControlDb { None => { if try_register_tld { // Let's try to automatically register this TLD for the identity - let result = self.spacetime_register_tld(domain.to_tld(), owner_identity).await?; + let result = self.spacetime_register_tld(tld.to_owned(), owner_identity)?; if let RegisterTldResult::Success { .. } = result { // This identity now owns this TLD } else { @@ -233,7 +233,7 @@ impl ControlDb { /// /// # Arguments /// * `domain` - The domain to lookup - pub async fn spacetime_lookup_tld(&self, domain: impl AsRef) -> Result> { + pub fn spacetime_lookup_tld(&self, domain: impl AsRef) -> Result> { let tree = self.db.open_tree("top_level_domains")?; match tree.get(domain.as_ref().to_lowercase().as_bytes())? { Some(owner) => Ok(Some(Identity::from_slice(&owner[..]))), @@ -512,8 +512,7 @@ impl ControlDb { /// Return the current budget for all identities as stored in the db. /// Note: this function is for the stored budget only and should *only* be called by functions in /// `control_budget`, where a cached copy is stored along with business logic for managing it. -<<<<<<< HEAD - pub async fn get_energy_balances(&self) -> Result> { + pub fn get_energy_balances(&self) -> Result> { let mut balances = vec![]; let tree = self.db.open_tree("energy_budget")?; for balance_entry in tree.iter() { @@ -557,7 +556,7 @@ impl ControlDb { /// Update the stored current budget for a identity. /// Note: this function is for the stored budget only and should *only* be called by functions in /// `control_budget`, where a cached copy is stored along with business logic for managing it. - pub async fn set_energy_balance(&self, identity: Identity, energy_balance: EnergyQuanta) -> Result<()> { + pub fn set_energy_balance(&self, identity: Identity, energy_balance: EnergyQuanta) -> Result<()> { let tree = self.db.open_tree("energy_budget")?; tree.insert(identity.as_bytes(), &energy_balance.0.to_be_bytes())?; diff --git a/crates/core/src/control_db/tests.rs b/crates/core/src/control_db/tests.rs index e6c48da306..56c8aa12c5 100644 --- a/crates/core/src/control_db/tests.rs +++ b/crates/core/src/control_db/tests.rs @@ -15,19 +15,19 @@ fn test_register_tld() -> anyhow::Result<()> { let domain: DomainName = "amaze".parse()?; let cdb = ControlDb::at(tmp.path())?; - cdb.spacetime_register_tld(domain.to_tld(), *ALICE).await?; - let owner = cdb.spacetime_lookup_tld(domain.tld()).await?; + cdb.spacetime_register_tld(domain.to_tld(), *ALICE)?; + let owner = cdb.spacetime_lookup_tld(domain.tld())?; assert_eq!(owner, Some(*ALICE)); - let unauthorized = cdb.spacetime_register_tld(domain.to_tld(), *BOB).await?; + let unauthorized = cdb.spacetime_register_tld(domain.to_tld(), *BOB)?; assert!(matches!(unauthorized, RegisterTldResult::Unauthorized { .. })); - let already_registered = cdb.spacetime_register_tld(domain.to_tld(), *ALICE).await?; + let already_registered = cdb.spacetime_register_tld(domain.to_tld(), *ALICE)?; assert!(matches!( already_registered, RegisterTldResult::AlreadyRegistered { .. } )); let domain = DomainName::from_str("amAZe")?; - let already_registered = cdb.spacetime_register_tld(domain.to_tld(), *ALICE).await?; + let already_registered = cdb.spacetime_register_tld(domain.to_tld(), *ALICE)?; assert!(matches!( already_registered, RegisterTldResult::AlreadyRegistered { .. } @@ -61,7 +61,7 @@ fn test_domain() -> anyhow::Result<()> { let already_registered = cdb.spacetime_insert_domain(&addr, domain_lower.clone(), *ALICE, true); assert!(matches!(already_registered, Err(Error::RecordAlreadyExists(_)))); - let tld_owner = cdb.spacetime_lookup_tld(domain.tld()).await?; + let tld_owner = cdb.spacetime_lookup_tld(domain.tld())?; assert_eq!(tld_owner, Some(*ALICE)); let registered_addr = cdb.spacetime_dns(&domain)?; diff --git a/crates/lib/src/name.rs b/crates/lib/src/name.rs index 57dea0a247..aaa17d1e20 100644 --- a/crates/lib/src/name.rs +++ b/crates/lib/src/name.rs @@ -1,7 +1,6 @@ use core::fmt; use std::{borrow::Borrow, ops::Deref, str::FromStr}; -use serde::{Deserialize, Serialize}; use spacetimedb_sats::{impl_deserialize, impl_serialize, impl_st}; #[cfg(test)] @@ -289,7 +288,6 @@ impl fmt::Display for TldRef { /// To construct a valid [`DomainName`], use [`parse_domain_name`] or the /// [`FromStr`] impl. #[derive(Debug, Clone, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct DomainName { // Iff there is a subdomain, next char in `domain_name` is '/'. tld_offset: usize, diff --git a/crates/standalone/src/energy_monitor.rs b/crates/standalone/src/energy_monitor.rs index 638849b341..639a73ae74 100644 --- a/crates/standalone/src/energy_monitor.rs +++ b/crates/standalone/src/energy_monitor.rs @@ -1,6 +1,6 @@ use crate::StandaloneEnv; use spacetimedb::host::{EnergyDiff, EnergyMonitor, EnergyMonitorFingerprint, EnergyQuanta}; -use spacetimedb_client_api::ControlNodeDelegate; +use spacetimedb_client_api::ControlStateWriteAccess; use std::{ sync::{Arc, Mutex, Weak}, time::Duration, diff --git a/crates/standalone/src/lib.rs b/crates/standalone/src/lib.rs index 70a784322a..f11c32484b 100644 --- a/crates/standalone/src/lib.rs +++ b/crates/standalone/src/lib.rs @@ -20,13 +20,12 @@ use spacetimedb::control_db::ControlDb; use spacetimedb::database_instance_context::DatabaseInstanceContext; use spacetimedb::database_instance_context_controller::DatabaseInstanceContextController; use spacetimedb::db::{db_metrics, Storage}; -use spacetimedb::hash::Hash; +use spacetimedb::host::EnergyQuanta; use spacetimedb::host::UpdateDatabaseResult; use spacetimedb::host::UpdateOutcome; use spacetimedb::host::{scheduler::Scheduler, HostController}; -use spacetimedb::host::{EnergyQuanta, UpdateDatabaseResult}; use spacetimedb::identity::Identity; -use spacetimedb::messages::control_db::{Database, DatabaseInstance, EnergyBalance, HostType, IdentityEmail, Node}; +use spacetimedb::messages::control_db::{Database, DatabaseInstance, HostType, IdentityEmail, Node}; use spacetimedb::messages::worker_db::DatabaseInstanceState; use spacetimedb::module_host_context::ModuleHostContext; use spacetimedb::object_db::ObjectDb; @@ -186,8 +185,10 @@ impl spacetimedb_client_api::NodeDelegate for StandaloneEnv { &self.private_key } + /// Standalone SpacetimeDB does not support SendGrid as a means to + /// reissue authentication tokens. fn sendgrid_controller(&self) -> Option<&SendGridController> { - self.sendgrid.as_ref() + None } async fn load_module_host_context(&self, db: Database, instance_id: u64) -> anyhow::Result { @@ -259,7 +260,7 @@ impl spacetimedb_client_api::ControlStateReadAccess for StandaloneEnv { } // Energy - fn get_energy_balance(&self, identity: &Identity) -> spacetimedb::control_db::Result> { + fn get_energy_balance(&self, identity: &Identity) -> spacetimedb::control_db::Result> { self.control_db.get_energy_balance(identity) } @@ -374,17 +375,13 @@ impl spacetimedb_client_api::ControlStateWriteAccess for StandaloneEnv { self.control_db.spacetime_insert_recovery_code(email, code) } - async fn add_energy(&self, identity: &Identity, quanta: u64) -> spacetimedb::control_db::Result<()> { + async fn add_energy(&self, identity: &Identity, amount: EnergyQuanta) -> spacetimedb::control_db::Result<()> { let mut balance = ::get_energy_balance(self, identity)? - .unwrap_or(EnergyBalance { - identity: *identity, - balance_quanta: 0, - }); - balance.balance_quanta = balance - .balance_quanta - .saturating_add(quanta.try_into().unwrap_or(i64::MAX)); + .map(|quanta| quanta.0) + .unwrap_or(0); + balance = balance.saturating_add(amount.0.try_into().unwrap_or(i128::MAX)); - self.control_db.set_energy_balance(identity, &balance) + self.control_db.set_energy_balance(*identity, EnergyQuanta(balance)) } async fn withdraw_energy(&self, identity: &Identity, amount: EnergyQuanta) -> spacetimedb::control_db::Result<()> { let energy_balance = self.control_db.get_energy_balance(identity)?; @@ -392,9 +389,7 @@ impl spacetimedb_client_api::ControlStateWriteAccess for StandaloneEnv { log::trace!("Withdrawing {} energy from {}", amount.0, identity); log::trace!("Old balance: {}", energy_balance.0); let new_balance = energy_balance - amount; - self.control_db - .set_energy_balance(*identity, new_balance.as_quanta()) - .await + self.control_db.set_energy_balance(*identity, new_balance.as_quanta()) } async fn register_tld(&self, identity: &Identity, tld: Tld) -> spacetimedb::control_db::Result { @@ -412,23 +407,6 @@ impl spacetimedb_client_api::ControlStateWriteAccess for StandaloneEnv { } } -#[async_trait::async_trait] -impl spacetimedb_client_api::ControlNodeDelegate for StandaloneEnv { - fn public_key(&self) -> &DecodingKey { - &self.public_key - } - - fn private_key(&self) -> &EncodingKey { - &self.private_key - } - - /// Standalone SpacetimeDB does not support SendGrid as a means to - /// reissue authentication tokens. - fn sendgrid_controller(&self) -> Option<&SendGridController> { - None - } -} - impl StandaloneEnv { async fn insert_database_instance(&self, database_instance: DatabaseInstance) -> Result<(), anyhow::Error> { let mut new_database_instance = database_instance.clone(); @@ -606,8 +584,9 @@ impl StandaloneEnv { let (dbic, (scheduler, scheduler_starter)) = tokio::task::spawn_blocking({ let database = database.clone(); let path = root_db_path.clone(); + let storage = self.storage; move || -> anyhow::Result<_> { - let dbic = DatabaseInstanceContext::from_database(&database, instance_id, path); + let dbic = DatabaseInstanceContext::from_database(storage, &database, instance_id, path); let sched = Scheduler::open(dbic.scheduler_db_path(root_db_path))?; Ok((dbic, sched)) } diff --git a/crates/testing/src/modules.rs b/crates/testing/src/modules.rs index 6eba4a20fa..f5b3c0f128 100644 --- a/crates/testing/src/modules.rs +++ b/crates/testing/src/modules.rs @@ -4,16 +4,14 @@ use std::path::PathBuf; use std::process::Command; use std::sync::Arc; +use tokio::runtime::{Builder, Runtime}; + use spacetimedb::address::Address; use spacetimedb::client::{ClientActorId, ClientConnection, Protocol}; use spacetimedb::database_logger::DatabaseLogger; use spacetimedb::db::Storage; -use spacetimedb::hash::hash_bytes; - -use spacetimedb::messages::control_db::HostType; -use spacetimedb_client_api::{ControlStateReadAccess, ControlStateWriteAccess, NodeDelegate}; +use spacetimedb_client_api::{ControlStateReadAccess, ControlStateWriteAccess, DatabaseDef, NodeDelegate}; use spacetimedb_standalone::StandaloneEnv; -use tokio::runtime::{Builder, Runtime}; fn start_runtime() -> Runtime { Builder::new_multi_thread().enable_all().build().unwrap() @@ -131,18 +129,21 @@ pub async fn load_module(name: &str) -> ModuleHandle { crate::set_key_env_vars(); let env = spacetimedb_standalone::StandaloneEnv::init(storage).await.unwrap(); - let identity = env.control_db().alloc_spacetime_identity().await.unwrap(); - let address = env.control_db().alloc_spacetime_address().await.unwrap(); + let identity = env.create_identity().await.unwrap(); + let address = env.create_address().await.unwrap(); let program_bytes = read_module(name); - let program_bytes_addr = hash_bytes(&program_bytes); - env.object_db().insert_object(program_bytes).unwrap(); - - let host_type = HostType::Wasmer; - - env.insert_database(&address, &identity, &program_bytes_addr, host_type, 1, true, false) - .await - .unwrap(); + env.publish_database( + &identity, + DatabaseDef { + address, + program_bytes, + num_replicas: 1, + trace_log: false, + }, + ) + .await + .unwrap(); let database = env.get_database_by_address(&address).unwrap().unwrap(); let instance = env.get_leader_database_instance_by_database(database.id).unwrap();