Refactor commit
This commit is contained in:
parent
ce2c158de2
commit
8eae4408f2
125
src/commit.rs
125
src/commit.rs
@ -1,17 +1,14 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use anyhow::{Error, Result};
|
use anyhow::{Error, Result};
|
||||||
|
|
||||||
use hex::FromHex;
|
use hex::FromHex;
|
||||||
use log::debug;
|
use sdk_common::pcd::Pcd;
|
||||||
use sdk_common::pcd::AnkPcdHash;
|
|
||||||
use sdk_common::silentpayments::create_transaction;
|
use sdk_common::silentpayments::create_transaction;
|
||||||
use sdk_common::sp_client::spclient::Recipient;
|
use sdk_common::sp_client::spclient::Recipient;
|
||||||
use sdk_common::{error::AnkError, network::CommitMessage};
|
use sdk_common::{error::AnkError, network::CommitMessage};
|
||||||
use sdk_common::sp_client::bitcoin::consensus::deserialize;
|
use sdk_common::sp_client::bitcoin::consensus::deserialize;
|
||||||
use sdk_common::sp_client::bitcoin::{Amount, Transaction, Txid, OutPoint};
|
use sdk_common::sp_client::bitcoin::{Amount, Transaction, Txid, OutPoint};
|
||||||
use sdk_common::process::{Process, ProcessState, CACHEDPROCESSES};
|
use sdk_common::process::{Process, ProcessState, CACHEDPROCESSES};
|
||||||
use serde_json::Value;
|
use serde_json::{json, Map, Value};
|
||||||
|
|
||||||
use crate::{lock_freezed_utxos, MutexExt, DAEMON, WALLET};
|
use crate::{lock_freezed_utxos, MutexExt, DAEMON, WALLET};
|
||||||
|
|
||||||
@ -19,19 +16,14 @@ pub(crate) fn handle_commit_request(commit_msg: CommitMessage) -> Result<OutPoin
|
|||||||
// Attempt to deserialize `init_tx` as a `Transaction`
|
// Attempt to deserialize `init_tx` as a `Transaction`
|
||||||
if let Ok(tx) = deserialize::<Transaction>(&Vec::from_hex(&commit_msg.init_tx)?) {
|
if let Ok(tx) = deserialize::<Transaction>(&Vec::from_hex(&commit_msg.init_tx)?) {
|
||||||
// This is the first transaction of a chain of commitments
|
// This is the first transaction of a chain of commitments
|
||||||
|
// Create the root commitment outpoint
|
||||||
// Ensure the transaction has only one output
|
let root_commitment = OutPoint::new(tx.txid(), 0);
|
||||||
if tx.output.len() != 1 {
|
|
||||||
return Err(AnkError::NewTxError(
|
|
||||||
"Transaction must have only one output".to_string(),
|
|
||||||
))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Check that the output pays us
|
// TODO: Check that the output pays us
|
||||||
|
|
||||||
// Validation tokens must be empty for the initial transaction
|
// Validation tokens must be empty for the initial transaction
|
||||||
if !commit_msg.validation_tokens.is_empty() {
|
if !commit_msg.validation_tokens.is_empty() {
|
||||||
return Err(AnkError::NewTxError(
|
return Err(AnkError::GenericError(
|
||||||
"Validation tokens must be empty".to_string(),
|
"Validation tokens must be empty".to_string(),
|
||||||
))?;
|
))?;
|
||||||
}
|
}
|
||||||
@ -40,16 +32,30 @@ pub(crate) fn handle_commit_request(commit_msg: CommitMessage) -> Result<OutPoin
|
|||||||
let daemon = DAEMON.get().unwrap().lock_anyhow()?;
|
let daemon = DAEMON.get().unwrap().lock_anyhow()?;
|
||||||
daemon.broadcast(&tx)?;
|
daemon.broadcast(&tx)?;
|
||||||
|
|
||||||
// Create the root commitment outpoint
|
let roles = Value::Object(commit_msg.roles.iter().map(|(name, def)| (name.to_owned(), Value::String(serde_json::to_string(def).unwrap()))).collect());
|
||||||
let root_commitment = OutPoint::new(tx.txid(), 0);
|
|
||||||
|
// put roles in a map
|
||||||
|
let roles_only_map = json!({
|
||||||
|
"roles": roles
|
||||||
|
});
|
||||||
|
|
||||||
|
// We check that for testing but it's useless
|
||||||
|
assert!(commit_msg.roles == roles.extract_roles()?);
|
||||||
|
|
||||||
|
let roles_commitment = roles_only_map.hash_fields(root_commitment)?;
|
||||||
|
|
||||||
|
assert!(roles_commitment.get("roles") == commit_msg.pcd_commitment.get("roles"));
|
||||||
|
|
||||||
|
// We always keep an empty state as the last state
|
||||||
|
let empty_state = ProcessState {
|
||||||
|
commited_in: root_commitment,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
// Initialize the process state
|
// Initialize the process state
|
||||||
let init_state = ProcessState {
|
let mut init_state = empty_state.clone();
|
||||||
commited_in: root_commitment,
|
init_state.encrypted_pcd = roles_only_map;
|
||||||
encrypted_pcd: Value::Object(commit_msg.encrypted_pcd),
|
init_state.pcd_commitment = commit_msg.pcd_commitment;
|
||||||
keys: commit_msg.keys,
|
|
||||||
validation_tokens: vec![],
|
|
||||||
};
|
|
||||||
|
|
||||||
// Access the cached processes and insert the new commitment
|
// Access the cached processes and insert the new commitment
|
||||||
let mut commitments = CACHEDPROCESSES
|
let mut commitments = CACHEDPROCESSES
|
||||||
@ -59,15 +65,14 @@ pub(crate) fn handle_commit_request(commit_msg: CommitMessage) -> Result<OutPoin
|
|||||||
// We are confident that `root_commitment` doesn't exist in the map
|
// We are confident that `root_commitment` doesn't exist in the map
|
||||||
commitments.insert(
|
commitments.insert(
|
||||||
root_commitment,
|
root_commitment,
|
||||||
Process::new(vec![init_state], HashMap::new(), vec![]),
|
Process::new(vec![init_state, empty_state], vec![]),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add the outpoint to the list of frozen UTXOs
|
// Add the outpoint to the list of frozen UTXOs
|
||||||
let new_root_outpoint = OutPoint::new(tx.txid(), 0);
|
lock_freezed_utxos()?.insert(root_commitment);
|
||||||
lock_freezed_utxos()?.insert(new_root_outpoint);
|
|
||||||
|
|
||||||
// Wait for validation tokens to spend the new output and commit the hash
|
// Wait for validation tokens to spend the new output and commit the hash
|
||||||
Ok(new_root_outpoint)
|
Ok(root_commitment)
|
||||||
}
|
}
|
||||||
// Attempt to deserialize `init_tx` as an `OutPoint`
|
// Attempt to deserialize `init_tx` as an `OutPoint`
|
||||||
else if let Ok(outpoint) = deserialize::<OutPoint>(&Vec::from_hex(&commit_msg.init_tx)?) {
|
else if let Ok(outpoint) = deserialize::<OutPoint>(&Vec::from_hex(&commit_msg.init_tx)?) {
|
||||||
@ -80,50 +85,57 @@ pub(crate) fn handle_commit_request(commit_msg: CommitMessage) -> Result<OutPoin
|
|||||||
.get_mut(&outpoint)
|
.get_mut(&outpoint)
|
||||||
.ok_or(Error::msg("Commitment not found"))?;
|
.ok_or(Error::msg("Commitment not found"))?;
|
||||||
|
|
||||||
let pcd_hash = AnkPcdHash::from_map(&commit_msg.encrypted_pcd);
|
|
||||||
|
|
||||||
if commit_msg.validation_tokens.is_empty() {
|
if commit_msg.validation_tokens.is_empty() {
|
||||||
// Register a new state if validation tokens are empty
|
// Register a new state if validation tokens are empty
|
||||||
|
|
||||||
// Get all the latest concurrent states
|
// Get all the latest concurrent states
|
||||||
let concurrent_states = commitment.get_latest_concurrent_states();
|
let concurrent_states = commitment.get_latest_concurrent_states()?;
|
||||||
|
|
||||||
let current_outpoint = concurrent_states.first().unwrap().commited_in;
|
let (empty_state, actual_states) = concurrent_states.split_last().unwrap(); // We necessary have 1 state
|
||||||
|
let current_outpoint = empty_state.commited_in;
|
||||||
|
|
||||||
// Check for existing states with the same PCD hash
|
// Check for existing states with the same PCD hash
|
||||||
if concurrent_states
|
if actual_states
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.any(|state| AnkPcdHash::from_value(&state.encrypted_pcd) == pcd_hash)
|
.any(|state| state.pcd_commitment == commit_msg.pcd_commitment)
|
||||||
{
|
{
|
||||||
return Err(anyhow::Error::msg("Proposed state already exists"));
|
return Err(anyhow::Error::msg("Proposed state already exists"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert the new process state
|
let roles = Value::Object(commit_msg.roles.iter().map(|(name, def)| (name.to_owned(), Value::String(serde_json::to_string(def).unwrap()))).collect());
|
||||||
commitment.insert_state(ProcessState {
|
|
||||||
commited_in: current_outpoint,
|
let roles_only_map = json!({
|
||||||
encrypted_pcd: Value::Object(commit_msg.encrypted_pcd),
|
"roles": roles
|
||||||
keys: commit_msg.keys,
|
|
||||||
validation_tokens: vec![],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let new_state = ProcessState {
|
||||||
|
commited_in: current_outpoint,
|
||||||
|
pcd_commitment: commit_msg.pcd_commitment,
|
||||||
|
encrypted_pcd: roles_only_map,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Insert the new process state
|
||||||
|
commitment.insert_concurrent_state(new_state)?;
|
||||||
|
|
||||||
Ok(current_outpoint)
|
Ok(current_outpoint)
|
||||||
} else {
|
} else {
|
||||||
// Validation tokens are provided; process the pending state
|
// Validation tokens are provided; process the pending state
|
||||||
|
|
||||||
let new_state_commitment = AnkPcdHash::from_map(&commit_msg.encrypted_pcd);
|
// Clone the state, we'll need it for validation purpose
|
||||||
|
let mut state_to_validate = commitment.get_latest_concurrent_states()?
|
||||||
// Clone the previous state, we'll need it for validation purpose
|
|
||||||
if let Some(mut state_to_validate) = commitment.get_latest_concurrent_states()
|
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.find(|s| {
|
.find(|state| {
|
||||||
AnkPcdHash::from_value(&s.encrypted_pcd) == new_state_commitment
|
state.pcd_commitment == commit_msg.pcd_commitment
|
||||||
})
|
})
|
||||||
.cloned()
|
.ok_or(anyhow::Error::msg("Unknown state"))?
|
||||||
{
|
.clone();
|
||||||
|
|
||||||
// We update the validation tokens for our clone
|
// We update the validation tokens for our clone
|
||||||
state_to_validate.validation_tokens = commit_msg.validation_tokens;
|
state_to_validate.validation_tokens = commit_msg.validation_tokens;
|
||||||
let previous_state = commitment.get_previous_state(&state_to_validate);
|
// We test the validity of the state with the provided proofs
|
||||||
if state_to_validate.is_valid(previous_state).is_ok() {
|
state_to_validate.is_valid(commitment.get_latest_commited_state())?;
|
||||||
|
|
||||||
// If the new state is valid we commit it in a new transaction that spends the last commited_in
|
// If the new state is valid we commit it in a new transaction that spends the last commited_in
|
||||||
// By spending it we also know the next outpoint to monitor for the next state
|
// By spending it we also know the next outpoint to monitor for the next state
|
||||||
// We add a placeholder state with that information at the tip of the chain
|
// We add a placeholder state with that information at the tip of the chain
|
||||||
@ -167,10 +179,10 @@ pub(crate) fn handle_commit_request(commit_msg: CommitMessage) -> Result<OutPoin
|
|||||||
|
|
||||||
// We're ready to commit, we first update our process states
|
// We're ready to commit, we first update our process states
|
||||||
// We remove all concurrent states
|
// We remove all concurrent states
|
||||||
let rm_states = commitment.remove_latest_concurrent_states();
|
let _ = commitment.remove_all_concurrent_states()?;
|
||||||
debug!("removed states: {:?}", rm_states);
|
// debug!("removed states: {:?}", rm_states);
|
||||||
// We push the validated state back
|
// We push the validated state back
|
||||||
commitment.insert_state(state_to_validate);
|
commitment.insert_concurrent_state(state_to_validate.clone())?;
|
||||||
|
|
||||||
// We broadcast transaction
|
// We broadcast transaction
|
||||||
let txid = daemon.broadcast(&new_tx)?;
|
let txid = daemon.broadcast(&new_tx)?;
|
||||||
@ -181,20 +193,9 @@ pub(crate) fn handle_commit_request(commit_msg: CommitMessage) -> Result<OutPoin
|
|||||||
// Add the newly created outpoint to our list of freezed utxos
|
// Add the newly created outpoint to our list of freezed utxos
|
||||||
freezed_utxos.insert(commited_in);
|
freezed_utxos.insert(commited_in);
|
||||||
|
|
||||||
let empty_state = ProcessState {
|
commitment.update_states_tip(commited_in)?;
|
||||||
commited_in,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
commitment.insert_state(empty_state);
|
|
||||||
|
|
||||||
Ok(commited_in)
|
Ok(commited_in)
|
||||||
} else {
|
|
||||||
return Err(Error::msg("Invalid state"));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(Error::msg("Unknown proposal, must create it first before sending validations"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(Error::msg("init_tx must be a valid transaction or txid"))
|
Err(Error::msg("init_tx must be a valid transaction or txid"))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user