dkforest

A forum and chat platform (onion)
git clone https://git.dasho.dev/n0tr1v/dkforest.git
Log | Files | Refs | LICENSE

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:
Acmd/dkf/migrations/161.sql | 6++++++
Mpkg/actions/actions.go | 38+++++++++++++++++++++++++++++++++++++-
Mpkg/database/tablePokerXmrTransactions.go | 25+++++++++++++++++++++++--
Mpkg/web/handlers/poker.go | 28+++++++++++++++++++++++-----
Mpkg/web/public/views/pages/poker.gohtml | 4+++-
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>