From 00bf2407fe7f9dd39fa8ac0e0386446b7443eeb4 Mon Sep 17 00:00:00 2001
From: "James C. Owens" <jamesowens@optonline.net>
Date: Sun, 8 Oct 2023 12:29:24 -0400
Subject: [PATCH] Implementation of add, edit, and delete sidestake buttons in
 options

---
 src/gridcoin/sidestake.cpp          |  47 ++++----
 src/gridcoin/sidestake.h            |   2 +-
 src/miner.cpp                       |   2 +-
 src/qt/editsidestakedialog.cpp      |  51 +++++----
 src/qt/editsidestakedialog.h        |   9 +-
 src/qt/forms/editsidestakedialog.ui | 153 +++++++++++---------------
 src/qt/forms/optionsdialog.ui       |  17 +++
 src/qt/optionsdialog.cpp            | 113 +++++++++++++++++--
 src/qt/optionsdialog.h              |   8 ++
 src/qt/sidestaketablemodel.cpp      | 162 +++++++++++++++++++++++++++-
 src/qt/sidestaketablemodel.h        |   6 ++
 src/rpc/mining.cpp                  |   3 +-
 12 files changed, 420 insertions(+), 153 deletions(-)

diff --git a/src/gridcoin/sidestake.cpp b/src/gridcoin/sidestake.cpp
index dc27fdce93..e21678a347 100644
--- a/src/gridcoin/sidestake.cpp
+++ b/src/gridcoin/sidestake.cpp
@@ -208,7 +208,8 @@ const std::vector<SideStake_ptr> SideStakeRegistry::SideStakeEntries() const
     return sidestakes;
 }
 
-const std::vector<SideStake_ptr> SideStakeRegistry::ActiveSideStakeEntries(const bool& local_only)
+const std::vector<SideStake_ptr> SideStakeRegistry::ActiveSideStakeEntries(const bool& local_only,
+                                                                           const bool& include_zero_alloc)
 {
     std::vector<SideStake_ptr> sidestakes;
     double allocation_sum = 0.0;
@@ -225,8 +226,10 @@ const std::vector<SideStake_ptr> SideStakeRegistry::ActiveSideStakeEntries(const
         for (const auto& entry : m_sidestake_entries)
         {
             if (entry.second->m_status == SideStakeStatus::MANDATORY && allocation_sum + entry.second->m_allocation <= 1.0) {
-                sidestakes.push_back(entry.second);
-                allocation_sum += entry.second->m_allocation;
+                if ((include_zero_alloc && entry.second->m_allocation == 0.0) || entry.second->m_allocation > 0.0) {
+                    sidestakes.push_back(entry.second);
+                    allocation_sum += entry.second->m_allocation;
+                }
             }
         }
     }
@@ -240,8 +243,10 @@ const std::vector<SideStake_ptr> SideStakeRegistry::ActiveSideStakeEntries(const
         for (const auto& entry : m_local_sidestake_entries)
         {
             if (entry.second->m_status == SideStakeStatus::ACTIVE && allocation_sum + entry.second->m_allocation <= 1.0) {
-                sidestakes.push_back(entry.second);
-                allocation_sum += entry.second->m_allocation;
+                if ((include_zero_alloc && entry.second->m_allocation == 0.0) || entry.second->m_allocation > 0.0) {
+                    sidestakes.push_back(entry.second);
+                    allocation_sum += entry.second->m_allocation;
+                }
             }
         }
     }
@@ -579,22 +584,22 @@ void SideStakeRegistry::LoadLocalSideStakesFromConfig()
 
     bool new_format_valid = false;
 
-    if (addresses.size() != allocations.size() || (!descriptions.empty() && descriptions.size() != addresses.size()))
-    {
-        LogPrintf("WARN: %s: Malformed new style sidestaking configuration entries. Reverting to original format in read only "
-                  "gridcoinresearch.conf file.",
-                  __func__);
-    } else {
-        new_format_valid = true;
+    if (!addresses.empty()) {
+        if (addresses.size() != allocations.size() || (!descriptions.empty() && addresses.size() != descriptions.size())) {
+            LogPrintf("WARN: %s: Malformed new style sidestaking configuration entries. "
+                      "Reverting to original format in read only gridcoinresearch.conf file.",
+                      __func__);
+        } else {
+            new_format_valid = true;
 
-        for (unsigned int i = 0; i < addresses.size(); ++i)
-        {
-            if (descriptions.empty()) {
-                raw_vSideStakeAlloc.push_back(std::make_tuple(addresses[i], allocations[i], ""));
-            } else {
-                raw_vSideStakeAlloc.push_back(std::make_tuple(addresses[i], allocations[i], descriptions[i]));
+            for (unsigned int i = 0; i < addresses.size(); ++i)
+            {
+                if (descriptions.empty()) {
+                    raw_vSideStakeAlloc.push_back(std::make_tuple(addresses[i], allocations[i], ""));
+                } else {
+                    raw_vSideStakeAlloc.push_back(std::make_tuple(addresses[i], allocations[i], descriptions[i]));
+                }
             }
-
         }
     }
 
@@ -652,9 +657,9 @@ void SideStakeRegistry::LoadLocalSideStakesFromConfig()
 
         dAllocation /= 100.0;
 
-        if (dAllocation <= 0)
+        if (dAllocation < 0)
         {
-            LogPrintf("WARN: %s: Negative or zero allocation provided. Skipping allocation.", __func__);
+            LogPrintf("WARN: %s: Negative allocation provided. Skipping allocation.", __func__);
             continue;
         }
 
diff --git a/src/gridcoin/sidestake.h b/src/gridcoin/sidestake.h
index 54583e6858..dccb6b216a 100644
--- a/src/gridcoin/sidestake.h
+++ b/src/gridcoin/sidestake.h
@@ -412,7 +412,7 @@ class SideStakeRegistry : public IContractHandler
     //!
     //! \return A vector of smart pointers to sidestake entries.
     //!
-    const std::vector<SideStake_ptr> ActiveSideStakeEntries(const bool& local_only = false);
+    const std::vector<SideStake_ptr> ActiveSideStakeEntries(const bool& local_only, const bool& include_zero_alloc);
 
     //!
     //! \brief Get the current sidestake entry for the specified key string.
diff --git a/src/miner.cpp b/src/miner.cpp
index ca028355c0..c1225391a0 100644
--- a/src/miner.cpp
+++ b/src/miner.cpp
@@ -1321,7 +1321,7 @@ void StakeMiner(CWallet *pwallet)
 
         // Note that fEnableSideStaking is now processed internal to ActiveSideStakeEntries. The sidestaking flag only
         // controls local sidestakes. If there exists mandatory sidestakes, they occur regardless of the flag.
-        vSideStakeAlloc = GRC::GetSideStakeRegistry().ActiveSideStakeEntries();
+        vSideStakeAlloc = GRC::GetSideStakeRegistry().ActiveSideStakeEntries(false, false);
 
         // wait for next round
         if (!MilliSleep(nMinerSleep)) return;
diff --git a/src/qt/editsidestakedialog.cpp b/src/qt/editsidestakedialog.cpp
index 5fe71ac552..fac1001e4d 100644
--- a/src/qt/editsidestakedialog.cpp
+++ b/src/qt/editsidestakedialog.cpp
@@ -4,7 +4,6 @@
 
 #include "editsidestakedialog.h"
 #include "ui_editsidestakedialog.h"
-#include "optionsmodel.h"
 #include "sidestaketablemodel.h"
 #include "guiutil.h"
 #include "qt/decoration.h"
@@ -29,10 +28,16 @@ EditSideStakeDialog::EditSideStakeDialog(Mode mode, QWidget* parent)
     {
     case NewSideStake:
         setWindowTitle(tr("New SideStake"));
+        ui->statusLineEdit->setEnabled(false);
+        ui->statusLabel->setHidden(true);
+        ui->statusLineEdit->setHidden(true);
         break;
     case EditSideStake:
         setWindowTitle(tr("Edit SideStake"));
         ui->addressLineEdit->setEnabled(false);
+        ui->statusLabel->setHidden(false);
+        ui->statusLineEdit->setHidden(false);
+        ui->statusLineEdit->setEnabled(false);
         break;
     }
 
@@ -45,7 +50,7 @@ EditSideStakeDialog::~EditSideStakeDialog()
     delete ui;
 }
 
-void EditSideStakeDialog::setModel(OptionsModel *model)
+void EditSideStakeDialog::setModel(SideStakeTableModel* model)
 {
     this->model = model;
     if (!model) {
@@ -56,11 +61,28 @@ void EditSideStakeDialog::setModel(OptionsModel *model)
     mapper->addMapping(ui->addressLineEdit, SideStakeTableModel::Address);
     mapper->addMapping(ui->allocationLineEdit, SideStakeTableModel::Allocation);
     mapper->addMapping(ui->descriptionLineEdit, SideStakeTableModel::Description);
+    mapper->addMapping(ui->statusLineEdit, SideStakeTableModel::Status);
 }
 
 void EditSideStakeDialog::loadRow(int row)
 {
     mapper->setCurrentIndex(row);
+
+    GRC::SideStake* sidestake = static_cast<GRC::SideStake*>(model->index(row, 0, QModelIndex()).internalPointer());
+
+    LogPrint(BCLog::LogFlags::VERBOSE, "INFO: %s: ui->addressLineEdit = %s, ui->allocationLineEdit = %s, "
+                                       "sidestake address = %s, sidestake allocation = %f",
+             __func__,
+             ui->addressLineEdit->text().toStdString(),
+             ui->allocationLineEdit->text().toStdString(),
+             sidestake->m_key.ToString(),
+             sidestake->m_allocation);
+
+    // For some reason the mapper is not setting the fields from the selected row. Populate manually.
+    ui->addressLineEdit->setText(model->index(row, SideStakeTableModel::Address, QModelIndex()).data().toString());
+    ui->allocationLineEdit->setText(model->index(row, SideStakeTableModel::Allocation, QModelIndex()).data().toString());
+    ui->descriptionLineEdit->setText(model->index(row, SideStakeTableModel::Description, QModelIndex()).data().toString());
+    ui->statusLineEdit->setText(model->index(row, SideStakeTableModel::Status, QModelIndex()).data().toString());
 }
 
 bool EditSideStakeDialog::saveCurrentRow()
@@ -72,9 +94,9 @@ bool EditSideStakeDialog::saveCurrentRow()
     switch (mode)
     {
     case NewSideStake:
-        address = model->getSideStakeTableModel()->addRow(ui->addressLineEdit->text(),
-                                                          ui->allocationLineEdit->text(),
-                                                          ui->descriptionLineEdit->text());
+        address = model->addRow(ui->addressLineEdit->text(),
+                                ui->allocationLineEdit->text(),
+                                ui->descriptionLineEdit->text());
         break;
     case EditSideStake:
         if (mapper->submit()) {
@@ -93,7 +115,7 @@ void EditSideStakeDialog::accept()
 
     if (!saveCurrentRow())
     {
-        switch(model->getSideStakeTableModel()->getEditStatus())
+        switch(model->getEditStatus())
         {
         case SideStakeTableModel::OK:
             // Failed with unknown reason. Just reject.
@@ -115,23 +137,14 @@ void EditSideStakeDialog::accept()
             break;
         case SideStakeTableModel::INVALID_ALLOCATION:
             QMessageBox::warning(this, windowTitle(),
-                                 tr("The entered allocation is not valid.").arg(ui->allocationLineEdit->text()),
+                                 tr("The entered allocation is not valid. Check to make sure that the "
+                                    "allocation is greater than zero and when added to the other allocations "
+                                    "totals less than 100.").arg(ui->allocationLineEdit->text()),
                                  QMessageBox::Ok, QMessageBox::Ok);
-
         }
+
         return;
     }
 
     QDialog::accept();
 }
-
-QString EditSideStakeDialog::getAddress() const
-{
-    return address;
-}
-
-void EditSideStakeDialog::setAddress(const QString &address)
-{
-    this->address = address;
-    ui->addressLineEdit->setText(address);
-}
diff --git a/src/qt/editsidestakedialog.h b/src/qt/editsidestakedialog.h
index b9256342b6..c707eaa446 100644
--- a/src/qt/editsidestakedialog.h
+++ b/src/qt/editsidestakedialog.h
@@ -14,7 +14,7 @@ QT_END_NAMESPACE
 namespace Ui {
 class EditSideStakeDialog;
 }
-class OptionsModel;
+class SideStakeTableModel;
 
 /** Dialog for editing an address and associated information.
  */
@@ -31,12 +31,9 @@ class EditSideStakeDialog : public QDialog
     explicit EditSideStakeDialog(Mode mode, QWidget* parent = nullptr);
     ~EditSideStakeDialog();
 
-    void setModel(OptionsModel *model);
+    void setModel(SideStakeTableModel* model);
     void loadRow(int row);
 
-    QString getAddress() const;
-    void setAddress(const QString &address);
-
 public slots:
     void accept();
 
@@ -46,7 +43,7 @@ public slots:
     Ui::EditSideStakeDialog *ui;
     QDataWidgetMapper *mapper;
     Mode mode;
-    OptionsModel *model;
+    SideStakeTableModel *model;
 
     QString address;
 };
diff --git a/src/qt/forms/editsidestakedialog.ui b/src/qt/forms/editsidestakedialog.ui
index 5e528b2b4c..a2dd71f36b 100644
--- a/src/qt/forms/editsidestakedialog.ui
+++ b/src/qt/forms/editsidestakedialog.ui
@@ -15,141 +15,114 @@
   </property>
   <layout class="QVBoxLayout" name="verticalLayout">
    <item>
-    <spacer name="verticalSpacer_4">
-     <property name="orientation">
-      <enum>Qt::Vertical</enum>
-     </property>
-     <property name="sizeHint" stdset="0">
-      <size>
-       <width>20</width>
-       <height>40</height>
-      </size>
-     </property>
-    </spacer>
-   </item>
-   <item>
-    <layout class="QHBoxLayout" name="horizontalLayout">
-     <item>
+    <layout class="QFormLayout" name="formLayout">
+     <item row="0" column="1">
+      <spacer name="verticalSpacer_4">
+       <property name="orientation">
+        <enum>Qt::Vertical</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>20</width>
+         <height>40</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+     <item row="1" column="0">
       <widget class="QLabel" name="addressLabel">
        <property name="text">
         <string>Address</string>
        </property>
       </widget>
      </item>
-     <item>
-      <spacer name="horizontalSpacer">
+     <item row="1" column="1">
+      <widget class="QLineEdit" name="addressLineEdit"/>
+     </item>
+     <item row="2" column="1">
+      <spacer name="verticalSpacer_2">
        <property name="orientation">
-        <enum>Qt::Horizontal</enum>
+        <enum>Qt::Vertical</enum>
        </property>
        <property name="sizeHint" stdset="0">
         <size>
-         <width>10</width>
-         <height>20</height>
+         <width>20</width>
+         <height>40</height>
         </size>
        </property>
       </spacer>
      </item>
-     <item>
-      <widget class="QLineEdit" name="addressLineEdit"/>
-     </item>
-    </layout>
-   </item>
-   <item>
-    <spacer name="verticalSpacer_2">
-     <property name="orientation">
-      <enum>Qt::Vertical</enum>
-     </property>
-     <property name="sizeHint" stdset="0">
-      <size>
-       <width>20</width>
-       <height>40</height>
-      </size>
-     </property>
-    </spacer>
-   </item>
-   <item>
-    <layout class="QHBoxLayout" name="horizontalLayout_2">
-     <item>
+     <item row="3" column="0">
       <widget class="QLabel" name="allocationLabel">
        <property name="text">
         <string>Allocation</string>
        </property>
       </widget>
      </item>
-     <item>
-      <spacer name="horizontalSpacer_2">
+     <item row="3" column="1">
+      <widget class="QLineEdit" name="allocationLineEdit"/>
+     </item>
+     <item row="4" column="1">
+      <spacer name="verticalSpacer">
        <property name="orientation">
-        <enum>Qt::Horizontal</enum>
+        <enum>Qt::Vertical</enum>
        </property>
        <property name="sizeHint" stdset="0">
         <size>
-         <width>10</width>
-         <height>20</height>
+         <width>20</width>
+         <height>40</height>
         </size>
        </property>
       </spacer>
      </item>
-     <item>
-      <widget class="QLineEdit" name="allocationLineEdit"/>
-     </item>
-    </layout>
-   </item>
-   <item>
-    <spacer name="verticalSpacer_3">
-     <property name="orientation">
-      <enum>Qt::Vertical</enum>
-     </property>
-     <property name="sizeHint" stdset="0">
-      <size>
-       <width>20</width>
-       <height>40</height>
-      </size>
-     </property>
-    </spacer>
-   </item>
-   <item>
-    <layout class="QHBoxLayout" name="horizontalLayout_3">
-     <item>
+     <item row="5" column="0">
       <widget class="QLabel" name="descriptionLabel">
        <property name="text">
         <string>Description</string>
        </property>
       </widget>
      </item>
-     <item>
-      <spacer name="horizontalSpacer_3">
+     <item row="5" column="1">
+      <widget class="QLineEdit" name="descriptionLineEdit"/>
+     </item>
+     <item row="6" column="1">
+      <spacer name="verticalSpacer_3">
        <property name="orientation">
-        <enum>Qt::Horizontal</enum>
-       </property>
-       <property name="sizeType">
-        <enum>QSizePolicy::Expanding</enum>
+        <enum>Qt::Vertical</enum>
        </property>
        <property name="sizeHint" stdset="0">
         <size>
-         <width>10</width>
-         <height>20</height>
+         <width>20</width>
+         <height>40</height>
         </size>
        </property>
       </spacer>
      </item>
-     <item>
-      <widget class="QLineEdit" name="descriptionLineEdit"/>
+     <item row="7" column="1">
+      <widget class="QLineEdit" name="statusLineEdit"/>
+     </item>
+     <item row="7" column="0">
+      <widget class="QLabel" name="statusLabel">
+       <property name="text">
+        <string>Status</string>
+       </property>
+      </widget>
+     </item>
+     <item row="8" column="1">
+      <spacer name="verticalSpacer_5">
+       <property name="orientation">
+        <enum>Qt::Vertical</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>20</width>
+         <height>40</height>
+        </size>
+       </property>
+      </spacer>
      </item>
     </layout>
    </item>
-   <item>
-    <spacer name="verticalSpacer">
-     <property name="orientation">
-      <enum>Qt::Vertical</enum>
-     </property>
-     <property name="sizeHint" stdset="0">
-      <size>
-       <width>20</width>
-       <height>40</height>
-      </size>
-     </property>
-    </spacer>
-   </item>
    <item>
     <widget class="QDialogButtonBox" name="buttonBox">
      <property name="orientation">
diff --git a/src/qt/forms/optionsdialog.ui b/src/qt/forms/optionsdialog.ui
index 1c34716190..efb8ea568b 100644
--- a/src/qt/forms/optionsdialog.ui
+++ b/src/qt/forms/optionsdialog.ui
@@ -342,6 +342,9 @@
            </item>
            <item>
             <widget class="QPushButton" name="pushButtonEditSideStake">
+             <property name="enabled">
+              <bool>false</bool>
+             </property>
              <property name="text">
               <string>Edit</string>
              </property>
@@ -351,6 +354,20 @@
              </property>
             </widget>
            </item>
+           <item>
+            <widget class="QPushButton" name="pushButtonDeleteSideStake">
+             <property name="enabled">
+              <bool>false</bool>
+             </property>
+             <property name="text">
+              <string>Delete</string>
+             </property>
+             <property name="icon">
+              <iconset resource="../bitcoin.qrc">
+               <normaloff>:/icons/remove</normaloff>:/icons/remove</iconset>
+             </property>
+            </widget>
+           </item>
            <item>
             <spacer name="horizontalSpacer_SideStake">
              <property name="orientation">
diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp
index 96757b2e79..29100fc75f 100644
--- a/src/qt/optionsdialog.cpp
+++ b/src/qt/optionsdialog.cpp
@@ -11,6 +11,7 @@
 #include "sidestaketablemodel.h"
 #include "editsidestakedialog.h"
 
+#include <QSortFilterProxyModel>
 #include <QDir>
 #include <QIntValidator>
 #include <QLocale>
@@ -147,6 +148,7 @@ void OptionsDialog::setModel(OptionsModel *model)
 
         sidestake_model->refresh();
 
+        //ui->sidestakingTableView->setModel(m_sidestake_proxy_model);
         ui->sidestakingTableView->setModel(sidestake_model);
         ui->sidestakingTableView->verticalHeader()->hide();
         ui->sidestakingTableView->setSelectionBehavior(QAbstractItemView::SelectRows);
@@ -161,11 +163,22 @@ void OptionsDialog::setModel(OptionsModel *model)
         ui->sidestakingTableView->horizontalHeader()->setStretchLastSection(true);
         ui->sidestakingTableView->setShowGrid(true);
 
+        ui->sidestakingTableView->sortByColumn(0, Qt::AscendingOrder);
+
         connect(ui->enableSideStaking, &QCheckBox::toggled, this, &OptionsDialog::hideSideStakeEdit);
         connect(ui->enableSideStaking, &QCheckBox::toggled, this, &OptionsDialog::refreshSideStakeTableModel);
 
         connect(ui->pushButtonNewSideStake, &QPushButton::clicked, this, &OptionsDialog::newSideStakeButton_clicked);
         connect(ui->pushButtonEditSideStake, &QPushButton::clicked, this, &OptionsDialog::editSideStakeButton_clicked);
+        connect(ui->pushButtonDeleteSideStake, &QPushButton::clicked, this, &OptionsDialog::deleteSideStakeButton_clicked);
+
+        connect(ui->sidestakingTableView->selectionModel(), &QItemSelectionModel::selectionChanged,
+                this, &OptionsDialog::sidestakeSelectionChanged);
+
+        ui->sidestakingTableView->installEventFilter(this);
+
+        connect(this, &OptionsDialog::sidestakeAllocationInvalid, this, &OptionsDialog::handleSideStakeAllocationInvalid);
+
     }
 
     /* update the display unit, to not use the default ("BTC") */
@@ -254,7 +267,8 @@ void OptionsDialog::setSaveButtonState(bool fState)
 
 void OptionsDialog::on_okButton_clicked()
 {
-    mapper->submit();
+    refreshSideStakeTableModel();
+
     accept();
 }
 
@@ -265,8 +279,6 @@ void OptionsDialog::on_cancelButton_clicked()
 
 void OptionsDialog::on_applyButton_clicked()
 {
-    mapper->submit();
-
     refreshSideStakeTableModel();
 
     disableApplyButton();
@@ -280,29 +292,59 @@ void OptionsDialog::newSideStakeButton_clicked()
 
     EditSideStakeDialog dialog(EditSideStakeDialog::NewSideStake, this);
 
-    dialog.setModel(model);
+    dialog.setModel(model->getSideStakeTableModel());
 
     dialog.exec();
 }
 
 void OptionsDialog::editSideStakeButton_clicked()
 {
-    if (!model) {
+    if (!model || !ui->sidestakingTableView->selectionModel()) {
         return;
     }
 
-    EditSideStakeDialog dialog(EditSideStakeDialog::EditSideStake, this);
+    QModelIndexList indexes = ui->sidestakingTableView->selectionModel()->selectedRows();
 
-    dialog.setModel(model);
+    if (indexes.isEmpty()) {
+        return;
+    }
 
+    if (indexes.size() > 1) {
+        QMessageBox::warning(this, tr("Error"), tr("You can only edit one sidestake at a time."),  QMessageBox::Ok);
+    }
+
+    EditSideStakeDialog dialog(EditSideStakeDialog::EditSideStake, this);
+
+    dialog.setModel(model->getSideStakeTableModel());
+    dialog.loadRow(indexes.at(0).row());
     dialog.exec();
 }
 
+void OptionsDialog::deleteSideStakeButton_clicked()
+{
+    if (!model || !ui->sidestakingTableView->selectionModel()) {
+        return;
+    }
+
+    QModelIndexList indexes = ui->sidestakingTableView->selectionModel()->selectedRows();
+
+    if (indexes.isEmpty()) {
+        return;
+    }
+
+    if (indexes.size() > 1) {
+        QMessageBox::warning(this, tr("Error"), tr("You can only delete one sidestake at a time."),  QMessageBox::Ok);
+    }
+
+    model->getSideStakeTableModel()->removeRows(indexes.at(0).row(), 1);
+}
+
 void OptionsDialog::showRestartWarning_Proxy()
 {
     if(!fRestartWarningDisplayed_Proxy)
     {
-        QMessageBox::warning(this, tr("Warning"), tr("This setting will take effect after restarting Gridcoin."), QMessageBox::Ok);
+        QMessageBox::warning(this, tr("Warning"), tr("This setting will take effect"
+                                                     " after restarting Gridcoin."), QMessageBox::Ok);
         fRestartWarningDisplayed_Proxy = true;
     }
 }
@@ -436,9 +478,12 @@ void OptionsDialog::handleMinStakeSplitValueValid(QValidatedLineEdit *object, bo
 
 void OptionsDialog::refreshSideStakeTableModel()
 {
-    mapper->submit();
-
-    model->getSideStakeTableModel()->refresh();
+    if (!mapper->submit()
+        && model->getSideStakeTableModel()->getEditStatus() == SideStakeTableModel::INVALID_ALLOCATION) {
+        emit sidestakeAllocationInvalid();
+    } else {
+        model->getSideStakeTableModel()->refresh();
+    }
 }
 
 bool OptionsDialog::eventFilter(QObject *object, QEvent *event)
@@ -497,5 +542,51 @@ bool OptionsDialog::eventFilter(QObject *object, QEvent *event)
             }
         }
     }
+
+    // This is required to provide immediate feedback on invalid allocation entries on in place editing.
+    if (object == ui->sidestakingTableView)
+    {
+        if (model->getSideStakeTableModel()->getEditStatus() == SideStakeTableModel::INVALID_ALLOCATION) {
+            LogPrint(BCLog::LogFlags::VERBOSE, "INFO %s: event type = %i",
+                     __func__,
+                     (int) event->type());
+
+            emit sidestakeAllocationInvalid();
+        }
+    }
+
     return QDialog::eventFilter(object, event);
 }
+
+void OptionsDialog::sidestakeSelectionChanged()
+{
+    QTableView *table = ui->sidestakingTableView;
+
+    if (table->selectionModel()->hasSelection()) {
+        QModelIndexList indexes = ui->sidestakingTableView->selectionModel()->selectedRows();
+
+        if (indexes.size() > 1) {
+            ui->pushButtonEditSideStake->setEnabled(false);
+            ui->pushButtonDeleteSideStake->setEnabled(false);
+        } else if (static_cast<GRC::SideStake*>(indexes.at(0).internalPointer())->m_status
+                   == GRC::SideStakeStatus::MANDATORY) {
+            ui->pushButtonEditSideStake->setEnabled(false);
+            ui->pushButtonDeleteSideStake->setEnabled(false);
+        } else {
+            ui->pushButtonEditSideStake->setEnabled(true);
+            ui->pushButtonDeleteSideStake->setEnabled(true);
+        }
+    }
+}
+
+void OptionsDialog::handleSideStakeAllocationInvalid()
+{
+    model->getSideStakeTableModel()->refresh();
+
+    QMessageBox::warning(this, windowTitle(),
+                         tr("The entered allocation is not valid and is reverted. Check to make sure "
+                            "that the allocation is greater than or equal to zero and when added to the other "
+                            "allocations totals less than 100."),
+                         QMessageBox::Ok, QMessageBox::Ok);
+
+}
diff --git a/src/qt/optionsdialog.h b/src/qt/optionsdialog.h
index bf95e9fce3..f734a1e7a4 100644
--- a/src/qt/optionsdialog.h
+++ b/src/qt/optionsdialog.h
@@ -7,6 +7,7 @@ namespace Ui {
 class OptionsDialog;
 }
 class OptionsModel;
+class QSortFilterProxyModel;
 class MonitoredDataMapper;
 class QValidatedLineEdit;
 
@@ -42,6 +43,7 @@ private slots:
 
     void newSideStakeButton_clicked();
     void editSideStakeButton_clicked();
+    void deleteSideStakeButton_clicked();
 
     void showRestartWarning_Proxy();
     void showRestartWarning_Lang();
@@ -54,6 +56,7 @@ private slots:
     void handleProxyIpValid(QValidatedLineEdit *object, bool fState);
     void handleStakingEfficiencyValid(QValidatedLineEdit *object, bool fState);
     void handleMinStakeSplitValueValid(QValidatedLineEdit *object, bool fState);
+    void handleSideStakeAllocationInvalid();
 
     void refreshSideStakeTableModel();
 
@@ -61,6 +64,7 @@ private slots:
     void proxyIpValid(QValidatedLineEdit *object, bool fValid);
     void stakingEfficiencyValid(QValidatedLineEdit *object, bool fValid);
     void minStakeSplitValueValid(QValidatedLineEdit *object, bool fValid);
+    void sidestakeAllocationInvalid();
 
 private:
     Ui::OptionsDialog *ui;
@@ -72,6 +76,8 @@ private slots:
     bool fStakingEfficiencyValid;
     bool fMinStakeSplitValueValid;
 
+    //QSortFilterProxyModel *m_sidestake_proxy_model;
+
     enum SideStakeTableColumnWidths
     {
         ADDRESS_COLUMN_WIDTH = 200,
@@ -81,6 +87,8 @@ private slots:
         STATUS_COLUMN_WIDTH = 150
     };
 
+private slots:
+    void sidestakeSelectionChanged();
 };
 
 #endif // BITCOIN_QT_OPTIONSDIALOG_H
diff --git a/src/qt/sidestaketablemodel.cpp b/src/qt/sidestaketablemodel.cpp
index 8514b10df9..93b8fb76de 100644
--- a/src/qt/sidestaketablemodel.cpp
+++ b/src/qt/sidestaketablemodel.cpp
@@ -48,7 +48,7 @@ class SideStakeTablePriv
     {
         m_cached_sidestakes.clear();
 
-        std::vector<GRC::SideStake_ptr> core_sidestakes = GRC::GetSideStakeRegistry().ActiveSideStakeEntries();
+        std::vector<GRC::SideStake_ptr> core_sidestakes = GRC::GetSideStakeRegistry().ActiveSideStakeEntries(false, true);
 
         m_cached_sidestakes.reserve(core_sidestakes.size());
 
@@ -146,6 +146,120 @@ QVariant SideStakeTableModel::data(const QModelIndex &index, int role) const
     return QVariant();
 }
 
+bool SideStakeTableModel::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+    if (!index.isValid()) {
+        return false;
+    }
+
+    GRC::SideStakeRegistry& registry = GRC::GetSideStakeRegistry();
+
+    GRC::SideStake* rec = static_cast<GRC::SideStake*>(index.internalPointer());
+
+    m_edit_status = OK;
+
+    if (role == Qt::EditRole) {
+        switch (index.column())
+        {
+        case Address:
+        {
+            CBitcoinAddress address;
+            address.SetString(value.toString().toStdString());
+
+
+            if (rec->m_key == address) {
+                m_edit_status = NO_CHANGES;
+                return false;
+            } else if (!address.IsValid()) {
+                m_edit_status = INVALID_ADDRESS;
+                return false;
+            }
+
+            std::vector<GRC::SideStake_ptr> sidestakes = registry.Try(address, true);
+
+            if (!sidestakes.empty()) {
+                m_edit_status = DUPLICATE_ADDRESS;
+                return false;
+            }
+
+            // There is no valid state change left for address. If you are editing the item, the address field is
+            // not editable, so will be NO_CHANGES. For a non-matching address, it will be covered by the dialog
+            // in New mode.
+            break;
+        }
+        case Allocation:
+        {
+            double prior_total_allocation = 0.0;
+
+            // Save the original local sidestake (also in the core).
+            GRC::SideStake orig_sidestake = *rec;
+
+            for (const auto& entry : registry.ActiveSideStakeEntries(false, true)) {
+                if (entry->m_key == orig_sidestake.m_key) {
+                    continue;
+                }
+
+                prior_total_allocation += entry->m_allocation * 100.0;
+            }
+
+            if (rec->m_allocation * 100.0 == value.toDouble()) {
+                m_edit_status = NO_CHANGES;
+                return false;
+            } else if (value.toDouble() < 0.0 || prior_total_allocation + value.toDouble() > 100.0) {
+                m_edit_status = INVALID_ALLOCATION;
+                return false;
+            }
+
+
+            // Delete the original sidestake
+            registry.NonContractDelete(orig_sidestake.m_key, false);
+
+            // Add back the sidestake with the modified allocation
+            registry.NonContractAdd(GRC::SideStake(orig_sidestake.m_key,
+                                                   value.toDouble() / 100.0,
+                                                   orig_sidestake.m_description,
+                                                   int64_t {0},
+                                                   uint256 {},
+                                                   orig_sidestake.m_status.Value()), true);
+
+            break;
+        }
+        case Description:
+        {
+            if (rec->m_description == value.toString().toStdString()) {
+                m_edit_status = NO_CHANGES;
+                return false;
+            }
+
+            // Save the original local sidestake (also in the core).
+            GRC::SideStake orig_sidestake = *rec;
+
+            // Delete the original sidestake
+            registry.NonContractDelete(orig_sidestake.m_key, false);
+
+            // Add back the sidestake with the modified allocation
+            registry.NonContractAdd(GRC::SideStake(orig_sidestake.m_key,
+                                                   orig_sidestake.m_allocation,
+                                                   value.toString().toStdString(),
+                                                   int64_t {0},
+                                                   uint256 {},
+                                                   orig_sidestake.m_status.Value()), true);
+
+            break;
+        }
+        case Status:
+            // Status is not editable
+            return false;
+        }
+
+        updateSideStakeTableModel();
+
+        return true;
+    }
+
+    return false;
+}
+
 QVariant SideStakeTableModel::headerData(int section, Qt::Orientation orientation, int role) const
 {
     if(orientation == Qt::Horizontal)
@@ -160,9 +274,19 @@ QVariant SideStakeTableModel::headerData(int section, Qt::Orientation orientatio
 
 Qt::ItemFlags SideStakeTableModel::flags(const QModelIndex &index) const
 {
-    if (!index.isValid()) return Qt::NoItemFlags;
+    if (!index.isValid()) {
+        return Qt::NoItemFlags;
+    }
+
+    GRC::SideStake* rec = static_cast<GRC::SideStake*>(index.internalPointer());
 
     Qt::ItemFlags retval = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
+
+    if (rec->m_status == GRC::SideStakeStatus::ACTIVE
+        && (index.column() == Allocation || index.column() == Description)) {
+        retval |= Qt::ItemIsEditable;
+    }
+
     return retval;
 }
 
@@ -198,13 +322,22 @@ QString SideStakeTableModel::addRow(const QString &address, const QString &alloc
     // UI model.
     std::vector<GRC::SideStake_ptr> core_local_sidestake = registry.Try(sidestake_address, true);
 
+    double prior_total_allocation = 0.0;
+
+    // Get total allocation of all active/mandatory sidestake entries
+    for (const auto& entry : registry.ActiveSideStakeEntries(false, true)) {
+        prior_total_allocation += entry->m_allocation * 100.0;
+    }
+
     if (!core_local_sidestake.empty()) {
         m_edit_status = DUPLICATE_ADDRESS;
         return QString();
     }
 
+    // The new allocation must be parseable as a double, must be greater than or equal to 0, and
+    // must result in a total allocation of less than 100.
     if (!ParseDouble(allocation.toStdString(), &sidestake_allocation)
-        && (sidestake_allocation < 0.0 || sidestake_allocation > 1.0)) {
+        || sidestake_allocation < 0.0 || prior_total_allocation + sidestake_allocation > 100.0) {
         m_edit_status = INVALID_ALLOCATION;
         return QString();
     }
@@ -223,6 +356,26 @@ QString SideStakeTableModel::addRow(const QString &address, const QString &alloc
     return QString::fromStdString(sidestake_address.ToString());
 }
 
+bool SideStakeTableModel::removeRows(int row, int count, const QModelIndex &parent)
+{
+    Q_UNUSED(parent);
+    GRC::SideStake* rec = m_priv->index(row);
+
+    if(count != 1 || !rec || rec->m_status == GRC::SideStakeStatus::MANDATORY)
+    {
+        // Can only remove one row at a time, and cannot remove rows not in model.
+        // Also refuse to remove mandatory sidestakes.
+        return false;
+    }
+    {
+        GRC::GetSideStakeRegistry().NonContractDelete(rec->m_key);
+    }
+
+    updateSideStakeTableModel();
+
+    return true;
+}
+
 SideStakeTableModel::EditStatus SideStakeTableModel::getEditStatus() const
 {
     return m_edit_status;
@@ -232,6 +385,9 @@ void SideStakeTableModel::refresh()
 {
     Q_EMIT layoutAboutToBeChanged();
     m_priv->refreshSideStakes();
+
+    m_edit_status = OK;
+
     Q_EMIT layoutChanged();
 }
 
diff --git a/src/qt/sidestaketablemodel.h b/src/qt/sidestaketablemodel.h
index 24d7263e72..ab94e14d38 100644
--- a/src/qt/sidestaketablemodel.h
+++ b/src/qt/sidestaketablemodel.h
@@ -28,6 +28,10 @@ class SideStakeLessThan
     Qt::SortOrder m_order;
 };
 
+//!
+//! \brief The SideStakeTableModel class represents the core sidestake registry as a model which can be consumed
+//! and updated by the GUI.
+//!
 class SideStakeTableModel : public QAbstractTableModel
 {
     Q_OBJECT
@@ -57,9 +61,11 @@ class SideStakeTableModel : public QAbstractTableModel
     int rowCount(const QModelIndex &parent) const;
     int columnCount(const QModelIndex &parent) const;
     QVariant data(const QModelIndex &index, int role) const;
+    bool setData(const QModelIndex &index, const QVariant &value, int role);
     QVariant headerData(int section, Qt::Orientation orientation, int role) const;
     QModelIndex index(int row, int column, const QModelIndex &parent) const;
     Qt::ItemFlags flags(const QModelIndex &index) const;
+    bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex());
     void sort(int column, Qt::SortOrder order);
     /*@}*/
 
diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp
index 373ba3e98b..dc0cc887ce 100644
--- a/src/rpc/mining.cpp
+++ b/src/rpc/mining.cpp
@@ -108,7 +108,8 @@ UniValue getstakinginfo(const UniValue& params, bool fHelp)
     }
     obj.pushKV("stake-splitting", stakesplitting);
 
-    vSideStakeAlloc = GRC::GetSideStakeRegistry().ActiveSideStakeEntries();
+    // This is what the miner sees...
+    vSideStakeAlloc = GRC::GetSideStakeRegistry().ActiveSideStakeEntries(false, false);
 
     sidestaking.pushKV("local_side_staking_enabled", fEnableSideStaking);