8 log "github.com/sirupsen/logrus"
9 "github.com/tendermint/tmlibs/db"
11 "github.com/bytom/account"
12 "github.com/bytom/asset"
13 "github.com/bytom/blockchain/query"
14 "github.com/bytom/crypto/sha3pool"
15 chainjson "github.com/bytom/encoding/json"
16 "github.com/bytom/protocol/bc"
17 "github.com/bytom/protocol/bc/types"
21 //TxPrefix is wallet database transactions prefix
23 //TxIndexPrefix is wallet database tx index prefix
24 TxIndexPrefix = "TID:"
27 func formatKey(blockHeight uint64, position uint32) string {
28 return fmt.Sprintf("%016x%08x", blockHeight, position)
31 func calcAnnotatedKey(formatKey string) []byte {
32 return []byte(TxPrefix + formatKey)
35 func calcDeleteKey(blockHeight uint64) []byte {
36 return []byte(fmt.Sprintf("%s%016x", TxPrefix, blockHeight))
39 func calcTxIndexKey(txID string) []byte {
40 return []byte(TxIndexPrefix + txID)
43 // deleteTransaction delete transactions when orphan block rollback
44 func (w *Wallet) deleteTransactions(batch db.Batch, height uint64) {
45 tmpTx := query.AnnotatedTx{}
46 txIter := w.DB.IteratorPrefix(calcDeleteKey(height))
47 defer txIter.Release()
50 if err := json.Unmarshal(txIter.Value(), &tmpTx); err == nil {
51 batch.Delete(calcTxIndexKey(tmpTx.ID.String()))
53 batch.Delete(txIter.Key())
57 // saveExternalAssetDefinition save external and local assets definition,
58 // when query ,query local first and if have no then query external
59 // details see getAliasDefinition
60 func saveExternalAssetDefinition(b *types.Block, walletDB db.DB) {
61 storeBatch := walletDB.NewBatch()
62 defer storeBatch.Write()
64 for _, tx := range b.Transactions {
65 for _, orig := range tx.Inputs {
66 if ii, ok := orig.TypedInput.(*types.IssuanceInput); ok {
67 if isValidJSON(ii.AssetDefinition) {
68 assetID := ii.AssetID()
69 if assetExist := walletDB.Get(asset.ExtAssetKey(&assetID)); assetExist == nil {
70 storeBatch.Set(asset.ExtAssetKey(&assetID), ii.AssetDefinition)
78 // Summary is the struct of transaction's input and output summary
80 Type string `json:"type"`
81 AssetID bc.AssetID `json:"asset_id,omitempty"`
82 AssetAlias string `json:"asset_alias,omitempty"`
83 Amount uint64 `json:"amount,omitempty"`
84 AccountID string `json:"account_id,omitempty"`
85 AccountAlias string `json:"account_alias,omitempty"`
86 Arbitrary chainjson.HexBytes `json:"arbitrary,omitempty"`
89 // TxSummary is the struct of transaction summary
90 type TxSummary struct {
91 ID bc.Hash `json:"tx_id"`
92 Timestamp uint64 `json:"block_time"`
93 Inputs []Summary `json:"inputs"`
94 Outputs []Summary `json:"outputs"`
97 // indexTransactions saves all annotated transactions to the database.
98 func (w *Wallet) indexTransactions(batch db.Batch, b *types.Block, txStatus *bc.TransactionStatus) error {
99 annotatedTxs := w.filterAccountTxs(b, txStatus)
100 saveExternalAssetDefinition(b, w.DB)
101 annotateTxsAccount(annotatedTxs, w.DB)
103 for _, tx := range annotatedTxs {
104 rawTx, err := json.Marshal(tx)
106 log.WithField("err", err).Error("inserting annotated_txs to db")
110 batch.Set(calcAnnotatedKey(formatKey(b.Height, uint32(tx.Position))), rawTx)
111 batch.Set(calcTxIndexKey(tx.ID.String()), []byte(formatKey(b.Height, uint32(tx.Position))))
113 // delete unconfirmed transaction
114 batch.Delete(calcUnconfirmedTxKey(tx.ID.String()))
119 // filterAccountTxs related and build the fully annotated transactions.
120 func (w *Wallet) filterAccountTxs(b *types.Block, txStatus *bc.TransactionStatus) []*query.AnnotatedTx {
121 annotatedTxs := make([]*query.AnnotatedTx, 0, len(b.Transactions))
124 for pos, tx := range b.Transactions {
125 statusFail, _ := txStatus.GetStatus(pos)
126 for _, v := range tx.Outputs {
128 sha3pool.Sum256(hash[:], v.ControlProgram)
130 if bytes := w.DB.Get(account.ContractKey(hash)); bytes != nil {
131 annotatedTxs = append(annotatedTxs, w.buildAnnotatedTransaction(tx, b, statusFail, pos))
132 continue transactionLoop
136 for _, v := range tx.Inputs {
137 outid, err := v.SpentOutputID()
141 if bytes := w.DB.Get(account.StandardUTXOKey(outid)); bytes != nil {
142 annotatedTxs = append(annotatedTxs, w.buildAnnotatedTransaction(tx, b, statusFail, pos))
143 continue transactionLoop
151 // GetTransactionByTxID get transaction by txID
152 func (w *Wallet) GetTransactionByTxID(txID string) (*query.AnnotatedTx, error) {
153 formatKey := w.DB.Get(calcTxIndexKey(txID))
154 if formatKey == nil {
155 return nil, fmt.Errorf("No transaction(tx_id=%s) ", txID)
158 annotatedTx := &query.AnnotatedTx{}
159 txInfo := w.DB.Get(calcAnnotatedKey(string(formatKey)))
160 if err := json.Unmarshal(txInfo, annotatedTx); err != nil {
163 annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
165 return annotatedTx, nil
168 // GetTransactionsSummary get transactions summary
169 func (w *Wallet) GetTransactionsSummary(transactions []*query.AnnotatedTx) []TxSummary {
172 for _, annotatedTx := range transactions {
173 tmpTxSummary := TxSummary{
174 Inputs: make([]Summary, len(annotatedTx.Inputs)),
175 Outputs: make([]Summary, len(annotatedTx.Outputs)),
177 Timestamp: annotatedTx.Timestamp,
180 for i, input := range annotatedTx.Inputs {
181 tmpTxSummary.Inputs[i].Type = input.Type
182 tmpTxSummary.Inputs[i].AccountID = input.AccountID
183 tmpTxSummary.Inputs[i].AccountAlias = input.AccountAlias
184 tmpTxSummary.Inputs[i].AssetID = input.AssetID
185 tmpTxSummary.Inputs[i].AssetAlias = input.AssetAlias
186 tmpTxSummary.Inputs[i].Amount = input.Amount
187 tmpTxSummary.Inputs[i].Arbitrary = input.Arbitrary
189 for j, output := range annotatedTx.Outputs {
190 tmpTxSummary.Outputs[j].Type = output.Type
191 tmpTxSummary.Outputs[j].AccountID = output.AccountID
192 tmpTxSummary.Outputs[j].AccountAlias = output.AccountAlias
193 tmpTxSummary.Outputs[j].AssetID = output.AssetID
194 tmpTxSummary.Outputs[j].AssetAlias = output.AssetAlias
195 tmpTxSummary.Outputs[j].Amount = output.Amount
198 Txs = append(Txs, tmpTxSummary)
204 func findTransactionsByAccount(annotatedTx *query.AnnotatedTx, accountID string) bool {
205 for _, input := range annotatedTx.Inputs {
206 if input.AccountID == accountID {
211 for _, output := range annotatedTx.Outputs {
212 if output.AccountID == accountID {
220 // GetTransactions get all walletDB transactions, and filter transactions by accountID optional
221 func (w *Wallet) GetTransactions(accountID string) ([]*query.AnnotatedTx, error) {
222 annotatedTxs := []*query.AnnotatedTx{}
224 txIter := w.DB.IteratorPrefix([]byte(TxPrefix))
225 defer txIter.Release()
227 annotatedTx := &query.AnnotatedTx{}
228 if err := json.Unmarshal(txIter.Value(), &annotatedTx); err != nil {
232 if accountID == "" || findTransactionsByAccount(annotatedTx, accountID) {
233 annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
234 annotatedTxs = append([]*query.AnnotatedTx{annotatedTx}, annotatedTxs...)
238 return annotatedTxs, nil
241 // GetAccountBalances return all account balances
242 func (w *Wallet) GetAccountBalances(accountID string, id string) ([]AccountBalance, error) {
243 return w.indexBalances(w.GetAccountUtxos(accountID, "", false, false))
246 // AccountBalance account balance
247 type AccountBalance struct {
248 AccountID string `json:"account_id"`
249 Alias string `json:"account_alias"`
250 AssetAlias string `json:"asset_alias"`
251 AssetID string `json:"asset_id"`
252 Amount uint64 `json:"amount"`
253 AssetDefinition map[string]interface{} `json:"asset_definition"`
256 func (w *Wallet) indexBalances(accountUTXOs []*account.UTXO) ([]AccountBalance, error) {
257 accBalance := make(map[string]map[string]uint64)
258 balances := []AccountBalance{}
260 for _, accountUTXO := range accountUTXOs {
261 assetID := accountUTXO.AssetID.String()
262 if _, ok := accBalance[accountUTXO.AccountID]; ok {
263 if _, ok := accBalance[accountUTXO.AccountID][assetID]; ok {
264 accBalance[accountUTXO.AccountID][assetID] += accountUTXO.Amount
266 accBalance[accountUTXO.AccountID][assetID] = accountUTXO.Amount
269 accBalance[accountUTXO.AccountID] = map[string]uint64{assetID: accountUTXO.Amount}
273 var sortedAccount []string
274 for k := range accBalance {
275 sortedAccount = append(sortedAccount, k)
277 sort.Strings(sortedAccount)
279 for _, id := range sortedAccount {
280 var sortedAsset []string
281 for k := range accBalance[id] {
282 sortedAsset = append(sortedAsset, k)
284 sort.Strings(sortedAsset)
286 for _, assetID := range sortedAsset {
287 alias := w.AccountMgr.GetAliasByID(id)
288 targetAsset, err := w.AssetReg.GetAsset(assetID)
293 assetAlias := *targetAsset.Alias
294 balances = append(balances, AccountBalance{
298 AssetAlias: assetAlias,
299 Amount: accBalance[id][assetID],
300 AssetDefinition: targetAsset.DefinitionMap,