OSDN Git Service

update GetUTXOs
[bytom/vapor.git] / wallet / wallet_test.go
1 package wallet
2
3 import (
4         "encoding/json"
5         "io/ioutil"
6         "os"
7         "reflect"
8         "testing"
9         "time"
10
11         "github.com/vapor/account"
12         "github.com/vapor/asset"
13         "github.com/vapor/blockchain/pseudohsm"
14         "github.com/vapor/blockchain/signers"
15         "github.com/vapor/blockchain/txbuilder"
16         "github.com/vapor/config"
17         "github.com/vapor/consensus"
18         "github.com/vapor/crypto/ed25519/chainkd"
19         "github.com/vapor/database"
20         dbm "github.com/vapor/database/leveldb"
21         "github.com/vapor/event"
22         "github.com/vapor/protocol"
23         "github.com/vapor/protocol/bc"
24         "github.com/vapor/protocol/bc/types"
25 )
26
27 func TestEncodeDecodeGlobalTxIndex(t *testing.T) {
28         want := &struct {
29                 BlockHash bc.Hash
30                 Position  uint64
31         }{
32                 BlockHash: bc.NewHash([32]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20}),
33                 Position:  1,
34         }
35
36         globalTxIdx := database.CalcGlobalTxIndex(&want.BlockHash, want.Position)
37         blockHashGot, positionGot := parseGlobalTxIdx(globalTxIdx)
38         if *blockHashGot != want.BlockHash {
39                 t.Errorf("blockHash mismatch. Get: %v. Expect: %v", *blockHashGot, want.BlockHash)
40         }
41
42         if positionGot != want.Position {
43                 t.Errorf("position mismatch. Get: %v. Expect: %v", positionGot, want.Position)
44         }
45 }
46
47 func TestWalletVersion(t *testing.T) {
48         // prepare wallet
49         dirPath, err := ioutil.TempDir(".", "")
50         if err != nil {
51                 t.Fatal(err)
52         }
53         defer os.RemoveAll(dirPath)
54
55         testDB := dbm.NewDB("testdb", "leveldb", "temp")
56         testStore := database.NewWalletStore(testDB)
57         defer func() {
58                 testDB.Close()
59                 os.RemoveAll("temp")
60         }()
61
62         dispatcher := event.NewDispatcher()
63         w := mockWallet(testStore, nil, nil, nil, dispatcher, false)
64
65         // legacy status test case
66         type legacyStatusInfo struct {
67                 WorkHeight uint64
68                 WorkHash   bc.Hash
69                 BestHeight uint64
70                 BestHash   bc.Hash
71         }
72         rawWallet, err := json.Marshal(legacyStatusInfo{})
73         if err != nil {
74                 t.Fatal("Marshal legacyStatusInfo")
75         }
76
77         w.store.SetWalletInfo(rawWallet)
78         rawWallet = w.store.GetWalletInfo()
79         if rawWallet == nil {
80                 t.Fatal("fail to load wallet StatusInfo")
81         }
82
83         if err := json.Unmarshal(rawWallet, &w.status); err != nil {
84                 t.Fatal(err)
85         }
86
87         if err := w.checkWalletInfo(); err != errWalletVersionMismatch {
88                 t.Fatal("fail to detect legacy wallet version")
89         }
90
91         // lower wallet version test case
92         lowerVersion := StatusInfo{Version: currentVersion - 1}
93         rawWallet, err = json.Marshal(lowerVersion)
94         if err != nil {
95                 t.Fatal("save wallet info")
96         }
97
98         w.store.SetWalletInfo(rawWallet)
99         rawWallet = w.store.GetWalletInfo()
100         if rawWallet == nil {
101                 t.Fatal("fail to load wallet StatusInfo")
102         }
103
104         if err := json.Unmarshal(rawWallet, &w.status); err != nil {
105                 t.Fatal(err)
106         }
107
108         if err := w.checkWalletInfo(); err != errWalletVersionMismatch {
109                 t.Fatal("fail to detect expired wallet version")
110         }
111 }
112
113 func TestWalletUpdate(t *testing.T) {
114         dirPath, err := ioutil.TempDir(".", "")
115         if err != nil {
116                 t.Fatal(err)
117         }
118         defer os.RemoveAll(dirPath)
119
120         config.CommonConfig = config.DefaultConfig()
121         testDB := dbm.NewDB("testdb", "leveldb", "temp")
122         defer func() {
123                 testDB.Close()
124                 os.RemoveAll("temp")
125         }()
126
127         store := database.NewStore(testDB)
128         testStore := database.NewWalletStore(testDB)
129         dispatcher := event.NewDispatcher()
130         txPool := protocol.NewTxPool(store, dispatcher)
131
132         chain, err := protocol.NewChain(store, txPool, dispatcher)
133         if err != nil {
134                 t.Fatal(err)
135         }
136
137         accountStore := database.NewAccountStore(testDB)
138         accountManager := account.NewManager(accountStore, chain)
139         hsm, err := pseudohsm.New(dirPath)
140         if err != nil {
141                 t.Fatal(err)
142         }
143
144         xpub1, _, err := hsm.XCreate("test_pub1", "password", "en")
145         if err != nil {
146                 t.Fatal(err)
147         }
148
149         testAccount, err := accountManager.Create([]chainkd.XPub{xpub1.XPub}, 1, "testAccount", signers.BIP0044)
150         if err != nil {
151                 t.Fatal(err)
152         }
153
154         controlProg, err := accountManager.CreateAddress(testAccount.ID, false)
155         if err != nil {
156                 t.Fatal(err)
157         }
158
159         controlProg.KeyIndex = 1
160
161         reg := asset.NewRegistry(testDB, chain)
162         asset := bc.AssetID{V0: 5}
163
164         utxos := []*account.UTXO{}
165         btmUtxo := mockUTXO(controlProg, consensus.BTMAssetID)
166         utxos = append(utxos, btmUtxo)
167         OtherUtxo := mockUTXO(controlProg, &asset)
168         utxos = append(utxos, OtherUtxo)
169
170         _, txData, err := mockTxData(utxos, testAccount)
171         if err != nil {
172                 t.Fatal(err)
173         }
174
175         tx := types.NewTx(*txData)
176         block := mockSingleBlock(tx)
177         txStatus := bc.NewTransactionStatus()
178         txStatus.SetStatus(0, false)
179         txStatus.SetStatus(1, false)
180         store.SaveBlock(block, txStatus)
181
182         w := mockWallet(testStore, accountManager, reg, chain, dispatcher, true)
183         err = w.AttachBlock(block)
184         if err != nil {
185                 t.Fatal(err)
186         }
187
188         if _, err := w.GetTransactionByTxID(tx.ID.String()); err != nil {
189                 t.Fatal(err)
190         }
191
192         wants, err := w.GetTransactions("")
193         if len(wants) != 1 {
194                 t.Fatal(err)
195         }
196
197         if wants[0].ID != tx.ID {
198                 t.Fatal("account txID mismatch")
199         }
200
201         for position, tx := range block.Transactions {
202                 get := w.store.GetGlobalTransaction(tx.ID.String())
203                 bh := block.BlockHeader.Hash()
204                 expect := database.CalcGlobalTxIndex(&bh, uint64(position))
205                 if !reflect.DeepEqual(get, expect) {
206                         t.Fatalf("position#%d: compare retrieved globalTxIdx err", position)
207                 }
208         }
209 }
210
211 func TestRescanWallet(t *testing.T) {
212         // prepare wallet & db
213         dirPath, err := ioutil.TempDir(".", "")
214         if err != nil {
215                 t.Fatal(err)
216         }
217         defer os.RemoveAll(dirPath)
218
219         config.CommonConfig = config.DefaultConfig()
220         testDB := dbm.NewDB("testdb", "leveldb", "temp")
221         testStore := database.NewWalletStore(testDB)
222         defer func() {
223                 testDB.Close()
224                 os.RemoveAll("temp")
225         }()
226
227         store := database.NewStore(testDB)
228         dispatcher := event.NewDispatcher()
229         txPool := protocol.NewTxPool(store, dispatcher)
230         chain, err := protocol.NewChain(store, txPool, dispatcher)
231         if err != nil {
232                 t.Fatal(err)
233         }
234
235         statusInfo := StatusInfo{
236                 Version:  currentVersion,
237                 WorkHash: bc.Hash{V0: 0xff},
238         }
239         rawWallet, err := json.Marshal(statusInfo)
240         if err != nil {
241                 t.Fatal("save wallet info")
242         }
243
244         w := mockWallet(testStore, nil, nil, chain, dispatcher, false)
245         w.store.SetWalletInfo(rawWallet)
246         rawWallet = w.store.GetWalletInfo()
247         if rawWallet == nil {
248                 t.Fatal("fail to load wallet StatusInfo")
249         }
250
251         if err := json.Unmarshal(rawWallet, &w.status); err != nil {
252                 t.Fatal(err)
253         }
254
255         // rescan wallet
256         if err := w.loadWalletInfo(); err != nil {
257                 t.Fatal(err)
258         }
259
260         block := config.GenesisBlock()
261         if w.status.WorkHash != block.Hash() {
262                 t.Fatal("reattach from genesis block")
263         }
264 }
265
266 func TestMemPoolTxQueryLoop(t *testing.T) {
267         dirPath, err := ioutil.TempDir(".", "")
268         if err != nil {
269                 t.Fatal(err)
270         }
271         config.CommonConfig = config.DefaultConfig()
272         testDB := dbm.NewDB("testdb", "leveldb", dirPath)
273         defer func() {
274                 testDB.Close()
275                 os.RemoveAll(dirPath)
276         }()
277
278         store := database.NewStore(testDB)
279         dispatcher := event.NewDispatcher()
280         txPool := protocol.NewTxPool(store, dispatcher)
281
282         chain, err := protocol.NewChain(store, txPool, dispatcher)
283         if err != nil {
284                 t.Fatal(err)
285         }
286
287         accountStore := database.NewAccountStore(testDB)
288         accountManager := account.NewManager(accountStore, chain)
289         hsm, err := pseudohsm.New(dirPath)
290         if err != nil {
291                 t.Fatal(err)
292         }
293
294         xpub1, _, err := hsm.XCreate("test_pub1", "password", "en")
295         if err != nil {
296                 t.Fatal(err)
297         }
298
299         testAccount, err := accountManager.Create([]chainkd.XPub{xpub1.XPub}, 1, "testAccount", signers.BIP0044)
300         if err != nil {
301                 t.Fatal(err)
302         }
303
304         controlProg, err := accountManager.CreateAddress(testAccount.ID, false)
305         if err != nil {
306                 t.Fatal(err)
307         }
308
309         controlProg.KeyIndex = 1
310
311         reg := asset.NewRegistry(testDB, chain)
312         asset := bc.AssetID{V0: 5}
313
314         utxos := []*account.UTXO{}
315         btmUtxo := mockUTXO(controlProg, consensus.BTMAssetID)
316         utxos = append(utxos, btmUtxo)
317         OtherUtxo := mockUTXO(controlProg, &asset)
318         utxos = append(utxos, OtherUtxo)
319
320         _, txData, err := mockTxData(utxos, testAccount)
321         if err != nil {
322                 t.Fatal(err)
323         }
324
325         tx := types.NewTx(*txData)
326         //block := mockSingleBlock(tx)
327         txStatus := bc.NewTransactionStatus()
328         txStatus.SetStatus(0, false)
329         walletStore := database.NewWalletStore(testDB)
330         w, err := NewWallet(walletStore, accountManager, reg, hsm, chain, dispatcher, false)
331         go w.memPoolTxQueryLoop()
332         w.eventDispatcher.Post(protocol.TxMsgEvent{TxMsg: &protocol.TxPoolMsg{TxDesc: &protocol.TxDesc{Tx: tx}, MsgType: protocol.MsgNewTx}})
333         time.Sleep(time.Millisecond * 10)
334         if _, err := w.GetUnconfirmedTxByTxID(tx.ID.String()); err != nil {
335                 t.Fatal("dispatch new tx msg error:", err)
336         }
337         w.eventDispatcher.Post(protocol.TxMsgEvent{TxMsg: &protocol.TxPoolMsg{TxDesc: &protocol.TxDesc{Tx: tx}, MsgType: protocol.MsgRemoveTx}})
338         time.Sleep(time.Millisecond * 10)
339         txs, err := w.GetUnconfirmedTxs(testAccount.ID)
340         if err != nil {
341                 t.Fatal("get unconfirmed tx error:", err)
342         }
343
344         if len(txs) != 0 {
345                 t.Fatal("dispatch remove tx msg error")
346         }
347
348         w.eventDispatcher.Post(protocol.TxMsgEvent{TxMsg: &protocol.TxPoolMsg{TxDesc: &protocol.TxDesc{Tx: tx}, MsgType: 2}})
349 }
350
351 func mockUTXO(controlProg *account.CtrlProgram, assetID *bc.AssetID) *account.UTXO {
352         utxo := &account.UTXO{}
353         utxo.OutputID = bc.Hash{V0: 1}
354         utxo.SourceID = bc.Hash{V0: 2}
355         utxo.AssetID = *assetID
356         utxo.Amount = 1000000000
357         utxo.SourcePos = 0
358         utxo.ControlProgram = controlProg.ControlProgram
359         utxo.AccountID = controlProg.AccountID
360         utxo.Address = controlProg.Address
361         utxo.ControlProgramIndex = controlProg.KeyIndex
362         return utxo
363 }
364
365 func mockTxData(utxos []*account.UTXO, testAccount *account.Account) (*txbuilder.Template, *types.TxData, error) {
366         tplBuilder := txbuilder.NewBuilder(time.Now())
367
368         for _, utxo := range utxos {
369                 txInput, sigInst, err := account.UtxoToInputs(testAccount.Signer, utxo)
370                 if err != nil {
371                         return nil, nil, err
372                 }
373                 tplBuilder.AddInput(txInput, sigInst)
374
375                 out := &types.TxOutput{}
376                 if utxo.AssetID == *consensus.BTMAssetID {
377                         out = types.NewIntraChainOutput(utxo.AssetID, 100, utxo.ControlProgram)
378                 } else {
379                         out = types.NewIntraChainOutput(utxo.AssetID, utxo.Amount, utxo.ControlProgram)
380                 }
381                 tplBuilder.AddOutput(out)
382         }
383
384         return tplBuilder.Build()
385 }
386
387 func mockWallet(store WalletStorer, account *account.Manager, asset *asset.Registry, chain *protocol.Chain, dispatcher *event.Dispatcher, txIndexFlag bool) *Wallet {
388         wallet := &Wallet{
389                 store:           store,
390                 AccountMgr:      account,
391                 AssetReg:        asset,
392                 chain:           chain,
393                 RecoveryMgr:     newRecoveryManager(store, account),
394                 eventDispatcher: dispatcher,
395                 TxIndexFlag:     txIndexFlag,
396         }
397         wallet.txMsgSub, _ = wallet.eventDispatcher.Subscribe(protocol.TxMsgEvent{})
398         return wallet
399 }
400
401 func mockSingleBlock(tx *types.Tx) *types.Block {
402         return &types.Block{
403                 BlockHeader: types.BlockHeader{
404                         Version: 1,
405                         Height:  1,
406                 },
407                 Transactions: []*types.Tx{config.GenesisTx(), tx},
408         }
409 }