12 "github.com/vapor/account"
13 acc "github.com/vapor/account"
14 "github.com/vapor/asset"
15 "github.com/vapor/blockchain/pseudohsm"
16 "github.com/vapor/blockchain/query"
17 "github.com/vapor/blockchain/signers"
18 "github.com/vapor/blockchain/txbuilder"
19 "github.com/vapor/config"
20 "github.com/vapor/consensus"
21 "github.com/vapor/crypto/ed25519/chainkd"
22 "github.com/vapor/database"
23 dbm "github.com/vapor/database/leveldb"
24 "github.com/vapor/errors"
25 "github.com/vapor/event"
26 "github.com/vapor/protocol"
27 "github.com/vapor/protocol/bc"
28 "github.com/vapor/protocol/bc/types"
31 func TestEncodeDecodeGlobalTxIndex(t *testing.T) {
36 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}),
40 globalTxIdx := database.CalcGlobalTxIndex(&want.BlockHash, want.Position)
41 blockHashGot, positionGot := parseGlobalTxIdx(globalTxIdx)
42 if *blockHashGot != want.BlockHash {
43 t.Errorf("blockHash mismatch. Get: %v. Expect: %v", *blockHashGot, want.BlockHash)
46 if positionGot != want.Position {
47 t.Errorf("position mismatch. Get: %v. Expect: %v", positionGot, want.Position)
51 func TestWalletVersion(t *testing.T) {
53 dirPath, err := ioutil.TempDir(".", "")
57 defer os.RemoveAll(dirPath)
59 testDB := dbm.NewDB("testdb", "leveldb", "temp")
60 walletStore := newMockWalletStore(testDB)
66 dispatcher := event.NewDispatcher()
67 w := mockWallet(walletStore, nil, nil, nil, dispatcher, false)
69 // legacy status test case
70 type legacyStatusInfo struct {
76 rawWallet, err := json.Marshal(legacyStatusInfo{})
78 t.Fatal("Marshal legacyStatusInfo")
81 w.store.SetWalletInfo(rawWallet)
82 rawWallet = w.store.GetWalletInfo()
84 t.Fatal("fail to load wallet StatusInfo")
87 if err := json.Unmarshal(rawWallet, &w.status); err != nil {
91 if err := w.checkWalletInfo(); err != errWalletVersionMismatch {
92 t.Fatal("fail to detect legacy wallet version")
95 // lower wallet version test case
96 lowerVersion := StatusInfo{Version: currentVersion - 1}
97 rawWallet, err = json.Marshal(lowerVersion)
99 t.Fatal("save wallet info")
102 w.store.SetWalletInfo(rawWallet)
103 rawWallet = w.store.GetWalletInfo()
104 if rawWallet == nil {
105 t.Fatal("fail to load wallet StatusInfo")
108 if err := json.Unmarshal(rawWallet, &w.status); err != nil {
112 if err := w.checkWalletInfo(); err != errWalletVersionMismatch {
113 t.Fatal("fail to detect expired wallet version")
117 func TestWalletUpdate(t *testing.T) {
118 dirPath, err := ioutil.TempDir(".", "")
122 defer os.RemoveAll(dirPath)
124 config.CommonConfig = config.DefaultConfig()
125 testDB := dbm.NewDB("testdb", "leveldb", "temp")
131 store := database.NewStore(testDB)
132 walletStore := database.NewWalletStore(testDB)
133 // walletStore := newMockWalletStore(testDB)
134 dispatcher := event.NewDispatcher()
135 txPool := protocol.NewTxPool(store, dispatcher)
137 chain, err := protocol.NewChain(store, txPool, dispatcher)
142 accountStore := database.NewAccountStore(testDB)
143 accountManager := account.NewManager(accountStore, chain)
144 hsm, err := pseudohsm.New(dirPath)
149 xpub1, _, err := hsm.XCreate("test_pub1", "password", "en")
154 testAccount, err := accountManager.Create([]chainkd.XPub{xpub1.XPub}, 1, "testAccount", signers.BIP0044)
159 controlProg, err := accountManager.CreateAddress(testAccount.ID, false)
164 controlProg.KeyIndex = 1
166 reg := asset.NewRegistry(testDB, chain)
167 asset := bc.AssetID{V0: 5}
169 utxos := []*account.UTXO{}
170 btmUtxo := mockUTXO(controlProg, consensus.BTMAssetID)
171 utxos = append(utxos, btmUtxo)
172 OtherUtxo := mockUTXO(controlProg, &asset)
173 utxos = append(utxos, OtherUtxo)
175 _, txData, err := mockTxData(utxos, testAccount)
180 tx := types.NewTx(*txData)
181 block := mockSingleBlock(tx)
182 txStatus := bc.NewTransactionStatus()
183 txStatus.SetStatus(0, false)
184 txStatus.SetStatus(1, false)
185 store.SaveBlock(block, txStatus)
187 w := mockWallet(walletStore, accountManager, reg, chain, dispatcher, true)
188 err = w.AttachBlock(block)
193 if _, err := w.GetTransactionByTxID(tx.ID.String()); err != nil {
197 wants, err := w.GetTransactions(testAccount.ID, "", 1, false)
202 if wants[0].ID != tx.ID {
203 t.Fatal("account txID mismatch")
206 for position, tx := range block.Transactions {
207 get := w.store.GetGlobalTransactionIndex(tx.ID.String())
208 bh := block.BlockHeader.Hash()
209 expect := CalcGlobalTxIndex(&bh, uint64(position))
210 if !reflect.DeepEqual(get, expect) {
211 t.Fatalf("position#%d: compare retrieved globalTxIdx err", position)
216 func TestRescanWallet(t *testing.T) {
217 // prepare wallet & db
218 dirPath, err := ioutil.TempDir(".", "")
222 defer os.RemoveAll(dirPath)
224 config.CommonConfig = config.DefaultConfig()
225 testDB := dbm.NewDB("testdb", "leveldb", "temp")
226 walletStore := database.NewWalletStore(testDB)
232 store := database.NewStore(testDB)
233 dispatcher := event.NewDispatcher()
234 txPool := protocol.NewTxPool(store, dispatcher)
235 chain, err := protocol.NewChain(store, txPool, dispatcher)
240 statusInfo := StatusInfo{
241 Version: currentVersion,
242 WorkHash: bc.Hash{V0: 0xff},
244 rawWallet, err := json.Marshal(statusInfo)
246 t.Fatal("save wallet info")
249 w := mockWallet(walletStore, nil, nil, chain, dispatcher, false)
250 w.store.SetWalletInfo(rawWallet)
251 rawWallet = w.store.GetWalletInfo()
252 if rawWallet == nil {
253 t.Fatal("fail to load wallet StatusInfo")
256 if err := json.Unmarshal(rawWallet, &w.status); err != nil {
261 if err := w.loadWalletInfo(); err != nil {
265 block := config.GenesisBlock()
266 if w.status.WorkHash != block.Hash() {
267 t.Fatal("reattach from genesis block")
271 func TestMemPoolTxQueryLoop(t *testing.T) {
272 dirPath, err := ioutil.TempDir(".", "")
276 config.CommonConfig = config.DefaultConfig()
277 testDB := dbm.NewDB("testdb", "leveldb", dirPath)
280 os.RemoveAll(dirPath)
283 store := database.NewStore(testDB)
284 dispatcher := event.NewDispatcher()
285 txPool := protocol.NewTxPool(store, dispatcher)
287 chain, err := protocol.NewChain(store, txPool, dispatcher)
292 accountStore := database.NewAccountStore(testDB)
293 accountManager := account.NewManager(accountStore, chain)
294 hsm, err := pseudohsm.New(dirPath)
299 xpub1, _, err := hsm.XCreate("test_pub1", "password", "en")
304 testAccount, err := accountManager.Create([]chainkd.XPub{xpub1.XPub}, 1, "testAccount", signers.BIP0044)
309 controlProg, err := accountManager.CreateAddress(testAccount.ID, false)
314 controlProg.KeyIndex = 1
316 reg := asset.NewRegistry(testDB, chain)
317 asset := bc.AssetID{V0: 5}
319 utxos := []*account.UTXO{}
320 btmUtxo := mockUTXO(controlProg, consensus.BTMAssetID)
321 utxos = append(utxos, btmUtxo)
322 OtherUtxo := mockUTXO(controlProg, &asset)
323 utxos = append(utxos, OtherUtxo)
325 _, txData, err := mockTxData(utxos, testAccount)
330 tx := types.NewTx(*txData)
331 //block := mockSingleBlock(tx)
332 txStatus := bc.NewTransactionStatus()
333 txStatus.SetStatus(0, false)
334 walletStore := database.NewWalletStore(testDB)
335 w, err := NewWallet(walletStore, accountManager, reg, hsm, chain, dispatcher, false)
336 go w.memPoolTxQueryLoop()
337 w.eventDispatcher.Post(protocol.TxMsgEvent{TxMsg: &protocol.TxPoolMsg{TxDesc: &protocol.TxDesc{Tx: tx}, MsgType: protocol.MsgNewTx}})
338 time.Sleep(time.Millisecond * 10)
339 if _, err := w.GetUnconfirmedTxByTxID(tx.ID.String()); err != nil {
340 t.Fatal("dispatch new tx msg error:", err)
342 w.eventDispatcher.Post(protocol.TxMsgEvent{TxMsg: &protocol.TxPoolMsg{TxDesc: &protocol.TxDesc{Tx: tx}, MsgType: protocol.MsgRemoveTx}})
343 time.Sleep(time.Millisecond * 10)
344 txs, err := w.GetUnconfirmedTxs(testAccount.ID)
346 t.Fatal("get unconfirmed tx error:", err)
350 t.Fatal("dispatch remove tx msg error")
353 w.eventDispatcher.Post(protocol.TxMsgEvent{TxMsg: &protocol.TxPoolMsg{TxDesc: &protocol.TxDesc{Tx: tx}, MsgType: 2}})
356 func mockUTXO(controlProg *account.CtrlProgram, assetID *bc.AssetID) *account.UTXO {
357 utxo := &account.UTXO{}
358 utxo.OutputID = bc.Hash{V0: 1}
359 utxo.SourceID = bc.Hash{V0: 2}
360 utxo.AssetID = *assetID
361 utxo.Amount = 1000000000
363 utxo.ControlProgram = controlProg.ControlProgram
364 utxo.AccountID = controlProg.AccountID
365 utxo.Address = controlProg.Address
366 utxo.ControlProgramIndex = controlProg.KeyIndex
370 func mockTxData(utxos []*account.UTXO, testAccount *account.Account) (*txbuilder.Template, *types.TxData, error) {
371 tplBuilder := txbuilder.NewBuilder(time.Now())
373 for _, utxo := range utxos {
374 txInput, sigInst, err := account.UtxoToInputs(testAccount.Signer, utxo)
378 tplBuilder.AddInput(txInput, sigInst)
380 out := &types.TxOutput{}
381 if utxo.AssetID == *consensus.BTMAssetID {
382 out = types.NewIntraChainOutput(utxo.AssetID, 100, utxo.ControlProgram)
384 out = types.NewIntraChainOutput(utxo.AssetID, utxo.Amount, utxo.ControlProgram)
386 tplBuilder.AddOutput(out)
389 return tplBuilder.Build()
392 func mockWallet(store WalletStore, account *account.Manager, asset *asset.Registry, chain *protocol.Chain, dispatcher *event.Dispatcher, txIndexFlag bool) *Wallet {
398 RecoveryMgr: newRecoveryManager(store, account),
399 eventDispatcher: dispatcher,
400 TxIndexFlag: txIndexFlag,
402 wallet.txMsgSub, _ = wallet.eventDispatcher.Subscribe(protocol.TxMsgEvent{})
406 func mockSingleBlock(tx *types.Tx) *types.Block {
408 BlockHeader: types.BlockHeader{
412 Transactions: []*types.Tx{config.GenesisTx(), tx},
417 WalletKey = []byte{0x00, 0x3a}
418 TxIndexPrefix = []byte{0x01, 0x3a}
419 TxPrefix = []byte{0x02, 0x3a}
422 func CalcGlobalTxIndex(blockHash *bc.Hash, position uint64) []byte {
423 txIdx := make([]byte, 40)
424 copy(txIdx[:32], blockHash.Bytes())
425 binary.BigEndian.PutUint64(txIdx[32:], position)
429 func calcTxIndexKey(txID string) []byte {
430 return append(TxIndexPrefix, []byte(txID)...)
433 func calcAnnotatedKey(formatKey string) []byte {
434 return append(TxPrefix, []byte(formatKey)...)
437 type mockAccountStore struct {
442 // NewAccountStore create new AccountStore.
443 func newMockAccountStore(db dbm.DB) *mockAccountStore {
444 return &mockAccountStore{
450 func (store *mockAccountStore) InitBatch() error { return nil }
451 func (store *mockAccountStore) CommitBatch() error { return nil }
452 func (store *mockAccountStore) DeleteAccount(*account.Account) error { return nil }
453 func (store *mockAccountStore) DeleteStandardUTXO(outputID bc.Hash) { return }
454 func (store *mockAccountStore) GetAccountByAlias(string) (*account.Account, error) { return nil, nil }
455 func (store *mockAccountStore) GetAccountByID(string) (*account.Account, error) { return nil, nil }
456 func (store *mockAccountStore) GetAccountIndex([]chainkd.XPub) uint64 { return 0 }
457 func (store *mockAccountStore) GetBip44ContractIndex(string, bool) uint64 { return 0 }
458 func (store *mockAccountStore) GetCoinbaseArbitrary() []byte { return nil }
459 func (store *mockAccountStore) GetContractIndex(string) uint64 { return 0 }
460 func (store *mockAccountStore) GetControlProgram(bc.Hash) (*account.CtrlProgram, error) {
463 func (store *mockAccountStore) GetUTXO(outid bc.Hash) (*account.UTXO, error) { return nil, nil }
464 func (store *mockAccountStore) GetMiningAddress() (*account.CtrlProgram, error) { return nil, nil }
465 func (store *mockAccountStore) ListAccounts(string) ([]*account.Account, error) { return nil, nil }
466 func (store *mockAccountStore) ListControlPrograms() ([]*account.CtrlProgram, error) { return nil, nil }
467 func (store *mockAccountStore) ListUTXOs() ([]*account.UTXO, error) { return nil, nil }
468 func (store *mockAccountStore) SetAccount(*account.Account) error { return nil }
469 func (store *mockAccountStore) SetAccountIndex(*account.Account) { return }
470 func (store *mockAccountStore) SetBip44ContractIndex(string, bool, uint64) { return }
471 func (store *mockAccountStore) SetCoinbaseArbitrary([]byte) { return }
472 func (store *mockAccountStore) SetContractIndex(string, uint64) { return }
473 func (store *mockAccountStore) SetControlProgram(bc.Hash, *account.CtrlProgram) error { return nil }
474 func (store *mockAccountStore) SetMiningAddress(*account.CtrlProgram) error { return nil }
475 func (store *mockAccountStore) SetStandardUTXO(outputID bc.Hash, utxo *account.UTXO) error { return nil }
477 // WalletStore store wallet using leveldb
478 type mockWalletStore struct {
483 // NewWalletStore create new WalletStore struct
484 func newMockWalletStore(db dbm.DB) *mockWalletStore {
485 return &mockWalletStore{
491 func (store *mockWalletStore) InitBatch() error { return nil }
492 func (store *mockWalletStore) CommitBatch() error { return nil }
493 func (store *mockWalletStore) DeleteContractUTXO(bc.Hash) { return }
494 func (store *mockWalletStore) DeleteRecoveryStatus() { return }
495 func (store *mockWalletStore) DeleteTransactions(uint64) { return }
496 func (store *mockWalletStore) DeleteUnconfirmedTransaction(string) { return }
497 func (store *mockWalletStore) DeleteWalletTransactions() { return }
498 func (store *mockWalletStore) DeleteWalletUTXOs() { return }
499 func (store *mockWalletStore) GetAsset(*bc.AssetID) (*asset.Asset, error) { return nil, nil }
500 func (store *mockWalletStore) GetControlProgram(bc.Hash) (*acc.CtrlProgram, error) { return nil, nil }
501 func (store *mockWalletStore) GetGlobalTransactionIndex(string) []byte { return nil }
502 func (store *mockWalletStore) GetStandardUTXO(bc.Hash) (*acc.UTXO, error) { return nil, nil }
504 // func (store *mockWalletStore) GetTransaction(string) (*query.AnnotatedTx, error) { return nil, nil }
505 func (store *mockWalletStore) GetUnconfirmedTransaction(string) (*query.AnnotatedTx, error) {
508 func (store *mockWalletStore) GetRecoveryStatus([]byte) []byte { return nil }
509 func (store *mockWalletStore) ListAccountUTXOs(string) ([]*acc.UTXO, error) { return nil, nil }
510 func (store *mockWalletStore) ListTransactions(string, string, uint, bool) ([]*query.AnnotatedTx, error) {
513 func (store *mockWalletStore) ListUnconfirmedTransactions() ([]*query.AnnotatedTx, error) {
516 func (store *mockWalletStore) SetAssetDefinition(*bc.AssetID, []byte) { return }
517 func (store *mockWalletStore) SetContractUTXO(bc.Hash, *acc.UTXO) error { return nil }
518 func (store *mockWalletStore) SetGlobalTransactionIndex(string, *bc.Hash, uint64) { return }
519 func (store *mockWalletStore) SetRecoveryStatus([]byte, []byte) { return }
520 func (store *mockWalletStore) SetTransaction(uint64, *query.AnnotatedTx) error { return nil }
521 func (store *mockWalletStore) SetUnconfirmedTransaction(string, *query.AnnotatedTx) error {
525 // GetTransaction get tx by txid
526 func (store *mockWalletStore) GetTransaction(txID string) (*query.AnnotatedTx, error) {
527 formatKey := store.walletDB.Get(calcTxIndexKey(txID))
528 if formatKey == nil {
529 return nil, errors.New("account TXID not found")
531 rawTx := store.walletDB.Get(calcAnnotatedKey(string(formatKey)))
532 tx := new(query.AnnotatedTx)
533 if err := json.Unmarshal(rawTx, tx); err != nil {
539 // GetWalletInfo get wallet information
540 func (store *mockWalletStore) GetWalletInfo() []byte {
541 return store.walletDB.Get([]byte(WalletKey))
544 // SetWalletInfo get wallet information
545 func (store *mockWalletStore) SetWalletInfo(rawWallet []byte) {
546 if store.batch == nil {
547 store.walletDB.Set([]byte(WalletKey), rawWallet)
549 store.batch.Set([]byte(WalletKey), rawWallet)