10 log "github.com/sirupsen/logrus"
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"
25 //TxPrefix is wallet database transactions prefix
27 //TxIndexPrefix is wallet database tx index prefix
28 TxIndexPrefix = "TID:"
29 //TxIndexPrefix is wallet database global tx index prefix
30 GlobalTxIndexPrefix = "GTID:"
33 var errAccntTxIDNotFound = errors.New("account TXID not found")
35 func formatKey(blockHeight uint64, position uint32) string {
36 return fmt.Sprintf("%016x%08x", blockHeight, position)
39 func calcAnnotatedKey(formatKey string) []byte {
40 return []byte(TxPrefix + formatKey)
43 func calcDeleteKey(blockHeight uint64) []byte {
44 return []byte(fmt.Sprintf("%s%016x", TxPrefix, blockHeight))
47 func calcTxIndexKey(txID string) []byte {
48 return []byte(TxIndexPrefix + txID)
51 func calcGlobalTxIndexKey(txID string) []byte {
52 return []byte(GlobalTxIndexPrefix + txID)
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)
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
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()
77 if err := json.Unmarshal(txIter.Value(), &tmpTx); err == nil {
78 batch.Delete(calcTxIndexKey(tmpTx.ID.String()))
80 batch.Delete(txIter.Key())
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()
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)
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"`
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"`
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)
130 for _, tx := range annotatedTxs {
131 rawTx, err := json.Marshal(tx)
133 log.WithFields(log.Fields{"module": logModule, "err": err}).Error("inserting annotated_txs to db")
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))))
140 // delete unconfirmed transaction
141 batch.Delete(calcUnconfirmedTxKey(tx.ID.String()))
148 for position, globalTx := range b.Transactions {
149 blockHash := b.BlockHeader.Hash()
150 batch.Set(calcGlobalTxIndexKey(globalTx.ID.String()), calcGlobalTxIndex(&blockHash, uint64(position)))
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))
161 for pos, tx := range b.Transactions {
162 for _, v := range tx.Outputs {
164 sha3pool.Sum256(hash[:], v.ControlProgram)
166 if bytes := w.DB.Get(account.ContractKey(hash)); bytes != nil {
167 annotatedTxs = append(annotatedTxs, w.buildAnnotatedTransaction(tx, b, pos))
168 continue transactionLoop
172 for _, v := range tx.Inputs {
173 outid, err := v.SpentOutputID()
177 if bytes := w.DB.Get(account.StandardUTXOKey(outid)); bytes != nil {
178 annotatedTxs = append(annotatedTxs, w.buildAnnotatedTransaction(tx, b, pos))
179 continue transactionLoop
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 {
195 return w.getGlobalTxByTxID(txID)
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
205 txInfo := w.DB.Get(calcAnnotatedKey(string(formatKey)))
206 if err := json.Unmarshal(txInfo, annotatedTx); err != nil {
210 annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
211 return annotatedTx, nil
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)
220 blockHash, pos := parseGlobalTxIdx(globalTxIdx)
221 block, err := w.chain.GetBlockByHash(blockHash)
226 tx := block.Transactions[int(pos)]
227 return w.buildAnnotatedTransaction(tx, block, int(pos)), nil
230 // GetTransactionsSummary get transactions summary
231 func (w *Wallet) GetTransactionsSummary(transactions []*query.AnnotatedTx) []TxSummary {
234 for _, annotatedTx := range transactions {
235 tmpTxSummary := TxSummary{
236 Inputs: make([]Summary, len(annotatedTx.Inputs)),
237 Outputs: make([]Summary, len(annotatedTx.Outputs)),
239 Timestamp: annotatedTx.Timestamp,
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
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
260 Txs = append(Txs, tmpTxSummary)
266 func findTransactionsByAccount(annotatedTx *query.AnnotatedTx, accountID string) bool {
267 for _, input := range annotatedTx.Inputs {
268 if input.AccountID == accountID {
273 for _, output := range annotatedTx.Outputs {
274 if output.AccountID == accountID {
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{}
286 txIter := w.DB.IteratorPrefix([]byte(TxPrefix))
287 defer txIter.Release()
289 annotatedTx := &query.AnnotatedTx{}
290 if err := json.Unmarshal(txIter.Value(), &annotatedTx); err != nil {
294 if accountID == "" || findTransactionsByAccount(annotatedTx, accountID) {
295 annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
296 annotatedTxs = append([]*query.AnnotatedTx{annotatedTx}, annotatedTxs...)
300 return annotatedTxs, nil
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))
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"`
318 func (w *Wallet) indexBalances(accountUTXOs []*account.UTXO) ([]AccountBalance, error) {
319 accBalance := make(map[string]map[string]uint64)
320 balances := []AccountBalance{}
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
328 accBalance[accountUTXO.AccountID][assetID] = accountUTXO.Amount
331 accBalance[accountUTXO.AccountID] = map[string]uint64{assetID: accountUTXO.Amount}
335 var sortedAccount []string
336 for k := range accBalance {
337 sortedAccount = append(sortedAccount, k)
339 sort.Strings(sortedAccount)
341 for _, id := range sortedAccount {
342 var sortedAsset []string
343 for k := range accBalance[id] {
344 sortedAsset = append(sortedAsset, k)
346 sort.Strings(sortedAsset)
348 for _, assetID := range sortedAsset {
349 alias := w.AccountMgr.GetAliasByID(id)
350 targetAsset, err := w.AssetReg.GetAsset(assetID)
355 assetAlias := *targetAsset.Alias
356 balances = append(balances, AccountBalance{
360 AssetAlias: assetAlias,
361 Amount: accBalance[id][assetID],
362 AssetDefinition: targetAsset.DefinitionMap,
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))
375 type voteDetail struct {
376 Vote string `json:"vote"`
377 VoteNumber uint64 `json:"vote_number"`
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"`
388 func (w *Wallet) indexVotes(accountUTXOs []*account.UTXO) ([]AccountVotes, error) {
389 accVote := make(map[string]map[string]uint64)
390 votes := []AccountVotes{}
392 for _, accountUTXO := range accountUTXOs {
393 if accountUTXO.AssetID != *consensus.BTMAssetID || accountUTXO.Vote == nil {
396 xpub := hex.EncodeToString(accountUTXO.Vote)
397 if _, ok := accVote[accountUTXO.AccountID]; ok {
398 accVote[accountUTXO.AccountID][xpub] += accountUTXO.Amount
400 accVote[accountUTXO.AccountID] = map[string]uint64{xpub: accountUTXO.Amount}
405 var sortedAccount []string
406 for k := range accVote {
407 sortedAccount = append(sortedAccount, k)
409 sort.Strings(sortedAccount)
411 for _, id := range sortedAccount {
412 var sortedXpub []string
413 for k := range accVote[id] {
414 sortedXpub = append(sortedXpub, k)
416 sort.Strings(sortedXpub)
418 voteDetails := []voteDetail{}
419 voteTotal := uint64(0)
420 for _, xpub := range sortedXpub {
421 voteDetails = append(voteDetails, voteDetail{
423 VoteNumber: accVote[id][xpub],
425 voteTotal += accVote[id][xpub]
427 alias := w.AccountMgr.GetAliasByID(id)
428 votes = append(votes, AccountVotes{
431 VoteDetails: voteDetails,
432 TotalVoteNumber: voteTotal,