github.com/Bytom/bytom@v1.1.2-0.20210127130405-ae40204c0b09/wallet/indexer.go (about) 1 package wallet 2 3 import ( 4 "encoding/binary" 5 "encoding/json" 6 "fmt" 7 "sort" 8 9 log "github.com/sirupsen/logrus" 10 11 "github.com/bytom/bytom/account" 12 "github.com/bytom/bytom/asset" 13 "github.com/bytom/bytom/blockchain/query" 14 "github.com/bytom/bytom/crypto/sha3pool" 15 dbm "github.com/bytom/bytom/database/leveldb" 16 chainjson "github.com/bytom/bytom/encoding/json" 17 "github.com/bytom/bytom/errors" 18 "github.com/bytom/bytom/protocol/bc" 19 "github.com/bytom/bytom/protocol/bc/types" 20 ) 21 22 const ( 23 //TxPrefix is wallet database transactions prefix 24 TxPrefix = "TXS:" 25 //TxIndexPrefix is wallet database tx index prefix 26 TxIndexPrefix = "TID:" 27 //TxIndexPrefix is wallet database global tx index prefix 28 GlobalTxIndexPrefix = "GTID:" 29 ) 30 31 var errAccntTxIDNotFound = errors.New("account TXID not found") 32 33 func formatKey(blockHeight uint64, position uint32) string { 34 return fmt.Sprintf("%016x%08x", blockHeight, position) 35 } 36 37 func calcAnnotatedKey(formatKey string) []byte { 38 return []byte(TxPrefix + formatKey) 39 } 40 41 func calcDeleteKey(blockHeight uint64) []byte { 42 return []byte(fmt.Sprintf("%s%016x", TxPrefix, blockHeight)) 43 } 44 45 func calcTxIndexKey(txID string) []byte { 46 return []byte(TxIndexPrefix + txID) 47 } 48 49 func calcGlobalTxIndexKey(txID string) []byte { 50 return []byte(GlobalTxIndexPrefix + txID) 51 } 52 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) 57 return txIdx 58 } 59 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 66 } 67 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() 73 74 for txIter.Next() { 75 if err := json.Unmarshal(txIter.Value(), &tmpTx); err == nil { 76 batch.Delete(calcTxIndexKey(tmpTx.ID.String())) 77 } 78 batch.Delete(txIter.Key()) 79 } 80 } 81 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() 88 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) 96 } 97 } 98 } 99 } 100 } 101 } 102 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"` 112 } 113 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"` 120 } 121 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) 127 128 for _, tx := range annotatedTxs { 129 rawTx, err := json.Marshal(tx) 130 if err != nil { 131 log.WithFields(log.Fields{"module": logModule, "err": err}).Error("inserting annotated_txs to db") 132 return err 133 } 134 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)))) 137 138 // delete unconfirmed transaction 139 batch.Delete(calcUnconfirmedTxKey(tx.ID.String())) 140 } 141 142 if !w.TxIndexFlag { 143 return nil 144 } 145 146 for position, globalTx := range b.Transactions { 147 blockHash := b.BlockHeader.Hash() 148 batch.Set(calcGlobalTxIndexKey(globalTx.ID.String()), calcGlobalTxIndex(&blockHash, uint64(position))) 149 } 150 151 return nil 152 } 153 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)) 157 158 transactionLoop: 159 for pos, tx := range b.Transactions { 160 statusFail, _ := txStatus.GetStatus(pos) 161 for _, v := range tx.Outputs { 162 var hash [32]byte 163 sha3pool.Sum256(hash[:], v.ControlProgram) 164 165 if bytes := w.DB.Get(account.ContractKey(hash)); bytes != nil { 166 annotatedTxs = append(annotatedTxs, w.buildAnnotatedTransaction(tx, b, statusFail, pos)) 167 continue transactionLoop 168 } 169 } 170 171 for _, v := range tx.Inputs { 172 outid, err := v.SpentOutputID() 173 if err != nil { 174 continue 175 } 176 if bytes := w.DB.Get(account.StandardUTXOKey(outid)); bytes != nil { 177 annotatedTxs = append(annotatedTxs, w.buildAnnotatedTransaction(tx, b, statusFail, pos)) 178 continue transactionLoop 179 } 180 } 181 } 182 183 return annotatedTxs 184 } 185 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 { 191 return nil, err 192 } 193 194 return w.getGlobalTxByTxID(txID) 195 } 196 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 202 } 203 204 txInfo := w.DB.Get(calcAnnotatedKey(string(formatKey))) 205 if err := json.Unmarshal(txInfo, annotatedTx); err != nil { 206 return nil, err 207 } 208 209 annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx}) 210 return annotatedTx, nil 211 } 212 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) 217 } 218 219 blockHash, pos := parseGlobalTxIdx(globalTxIdx) 220 block, err := w.chain.GetBlockByHash(blockHash) 221 if err != nil { 222 return nil, err 223 } 224 225 txStatus, err := w.chain.GetTransactionStatus(blockHash) 226 if err != nil { 227 return nil, err 228 } 229 230 statusFail, err := txStatus.GetStatus(int(pos)) 231 if err != nil { 232 return nil, err 233 } 234 235 tx := block.Transactions[int(pos)] 236 return w.buildAnnotatedTransaction(tx, block, statusFail, int(pos)), nil 237 } 238 239 // GetTransactionsSummary get transactions summary 240 func (w *Wallet) GetTransactionsSummary(transactions []*query.AnnotatedTx) []TxSummary { 241 Txs := []TxSummary{} 242 243 for _, annotatedTx := range transactions { 244 tmpTxSummary := TxSummary{ 245 Inputs: make([]Summary, len(annotatedTx.Inputs)), 246 Outputs: make([]Summary, len(annotatedTx.Outputs)), 247 ID: annotatedTx.ID, 248 Timestamp: annotatedTx.Timestamp, 249 } 250 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 259 } 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 267 } 268 269 Txs = append(Txs, tmpTxSummary) 270 } 271 272 return Txs 273 } 274 275 func findTransactionsByAccount(annotatedTx *query.AnnotatedTx, accountID string) bool { 276 for _, input := range annotatedTx.Inputs { 277 if input.AccountID == accountID { 278 return true 279 } 280 } 281 282 for _, output := range annotatedTx.Outputs { 283 if output.AccountID == accountID { 284 return true 285 } 286 } 287 288 return false 289 } 290 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{} 294 295 txIter := w.DB.IteratorPrefix([]byte(TxPrefix)) 296 defer txIter.Release() 297 for txIter.Next() { 298 annotatedTx := &query.AnnotatedTx{} 299 if err := json.Unmarshal(txIter.Value(), &annotatedTx); err != nil { 300 return nil, err 301 } 302 303 if accountID == "" || findTransactionsByAccount(annotatedTx, accountID) { 304 annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx}) 305 annotatedTxs = append([]*query.AnnotatedTx{annotatedTx}, annotatedTxs...) 306 } 307 } 308 309 return annotatedTxs, nil 310 } 311 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)) 315 } 316 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"` 325 } 326 327 func (w *Wallet) indexBalances(accountUTXOs []*account.UTXO) ([]AccountBalance, error) { 328 accBalance := make(map[string]map[string]uint64) 329 balances := []AccountBalance{} 330 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 336 } else { 337 accBalance[accountUTXO.AccountID][assetID] = accountUTXO.Amount 338 } 339 } else { 340 accBalance[accountUTXO.AccountID] = map[string]uint64{assetID: accountUTXO.Amount} 341 } 342 } 343 344 var sortedAccount []string 345 for k := range accBalance { 346 sortedAccount = append(sortedAccount, k) 347 } 348 sort.Strings(sortedAccount) 349 350 for _, id := range sortedAccount { 351 var sortedAsset []string 352 for k := range accBalance[id] { 353 sortedAsset = append(sortedAsset, k) 354 } 355 sort.Strings(sortedAsset) 356 357 for _, assetID := range sortedAsset { 358 alias := w.AccountMgr.GetAliasByID(id) 359 targetAsset, err := w.AssetReg.GetAsset(assetID) 360 if err != nil { 361 return nil, err 362 } 363 364 assetAlias := *targetAsset.Alias 365 balances = append(balances, AccountBalance{ 366 Alias: alias, 367 AccountID: id, 368 AssetID: assetID, 369 AssetAlias: assetAlias, 370 Amount: accBalance[id][assetID], 371 AssetDefinition: targetAsset.DefinitionMap, 372 }) 373 } 374 } 375 376 return balances, nil 377 }