Skip to content

Commit

Permalink
fix!: propose and mint burnt L1 UTXOs in consensus
Browse files Browse the repository at this point in the history
  • Loading branch information
sdbondi committed Aug 27, 2024
1 parent 5329042 commit ce0cb29
Show file tree
Hide file tree
Showing 63 changed files with 2,219 additions and 151 deletions.
6 changes: 4 additions & 2 deletions Cargo.lock

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

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ members = [
"utilities/tariswap_test_bench",
"utilities/transaction_submitter",
"utilities/transaction_submitter",
"utilities/generate_ristretto_value_lookup", "applications/tari_watcher",
"utilities/generate_ristretto_value_lookup",
"applications/tari_watcher",
]
resolver = "2"

Expand Down
77 changes: 45 additions & 32 deletions applications/tari_dan_app_utilities/src/base_layer_scanner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ use tari_base_node_client::{
BaseNodeClient,
BaseNodeClientError,
};
use tari_common::configuration::Network;
use tari_common_types::types::{Commitment, FixedHash, FixedHashSizeError, PublicKey};
use tari_core::transactions::{
tari_amount::MicroMinotari,
Expand All @@ -44,9 +43,9 @@ use tari_crypto::{
ristretto::RistrettoPublicKey,
tari_utilities::{hex::Hex, ByteArray},
};
use tari_dan_common_types::{optional::Optional, shard::Shard, Epoch, NodeAddressable, NodeHeight, ShardGroup};
use tari_dan_common_types::{optional::Optional, NodeAddressable};
use tari_dan_storage::{
consensus_models::{Block, SubstateRecord},
consensus_models::{BurntUtxo, SubstateRecord},
global::{GlobalDb, MetadataKey},
StateStore,
StorageError,
Expand All @@ -56,10 +55,11 @@ use tari_engine_types::{
confidential::UnclaimedConfidentialOutput,
substate::{SubstateId, SubstateValue},
};
use tari_epoch_manager::{base_layer::EpochManagerHandle, EpochManagerError};
use tari_epoch_manager::{base_layer::EpochManagerHandle, EpochManagerError, EpochManagerReader};
use tari_shutdown::ShutdownSignal;
use tari_state_store_sqlite::SqliteStateStore;
use tari_template_lib::models::{EncryptedData, TemplateAddress, UnclaimedConfidentialOutputAddress};
use tari_transaction::VersionedSubstateId;
use tokio::{task, task::JoinHandle, time};

use crate::{
Expand All @@ -70,7 +70,6 @@ use crate::{
const LOG_TARGET: &str = "tari::dan::base_layer_scanner";

pub fn spawn<TAddr: NodeAddressable + 'static>(
network: Network,
global_db: GlobalDb<SqliteGlobalDbAdapter<TAddr>>,
base_node_client: GrpcBaseNodeClient,
epoch_manager: EpochManagerHandle<TAddr>,
Expand All @@ -86,7 +85,6 @@ pub fn spawn<TAddr: NodeAddressable + 'static>(
) -> JoinHandle<anyhow::Result<()>> {
task::spawn(async move {
let base_layer_scanner = BaseLayerScanner::new(
network,
global_db,
base_node_client,
epoch_manager,
Expand All @@ -107,7 +105,6 @@ pub fn spawn<TAddr: NodeAddressable + 'static>(
}

pub struct BaseLayerScanner<TAddr> {
network: Network,
global_db: GlobalDb<SqliteGlobalDbAdapter<TAddr>>,
last_scanned_height: u64,
last_scanned_tip: Option<FixedHash>,
Expand All @@ -129,7 +126,6 @@ pub struct BaseLayerScanner<TAddr> {

impl<TAddr: NodeAddressable + 'static> BaseLayerScanner<TAddr> {
pub fn new(
network: Network,
global_db: GlobalDb<SqliteGlobalDbAdapter<TAddr>>,
base_node_client: GrpcBaseNodeClient,
epoch_manager: EpochManagerHandle<TAddr>,
Expand All @@ -144,7 +140,6 @@ impl<TAddr: NodeAddressable + 'static> BaseLayerScanner<TAddr> {
burnt_utxo_sidechain_id: Option<PublicKey>,
) -> Self {
Self {
network,
global_db,
last_scanned_tip: None,
last_scanned_height: 0,
Expand Down Expand Up @@ -397,11 +392,11 @@ impl<TAddr: NodeAddressable + 'static> BaseLayerScanner<TAddr> {
}
info!(
target: LOG_TARGET,
"Found burned output: {} with commitment {}",
"⛓️ Found burned output: {} with commitment {}",
output_hash,
output.commitment.as_public_key()
);
self.register_burnt_utxo(&output).await?;
self.register_burnt_utxo(&output, &block_info).await?;
},
}
}
Expand Down Expand Up @@ -438,12 +433,34 @@ impl<TAddr: NodeAddressable + 'static> BaseLayerScanner<TAddr> {
Ok(())
}

async fn register_burnt_utxo(&mut self, output: &TransactionOutput) -> Result<(), BaseLayerScannerError> {
async fn register_burnt_utxo(
&mut self,
output: &TransactionOutput,
block_info: &BlockInfo,
) -> Result<(), BaseLayerScannerError> {
let substate_id = SubstateId::UnclaimedConfidentialOutput(
UnclaimedConfidentialOutputAddress::try_from_commitment(output.commitment.as_bytes()).map_err(|e|
// Technically impossible, but anyway
BaseLayerScannerError::InvalidSideChainUtxoResponse(format!("Invalid commitment: {}", e)))?,
);
let consensus_constants = self.epoch_manager.get_base_layer_consensus_constants().await?;
let epoch = consensus_constants.height_to_epoch(block_info.height);
let Some(local_committee_info) = self.epoch_manager.get_local_committee_info(epoch).await.optional()? else {
debug!(
target: LOG_TARGET,
"Validator node is not registered for the current epoch {epoch}. Ignoring burnt UTXO.",
);
return Ok(());
};

if !local_committee_info.includes_substate_id(&substate_id) {
debug!(
target: LOG_TARGET,
"Validator node is not part of the committee for the burnt UTXO {substate_id}. Ignoring."
);
return Ok(());
}

let encrypted_data_bytes = output.encrypted_data.as_bytes();
if encrypted_data_bytes.len() < EncryptedData::size() {
return Err(BaseLayerScannerError::InvalidSideChainUtxoResponse(
Expand All @@ -457,34 +474,30 @@ impl<TAddr: NodeAddressable + 'static> BaseLayerScanner<TAddr> {
BaseLayerScannerError::InvalidSideChainUtxoResponse("Encrypted data has too few bytes".to_string())
})?,
});

info!(
target: LOG_TARGET,
"⛓️ Burnt UTXO {substate_id} registered at height {}",
block_info.height,
);

self.state_store
.with_write_tx(|tx| {
let genesis = Block::genesis(
self.network,
Epoch(0),
ShardGroup::all_shards(self.consensus_constants.num_preshards),
);

// TODO: This should be proposed in a block...
SubstateRecord {
substate_id,
version: 0,
substate_value: substate,
state_hash: Default::default(),
created_by_transaction: Default::default(),
created_justify: *genesis.justify().id(),
created_block: *genesis.id(),
created_height: NodeHeight::zero(),
created_by_shard: Shard::zero(),
created_at_epoch: Epoch(0),
destroyed: None,
if SubstateRecord::exists(&**tx, &VersionedSubstateId::new(substate_id.clone(), 0))? {
warn!(
target: LOG_TARGET,
"Burnt UTXO {substate_id} already exists. Ignoring.",
);
return Ok(());
}
.create(tx)

BurntUtxo::new(substate_id, substate, block_info.height).insert(tx)
})
.map_err(|source| BaseLayerScannerError::CouldNotRegisterBurntUtxo {
commitment: Box::new(output.commitment.clone()),
source,
})?;

Ok(())
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub struct ConsensusConstants {
pub max_base_layer_blocks_ahead: u64,
pub max_base_layer_blocks_behind: u64,
pub num_preshards: NumPreshards,
pub pacemaker_max_base_time: std::time::Duration,
pub pacemaker_max_base_time: Duration,
}

impl ConsensusConstants {
Expand Down
1 change: 0 additions & 1 deletion applications/tari_indexer/src/bootstrap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,6 @@ pub async fn spawn_services(

// Base Node scanner
base_layer_scanner::spawn(
config.network,
global_db,
base_node_client.clone(),
epoch_manager.clone(),
Expand Down
2 changes: 2 additions & 0 deletions applications/tari_swarm_daemon/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@ tari_engine_types = { workspace = true }
minotari_node_grpc_client = { workspace = true }
minotari_wallet_grpc_client = { workspace = true }
tari_validator_node_client = { workspace = true }
tari_wallet_daemon_client = { workspace = true }
tari_dan_engine = { workspace = true }

anyhow = { workspace = true }
async-trait = { workspace = true }
axum = { workspace = true, features = ["multipart"] }
axum-jrpc = { workspace = true }
base64 = "0.22.1"
clap = { workspace = true, features = ["derive"] }
fern = { workspace = true, features = ["colored"] }
futures = { workspace = true }
Expand Down
3 changes: 3 additions & 0 deletions applications/tari_swarm_daemon/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,9 @@ async fn create_paths(config: &Config) -> anyhow::Result<()> {
fs::create_dir_all(&config.base_dir.join("templates"))
.await
.context("Failed to create templates directory")?;
fs::create_dir_all(&config.base_dir.join("misc"))
.await
.context("Failed to create misc directory")?;
Ok(())
}

Expand Down
33 changes: 32 additions & 1 deletion applications/tari_swarm_daemon/src/process_manager/handle.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
// Copyright 2024 The Tari Project
// SPDX-License-Identifier: BSD-3-Clause

use std::{collections::HashMap, path::PathBuf};
use std::{
collections::HashMap,
path::{Path, PathBuf},
};

use tari_common_types::types::FixedHash;
use tokio::sync::{mpsc, oneshot};
Expand Down Expand Up @@ -49,6 +52,13 @@ pub enum ProcessManagerRequest {
instance_id: InstanceId,
reply: Reply<()>,
},
BurnFunds {
amount: u64,
wallet_instance_id: InstanceId,
account_name: String,
out_path: PathBuf,
reply: Reply<PathBuf>,
},
}

pub struct TemplateData {
Expand Down Expand Up @@ -228,6 +238,27 @@ impl ProcessManagerHandle {
rx_reply.await?
}

pub async fn burn_funds<P: AsRef<Path>>(
&self,
amount: u64,
wallet_instance_id: InstanceId,
account_name: String,
out_path: P,
) -> anyhow::Result<PathBuf> {
let (tx_reply, rx_reply) = oneshot::channel();
self.tx_request
.send(ProcessManagerRequest::BurnFunds {
amount,
wallet_instance_id,
account_name,
out_path: out_path.as_ref().to_path_buf(),
reply: tx_reply,
})
.await?;

rx_reply.await?
}

pub(crate) async fn stop_all(&self) -> anyhow::Result<usize> {
let instances = self.list_instances(None).await?;
let num_instances = instances.len();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,10 @@ impl InstanceManager {
// self.wallet_daemons.values()
// }

pub fn get_wallet_daemon_mut(&mut self, id: InstanceId) -> Option<&mut WalletDaemonProcess> {
self.wallet_daemons.get_mut(&id)
}

pub fn get_instance_mut(&mut self, id: InstanceId) -> Option<&mut Instance> {
self.instances_mut().find(|i| i.id() == id)
}
Expand Down
52 changes: 51 additions & 1 deletion applications/tari_swarm_daemon/src/process_manager/manager.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright 2024 The Tari Project
// SPDX-License-Identifier: BSD-3-Clause

use std::{collections::HashMap, time::Duration};
use std::{collections::HashMap, fs::File, path::PathBuf, time::Duration};

use anyhow::{anyhow, Context};
use log::info;
Expand Down Expand Up @@ -103,6 +103,7 @@ impl ProcessManager {
Ok(())
}

#[allow(clippy::too_many_lines)]
async fn handle_request(&mut self, req: ProcessManagerRequest) -> anyhow::Result<()> {
use ProcessManagerRequest::*;
match req {
Expand Down Expand Up @@ -198,11 +199,60 @@ impl ProcessManager {
log::warn!("Request cancelled before response could be sent")
}
},
BurnFunds {
amount,
wallet_instance_id,
account_name,
out_path,
reply,
} => {
let result = self
.burn_funds_to_wallet_account(amount, wallet_instance_id, account_name, out_path)
.await;
if reply.send(result).is_err() {
log::warn!("Request cancelled before response could be sent")
}
},
}

Ok(())
}

async fn burn_funds_to_wallet_account(
&mut self,
amount: u64,
wallet_instance_id: InstanceId,
account_name: String,
out_path: PathBuf,
) -> anyhow::Result<PathBuf> {
let wallet = self
.instance_manager
.get_wallet_daemon_mut(wallet_instance_id)
.ok_or_else(|| {
anyhow!(
"No DanTariConsoleWallet instances {wallet_instance_id} found. Please start a wallet before \
burning funds"
)
})?;
let claim_public_key = wallet.get_account_public_key(account_name.clone()).await?;
let wallet = self
.instance_manager
.minotari_wallets()
.next()
.ok_or_else(|| anyhow!("No MinoTariConsoleWallet instances found"))?;

let proof = wallet.burn_funds(amount, &claim_public_key).await?;

let file_name = PathBuf::from(format!("burn_proof-{}.json", proof.tx_id));
let path = out_path.join(&file_name);
let mut file = File::create(path)?;
serde_json::to_writer_pretty(&mut file, &proof)?;

info!("🔥 Burned {amount} Tari to account {account_name}");
self.mine(10).await?;
Ok(file_name)
}

async fn register_all_validator_nodes(&mut self) -> anyhow::Result<()> {
let mut skip = vec![];
for vn in self.instance_manager.validator_nodes_mut() {
Expand Down
Loading

0 comments on commit ce0cb29

Please sign in to comment.