9 log "github.com/sirupsen/logrus"
11 "github.com/bytom/account"
12 "github.com/bytom/asset"
13 "github.com/bytom/blockchain/query"
14 "github.com/bytom/crypto/sha3pool"
15 dbm "github.com/bytom/database/leveldb"
16 chainjson "github.com/bytom/encoding/json"
17 "github.com/bytom/errors"
18 "github.com/bytom/protocol/bc"
19 "github.com/bytom/protocol/bc/types"
23 //TxPrefix is wallet database transactions prefix
25 //TxIndexPrefix is wallet database tx index prefix
26 TxIndexPrefix = "TID:"
27 //TxIndexPrefix is wallet database global tx index prefix
28 GlobalTxIndexPrefix = "GTID:"
31 var errAccntTxIDNotFound = errors.New("account TXID not found")
33 func formatKey(blockHeight uint64, position uint32) string {
34 return fmt.Sprintf("%016x%08x", blockHeight, position)
37 func calcAnnotatedKey(formatKey string) []byte {
38 return []byte(TxPrefix + formatKey)
41 func calcDeleteKey(blockHeight uint64) []byte {
42 return []byte(fmt.Sprintf("%s%016x", TxPrefix, blockHeight))
45 func calcTxIndexKey(txID string) []byte {
46 return []byte(TxIndexPrefix + txID)
49 func calcGlobalTxIndexKey(txID string) []byte {
50 return []byte(GlobalTxIndexPrefix + txID)
53 func calcGlobalTxIndex(blockHash *bc.Hash, position uint64) []byte {
54 txIdx := make([]byte, 40)
55 copy(txIdx[:32], blockHash.Bytes())
56 binary.BigEndian.PutUint64(txIdx[32:], position)
60 func parseGlobalTxIdx(globalTxIdx []byte) (*bc.Hash, uint64) {
61 var hashBytes [32]byte
62 copy(hashBytes[:], globalTxIdx[:32])
63 hash := bc.NewHash(hashBytes)
64 position := binary.BigEndian.Uint64(globalTxIdx[32:])
65 return &hash, position
68 // deleteTransaction delete transactions when orphan block rollback
69 func (w *Wallet) deleteTransactions(batch dbm.Batch, height uint64) {
70 tmpTx := query.AnnotatedTx{}
71 txIter := w.DB.IteratorPrefix(calcDeleteKey(height))
72 defer txIter.Release()
75 if err := json.Unmarshal(txIter.Value(), &tmpTx); err == nil {
76 batch.Delete(calcTxIndexKey(tmpTx.ID.String()))
78 batch.Delete(txIter.Key())
82 // saveExternalAssetDefinition save external and local assets definition,
83 // when query ,query local first and if have no then query external
84 // details see getAliasDefinition
85 func saveExternalAssetDefinition(b *types.Block, walletDB dbm.DB) {
86 storeBatch := walletDB.NewBatch()
87 defer storeBatch.Write()
89 for _, tx := range b.Transactions {
90 for _, orig := range tx.Inputs {
91 if ii, ok := orig.TypedInput.(*types.IssuanceInput); ok {
92 if isValidJSON(ii.AssetDefinition) {
93 assetID := ii.AssetID()
94 if assetExist := walletDB.Get(asset.ExtAssetKey(&assetID)); assetExist == nil {
95 storeBatch.Set(asset.ExtAssetKey(&assetID), ii.AssetDefinition)
103 // Summary is the struct of transaction's input and output summary
104 type Summary struct {
105 Type string `json:"type"`
106 AssetID bc.AssetID `json:"asset_id,omitempty"`
107 AssetAlias string `json:"asset_alias,omitempty"`
108 Amount uint64 `json:"amount,omitempty"`
109 AccountID string `json:"account_id,omitempty"`
110 AccountAlias string `json:"account_alias,omitempty"`
111 Arbitrary chainjson.HexBytes `json:"arbitrary,omitempty"`
114 // TxSummary is the struct of transaction summary
115 type TxSummary struct {
116 ID bc.Hash `json:"tx_id"`
117 Timestamp uint64 `json:"block_time"`
118 Inputs []Summary `json:"inputs"`
119 Outputs []Summary `json:"outputs"`
122 // indexTransactions saves all annotated transactions to the database.
123 func (w *Wallet) indexTransactions(batch dbm.Batch, b *types.Block, txStatus *bc.TransactionStatus) error {
124 annotatedTxs := w.filterAccountTxs(b, txStatus)
125 saveExternalAssetDefinition(b, w.DB)
126 annotateTxsAccount(annotatedTxs, w.DB)
128 for _, tx := range annotatedTxs {
129 rawTx, err := json.Marshal(tx)
131 log.WithFields(log.Fields{"module": logModule, "err": err}).Error("inserting annotated_txs to db")
135 batch.Set(calcAnnotatedKey(formatKey(b.Height, uint32(tx.Position))), rawTx)
136 batch.Set(calcTxIndexKey(tx.ID.String()), []byte(formatKey(b.Height, uint32(tx.Position))))
138 // delete unconfirmed transaction
139 batch.Delete(calcUnconfirmedTxKey(tx.ID.String()))
146 for position, globalTx := range b.Transactions {
147 blockHash := b.BlockHeader.Hash()
148 batch.Set(calcGlobalTxIndexKey(globalTx.ID.String()), calcGlobalTxIndex(&blockHash, uint64(position)))
154 // filterAccountTxs related and build the fully annotated transactions.
155 func (w *Wallet) filterAccountTxs(b *types.Block, txStatus *bc.TransactionStatus) []*query.AnnotatedTx {
156 annotatedTxs := make([]*query.AnnotatedTx, 0, len(b.Transactions))
159 for pos, tx := range b.Transactions {
160 statusFail, _ := txStatus.GetStatus(pos)
161 for _, v := range tx.Outputs {
163 sha3pool.Sum256(hash[:], v.ControlProgram)
165 if bytes := w.DB.Get(account.ContractKey(hash)); bytes != nil {
166 annotatedTxs = append(annotatedTxs, w.buildAnnotatedTransaction(tx, b, statusFail, pos))
167 continue transactionLoop
171 for _, v := range tx.Inputs {
172 outid, err := v.SpentOutputID()
176 if bytes := w.DB.Get(account.StandardUTXOKey(outid)); bytes != nil {
177 annotatedTxs = append(annotatedTxs, w.buildAnnotatedTransaction(tx, b, statusFail, pos))
178 continue transactionLoop
186 // GetTransactionByTxID get transaction by txID
187 func (w *Wallet) GetTransactionByTxID(txID string) (*query.AnnotatedTx, error) {
188 if annotatedTx, err := w.getAccountTxByTxID(txID); err == nil {
189 return annotatedTx, nil
190 } else if !w.TxIndexFlag {
194 return w.getGlobalTxByTxID(txID)
197 func (w *Wallet) getAccountTxByTxID(txID string) (*query.AnnotatedTx, error) {
198 annotatedTx := &query.AnnotatedTx{}
199 formatKey := w.DB.Get(calcTxIndexKey(txID))
200 if formatKey == nil {
201 return nil, errAccntTxIDNotFound
204 txInfo := w.DB.Get(calcAnnotatedKey(string(formatKey)))
205 if err := json.Unmarshal(txInfo, annotatedTx); err != nil {
209 annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
210 return annotatedTx, nil
213 func (w *Wallet) getGlobalTxByTxID(txID string) (*query.AnnotatedTx, error) {
214 globalTxIdx := w.DB.Get(calcGlobalTxIndexKey(txID))
215 if globalTxIdx == nil {
216 return nil, fmt.Errorf("No transaction(tx_id=%s) ", txID)
219 blockHash, pos := parseGlobalTxIdx(globalTxIdx)
220 block, err := w.chain.GetBlockByHash(blockHash)
225 txStatus, err := w.chain.GetTransactionStatus(blockHash)
230 statusFail, err := txStatus.GetStatus(int(pos))
235 tx := block.Transactions[int(pos)]
236 return w.buildAnnotatedTransaction(tx, block, statusFail, int(pos)), nil
239 // GetTransactionsSummary get transactions summary
240 func (w *Wallet) GetTransactionsSummary(transactions []*query.AnnotatedTx) []TxSummary {
243 for _, annotatedTx := range transactions {
244 tmpTxSummary := TxSummary{
245 Inputs: make([]Summary, len(annotatedTx.Inputs)),
246 Outputs: make([]Summary, len(annotatedTx.Outputs)),
248 Timestamp: annotatedTx.Timestamp,
251 for i, input := range annotatedTx.Inputs {
252 tmpTxSummary.Inputs[i].Type = input.Type
253 tmpTxSummary.Inputs[i].AccountID = input.AccountID
254 tmpTxSummary.Inputs[i].AccountAlias = input.AccountAlias
255 tmpTxSummary.Inputs[i].AssetID = input.AssetID
256 tmpTxSummary.Inputs[i].AssetAlias = input.AssetAlias
257 tmpTxSummary.Inputs[i].Amount = input.Amount
258 tmpTxSummary.Inputs[i].Arbitrary = input.Arbitrary
260 for j, output := range annotatedTx.Outputs {
261 tmpTxSummary.Outputs[j].Type = output.Type
262 tmpTxSummary.Outputs[j].AccountID = output.AccountID
263 tmpTxSummary.Outputs[j].AccountAlias = output.AccountAlias
264 tmpTxSummary.Outputs[j].AssetID = output.AssetID
265 tmpTxSummary.Outputs[j].AssetAlias = output.AssetAlias
266 tmpTxSummary.Outputs[j].Amount = output.Amount
269 Txs = append(Txs, tmpTxSummary)
275 func findTransactionsByAccount(annotatedTx *query.AnnotatedTx, accountID string) bool {
276 for _, input := range annotatedTx.Inputs {
277 if input.AccountID == accountID {
282 for _, output := range annotatedTx.Outputs {
283 if output.AccountID == accountID {
291 // GetTransactions get all walletDB transactions, and filter transactions by accountID optional
292 func (w *Wallet) GetTransactions(accountID string) ([]*query.AnnotatedTx, error) {
293 annotatedTxs := []*query.AnnotatedTx{}
295 txIter := w.DB.IteratorPrefix([]byte(TxPrefix))
296 defer txIter.Release()
298 annotatedTx := &query.AnnotatedTx{}
299 if err := json.Unmarshal(txIter.Value(), &annotatedTx); err != nil {
303 if accountID == "" || findTransactionsByAccount(annotatedTx, accountID) {
304 annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
305 annotatedTxs = append([]*query.AnnotatedTx{annotatedTx}, annotatedTxs...)
309 return annotatedTxs, nil
312 // GetAccountBalances return all account balances
313 func (w *Wallet) GetAccountBalances(accountID string, id string) ([]AccountBalance, error) {
314 return w.indexBalances(w.GetAccountUtxos(accountID, "", false, false))
317 // AccountBalance account balance
318 type AccountBalance struct {
319 AccountID string `json:"account_id"`
320 Alias string `json:"account_alias"`
321 AssetAlias string `json:"asset_alias"`
322 AssetID string `json:"asset_id"`
323 Amount uint64 `json:"amount"`
324 AssetDefinition map[string]interface{} `json:"asset_definition"`
327 func (w *Wallet) indexBalances(accountUTXOs []*account.UTXO) ([]AccountBalance, error) {
328 accBalance := make(map[string]map[string]uint64)
329 balances := []AccountBalance{}
331 for _, accountUTXO := range accountUTXOs {
332 assetID := accountUTXO.AssetID.String()
333 if _, ok := accBalance[accountUTXO.AccountID]; ok {
334 if _, ok := accBalance[accountUTXO.AccountID][assetID]; ok {
335 accBalance[accountUTXO.AccountID][assetID] += accountUTXO.Amount
337 accBalance[accountUTXO.AccountID][assetID] = accountUTXO.Amount
340 accBalance[accountUTXO.AccountID] = map[string]uint64{assetID: accountUTXO.Amount}
344 var sortedAccount []string
345 for k := range accBalance {
346 sortedAccount = append(sortedAccount, k)
348 sort.Strings(sortedAccount)
350 for _, id := range sortedAccount {
351 var sortedAsset []string
352 for k := range accBalance[id] {
353 sortedAsset = append(sortedAsset, k)
355 sort.Strings(sortedAsset)
357 for _, assetID := range sortedAsset {
358 alias := w.AccountMgr.GetAliasByID(id)
359 targetAsset, err := w.AssetReg.GetAsset(assetID)
364 assetAlias := *targetAsset.Alias
365 balances = append(balances, AccountBalance{
369 AssetAlias: assetAlias,
370 Amount: accBalance[id][assetID],
371 AssetDefinition: targetAsset.DefinitionMap,