commit 6e0b874493fbb3f010ce9794791a6cc640107385
parent 2ffb04814def70eb7642a846c28ea4756cb464a4
Author: n0tr1v <n0tr1v@protonmail.com>
Date: Thu, 18 Jan 2024 17:15:33 -0800
ensure we update database before sending out monero payment.
If the server crash in between, we will be able to figure out that that monero balance no longer matches what's in the database, and will be able to re-process the missing transactions.
Diffstat:
5 files changed, 92 insertions(+), 9 deletions(-)
diff --git a/cmd/dkf/migrations/161.sql b/cmd/dkf/migrations/161.sql
@@ -0,0 +1,6 @@
+-- +migrate Up
+ALTER TABLE poker_xmr_transactions ADD COLUMN status INTEGER NOT NULL DEFAULT 0;
+CREATE INDEX poker_xmr_transactions_status_idx ON poker_xmr_transactions(status);
+UPDATE poker_xmr_transactions SET status = 2 WHERE is_in = 0 AND status = 0;
+
+-- +migrate Down
diff --git a/pkg/actions/actions.go b/pkg/actions/actions.go
@@ -77,7 +77,7 @@ func Start(c *cli.Context) error {
config.CaptchaDifficulty.Store(settings.CaptchaDifficulty)
config.MoneroPrice.Store(settings.MoneroPrice)
- config.Xmr()
+ walletClient := config.Xmr()
utils.SGo(func() { cleanupDatabase(db) })
utils.SGo(func() { managers.ActiveUsers.CleanupUsersCache() })
@@ -86,6 +86,13 @@ func Start(c *cli.Context) error {
poker.Refund(db)
+ if !walletIsBalanced(db, walletClient) {
+ // TODO: automatically send transactions that are not processed
+ config.PokerWithdrawEnabled.Store(false)
+ logrus.Error("wallet is not balanced")
+ dutils.RootAdminNotify(db, "wallet is not balanced; poker withdraw disabled")
+ }
+
interceptors.LoadFilters(db)
interceptors.ChessInstance = interceptors.NewChess(db)
interceptors.BattleshipInstance = interceptors.NewBattleship(db)
@@ -95,6 +102,35 @@ func Start(c *cli.Context) error {
return nil
}
+// Returns either or not the monero wallet balance matches the database balance
+func walletIsBalanced(db *database.DkfDB, client wallet1.Client) (balanced bool) {
+ resBalance, err := client.GetBalance(&wallet1.RequestGetBalance{})
+ if err != nil {
+ logrus.Error(err)
+ return false
+ }
+ walletBalance := database.Piconero(resBalance.Balance)
+
+ var diffInOut database.Piconero
+ if err := db.WithE(func(tx *database.DkfDB) error {
+ sumIn, err := tx.GetPokerXmrTransactionsSumIn()
+ if err != nil {
+ return err
+ }
+ sumOut, err := tx.GetPokerXmrTransactionsSumOut()
+ if err != nil {
+ return err
+ }
+ diffInOut = sumIn - sumOut
+ return nil
+ }); err != nil {
+ logrus.Error(err)
+ return false
+ }
+
+ return walletBalance == diffInOut
+}
+
func isFirstUse(db *database.DkfDB) bool {
var count int64
db.DB().Model(database.User{}).Count(&count)
diff --git a/pkg/database/tablePokerXmrTransactions.go b/pkg/database/tablePokerXmrTransactions.go
@@ -7,8 +7,15 @@ import (
"time"
)
+const (
+ PokerXmrTransactionStatusPending = 1
+ PokerXmrTransactionStatusSuccess = 2
+ PokerXmrTransactionStatusFailed = 3
+)
+
type PokerXmrTransaction struct {
ID int64
+ Status int64
TxID string
UserID UserID
Address string
@@ -23,6 +30,10 @@ type PokerXmrTransaction struct {
User User
}
+func (t *PokerXmrTransaction) SetStatus(db *DkfDB, newStatus int64) error {
+ return db.db.Model(t).Select("Status").Updates(PokerXmrTransaction{Status: newStatus}).Error
+}
+
func (t *PokerXmrTransaction) HasEnoughConfirmations() bool {
return t.Confirmations >= t.ConfirmationsNeeded()
}
@@ -42,10 +53,15 @@ func (d *DkfDB) GetPokerXmrTransactionsSumOut() (out Piconero, err error) {
Amount Piconero
Fee Piconero
}
- err = d.db.Raw(`SELECT SUM(amount) AS amount, SUM(fee) AS fee FROM poker_xmr_transactions WHERE is_in = 0`).Scan(&tmp).Error
+ err = d.db.Raw(`SELECT SUM(amount) AS amount, SUM(fee) AS fee FROM poker_xmr_transactions WHERE is_in = 0 AND status = 2`).Scan(&tmp).Error
return tmp.Amount + tmp.Fee, err
}
+func (d *DkfDB) GetPokerXmrPendingTransactions() (out []PokerXmrTransaction, err error) {
+ err = d.db.Find(&out, "is_in = 0 AND status = 1").Error
+ return
+}
+
func (d *DkfDB) GetLastUserWithdrawPokerXmrTransaction(userID UserID) (out PokerXmrTransaction, err error) {
err = d.db.Order("id DESC").First(&out, "user_id = ? AND is_in = 0", userID).Error
return
@@ -77,7 +93,12 @@ func (d *DkfDB) CreatePokerXmrInTransaction(userID UserID, transfer *wallet.Tran
}
func (d *DkfDB) CreatePokerXmrTransaction(userID UserID, transfer *wallet.ResponseTransfer) (out PokerXmrTransaction, err error) {
- err = d.db.Create(&PokerXmrTransaction{UserID: userID, Amount: Piconero(transfer.Amount), Fee: Piconero(transfer.Fee), IsIn: false}).Error
+ err = d.db.Create(&PokerXmrTransaction{
+ Status: PokerXmrTransactionStatusPending,
+ UserID: userID,
+ Amount: Piconero(transfer.Amount),
+ Fee: Piconero(transfer.Fee),
+ IsIn: false}).Error
return
}
diff --git a/pkg/web/handlers/poker.go b/pkg/web/handlers/poker.go
@@ -207,6 +207,7 @@ func PokerHomeHandler(c echo.Context) error {
dutils.RootAdminNotify(db, fmt.Sprintf("new withdraw %s xmr by %s", withdrawAmount.XmrStr(), authUser.Username))
+ var pokerXmrTx database.PokerXmrTransaction
if err := db.WithE(func(tx *database.DkfDB) error {
xmrBalance, err := authUser.GetXmrBalance(tx)
if err != nil {
@@ -218,11 +219,7 @@ func PokerHomeHandler(c echo.Context) error {
if err := authUser.SubXmrBalance(tx, withdrawAmount+transactionFee); err != nil {
return err
}
- if _, err := walletRpcClient.RelayTx(&wallet1.RequestRelayTx{Hex: res.TxMetadata}); err != nil {
- logrus.Error(err)
- return err
- }
- if _, err := tx.CreatePokerXmrTransaction(authUser.ID, res); err != nil {
+ if pokerXmrTx, err = tx.CreatePokerXmrTransaction(authUser.ID, res); err != nil {
logrus.Error("failed to create poker xmr transaction", err)
return err
}
@@ -232,6 +229,27 @@ func PokerHomeHandler(c echo.Context) error {
return c.Render(http.StatusOK, "poker", data)
}
+ if _, err := walletRpcClient.RelayTx(&wallet1.RequestRelayTx{Hex: res.TxMetadata}); err != nil {
+ if err := db.WithE(func(tx *database.DkfDB) error {
+ if err := pokerXmrTx.SetStatus(tx, database.PokerXmrTransactionStatusFailed); err != nil {
+ return err
+ }
+ if err := authUser.IncrXmrBalance(tx, withdrawAmount+transactionFee); err != nil {
+ return err
+ }
+ return nil
+ }); err != nil {
+ logrus.Error(err)
+ }
+ logrus.Error(err)
+ data.Error = err.Error()
+ return c.Render(http.StatusOK, "poker", data)
+ }
+
+ if err := pokerXmrTx.SetStatus(db, database.PokerXmrTransactionStatusSuccess); err != nil {
+ logrus.Error(err)
+ }
+
pokerWithdrawCache.Delete(authUser.ID)
return hutils.RedirectReferer(c)
}
diff --git a/pkg/web/public/views/pages/poker.gohtml b/pkg/web/public/views/pages/poker.gohtml
@@ -140,6 +140,7 @@
<th>Amount XMR</th>
<th>Confirmations</th>
<th>In/Out</th>
+ <th>Status</th>
<th>Created at</th>
</tr>
{{ range .Data.Transactions }}
@@ -147,10 +148,11 @@
<td style="font-family: monospace;">{{ .Amount.XmrStr }}</td>
<td>{{ if .IsIn }}{{ .Confirmations }}/{{ .ConfirmationsNeeded }}{{ else }}-{{ end }}</td>
<td>{{ if .IsIn }}IN{{ else }}OUT{{ end }}</td>
+ <td>{{ if eq .Status 1 }}pending{{ else if eq .Status 2 }}success{{ else if eq .Status 3 }}failed{{ else }}-{{ end }}</td>
<td>{{ .CreatedAt.Format "Jan 02, 2006 15:04:05" }}</td>
</tr>
{{ else }}
- <tr><td colspan="4"><em>No transactions to show</em></td></tr>
+ <tr><td colspan="5"><em>No transactions to show</em></td></tr>
{{ end }}
</table>
</div>