Skip to content
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
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ strum = { version = "0.26.1", features = ["derive"] }
bs58 = "0.5.0"
base64 = "0.22.1"
copypasta = "0.10.1"
dash-sdk = { git = "https://github.com/dashpay/platform", rev = "994f7627080e4ab490f1dbf0f09c0fb37d0fbbb0" }
dash-sdk = { git = "https://github.com/dashpay/platform", branch = "test/testWithoutSpan2" }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

⚠️ Potential issue

Using test/testWithoutSpan2 branch is risky for production dependency

Based on the verification results:

  1. The branch is actively being developed with 20 commits ahead of v1.6-dev
  2. It has 30 open pull requests targeting it, indicating it's an unstable testing branch
  3. The branch is unprotected, allowing direct pushes without review
  4. Recent commits show it's frequently merging changes from v1.6-dev

Recommendations:

  • Pin to a specific commit from v1.6-dev branch (e.g., a78c7b06ef6522478c22c3116632ceef483eee85)
  • Or wait until the changes in test/testWithoutSpan2 are merged into v1.6-dev and use that branch instead
🔗 Analysis chain

Verify the stability and production readiness of the test branch.

Switching from a fixed commit hash (994f7627080e4ab490f1dbf0f09c0fb37d0fbbb0) to a test branch (test/testWithoutSpan2) raises several concerns:

  1. The dependency is now less stable as it tracks a branch instead of a fixed commit
  2. The branch name suggests it's for testing purposes, which may not be suitable for production use
  3. The code could change unexpectedly when the branch is updated

Consider either:

  • Pinning to a specific commit hash from this branch
  • Using a more stable, production-ready branch

Let's verify the branch status and recent changes:


Let me gather more information about the relationship between this test branch and the main development branches.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check the branch status and recent commits

# Fetch the latest commits on the branch
gh api \
  -H "Accept: application/vnd.github+json" \
  repos/dashpay/platform/commits?sha=test/testWithoutSpan2 \
  --jq '.[0:5] | .[] | {sha: .sha, date: .commit.author.date, message: .commit.message}'

# Check if this is an active development branch
gh api \
  -H "Accept: application/vnd.github+json" \
  repos/dashpay/platform/branches/test/testWithoutSpan2 \
  --jq '{protected: .protected, behind_by: .behind_by}'

Length of output: 1104


Script:

#!/bin/bash
# Check the base branch and compare with main development branches
gh api \
  -H "Accept: application/vnd.github+json" \
  repos/dashpay/platform/compare/v1.6-dev...test/testWithoutSpan2 \
  --jq '{ahead_by: .ahead_by, behind_by: .behind_by, status: .status}'

# Get latest commits from v1.6-dev branch for comparison
gh api \
  -H "Accept: application/vnd.github+json" \
  repos/dashpay/platform/commits?sha=v1.6-dev \
  --jq '.[0:3] | .[] | {sha: .sha, date: .commit.author.date, message: .commit.message}'

# Check if there are any open PRs targeting this test branch
gh api \
  -H "Accept: application/vnd.github+json" \
  repos/dashpay/platform/pulls?head=test/testWithoutSpan2 \
  --jq 'length'

Length of output: 992

thiserror = "1"
serde = "1.0.197"
serde_json = "1.0.120"
Expand Down
38 changes: 36 additions & 2 deletions src/backend_task/contested_names/query_dpns_contested_resources.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::app::TaskResult;
use crate::context::AppContext;
use crate::model::proof_log_item::{ProofLogItem, RequestType};
use dash_sdk::dpp::data_contract::accessors::v0::DataContractV0Getters;
use dash_sdk::dpp::data_contract::document_type::accessors::DocumentTypeV0Getters;
use dash_sdk::drive::query::vote_polls_by_document_type_query::VotePollsByDocumentTypeQuery;
Expand Down Expand Up @@ -33,11 +34,44 @@ impl AppContext {
order_ascending: true,
};

let contested_resources =
ContestedResource::fetch_many(&sdk, query)
let (contested_resources, metadata, proof) =
ContestedResource::fetch_many_with_metadata_and_proof(&sdk, query.clone(), None)
.await
.map_err(|e| {
tracing::error!("error fetching contested resources: {}", e);
if let dash_sdk::Error::Proof(dash_sdk::ProofVerifierError::GroveDBError {
proof_bytes,
height,
time_ms,
error,
}) = &e
{
// Encode the query using bincode
let encoded_query =
match bincode::encode_to_vec(&query, bincode::config::standard())
.map_err(|encode_err| {
tracing::error!("error encoding query: {}", encode_err);
format!("error encoding query: {}", encode_err)
}) {
Ok(encoded_query) => encoded_query,
Err(e) => return e,
};

if let Err(e) = self
.db
.insert_proof_log_item(ProofLogItem {
request_type: RequestType::GetContestedResources,
request_bytes: encoded_query,
height: *height,
time_ms: *time_ms,
proof_bytes: proof_bytes.clone(),
error: Some(error.clone()),
})
.map_err(|e| e.to_string())
{
return e;
}
}
format!("error fetching contested resources: {}", e)
})?;

Expand Down
67 changes: 57 additions & 10 deletions src/database/initialization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::fs;
use std::path::Path;

pub const DEFAULT_DB_VERSION: u16 = 1;
pub const MIN_SUPPORTED_DB_VERSION: u16 = 1;
pub const CURRENT_DB_VERSION: u16 = 2;
pub const DEFAULT_NETWORK: &str = "dash";

impl Database {
Expand All @@ -16,17 +16,44 @@ impl Database {
self.set_default_version()?;
} else {
// Perform version check and back up and recreate the database if outdated.
if self.is_outdated()? {
self.backup_and_recreate_db(db_file_path)?;
self.create_tables()?;
self.set_default_version()?;
println!("Database reinitialized with default settings.");
if let Some(current_version) = self.is_outdated()? {
self.backup_db(db_file_path)?;
if let Err(e) = self.try_perform_migration(current_version, CURRENT_DB_VERSION) {
// The migration failed
println!("Migration failed: {:?}", e);
self.recreate_db(db_file_path)?;
self.create_tables()?;
self.set_default_version()?;
println!("Database reinitialized with default settings.");
}
}
}

Ok(())
}

fn apply_version_changes(&self, version: u16) -> rusqlite::Result<()> {
match version {
2 => {
self.initialize_proof_log_table()?;
}
_ => {}
}

Ok(())
}
fn try_perform_migration(
&self,
original_version: u16,
to_version: u16,
) -> rusqlite::Result<()> {
for version in (original_version + 1)..=to_version {
self.apply_version_changes(version)?;
self.update_database_version(version)?;
}
Ok(())
}

/// Checks if the `settings` table is empty or missing, indicating a first-time setup.
fn is_first_time_setup(&self) -> rusqlite::Result<bool> {
let conn = self.conn.lock().unwrap();
Expand All @@ -50,7 +77,7 @@ impl Database {
}

/// Checks if the current database version is below the minimum supported version.
fn is_outdated(&self) -> rusqlite::Result<bool> {
fn is_outdated(&self) -> rusqlite::Result<Option<u16>> {
let conn = self.conn.lock().unwrap();
let version: u16 = conn
.query_row(
Expand All @@ -59,11 +86,15 @@ impl Database {
|row| row.get(0),
)
.unwrap_or(0); // Default to version 0 if there's no version set
Ok(version < MIN_SUPPORTED_DB_VERSION)
if version < CURRENT_DB_VERSION {
Ok(Some(version))
} else {
Ok(None)
}
}

/// Backs up the existing database with a unique timestamped filename, recreates `data.db`, and refreshes the connection.
fn backup_and_recreate_db(&self, db_file_path: &Path) -> rusqlite::Result<()> {
fn backup_db(&self, db_file_path: &Path) -> rusqlite::Result<()> {
if db_file_path.exists() {
// Create a "backups" folder in the same directory as `data.db` if not exists
let backups_dir = db_file_path
Expand All @@ -82,11 +113,25 @@ impl Database {
let backup_path = backups_dir.join(backup_filename);

// Rename `data.db` to the unique backup file
fs::rename(db_file_path, &backup_path)
fs::copy(db_file_path, &backup_path)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Use appropriate error types for filesystem operations

In backup_db and recreate_db, wrapping std::io::Error into rusqlite::Error variants like ToSqlConversionFailure or FromSqlConversionFailure is misleading. These errors are related to filesystem operations, not SQL conversion, and misrepresenting them can make debugging more challenging.

Consider propagating the std::io::Error directly or mapping it to a more suitable error type. You might also define a custom error type to represent filesystem errors in your database operations.

Apply this diff to adjust error handling:

 // In backup_db
 fs::copy(db_file_path, &backup_path)
-    .map_err(|e| rusqlite::Error::ToSqlConversionFailure(e.into()))?;
+    .map_err(|e| rusqlite::Error::Other(Box::new(e)))?;

 // In recreate_db
 fs::remove_file(db_file_path).map_err(|e| {
-    rusqlite::Error::FromSqlConversionFailure(
-        0,
-        rusqlite::types::Type::Text,
-        Box::new(e),
-    )
+    rusqlite::Error::Other(Box::new(e))
 })?;

Alternatively, if you decide to define a custom error type:

#[derive(Debug)]
enum DatabaseError {
    IoError(std::io::Error),
    SqliteError(rusqlite::Error),
    // Add other variants as needed
}

impl From<std::io::Error> for DatabaseError {
    fn from(error: std::io::Error) -> Self {
        DatabaseError::IoError(error)
    }
}

impl From<rusqlite::Error> for DatabaseError {
    fn from(error: rusqlite::Error) -> Self {
        DatabaseError::SqliteError(error)
    }
}

Then update your method signatures to return Result<(), DatabaseError> and adjust error propagation accordingly.

Also applies to: 129-135

.map_err(|e| rusqlite::Error::ToSqlConversionFailure(e.into()))?;
println!("Old database backed up to {:?}", backup_path);
}
Ok(())
}

/// Backs up the existing database with a unique timestamped filename, recreates `data.db`, and refreshes the connection.
fn recreate_db(&self, db_file_path: &Path) -> rusqlite::Result<()> {
// Remove the existing database file if it exists
if db_file_path.exists() {
fs::remove_file(db_file_path).map_err(|e| {
rusqlite::Error::FromSqlConversionFailure(
0,
rusqlite::types::Type::Text,
Box::new(e),
)
})?;
}
// Create a new empty `data.db` file and set up the initial schema
let new_conn = Connection::open(db_file_path)?;

Expand Down Expand Up @@ -291,6 +336,8 @@ impl Database {
[],
)?;

self.initialize_proof_log_table()?;

Ok(())
}

Expand Down
1 change: 1 addition & 0 deletions src/database/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod contested_names;
mod contracts;
mod identities;
mod initialization;
mod proof_log;
mod settings;
mod utxo;
mod wallet;
Expand Down
125 changes: 125 additions & 0 deletions src/database/proof_log.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
use crate::database::Database;
use crate::model::proof_log_item::{ProofLogItem, RequestType};
use rusqlite::params;
use std::ops::Range;

impl Database {
pub fn initialize_proof_log_table(&self) -> rusqlite::Result<()> {
// Create the proof log tree
self.execute(
"CREATE TABLE IF NOT EXISTS proof_log (
proof_id INTEGER PRIMARY KEY AUTOINCREMENT,
request_type INTEGER NOT NULL,
request_bytes BLOB NOT NULL,
height INTEGER NOT NULL,
time_ms INTEGER NOT NULL,
proof_bytes BLOB NOT NULL,
error TEXT
)",
[],
)?;

// Create an index on request_type and time for combined queries
self.execute(
"CREATE INDEX IF NOT EXISTS idx_proof_log_request_type_time ON proof_log (request_type, time_ms)",
[],
)?;

// Create an index on time for queries ordered by time
self.execute(
"CREATE INDEX IF NOT EXISTS idx_proof_log_time ON proof_log (time_ms)",
[],
)?;

// Index for error, request_type, and time
self.execute(
"CREATE INDEX IF NOT EXISTS idx_proof_log_error_request_type_time ON proof_log (error, request_type, time_ms)",
[],
)?;

// Index for error and time
self.execute(
"CREATE INDEX IF NOT EXISTS idx_proof_log_error_time ON proof_log (error, time_ms)",
[],
)?;
Ok(())
}

/// Inserts a new ProofLogItem into the proof_log table
pub fn insert_proof_log_item(&self, item: ProofLogItem) -> rusqlite::Result<()> {
let conn = self.conn.lock().unwrap();

// Convert RequestType to u8
let request_type_int: u8 = item.request_type.into();

conn.execute(
"INSERT INTO proof_log (request_type, request_bytes, height, time_ms, proof_bytes, error)
VALUES (?, ?, ?, ?, ?, ?)",
params![
request_type_int,
item.request_bytes,
item.height,
item.time_ms,
item.proof_bytes,
item.error,
],
)?;

Ok(())
}

/// Retrieves ProofLogItems with options for filtering and pagination
pub fn get_proof_log_items(
&self,
only_get_errored: bool,
range: Range<u64>,
) -> rusqlite::Result<Vec<ProofLogItem>> {
let conn = self.conn.lock().unwrap();

// Build the query based on the only_get_errored flag
let mut query = String::from(
"SELECT request_type, request_bytes, height, time_ms, proof_bytes, error FROM proof_log",
);

if only_get_errored {
query.push_str(" WHERE error IS NOT NULL");
}

query.push_str(" ORDER BY time_ms DESC LIMIT ? OFFSET ?");

let mut stmt = conn.prepare(&query)?;

let proof_log_iter =
stmt.query_map(params![range.end - range.start, range.start], |row| {
let request_type_int: u8 = row.get(0)?;
let request_bytes: Vec<u8> = row.get(1)?;
let height: u64 = row.get(2)?;
let time_ms: u64 = row.get(3)?;
let proof_bytes: Vec<u8> = row.get(4)?;
let error: Option<String> = row.get(5)?;

// Convert u8 to RequestType
let request_type = RequestType::try_from(request_type_int).map_err(|_| {
rusqlite::Error::FromSqlConversionFailure(
request_type_int as usize,
rusqlite::types::Type::Integer,
Box::new(std::fmt::Error),
)
})?;
Comment on lines +102 to +108
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Use a more appropriate error type in FromSqlConversionFailure

The error handling when converting request_type_int to RequestType uses std::fmt::Error, which may not accurately represent the error context. Using a more descriptive error type will improve error reporting and debugging.

Consider applying the following change:

 let request_type = RequestType::try_from(request_type_int).map_err(|_| {
     rusqlite::Error::FromSqlConversionFailure(
         request_type_int as usize,
         rusqlite::types::Type::Integer,
-        Box::new(std::fmt::Error),
+        Box::new(std::io::Error::new(std::io::ErrorKind::InvalidData, format!("Invalid RequestType value: {}", request_type_int))),
     )
 })?;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let request_type = RequestType::try_from(request_type_int).map_err(|_| {
rusqlite::Error::FromSqlConversionFailure(
request_type_int as usize,
rusqlite::types::Type::Integer,
Box::new(std::fmt::Error),
)
})?;
let request_type = RequestType::try_from(request_type_int).map_err(|_| {
rusqlite::Error::FromSqlConversionFailure(
request_type_int as usize,
rusqlite::types::Type::Integer,
Box::new(std::io::Error::new(std::io::ErrorKind::InvalidData, format!("Invalid RequestType value: {}", request_type_int))),
)
})?;


Ok(ProofLogItem {
request_type,
request_bytes,
height,
time_ms,
proof_bytes,
error,
})
})?;

// Collect the results into a vector
let items: rusqlite::Result<Vec<ProofLogItem>> = proof_log_iter.collect();

items
}
}
14 changes: 14 additions & 0 deletions src/database/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,20 @@ impl Database {

Ok(())
}

/// Updates the database version in the settings table.
pub fn update_database_version(&self, new_version: u16) -> Result<()> {
// Ensure the database version is updated
self.execute(
"UPDATE settings
SET database_version = ?
WHERE id = 1",
params![new_version],
)?;

Ok(())
}

/// Retrieves the settings from the database.
pub fn get_settings(&self) -> Result<Option<(Network, RootScreenType, Option<PasswordInfo>)>> {
// Query the settings row
Expand Down
1 change: 1 addition & 0 deletions src/model/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod contested_name;
pub mod password_info;
pub mod proof_log_item;
pub mod qualified_contract;
pub mod qualified_identity;
pub mod wallet;
Loading