Skip to content

Commit 9060210

Browse files
authored
Merge pull request #116 from buffrr/finality
Introduce commitment finality and different PTR transfer mechanisms
2 parents 90c6581 + 7aef424 commit 9060210

File tree

20 files changed

+2498
-523
lines changed

20 files changed

+2498
-523
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

SUBSPACES.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44

55
### 1. Initialize the Space
66

7-
Initialize your space for operation:
7+
Delegate your space to a UTXO that can be used to submit commitments:
88

99
```bash
10-
$ space-cli operate @bitcoin
10+
$ space-cli delegate @bitcoin
1111
```
1212

1313
### 2. Issue Subspaces
@@ -57,12 +57,12 @@ $ space-cli getcommitment @bitcoin
5757
```
5858

5959

60-
### Delegating Operational Control
60+
### Authorizing Operational Control
6161

62-
You can authorize another party to make commitments on your behalf:
62+
You can authorize another party to make commitments on your behalf by transferring the space pointer:
6363

6464
```bash
65-
$ space-cli delegate @bitcoin --to <operator-address>
65+
$ space-cli authorize @bitcoin --to <operator-address>
6666
```
6767

6868
## Binding Handles On-Chain

client/src/bin/space-cli.rs

Lines changed: 78 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,12 @@ use spaces_client::{
3535
serialize_base64,
3636
wallets::{AddressKind, WalletResponse},
3737
};
38-
use spaces_client::rpc::{CommitParams, CreatePtrParams, TransferPtrParams};
38+
use spaces_client::rpc::{CommitParams, CreatePtrParams, DelegateParams, SetPtrDataParams, TransferPtrParams};
3939
use spaces_client::store::Sha256;
4040
use spaces_protocol::bitcoin::{Amount, FeeRate, OutPoint, Txid};
4141
use spaces_protocol::slabel::SLabel;
4242
use spaces_ptr::sptr::Sptr;
4343
use spaces_wallet::{bitcoin::secp256k1::schnorr::Signature, export::WalletExport, nostr::{NostrEvent, NostrTag}, Listing};
44-
use spaces_wallet::address::SpaceAddress;
4544
use spaces_wallet::bitcoin::hashes::sha256;
4645
use spaces_wallet::bitcoin::ScriptBuf;
4746

@@ -157,6 +156,8 @@ enum Commands {
157156
CreatePtr {
158157
/// The script public key as hex string
159158
spk: String,
159+
160+
#[arg(long, short)]
160161
fee_rate: Option<u64>,
161162
},
162163
/// Get ptr info
@@ -208,10 +209,9 @@ enum Commands {
208209
fee_rate: Option<u64>,
209210
},
210211
/// Initialize a space for operation of off-chain subspaces
211-
#[command(name = "operate")]
212-
Operate {
213-
/// The space to apply new root
214-
#[arg(display_order = 0)]
212+
#[command(name = "delegate")]
213+
Delegate {
214+
/// The space to delegate
215215
space: String,
216216
/// Fee rate to use in sat/vB
217217
#[arg(long, short)]
@@ -221,19 +221,26 @@ enum Commands {
221221
#[command(name = "commit")]
222222
Commit {
223223
/// The space to apply new root
224-
#[arg(display_order = 0)]
225224
space: String,
226225
/// The new state root
227-
#[arg(long, display_order = 1)]
228226
root: sha256::Hash,
229227
/// Fee rate to use in sat/vB
230228
#[arg(long, short)]
231229
fee_rate: Option<u64>,
232230
},
231+
/// Rollback the last pending commitment
232+
#[command(name = "rollback")]
233+
Rollback {
234+
/// The space to rollback
235+
space: String,
236+
/// Fee rate to use in sat/vB
237+
#[arg(long, short)]
238+
fee_rate: Option<u64>,
239+
},
233240
/// Delegate operation of a space to someone else
234-
#[command(name = "delegate")]
235-
Delegate {
236-
/// Ptrs to send
241+
#[command(name = "authorize")]
242+
Authorize {
243+
/// Space to authorize
237244
#[arg(display_order = 0)]
238245
space: String,
239246
/// Recipient space name or address (must be a space address)
@@ -408,11 +415,11 @@ enum Commands {
408415
#[arg(default_value = "0")]
409416
target_interval: usize,
410417
},
411-
/// Associate on-chain record data with a space as a fallback to P2P options like Fabric.
418+
/// Associate on-chain record data with a space/sptr as a fallback to P2P options like Fabric.
412419
#[command(name = "setrawfallback")]
413420
SetRawFallback {
414-
/// Space name
415-
space: String,
421+
/// Space name or SPTR identifier
422+
space_or_sptr: String,
416423
/// Hex encoded data
417424
data: String,
418425
/// Fee rate to use in sat/vB
@@ -839,11 +846,10 @@ async fn handle_commands(cli: &SpaceCli, command: Commands) -> Result<(), Client
839846
.await?
840847
}
841848
Commands::SetRawFallback {
842-
mut space,
849+
space_or_sptr,
843850
data,
844851
fee_rate,
845852
} => {
846-
space = normalize_space(&space);
847853
let data = match hex::decode(data) {
848854
Ok(data) => data,
849855
Err(e) => {
@@ -854,19 +860,35 @@ async fn handle_commands(cli: &SpaceCli, command: Commands) -> Result<(), Client
854860
}
855861
};
856862

857-
let space_script =
858-
spaces_protocol::script::SpaceScript::create_set_fallback(data.as_slice());
863+
// Check if it's an SPTR (starts with "sptr1")
864+
if space_or_sptr.starts_with("sptr1") {
865+
let sptr = Sptr::from_str(&space_or_sptr).map_err(|e| {
866+
ClientError::Custom(format!("Invalid SPTR: {}", e))
867+
})?;
868+
cli.send_request(
869+
Some(RpcWalletRequest::SetPtrData(SetPtrDataParams { sptr, data })),
870+
None,
871+
fee_rate,
872+
false,
873+
)
874+
.await?;
875+
} else {
876+
// Space fallback: use existing space script
877+
let space = normalize_space(&space_or_sptr);
878+
let space_script =
879+
spaces_protocol::script::SpaceScript::create_set_fallback(data.as_slice());
859880

860-
cli.send_request(
861-
Some(RpcWalletRequest::Execute(ExecuteParams {
862-
context: vec![space],
863-
space_script,
864-
})),
865-
None,
866-
fee_rate,
867-
false,
868-
)
869-
.await?;
881+
cli.send_request(
882+
Some(RpcWalletRequest::Execute(ExecuteParams {
883+
context: vec![space],
884+
space_script,
885+
})),
886+
None,
887+
fee_rate,
888+
false,
889+
)
890+
.await?;
891+
}
870892
}
871893
Commands::ListUnspent => {
872894
let utxos = cli.client.wallet_list_unspent(&cli.wallet).await?;
@@ -1111,49 +1133,54 @@ async fn handle_commands(cli: &SpaceCli, command: Commands) -> Result<(), Client
11111133
.map_err(|e| ClientError::Custom(e.to_string()))?;
11121134
println!("{}", serde_json::to_string(&ptrout).expect("result"));
11131135
}
1114-
Commands::Operate { space, fee_rate } => {
1136+
Commands::Delegate { space, fee_rate } => {
11151137
let space_info = match cli.client.get_space(&space).await? {
11161138
Some(space_info) => space_info,
11171139
None => return Err(ClientError::Custom("no such space".to_string()))
11181140
};
11191141
let commitments_tip = cli.client.get_commitment(
11201142
space_info.spaceout.space.as_ref().expect("space").name.clone(),
1121-
None
1143+
None
11221144
).await?;
11231145
if commitments_tip.is_some() {
1124-
return Err(ClientError::Custom("space is already operational".to_string()));
1146+
return Err(ClientError::Custom("space is already delegated".to_string()));
11251147
}
1126-
1127-
let address = cli.client.wallet_get_new_address(&cli.wallet, AddressKind::Space).await?;
1128-
let address = SpaceAddress::from_str(&address)
1129-
.expect("valid");
1130-
let spk = address.script_pubkey();
1131-
let sptr = Sptr::from_spk::<Sha256>(spk.clone());
11321148

1133-
println!("Assigning space to sptr {}", sptr);
1149+
println!("Delegating space {}", space);
11341150
cli.send_request(
1135-
Some(RpcWalletRequest::Transfer(TransferSpacesParams {
1136-
spaces: vec![space],
1137-
to: Some(address.to_string()),
1151+
Some(RpcWalletRequest::Delegate(DelegateParams {
1152+
space: space_info.spaceout.space.as_ref().expect("space").name.clone(),
11381153
})),
11391154
None,
11401155
fee_rate,
11411156
false,
11421157
)
11431158
.await?;
1144-
println!("Creating UTXO for sptr {}", sptr);
1159+
println!("Space delegation should be complete once tx is confirmed");
1160+
}
1161+
Commands::Commit { space, root, fee_rate } => {
1162+
let space_info = match cli.client.get_space(&space).await? {
1163+
Some(space_info) => space_info,
1164+
None => return Err(ClientError::Custom("no such space".to_string()))
1165+
};
1166+
1167+
let label = space_info.spaceout.space.as_ref().expect("space").name.clone();
1168+
let delegation = cli.client.get_delegation(label.clone()).await?;
1169+
if delegation.is_none() {
1170+
return Err(ClientError::Custom("space is not operational - use operate @<your-space> first.".to_string()));
1171+
}
11451172
cli.send_request(
1146-
Some(RpcWalletRequest::CreatePtr(CreatePtrParams {
1147-
spk: hex::encode(spk.as_bytes()),
1173+
Some(RpcWalletRequest::Commit(CommitParams {
1174+
space: label.clone(),
1175+
root: Some(root),
11481176
})),
11491177
None,
11501178
fee_rate,
11511179
false,
11521180
)
11531181
.await?;
1154-
println!("Space should be operational once txs are confirmed");
11551182
}
1156-
Commands::Commit { space, root, fee_rate } => {
1183+
Commands::Rollback { space, fee_rate } => {
11571184
let space_info = match cli.client.get_space(&space).await? {
11581185
Some(space_info) => space_info,
11591186
None => return Err(ClientError::Custom("no such space".to_string()))
@@ -1162,20 +1189,21 @@ async fn handle_commands(cli: &SpaceCli, command: Commands) -> Result<(), Client
11621189
let label = space_info.spaceout.space.as_ref().expect("space").name.clone();
11631190
let delegation = cli.client.get_delegation(label.clone()).await?;
11641191
if delegation.is_none() {
1165-
return Err(ClientError::Custom("space is not operational - use operate @<your-space> first.".to_string()));
1192+
return Err(ClientError::Custom("space is not delegated - use delegate @<your-space> first.".to_string()));
11661193
}
11671194
cli.send_request(
11681195
Some(RpcWalletRequest::Commit(CommitParams {
11691196
space: label.clone(),
1170-
root,
1197+
root: None,
11711198
})),
11721199
None,
11731200
fee_rate,
11741201
false,
11751202
)
11761203
.await?;
1204+
println!("Rollback transaction sent");
11771205
}
1178-
Commands::Delegate { space, to, fee_rate } => {
1206+
Commands::Authorize { space, to, fee_rate } => {
11791207
let space_info = match cli.client.get_space(&space).await? {
11801208
Some(space_info) => space_info,
11811209
None => return Err(ClientError::Custom("no such space".to_string()))
@@ -1184,7 +1212,7 @@ async fn handle_commands(cli: &SpaceCli, command: Commands) -> Result<(), Client
11841212
let label = space_info.spaceout.space.as_ref().expect("space").name.clone();
11851213
let delegation = cli.client.get_delegation(label.clone()).await?;
11861214
if delegation.is_none() {
1187-
return Err(ClientError::Custom("space is not operational - use operate @<your-space> first.".to_string()));
1215+
return Err(ClientError::Custom("space is not delegated - use delegate @<your-space> first.".to_string()));
11881216
}
11891217
let delegation = delegation.unwrap();
11901218

client/src/client.rs

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,8 @@ use spaces_protocol::{
1515
validate::{TxChangeSet, UpdateKind, Validator},
1616
Bytes, Covenant, FullSpaceOut, RevokeReason, SpaceOut,
1717
};
18-
use spaces_ptr::{CommitmentKey, RegistryKey};
19-
use spaces_ptr::sptr::Sptr;
20-
use spaces_wallet::bitcoin::{Network, ScriptBuf, Transaction};
18+
use spaces_ptr::{CommitmentKey, RegistryKey, RegistrySptrKey, PtrOutpointKey};
19+
use spaces_wallet::bitcoin::{Network, Transaction};
2120

2221
use crate::{
2322
source::BitcoinRpcError,
@@ -258,7 +257,8 @@ impl Client {
258257
spaces_ptr::TxContext::from_tx::<Chain, Sha256>(
259258
chain,
260259
tx,
261-
spaceouts.is_some() || spaceouts_input_ctx.is_some())?
260+
spaceouts_input_ctx.is_some(),
261+
spaceouts.clone().unwrap_or(vec![]) , height)?
262262
} else {
263263
None
264264
};
@@ -308,20 +308,31 @@ impl Client {
308308

309309
// Remove revoked delegations
310310
for revoked in changeset.revoked_delegations {
311-
state.remove_delegation(revoked);
311+
let sptr_key = RegistrySptrKey::from_sptr::<Sha256>(revoked.sptr);
312+
state.remove_delegation(sptr_key);
312313
}
314+
// Remove revoked commitments
315+
for revoked in changeset.revoked_commitments {
316+
let commitment_key = CommitmentKey::new::<Sha256>(&revoked.space, revoked.commitment.state_root);
317+
state.remove_commitment(commitment_key);
318+
}
319+
313320
// Create new delegations
314321
for delegation in changeset.new_delegations {
315-
state.insert_delegation(delegation.sptr_key, delegation.space);
322+
let sptr_key = RegistrySptrKey::from_sptr::<Sha256>(delegation.sptr);
323+
state.insert_delegation(sptr_key, delegation.space);
316324
}
317325

318326
// Insert new commitments
319-
for (space, commitment) in changeset.commitments {
320-
let commitment_key = CommitmentKey::new::<Sha256>(&space, commitment.state_root);
327+
for commitment_info in changeset.commitments {
328+
let commitment_key = CommitmentKey::new::<Sha256>(&commitment_info.space, commitment_info.commitment.state_root);
329+
let registry_key = RegistryKey::from_slabel::<Sha256>(&commitment_info.space);
330+
321331
// Points space -> commitments tip
322-
state.insert_registry(RegistryKey::from_slabel::<Sha256>(&space), commitment.state_root);
332+
state.insert_registry(registry_key, commitment_info.commitment.state_root);
323333
// commitment key = HASH(HASH(space) || state root) -> commitment
324-
state.insert_commitment(commitment_key, commitment);
334+
state.insert_commitment(commitment_key, commitment_info.commitment);
335+
325336
}
326337

327338
// Create ptrs
@@ -333,12 +344,11 @@ impl Client {
333344

334345
// Ptr => Outpoint
335346
if let Some(ptr) = create.sptr.as_ref() {
336-
let ptr_key = Sptr::from_spk::<Sha256>(ScriptBuf::from(ptr.genesis_spk.clone()));
337-
state.insert_ptr(ptr_key, outpoint.into());
347+
state.insert_ptr(ptr.id, outpoint.into());
338348
}
339349

340350
// Outpoint => PtrOut
341-
let outpoint_key = OutpointKey::from_outpoint::<Sha256>(outpoint);
351+
let outpoint_key = PtrOutpointKey::from_outpoint::<Sha256>(outpoint);
342352
state.insert_ptrout(outpoint_key, create);
343353
}
344354
}

0 commit comments

Comments
 (0)