9 log "github.com/sirupsen/logrus"
10 "github.com/tendermint/tmlibs/db"
12 "github.com/vapor/account"
13 "github.com/vapor/asset"
14 "github.com/vapor/blockchain/query"
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"
19 "github.com/vapor/protocol/vm/vmutil"
23 //TxPrefix is wallet database transactions prefix
25 //TxIndexPrefix is wallet database tx index prefix
26 TxIndexPrefix = "TID:"
29 func formatKey(blockHeight uint64, position uint32) string {
30 return fmt.Sprintf("%016x%08x", blockHeight, position)
33 func calcAnnotatedKey(formatKey string) []byte {
34 return []byte(TxPrefix + formatKey)
37 func calcDeleteKey(blockHeight uint64) []byte {
38 return []byte(fmt.Sprintf("%s%016x", TxPrefix, blockHeight))
41 func calcTxIndexKey(txID string) []byte {
42 return []byte(TxIndexPrefix + txID)
45 // deleteTransaction delete transactions when orphan block rollback
46 func (w *Wallet) deleteTransactions(batch db.Batch, height uint64) {
47 tmpTx := query.AnnotatedTx{}
48 txIter := w.DB.IteratorPrefix(calcDeleteKey(height))
49 defer txIter.Release()
52 if err := json.Unmarshal(txIter.Value(), &tmpTx); err == nil {
53 batch.Delete(calcTxIndexKey(tmpTx.ID.String()))
55 batch.Delete(txIter.Key())
59 // saveExternalAssetDefinition save external and local assets definition,
60 // when query ,query local first and if have no then query external
61 // details see getAliasDefinition
62 func saveExternalAssetDefinition(b *types.Block, walletDB db.DB) {
63 storeBatch := walletDB.NewBatch()
64 defer storeBatch.Write()
66 for _, tx := range b.Transactions {
67 for _, orig := range tx.Inputs {
68 if ii, ok := orig.TypedInput.(*types.IssuanceInput); ok {
69 if isValidJSON(ii.AssetDefinition) {
70 assetID := ii.AssetID()
71 if assetExist := walletDB.Get(asset.ExtAssetKey(&assetID)); assetExist == nil {
72 storeBatch.Set(asset.ExtAssetKey(&assetID), ii.AssetDefinition)
80 // Summary is the struct of transaction's input and output summary
82 Type string `json:"type"`
83 AssetID bc.AssetID `json:"asset_id,omitempty"`
84 AssetAlias string `json:"asset_alias,omitempty"`
85 Amount uint64 `json:"amount,omitempty"`
86 AccountID string `json:"account_id,omitempty"`
87 AccountAlias string `json:"account_alias,omitempty"`
88 Arbitrary chainjson.HexBytes `json:"arbitrary,omitempty"`
91 // TxSummary is the struct of transaction summary
92 type TxSummary struct {
93 ID bc.Hash `json:"tx_id"`
94 Timestamp uint64 `json:"block_time"`
95 Inputs []Summary `json:"inputs"`
96 Outputs []Summary `json:"outputs"`
99 // indexTransactions saves all annotated transactions to the database.
100 func (w *Wallet) indexTransactions(batch db.Batch, b *types.Block, txStatus *bc.TransactionStatus) error {
101 annotatedTxs := w.filterAccountTxs(b, txStatus)
102 saveExternalAssetDefinition(b, w.DB)
103 annotateTxsAccount(annotatedTxs, w.DB)
105 for _, tx := range annotatedTxs {
106 rawTx, err := json.Marshal(tx)
108 log.WithField("err", err).Error("inserting annotated_txs to db")
112 batch.Set(calcAnnotatedKey(formatKey(b.Height, uint32(tx.Position))), rawTx)
113 batch.Set(calcTxIndexKey(tx.ID.String()), []byte(formatKey(b.Height, uint32(tx.Position))))
115 // delete unconfirmed transaction
116 batch.Delete(calcUnconfirmedTxKey(tx.ID.String()))
121 // filterAccountTxs related and build the fully annotated transactions.
122 func (w *Wallet) filterAccountTxs(b *types.Block, txStatus *bc.TransactionStatus) []*query.AnnotatedTx {
123 annotatedTxs := make([]*query.AnnotatedTx, 0, len(b.Transactions))
124 redeemContract := w.dposAddress.ScriptAddress()
125 program, _ := vmutil.P2WPKHProgram(redeemContract)
127 for pos, tx := range b.Transactions {
128 statusFail, _ := txStatus.GetStatus(pos)
129 for _, v := range tx.Outputs {
131 if bytes.Equal(v.ControlProgram, program) {
132 annotatedTxs = append(annotatedTxs, w.buildAnnotatedTransaction(tx, b, statusFail, pos))
133 continue transactionLoop
137 sha3pool.Sum256(hash[:], v.ControlProgram)
138 if bytes := w.DB.Get(account.ContractKey(hash)); bytes != nil {
139 annotatedTxs = append(annotatedTxs, w.buildAnnotatedTransaction(tx, b, statusFail, pos))
140 continue transactionLoop
144 for _, v := range tx.Inputs {
145 if bytes.Equal(v.ControlProgram(), program) {
146 annotatedTxs = append(annotatedTxs, w.buildAnnotatedTransaction(tx, b, statusFail, pos))
147 continue transactionLoop
150 outid, err := v.SpentOutputID()
154 if bytes := w.DB.Get(account.StandardUTXOKey(outid)); bytes != nil {
155 annotatedTxs = append(annotatedTxs, w.buildAnnotatedTransaction(tx, b, statusFail, pos))
156 continue transactionLoop
164 // GetTransactionByTxID get transaction by txID
165 func (w *Wallet) GetTransactionByTxID(txID string) (*query.AnnotatedTx, error) {
166 formatKey := w.DB.Get(calcTxIndexKey(txID))
167 if formatKey == nil {
168 return nil, fmt.Errorf("No transaction(tx_id=%s) ", txID)
171 annotatedTx := &query.AnnotatedTx{}
172 txInfo := w.DB.Get(calcAnnotatedKey(string(formatKey)))
173 if err := json.Unmarshal(txInfo, annotatedTx); err != nil {
176 annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
178 return annotatedTx, nil
181 // GetTransactionsSummary get transactions summary
182 func (w *Wallet) GetTransactionsSummary(transactions []*query.AnnotatedTx) []TxSummary {
185 for _, annotatedTx := range transactions {
186 tmpTxSummary := TxSummary{
187 Inputs: make([]Summary, len(annotatedTx.Inputs)),
188 Outputs: make([]Summary, len(annotatedTx.Outputs)),
190 Timestamp: annotatedTx.Timestamp,
193 for i, input := range annotatedTx.Inputs {
194 tmpTxSummary.Inputs[i].Type = input.Type
195 tmpTxSummary.Inputs[i].AccountID = input.AccountID
196 tmpTxSummary.Inputs[i].AccountAlias = input.AccountAlias
197 tmpTxSummary.Inputs[i].AssetID = input.AssetID
198 tmpTxSummary.Inputs[i].AssetAlias = input.AssetAlias
199 tmpTxSummary.Inputs[i].Amount = input.Amount
200 tmpTxSummary.Inputs[i].Arbitrary = input.Arbitrary
202 for j, output := range annotatedTx.Outputs {
203 tmpTxSummary.Outputs[j].Type = output.Type
204 tmpTxSummary.Outputs[j].AccountID = output.AccountID
205 tmpTxSummary.Outputs[j].AccountAlias = output.AccountAlias
206 tmpTxSummary.Outputs[j].AssetID = output.AssetID
207 tmpTxSummary.Outputs[j].AssetAlias = output.AssetAlias
208 tmpTxSummary.Outputs[j].Amount = output.Amount
211 Txs = append(Txs, tmpTxSummary)
217 func findTransactionsByAccount(annotatedTx *query.AnnotatedTx, accountID string) bool {
218 for _, input := range annotatedTx.Inputs {
219 if input.AccountID == accountID {
224 for _, output := range annotatedTx.Outputs {
225 if output.AccountID == accountID {
233 // GetTransactions get all walletDB transactions, and filter transactions by accountID optional
234 func (w *Wallet) GetTransactions(accountID string) ([]*query.AnnotatedTx, error) {
235 annotatedTxs := []*query.AnnotatedTx{}
237 txIter := w.DB.IteratorPrefix([]byte(TxPrefix))
238 defer txIter.Release()
240 annotatedTx := &query.AnnotatedTx{}
241 if err := json.Unmarshal(txIter.Value(), &annotatedTx); err != nil {
245 if accountID == "" || findTransactionsByAccount(annotatedTx, accountID) {
246 annotateTxsAsset(w, []*query.AnnotatedTx{annotatedTx})
247 annotatedTxs = append([]*query.AnnotatedTx{annotatedTx}, annotatedTxs...)
251 return annotatedTxs, nil
254 // GetAccountBalances return all account balances
255 func (w *Wallet) GetAccountBalances(accountID string, id string) ([]AccountBalance, error) {
256 return w.indexBalances(w.GetAccountUtxos(accountID, "", false, false))
259 // AccountBalance account balance
260 type AccountBalance struct {
261 AccountID string `json:"account_id"`
262 Alias string `json:"account_alias"`
263 AssetAlias string `json:"asset_alias"`
264 AssetID string `json:"asset_id"`
265 Amount uint64 `json:"amount"`
266 AssetDefinition map[string]interface{} `json:"asset_definition"`
269 func (w *Wallet) indexBalances(accountUTXOs []*account.UTXO) ([]AccountBalance, error) {
270 accBalance := make(map[string]map[string]uint64)
271 balances := []AccountBalance{}
273 for _, accountUTXO := range accountUTXOs {
274 assetID := accountUTXO.AssetID.String()
275 if _, ok := accBalance[accountUTXO.AccountID]; ok {
276 if _, ok := accBalance[accountUTXO.AccountID][assetID]; ok {
277 accBalance[accountUTXO.AccountID][assetID] += accountUTXO.Amount
279 accBalance[accountUTXO.AccountID][assetID] = accountUTXO.Amount
282 accBalance[accountUTXO.AccountID] = map[string]uint64{assetID: accountUTXO.Amount}
286 var sortedAccount []string
287 for k := range accBalance {
288 sortedAccount = append(sortedAccount, k)
290 sort.Strings(sortedAccount)
292 for _, id := range sortedAccount {
293 var sortedAsset []string
294 for k := range accBalance[id] {
295 sortedAsset = append(sortedAsset, k)
297 sort.Strings(sortedAsset)
299 for _, assetID := range sortedAsset {
300 alias := w.AccountMgr.GetAliasByID(id)
301 targetAsset, err := w.AssetReg.GetAsset(assetID)
306 assetAlias := *targetAsset.Alias
307 balances = append(balances, AccountBalance{
311 AssetAlias: assetAlias,
312 Amount: accBalance[id][assetID],
313 AssetDefinition: targetAsset.DefinitionMap,