|
@@ -16,22 +16,34 @@
|
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
*/
|
|
*/
|
|
|
|
|
|
|
|
-use log::info;
|
|
|
|
|
|
|
+use std::collections::HashMap;
|
|
|
|
|
+
|
|
|
|
|
+use log::{debug, error, info};
|
|
|
|
|
+use smol::io::Cursor;
|
|
|
use tinyjson::JsonValue;
|
|
use tinyjson::JsonValue;
|
|
|
|
|
|
|
|
use darkfi::{
|
|
use darkfi::{
|
|
|
blockchain::{
|
|
blockchain::{
|
|
|
- BlockInfo, HeaderHash, SLED_PENDING_TX_ORDER_TREE, SLED_PENDING_TX_TREE,
|
|
|
|
|
|
|
+ BlockInfo, BlockchainOverlay, HeaderHash, SLED_PENDING_TX_ORDER_TREE, SLED_PENDING_TX_TREE,
|
|
|
SLED_TX_LOCATION_TREE, SLED_TX_TREE,
|
|
SLED_TX_LOCATION_TREE, SLED_TX_TREE,
|
|
|
},
|
|
},
|
|
|
|
|
+ error::TxVerifyFailed,
|
|
|
|
|
+ runtime::vm_runtime::Runtime,
|
|
|
tx::Transaction,
|
|
tx::Transaction,
|
|
|
util::time::Timestamp,
|
|
util::time::Timestamp,
|
|
|
- validator::fees::GasData,
|
|
|
|
|
|
|
+ validator::fees::{circuit_gas_use, GasData, PALLAS_SCHNORR_SIGNATURE_FEE},
|
|
|
|
|
+ zk::VerifyingKey,
|
|
|
Error, Result,
|
|
Error, Result,
|
|
|
};
|
|
};
|
|
|
-use darkfi_sdk::tx::TransactionHash;
|
|
|
|
|
|
|
+use darkfi_sdk::{
|
|
|
|
|
+ crypto::{ContractId, PublicKey},
|
|
|
|
|
+ deploy::DeployParamsV1,
|
|
|
|
|
+ pasta::pallas,
|
|
|
|
|
+ tx::TransactionHash,
|
|
|
|
|
+};
|
|
|
|
|
+use darkfi_serial::{deserialize_async, serialize_async, AsyncDecodable, AsyncEncodable};
|
|
|
|
|
|
|
|
-use crate::ExplorerDb;
|
|
|
|
|
|
|
+use crate::ExplorerService;
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
#[derive(Debug, Clone)]
|
|
|
/// Structure representing a `TRANSACTIONS_TABLE` record.
|
|
/// Structure representing a `TRANSACTIONS_TABLE` record.
|
|
@@ -74,7 +86,7 @@ impl TransactionRecord {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-impl ExplorerDb {
|
|
|
|
|
|
|
+impl ExplorerService {
|
|
|
/// Resets transactions in the database by clearing transaction-related trees, returning an Ok result on success.
|
|
/// Resets transactions in the database by clearing transaction-related trees, returning an Ok result on success.
|
|
|
pub fn reset_transactions(&self) -> Result<()> {
|
|
pub fn reset_transactions(&self) -> Result<()> {
|
|
|
// Initialize transaction trees to reset
|
|
// Initialize transaction trees to reset
|
|
@@ -83,7 +95,7 @@ impl ExplorerDb {
|
|
|
|
|
|
|
|
// Iterate over each associated transaction tree and delete its contents
|
|
// Iterate over each associated transaction tree and delete its contents
|
|
|
for tree_name in &trees_to_reset {
|
|
for tree_name in &trees_to_reset {
|
|
|
- let tree = &self.blockchain.sled_db.open_tree(tree_name)?;
|
|
|
|
|
|
|
+ let tree = &self.db.blockchain.sled_db.open_tree(tree_name)?;
|
|
|
tree.clear()?;
|
|
tree.clear()?;
|
|
|
let tree_name_str = std::str::from_utf8(tree_name)?;
|
|
let tree_name_str = std::str::from_utf8(tree_name)?;
|
|
|
info!(target: "blockchain-explorer::blocks", "Successfully reset transaction tree: {tree_name_str}");
|
|
info!(target: "blockchain-explorer::blocks", "Successfully reset transaction tree: {tree_name_str}");
|
|
@@ -94,7 +106,7 @@ impl ExplorerDb {
|
|
|
|
|
|
|
|
/// Provides the transaction count of all the transactions in the explorer database.
|
|
/// Provides the transaction count of all the transactions in the explorer database.
|
|
|
pub fn get_transaction_count(&self) -> usize {
|
|
pub fn get_transaction_count(&self) -> usize {
|
|
|
- self.blockchain.txs_len()
|
|
|
|
|
|
|
+ self.db.blockchain.txs_len()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// Fetches all known transactions from the database.
|
|
/// Fetches all known transactions from the database.
|
|
@@ -104,7 +116,7 @@ impl ExplorerDb {
|
|
|
/// it returns an empty vector.
|
|
/// it returns an empty vector.
|
|
|
pub fn get_transactions(&self) -> Result<Vec<TransactionRecord>> {
|
|
pub fn get_transactions(&self) -> Result<Vec<TransactionRecord>> {
|
|
|
// Retrieve all transactions and handle any errors encountered
|
|
// Retrieve all transactions and handle any errors encountered
|
|
|
- let txs = self.blockchain.transactions.get_all().map_err(|e| {
|
|
|
|
|
|
|
+ let txs = self.db.blockchain.transactions.get_all().map_err(|e| {
|
|
|
Error::DatabaseError(format!("[get_transactions] Trxs retrieval: {e:?}"))
|
|
Error::DatabaseError(format!("[get_transactions] Trxs retrieval: {e:?}"))
|
|
|
})?;
|
|
})?;
|
|
|
|
|
|
|
@@ -134,7 +146,7 @@ impl ExplorerDb {
|
|
|
.map_err(|_| Error::ParseFailed("[get_transactions_by_header_hash] Invalid hash"))?;
|
|
.map_err(|_| Error::ParseFailed("[get_transactions_by_header_hash] Invalid hash"))?;
|
|
|
|
|
|
|
|
// Fetch block by hash and handle encountered errors
|
|
// Fetch block by hash and handle encountered errors
|
|
|
- let block = match self.blockchain.get_blocks_by_hash(&[header_hash]) {
|
|
|
|
|
|
|
+ let block = match self.db.blockchain.get_blocks_by_hash(&[header_hash]) {
|
|
|
Ok(blocks) => blocks.first().cloned().unwrap(),
|
|
Ok(blocks) => blocks.first().cloned().unwrap(),
|
|
|
Err(Error::BlockNotFound(_)) => return Ok(vec![]),
|
|
Err(Error::BlockNotFound(_)) => return Ok(vec![]),
|
|
|
Err(e) => {
|
|
Err(e) => {
|
|
@@ -161,7 +173,7 @@ impl ExplorerDb {
|
|
|
&self,
|
|
&self,
|
|
|
tx_hash: &TransactionHash,
|
|
tx_hash: &TransactionHash,
|
|
|
) -> Result<Option<TransactionRecord>> {
|
|
) -> Result<Option<TransactionRecord>> {
|
|
|
- let tx_store = &self.blockchain.transactions;
|
|
|
|
|
|
|
+ let tx_store = &self.db.blockchain.transactions;
|
|
|
|
|
|
|
|
// Attempt to retrieve the transaction using the provided hash handling any potential errors
|
|
// Attempt to retrieve the transaction using the provided hash handling any potential errors
|
|
|
let tx_opt = &tx_store.get(&[*tx_hash], false).map_err(|e| {
|
|
let tx_opt = &tx_store.get(&[*tx_hash], false).map_err(|e| {
|
|
@@ -183,7 +195,7 @@ impl ExplorerDb {
|
|
|
fn get_tx_block_info(&self, tx_hash: &TransactionHash) -> Result<Option<BlockInfo>> {
|
|
fn get_tx_block_info(&self, tx_hash: &TransactionHash) -> Result<Option<BlockInfo>> {
|
|
|
// Retrieve the location of the transaction
|
|
// Retrieve the location of the transaction
|
|
|
let location =
|
|
let location =
|
|
|
- self.blockchain.transactions.get_location(&[*tx_hash], false).map_err(|e| {
|
|
|
|
|
|
|
+ self.db.blockchain.transactions.get_location(&[*tx_hash], false).map_err(|e| {
|
|
|
Error::DatabaseError(format!(
|
|
Error::DatabaseError(format!(
|
|
|
"[get_tx_block_info] Location retrieval failed: {e:?}"
|
|
"[get_tx_block_info] Location retrieval failed: {e:?}"
|
|
|
))
|
|
))
|
|
@@ -193,7 +205,7 @@ impl ExplorerDb {
|
|
|
let header_hash = match location {
|
|
let header_hash = match location {
|
|
|
None => return Ok(None),
|
|
None => return Ok(None),
|
|
|
Some((block_height, _)) => {
|
|
Some((block_height, _)) => {
|
|
|
- self.blockchain.blocks.get_order(&[block_height], false).map_err(|e| {
|
|
|
|
|
|
|
+ self.db.blockchain.blocks.get_order(&[block_height], false).map_err(|e| {
|
|
|
Error::DatabaseError(format!(
|
|
Error::DatabaseError(format!(
|
|
|
"[get_tx_block_info] Block retrieval failed: {e:?}"
|
|
"[get_tx_block_info] Block retrieval failed: {e:?}"
|
|
|
))
|
|
))
|
|
@@ -218,7 +230,7 @@ impl ExplorerDb {
|
|
|
/// the specified [`HeaderHash`]. It returns the associated [`BlockInfo`] if found,
|
|
/// the specified [`HeaderHash`]. It returns the associated [`BlockInfo`] if found,
|
|
|
/// or `None` when not found.
|
|
/// or `None` when not found.
|
|
|
fn get_block_info(&self, header_hash: HeaderHash) -> Result<Option<BlockInfo>> {
|
|
fn get_block_info(&self, header_hash: HeaderHash) -> Result<Option<BlockInfo>> {
|
|
|
- match self.blockchain.get_blocks_by_hash(&[header_hash]) {
|
|
|
|
|
|
|
+ match self.db.blockchain.get_blocks_by_hash(&[header_hash]) {
|
|
|
Err(Error::BlockNotFound(_)) => Ok(None),
|
|
Err(Error::BlockNotFound(_)) => Ok(None),
|
|
|
Ok(block_info) => Ok(block_info.into_iter().next()),
|
|
Ok(block_info) => Ok(block_info.into_iter().next()),
|
|
|
Err(e) => Err(Error::DatabaseError(format!(
|
|
Err(e) => Err(Error::DatabaseError(format!(
|
|
@@ -227,6 +239,202 @@ impl ExplorerDb {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /// Calculates the gas data for a given transaction, returning a [`GasData`] instance detailing
|
|
|
|
|
+ /// various aspects of the gas usage.
|
|
|
|
|
+ pub async fn calculate_tx_gas_data(
|
|
|
|
|
+ &self,
|
|
|
|
|
+ tx: &Transaction,
|
|
|
|
|
+ verify_fee: bool,
|
|
|
|
|
+ ) -> Result<GasData> {
|
|
|
|
|
+ let tx_hash = tx.hash();
|
|
|
|
|
+
|
|
|
|
|
+ let overlay = BlockchainOverlay::new(&self.db.blockchain)?;
|
|
|
|
|
+
|
|
|
|
|
+ // Gas accumulators
|
|
|
|
|
+ let mut total_gas_used = 0;
|
|
|
|
|
+ let mut zk_circuit_gas_used = 0;
|
|
|
|
|
+ let mut wasm_gas_used = 0;
|
|
|
|
|
+ let mut deploy_gas_used = 0;
|
|
|
|
|
+ let mut gas_paid = 0;
|
|
|
|
|
+
|
|
|
|
|
+ // Table of public inputs used for ZK proof verification
|
|
|
|
|
+ let mut zkp_table = vec![];
|
|
|
|
|
+ // Table of public keys used for signature verification
|
|
|
|
|
+ let mut sig_table = vec![];
|
|
|
|
|
+
|
|
|
|
|
+ // Index of the Fee-paying call
|
|
|
|
|
+ let fee_call_idx = 0;
|
|
|
|
|
+
|
|
|
|
|
+ // Map of ZK proof verifying keys for the transaction
|
|
|
|
|
+ let mut verifying_keys: HashMap<[u8; 32], HashMap<String, VerifyingKey>> = HashMap::new();
|
|
|
|
|
+ for call in &tx.calls {
|
|
|
|
|
+ verifying_keys.insert(call.data.contract_id.to_bytes(), HashMap::new());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ let block_target = self.db.blockchain.blocks.get_last()?.0 + 1;
|
|
|
|
|
+
|
|
|
|
|
+ // We'll also take note of all the circuits in a Vec so we can calculate their verification cost.
|
|
|
|
|
+ let mut circuits_to_verify = vec![];
|
|
|
|
|
+
|
|
|
|
|
+ // Iterate over all calls to get the metadata
|
|
|
|
|
+ for (idx, call) in tx.calls.iter().enumerate() {
|
|
|
|
|
+ // Transaction must not contain a Money::PoWReward(0x02) call
|
|
|
|
|
+ if call.data.is_money_pow_reward() {
|
|
|
|
|
+ error!(target: "block_explorer::calculate_tx_gas_data", "Reward transaction detected");
|
|
|
|
|
+ return Err(TxVerifyFailed::ErroneousTxs(vec![tx.clone()]).into())
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Write the actual payload data
|
|
|
|
|
+ let mut payload = vec![];
|
|
|
|
|
+ tx.calls.encode_async(&mut payload).await?;
|
|
|
|
|
+
|
|
|
|
|
+ let wasm = overlay.lock().unwrap().contracts.get(call.data.contract_id)?;
|
|
|
|
|
+
|
|
|
|
|
+ let mut runtime = Runtime::new(
|
|
|
|
|
+ &wasm,
|
|
|
|
|
+ overlay.clone(),
|
|
|
|
|
+ call.data.contract_id,
|
|
|
|
|
+ block_target,
|
|
|
|
|
+ block_target,
|
|
|
|
|
+ tx_hash,
|
|
|
|
|
+ idx as u8,
|
|
|
|
|
+ )?;
|
|
|
|
|
+
|
|
|
|
|
+ let metadata = runtime.metadata(&payload)?;
|
|
|
|
|
+
|
|
|
|
|
+ // Decode the metadata retrieved from the execution
|
|
|
|
|
+ let mut decoder = Cursor::new(&metadata);
|
|
|
|
|
+
|
|
|
|
|
+ // The tuple is (zkas_ns, public_inputs)
|
|
|
|
|
+ let zkp_pub: Vec<(String, Vec<pallas::Base>)> =
|
|
|
|
|
+ AsyncDecodable::decode_async(&mut decoder).await?;
|
|
|
|
|
+ let sig_pub: Vec<PublicKey> = AsyncDecodable::decode_async(&mut decoder).await?;
|
|
|
|
|
+
|
|
|
|
|
+ if decoder.position() != metadata.len() as u64 {
|
|
|
|
|
+ error!(
|
|
|
|
|
+ target: "block_explorer::calculate_tx_gas_data",
|
|
|
|
|
+ "[BLOCK_EXPLORER] Failed decoding entire metadata buffer for {}:{}", tx_hash, idx,
|
|
|
|
|
+ );
|
|
|
|
|
+ return Err(TxVerifyFailed::ErroneousTxs(vec![tx.clone()]).into())
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Here we'll look up verifying keys and insert them into the per-contract map.
|
|
|
|
|
+ for (zkas_ns, _) in &zkp_pub {
|
|
|
|
|
+ let inner_vk_map =
|
|
|
|
|
+ verifying_keys.get_mut(&call.data.contract_id.to_bytes()).unwrap();
|
|
|
|
|
+
|
|
|
|
|
+ // TODO: This will be a problem in case of ::deploy, unless we force a different
|
|
|
|
|
+ // namespace and disable updating existing circuit. Might be a smart idea to do
|
|
|
|
|
+ // so in order to have to care less about being able to verify historical txs.
|
|
|
|
|
+ if inner_vk_map.contains_key(zkas_ns.as_str()) {
|
|
|
|
|
+ continue
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ let (zkbin, vk) =
|
|
|
|
|
+ overlay.lock().unwrap().contracts.get_zkas(&call.data.contract_id, zkas_ns)?;
|
|
|
|
|
+
|
|
|
|
|
+ inner_vk_map.insert(zkas_ns.to_string(), vk);
|
|
|
|
|
+ circuits_to_verify.push(zkbin);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ zkp_table.push(zkp_pub);
|
|
|
|
|
+ sig_table.push(sig_pub);
|
|
|
|
|
+
|
|
|
|
|
+ // Contracts are not included within blocks. They need to be deployed off-chain so that they can be accessed and utilized for fee data computation
|
|
|
|
|
+ if call.data.is_deployment()
|
|
|
|
|
+ /* DeployV1 */
|
|
|
|
|
+ {
|
|
|
|
|
+ // Deserialize the deployment parameters
|
|
|
|
|
+ let deploy_params: DeployParamsV1 = deserialize_async(&call.data.data[1..]).await?;
|
|
|
|
|
+ let deploy_cid = ContractId::derive_public(deploy_params.public_key);
|
|
|
|
|
+
|
|
|
|
|
+ // Instantiate the new deployment runtime
|
|
|
|
|
+ let mut deploy_runtime = Runtime::new(
|
|
|
|
|
+ &deploy_params.wasm_bincode,
|
|
|
|
|
+ overlay.clone(),
|
|
|
|
|
+ deploy_cid,
|
|
|
|
|
+ block_target,
|
|
|
|
|
+ block_target,
|
|
|
|
|
+ tx_hash,
|
|
|
|
|
+ idx as u8,
|
|
|
|
|
+ )?;
|
|
|
|
|
+
|
|
|
|
|
+ deploy_runtime.deploy(&deploy_params.ix)?;
|
|
|
|
|
+
|
|
|
|
|
+ deploy_gas_used = deploy_runtime.gas_used();
|
|
|
|
|
+
|
|
|
|
|
+ // Append the used deployment gas
|
|
|
|
|
+ total_gas_used += deploy_gas_used;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // At this point we're done with the call and move on to the next one.
|
|
|
|
|
+ // Accumulate the WASM gas used.
|
|
|
|
|
+ wasm_gas_used = runtime.gas_used();
|
|
|
|
|
+
|
|
|
|
|
+ // Append the used wasm gas
|
|
|
|
|
+ total_gas_used += wasm_gas_used;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // The signature fee is tx_size + fixed_sig_fee * n_signatures
|
|
|
|
|
+ let signature_gas_used = (PALLAS_SCHNORR_SIGNATURE_FEE * tx.signatures.len() as u64) +
|
|
|
|
|
+ serialize_async(tx).await.len() as u64;
|
|
|
|
|
+
|
|
|
|
|
+ // Append the used signature gas
|
|
|
|
|
+ total_gas_used += signature_gas_used;
|
|
|
|
|
+
|
|
|
|
|
+ // The ZK circuit fee is calculated using a function in validator/fees.rs
|
|
|
|
|
+ for zkbin in circuits_to_verify.iter() {
|
|
|
|
|
+ zk_circuit_gas_used = circuit_gas_use(zkbin);
|
|
|
|
|
+
|
|
|
|
|
+ // Append the used zk circuit gas
|
|
|
|
|
+ total_gas_used += zk_circuit_gas_used;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if verify_fee {
|
|
|
|
|
+ // Deserialize the fee call to find the paid fee
|
|
|
|
|
+ let fee: u64 = match deserialize_async(&tx.calls[fee_call_idx].data.data[1..9]).await {
|
|
|
|
|
+ Ok(v) => v,
|
|
|
|
|
+ Err(e) => {
|
|
|
|
|
+ error!(
|
|
|
|
|
+ target: "block_explorer::calculate_tx_gas_data",
|
|
|
|
|
+ "[VALIDATOR] Failed deserializing tx {} fee call: {}", tx_hash, e,
|
|
|
|
|
+ );
|
|
|
|
|
+ return Err(TxVerifyFailed::InvalidFee.into())
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // TODO: This counts 1 gas as 1 token unit. Pricing should be better specified.
|
|
|
|
|
+ // Check that enough fee has been paid for the used gas in this transaction.
|
|
|
|
|
+ if total_gas_used > fee {
|
|
|
|
|
+ error!(
|
|
|
|
|
+ target: "block_explorer::calculate_tx_gas_data",
|
|
|
|
|
+ "[VALIDATOR] Transaction {} has insufficient fee. Required: {}, Paid: {}",
|
|
|
|
|
+ tx_hash, total_gas_used, fee,
|
|
|
|
|
+ );
|
|
|
|
|
+ return Err(TxVerifyFailed::InsufficientFee.into())
|
|
|
|
|
+ }
|
|
|
|
|
+ debug!(target: "block_explorer::calculate_tx_gas_data", "The gas paid for transaction {}: {}", tx_hash, gas_paid);
|
|
|
|
|
+
|
|
|
|
|
+ // Store paid fee
|
|
|
|
|
+ gas_paid = fee;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Commit changes made to the overlay
|
|
|
|
|
+ overlay.lock().unwrap().overlay.lock().unwrap().apply()?;
|
|
|
|
|
+
|
|
|
|
|
+ let fee_data = GasData {
|
|
|
|
|
+ paid: gas_paid,
|
|
|
|
|
+ wasm: wasm_gas_used,
|
|
|
|
|
+ zk_circuits: zk_circuit_gas_used,
|
|
|
|
|
+ signatures: signature_gas_used,
|
|
|
|
|
+ deployments: deploy_gas_used,
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ debug!(target: "block_explorer::calculate_tx_gas_data", "The total gas usage for transaction {}: {:?}", tx_hash, fee_data);
|
|
|
|
|
+
|
|
|
|
|
+ Ok(fee_data)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/// Converts a [`Transaction`] and its associated block information into a [`TransactionRecord`].
|
|
/// Converts a [`Transaction`] and its associated block information into a [`TransactionRecord`].
|
|
|
///
|
|
///
|
|
|
/// This auxiliary function first retrieves the gas data associated with the provided transaction.
|
|
/// This auxiliary function first retrieves the gas data associated with the provided transaction.
|
|
@@ -239,7 +447,7 @@ impl ExplorerDb {
|
|
|
tx: &Transaction,
|
|
tx: &Transaction,
|
|
|
) -> Result<TransactionRecord> {
|
|
) -> Result<TransactionRecord> {
|
|
|
// Fetch the gas data associated with the transaction
|
|
// Fetch the gas data associated with the transaction
|
|
|
- let gas_data_option = self.metrics_store.get_tx_gas_data(&tx.hash()).map_err(|e| {
|
|
|
|
|
|
|
+ let gas_data_option = self.db.metrics_store.get_tx_gas_data(&tx.hash()).map_err(|e| {
|
|
|
Error::DatabaseError(format!(
|
|
Error::DatabaseError(format!(
|
|
|
"[to_tx_record] Failed to fetch the gas data associated with transaction {}: {e:?}",
|
|
"[to_tx_record] Failed to fetch the gas data associated with transaction {}: {e:?}",
|
|
|
tx.hash()
|
|
tx.hash()
|