From da55119a1690dae653c8352b91bfcef048600ada Mon Sep 17 00:00:00 2001 From: Maksym Hrynenko Date: Mon, 3 Jun 2024 12:27:20 +0300 Subject: [PATCH 1/2] add: error for failed airdrop transaction --- internal/assets/migrations/001_initial.sql | 1 + internal/broadcaster/broadcaster.go | 50 +++++++++++++++++++--- internal/data/airdrops.go | 1 + 3 files changed, 45 insertions(+), 7 deletions(-) diff --git a/internal/assets/migrations/001_initial.sql b/internal/assets/migrations/001_initial.sql index cf2491b..1db0684 100644 --- a/internal/assets/migrations/001_initial.sql +++ b/internal/assets/migrations/001_initial.sql @@ -7,6 +7,7 @@ CREATE TABLE airdrops nullifier text NOT NULL, address text NOT NULL, tx_hash text, + error text, amount text NOT NULL, status tx_status_enum NOT NULL, created_at timestamp without time zone NOT NULL DEFAULT NOW(), diff --git a/internal/broadcaster/broadcaster.go b/internal/broadcaster/broadcaster.go index 039c670..7b1ea26 100644 --- a/internal/broadcaster/broadcaster.go +++ b/internal/broadcaster/broadcaster.go @@ -73,12 +73,12 @@ func (r *Runner) handlePending(ctx context.Context, airdrop data.Airdrop) (err e defer func() { r.UnlockNonce() if err != nil { - r.updateAirdropStatus(ctx, airdrop.ID, txHash, data.TxStatusFailed) + r.updateAirdropStatus(ctx, airdrop.ID, txHash, data.TxStatusFailed, err) } }() r.LockNonce() - r.updateAirdropStatus(ctx, airdrop.ID, txHash, data.TxStatusInProgress) + r.updateAirdropStatus(ctx, airdrop.ID, txHash, data.TxStatusInProgress, nil) tx, err := r.genTx(ctx, airdrop) if err != nil { @@ -136,32 +136,68 @@ func (r *Runner) waitForTransactionMined(ctx context.Context, transaction *types go func() { log.Debugf("waiting to mine") - if _, err := bind.WaitMined(ctx, r.RPC, transaction); err != nil { + receipt, err := bind.WaitMined(ctx, r.RPC, transaction) + if err != nil { log.WithError(err).WithField("transaction", transaction).Error("failed to wait for mined tx") - r.updateAirdropStatus(ctx, airdrop.ID, transaction.Hash().String(), data.TxStatusFailed) + r.updateAirdropStatus(ctx, airdrop.ID, transaction.Hash().String(), data.TxStatusFailed, err) + } + + if receipt.Status != types.ReceiptStatusSuccessful { + txErr, err := r.getTxError(ctx, transaction, r.Address) + if err != nil { + log.WithError(err).WithField("transaction", transaction).Error("failed to get tx error") + r.updateAirdropStatus(ctx, airdrop.ID, transaction.Hash().String(), data.TxStatusFailed, err) + } + + log.WithError(err).WithField("transaction", transaction).Error("transaction was mined with failed status") + r.updateAirdropStatus(ctx, airdrop.ID, transaction.Hash().String(), data.TxStatusFailed, txErr) } - r.updateAirdropStatus(ctx, airdrop.ID, transaction.Hash().String(), data.TxStatusCompleted) + r.updateAirdropStatus(ctx, airdrop.ID, transaction.Hash().String(), data.TxStatusCompleted, nil) - log.Debugf("was mined") + log.Debugf("was mined sucessfully") }() } +func (r *Runner) getTxError(ctx context.Context, tx *types.Transaction, txSender common.Address) (error, error) { + msg := ethereum.CallMsg{ + From: txSender, + To: tx.To(), + Gas: tx.Gas(), + GasPrice: tx.GasPrice(), + Value: tx.Value(), + Data: tx.Data(), + } + + res, err := r.RPC.CallContract(ctx, msg, nil) + if err != nil { + return nil, errors.Wrap(err, "failed to make call") + } + + return errors.New(string(res)), nil +} + // If we don't update tx status from pending, having the successful funds // transfer, it will be possible to double-spend. With this solution the // double-spend may still occur, if the service is restarted before the // successful update. There is a better solution with file creation on context // cancellation and parsing it on start. -func (r *Runner) updateAirdropStatus(ctx context.Context, id, txHash, status string) { +func (r *Runner) updateAirdropStatus(ctx context.Context, id, txHash, status string, txErr error) { running.UntilSuccess(ctx, r.log, "tx-status-updater", func(_ context.Context) (bool, error) { var ptr *string if txHash != "" { ptr = &txHash } + var errMsg *string + if txErr != nil { + msg := txErr.Error() + errMsg = &msg + } err := r.q.New().Update(id, map[string]any{ "status": status, "tx_hash": ptr, + "error": errMsg, }) return err == nil, err diff --git a/internal/data/airdrops.go b/internal/data/airdrops.go index 107f85b..3c1789b 100644 --- a/internal/data/airdrops.go +++ b/internal/data/airdrops.go @@ -24,6 +24,7 @@ type Airdrop struct { Nullifier string `db:"nullifier"` Address string `db:"address"` TxHash *string `db:"tx_hash"` + Error *string `db:"error"` Amount string `db:"amount"` Status string `db:"status"` CreatedAt time.Time `db:"created_at"` From 2d77f8599f2acc0420e9300f811452b661a33d80 Mon Sep 17 00:00:00 2001 From: Maksym Hrynenko Date: Mon, 3 Jun 2024 12:33:35 +0300 Subject: [PATCH 2/2] fix: typo in log --- internal/broadcaster/broadcaster.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/broadcaster/broadcaster.go b/internal/broadcaster/broadcaster.go index 7b1ea26..c2a8795 100644 --- a/internal/broadcaster/broadcaster.go +++ b/internal/broadcaster/broadcaster.go @@ -155,7 +155,7 @@ func (r *Runner) waitForTransactionMined(ctx context.Context, transaction *types r.updateAirdropStatus(ctx, airdrop.ID, transaction.Hash().String(), data.TxStatusCompleted, nil) - log.Debugf("was mined sucessfully") + log.Debugf("was mined successfully") }() }