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
118 changes: 116 additions & 2 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@ use crate::app_dir::{
app_user_data_file_path, copy_env_file_if_not_exists,
create_app_user_data_directory_if_not_exists,
};
use crate::backend_task::contested_names::ContestedResourceTask;
use crate::backend_task::core::CoreItem;
use crate::backend_task::{BackendTask, BackendTaskSuccessResult};
use crate::components::core_zmq_listener::{CoreZMQListener, ZMQMessage};
use crate::context::AppContext;
use crate::database::Database;
use crate::logging::initialize_logger;
use crate::ui::document_query_screen::DocumentQueryScreen;
use crate::ui::dpns_contested_names_screen::{DPNSContestedNamesScreen, DPNSSubscreen};
use crate::ui::dpns_contested_names_screen::{
DPNSContestedNamesScreen, DPNSSubscreen, IndividualVoteCastingStatus,
};
use crate::ui::identities::identities_screen::IdentitiesScreen;
use crate::ui::network_chooser_screen::NetworkChooserScreen;
use crate::ui::tool_screens::proof_log_screen::ProofLogScreen;
Expand All @@ -18,12 +21,13 @@ use crate::ui::wallet::wallets_screen::WalletsBalancesScreen;
use crate::ui::withdrawal_statuses_screen::WithdrawsStatusScreen;
use crate::ui::{MessageType, RootScreenType, Screen, ScreenLike, ScreenType};
use dash_sdk::dpp::dashcore::Network;
use dash_sdk::dpp::identity::accessors::IdentityGettersV0;
use derive_more::From;
use eframe::{egui, App};
use std::collections::BTreeMap;
use std::ops::BitOrAssign;
use std::sync::{mpsc, Arc};
use std::time::Instant;
use std::time::{Duration, Instant, SystemTime};
use std::vec;
use tokio::sync::mpsc as tokiompsc;

Expand Down Expand Up @@ -56,11 +60,13 @@ pub struct AppState {
pub task_result_sender: tokiompsc::Sender<TaskResult>, // Channel sender for sending task results
pub task_result_receiver: tokiompsc::Receiver<TaskResult>, // Channel receiver for receiving task results
last_repaint: Instant, // Track the last time we requested a repaint
last_scheduled_vote_check: Instant, // Last time we checked if there are scheduled masternode votes to cast
}

#[derive(Debug, Clone, PartialEq)]
pub enum DesiredAppAction {
None,
Refresh,
PopScreen,
GoToMainScreen,
SwitchNetwork(Network),
Expand All @@ -72,6 +78,7 @@ impl DesiredAppAction {
pub fn create_action(&self, app_context: &Arc<AppContext>) -> AppAction {
match self {
DesiredAppAction::None => AppAction::None,
DesiredAppAction::Refresh => AppAction::Refresh,
DesiredAppAction::PopScreen => AppAction::PopScreen,
DesiredAppAction::GoToMainScreen => AppAction::GoToMainScreen,
DesiredAppAction::AddScreenType(screen_type) => {
Expand All @@ -88,11 +95,13 @@ impl DesiredAppAction {
#[derive(Debug, PartialEq)]
pub enum AppAction {
None,
Refresh,
PopScreen,
PopScreenAndRefresh,
GoToMainScreen,
SwitchNetwork(Network),
SetMainScreen(RootScreenType),
SetMainScreenThenPopScreen(RootScreenType),
AddScreen(Screen),
PopThenAddScreenToMainScreen(RootScreenType, Screen),
BackendTask(BackendTask),
Expand Down Expand Up @@ -145,6 +154,8 @@ impl AppState {
DPNSContestedNamesScreen::new(&mainnet_app_context, DPNSSubscreen::Past);
let mut dpns_my_usernames_screen =
DPNSContestedNamesScreen::new(&mainnet_app_context, DPNSSubscreen::Owned);
let mut dpns_scheduled_votes_screen =
DPNSContestedNamesScreen::new(&mainnet_app_context, DPNSSubscreen::ScheduledVotes);
let mut transition_visualizer_screen =
TransitionVisualizerScreen::new(&mainnet_app_context);
let mut proof_log_screen = ProofLogScreen::new(&mainnet_app_context);
Expand Down Expand Up @@ -187,6 +198,10 @@ impl AppState {
DPNSContestedNamesScreen::new(&testnet_app_context, DPNSSubscreen::Past);
dpns_my_usernames_screen =
DPNSContestedNamesScreen::new(&testnet_app_context, DPNSSubscreen::Owned);
dpns_scheduled_votes_screen = DPNSContestedNamesScreen::new(
&testnet_app_context,
DPNSSubscreen::ScheduledVotes,
);
transition_visualizer_screen = TransitionVisualizerScreen::new(testnet_app_context);
document_query_screen = DocumentQueryScreen::new(testnet_app_context);
wallets_balances_screen = WalletsBalancesScreen::new(testnet_app_context);
Expand Down Expand Up @@ -244,6 +259,10 @@ impl AppState {
RootScreenType::RootScreenDPNSOwnedNames,
Screen::DPNSContestedNamesScreen(dpns_my_usernames_screen),
),
(
RootScreenType::RootScreenDPNSScheduledVotes,
Screen::DPNSContestedNamesScreen(dpns_scheduled_votes_screen),
),
(
RootScreenType::RootScreenWalletsBalances,
Screen::WalletsBalancesScreen(wallets_balances_screen),
Expand Down Expand Up @@ -281,6 +300,7 @@ impl AppState {
task_result_sender,
task_result_receiver,
last_repaint,
last_scheduled_vote_check: Instant::now(),
}
}

Expand Down Expand Up @@ -396,6 +416,7 @@ impl App for AppState {
*status = event;
}
}

// Poll the receiver for any new task results
while let Ok(task_result) = self.task_result_receiver.try_recv() {
// Handle the result on the main thread
Expand All @@ -404,6 +425,9 @@ impl App for AppState {
BackendTaskSuccessResult::None => {
self.visible_screen_mut().pop_on_success();
}
BackendTaskSuccessResult::Refresh => {
self.visible_screen_mut().refresh();
}
BackendTaskSuccessResult::Message(message) => {
self.visible_screen_mut()
.display_message(&message, MessageType::Success);
Expand All @@ -417,6 +441,16 @@ impl App for AppState {
BackendTaskSuccessResult::SuccessfulVotes(_) => {
self.visible_screen_mut().refresh();
}
BackendTaskSuccessResult::CastScheduledVote(vote) => {
let _ = self
.current_app_context()
.mark_vote_executed(vote.voter_id.as_slice(), vote.contested_name);
self.visible_screen_mut().display_message(
"Successfully cast scheduled vote",
MessageType::Success,
);
self.visible_screen_mut().refresh();
}
BackendTaskSuccessResult::WithdrawalStatus(_) => {
self.visible_screen_mut().display_task_result(message);
}
Expand Down Expand Up @@ -478,6 +512,75 @@ impl App for AppState {
}
}

// Check if there are scheduled masternode votes to cast and if so, cast them
let now = Instant::now();
if now.duration_since(self.last_scheduled_vote_check) > Duration::from_secs(60) {
self.last_scheduled_vote_check = now;
let app_context = self.current_app_context();

// Query the database
let db_votes = match app_context.get_scheduled_votes() {
Ok(votes) => votes,
Err(e) => {
eprintln!("Error querying scheduled votes: {}", e);
return;
}
};

// Filter due votes
let current_time = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_millis() as u64;
let due_votes: Vec<_> = db_votes
.into_iter()
.filter(|v| {
v.unix_timestamp <= current_time
&& !v.executed_successfully
&& !(v.unix_timestamp + 120000 < current_time) // Don't cast votes more than 2 minutes behind current time
})
.collect();

// For each due vote, construct a BackendTask and handle it
if !due_votes.is_empty() {
let local_identities = match app_context.load_local_voting_identities() {
Ok(identities) => identities,
Err(e) => {
eprintln!("Error querying local voting identities: {}", e);
return;
}
};

for vote in due_votes {
if let Some(voter) = local_identities
.iter()
.find(|i| i.identity.id() == vote.voter_id)
{
let dpns_screen = self
.main_screens
.get_mut(&RootScreenType::RootScreenDPNSScheduledVotes)
.unwrap();
if let Screen::DPNSContestedNamesScreen(screen) = dpns_screen {
screen.vote_cast_in_progress = true;
screen
.scheduled_votes
.lock()
.unwrap()
.iter_mut()
.find(|(v, _)| v == &vote)
.map(|(_, s)| *s = IndividualVoteCastingStatus::InProgress);
}
let task = BackendTask::ContestedResourceTask(
ContestedResourceTask::CastScheduledVote(vote, voter.clone()),
);
self.handle_backend_task(task);
} else {
eprintln!("Voter not found for scheduled vote: {:?}", vote);
}
}
}
}

// Use a timer to repaint the UI every 0.05 seconds
ctx.request_repaint_after(std::time::Duration::from_millis(50));

Expand All @@ -486,6 +589,7 @@ impl App for AppState {
match action {
AppAction::AddScreen(screen) => self.screen_stack.push(screen),
AppAction::None => {}
AppAction::Refresh => self.visible_screen_mut().refresh(),
AppAction::PopScreen => {
if !self.screen_stack.is_empty() {
self.screen_stack.pop();
Expand Down Expand Up @@ -515,6 +619,16 @@ impl App for AppState {
.update_settings(root_screen_type)
.ok();
}
AppAction::SetMainScreenThenPopScreen(root_screen_type) => {
self.selected_main_screen = root_screen_type;
self.active_root_screen_mut().refresh_on_arrival();
self.current_app_context()
.update_settings(root_screen_type)
.ok();
if !self.screen_stack.is_empty() {
self.screen_stack.pop();
}
}
AppAction::SwitchNetwork(network) => {
self.change_network(network);
self.current_app_context()
Expand Down
47 changes: 42 additions & 5 deletions src/backend_task/contested_names/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,29 @@ use crate::backend_task::BackendTaskSuccessResult;
use crate::context::AppContext;
use crate::model::qualified_identity::QualifiedIdentity;
use dash_sdk::dpp::voting::vote_choices::resource_vote_choice::ResourceVoteChoice;
use dash_sdk::platform::Identifier;
use dash_sdk::Sdk;
use std::sync::Arc;
use tokio::sync::mpsc;

#[derive(Debug, Clone, PartialEq)]
pub(crate) enum ContestedResourceTask {
QueryDPNSContestedResources,
QueryDPNSVoteContenders(String),
VoteOnDPNSName(String, ResourceVoteChoice, Vec<QualifiedIdentity>),
ScheduleDPNSVotes(Vec<ScheduledDPNSVote>),
CastScheduledVote(ScheduledDPNSVote, QualifiedIdentity),
ClearAllScheduledVotes,
ClearExecutedScheduledVotes,
DeleteScheduledVote(Identifier, String),
}

#[derive(Debug, Clone, PartialEq)]
pub struct ScheduledDPNSVote {
pub contested_name: String,
pub voter_id: Identifier,
pub choice: ResourceVoteChoice,
pub unix_timestamp: u64,
pub executed_successfully: bool,
}

impl AppContext {
Expand All @@ -31,14 +45,37 @@ impl AppContext {
.query_dpns_contested_resources(sdk, sender)
.await
.map(|_| BackendTaskSuccessResult::None),
ContestedResourceTask::QueryDPNSVoteContenders(name) => self
.query_dpns_vote_contenders(name, sdk, sender)
.await
.map(|_| BackendTaskSuccessResult::None),
ContestedResourceTask::VoteOnDPNSName(name, vote_choice, voters) => {
self.vote_on_dpns_name(name, *vote_choice, voters, sdk, sender)
.await
}
ContestedResourceTask::ScheduleDPNSVotes(scheduled_votes) => self
.insert_scheduled_votes(scheduled_votes)
.map(|_| BackendTaskSuccessResult::Message("Votes scheduled".to_string()))
.map_err(|e| format!("Error inserting scheduled votes: {}", e.to_string())),
ContestedResourceTask::CastScheduledVote(scheduled_vote, voter) => self
.vote_on_dpns_name(
&scheduled_vote.contested_name,
scheduled_vote.choice,
&vec![voter.clone()],
sdk,
sender,
)
.await
.map(|_| BackendTaskSuccessResult::CastScheduledVote(scheduled_vote.clone()))
.map_err(|e| format!("Error casting scheduled vote: {}", e.to_string())),
ContestedResourceTask::ClearAllScheduledVotes => self
.clear_all_scheduled_votes()
.map(|_| BackendTaskSuccessResult::Refresh)
.map_err(|e| format!("Error clearing all scheduled votes: {}", e.to_string())),
ContestedResourceTask::ClearExecutedScheduledVotes => self
.clear_executed_scheduled_votes()
.map(|_| BackendTaskSuccessResult::Refresh)
.map_err(|e| format!("Error clearing executed scheduled votes: {}", e.to_string())),
ContestedResourceTask::DeleteScheduledVote(voter_id, contested_name) => self
.delete_scheduled_vote(voter_id.as_slice(), contested_name)
.map(|_| BackendTaskSuccessResult::Refresh)
.map_err(|e| format!("Error clearing scheduled vote: {}", e.to_string())),
}
}
}
16 changes: 12 additions & 4 deletions src/backend_task/contested_names/vote_on_dpns_name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,23 @@ impl AppContext {
vote_choice: ResourceVoteChoice,
voters: &Vec<QualifiedIdentity>,
sdk: &Sdk,
_sender: mpsc::Sender<TaskResult>,
sender: mpsc::Sender<TaskResult>,
) -> Result<BackendTaskSuccessResult, String> {
// Send a refresh task to the frontend
// In particular, use this to show the cast is in progress on Scheduled Votes Screen
sender
.send(TaskResult::Refresh)
.await
.map_err(|e| format!("Error voting: {}", e.to_string()))?;

// Fetch DPNS contract and document type information
let data_contract = self.dpns_contract.as_ref();
let document_type = data_contract
.document_type_for_name("domain")
.expect("expected document type");

let Some(contested_index) = document_type.find_contested_index() else {
return Err("No contested index on dpns domains".to_string());
return Err("Error voting: No contested index on dpns domains".to_string());
};

// Hardcoded values for DPNS
Expand Down Expand Up @@ -75,20 +82,21 @@ impl AppContext {
vote_results.push(result);
} else {
return Err(format!(
"No associated voter identity for qualified identity: {:?}",
"Error voting: No associated voter identity for qualified identity: {:?}",
qualified_identity.identity.id()
));
}
}

// To do: if the voter already voted previously, the previous vote count should be removed
self.db
.update_vote_count(
name,
self.network.to_string().as_str(),
strength,
vote_choice,
)
.map_err(|e| format!("error updating ending time: {}", e))?;
.map_err(|e| format!("Error voting: Error updating vote count: {}", e))?;

Ok(BackendTaskSuccessResult::SuccessfulVotes(vote_results))
}
Expand Down
3 changes: 3 additions & 0 deletions src/backend_task/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::backend_task::identity::IdentityTask;
use crate::backend_task::withdrawal_statuses::{WithdrawStatusPartialData, WithdrawalsTask};
use crate::context::AppContext;
use crate::model::qualified_identity::QualifiedIdentity;
use contested_names::ScheduledDPNSVote;
use dash_sdk::dpp::voting::votes::Vote;
use dash_sdk::query_types::Documents;
use std::sync::Arc;
Expand All @@ -32,12 +33,14 @@ pub(crate) enum BackendTask {
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum BackendTaskSuccessResult {
None,
Refresh,
Message(String),
Documents(Documents),
CoreItem(CoreItem),
RegisteredIdentity(QualifiedIdentity),
ToppedUpIdentity(QualifiedIdentity),
SuccessfulVotes(Vec<Vote>),
CastScheduledVote(ScheduledDPNSVote),
WithdrawalStatus(WithdrawStatusPartialData),
}

Expand Down
Loading