10 log "github.com/sirupsen/logrus"
12 "github.com/vapor/account"
13 "github.com/vapor/blockchain/query"
14 "github.com/vapor/consensus"
15 "github.com/vapor/crypto/sha3pool"
16 chainjson "github.com/vapor/encoding/json"
17 "github.com/vapor/errors"
18 "github.com/vapor/protocol/bc"
19 "github.com/vapor/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 // saveExternalAssetDefinition save external and local assets definition,
69 // when query ,query local first and if have no then query external
70 // details see getAliasDefinition
71 func saveExternalAssetDefinition(b *types.Block, store Store) {
72 for _, tx := range b.Transactions {
73 for _, orig := range tx.Inputs {
74 if cci, ok := orig.TypedInput.(*types.CrossChainInput); ok {
75 assetID := cci.AssetId
76 if assetExist := store.GetAssetDefinition(assetID); assetExist == nil {
77 store.SetAssetDefinition(assetID, cci.AssetDefinition)
84 // Summary is the struct of transaction's input and output summary
86 Type string `json:"type"`
87 AssetID bc.AssetID `json:"asset_id,omitempty"`
88 AssetAlias string `json:"asset_alias,omitempty"`
89 Amount uint64 `json:"amount,omitempty"`
90 AccountID string `json:"account_id,omitempty"`
91 AccountAlias string `json:"account_alias,omitempty"`
92 Arbitrary chainjson.HexBytes `json:"arbitrary,omitempty"`
95 // TxSummary is the struct of transaction summary
96 type TxSummary struct {
97 ID bc.Hash `json:"tx_id"`
98 Timestamp uint64 `json:"block_time"`
99 Inputs []Summary `json:"inputs"`
100 Outputs []Summary `json:"outputs"`
103 // indexTransactions saves all annotated transactions to the database.
104 func (w *Wallet) indexTransactions(b *types.Block, txStatus *bc.TransactionStatus) error {
105 annotatedTxs := w.filterAccountTxs(b, txStatus)
106 saveExternalAssetDefinition(b, w.store)
107 annotateTxsAccount(annotatedTxs, w.store)
109 for _, tx := range annotatedTxs {
110 rawTx, err := json.Marshal(tx)
112 log.WithFields(log.Fields{"module": logModule, "err": err}).Error("inserting annotated_txs to db")
116 w.store.SetRawTransaction(b.Height, tx.Position, rawTx)
117 w.store.SetHeightAndPostion(tx.ID.String(), b.Height, tx.Position)
118 w.store.DeleteUnconfirmedTransaction(tx.ID.String())
125 for position, globalTx := range b.Transactions {
126 blockHash := b.BlockHeader.Hash()
127 w.store.SetGlobalTransactionIndex(globalTx.ID.String(), &blockHash, uint64(position))
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))
138 for pos, tx := range b.Transactions {
139 statusFail, _ := txStatus.GetStatus(pos)
140 for _, v := range tx.Outputs {
142 sha3pool.Sum256(hash[:], v.ControlProgram())
144 if bytes := w.store.GetRawProgramByHash(hash); bytes != nil {
145 annotatedTxs = append(annotatedTxs, w.buildAnnotatedTransaction(tx, b, statusFail, pos))
146 continue transactionLoop
150 for _, v := range tx.Inputs {
151 outid, err := v.SpentOutputID()
155 if bytes := w.store.GetStandardUTXO(outid); bytes != nil {
156 annotatedTxs = append(annotatedTxs, w.buildAnnotatedTransaction(tx, b, statusFail, pos))
157 continue transactionLoop
165 // GetTransactionByTxID get transaction by txID
166 func (w *Wallet) GetTransactionByTxID(txID string) (*query.AnnotatedTx, error) {
167 if annotatedTx, err := w.getAccountTxByTxID(txID); err == nil {
168 return annotatedTx, nil
169 } else if !w.TxIndexFlag {
173 return w.getGlobalTxByTxID(txID)
176 func (w *Wallet) getAccountTxByTxID(txID string) (*query.AnnotatedTx, error) {
177 annotatedTx := &query.AnnotatedTx{}
178 formatKey := w.store.GetTransactionIndex(txID)
179 if formatKey == nil {
180 return nil, errAccntTxIDNotFound
183 txInfo := w.store.GetTransaction(formatKey)
184 if err := json.Unmarshal(txInfo, annotatedTx); err != nil {
188 annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
189 return annotatedTx, nil
192 func (w *Wallet) getGlobalTxByTxID(txID string) (*query.AnnotatedTx, error) {
193 globalTxIdx := w.store.GetGlobalTransaction(txID)
194 if globalTxIdx == nil {
195 return nil, fmt.Errorf("No transaction(tx_id=%s) ", txID)
198 blockHash, pos := parseGlobalTxIdx(globalTxIdx)
199 block, err := w.chain.GetBlockByHash(blockHash)
204 txStatus, err := w.chain.GetTransactionStatus(blockHash)
209 statusFail, err := txStatus.GetStatus(int(pos))
214 tx := block.Transactions[int(pos)]
215 return w.buildAnnotatedTransaction(tx, block, statusFail, int(pos)), nil
218 // GetTransactionsSummary get transactions summary
219 func (w *Wallet) GetTransactionsSummary(transactions []*query.AnnotatedTx) []TxSummary {
222 for _, annotatedTx := range transactions {
223 tmpTxSummary := TxSummary{
224 Inputs: make([]Summary, len(annotatedTx.Inputs)),
225 Outputs: make([]Summary, len(annotatedTx.Outputs)),
227 Timestamp: annotatedTx.Timestamp,
230 for i, input := range annotatedTx.Inputs {
231 tmpTxSummary.Inputs[i].Type = input.Type
232 tmpTxSummary.Inputs[i].AccountID = input.AccountID
233 tmpTxSummary.Inputs[i].AccountAlias = input.AccountAlias
234 tmpTxSummary.Inputs[i].AssetID = input.AssetID
235 tmpTxSummary.Inputs[i].AssetAlias = input.AssetAlias
236 tmpTxSummary.Inputs[i].Amount = input.Amount
237 tmpTxSummary.Inputs[i].Arbitrary = input.Arbitrary
239 for j, output := range annotatedTx.Outputs {
240 tmpTxSummary.Outputs[j].Type = output.Type
241 tmpTxSummary.Outputs[j].AccountID = output.AccountID
242 tmpTxSummary.Outputs[j].AccountAlias = output.AccountAlias
243 tmpTxSummary.Outputs[j].AssetID = output.AssetID
244 tmpTxSummary.Outputs[j].AssetAlias = output.AssetAlias
245 tmpTxSummary.Outputs[j].Amount = output.Amount
248 Txs = append(Txs, tmpTxSummary)
254 func findTransactionsByAccount(annotatedTx *query.AnnotatedTx, accountID string) bool {
255 for _, input := range annotatedTx.Inputs {
256 if input.AccountID == accountID {
261 for _, output := range annotatedTx.Outputs {
262 if output.AccountID == accountID {
270 // GetTransactions get all walletDB transactions, and filter transactions by accountID optional
271 func (w *Wallet) GetTransactions(accountID string) ([]*query.AnnotatedTx, error) {
272 annotatedTxs := []*query.AnnotatedTx{}
273 annotatedTxs, err := w.store.GetTransactions()
278 newAnnotatedTxs := []*query.AnnotatedTx{}
279 for _, annotatedTx := range annotatedTxs {
280 if accountID == "" || findTransactionsByAccount(annotatedTx, accountID) {
281 annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
282 newAnnotatedTxs = append([]*query.AnnotatedTx{annotatedTx}, newAnnotatedTxs...)
286 return newAnnotatedTxs, nil
289 // GetAccountBalances return all account balances
290 func (w *Wallet) GetAccountBalances(accountID string, id string) ([]AccountBalance, error) {
291 return w.indexBalances(w.GetAccountUtxos(accountID, "", false, false, false))
294 // AccountBalance account balance
295 type AccountBalance struct {
296 AccountID string `json:"account_id"`
297 Alias string `json:"account_alias"`
298 AssetAlias string `json:"asset_alias"`
299 AssetID string `json:"asset_id"`
300 Amount uint64 `json:"amount"`
301 AssetDefinition map[string]interface{} `json:"asset_definition"`
304 func (w *Wallet) indexBalances(accountUTXOs []*account.UTXO) ([]AccountBalance, error) {
305 accBalance := make(map[string]map[string]uint64)
306 balances := []AccountBalance{}
308 for _, accountUTXO := range accountUTXOs {
309 assetID := accountUTXO.AssetID.String()
310 if _, ok := accBalance[accountUTXO.AccountID]; ok {
311 if _, ok := accBalance[accountUTXO.AccountID][assetID]; ok {
312 accBalance[accountUTXO.AccountID][assetID] += accountUTXO.Amount
314 accBalance[accountUTXO.AccountID][assetID] = accountUTXO.Amount
317 accBalance[accountUTXO.AccountID] = map[string]uint64{assetID: accountUTXO.Amount}
321 var sortedAccount []string
322 for k := range accBalance {
323 sortedAccount = append(sortedAccount, k)
325 sort.Strings(sortedAccount)
327 for _, id := range sortedAccount {
328 var sortedAsset []string
329 for k := range accBalance[id] {
330 sortedAsset = append(sortedAsset, k)
332 sort.Strings(sortedAsset)
334 for _, assetID := range sortedAsset {
335 alias := w.AccountMgr.GetAliasByID(id)
336 targetAsset, err := w.AssetReg.GetAsset(assetID)
341 assetAlias := *targetAsset.Alias
342 balances = append(balances, AccountBalance{
346 AssetAlias: assetAlias,
347 Amount: accBalance[id][assetID],
348 AssetDefinition: targetAsset.DefinitionMap,
356 // GetAccountVotes return all account votes
357 func (w *Wallet) GetAccountVotes(accountID string, id string) ([]AccountVotes, error) {
358 return w.indexVotes(w.GetAccountUtxos(accountID, "", false, false, true))
361 type voteDetail struct {
362 Vote string `json:"vote"`
363 VoteNumber uint64 `json:"vote_number"`
366 // AccountVotes account vote
367 type AccountVotes struct {
368 AccountID string `json:"account_id"`
369 Alias string `json:"account_alias"`
370 TotalVoteNumber uint64 `json:"total_vote_number"`
371 VoteDetails []voteDetail `json:"vote_details"`
374 func (w *Wallet) indexVotes(accountUTXOs []*account.UTXO) ([]AccountVotes, error) {
375 accVote := make(map[string]map[string]uint64)
376 votes := []AccountVotes{}
378 for _, accountUTXO := range accountUTXOs {
379 if accountUTXO.AssetID != *consensus.BTMAssetID || accountUTXO.Vote == nil {
382 xpub := hex.EncodeToString(accountUTXO.Vote)
383 if _, ok := accVote[accountUTXO.AccountID]; ok {
384 accVote[accountUTXO.AccountID][xpub] += accountUTXO.Amount
386 accVote[accountUTXO.AccountID] = map[string]uint64{xpub: accountUTXO.Amount}
391 var sortedAccount []string
392 for k := range accVote {
393 sortedAccount = append(sortedAccount, k)
395 sort.Strings(sortedAccount)
397 for _, id := range sortedAccount {
398 var sortedXpub []string
399 for k := range accVote[id] {
400 sortedXpub = append(sortedXpub, k)
402 sort.Strings(sortedXpub)
404 voteDetails := []voteDetail{}
405 voteTotal := uint64(0)
406 for _, xpub := range sortedXpub {
407 voteDetails = append(voteDetails, voteDetail{
409 VoteNumber: accVote[id][xpub],
411 voteTotal += accVote[id][xpub]
413 alias := w.AccountMgr.GetAliasByID(id)
414 votes = append(votes, AccountVotes{
417 VoteDetails: voteDetails,
418 TotalVoteNumber: voteTotal,