9 log "github.com/sirupsen/logrus"
11 acc "github.com/vapor/account"
12 "github.com/vapor/asset"
13 "github.com/vapor/blockchain/query"
14 dbm "github.com/vapor/database/leveldb"
15 "github.com/vapor/errors"
16 "github.com/vapor/protocol/bc"
17 "github.com/vapor/wallet"
21 sutxoPrefix byte = iota //SUTXOPrefix is ContractUTXOKey prefix
23 txPrefix //TxPrefix is wallet database transactions prefix
24 txIndexPrefix //TxIndexPrefix is wallet database tx index prefix
25 unconfirmedTxPrefix //UnconfirmedTxPrefix is txpool unconfirmed transactions prefix
26 globalTxIndexPrefix //GlobalTxIndexPrefix is wallet database global tx index prefix
30 recoveryKey //recoveryKey key for db store recovery info.
34 walletStore = []byte("WS:")
35 SUTXOPrefix = append(walletStore, sutxoPrefix, colon)
36 AccountAliasPrefix = append(walletStore, accountAliasPrefix, colon)
37 TxPrefix = append(walletStore, txPrefix, colon) //TxPrefix is wallet database transactions prefix
38 TxIndexPrefix = append(walletStore, txIndexPrefix, colon) //TxIndexPrefix is wallet database tx index prefix
39 UnconfirmedTxPrefix = append(walletStore, unconfirmedTxPrefix, colon) //UnconfirmedTxPrefix is txpool unconfirmed transactions prefix
40 GlobalTxIndexPrefix = append(walletStore, globalTxIndexPrefix, colon) //GlobalTxIndexPrefix is wallet database global tx index prefix
41 WalletKey = append(walletStore, walletKey)
42 MiningAddressKey = append(walletStore, miningAddressKey)
43 CoinbaseAbKey = append(walletStore, coinbaseAbKey)
44 RecoveryKey = append(walletStore, recoveryKey)
47 // ContractUTXOKey makes a smart contract unspent outputs key to store
48 func ContractUTXOKey(id bc.Hash) []byte {
49 return append(SUTXOPrefix, id.Bytes()...)
52 func calcDeleteKey(blockHeight uint64) []byte {
53 return []byte(fmt.Sprintf("%s%016x", TxPrefix, blockHeight))
56 func calcTxIndexKey(txID string) []byte {
57 return append(TxIndexPrefix, []byte(txID)...)
60 func calcAnnotatedKey(formatKey string) []byte {
61 return append(TxPrefix, []byte(formatKey)...)
64 func calcUnconfirmedTxKey(formatKey string) []byte {
65 return append(UnconfirmedTxPrefix, []byte(formatKey)...)
68 func CalcGlobalTxIndexKey(txID string) []byte {
69 return append(GlobalTxIndexPrefix, []byte(txID)...)
72 func CalcGlobalTxIndex(blockHash *bc.Hash, position uint64) []byte {
73 txIdx := make([]byte, 40)
74 copy(txIdx[:32], blockHash.Bytes())
75 binary.BigEndian.PutUint64(txIdx[32:], position)
79 func formatKey(blockHeight uint64, position uint32) string {
80 return fmt.Sprintf("%016x%08x", blockHeight, position)
83 func contractIndexKey(accountID string) []byte {
84 return append(ContractIndexPrefix, []byte(accountID)...)
87 // WalletStore store wallet using leveldb
88 type WalletStore struct {
93 // NewWalletStore create new WalletStore struct
94 func NewWalletStore(db dbm.DB) *WalletStore {
101 // InitBatch initial new wallet store
102 func (store *WalletStore) InitBatch() wallet.WalletStore {
103 newStore := NewWalletStore(store.db)
104 newStore.batch = newStore.db.NewBatch()
108 // CommitBatch commit batch
109 func (store *WalletStore) CommitBatch() error {
110 if store.batch == nil {
111 return errors.New("WalletStore commit fail, store batch is nil.")
119 // DeleteContractUTXO delete contract utxo by outputID
120 func (store *WalletStore) DeleteContractUTXO(outputID bc.Hash) {
121 if store.batch == nil {
122 store.db.Delete(ContractUTXOKey(outputID))
124 store.batch.Delete(ContractUTXOKey(outputID))
128 // DeleteRecoveryStatus delete recovery status
129 func (store *WalletStore) DeleteRecoveryStatus() {
130 if store.batch == nil {
131 store.db.Delete(RecoveryKey)
133 store.batch.Delete(RecoveryKey)
137 // DeleteTransactions delete transactions when orphan block rollback
138 func (store *WalletStore) DeleteTransactions(height uint64) {
139 batch := store.db.NewBatch()
140 if store.batch != nil {
143 txIter := store.db.IteratorPrefix(calcDeleteKey(height))
144 defer txIter.Release()
147 tmpTx := new(query.AnnotatedTx)
148 if err := json.Unmarshal(txIter.Value(), tmpTx); err == nil {
149 batch.Delete(calcTxIndexKey(tmpTx.ID.String()))
151 log.WithFields(log.Fields{"module": logModule, "err": err}).Warning("fail on DeleteTransactions.")
153 batch.Delete(txIter.Key())
155 if store.batch == nil {
160 // DeleteUnconfirmedTransaction delete unconfirmed tx by txID
161 func (store *WalletStore) DeleteUnconfirmedTransaction(txID string) {
162 if store.batch == nil {
163 store.db.Delete(calcUnconfirmedTxKey(txID))
165 store.batch.Delete(calcUnconfirmedTxKey(txID))
169 // DeleteWalletTransactions delete all txs in wallet
170 func (store *WalletStore) DeleteWalletTransactions() {
171 batch := store.db.NewBatch()
172 if store.batch != nil {
175 txIter := store.db.IteratorPrefix(TxPrefix)
176 defer txIter.Release()
179 batch.Delete(txIter.Key())
182 txIndexIter := store.db.IteratorPrefix(TxIndexPrefix)
183 defer txIndexIter.Release()
185 for txIndexIter.Next() {
186 batch.Delete(txIndexIter.Key())
188 if store.batch == nil {
193 // DeleteWalletUTXOs delete all utxos in wallet
194 func (store *WalletStore) DeleteWalletUTXOs() {
195 batch := store.db.NewBatch()
196 if store.batch != nil {
200 ruIter := store.db.IteratorPrefix(UTXOPrefix)
201 defer ruIter.Release()
204 batch.Delete(ruIter.Key())
207 suIter := store.db.IteratorPrefix(SUTXOPrefix)
208 defer suIter.Release()
211 batch.Delete(suIter.Key())
213 if store.batch == nil {
218 // GetAsset get asset by assetID
219 func (store *WalletStore) GetAsset(assetID *bc.AssetID) (*asset.Asset, error) {
220 definitionByte := store.db.Get(asset.ExtAssetKey(assetID))
221 if definitionByte == nil {
222 return nil, wallet.ErrGetAsset
225 definitionMap := make(map[string]interface{})
226 if err := json.Unmarshal(definitionByte, &definitionMap); err != nil {
230 alias := assetID.String()
231 externalAsset := &asset.Asset{
234 DefinitionMap: definitionMap,
235 RawDefinitionByte: definitionByte,
237 return externalAsset, nil
240 // GetGlobalTransactionIndex get global tx by txID
241 func (store *WalletStore) GetGlobalTransactionIndex(txID string) []byte {
242 return store.db.Get(CalcGlobalTxIndexKey(txID))
245 // GetStandardUTXO get standard utxo by id
246 func (store *WalletStore) GetStandardUTXO(outid bc.Hash) (*acc.UTXO, error) {
247 rawUTXO := store.db.Get(StandardUTXOKey(outid))
249 return nil, wallet.ErrGetStandardUTXO
252 UTXO := new(acc.UTXO)
253 if err := json.Unmarshal(rawUTXO, UTXO); err != nil {
260 // GetTransaction get tx by txid
261 func (store *WalletStore) GetTransaction(txID string) (*query.AnnotatedTx, error) {
262 formatKey := store.db.Get(calcTxIndexKey(txID))
263 if formatKey == nil {
264 return nil, wallet.ErrAccntTxIDNotFound
267 rawTx := store.db.Get(calcAnnotatedKey(string(formatKey)))
268 tx := new(query.AnnotatedTx)
269 if err := json.Unmarshal(rawTx, tx); err != nil {
276 // GetUnconfirmedTransaction get unconfirmed tx by txID
277 func (store *WalletStore) GetUnconfirmedTransaction(txID string) (*query.AnnotatedTx, error) {
278 rawUnconfirmedTx := store.db.Get(calcUnconfirmedTxKey(txID))
279 if rawUnconfirmedTx == nil {
280 return nil, fmt.Errorf("failed get unconfirmed tx, txID: %s ", txID)
283 tx := new(query.AnnotatedTx)
284 if err := json.Unmarshal(rawUnconfirmedTx, tx); err != nil {
291 // GetRecoveryStatus delete recovery status
292 func (store *WalletStore) GetRecoveryStatus() (*wallet.RecoveryState, error) {
293 rawStatus := store.db.Get(RecoveryKey)
294 if rawStatus == nil {
295 return nil, wallet.ErrGetRecoveryStatus
298 state := new(wallet.RecoveryState)
299 if err := json.Unmarshal(rawStatus, state); err != nil {
306 // GetWalletInfo get wallet information
307 func (store *WalletStore) GetWalletInfo() (*wallet.StatusInfo, error) {
308 rawStatus := store.db.Get(WalletKey)
309 if rawStatus == nil {
310 return nil, wallet.ErrGetWalletStatusInfo
313 status := new(wallet.StatusInfo)
314 if err := json.Unmarshal(rawStatus, status); err != nil {
321 // ListAccountUTXOs get all account unspent outputs
322 func (store *WalletStore) ListAccountUTXOs(id string, isSmartContract bool) ([]*acc.UTXO, error) {
328 idBytes, err := hex.DecodeString(id)
333 accountUtxoIter := store.db.IteratorPrefix(append(prefix, idBytes...))
334 defer accountUtxoIter.Release()
336 confirmedUTXOs := []*acc.UTXO{}
337 for accountUtxoIter.Next() {
338 utxo := new(acc.UTXO)
339 if err := json.Unmarshal(accountUtxoIter.Value(), utxo); err != nil {
343 confirmedUTXOs = append(confirmedUTXOs, utxo)
346 return confirmedUTXOs, nil
349 func (store *WalletStore) ListTransactions(accountID string, StartTxID string, count uint, unconfirmed bool) ([]*query.AnnotatedTx, error) {
350 annotatedTxs := []*query.AnnotatedTx{}
356 startKey = calcUnconfirmedTxKey(StartTxID)
358 formatKey := store.db.Get(calcTxIndexKey(StartTxID))
359 if formatKey == nil {
360 return nil, wallet.ErrAccntTxIDNotFound
363 startKey = calcAnnotatedKey(string(formatKey))
368 preFix = UnconfirmedTxPrefix
371 itr := store.db.IteratorPrefixWithStart(preFix, startKey, true)
374 for txNum := count; itr.Next() && txNum > 0; {
375 annotatedTx := new(query.AnnotatedTx)
376 if err := json.Unmarshal(itr.Value(), annotatedTx); err != nil {
380 if accountID == "" || wallet.FindTransactionsByAccount(annotatedTx, accountID) {
381 annotatedTxs = append([]*query.AnnotatedTx{annotatedTx}, annotatedTxs...)
386 return annotatedTxs, nil
389 // ListUnconfirmedTransactions get all unconfirmed txs
390 func (store *WalletStore) ListUnconfirmedTransactions() ([]*query.AnnotatedTx, error) {
391 annotatedTxs := []*query.AnnotatedTx{}
392 txIter := store.db.IteratorPrefix(UnconfirmedTxPrefix)
393 defer txIter.Release()
396 annotatedTx := new(query.AnnotatedTx)
397 if err := json.Unmarshal(txIter.Value(), annotatedTx); err != nil {
401 annotatedTxs = append(annotatedTxs, annotatedTx)
403 return annotatedTxs, nil
406 // SetAssetDefinition set assetID and definition
407 func (store *WalletStore) SetAssetDefinition(assetID *bc.AssetID, definition []byte) {
408 if store.batch == nil {
409 store.db.Set(asset.ExtAssetKey(assetID), definition)
411 store.batch.Set(asset.ExtAssetKey(assetID), definition)
415 // SetContractUTXO set standard utxo
416 func (store *WalletStore) SetContractUTXO(outputID bc.Hash, utxo *acc.UTXO) error {
417 data, err := json.Marshal(utxo)
422 if store.batch == nil {
423 store.db.Set(ContractUTXOKey(outputID), data)
425 store.batch.Set(ContractUTXOKey(outputID), data)
430 // SetGlobalTransactionIndex set global tx index by blockhash and position
431 func (store *WalletStore) SetGlobalTransactionIndex(globalTxID string, blockHash *bc.Hash, position uint64) {
432 if store.batch == nil {
433 store.db.Set(CalcGlobalTxIndexKey(globalTxID), CalcGlobalTxIndex(blockHash, position))
435 store.batch.Set(CalcGlobalTxIndexKey(globalTxID), CalcGlobalTxIndex(blockHash, position))
439 // SetRecoveryStatus set recovery status
440 func (store *WalletStore) SetRecoveryStatus(recoveryState *wallet.RecoveryState) error {
441 rawStatus, err := json.Marshal(recoveryState)
446 if store.batch == nil {
447 store.db.Set(RecoveryKey, rawStatus)
449 store.batch.Set(RecoveryKey, rawStatus)
454 // SetTransaction set raw transaction by block height and tx position
455 func (store *WalletStore) SetTransaction(height uint64, tx *query.AnnotatedTx) error {
456 batch := store.db.NewBatch()
457 if store.batch != nil {
461 rawTx, err := json.Marshal(tx)
466 batch.Set(calcAnnotatedKey(formatKey(height, tx.Position)), rawTx)
467 batch.Set(calcTxIndexKey(tx.ID.String()), []byte(formatKey(height, tx.Position)))
469 if store.batch == nil {
475 // SetUnconfirmedTransaction set unconfirmed tx by txID
476 func (store *WalletStore) SetUnconfirmedTransaction(txID string, tx *query.AnnotatedTx) error {
477 rawTx, err := json.Marshal(tx)
482 if store.batch == nil {
483 store.db.Set(calcUnconfirmedTxKey(txID), rawTx)
485 store.batch.Set(calcUnconfirmedTxKey(txID), rawTx)
490 // SetWalletInfo get wallet information
491 func (store *WalletStore) SetWalletInfo(status *wallet.StatusInfo) error {
492 rawWallet, err := json.Marshal(status)
497 if store.batch == nil {
498 store.db.Set(WalletKey, rawWallet)
500 store.batch.Set(WalletKey, rawWallet)