github.com/bytom/bytom@v1.1.2-0.20221014091027-bbcba3df6075/wallet/indexer.go (about) 1 package wallet 2 3 import ( 4 "encoding/binary" 5 "encoding/hex" 6 "encoding/json" 7 "fmt" 8 "sort" 9 10 log "github.com/sirupsen/logrus" 11 12 "github.com/bytom/bytom/account" 13 "github.com/bytom/bytom/asset" 14 "github.com/bytom/bytom/blockchain/query" 15 "github.com/bytom/bytom/consensus" 16 "github.com/bytom/bytom/crypto/sha3pool" 17 dbm "github.com/bytom/bytom/database/leveldb" 18 chainjson "github.com/bytom/bytom/encoding/json" 19 "github.com/bytom/bytom/errors" 20 "github.com/bytom/bytom/protocol/bc" 21 "github.com/bytom/bytom/protocol/bc/types" 22 ) 23 24 const ( 25 //TxPrefix is wallet database transactions prefix 26 TxPrefix = "TXS:" 27 //TxIndexPrefix is wallet database tx index prefix 28 TxIndexPrefix = "TID:" 29 //TxIndexPrefix is wallet database global tx index prefix 30 GlobalTxIndexPrefix = "GTID:" 31 ) 32 33 var errAccntTxIDNotFound = errors.New("account TXID not found") 34 35 func formatKey(blockHeight uint64, position uint32) string { 36 return fmt.Sprintf("%016x%08x", blockHeight, position) 37 } 38 39 func calcAnnotatedKey(formatKey string) []byte { 40 return []byte(TxPrefix + formatKey) 41 } 42 43 func calcDeleteKey(blockHeight uint64) []byte { 44 return []byte(fmt.Sprintf("%s%016x", TxPrefix, blockHeight)) 45 } 46 47 func calcTxIndexKey(txID string) []byte { 48 return []byte(TxIndexPrefix + txID) 49 } 50 51 func calcGlobalTxIndexKey(txID string) []byte { 52 return []byte(GlobalTxIndexPrefix + txID) 53 } 54 55 func calcGlobalTxIndex(blockHash *bc.Hash, position uint64) []byte { 56 txIdx := make([]byte, 40) 57 copy(txIdx[:32], blockHash.Bytes()) 58 binary.BigEndian.PutUint64(txIdx[32:], position) 59 return txIdx 60 } 61 62 func parseGlobalTxIdx(globalTxIdx []byte) (*bc.Hash, uint64) { 63 var hashBytes [32]byte 64 copy(hashBytes[:], globalTxIdx[:32]) 65 hash := bc.NewHash(hashBytes) 66 position := binary.BigEndian.Uint64(globalTxIdx[32:]) 67 return &hash, position 68 } 69 70 // deleteTransaction delete transactions when orphan block rollback 71 func (w *Wallet) deleteTransactions(batch dbm.Batch, height uint64) { 72 tmpTx := query.AnnotatedTx{} 73 txIter := w.DB.IteratorPrefix(calcDeleteKey(height)) 74 defer txIter.Release() 75 76 for txIter.Next() { 77 if err := json.Unmarshal(txIter.Value(), &tmpTx); err == nil { 78 batch.Delete(calcTxIndexKey(tmpTx.ID.String())) 79 } 80 batch.Delete(txIter.Key()) 81 } 82 } 83 84 // saveExternalAssetDefinition save external and local assets definition, 85 // when query ,query local first and if have no then query external 86 // details see getAliasDefinition 87 func saveExternalAssetDefinition(b *types.Block, walletDB dbm.DB) { 88 storeBatch := walletDB.NewBatch() 89 defer storeBatch.Write() 90 91 for _, tx := range b.Transactions { 92 for _, orig := range tx.Inputs { 93 if ii, ok := orig.TypedInput.(*types.IssuanceInput); ok { 94 if isValidJSON(ii.AssetDefinition) { 95 assetID := ii.AssetID() 96 if assetExist := walletDB.Get(asset.ExtAssetKey(&assetID)); assetExist == nil { 97 storeBatch.Set(asset.ExtAssetKey(&assetID), ii.AssetDefinition) 98 } 99 } 100 } 101 } 102 } 103 } 104 105 // Summary is the struct of transaction's input and output summary 106 type Summary struct { 107 Type string `json:"type"` 108 AssetID bc.AssetID `json:"asset_id,omitempty"` 109 AssetAlias string `json:"asset_alias,omitempty"` 110 Amount uint64 `json:"amount,omitempty"` 111 AccountID string `json:"account_id,omitempty"` 112 AccountAlias string `json:"account_alias,omitempty"` 113 Arbitrary chainjson.HexBytes `json:"arbitrary,omitempty"` 114 } 115 116 // TxSummary is the struct of transaction summary 117 type TxSummary struct { 118 ID bc.Hash `json:"tx_id"` 119 Timestamp uint64 `json:"block_time"` 120 Inputs []Summary `json:"inputs"` 121 Outputs []Summary `json:"outputs"` 122 } 123 124 // indexTransactions saves all annotated transactions to the database. 125 func (w *Wallet) indexTransactions(batch dbm.Batch, b *types.Block) error { 126 annotatedTxs := w.filterAccountTxs(b) 127 saveExternalAssetDefinition(b, w.DB) 128 annotateTxsAccount(annotatedTxs, w.DB) 129 130 for _, tx := range annotatedTxs { 131 rawTx, err := json.Marshal(tx) 132 if err != nil { 133 log.WithFields(log.Fields{"module": logModule, "err": err}).Error("inserting annotated_txs to db") 134 return err 135 } 136 137 batch.Set(calcAnnotatedKey(formatKey(b.Height, uint32(tx.Position))), rawTx) 138 batch.Set(calcTxIndexKey(tx.ID.String()), []byte(formatKey(b.Height, uint32(tx.Position)))) 139 140 // delete unconfirmed transaction 141 batch.Delete(calcUnconfirmedTxKey(tx.ID.String())) 142 } 143 144 if !w.TxIndexFlag { 145 return nil 146 } 147 148 for position, globalTx := range b.Transactions { 149 blockHash := b.BlockHeader.Hash() 150 batch.Set(calcGlobalTxIndexKey(globalTx.ID.String()), calcGlobalTxIndex(&blockHash, uint64(position))) 151 } 152 153 return nil 154 } 155 156 // filterAccountTxs related and build the fully annotated transactions. 157 func (w *Wallet) filterAccountTxs(b *types.Block) []*query.AnnotatedTx { 158 annotatedTxs := make([]*query.AnnotatedTx, 0, len(b.Transactions)) 159 160 transactionLoop: 161 for pos, tx := range b.Transactions { 162 for _, v := range tx.Outputs { 163 var hash [32]byte 164 sha3pool.Sum256(hash[:], v.ControlProgram) 165 166 if bytes := w.DB.Get(account.ContractKey(hash)); bytes != nil { 167 annotatedTxs = append(annotatedTxs, w.buildAnnotatedTransaction(tx, b, pos)) 168 continue transactionLoop 169 } 170 } 171 172 for _, v := range tx.Inputs { 173 outid, err := v.SpentOutputID() 174 if err != nil { 175 continue 176 } 177 if bytes := w.DB.Get(account.StandardUTXOKey(outid)); bytes != nil { 178 annotatedTxs = append(annotatedTxs, w.buildAnnotatedTransaction(tx, b, pos)) 179 continue transactionLoop 180 } 181 } 182 } 183 184 return annotatedTxs 185 } 186 187 // GetTransactionByTxID get transaction by txID 188 func (w *Wallet) GetTransactionByTxID(txID string) (*query.AnnotatedTx, error) { 189 if annotatedTx, err := w.getAccountTxByTxID(txID); err == nil { 190 return annotatedTx, nil 191 } else if !w.TxIndexFlag { 192 return nil, err 193 } 194 195 return w.getGlobalTxByTxID(txID) 196 } 197 198 func (w *Wallet) getAccountTxByTxID(txID string) (*query.AnnotatedTx, error) { 199 annotatedTx := &query.AnnotatedTx{} 200 formatKey := w.DB.Get(calcTxIndexKey(txID)) 201 if formatKey == nil { 202 return nil, errAccntTxIDNotFound 203 } 204 205 txInfo := w.DB.Get(calcAnnotatedKey(string(formatKey))) 206 if err := json.Unmarshal(txInfo, annotatedTx); err != nil { 207 return nil, err 208 } 209 210 annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx}) 211 return annotatedTx, nil 212 } 213 214 func (w *Wallet) getGlobalTxByTxID(txID string) (*query.AnnotatedTx, error) { 215 globalTxIdx := w.DB.Get(calcGlobalTxIndexKey(txID)) 216 if globalTxIdx == nil { 217 return nil, fmt.Errorf("No transaction(tx_id=%s) ", txID) 218 } 219 220 blockHash, pos := parseGlobalTxIdx(globalTxIdx) 221 block, err := w.chain.GetBlockByHash(blockHash) 222 if err != nil { 223 return nil, err 224 } 225 226 tx := block.Transactions[int(pos)] 227 return w.buildAnnotatedTransaction(tx, block, int(pos)), nil 228 } 229 230 // GetTransactionsSummary get transactions summary 231 func (w *Wallet) GetTransactionsSummary(transactions []*query.AnnotatedTx) []TxSummary { 232 Txs := []TxSummary{} 233 234 for _, annotatedTx := range transactions { 235 tmpTxSummary := TxSummary{ 236 Inputs: make([]Summary, len(annotatedTx.Inputs)), 237 Outputs: make([]Summary, len(annotatedTx.Outputs)), 238 ID: annotatedTx.ID, 239 Timestamp: annotatedTx.Timestamp, 240 } 241 242 for i, input := range annotatedTx.Inputs { 243 tmpTxSummary.Inputs[i].Type = input.Type 244 tmpTxSummary.Inputs[i].AccountID = input.AccountID 245 tmpTxSummary.Inputs[i].AccountAlias = input.AccountAlias 246 tmpTxSummary.Inputs[i].AssetID = input.AssetID 247 tmpTxSummary.Inputs[i].AssetAlias = input.AssetAlias 248 tmpTxSummary.Inputs[i].Amount = input.Amount 249 tmpTxSummary.Inputs[i].Arbitrary = input.Arbitrary 250 } 251 for j, output := range annotatedTx.Outputs { 252 tmpTxSummary.Outputs[j].Type = output.Type 253 tmpTxSummary.Outputs[j].AccountID = output.AccountID 254 tmpTxSummary.Outputs[j].AccountAlias = output.AccountAlias 255 tmpTxSummary.Outputs[j].AssetID = output.AssetID 256 tmpTxSummary.Outputs[j].AssetAlias = output.AssetAlias 257 tmpTxSummary.Outputs[j].Amount = output.Amount 258 } 259 260 Txs = append(Txs, tmpTxSummary) 261 } 262 263 return Txs 264 } 265 266 func findTransactionsByAccount(annotatedTx *query.AnnotatedTx, accountID string) bool { 267 for _, input := range annotatedTx.Inputs { 268 if input.AccountID == accountID { 269 return true 270 } 271 } 272 273 for _, output := range annotatedTx.Outputs { 274 if output.AccountID == accountID { 275 return true 276 } 277 } 278 279 return false 280 } 281 282 // GetTransactions get all walletDB transactions, and filter transactions by accountID optional 283 func (w *Wallet) GetTransactions(accountID string) ([]*query.AnnotatedTx, error) { 284 annotatedTxs := []*query.AnnotatedTx{} 285 286 txIter := w.DB.IteratorPrefix([]byte(TxPrefix)) 287 defer txIter.Release() 288 for txIter.Next() { 289 annotatedTx := &query.AnnotatedTx{} 290 if err := json.Unmarshal(txIter.Value(), &annotatedTx); err != nil { 291 return nil, err 292 } 293 294 if accountID == "" || findTransactionsByAccount(annotatedTx, accountID) { 295 annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx}) 296 annotatedTxs = append([]*query.AnnotatedTx{annotatedTx}, annotatedTxs...) 297 } 298 } 299 300 return annotatedTxs, nil 301 } 302 303 // GetAccountBalances return all account balances 304 func (w *Wallet) GetAccountBalances(accountID string, id string) ([]AccountBalance, error) { 305 return w.indexBalances(w.GetAccountUtxos(accountID, "", false, false, false)) 306 } 307 308 // AccountBalance account balance 309 type AccountBalance struct { 310 AccountID string `json:"account_id"` 311 Alias string `json:"account_alias"` 312 AssetAlias string `json:"asset_alias"` 313 AssetID string `json:"asset_id"` 314 Amount uint64 `json:"amount"` 315 AssetDefinition map[string]interface{} `json:"asset_definition"` 316 } 317 318 func (w *Wallet) indexBalances(accountUTXOs []*account.UTXO) ([]AccountBalance, error) { 319 accBalance := make(map[string]map[string]uint64) 320 balances := []AccountBalance{} 321 322 for _, accountUTXO := range accountUTXOs { 323 assetID := accountUTXO.AssetID.String() 324 if _, ok := accBalance[accountUTXO.AccountID]; ok { 325 if _, ok := accBalance[accountUTXO.AccountID][assetID]; ok { 326 accBalance[accountUTXO.AccountID][assetID] += accountUTXO.Amount 327 } else { 328 accBalance[accountUTXO.AccountID][assetID] = accountUTXO.Amount 329 } 330 } else { 331 accBalance[accountUTXO.AccountID] = map[string]uint64{assetID: accountUTXO.Amount} 332 } 333 } 334 335 var sortedAccount []string 336 for k := range accBalance { 337 sortedAccount = append(sortedAccount, k) 338 } 339 sort.Strings(sortedAccount) 340 341 for _, id := range sortedAccount { 342 var sortedAsset []string 343 for k := range accBalance[id] { 344 sortedAsset = append(sortedAsset, k) 345 } 346 sort.Strings(sortedAsset) 347 348 for _, assetID := range sortedAsset { 349 alias := w.AccountMgr.GetAliasByID(id) 350 targetAsset, err := w.AssetReg.GetAsset(assetID) 351 if err != nil { 352 return nil, err 353 } 354 355 assetAlias := *targetAsset.Alias 356 balances = append(balances, AccountBalance{ 357 Alias: alias, 358 AccountID: id, 359 AssetID: assetID, 360 AssetAlias: assetAlias, 361 Amount: accBalance[id][assetID], 362 AssetDefinition: targetAsset.DefinitionMap, 363 }) 364 } 365 } 366 367 return balances, nil 368 } 369 370 // GetAccountVotes return all account votes 371 func (w *Wallet) GetAccountVotes(accountID string, id string) ([]AccountVotes, error) { 372 return w.indexVotes(w.GetAccountUtxos(accountID, "", false, false, true)) 373 } 374 375 type voteDetail struct { 376 Vote string `json:"vote"` 377 VoteNumber uint64 `json:"vote_number"` 378 } 379 380 // AccountVotes account vote 381 type AccountVotes struct { 382 AccountID string `json:"account_id"` 383 Alias string `json:"account_alias"` 384 TotalVoteNumber uint64 `json:"total_vote_number"` 385 VoteDetails []voteDetail `json:"vote_details"` 386 } 387 388 func (w *Wallet) indexVotes(accountUTXOs []*account.UTXO) ([]AccountVotes, error) { 389 accVote := make(map[string]map[string]uint64) 390 votes := []AccountVotes{} 391 392 for _, accountUTXO := range accountUTXOs { 393 if accountUTXO.AssetID != *consensus.BTMAssetID || accountUTXO.Vote == nil { 394 continue 395 } 396 xpub := hex.EncodeToString(accountUTXO.Vote) 397 if _, ok := accVote[accountUTXO.AccountID]; ok { 398 accVote[accountUTXO.AccountID][xpub] += accountUTXO.Amount 399 } else { 400 accVote[accountUTXO.AccountID] = map[string]uint64{xpub: accountUTXO.Amount} 401 402 } 403 } 404 405 var sortedAccount []string 406 for k := range accVote { 407 sortedAccount = append(sortedAccount, k) 408 } 409 sort.Strings(sortedAccount) 410 411 for _, id := range sortedAccount { 412 var sortedXpub []string 413 for k := range accVote[id] { 414 sortedXpub = append(sortedXpub, k) 415 } 416 sort.Strings(sortedXpub) 417 418 voteDetails := []voteDetail{} 419 voteTotal := uint64(0) 420 for _, xpub := range sortedXpub { 421 voteDetails = append(voteDetails, voteDetail{ 422 Vote: xpub, 423 VoteNumber: accVote[id][xpub], 424 }) 425 voteTotal += accVote[id][xpub] 426 } 427 alias := w.AccountMgr.GetAliasByID(id) 428 votes = append(votes, AccountVotes{ 429 Alias: alias, 430 AccountID: id, 431 VoteDetails: voteDetails, 432 TotalVoteNumber: voteTotal, 433 }) 434 } 435 436 return votes, nil 437 } 438