8 log "github.com/sirupsen/logrus"
10 "github.com/bytom/account"
11 "github.com/bytom/asset"
12 "github.com/bytom/blockchain/query"
13 dbm "github.com/bytom/database/leveldb"
14 chainjson "github.com/bytom/encoding/json"
15 "github.com/bytom/errors"
16 "github.com/bytom/protocol/bc"
17 "github.com/bytom/protocol/bc/types"
21 //TxPrefix is wallet database transactions prefix
23 //TxIndexPrefix is wallet database tx index prefix
24 TxIndexPrefix = "TID:"
25 //TxIndexPrefix is wallet database global tx index prefix
26 GlobalTxIndexPrefix = "GTID:"
29 var ErrAccntTxIDNotFound = errors.New("account TXID not found")
31 func formatKey(blockHeight uint64, position uint32) string {
32 return fmt.Sprintf("%016x%08x", blockHeight, position)
35 func calcAnnotatedKey(formatKey string) []byte {
36 return []byte(TxPrefix + formatKey)
39 func calcDeleteKey(blockHeight uint64) []byte {
40 return []byte(fmt.Sprintf("%s%016x", TxPrefix, blockHeight))
43 func calcTxIndexKey(txID string) []byte {
44 return []byte(TxIndexPrefix + txID)
47 func calcGlobalTxIndexKey(txID string) []byte {
48 return []byte(GlobalTxIndexPrefix + txID)
51 // deleteTransaction delete transactions when orphan block rollback
52 func (w *Wallet) deleteTransactions(batch dbm.Batch, height uint64) {
53 tmpTx := query.AnnotatedTx{}
54 txIter := w.DB.IteratorPrefix(calcDeleteKey(height))
55 defer txIter.Release()
58 if err := json.Unmarshal(txIter.Value(), &tmpTx); err == nil {
59 batch.Delete(calcTxIndexKey(tmpTx.ID.String()))
61 batch.Delete(txIter.Key())
65 // saveExternalAssetDefinition save external and local assets definition,
66 // when query ,query local first and if have no then query external
67 // details see getAliasDefinition
68 func saveExternalAssetDefinition(b *types.Block, walletDB dbm.DB) {
69 storeBatch := walletDB.NewBatch()
70 defer storeBatch.Write()
72 for _, tx := range b.Transactions {
73 for _, orig := range tx.Inputs {
74 if ii, ok := orig.TypedInput.(*types.IssuanceInput); ok {
75 if isValidJSON(ii.AssetDefinition) {
76 assetID := ii.AssetID()
77 if assetExist := walletDB.Get(asset.ExtAssetKey(&assetID)); assetExist == nil {
78 storeBatch.Set(asset.ExtAssetKey(&assetID), ii.AssetDefinition)
86 // Summary is the struct of transaction's input and output summary
88 Type string `json:"type"`
89 AssetID bc.AssetID `json:"asset_id,omitempty"`
90 AssetAlias string `json:"asset_alias,omitempty"`
91 Amount uint64 `json:"amount,omitempty"`
92 AccountID string `json:"account_id,omitempty"`
93 AccountAlias string `json:"account_alias,omitempty"`
94 Arbitrary chainjson.HexBytes `json:"arbitrary,omitempty"`
97 // TxSummary is the struct of transaction summary
98 type TxSummary struct {
99 ID bc.Hash `json:"tx_id"`
100 Timestamp uint64 `json:"block_time"`
101 Inputs []Summary `json:"inputs"`
102 Outputs []Summary `json:"outputs"`
105 // indexTransactions saves all annotated transactions to the database.
106 func (w *Wallet) indexTransactions(batch dbm.Batch, b *types.Block, txStatus *bc.TransactionStatus) error {
107 annotatedTxs := w.filterAccountTxs(b, txStatus)
108 saveExternalAssetDefinition(b, w.DB)
109 annotateTxsAccount(annotatedTxs, w.DB)
111 for _, tx := range annotatedTxs {
112 rawTx, err := json.Marshal(tx)
114 log.WithFields(log.Fields{"module": logModule, "err": err}).Error("inserting annotated_txs to db")
118 batch.Set(calcAnnotatedKey(formatKey(b.Height, uint32(tx.Position))), rawTx)
119 batch.Set(calcTxIndexKey(tx.ID.String()), []byte(formatKey(b.Height, uint32(tx.Position))))
121 for _, out := range tx.Outputs {
123 batch.Set(calcGlobalTxIndexKey(out.Index), []byte(formatKey(b.Height, uint32(tx.Position))))
127 // delete unconfirmed transaction
128 batch.Delete(calcUnconfirmedTxKey(tx.ID.String()))
133 // filterAccountTxs related and build the fully annotated transactions.
134 func (w *Wallet) filterAccountTxs(b *types.Block, txStatus *bc.TransactionStatus) []*query.AnnotatedTx {
135 annotatedTxs := make([]*query.AnnotatedTx, 0, len(b.Transactions))
137 for pos, tx := range b.Transactions {
141 statusFail, _ := txStatus.GetStatus(pos)
142 annotatedTxs = append(annotatedTxs, w.buildAnnotatedTransaction(tx, b, statusFail, pos))
148 // GetTransactionByTxID get transaction by txID
149 func (w *Wallet) GetTransactionByTxID(txID string) (*query.AnnotatedTx, error) {
150 if annotatedTx, err := w.getAccountTxByTxID(txID); err == nil {
151 return annotatedTx, nil
154 return w.getGlobalTxByIndex(txID)
157 func (w *Wallet) getAccountTxByTxID(txID string) (*query.AnnotatedTx, error) {
158 annotatedTx := &query.AnnotatedTx{}
159 formatKey := w.DB.Get(calcTxIndexKey(txID))
160 if formatKey == nil {
161 return nil, ErrAccntTxIDNotFound
164 txInfo := w.DB.Get(calcAnnotatedKey(string(formatKey)))
165 if err := json.Unmarshal(txInfo, annotatedTx); err != nil {
169 annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
170 return annotatedTx, nil
173 func (w *Wallet) getGlobalTxByIndex(index string) (*query.AnnotatedTx, error) {
174 annotatedTx := &query.AnnotatedTx{}
175 formatKey := w.DB.Get(calcGlobalTxIndexKey(index))
176 if formatKey == nil {
177 return nil, ErrAccntTxIDNotFound
180 txInfo := w.DB.Get(calcAnnotatedKey(string(formatKey)))
181 if err := json.Unmarshal(txInfo, annotatedTx); err != nil {
185 annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
186 return annotatedTx, nil
189 // GetTransactionsSummary get transactions summary
190 func (w *Wallet) GetTransactionsSummary(transactions []*query.AnnotatedTx) []TxSummary {
193 for _, annotatedTx := range transactions {
194 tmpTxSummary := TxSummary{
195 Inputs: make([]Summary, len(annotatedTx.Inputs)),
196 Outputs: make([]Summary, len(annotatedTx.Outputs)),
198 Timestamp: annotatedTx.Timestamp,
201 for i, input := range annotatedTx.Inputs {
202 tmpTxSummary.Inputs[i].Type = input.Type
203 tmpTxSummary.Inputs[i].AccountID = input.AccountID
204 tmpTxSummary.Inputs[i].AccountAlias = input.AccountAlias
205 tmpTxSummary.Inputs[i].AssetID = input.AssetID
206 tmpTxSummary.Inputs[i].AssetAlias = input.AssetAlias
207 tmpTxSummary.Inputs[i].Amount = input.Amount
208 tmpTxSummary.Inputs[i].Arbitrary = input.Arbitrary
210 for j, output := range annotatedTx.Outputs {
211 tmpTxSummary.Outputs[j].Type = output.Type
212 tmpTxSummary.Outputs[j].AccountID = output.AccountID
213 tmpTxSummary.Outputs[j].AccountAlias = output.AccountAlias
214 tmpTxSummary.Outputs[j].AssetID = output.AssetID
215 tmpTxSummary.Outputs[j].AssetAlias = output.AssetAlias
216 tmpTxSummary.Outputs[j].Amount = output.Amount
219 Txs = append(Txs, tmpTxSummary)
225 func findTransactionsByAccount(annotatedTx *query.AnnotatedTx, accountID string) bool {
226 for _, input := range annotatedTx.Inputs {
227 if input.AccountID == accountID {
232 for _, output := range annotatedTx.Outputs {
233 if output.AccountID == accountID {
241 // GetTransactions get all walletDB transactions or unconfirmed transactions, and filter transactions by accountID and StartTxID optional
242 func (w *Wallet) GetTransactions(accountID string, StartTxID string, count uint, unconfirmed bool) ([]*query.AnnotatedTx, error) {
243 annotatedTxs := []*query.AnnotatedTx{}
249 startKey = calcUnconfirmedTxKey(StartTxID)
251 formatKey := w.DB.Get(calcTxIndexKey(StartTxID))
252 if formatKey == nil {
253 return nil, ErrAccntTxIDNotFound
255 startKey = calcAnnotatedKey(string(formatKey))
260 preFix = UnconfirmedTxPrefix
263 itr := w.DB.IteratorPrefixWithStart([]byte(preFix), startKey, true)
266 for txNum := count; itr.Next() && txNum > 0; {
267 annotatedTx := &query.AnnotatedTx{}
268 if err := json.Unmarshal(itr.Value(), &annotatedTx); err != nil {
272 if accountID == "" || findTransactionsByAccount(annotatedTx, accountID) {
273 annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
274 annotatedTxs = append([]*query.AnnotatedTx{annotatedTx}, annotatedTxs...)
280 sort.Sort(SortByTimestamp(annotatedTxs))
282 sort.Sort(SortByHeight(annotatedTxs))
285 return annotatedTxs, nil
288 // GetAccountBalances return all account balances
289 func (w *Wallet) GetAccountBalances(accountID string, id string) ([]AccountBalance, error) {
290 return w.indexBalances(w.GetAccountUtxos(accountID, "", false, false))
293 // AccountBalance account balance
294 type AccountBalance struct {
295 AccountID string `json:"account_id"`
296 Alias string `json:"account_alias"`
297 AssetAlias string `json:"asset_alias"`
298 AssetID string `json:"asset_id"`
299 Amount uint64 `json:"amount"`
300 AssetDefinition map[string]interface{} `json:"asset_definition"`
303 func (w *Wallet) indexBalances(accountUTXOs []*account.UTXO) ([]AccountBalance, error) {
304 accBalance := make(map[string]map[string]uint64)
305 balances := []AccountBalance{}
307 for _, accountUTXO := range accountUTXOs {
308 assetID := accountUTXO.AssetID.String()
309 if _, ok := accBalance[accountUTXO.AccountID]; ok {
310 if _, ok := accBalance[accountUTXO.AccountID][assetID]; ok {
311 accBalance[accountUTXO.AccountID][assetID] += accountUTXO.Amount
313 accBalance[accountUTXO.AccountID][assetID] = accountUTXO.Amount
316 accBalance[accountUTXO.AccountID] = map[string]uint64{assetID: accountUTXO.Amount}
320 var sortedAccount []string
321 for k := range accBalance {
322 sortedAccount = append(sortedAccount, k)
324 sort.Strings(sortedAccount)
326 for _, id := range sortedAccount {
327 var sortedAsset []string
328 for k := range accBalance[id] {
329 sortedAsset = append(sortedAsset, k)
331 sort.Strings(sortedAsset)
333 for _, assetID := range sortedAsset {
334 alias := w.AccountMgr.GetAliasByID(id)
335 targetAsset, err := w.AssetReg.GetAsset(assetID)
340 assetAlias := *targetAsset.Alias
341 balances = append(balances, AccountBalance{
345 AssetAlias: assetAlias,
346 Amount: accBalance[id][assetID],
347 AssetDefinition: targetAsset.DefinitionMap,