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/protocol/bc"
18 "github.com/vapor/protocol/bc/types"
21 func parseGlobalTxIdx(globalTxIdx []byte) (*bc.Hash, uint64) {
22 var hashBytes [32]byte
23 copy(hashBytes[:], globalTxIdx[:32])
24 hash := bc.NewHash(hashBytes)
25 position := binary.BigEndian.Uint64(globalTxIdx[32:])
26 return &hash, position
29 // saveExternalAssetDefinition save external and local assets definition,
30 // when query ,query local first and if have no then query external
31 // details see getAliasDefinition
32 func saveExternalAssetDefinition(b *types.Block, store WalletStorer) error {
34 defer store.CommitBatch()
36 for _, tx := range b.Transactions {
37 for _, orig := range tx.Inputs {
38 if cci, ok := orig.TypedInput.(*types.CrossChainInput); ok {
39 assetID := cci.AssetId
40 assetExist, err := store.GetAssetDefinition(assetID)
44 if assetExist == nil {
45 store.SetAssetDefinition(assetID, cci.AssetDefinition)
53 // Summary is the struct of transaction's input and output summary
55 Type string `json:"type"`
56 AssetID bc.AssetID `json:"asset_id,omitempty"`
57 AssetAlias string `json:"asset_alias,omitempty"`
58 Amount uint64 `json:"amount,omitempty"`
59 AccountID string `json:"account_id,omitempty"`
60 AccountAlias string `json:"account_alias,omitempty"`
61 Arbitrary chainjson.HexBytes `json:"arbitrary,omitempty"`
64 // TxSummary is the struct of transaction summary
65 type TxSummary struct {
66 ID bc.Hash `json:"tx_id"`
67 Timestamp uint64 `json:"block_time"`
68 Inputs []Summary `json:"inputs"`
69 Outputs []Summary `json:"outputs"`
72 // indexTransactions saves all annotated transactions to the database.
73 func (w *Wallet) indexTransactions(b *types.Block, txStatus *bc.TransactionStatus, annotatedTxs []*query.AnnotatedTx) error {
74 for _, tx := range annotatedTxs {
75 rawTx, err := json.Marshal(tx)
77 log.WithFields(log.Fields{"module": logModule, "err": err}).Error("inserting annotated_txs to db")
81 w.store.SetTransaction(b.Height, tx.Position, tx.ID.String(), rawTx)
82 w.store.DeleteUnconfirmedTransaction(tx.ID.String())
89 for position, globalTx := range b.Transactions {
90 blockHash := b.BlockHeader.Hash()
91 w.store.SetGlobalTransactionIndex(globalTx.ID.String(), &blockHash, uint64(position))
97 // filterAccountTxs related and build the fully annotated transactions.
98 func (w *Wallet) filterAccountTxs(b *types.Block, txStatus *bc.TransactionStatus) []*query.AnnotatedTx {
99 annotatedTxs := make([]*query.AnnotatedTx, 0, len(b.Transactions))
102 for pos, tx := range b.Transactions {
103 statusFail, _ := txStatus.GetStatus(pos)
104 for _, v := range tx.Outputs {
106 sha3pool.Sum256(hash[:], v.ControlProgram())
108 cp, err := w.store.GetControlProgram(hash)
110 log.WithFields(log.Fields{"module": logModule, "err": err, "hash": string(hash[:])}).Error("filterAccountTxs fail.")
114 annotatedTxs = append(annotatedTxs, w.buildAnnotatedTransaction(tx, b, statusFail, pos))
115 continue transactionLoop
119 for _, v := range tx.Inputs {
120 outid, err := v.SpentOutputID()
124 if bytes := w.store.GetStandardUTXO(outid); bytes != nil {
125 annotatedTxs = append(annotatedTxs, w.buildAnnotatedTransaction(tx, b, statusFail, pos))
126 continue transactionLoop
134 // GetTransactionByTxID get transaction by txID
135 func (w *Wallet) GetTransactionByTxID(txID string) (*query.AnnotatedTx, error) {
136 if annotatedTx, err := w.getAccountTxByTxID(txID); err == nil {
137 return annotatedTx, nil
138 } else if !w.TxIndexFlag {
142 return w.getGlobalTxByTxID(txID)
145 func (w *Wallet) getAccountTxByTxID(txID string) (*query.AnnotatedTx, error) {
146 annotatedTx := &query.AnnotatedTx{}
147 txInfo, err := w.store.GetTransaction(txID)
152 if err := json.Unmarshal(txInfo, annotatedTx); err != nil {
156 annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
157 return annotatedTx, nil
160 func (w *Wallet) getGlobalTxByTxID(txID string) (*query.AnnotatedTx, error) {
161 globalTxIdx := w.store.GetGlobalTransaction(txID)
162 if globalTxIdx == nil {
163 return nil, fmt.Errorf("No transaction(tx_id=%s) ", txID)
166 blockHash, pos := parseGlobalTxIdx(globalTxIdx)
167 block, err := w.chain.GetBlockByHash(blockHash)
172 txStatus, err := w.chain.GetTransactionStatus(blockHash)
177 statusFail, err := txStatus.GetStatus(int(pos))
182 tx := block.Transactions[int(pos)]
183 return w.buildAnnotatedTransaction(tx, block, statusFail, int(pos)), nil
186 // GetTransactionsSummary get transactions summary
187 func (w *Wallet) GetTransactionsSummary(transactions []*query.AnnotatedTx) []TxSummary {
190 for _, annotatedTx := range transactions {
191 tmpTxSummary := TxSummary{
192 Inputs: make([]Summary, len(annotatedTx.Inputs)),
193 Outputs: make([]Summary, len(annotatedTx.Outputs)),
195 Timestamp: annotatedTx.Timestamp,
198 for i, input := range annotatedTx.Inputs {
199 tmpTxSummary.Inputs[i].Type = input.Type
200 tmpTxSummary.Inputs[i].AccountID = input.AccountID
201 tmpTxSummary.Inputs[i].AccountAlias = input.AccountAlias
202 tmpTxSummary.Inputs[i].AssetID = input.AssetID
203 tmpTxSummary.Inputs[i].AssetAlias = input.AssetAlias
204 tmpTxSummary.Inputs[i].Amount = input.Amount
205 tmpTxSummary.Inputs[i].Arbitrary = input.Arbitrary
207 for j, output := range annotatedTx.Outputs {
208 tmpTxSummary.Outputs[j].Type = output.Type
209 tmpTxSummary.Outputs[j].AccountID = output.AccountID
210 tmpTxSummary.Outputs[j].AccountAlias = output.AccountAlias
211 tmpTxSummary.Outputs[j].AssetID = output.AssetID
212 tmpTxSummary.Outputs[j].AssetAlias = output.AssetAlias
213 tmpTxSummary.Outputs[j].Amount = output.Amount
216 Txs = append(Txs, tmpTxSummary)
222 func findTransactionsByAccount(annotatedTx *query.AnnotatedTx, accountID string) bool {
223 for _, input := range annotatedTx.Inputs {
224 if input.AccountID == accountID {
229 for _, output := range annotatedTx.Outputs {
230 if output.AccountID == accountID {
238 // GetTransactions get all walletDB transactions, and filter transactions by accountID optional
239 func (w *Wallet) GetTransactions(accountID string) ([]*query.AnnotatedTx, error) {
240 annotatedTxs := []*query.AnnotatedTx{}
241 annotatedTxs, err := w.store.GetTransactions()
246 newAnnotatedTxs := []*query.AnnotatedTx{}
247 for _, annotatedTx := range annotatedTxs {
248 if accountID == "" || findTransactionsByAccount(annotatedTx, accountID) {
249 annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
250 newAnnotatedTxs = append([]*query.AnnotatedTx{annotatedTx}, newAnnotatedTxs...)
254 return newAnnotatedTxs, nil
257 // GetAccountBalances return all account balances
258 func (w *Wallet) GetAccountBalances(accountID string, id string) ([]AccountBalance, error) {
259 return w.indexBalances(w.GetAccountUtxos(accountID, "", false, false, false))
262 // AccountBalance account balance
263 type AccountBalance struct {
264 AccountID string `json:"account_id"`
265 Alias string `json:"account_alias"`
266 AssetAlias string `json:"asset_alias"`
267 AssetID string `json:"asset_id"`
268 Amount uint64 `json:"amount"`
269 AssetDefinition map[string]interface{} `json:"asset_definition"`
272 func (w *Wallet) indexBalances(accountUTXOs []*account.UTXO) ([]AccountBalance, error) {
273 accBalance := make(map[string]map[string]uint64)
274 balances := []AccountBalance{}
276 for _, accountUTXO := range accountUTXOs {
277 assetID := accountUTXO.AssetID.String()
278 if _, ok := accBalance[accountUTXO.AccountID]; ok {
279 if _, ok := accBalance[accountUTXO.AccountID][assetID]; ok {
280 accBalance[accountUTXO.AccountID][assetID] += accountUTXO.Amount
282 accBalance[accountUTXO.AccountID][assetID] = accountUTXO.Amount
285 accBalance[accountUTXO.AccountID] = map[string]uint64{assetID: accountUTXO.Amount}
289 var sortedAccount []string
290 for k := range accBalance {
291 sortedAccount = append(sortedAccount, k)
293 sort.Strings(sortedAccount)
295 for _, id := range sortedAccount {
296 var sortedAsset []string
297 for k := range accBalance[id] {
298 sortedAsset = append(sortedAsset, k)
300 sort.Strings(sortedAsset)
302 for _, assetID := range sortedAsset {
303 alias := w.AccountMgr.GetAliasByID(id)
304 targetAsset, err := w.AssetReg.GetAsset(assetID)
309 assetAlias := *targetAsset.Alias
310 balances = append(balances, AccountBalance{
314 AssetAlias: assetAlias,
315 Amount: accBalance[id][assetID],
316 AssetDefinition: targetAsset.DefinitionMap,
324 // GetAccountVotes return all account votes
325 func (w *Wallet) GetAccountVotes(accountID string, id string) ([]AccountVotes, error) {
326 return w.indexVotes(w.GetAccountUtxos(accountID, "", false, false, true))
329 type voteDetail struct {
330 Vote string `json:"vote"`
331 VoteNumber uint64 `json:"vote_number"`
334 // AccountVotes account vote
335 type AccountVotes struct {
336 AccountID string `json:"account_id"`
337 Alias string `json:"account_alias"`
338 TotalVoteNumber uint64 `json:"total_vote_number"`
339 VoteDetails []voteDetail `json:"vote_details"`
342 func (w *Wallet) indexVotes(accountUTXOs []*account.UTXO) ([]AccountVotes, error) {
343 accVote := make(map[string]map[string]uint64)
344 votes := []AccountVotes{}
346 for _, accountUTXO := range accountUTXOs {
347 if accountUTXO.AssetID != *consensus.BTMAssetID || accountUTXO.Vote == nil {
350 xpub := hex.EncodeToString(accountUTXO.Vote)
351 if _, ok := accVote[accountUTXO.AccountID]; ok {
352 accVote[accountUTXO.AccountID][xpub] += accountUTXO.Amount
354 accVote[accountUTXO.AccountID] = map[string]uint64{xpub: accountUTXO.Amount}
359 var sortedAccount []string
360 for k := range accVote {
361 sortedAccount = append(sortedAccount, k)
363 sort.Strings(sortedAccount)
365 for _, id := range sortedAccount {
366 var sortedXpub []string
367 for k := range accVote[id] {
368 sortedXpub = append(sortedXpub, k)
370 sort.Strings(sortedXpub)
372 voteDetails := []voteDetail{}
373 voteTotal := uint64(0)
374 for _, xpub := range sortedXpub {
375 voteDetails = append(voteDetails, voteDetail{
377 VoteNumber: accVote[id][xpub],
379 voteTotal += accVote[id][xpub]
381 alias := w.AccountMgr.GetAliasByID(id)
382 votes = append(votes, AccountVotes{
385 VoteDetails: voteDetails,
386 TotalVoteNumber: voteTotal,