Skip to content

Commit 9722d76

Browse files
authored
deposit arrives ealier than observer confirmations (#25)
* should save failed deposit when deposit call may not be confirmed * should handle failed deposit when confirming call afterwards * slihgt fixes
1 parent e124a28 commit 9722d76

File tree

5 files changed

+118
-12
lines changed

5 files changed

+118
-12
lines changed

solana/mvm.go

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -743,7 +743,7 @@ func (node *Node) processDeposit(ctx context.Context, out *mtg.Action, restored
743743
logger.Printf("solana.ExtractTransferFromTransactionByIndex(%s %s %d) => %v", out.OutputId, out.DepositHash.String, out.DepositIndex.Int64, t)
744744
}
745745
if t == nil || t.AssetId != out.AssetId || t.Receiver != node.SolanaDepositEntry().String() {
746-
return node.failDepositRequest(ctx, out, "")
746+
return node.failDepositRequest(ctx, out, "", false)
747747
}
748748
asset, err := common.SafeReadAssetUntilSufficient(ctx, t.AssetId)
749749
if err != nil {
@@ -758,7 +758,7 @@ func (node *Node) processDeposit(ctx context.Context, out *mtg.Action, restored
758758
actual := mc.NewIntegerFromString(out.Amount.String())
759759
if expected.Cmp(actual) != 0 {
760760
logger.Printf("invalid deposit amount: %s %s", actual.String(), out.Amount.String())
761-
return node.failDepositRequest(ctx, out, "")
761+
return node.failDepositRequest(ctx, out, "", false)
762762
}
763763

764764
// user == nil: transfer solana withdrawn assets from mtg to mtg deposit entry by post call for failed prepare call
@@ -773,15 +773,15 @@ func (node *Node) processDeposit(ctx context.Context, out *mtg.Action, restored
773773
memo := solanaApp.ExtractMemoFromTransaction(ctx, tx, meta, node.SolanaPayer())
774774
logger.Printf("solana.ExtractMemoFromTransaction(%s) => %s", tx.Signatures[0].String(), memo)
775775
if memo == "" {
776-
return node.failDepositRequest(ctx, out, "")
776+
return node.failDepositRequest(ctx, out, "", false)
777777
}
778778
call, err = node.store.ReadSystemCallByRequestId(ctx, memo, common.RequestStateFailed)
779779
logger.Printf("store.ReadSystemCallByRequestId(%s) => %v %v", memo, call, err)
780780
if err != nil {
781781
panic(err)
782782
}
783783
if call == nil || call.Type != store.CallTypePrepare {
784-
return node.failDepositRequest(ctx, out, "")
784+
return node.failDepositRequest(ctx, out, "", false)
785785
}
786786
superior, err := node.store.ReadSystemCallByRequestId(ctx, call.Superior, common.RequestStateFailed)
787787
logger.Printf("store.ReadSystemCallByRequestId(%s) => %v %v", call.Superior, superior, err)
@@ -800,7 +800,7 @@ func (node *Node) processDeposit(ctx context.Context, out *mtg.Action, restored
800800
panic(err)
801801
}
802802
if call == nil || call.State != common.RequestStateDone {
803-
return node.failDepositRequest(ctx, out, "")
803+
return node.failDepositRequest(ctx, out, "", true)
804804
}
805805
switch call.Type {
806806
case store.CallTypeDeposit:
@@ -811,7 +811,7 @@ func (node *Node) processDeposit(ctx context.Context, out *mtg.Action, restored
811811
}
812812
call = superior
813813
default:
814-
return node.failDepositRequest(ctx, out, "")
814+
return node.failDepositRequest(ctx, out, "", false)
815815
}
816816
}
817817
mix, err := bot.NewMixAddressFromString(user.MixAddress)
@@ -822,7 +822,7 @@ func (node *Node) processDeposit(ctx context.Context, out *mtg.Action, restored
822822
id = common.UniqueId(id, t.Receiver)
823823
mtx := node.buildTransaction(ctx, out, node.conf.AppId, t.AssetId, mix.Members(), int(mix.Threshold), out.Amount.String(), []byte(out.DepositHash.String), id)
824824
if mtx == nil {
825-
return node.failDepositRequest(ctx, out, t.AssetId)
825+
return node.failDepositRequest(ctx, out, t.AssetId, false)
826826
}
827827
txs := []*mtg.Transaction{mtx}
828828
old := call.GetRefundIds()
@@ -838,9 +838,9 @@ func (node *Node) processDeposit(ctx context.Context, out *mtg.Action, restored
838838
return txs, ""
839839
}
840840

841-
func (node *Node) failDepositRequest(ctx context.Context, out *mtg.Action, compaction string) ([]*mtg.Transaction, string) {
841+
func (node *Node) failDepositRequest(ctx context.Context, out *mtg.Action, compaction string, save bool) ([]*mtg.Transaction, string) {
842842
logger.Printf("node.failDepositRequest(%v %s)", out, compaction)
843-
err := node.store.FailDepositRequestIfNotExist(ctx, out, compaction)
843+
err := node.store.FailDepositRequestIfNotExist(ctx, out, compaction, save)
844844
if err != nil {
845845
panic(err)
846846
}
@@ -1070,11 +1070,28 @@ func (node *Node) confirmBurnRelatedSystemCall(ctx context.Context, req *store.R
10701070
txs = append(txs, tx)
10711071
ids = append(ids, tx.TraceId)
10721072
}
1073+
1074+
fd, err := node.store.ReadFailedDepositByHash(ctx, signature)
1075+
if err != nil {
1076+
panic(err)
1077+
}
1078+
if fd != nil {
1079+
id := common.UniqueId(fd.Hash, fmt.Sprint(fd.Index))
1080+
id = common.UniqueId(id, node.SolanaDepositEntry().String())
1081+
memo := []byte(fd.Hash)
1082+
tx := node.buildTransaction(ctx, req.Output, node.conf.AppId, fd.AssetId, mix.Members(), int(mix.Threshold), fd.Amount, memo, id)
1083+
if tx == nil {
1084+
return node.failRequest(ctx, req, fd.AssetId)
1085+
}
1086+
txs = append(txs, tx)
1087+
ids = append(ids, tx.TraceId)
1088+
}
1089+
10731090
old := call.GetRefundIds()
10741091
old = append(old, ids...)
10751092
call.RefundTraces = sql.NullString{Valid: true, String: strings.Join(old, ",")}
10761093

1077-
err = node.store.ConfirmBurnRelatedSystemCallWithRequest(ctx, req, call, txs)
1094+
err = node.store.ConfirmBurnRelatedSystemCallWithRequest(ctx, req, call, fd, txs)
10781095
if err != nil {
10791096
panic(err)
10801097
}

store/call.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ func (s *SQLite3Store) ConfirmSystemCallsWithRequest(ctx context.Context, req *R
276276
return tx.Commit()
277277
}
278278

279-
func (s *SQLite3Store) ConfirmBurnRelatedSystemCallWithRequest(ctx context.Context, req *Request, call *SystemCall, txs []*mtg.Transaction) error {
279+
func (s *SQLite3Store) ConfirmBurnRelatedSystemCallWithRequest(ctx context.Context, req *Request, call *SystemCall, deposit *UnconfirmedDeposit, txs []*mtg.Transaction) error {
280280
switch call.Type {
281281
case CallTypePostProcess, CallTypeDeposit:
282282
default:
@@ -304,6 +304,13 @@ func (s *SQLite3Store) ConfirmBurnRelatedSystemCallWithRequest(ctx context.Conte
304304
return fmt.Errorf("SQLite3Store UPDATE system_calls %v", err)
305305
}
306306

307+
if deposit != nil {
308+
err = s.handleFailedDepositByRequest(ctx, tx, deposit, req)
309+
if err != nil {
310+
return err
311+
}
312+
}
313+
307314
err = s.finishRequest(ctx, tx, req, txs, "")
308315
if err != nil {
309316
return err

store/request.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ func (s *SQLite3Store) WriteDepositRequestIfNotExist(ctx context.Context, out *m
146146
return tx.Commit()
147147
}
148148

149-
func (s *SQLite3Store) FailDepositRequestIfNotExist(ctx context.Context, out *mtg.Action, compaction string) error {
149+
func (s *SQLite3Store) FailDepositRequestIfNotExist(ctx context.Context, out *mtg.Action, compaction string, save bool) error {
150150
s.mutex.Lock()
151151
defer s.mutex.Unlock()
152152

@@ -167,6 +167,13 @@ func (s *SQLite3Store) FailDepositRequestIfNotExist(ctx context.Context, out *mt
167167
return fmt.Errorf("INSERT requests %v", err)
168168
}
169169

170+
if save {
171+
err = s.writeFailedDeposit(ctx, tx, out)
172+
if err != nil {
173+
return err
174+
}
175+
}
176+
170177
err = s.writeActionResult(ctx, tx, out.OutputId, compaction, nil, out.OutputId)
171178
if err != nil {
172179
return err

store/schema.sql

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,20 @@ CREATE TABLE IF NOT EXISTS failed_calls (
239239
);
240240

241241

242+
CREATE TABLE IF NOT EXISTS unconfirmed_deposits (
243+
output_id VARCHAR NOT NULL,
244+
mixin_hash VARCHAR NOT NULL,
245+
mixin_index INTEGER NOT NULL,
246+
asset_id VARCHAR NOT NULL,
247+
amount VARCHAR NOT NULL,
248+
handled_by VARCHAR,
249+
created_at TIMESTAMP NOT NULL,
250+
PRIMARY KEY ('output_id')
251+
);
252+
253+
CREATE INDEX IF NOT EXISTS unconfirmed_deposits_by_hash ON unconfirmed_deposits(mixin_hash);
254+
255+
242256
CREATE TABLE IF NOT EXISTS burn_system_calls (
243257
call_id VARCHAR NOT NULL,
244258
asset_id VARCHAR NOT NULL,

store/unconfirmed_deposit.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package store
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
"fmt"
7+
"strings"
8+
"time"
9+
10+
"github.com/MixinNetwork/safe/mtg"
11+
)
12+
13+
type UnconfirmedDeposit struct {
14+
OutputId string
15+
Hash string
16+
Index int64
17+
AssetId string
18+
Amount string
19+
HandledBy sql.NullString
20+
CreatedAt time.Time
21+
}
22+
23+
var unconfirmedDepositCols = []string{"output_id", "mixin_hash", "mixin_index", "asset_id", "amount", "handled_by", "created_at"}
24+
25+
func unconfirmedDepositFromRow(row Row) (*UnconfirmedDeposit, error) {
26+
var d UnconfirmedDeposit
27+
err := row.Scan(&d.OutputId, &d.Hash, &d.Index, &d.AssetId, &d.Amount, &d.HandledBy, &d.CreatedAt)
28+
if err == sql.ErrNoRows {
29+
return nil, nil
30+
}
31+
return &d, err
32+
}
33+
34+
func (s *SQLite3Store) writeFailedDeposit(ctx context.Context, tx *sql.Tx, out *mtg.Action) error {
35+
existed, err := s.checkExistence(ctx, tx, "SELECT output_id FROM unconfirmed_deposits WHERE output_id=?", out.OutputId)
36+
if err != nil || existed {
37+
return err
38+
}
39+
40+
vals := []any{out.OutputId, out.DepositHash.String, out.DepositIndex, out.AssetId, out.Amount.String(), nil, out.SequencerCreatedAt}
41+
err = s.execOne(ctx, tx, buildInsertionSQL("unconfirmed_deposits", unconfirmedDepositCols), vals...)
42+
if err != nil {
43+
return fmt.Errorf("INSERT unconfirmed_deposits %v", err)
44+
}
45+
return nil
46+
}
47+
48+
func (s *SQLite3Store) handleFailedDepositByRequest(ctx context.Context, tx *sql.Tx, d *UnconfirmedDeposit, req *Request) error {
49+
err := s.execOne(ctx, tx, "UPDATE unconfirmed_deposits SET handled_by=? WHERE output_id=? AND handled_by IS NULL", req.Id, d.OutputId)
50+
if err != nil {
51+
return fmt.Errorf("UPDATE unconfirmed_deposits %v", err)
52+
}
53+
return nil
54+
}
55+
56+
func (s *SQLite3Store) ReadFailedDepositByHash(ctx context.Context, hash string) (*UnconfirmedDeposit, error) {
57+
query := fmt.Sprintf("SELECT %s FROM unconfirmed_deposits WHERE mixin_hash=?", strings.Join(unconfirmedDepositCols, ","))
58+
row := s.db.QueryRowContext(ctx, query, hash)
59+
60+
return unconfirmedDepositFromRow(row)
61+
}

0 commit comments

Comments
 (0)