9 log "github.com/sirupsen/logrus"
11 "github.com/vapor/account"
12 "github.com/vapor/blockchain/query"
13 "github.com/vapor/crypto/sha3pool"
14 dbm "github.com/vapor/database/leveldb"
15 chainjson "github.com/vapor/encoding/json"
16 "github.com/vapor/errors"
17 "github.com/vapor/protocol/bc"
18 "github.com/vapor/protocol/bc/types"
22 //TxPrefix is wallet database transactions prefix
24 //TxIndexPrefix is wallet database tx index prefix
25 TxIndexPrefix = "TID:"
26 //TxIndexPrefix is wallet database global tx index prefix
27 GlobalTxIndexPrefix = "GTID:"
30 var errAccntTxIDNotFound = errors.New("account TXID not found")
32 func formatKey(blockHeight uint64, position uint32) string {
33 return fmt.Sprintf("%016x%08x", blockHeight, position)
36 func calcAnnotatedKey(formatKey string) []byte {
37 return []byte(TxPrefix + formatKey)
40 func calcDeleteKey(blockHeight uint64) []byte {
41 return []byte(fmt.Sprintf("%s%016x", TxPrefix, blockHeight))
44 func calcTxIndexKey(txID string) []byte {
45 return []byte(TxIndexPrefix + txID)
48 func calcGlobalTxIndexKey(txID string) []byte {
49 return []byte(GlobalTxIndexPrefix + txID)
52 func calcGlobalTxIndex(blockHash *bc.Hash, position uint64) []byte {
53 txIdx := make([]byte, 40)
54 copy(txIdx[:32], blockHash.Bytes())
55 binary.BigEndian.PutUint64(txIdx[32:], position)
59 func parseGlobalTxIdx(globalTxIdx []byte) (*bc.Hash, uint64) {
60 var hashBytes [32]byte
61 copy(hashBytes[:], globalTxIdx[:32])
62 hash := bc.NewHash(hashBytes)
63 position := binary.BigEndian.Uint64(globalTxIdx[32:])
64 return &hash, position
67 // deleteTransaction delete transactions when orphan block rollback
68 func (w *Wallet) deleteTransactions(batch dbm.Batch, height uint64) {
69 tmpTx := query.AnnotatedTx{}
70 txIter := w.DB.IteratorPrefix(calcDeleteKey(height))
71 defer txIter.Release()
74 if err := json.Unmarshal(txIter.Value(), &tmpTx); err == nil {
75 batch.Delete(calcTxIndexKey(tmpTx.ID.String()))
77 batch.Delete(txIter.Key())
81 // saveExternalAssetDefinition save external and local assets definition,
82 // when query ,query local first and if have no then query external
83 // details see getAliasDefinition
84 func saveExternalAssetDefinition(b *types.Block, walletDB dbm.DB) {
85 storeBatch := walletDB.NewBatch()
86 defer storeBatch.Write()
88 for _, tx := range b.Transactions {
89 for _, _ = range tx.Inputs {
90 // handle cross chain input here
95 // Summary is the struct of transaction's input and output summary
97 Type string `json:"type"`
98 AssetID bc.AssetID `json:"asset_id,omitempty"`
99 AssetAlias string `json:"asset_alias,omitempty"`
100 Amount uint64 `json:"amount,omitempty"`
101 AccountID string `json:"account_id,omitempty"`
102 AccountAlias string `json:"account_alias,omitempty"`
103 Arbitrary chainjson.HexBytes `json:"arbitrary,omitempty"`
106 // TxSummary is the struct of transaction summary
107 type TxSummary struct {
108 ID bc.Hash `json:"tx_id"`
109 Timestamp uint64 `json:"block_time"`
110 Inputs []Summary `json:"inputs"`
111 Outputs []Summary `json:"outputs"`
114 // indexTransactions saves all annotated transactions to the database.
115 func (w *Wallet) indexTransactions(batch dbm.Batch, b *types.Block, txStatus *bc.TransactionStatus) error {
116 annotatedTxs := w.filterAccountTxs(b, txStatus)
117 saveExternalAssetDefinition(b, w.DB)
118 annotateTxsAccount(annotatedTxs, w.DB)
120 for _, tx := range annotatedTxs {
121 rawTx, err := json.Marshal(tx)
123 log.WithFields(log.Fields{"module": logModule, "err": err}).Error("inserting annotated_txs to db")
127 batch.Set(calcAnnotatedKey(formatKey(b.Height, uint32(tx.Position))), rawTx)
128 batch.Set(calcTxIndexKey(tx.ID.String()), []byte(formatKey(b.Height, uint32(tx.Position))))
130 // delete unconfirmed transaction
131 batch.Delete(calcUnconfirmedTxKey(tx.ID.String()))
138 for position, globalTx := range b.Transactions {
139 blockHash := b.BlockHeader.Hash()
140 batch.Set(calcGlobalTxIndexKey(globalTx.ID.String()), calcGlobalTxIndex(&blockHash, uint64(position)))
146 // filterAccountTxs related and build the fully annotated transactions.
147 func (w *Wallet) filterAccountTxs(b *types.Block, txStatus *bc.TransactionStatus) []*query.AnnotatedTx {
148 annotatedTxs := make([]*query.AnnotatedTx, 0, len(b.Transactions))
151 for pos, tx := range b.Transactions {
152 statusFail, _ := txStatus.GetStatus(pos)
153 for _, v := range tx.Outputs {
155 sha3pool.Sum256(hash[:], v.ControlProgram())
157 if bytes := w.DB.Get(account.ContractKey(hash)); bytes != nil {
158 annotatedTxs = append(annotatedTxs, w.buildAnnotatedTransaction(tx, b, statusFail, pos))
159 continue transactionLoop
163 for _, v := range tx.Inputs {
164 outid, err := v.SpentOutputID()
168 if bytes := w.DB.Get(account.StandardUTXOKey(outid)); bytes != nil {
169 annotatedTxs = append(annotatedTxs, w.buildAnnotatedTransaction(tx, b, statusFail, pos))
170 continue transactionLoop
178 // GetTransactionByTxID get transaction by txID
179 func (w *Wallet) GetTransactionByTxID(txID string) (*query.AnnotatedTx, error) {
180 if annotatedTx, err := w.getAccountTxByTxID(txID); err == nil {
181 return annotatedTx, nil
182 } else if !w.TxIndexFlag {
186 return w.getGlobalTxByTxID(txID)
189 func (w *Wallet) getAccountTxByTxID(txID string) (*query.AnnotatedTx, error) {
190 annotatedTx := &query.AnnotatedTx{}
191 formatKey := w.DB.Get(calcTxIndexKey(txID))
192 if formatKey == nil {
193 return nil, errAccntTxIDNotFound
196 txInfo := w.DB.Get(calcAnnotatedKey(string(formatKey)))
197 if err := json.Unmarshal(txInfo, annotatedTx); err != nil {
201 annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
202 return annotatedTx, nil
205 func (w *Wallet) getGlobalTxByTxID(txID string) (*query.AnnotatedTx, error) {
206 globalTxIdx := w.DB.Get(calcGlobalTxIndexKey(txID))
207 if globalTxIdx == nil {
208 return nil, fmt.Errorf("No transaction(tx_id=%s) ", txID)
211 blockHash, pos := parseGlobalTxIdx(globalTxIdx)
212 block, err := w.chain.GetBlockByHash(blockHash)
217 txStatus, err := w.chain.GetTransactionStatus(blockHash)
222 statusFail, err := txStatus.GetStatus(int(pos))
227 tx := block.Transactions[int(pos)]
228 return w.buildAnnotatedTransaction(tx, block, statusFail, int(pos)), nil
231 // GetTransactionsSummary get transactions summary
232 func (w *Wallet) GetTransactionsSummary(transactions []*query.AnnotatedTx) []TxSummary {
235 for _, annotatedTx := range transactions {
236 tmpTxSummary := TxSummary{
237 Inputs: make([]Summary, len(annotatedTx.Inputs)),
238 Outputs: make([]Summary, len(annotatedTx.Outputs)),
240 Timestamp: annotatedTx.Timestamp,
243 for i, input := range annotatedTx.Inputs {
244 tmpTxSummary.Inputs[i].Type = input.Type
245 tmpTxSummary.Inputs[i].AccountID = input.AccountID
246 tmpTxSummary.Inputs[i].AccountAlias = input.AccountAlias
247 tmpTxSummary.Inputs[i].AssetID = input.AssetID
248 tmpTxSummary.Inputs[i].AssetAlias = input.AssetAlias
249 tmpTxSummary.Inputs[i].Amount = input.Amount
250 tmpTxSummary.Inputs[i].Arbitrary = input.Arbitrary
252 for j, output := range annotatedTx.Outputs {
253 tmpTxSummary.Outputs[j].Type = output.Type
254 tmpTxSummary.Outputs[j].AccountID = output.AccountID
255 tmpTxSummary.Outputs[j].AccountAlias = output.AccountAlias
256 tmpTxSummary.Outputs[j].AssetID = output.AssetID
257 tmpTxSummary.Outputs[j].AssetAlias = output.AssetAlias
258 tmpTxSummary.Outputs[j].Amount = output.Amount
261 Txs = append(Txs, tmpTxSummary)
267 func findTransactionsByAccount(annotatedTx *query.AnnotatedTx, accountID string) bool {
268 for _, input := range annotatedTx.Inputs {
269 if input.AccountID == accountID {
274 for _, output := range annotatedTx.Outputs {
275 if output.AccountID == accountID {
283 // GetTransactions get all walletDB transactions, and filter transactions by accountID optional
284 func (w *Wallet) GetTransactions(accountID string) ([]*query.AnnotatedTx, error) {
285 annotatedTxs := []*query.AnnotatedTx{}
287 txIter := w.DB.IteratorPrefix([]byte(TxPrefix))
288 defer txIter.Release()
290 annotatedTx := &query.AnnotatedTx{}
291 if err := json.Unmarshal(txIter.Value(), &annotatedTx); err != nil {
295 if accountID == "" || findTransactionsByAccount(annotatedTx, accountID) {
296 annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
297 annotatedTxs = append([]*query.AnnotatedTx{annotatedTx}, annotatedTxs...)
301 return annotatedTxs, nil
304 // GetAccountBalances return all account balances
305 func (w *Wallet) GetAccountBalances(accountID string, id string) ([]AccountBalance, error) {
306 return w.indexBalances(w.GetAccountUtxos(accountID, "", false, false))
309 // AccountBalance account balance
310 type AccountBalance struct {
311 AccountID string `json:"account_id"`
312 Alias string `json:"account_alias"`
313 AssetAlias string `json:"asset_alias"`
314 AssetID string `json:"asset_id"`
315 Amount uint64 `json:"amount"`
316 AssetDefinition map[string]interface{} `json:"asset_definition"`
319 func (w *Wallet) indexBalances(accountUTXOs []*account.UTXO) ([]AccountBalance, error) {
320 accBalance := make(map[string]map[string]uint64)
321 balances := []AccountBalance{}
323 for _, accountUTXO := range accountUTXOs {
324 assetID := accountUTXO.AssetID.String()
325 if _, ok := accBalance[accountUTXO.AccountID]; ok {
326 if _, ok := accBalance[accountUTXO.AccountID][assetID]; ok {
327 accBalance[accountUTXO.AccountID][assetID] += accountUTXO.Amount
329 accBalance[accountUTXO.AccountID][assetID] = accountUTXO.Amount
332 accBalance[accountUTXO.AccountID] = map[string]uint64{assetID: accountUTXO.Amount}
336 var sortedAccount []string
337 for k := range accBalance {
338 sortedAccount = append(sortedAccount, k)
340 sort.Strings(sortedAccount)
342 for _, id := range sortedAccount {
343 var sortedAsset []string
344 for k := range accBalance[id] {
345 sortedAsset = append(sortedAsset, k)
347 sort.Strings(sortedAsset)
349 for _, assetID := range sortedAsset {
350 alias := w.AccountMgr.GetAliasByID(id)
351 targetAsset, err := w.AssetReg.GetAsset(assetID)
356 assetAlias := *targetAsset.Alias
357 balances = append(balances, AccountBalance{
361 AssetAlias: assetAlias,
362 Amount: accBalance[id][assetID],
363 AssetDefinition: targetAsset.DefinitionMap,