Skip to content

Commit 6bb1fbc

Browse files
build withdrawal transaction (#47)
* support withdraw tx * add some test * add more test * add ListOutputsByTransactionHash * fix typo * fix magic encoding * add ReadOutputBySequece * add tx field action_id * handle withdrawal transactions * slight fixes * add MixinFeeUserId * add ListConfirmedWithdrawalTransactions * slight fix * add ListConfirmedWithdrawalTransactionsBySequence * add TestAttachTxsConsumed * slight fix * fix serialize * update withdrawal hash for withdrawal tx * improve indx * slight improves * ListConfirmedWithdrawalTransactionsAfter * remove redundant tx state * fix memo check * fix strings split usage * reduce some serialization int size * fix withdrawal and mix address receivers check * update go modules * more transaction serialization improvement * fix mtg transaction serialization test * remove some deprecated code --------- Co-authored-by: Cedric Fung <[email protected]>
1 parent fe14ebc commit 6bb1fbc

11 files changed

+576
-120
lines changed

go.mod

+9-9
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
module github.com/MixinNetwork/trusted-group
22

3-
go 1.23.3
3+
go 1.23.4
44

55
require (
6-
github.com/MixinNetwork/mixin v0.18.17
6+
github.com/MixinNetwork/mixin v0.18.22
77
github.com/fox-one/mixin-sdk-go/v2 v2.0.10
88
github.com/gofrs/uuid/v5 v5.3.0
99
github.com/mattn/go-sqlite3 v1.14.24
1010
github.com/pelletier/go-toml v1.9.5
1111
github.com/shopspring/decimal v1.4.0
12-
github.com/stretchr/testify v1.9.0
12+
github.com/stretchr/testify v1.10.0
1313
)
1414

1515
require (
1616
filippo.io/edwards25519 v1.1.0 // indirect
1717
github.com/btcsuite/btcutil v1.0.2 // indirect
1818
github.com/davecgh/go-spew v1.1.1 // indirect
1919
github.com/fox-one/msgpack v1.0.0 // indirect
20-
github.com/go-resty/resty/v2 v2.16.0 // indirect
20+
github.com/go-resty/resty/v2 v2.16.3 // indirect
2121
github.com/gofrs/uuid v4.4.0+incompatible // indirect
2222
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
2323
github.com/golang/protobuf v1.5.4 // indirect
@@ -27,11 +27,11 @@ require (
2727
github.com/pmezard/go-difflib v1.0.0 // indirect
2828
github.com/vmihailenco/tagparser v0.1.2 // indirect
2929
github.com/zeebo/blake3 v0.2.4 // indirect
30-
golang.org/x/crypto v0.29.0 // indirect
31-
golang.org/x/net v0.31.0 // indirect
32-
golang.org/x/sync v0.9.0 // indirect
33-
golang.org/x/sys v0.27.0 // indirect
30+
golang.org/x/crypto v0.32.0 // indirect
31+
golang.org/x/net v0.34.0 // indirect
32+
golang.org/x/sync v0.10.0 // indirect
33+
golang.org/x/sys v0.29.0 // indirect
3434
google.golang.org/appengine v1.6.8 // indirect
35-
google.golang.org/protobuf v1.35.2 // indirect
35+
google.golang.org/protobuf v1.36.2 // indirect
3636
gopkg.in/yaml.v3 v3.0.1 // indirect
3737
)

go.sum

+16-16
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
22
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
33
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
44
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
5-
github.com/MixinNetwork/mixin v0.18.17 h1:TgkxbzUUmQAdQWPuF/FBEtSQ8Qvl4GmVLhGm5I2cqIA=
6-
github.com/MixinNetwork/mixin v0.18.17/go.mod h1:8nJ7tYEpt852/WXu6VFyqQM+hrvtbtVmzBFTsnRPHLw=
5+
github.com/MixinNetwork/mixin v0.18.22 h1:cj1FRPH2Hj3PkQYnVYngZVnrrZv27gxVlgujVwBhhkk=
6+
github.com/MixinNetwork/mixin v0.18.22/go.mod h1:elY5L05s8R63ejjY/9+Lsq8h+rHw1s7yzXVmLzkZqBA=
77
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
88
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
99
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
@@ -30,8 +30,8 @@ github.com/fox-one/msgpack v1.0.0 h1:atr4La29WdMPCoddlRAPK2e1yhBJ2cEFF+2X93KY5Vs
3030
github.com/fox-one/msgpack v1.0.0/go.mod h1:Gf/g5JQGPkB0JrQvfxCu8ZXm4jqXsCPe89mFe8i3vms=
3131
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
3232
github.com/go-resty/resty/v2 v2.12.0/go.mod h1:o0yGPrkS3lOe1+eFajk6kBW8ScXzwU3hD69/gt2yB/0=
33-
github.com/go-resty/resty/v2 v2.16.0 h1:qpKalHWI2bpp9BIKlyT8TYWEJXOk1NuKbfiT3RRnzWc=
34-
github.com/go-resty/resty/v2 v2.16.0/go.mod h1:0fHAoK7JoBy/Ch36N8VFeMsK7xQOHhvWaC3iOktwmIU=
33+
github.com/go-resty/resty/v2 v2.16.3 h1:zacNT7lt4b8M/io2Ahj6yPypL7bqx9n1iprfQuodV+E=
34+
github.com/go-resty/resty/v2 v2.16.3/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
3535
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
3636
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
3737
github.com/gofrs/uuid/v5 v5.3.0 h1:m0mUMr+oVYUdxpMLgSYCZiXe7PuVPnI94+OMeVBNedk=
@@ -98,8 +98,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
9898
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
9999
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
100100
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
101-
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
102-
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
101+
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
102+
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
103103
github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vbd1qPqc=
104104
github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
105105
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
@@ -115,8 +115,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
115115
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
116116
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
117117
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
118-
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
119-
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
118+
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
119+
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
120120
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
121121
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
122122
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@@ -139,17 +139,17 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
139139
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
140140
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
141141
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
142-
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
143-
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
142+
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
143+
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
144144
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
145145
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
146146
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
147147
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
148148
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
149149
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
150150
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
151-
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
152-
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
151+
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
152+
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
153153
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
154154
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
155155
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -163,8 +163,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
163163
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
164164
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
165165
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
166-
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
167-
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
166+
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
167+
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
168168
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
169169
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
170170
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@@ -216,8 +216,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
216216
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
217217
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
218218
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
219-
google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io=
220-
google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
219+
google.golang.org/protobuf v1.36.2 h1:R8FeyR1/eLmkutZOM5CWghmo5itiG9z0ktFlTVLuTmU=
220+
google.golang.org/protobuf v1.36.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
221221
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
222222
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
223223
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=

mtg/action.go

+2-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"context"
66
"database/sql"
77
"fmt"
8-
"strings"
98

109
"github.com/MixinNetwork/mixin/common"
1110
"github.com/MixinNetwork/mixin/crypto"
@@ -52,8 +51,8 @@ func actionJoinFromRow(row Row) (*Action, error) {
5251
if err == sql.ErrNoRows {
5352
return nil, nil
5453
}
55-
a.Senders = strings.Split(senders, ",")
56-
a.Signers = strings.Split(signers, ",")
54+
a.Senders = SplitIds(senders)
55+
a.Signers = SplitIds(signers)
5756
return &a, err
5857
}
5958

mtg/group.go

+57-1
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,13 @@ func (grp *Group) Run(ctx context.Context) {
245245
if err != nil {
246246
panic(err)
247247
}
248+
249+
// verify all withdrawal transactions
250+
logger.Verbosef("Group.Run(confirmWithdrawalTransactions)\n")
251+
err = grp.confirmWithdrawalTransactions(ctx)
252+
if err != nil {
253+
panic(err)
254+
}
248255
}
249256
}
250257

@@ -264,6 +271,30 @@ func (grp *Group) ListOutputsForTransaction(ctx context.Context, traceId string,
264271
return outputs
265272
}
266273

274+
func (grp *Group) ListOutputsByTransactionHash(ctx context.Context, hash string, sequence uint64) []*UnifiedOutput {
275+
outputs, err := grp.store.ListOutputsByTransactionHash(ctx, hash, sequence)
276+
if err != nil {
277+
panic(err)
278+
}
279+
return outputs
280+
}
281+
282+
func (grp *Group) ListUnconfirmedWithdrawalTransactions(ctx context.Context, limit int) []*Transaction {
283+
txs, err := grp.store.ListUnconfirmedWithdrawalTransactions(ctx, limit)
284+
if err != nil {
285+
panic(err)
286+
}
287+
return txs
288+
}
289+
290+
func (grp *Group) ListConfirmedWithdrawalTransactionsAfter(ctx context.Context, offset time.Time, limit int) []*Transaction {
291+
txs, err := grp.store.ListConfirmedWithdrawalTransactionsAfter(ctx, offset, limit)
292+
if err != nil {
293+
panic(err)
294+
}
295+
return txs
296+
}
297+
267298
// this function or rpc should be used only in ProcessOutput
268299
func (act *Action) CheckAssetBalanceAt(ctx context.Context, assetId string) decimal.Decimal {
269300
os, err := act.group.store.ListOutputsForAsset(ctx, act.AppId, assetId, act.consumed[assetId], act.Sequence, SafeUtxoStateUnspent, OutputsBatchSize)
@@ -345,7 +376,32 @@ func (grp *Group) snapshotTransaction(ctx context.Context, tx *Transaction) (boo
345376
if req.TransactionHash != tx.Hash.String() {
346377
panic(tx.TraceId)
347378
}
348-
return req.State == mixin.SafeUtxoStateSpent, nil
379+
return req.State == SafeUtxoStateSpent, nil
380+
}
381+
382+
func (grp *Group) confirmWithdrawalTransactions(ctx context.Context) error {
383+
txs, err := grp.store.ListUnconfirmedWithdrawalTransactions(ctx, 100)
384+
if err != nil || len(txs) == 0 {
385+
return err
386+
}
387+
for _, tx := range txs {
388+
req, err := grp.readTransactionUntilSufficient(ctx, tx.RequestID())
389+
logger.Verbosef("group.readTransactionUntilSufficient(%s, %s) => %v", tx.TraceId, tx.RequestID(), err)
390+
if err != nil {
391+
return err
392+
}
393+
if req.TransactionHash != tx.Hash.String() || req.Receivers[0].Destination != tx.Destination.String {
394+
panic(tx.TraceId)
395+
}
396+
if req.Receivers[0].WithdrawalHash == "" {
397+
continue
398+
}
399+
err = grp.store.ConfirmWithdrawalTransaction(ctx, tx.TraceId, req.Receivers[0].WithdrawalHash)
400+
if err != nil {
401+
return err
402+
}
403+
}
404+
return nil
349405
}
350406

351407
func generateGenesisId(conf *Configuration) string {

mtg/mtg_test.go

+119-3
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,12 @@ import (
2020

2121
const (
2222
USDTAssetId = "218bc6f4-7927-3f8e-8568-3a3725b74361"
23+
SOLAssetId = "64692c23-8971-4cf4-84a7-4dd1271dd887"
2324
testSender = "e14c1573-3aca-48b1-b437-766b4757b50d"
25+
26+
testWithdrawalDestination = "73yoz7kK3zgh2ScD9aTJpXCrKHETi1xyEKfMTH95ugff"
27+
testWithdrawalAmount = "0.0049"
28+
testWithdrawalMemo = "withdrawal-test"
2429
)
2530

2631
type Node struct {
@@ -51,12 +56,13 @@ func (n *Node) ProcessOutput(ctx context.Context, a *Action) ([]*Transaction, st
5156
return []*Transaction{}, ""
5257
}
5358
memo := string(b)
54-
items := strings.Split(memo, ",")
59+
items := SplitIds(memo)
5560

5661
var txs []*Transaction
5762
var storageTraceId string
5863
for _, tx := range items {
59-
if tx == "storage" {
64+
switch tx {
65+
case "storage":
6066
extra := []byte("storage-memo")
6167
enough := a.CheckAssetBalanceForStorageAt(ctx, extra)
6268
if !enough {
@@ -65,7 +71,11 @@ func (n *Node) ProcessOutput(ctx context.Context, a *Action) ([]*Transaction, st
6571
t := a.BuildStorageTransaction(ctx, extra)
6672
storageTraceId = t.TraceId
6773
txs = append(txs, t)
68-
} else {
74+
case "withdrawal":
75+
tid := "cf0564ba-bf51-4e8c-b504-3beb6c5c65e3"
76+
t := a.BuildWithdrawTransaction(ctx, tid, SOLAssetId, testWithdrawalAmount, testWithdrawalMemo, testWithdrawalDestination, "")
77+
txs = append(txs, t)
78+
default:
6979
amt := decimal.RequireFromString(tx)
7080
balance := a.CheckAssetBalanceAt(ctx, a.AssetId)
7181
if balance.Cmp(amt) < 0 {
@@ -150,6 +160,18 @@ func TestMTGCompaction(t *testing.T) {
150160
ts, _, err := node.Group.store.ListTransactions(ctx, TransactionStateInitial, 0)
151161
require.Nil(err)
152162
require.Len(ts, 1)
163+
164+
tx := ts[0]
165+
tx.consumed = node.Group.ListOutputsForTransaction(ctx, tx.TraceId, tx.Sequence)
166+
for _, o := range tx.consumed {
167+
tx.consumedIds = append(tx.consumedIds, o.OutputId)
168+
}
169+
tsb := SerializeTransactions(ts)
170+
require.Equal("010154b26ff29615c93faf99f74c4c5dcdb8847201c7d7eac8374ca5ec9dcb47a38fa5e559f9bda186359d9a1aba83cc3c0fa94c7337f67eaa3fcb95ee8513140604a20a218bc6f479273f8e85683a3725b743610006302e303033370000000000000047090500000000000000000000000000000000000000024c7337f67eaa3fcb95ee8513140604a28194ae75a00338f8ab2b4ce9ffbc87f4000000b862313237626363352d353536382d333832622d383937612d3531613131356133623734612c33306139393264622d666133362d333638302d396436392d3065363939333662373837622c38333864333032642d613131392d336462382d393966352d3034386533323235653437312c32613430363437612d376337642d333331392d616464332d6366393566346237383338642c31343662323264332d393766382d333938662d383036622d39393565633134393764373503", hex.EncodeToString(tsb))
171+
dts, err := DeserializeTransactions(tsb)
172+
require.Nil(err)
173+
require.Len(dts, 1)
174+
require.True(ts[0].Equal(dts[0]))
153175
}
154176

155177
func TestMTGCheckTxs(t *testing.T) {
@@ -221,6 +243,100 @@ func TestMTGStorage(t *testing.T) {
221243
require.Equal(storage.Hash.String(), tx.references[0].String())
222244
}
223245

246+
func TestMTGWithdrawal(t *testing.T) {
247+
require := require.New(t)
248+
ctx, node := testBuildGroup(require)
249+
require.NotNil(node)
250+
defer teardownTestDatabase(node.Group.store)
251+
252+
d, err := decimal.NewFromString("0.005")
253+
require.Nil(err)
254+
err = node.Group.store.WriteAction(ctx, &UnifiedOutput{
255+
OutputId: "7514b939-db92-3d31-abf4-7841f035e400",
256+
TransactionRequestId: "cf0564ba-bf51-4e8c-b504-3beb6c5c65e2",
257+
TransactionHash: "01c43005fd06e0b8f06a0af04faf7530331603e352a11032afd0fd9dbd84e8ee",
258+
OutputIndex: 0,
259+
AssetId: SOLAssetId,
260+
Amount: d,
261+
SendersThreshold: int64(1),
262+
Senders: []string{testSender},
263+
ReceiversThreshold: int64(node.Group.GetThreshold()),
264+
Extra: "",
265+
State: SafeUtxoStateUnspent,
266+
Sequence: 4655227,
267+
AppId: node.Group.GroupId,
268+
}, ActionStateDone)
269+
require.Nil(err)
270+
271+
testDrainInitialOutputs(ctx, require, node.Group, "withdrawal")
272+
as, err := node.Group.store.ListActions(ctx, ActionStateInitial, 0)
273+
require.Nil(err)
274+
require.Len(as, 1)
275+
wkr := node.Group.FindWorker(as[0].AppId)
276+
require.NotNil(wkr)
277+
err = node.Group.handleActionsQueue(ctx)
278+
require.Nil(err)
279+
280+
txs, _, err := node.Group.store.ListTransactions(ctx, TransactionStateInitial, 0)
281+
require.Nil(err)
282+
require.Len(txs, 1)
283+
tx, err := Deserialize(txs[0].Serialize())
284+
require.Nil(err)
285+
require.Equal(testWithdrawalAmount, tx.Amount)
286+
require.Equal(testWithdrawalMemo, tx.Memo)
287+
require.Equal(SOLAssetId, tx.AssetId)
288+
require.Equal(testWithdrawalDestination, tx.Destination.String)
289+
require.Equal("", tx.Tag.String)
290+
require.False(tx.WithdrawalHash.Valid)
291+
292+
outputs := node.Group.ListOutputsForTransaction(ctx, tx.TraceId, tx.Sequence)
293+
require.True(len(outputs) > 0)
294+
ver, consumed, err := node.Group.buildRawTransaction(ctx, tx, outputs)
295+
require.Nil(err)
296+
require.True(len(outputs) == len(consumed))
297+
raw := hex.EncodeToString(ver.Marshal())
298+
require.Equal(
299+
"77770005481360491383ebd4f0f97543f3440313b48b8fd06dcfa5a0c2cabe4252d3a8eb000101c43005fd06e0b8f06a0af04faf7530331603e352a11032afd0fd9dbd84e8ee0000000000000000000200a10003077a100000000000000000000000000000000000000000000000000000000000000000000000007777002c3733796f7a376b4b337a6768325363443961544a705843724b48455469317879454b664d544839357567666600000000000227100002f5c8b3dbb7a5b2f7e1e4640d9f61c142cda547917f227ba21ebc5d554651c50d18f71fbe1b5055f3d882a4ae2813fad315bf0dcb5a0e60f091121db882baff77f18e0e276648b1d42063f8bcf9d5a57252f4048c9939ded0999a0e263716976e0003fffe02000000000000000f7769746864726177616c2d746573740000",
300+
raw,
301+
)
302+
_, err = node.Group.updateTxWithOutputs(ctx, tx, consumed, &mixin.SafeMultisigRequest{
303+
RequestID: tx.RequestID(),
304+
TransactionHash: "f45e51276a031a46d25998605324e8a3f1b720d33f66dc226018448f53bda4c4",
305+
RawTransaction: raw,
306+
})
307+
require.Nil(err)
308+
309+
txs, _, err = node.Group.store.ListTransactions(ctx, TransactionStateInitial, 0)
310+
require.Nil(err)
311+
require.Len(txs, 0)
312+
txs, _, err = node.Group.store.ListTransactions(ctx, TransactionStateSigned, 0)
313+
require.Nil(err)
314+
require.Len(txs, 1)
315+
316+
err = node.Group.store.FinishTransaction(ctx, tx.TraceId)
317+
require.Nil(err)
318+
txs, _, err = node.Group.store.ListTransactions(ctx, TransactionStateSigned, 0)
319+
require.Nil(err)
320+
require.Len(txs, 0)
321+
txs, _, err = node.Group.store.ListTransactions(ctx, TransactionStateSnapshot, 0)
322+
require.Nil(err)
323+
require.Len(txs, 1)
324+
325+
tx = txs[0]
326+
tx.consumed = node.Group.ListOutputsForTransaction(ctx, tx.TraceId, tx.Sequence)
327+
for _, o := range tx.consumed {
328+
tx.consumedIds = append(tx.consumedIds, o.OutputId)
329+
}
330+
tsb := SerializeTransactions(txs)
331+
require.Equal("0100c8cf0564babf514e8cb5043beb6c5c65e37201c7d7eac8374ca5ec9dcb47a38fa57201c7d7eac8374ca5ec9dcb47a38fa5276192fd01413e56a50ff04061a218770d64692c2389714cf484a74dd1271dd8870006302e30303439000f7769746864726177616c2d7465737400000000004708a100000000000000000000000000000000000000017514b939db923d31abf47841f035e4007777002c3733796f7a376b4b337a6768325363443961544a705843724b48455469317879454b664d54483935756766660000", hex.EncodeToString(tsb))
332+
dtxs, err := DeserializeTransactions(tsb)
333+
require.Nil(err)
334+
require.Len(dtxs, 1)
335+
tx.Hash = crypto.Hash{}
336+
tx.Raw = nil
337+
require.True(txs[0].Equal(dtxs[0]))
338+
}
339+
224340
func testHandleCompactionTransaction(ctx context.Context, require *require.Assertions, group *Group, hash string) *UnifiedOutput {
225341
ts, _, err := group.store.ListTransactions(ctx, TransactionStateInitial, 0)
226342
require.Nil(err)

0 commit comments

Comments
 (0)