9 "github.com/vapor/wallet"
11 acc "github.com/vapor/account"
12 "github.com/vapor/asset"
13 "github.com/vapor/blockchain/query"
14 "github.com/vapor/blockchain/signers"
15 "github.com/vapor/crypto/ed25519/chainkd"
16 "github.com/vapor/crypto/sha3pool"
17 dbm "github.com/vapor/database/leveldb"
18 "github.com/vapor/errors"
19 "github.com/vapor/protocol/bc"
24 errAccntTxIDNotFound = errors.New("account TXID not found")
25 errGetAsset = errors.New("Failed to find asset definition")
28 func accountIndexKey(xpubs []chainkd.XPub) []byte {
31 cpy := append([]chainkd.XPub{}, xpubs[:]...)
32 sort.Sort(signers.SortKeys(cpy))
33 for _, xpub := range cpy {
34 xPubs = append(xPubs, xpub[:]...)
36 sha3pool.Sum256(hash[:], xPubs)
37 return append([]byte(dbm.AccountIndexPrefix), hash[:]...)
40 func Bip44ContractIndexKey(accountID string, change bool) []byte {
41 key := append([]byte(dbm.ContractIndexPrefix), accountID...)
43 return append(key, []byte{1}...)
45 return append(key, []byte{0}...)
48 // ContractKey account control promgram store prefix
49 func ContractKey(hash bc.Hash) []byte {
50 return append([]byte(dbm.ContractPrefix), hash.Bytes()...)
53 // AccountIDKey account id store prefix
54 func AccountIDKey(accountID string) []byte {
55 return append([]byte(dbm.AccountPrefix), []byte(accountID)...)
58 // StandardUTXOKey makes an account unspent outputs key to store
59 func StandardUTXOKey(id bc.Hash) []byte {
60 return append(dbm.UTXOPrefix, id.Bytes()...)
63 // ContractUTXOKey makes a smart contract unspent outputs key to store
64 func ContractUTXOKey(id bc.Hash) []byte {
65 return append(dbm.SUTXOPrefix, id.Bytes()...)
68 func calcDeleteKey(blockHeight uint64) []byte {
69 return []byte(fmt.Sprintf("%s%016x", dbm.TxPrefix, blockHeight))
72 func calcTxIndexKey(txID string) []byte {
73 return append(dbm.TxIndexPrefix, []byte(txID)...)
76 func calcAnnotatedKey(formatKey string) []byte {
77 return append(dbm.TxPrefix, []byte(formatKey)...)
80 func calcUnconfirmedTxKey(formatKey string) []byte {
81 return append(dbm.UnconfirmedTxPrefix, []byte(formatKey)...)
84 func CalcGlobalTxIndexKey(txID string) []byte {
85 return append(dbm.GlobalTxIndexPrefix, []byte(txID)...)
88 func CalcGlobalTxIndex(blockHash *bc.Hash, position uint64) []byte {
89 txIdx := make([]byte, 40)
90 copy(txIdx[:32], blockHash.Bytes())
91 binary.BigEndian.PutUint64(txIdx[32:], position)
95 func formatKey(blockHeight uint64, position uint32) string {
96 return fmt.Sprintf("%016x%08x", blockHeight, position)
99 func contractIndexKey(accountID string) []byte {
100 return append([]byte(dbm.ContractIndexPrefix), []byte(accountID)...)
103 func accountAliasKey(name string) []byte {
104 return append([]byte(dbm.AccountAliasPrefix), []byte(name)...)
107 // WalletStore store wallet using leveldb
108 type WalletStore struct {
113 // NewWalletStore create new WalletStore struct
114 func NewWalletStore(db dbm.DB) *WalletStore {
121 // InitBatch initial batch
122 func (store *WalletStore) InitBatch() error {
123 if store.batch != nil {
124 return errors.New("WalletStore initail fail, store batch is not nil.")
127 store.batch = store.db.NewBatch()
131 // CommitBatch commit batch
132 func (store *WalletStore) CommitBatch() error {
133 if store.batch == nil {
134 return errors.New("WalletStore commit fail, store batch is nil.")
142 // DeleteContractUTXO delete contract utxo by outputID
143 func (store *WalletStore) DeleteContractUTXO(outputID bc.Hash) {
144 if store.batch == nil {
145 store.db.Delete(ContractUTXOKey(outputID))
147 store.batch.Delete(ContractUTXOKey(outputID))
151 // DeleteRecoveryStatus delete recovery status
152 func (store *WalletStore) DeleteRecoveryStatus() {
153 if store.batch == nil {
154 store.db.Delete(dbm.RecoveryKey)
156 store.batch.Delete(dbm.RecoveryKey)
160 // DeleteTransactions delete transactions when orphan block rollback
161 func (store *WalletStore) DeleteTransactions(height uint64) {
162 batch := store.db.NewBatch()
163 if store.batch != nil {
166 txIter := store.db.IteratorPrefix(calcDeleteKey(height))
167 defer txIter.Release()
169 tmpTx := query.AnnotatedTx{}
171 if err := json.Unmarshal(txIter.Value(), &tmpTx); err == nil {
172 batch.Delete(calcTxIndexKey(tmpTx.ID.String()))
174 batch.Delete(txIter.Key())
176 if store.batch == nil {
181 // DeleteUnconfirmedTransaction delete unconfirmed tx by txID
182 func (store *WalletStore) DeleteUnconfirmedTransaction(txID string) {
183 if store.batch == nil {
184 store.db.Delete(calcUnconfirmedTxKey(txID))
186 store.batch.Delete(calcUnconfirmedTxKey(txID))
190 // DeleteWalletTransactions delete all txs in wallet
191 func (store *WalletStore) DeleteWalletTransactions() {
192 batch := store.db.NewBatch()
193 if store.batch != nil {
196 txIter := store.db.IteratorPrefix([]byte(dbm.TxPrefix))
197 defer txIter.Release()
200 batch.Delete(txIter.Key())
203 txIndexIter := store.db.IteratorPrefix([]byte(dbm.TxIndexPrefix))
204 defer txIndexIter.Release()
206 for txIndexIter.Next() {
207 batch.Delete(txIndexIter.Key())
209 if store.batch == nil {
214 // DeleteWalletUTXOs delete all txs in wallet
215 func (store *WalletStore) DeleteWalletUTXOs() {
216 batch := store.db.NewBatch()
217 if store.batch != nil {
221 ruIter := store.db.IteratorPrefix([]byte(dbm.UTXOPrefix))
222 defer ruIter.Release()
225 batch.Delete(ruIter.Key())
228 suIter := store.db.IteratorPrefix([]byte(dbm.SUTXOPrefix))
229 defer suIter.Release()
232 batch.Delete(suIter.Key())
234 if store.batch == nil {
239 // GetAsset get asset by assetID
240 func (store *WalletStore) GetAsset(assetID *bc.AssetID) (*asset.Asset, error) {
241 definitionByte := store.db.Get(asset.ExtAssetKey(assetID))
242 if definitionByte == nil {
243 return nil, errGetAsset
246 definitionMap := make(map[string]interface{})
247 if err := json.Unmarshal(definitionByte, &definitionMap); err != nil {
251 alias := assetID.String()
252 externalAsset := &asset.Asset{
255 DefinitionMap: definitionMap,
256 RawDefinitionByte: definitionByte,
258 return externalAsset, nil
261 // GetGlobalTransactionIndex get global tx by txID
262 func (store *WalletStore) GetGlobalTransactionIndex(txID string) []byte {
263 return store.db.Get(CalcGlobalTxIndexKey(txID))
266 // GetStandardUTXO get standard utxo by id
267 func (store *WalletStore) GetStandardUTXO(outid bc.Hash) (*acc.UTXO, error) {
268 rawUTXO := store.db.Get(StandardUTXOKey(outid))
270 return nil, fmt.Errorf("failed get standard UTXO, outputID: %s ", outid.String())
273 UTXO := new(acc.UTXO)
274 if err := json.Unmarshal(rawUTXO, UTXO); err != nil {
281 // GetTransaction get tx by txid
282 func (store *WalletStore) GetTransaction(txID string) (*query.AnnotatedTx, error) {
283 formatKey := store.db.Get(calcTxIndexKey(txID))
284 if formatKey == nil {
285 return nil, errAccntTxIDNotFound
288 rawTx := store.db.Get(calcAnnotatedKey(string(formatKey)))
289 tx := new(query.AnnotatedTx)
290 if err := json.Unmarshal(rawTx, tx); err != nil {
297 // GetUnconfirmedTransaction get unconfirmed tx by txID
298 func (store *WalletStore) GetUnconfirmedTransaction(txID string) (*query.AnnotatedTx, error) {
299 rawUnconfirmedTx := store.db.Get(calcUnconfirmedTxKey(txID))
300 if rawUnconfirmedTx == nil {
301 return nil, fmt.Errorf("failed get unconfirmed tx, txID: %s ", txID)
304 tx := new(query.AnnotatedTx)
305 if err := json.Unmarshal(rawUnconfirmedTx, tx); err != nil {
312 // GetRecoveryStatus delete recovery status
313 func (store *WalletStore) GetRecoveryStatus() (*wallet.RecoveryState, error) {
314 rawStatus := store.db.Get(dbm.RecoveryKey)
315 if rawStatus == nil {
316 return nil, wallet.ErrGetRecoveryStatus
319 state := new(wallet.RecoveryState)
320 if err := json.Unmarshal(rawStatus, state); err != nil {
327 // GetWalletInfo get wallet information
328 func (store *WalletStore) GetWalletInfo() (*wallet.StatusInfo, error) {
329 rawStatus := store.db.Get([]byte(dbm.WalletKey))
330 if rawStatus == nil {
331 return nil, wallet.ErrGetWalletStatusInfo
334 status := new(wallet.StatusInfo)
335 if err := json.Unmarshal(rawStatus, status); err != nil {
342 // ListAccountUTXOs get all account unspent outputs
343 func (store *WalletStore) ListAccountUTXOs(key string) ([]*acc.UTXO, error) {
344 accountUtxoIter := store.db.IteratorPrefix([]byte(key))
345 defer accountUtxoIter.Release()
347 confirmedUTXOs := []*acc.UTXO{}
348 for accountUtxoIter.Next() {
349 utxo := new(acc.UTXO)
350 if err := json.Unmarshal(accountUtxoIter.Value(), utxo); err != nil {
354 confirmedUTXOs = append(confirmedUTXOs, utxo)
356 return confirmedUTXOs, nil
359 func (store *WalletStore) ListTransactions(accountID string, StartTxID string, count uint, unconfirmed bool) ([]*query.AnnotatedTx, error) {
360 annotatedTxs := []*query.AnnotatedTx{}
362 preFix := dbm.TxPrefix
366 startKey = calcUnconfirmedTxKey(StartTxID)
368 formatKey := store.db.Get(calcTxIndexKey(StartTxID))
369 if formatKey == nil {
370 return nil, errAccntTxIDNotFound
373 startKey = calcAnnotatedKey(string(formatKey))
378 preFix = dbm.UnconfirmedTxPrefix
381 itr := store.db.IteratorPrefixWithStart([]byte(preFix), startKey, true)
384 for txNum := count; itr.Next() && txNum > 0; txNum-- {
385 annotatedTx := new(query.AnnotatedTx)
386 if err := json.Unmarshal(itr.Value(), &annotatedTx); err != nil {
390 annotatedTxs = append(annotatedTxs, annotatedTx)
393 return annotatedTxs, nil
396 // ListUnconfirmedTransactions get all unconfirmed txs
397 func (store *WalletStore) ListUnconfirmedTransactions() ([]*query.AnnotatedTx, error) {
398 annotatedTxs := []*query.AnnotatedTx{}
399 txIter := store.db.IteratorPrefix([]byte(dbm.UnconfirmedTxPrefix))
400 defer txIter.Release()
403 annotatedTx := &query.AnnotatedTx{}
404 if err := json.Unmarshal(txIter.Value(), &annotatedTx); err != nil {
408 annotatedTxs = append(annotatedTxs, annotatedTx)
410 return annotatedTxs, nil
413 // SetAssetDefinition set assetID and definition
414 func (store *WalletStore) SetAssetDefinition(assetID *bc.AssetID, definition []byte) {
415 if store.batch == nil {
416 store.db.Set(asset.ExtAssetKey(assetID), definition)
418 store.batch.Set(asset.ExtAssetKey(assetID), definition)
422 // SetContractUTXO set standard utxo
423 func (store *WalletStore) SetContractUTXO(outputID bc.Hash, utxo *acc.UTXO) error {
424 data, err := json.Marshal(utxo)
429 if store.batch == nil {
430 store.db.Set(ContractUTXOKey(outputID), data)
432 store.batch.Set(ContractUTXOKey(outputID), data)
437 // SetGlobalTransactionIndex set global tx index by blockhash and position
438 func (store *WalletStore) SetGlobalTransactionIndex(globalTxID string, blockHash *bc.Hash, position uint64) {
439 if store.batch == nil {
440 store.db.Set(CalcGlobalTxIndexKey(globalTxID), CalcGlobalTxIndex(blockHash, position))
442 store.batch.Set(CalcGlobalTxIndexKey(globalTxID), CalcGlobalTxIndex(blockHash, position))
446 // SetRecoveryStatus set recovery status
447 func (store *WalletStore) SetRecoveryStatus(recoveryState *wallet.RecoveryState) error {
448 rawStatus, err := json.Marshal(recoveryState)
453 if store.batch == nil {
454 store.db.Set(dbm.RecoveryKey, rawStatus)
456 store.batch.Set(dbm.RecoveryKey, rawStatus)
461 // SetTransaction set raw transaction by block height and tx position
462 func (store *WalletStore) SetTransaction(height uint64, tx *query.AnnotatedTx) error {
463 batch := store.db.NewBatch()
464 if store.batch != nil {
468 rawTx, err := json.Marshal(tx)
473 batch.Set(calcAnnotatedKey(formatKey(height, tx.Position)), rawTx)
474 batch.Set(calcTxIndexKey(tx.ID.String()), []byte(formatKey(height, tx.Position)))
476 if store.batch == nil {
482 // SetUnconfirmedTransaction set unconfirmed tx by txID
483 func (store *WalletStore) SetUnconfirmedTransaction(txID string, tx *query.AnnotatedTx) error {
484 rawTx, err := json.Marshal(tx)
489 if store.batch == nil {
490 store.db.Set(calcUnconfirmedTxKey(txID), rawTx)
492 store.batch.Set(calcUnconfirmedTxKey(txID), rawTx)
497 // SetWalletInfo get wallet information
498 func (store *WalletStore) SetWalletInfo(status *wallet.StatusInfo) error {
499 rawWallet, err := json.Marshal(status)
504 if store.batch == nil {
505 store.db.Set([]byte(dbm.WalletKey), rawWallet)
507 store.batch.Set([]byte(dbm.WalletKey), rawWallet)