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: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
target: aarch64-unknown-linux-gnu
- os: macos-latest
target: aarch64-apple-darwin
- os: macos-13
- os: macos-15-intel
target: x86_64-apple-darwin

steps:
Expand Down
8 changes: 4 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,8 @@

resolver = "2"
members = [ "client", "protocol", "veritas", "testutil", "wallet"]


[workspace.package]
version = "0.0.9"
edition = "2021"
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
Checkout [releases](https://github.com/spacesprotocol/spaces/releases) for an immediately usable binary version of this software.


## Work on Subspaces

Spaces is live on mainnet. Subspaces is live on testnet4, and development work is happening on the [subspaces branch](https://github.com/spacesprotocol/spaces/tree/subspaces).


## What does it do?

Spaces are sovereign Bitcoin identities. They leverage the existing infrastructure and security of Bitcoin without requiring a new blockchain or any modifications to Bitcoin itself [learn more](https://spacesprotocol.org).
Expand Down
4 changes: 2 additions & 2 deletions client/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "spaces_client"
version = "0.0.7"
edition = "2021"
version.workspace = true
edition.workspace = true


[[bin]]
Expand Down
25 changes: 0 additions & 25 deletions client/src/bin/space-cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,9 @@ use jsonrpsee::{
core::{client::Error, ClientError},
http_client::HttpClient,
};
use serde::{Deserialize, Serialize};
use spaces_client::{
auth::{auth_token_from_cookie, auth_token_from_creds, http_client_with_auth},
config::{default_cookie_path, default_spaces_rpc_port, ExtendedNetwork},
deserialize_base64,
format::{
print_error_rpc_response, print_list_bidouts, print_list_spaces_response,
print_list_transactions, print_list_unspent, print_list_wallets, print_server_info,
Expand All @@ -32,7 +30,6 @@ use spaces_client::{
BidParams, ExecuteParams, OpenParams, RegisterParams, RpcClient, RpcWalletRequest,
RpcWalletTxBuilder, SendCoinsParams, TransferSpacesParams,
},
serialize_base64,
wallets::{AddressKind, WalletResponse},
};
use spaces_protocol::bitcoin::{Amount, FeeRate, OutPoint, Txid};
Expand Down Expand Up @@ -369,28 +366,6 @@ struct SpaceCli {
client: HttpClient,
}

#[derive(Serialize, Deserialize)]
struct SignedDnsUpdate {
serial: u32,
space: String,
#[serde(
serialize_with = "serialize_base64",
deserialize_with = "deserialize_base64"
)]
packet: Vec<u8>,
signature: Signature,
#[serde(skip_serializing_if = "Option::is_none")]
proof: Option<Base64Bytes>,
}

#[derive(Serialize, Deserialize)]
struct Base64Bytes(
#[serde(
serialize_with = "serialize_base64",
deserialize_with = "deserialize_base64"
)]
Vec<u8>,
);

impl SpaceCli {
async fn configure() -> anyhow::Result<(Self, Args)> {
Expand Down
7 changes: 6 additions & 1 deletion client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,12 @@ impl Client {
// Space => Outpoint mapping will be removed
// since this type of revocation only happens when an
// expired space is being re-opened for auction.
// No bids here so only remove Outpoint -> Spaceout
// Remove both Space -> Outpoint and Outpoint -> Spaceout mappings
if let Some(space) = update.output.spaceout.space.as_ref() {
let base_hash = Sha256::hash(space.name.as_ref());
let space_key = SpaceKey::from(base_hash);
state.remove(space_key);
}
let hash =
OutpointKey::from_outpoint::<Sha256>(update.output.outpoint());
state.remove(hash);
Expand Down
12 changes: 11 additions & 1 deletion client/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use rand::{
{thread_rng, Rng},
};
use serde::Deserialize;
use spaces_protocol::bitcoin::Network;
use spaces_protocol::{bitcoin::Network, constants::ChainAnchor};

use crate::{
auth::{auth_token_from_cookie, auth_token_from_creds},
Expand Down Expand Up @@ -117,6 +117,16 @@ impl ExtendedNetwork {
_ => Err(()),
}
}

pub fn genesis(&self) -> ChainAnchor {
match self {
ExtendedNetwork::Testnet => ChainAnchor::TESTNET(),
ExtendedNetwork::Testnet4 => ChainAnchor::TESTNET4(),
ExtendedNetwork::Regtest => ChainAnchor::REGTEST(),
ExtendedNetwork::Mainnet => ChainAnchor::MAINNET(),
_ => panic!("unsupported network"),
}
}
}

impl Args {
Expand Down
23 changes: 14 additions & 9 deletions client/src/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -513,14 +513,13 @@ impl WalletManager {
.map_err(|_| anyhow!("Mnemonic generation error"))?;

let start_block = self.get_wallet_start_block(client).await?;
self.setup_new_wallet(name.to_string(), mnemonic.to_string(), start_block)?;
self.setup_new_wallet(name.to_string(), mnemonic.to_string(), Some(start_block.height))?;
self.load_wallet(name).await?;
Ok(mnemonic.to_string())
}

pub async fn recover_wallet(&self, client: &reqwest::Client, name: &str, mnemonic: &str) -> anyhow::Result<()> {
let start_block = self.get_wallet_start_block(client).await?;
self.setup_new_wallet(name.to_string(), mnemonic.to_string(), start_block)?;
pub async fn recover_wallet(&self, name: &str, mnemonic: &str) -> anyhow::Result<()> {
self.setup_new_wallet(name.to_string(), mnemonic.to_string(), None)?;
self.load_wallet(name).await?;
Ok(())
}
Expand All @@ -529,14 +528,14 @@ impl WalletManager {
&self,
name: String,
mnemonic: String,
start_block: BlockId,
start_block_height: Option<u32>,
) -> anyhow::Result<()> {
let wallet_path = self.data_dir.join(&name);
if wallet_path.exists() {
return Err(anyhow!(format!("Wallet `{}` already exists", name)));
}

let export = self.wallet_from_mnemonic(name.clone(), mnemonic, start_block)?;
let export = self.wallet_from_mnemonic(name.clone(), mnemonic, start_block_height)?;
fs::create_dir_all(&wallet_path)?;
let wallet_export_path = wallet_path.join("wallet.json");
let mut file = fs::File::create(wallet_export_path)?;
Expand All @@ -548,7 +547,7 @@ impl WalletManager {
&self,
name: String,
mnemonic: String,
start_block: BlockId,
start_block_height: Option<u32>,
) -> anyhow::Result<WalletExport> {
let (network, _) = self.fallback_network();
let xpriv = Self::descriptor_from_mnemonic(network, &mnemonic)?;
Expand All @@ -557,8 +556,14 @@ impl WalletManager {
let tmp = bdk::Wallet::create(external, internal)
.network(network)
.create_wallet_no_persist()?;

let start_block_height = match start_block_height {
Some(height) => height,
None => self.network.genesis().height,
};

let export =
WalletExport::export_wallet(&tmp, &name, start_block.height).map_err(|e| anyhow!(e))?;
WalletExport::export_wallet(&tmp, &name, start_block_height).map_err(|e| anyhow!(e))?;

Ok(export)
}
Expand Down Expand Up @@ -946,7 +951,7 @@ impl RpcServer for RpcServerImpl {

async fn wallet_recover(&self, name: &str, mnemonic: String) -> Result<(), ErrorObjectOwned> {
self.wallet_manager
.recover_wallet(&self.client, name, &mnemonic)
.recover_wallet(name, &mnemonic)
.await
.map_err(|error| {
ErrorObjectOwned::owned(RPC_WALLET_NOT_LOADED, error.to_string(), None::<String>)
Expand Down
8 changes: 1 addition & 7 deletions client/src/spaces.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,12 +257,6 @@ impl Spaced {
}

pub fn genesis(network: ExtendedNetwork) -> ChainAnchor {
match network {
ExtendedNetwork::Testnet => ChainAnchor::TESTNET(),
ExtendedNetwork::Testnet4 => ChainAnchor::TESTNET4(),
ExtendedNetwork::Regtest => ChainAnchor::REGTEST(),
ExtendedNetwork::Mainnet => ChainAnchor::MAINNET(),
_ => panic!("unsupported network"),
}
network.genesis()
}
}
21 changes: 15 additions & 6 deletions client/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,11 @@ impl Store {
Ok(Database::new(Box::new(FileBackend::new(file)?), config)?)
}

pub fn iter(&self) -> SnapshotIterator<Sha256Hasher> {
pub fn iter(&self) -> SnapshotIterator<'_, Sha256Hasher> {
return self.0.iter();
}

pub fn write(&self) -> Result<WriteTx> {
pub fn write(&self) -> Result<WriteTx<'_>> {
Ok(self.0.begin_write()?)
}

Expand Down Expand Up @@ -218,10 +218,19 @@ impl ChainState for LiveSnapshot {
if let Some(outpoint) = outpoint {
let spaceout = self.get_spaceout(&outpoint)?;

return Ok(Some(FullSpaceOut {
txid: outpoint.txid,
spaceout: spaceout.expect("should exist if outpoint exists"),
}));
// Handle data inconsistency gracefully: if outpoint exists but spaceout doesn't,
// this indicates the space was revoked but the space->outpoint mapping wasn't cleaned up.
// Clean up the inconsistent mapping and return None instead of panicking.
if let Some(spaceout) = spaceout {
return Ok(Some(FullSpaceOut {
txid: outpoint.txid,
spaceout,
}));
} else {
// Clean up the inconsistent space->outpoint mapping
self.remove(*space_hash);
return Ok(None);
}
}
Ok(None)
}
Expand Down
13 changes: 7 additions & 6 deletions client/src/wallets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -820,19 +820,20 @@ impl RpcWallet {
let mut pending = vec![];
let mut outbid = vec![];
for (txid, event) in recent_events {
let tx = wallet.get_tx(txid);
if tx.as_ref().is_some_and(|tx| !tx.chain_position.is_confirmed()) {
pending.push(SLabel::from_str(event.space.as_ref().unwrap()).expect("valid space name"));
continue;
}

if unspent.iter().any(|out| {
out.space
.as_ref()
.is_some_and(|s| &s.name.to_string() == event.space.as_ref().unwrap())
}) {
continue;
}

let tx = wallet.get_tx(txid);
if tx.as_ref().is_some_and(|tx| !tx.chain_position.is_confirmed()) {
pending.push(SLabel::from_str(event.space.as_ref().unwrap()).expect("valid space name"));
continue;
}

let name = SLabel::from_str(event.space.as_ref().unwrap()).expect("valid space name");
let spacehash = SpaceKey::from(Sha256::hash(name.as_ref()));
let space = state.get_space_info(&spacehash)?;
Expand Down
4 changes: 2 additions & 2 deletions protocol/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "spaces_protocol"
version = "0.0.7"
edition = "2021"
version.workspace = true
edition.workspace = true

[dependencies]
bitcoin = { version = "0.32.2", features = ["base64", "serde"], default-features = false }
Expand Down
2 changes: 1 addition & 1 deletion protocol/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ impl FullSpaceOut {

pub fn refund_signing_info(
&self,
) -> Option<(Transaction, Prevouts<TxOut>, schnorr::Signature)> {
) -> Option<(Transaction, Prevouts<'_, TxOut>, schnorr::Signature)> {
if self.spaceout.space.is_none() {
return None;
}
Expand Down
16 changes: 12 additions & 4 deletions protocol/src/script.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,10 +148,18 @@ impl SpaceScript {
let existing = src.get_space_outpoint(&spacehash)?;
match existing {
None => OpenHistory::NewSpace(name.to_owned()),
Some(outpoint) => OpenHistory::ExistingSpace(FullSpaceOut {
txid: outpoint.txid,
spaceout: src.get_spaceout(&outpoint)?.expect("spaceout exists"),
}),
Some(outpoint) => {
// Handle data inconsistency: if spaceout doesn't exist, treat as new space
// This can happen if the space was revoked but the space->outpoint mapping
// wasn't cleaned up properly
match src.get_spaceout(&outpoint)? {
Some(spaceout) => OpenHistory::ExistingSpace(FullSpaceOut {
txid: outpoint.txid,
spaceout,
}),
None => OpenHistory::NewSpace(name.to_owned()),
}
}
}
};
let open = Ok(kind);
Expand Down
2 changes: 1 addition & 1 deletion protocol/src/slabel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ impl Display for SLabelRef<'_> {
}

impl SLabel {
pub fn as_name_ref(&self) -> SLabelRef {
pub fn as_name_ref(&self) -> SLabelRef<'_> {
SLabelRef(&self.0)
}

Expand Down
4 changes: 2 additions & 2 deletions veritas/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "spaces_veritas"
version = "0.0.7"
edition = "2021"
version.workspace = true
edition.workspace = true

[lib]
crate-type = ["cdylib", "rlib"]
Expand Down
2 changes: 1 addition & 1 deletion veritas/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ impl Veritas {
}

impl Proof {
pub fn iter(&self) -> ProofIter {
pub fn iter(&self) -> ProofIter<'_> {
ProofIter {
inner: self.inner.iter(),
}
Expand Down
4 changes: 2 additions & 2 deletions wallet/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "spaces_wallet"
version = "0.0.7"
edition = "2021"
version.workspace = true
edition.workspace = true

[dependencies]
spaces_protocol = { path = "../protocol", features = ["std"], version = "*" }
Expand Down
Loading
Loading