Skip to content

Commit 37490f5

Browse files
committed
Implement poll notifications in core
This commit implements the ability to specify an external command to run via -pollnotify=<cmd> argument that takes two placeholders, %s1 and %s2, which are replaced with the poll txid and the notification status (added, deleted, or expiration_warning). The notifications are sent on all non-expired polls. Add/delete notifications are executed by the poll contract handlers. The expiration warning is handled by a scheduler function which checks every 5 minutes and does a single shot notification for polls that are within -pollexpirewarningtime=<hours> of expiration. This should be sufficient to drive an external script for notification via email or other desired approach.
1 parent 4519201 commit 37490f5

File tree

5 files changed

+128
-1
lines changed

5 files changed

+128
-1
lines changed

src/gridcoin/gridcoin.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,15 @@ void ScheduleRegistriesPassivation(CScheduler& scheduler)
487487

488488
scheduler.scheduleEvery(RunDBPassivation, std::chrono::minutes{5});
489489
}
490+
491+
void SchedulePollNotifications(CScheduler& scheduler)
492+
{
493+
// Run poll notifications every 5 minutes. This is a very thin call most of the time.
494+
// Please see the NotifyPoll function and notify_poll.
495+
496+
scheduler.scheduleEvery(NotifyPoll, std::chrono::minutes{5});
497+
}
498+
490499
} // Anonymous namespace
491500

492501
// -----------------------------------------------------------------------------
@@ -552,6 +561,10 @@ void GRC::ScheduleBackgroundJobs(CScheduler& scheduler)
552561
ScheduleBackups(scheduler);
553562
ScheduleUpdateChecks(scheduler);
554563
ScheduleRegistriesPassivation(scheduler);
564+
565+
#if HAVE_SYSTEM
566+
SchedulePollNotifications(scheduler);
567+
#endif
555568
}
556569

557570
bool GRC::CleanConfig() {
@@ -615,3 +628,25 @@ void GRC::RunDBPassivation()
615628
registry.PassivateDB();
616629
}
617630
}
631+
632+
void GRC::NotifyPoll()
633+
{
634+
PollRegistry& poll_registy = GetPollRegistry();
635+
636+
LOCK2(cs_main, poll_registy.cs_poll_registry);
637+
638+
// Get the expiration warning time from the configuration. Default is 7 days in hours.
639+
int64_t expiration_warning_time = gArgs.GetArg("-pollexpirewarningtime", 7 * 24);
640+
641+
for (const auto& poll_ref : poll_registy.Polls()) {
642+
if (!poll_ref->Ref().Expired(GetAdjustedTime())
643+
&& !poll_ref->Ref().IsExpiringWarningNotified()
644+
&& poll_ref->Ref().Expiration() - GetAdjustedTime() < expiration_warning_time * 3600) {
645+
646+
// Note this will set m_is_expiring_warning_notified to true as well as execute the poll notification
647+
// free thread to run the provided command. So this is effectively a "single-shot" expiration
648+
// warning notification for each expiring poll.
649+
poll_ref->Ref().Notify(PollReference::PollNotificationType::EXPIRE_WARNING);
650+
}
651+
}
652+
}

src/gridcoin/gridcoin.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ bool CleanConfig();
4646
//! with registry db.
4747
//!
4848
void RunDBPassivation();
49+
50+
//!
51+
//! \brief Function to provide for poll expiration/warning notification.
52+
//!
53+
void NotifyPoll();
4954
} // namespace GRC
5055

5156
#endif // GRIDCOIN_GRIDCOIN_H

src/gridcoin/voting/registry.cpp

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,7 @@ PollReference::PollReference()
332332
, m_duration_days(0)
333333
, m_votes({})
334334
, m_magnitude_weight_factor(Fraction())
335+
, m_expiration_warn_notified(false)
335336
{
336337
}
337338

@@ -764,6 +765,49 @@ std::optional<CAmount> PollReference::GetActiveVoteWeight(const PollResultOption
764765
}
765766
}
766767

768+
std::string PollReference::NotifyTypeToString(const PollNotificationType& notify_type) const
769+
{
770+
switch (notify_type) {
771+
case PollNotificationType::ADD:
772+
return "added";
773+
case PollNotificationType::DELETE:
774+
return "deleted";
775+
case PollNotificationType::EXPIRE_WARNING:
776+
return "expiration_warning";
777+
default:
778+
return "unknown";
779+
}
780+
}
781+
782+
void PollReference::Notify(const PollNotificationType& notify_type) const
783+
{
784+
#if HAVE_SYSTEM
785+
// Support running an external command on poll creation. Do not notify for already expired polls. This is especially
786+
// important during a sync to avoid spamming poll notifications.
787+
if (!Expired(GetAdjustedTime())) {
788+
std::string strCmd = gArgs.GetArg("-pollnotify", std::string {});
789+
790+
if (strCmd.empty()) {
791+
return;
792+
}
793+
794+
// The placeholders %s1 and %s2 are replaced with the txid and the notification type.
795+
boost::replace_all(strCmd, "%s1", m_txid.ToString());
796+
boost::replace_all(strCmd, "%s2", NotifyTypeToString(notify_type));
797+
boost::thread t(runCommand, strCmd); // thread runs free
798+
799+
if (notify_type == EXPIRE_WARNING) {
800+
m_expiration_warn_notified = true;
801+
}
802+
}
803+
#endif
804+
}
805+
806+
bool PollReference::IsExpiringWarningNotified() const
807+
{
808+
return m_expiration_warn_notified;
809+
}
810+
767811
void PollReference::LinkVote(const uint256 txid)
768812
{
769813
m_votes.emplace_back(txid);
@@ -1064,6 +1108,8 @@ void PollRegistry::AddPoll(const ContractContext& ctx) EXCLUSIVE_LOCKS_REQUIRED(
10641108

10651109
poll_ref.m_magnitude_weight_factor = payload->m_poll.ResolveMagnitudeWeightFactor(poll_ref.GetStartingBlockIndexPtr());
10661110

1111+
poll_ref.Notify(PollReference::PollNotificationType::ADD);
1112+
10671113
if (fQtActive && !poll_ref.Expired(GetAdjustedTime())) {
10681114
uiInterface.NewPollReceived(poll_ref.Time());
10691115
}
@@ -1142,6 +1188,12 @@ void PollRegistry::DeletePoll(const ContractContext& ctx) EXCLUSIVE_LOCKS_REQUIR
11421188

11431189
int64_t poll_time = payload->m_poll.m_timestamp;
11441190

1191+
PollReference* poll_ref = TryBy(payload->m_poll.m_title);
1192+
1193+
// Note this reference will effectively disappear once this function exits, but this is ok, because there will
1194+
// be no need to further reference it after this point for purposes of notification.
1195+
poll_ref->Notify(PollReference::PollNotificationType::DELETE);
1196+
11451197
m_polls.erase(ToLower(payload->m_poll.m_title));
11461198

11471199
m_polls_by_txid.erase(ctx.m_tx.GetHash());
@@ -1155,7 +1207,6 @@ void PollRegistry::DeletePoll(const ContractContext& ctx) EXCLUSIVE_LOCKS_REQUIR
11551207
if (fQtActive) {
11561208
uiInterface.NewPollReceived(poll_time);;
11571209
}
1158-
11591210
}
11601211

11611212
void PollRegistry::DeleteVote(const ContractContext& ctx) EXCLUSIVE_LOCKS_REQUIRED(cs_main, PollRegistry::cs_poll_registry)

src/gridcoin/voting/registry.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,15 @@ class PollReference
4747
friend class PollRegistry;
4848

4949
public:
50+
51+
enum PollNotificationType
52+
{
53+
UNKNOWN,
54+
ADD,
55+
DELETE,
56+
EXPIRE_WARNING
57+
};
58+
5059
//!
5160
//! \brief Initialize an empty, invalid poll reference object.
5261
//!
@@ -167,6 +176,26 @@ class PollReference
167176
//!
168177
std::optional<CAmount> GetActiveVoteWeight(const PollResultOption &result) const;
169178

179+
//!
180+
//! \brief Provides string equivalent of PollNotificationType
181+
//! \param notify_type
182+
//! \return string equivalent of PollNotificationType
183+
//!
184+
std::string NotifyTypeToString(const PollNotificationType& notify_type) const;
185+
186+
//!
187+
//! \brief Runs a free thread executing provided poll notification command. Also sets m_expiration_warn_notified
188+
//! to true if the notification type is EXPIRE_WARNING.
189+
//! \param notify_type
190+
//!
191+
void Notify(const PollNotificationType& notify_type) const;
192+
193+
//!
194+
//! \brief Returns whether the expiration warning was sent.
195+
//! \return boolean indicating whether the expiration warning was sent.
196+
//!
197+
bool IsExpiringWarningNotified() const;
198+
170199
//!
171200
//! \brief Record a transaction that contains a response to the poll.
172201
//!
@@ -193,6 +222,7 @@ class PollReference
193222
uint32_t m_duration_days; //!< Number of days the poll remains active.
194223
std::vector<uint256> m_votes; //!< Hashes of the linked vote transactions.
195224
mutable Fraction m_magnitude_weight_factor; //!< Magnitude weight factor for the poll (defined at poll start).
225+
mutable bool m_expiration_warn_notified; //!< Flag to indicate whether the expiration warning was sent.
196226
}; // PollReference
197227

198228
//!

src/init.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,12 @@ void SetupServerArgs()
360360
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
361361
argsman.AddArg("-walletnotify=<cmd>", "Execute command when a wallet transaction changes (%s in cmd is replaced by TxID)",
362362
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
363+
argsman.AddArg("-pollnotify=<cmd>", "Execute command when a poll is added/deleted/expiring (%s1 in cmd is replaced by poll id,"
364+
"%s2 is relaced by status: added, deleted, expiration warning)",
365+
ArgsManager::ALLOW_ANY | ArgsManager::IMMEDIATE_EFFECT, OptionsCategory::OPTIONS);
366+
argsman.AddArg("-pollexpirewarningtime=<hours>", "Hours before poll expiration to execute notification command. Default is "
367+
"7 days (168 hours)",
368+
ArgsManager::ALLOW_ANY | ArgsManager::IMMEDIATE_EFFECT, OptionsCategory::OPTIONS);
363369
#endif
364370
argsman.AddArg("-confchange", "Require confirmations for change (default: 0)",
365371
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);

0 commit comments

Comments
 (0)