OSDN Git Service

ae3f00922c379c57dba257909ceb6159067a264b
[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         accountManager := account.NewManager(testDB, chain)
138         hsm, err := pseudohsm.New(dirPath)
139         if err != nil {
140                 t.Fatal(err)
141         }
142
143         xpub1, _, err := hsm.XCreate("test_pub1", "password", "en")
144         if err != nil {
145                 t.Fatal(err)
146         }
147
148         testAccount, err := accountManager.Create([]chainkd.XPub{xpub1.XPub}, 1, "testAccount", signers.BIP0044)
149         if err != nil {
150                 t.Fatal(err)
151         }
152
153         controlProg, err := accountManager.CreateAddress(testAccount.ID, false)
154         if err != nil {
155                 t.Fatal(err)
156         }
157
158         controlProg.KeyIndex = 1
159
160         reg := asset.NewRegistry(testDB, chain)
161         asset := bc.AssetID{V0: 5}
162
163         utxos := []*account.UTXO{}
164         btmUtxo := mockUTXO(controlProg, consensus.BTMAssetID)
165         utxos = append(utxos, btmUtxo)
166         OtherUtxo := mockUTXO(controlProg, &asset)
167         utxos = append(utxos, OtherUtxo)
168
169         _, txData, err := mockTxData(utxos, testAccount)
170         if err != nil {
171                 t.Fatal(err)
172         }
173
174         tx := types.NewTx(*txData)
175         block := mockSingleBlock(tx)
176         txStatus := bc.NewTransactionStatus()
177         txStatus.SetStatus(0, false)
178         txStatus.SetStatus(1, false)
179         store.SaveBlock(block, txStatus)
180
181         w := mockWallet(testStore, accountManager, reg, chain, dispatcher, true)
182         err = w.AttachBlock(block)
183         if err != nil {
184                 t.Fatal(err)
185         }
186
187         if _, err := w.GetTransactionByTxID(tx.ID.String()); err != nil {
188                 t.Fatal(err)
189         }
190
191         wants, err := w.GetTransactions("")
192         if len(wants) != 1 {
193                 t.Fatal(err)
194         }
195
196         if wants[0].ID != tx.ID {
197                 t.Fatal("account txID mismatch")
198         }
199
200         for position, tx := range block.Transactions {
201                 get := w.store.GetGlobalTransaction(tx.ID.String())
202                 bh := block.BlockHeader.Hash()
203                 expect := database.CalcGlobalTxIndex(&bh, uint64(position))
204                 if !reflect.DeepEqual(get, expect) {
205                         t.Fatalf("position#%d: compare retrieved globalTxIdx err", position)
206                 }
207         }
208 }
209
210 func TestRescanWallet(t *testing.T) {
211         // prepare wallet & db
212         dirPath, err := ioutil.TempDir(".", "")
213         if err != nil {
214                 t.Fatal(err)
215         }
216         defer os.RemoveAll(dirPath)
217
218         config.CommonConfig = config.DefaultConfig()
219         testDB := dbm.NewDB("testdb", "leveldb", "temp")
220         testStore := database.NewWalletStore(testDB)
221         defer func() {
222                 testDB.Close()
223                 os.RemoveAll("temp")
224         }()
225
226         store := database.NewStore(testDB)
227         dispatcher := event.NewDispatcher()
228         txPool := protocol.NewTxPool(store, dispatcher)
229         chain, err := protocol.NewChain(store, txPool, dispatcher)
230         if err != nil {
231                 t.Fatal(err)
232         }
233
234         statusInfo := StatusInfo{
235                 Version:  currentVersion,
236                 WorkHash: bc.Hash{V0: 0xff},
237         }
238         rawWallet, err := json.Marshal(statusInfo)
239         if err != nil {
240                 t.Fatal("save wallet info")
241         }
242
243         w := mockWallet(testStore, nil, nil, chain, dispatcher, false)
244         w.store.SetWalletInfo(rawWallet)
245         rawWallet = w.store.GetWalletInfo()
246         if rawWallet == nil {
247                 t.Fatal("fail to load wallet StatusInfo")
248         }
249
250         if err := json.Unmarshal(rawWallet, &w.status); err != nil {
251                 t.Fatal(err)
252         }
253
254         // rescan wallet
255         if err := w.loadWalletInfo(); err != nil {
256                 t.Fatal(err)
257         }
258
259         block := config.GenesisBlock()
260         if w.status.WorkHash != block.Hash() {
261                 t.Fatal("reattach from genesis block")
262         }
263 }
264
265 func TestMemPoolTxQueryLoop(t *testing.T) {
266         dirPath, err := ioutil.TempDir(".", "")
267         if err != nil {
268                 t.Fatal(err)
269         }
270         config.CommonConfig = config.DefaultConfig()
271         testDB := dbm.NewDB("testdb", "leveldb", dirPath)
272         testStore := database.NewWalletStore(testDB)
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         accountManager := account.NewManager(testDB, chain)
288         hsm, err := pseudohsm.New(dirPath)
289         if err != nil {
290                 t.Fatal(err)
291         }
292
293         xpub1, _, err := hsm.XCreate("test_pub1", "password", "en")
294         if err != nil {
295                 t.Fatal(err)
296         }
297
298         testAccount, err := accountManager.Create([]chainkd.XPub{xpub1.XPub}, 1, "testAccount", signers.BIP0044)
299         if err != nil {
300                 t.Fatal(err)
301         }
302
303         controlProg, err := accountManager.CreateAddress(testAccount.ID, false)
304         if err != nil {
305                 t.Fatal(err)
306         }
307
308         controlProg.KeyIndex = 1
309
310         reg := asset.NewRegistry(testDB, chain)
311         asset := bc.AssetID{V0: 5}
312
313         utxos := []*account.UTXO{}
314         btmUtxo := mockUTXO(controlProg, consensus.BTMAssetID)
315         utxos = append(utxos, btmUtxo)
316         OtherUtxo := mockUTXO(controlProg, &asset)
317         utxos = append(utxos, OtherUtxo)
318
319         _, txData, err := mockTxData(utxos, testAccount)
320         if err != nil {
321                 t.Fatal(err)
322         }
323
324         tx := types.NewTx(*txData)
325         //block := mockSingleBlock(tx)
326         txStatus := bc.NewTransactionStatus()
327         txStatus.SetStatus(0, false)
328         w, err := NewWallet(testStore, accountManager, reg, hsm, chain, dispatcher, false)
329         go w.memPoolTxQueryLoop()
330         w.eventDispatcher.Post(protocol.TxMsgEvent{TxMsg: &protocol.TxPoolMsg{TxDesc: &protocol.TxDesc{Tx: tx}, MsgType: protocol.MsgNewTx}})
331         time.Sleep(time.Millisecond * 10)
332         if _, err := w.GetUnconfirmedTxByTxID(tx.ID.String()); err != nil {
333                 t.Fatal("dispatch new tx msg error:", err)
334         }
335         w.eventDispatcher.Post(protocol.TxMsgEvent{TxMsg: &protocol.TxPoolMsg{TxDesc: &protocol.TxDesc{Tx: tx}, MsgType: protocol.MsgRemoveTx}})
336         time.Sleep(time.Millisecond * 10)
337         txs, err := w.GetUnconfirmedTxs(testAccount.ID)
338         if err != nil {
339                 t.Fatal("get unconfirmed tx error:", err)
340         }
341
342         if len(txs) != 0 {
343                 t.Fatal("dispatch remove tx msg error")
344         }
345
346         w.eventDispatcher.Post(protocol.TxMsgEvent{TxMsg: &protocol.TxPoolMsg{TxDesc: &protocol.TxDesc{Tx: tx}, MsgType: 2}})
347 }
348
349 func mockUTXO(controlProg *account.CtrlProgram, assetID *bc.AssetID) *account.UTXO {
350         utxo := &account.UTXO{}
351         utxo.OutputID = bc.Hash{V0: 1}
352         utxo.SourceID = bc.Hash{V0: 2}
353         utxo.AssetID = *assetID
354         utxo.Amount = 1000000000
355         utxo.SourcePos = 0
356         utxo.ControlProgram = controlProg.ControlProgram
357         utxo.AccountID = controlProg.AccountID
358         utxo.Address = controlProg.Address
359         utxo.ControlProgramIndex = controlProg.KeyIndex
360         return utxo
361 }
362
363 func mockTxData(utxos []*account.UTXO, testAccount *account.Account) (*txbuilder.Template, *types.TxData, error) {
364         tplBuilder := txbuilder.NewBuilder(time.Now())
365
366         for _, utxo := range utxos {
367                 txInput, sigInst, err := account.UtxoToInputs(testAccount.Signer, utxo)
368                 if err != nil {
369                         return nil, nil, err
370                 }
371                 tplBuilder.AddInput(txInput, sigInst)
372
373                 out := &types.TxOutput{}
374                 if utxo.AssetID == *consensus.BTMAssetID {
375                         out = types.NewIntraChainOutput(utxo.AssetID, 100, utxo.ControlProgram)
376                 } else {
377                         out = types.NewIntraChainOutput(utxo.AssetID, utxo.Amount, utxo.ControlProgram)
378                 }
379                 tplBuilder.AddOutput(out)
380         }
381
382         return tplBuilder.Build()
383 }
384
385 func mockWallet(store database.WalletStorer, account *account.Manager, asset *asset.Registry, chain *protocol.Chain, dispatcher *event.Dispatcher, txIndexFlag bool) *Wallet {
386         wallet := &Wallet{
387                 store:           store,
388                 AccountMgr:      account,
389                 AssetReg:        asset,
390                 chain:           chain,
391                 RecoveryMgr:     newRecoveryManager(store, account),
392                 eventDispatcher: dispatcher,
393                 TxIndexFlag:     txIndexFlag,
394         }
395         wallet.txMsgSub, _ = wallet.eventDispatcher.Subscribe(protocol.TxMsgEvent{})
396         return wallet
397 }
398
399 func mockSingleBlock(tx *types.Tx) *types.Block {
400         return &types.Block{
401                 BlockHeader: types.BlockHeader{
402                         Version: 1,
403                         Height:  1,
404                 },
405                 Transactions: []*types.Tx{config.GenesisTx(), tx},
406         }
407 }