Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix!: propose and mint burnt L1 UTXOs in consensus #1124

Merged
merged 1 commit into from
Aug 28, 2024
Merged
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
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
Loading