OSDN Git Service

add asset info when query transactions (#685)
[bytom/bytom.git] / wallet / annotated.go
1 package wallet
2
3 import (
4         "encoding/json"
5         "fmt"
6
7         log "github.com/sirupsen/logrus"
8         "github.com/tendermint/tmlibs/db"
9
10         "github.com/bytom/account"
11         "github.com/bytom/asset"
12         "github.com/bytom/blockchain/query"
13         "github.com/bytom/blockchain/signers"
14         "github.com/bytom/common"
15         "github.com/bytom/consensus"
16         "github.com/bytom/consensus/segwit"
17         "github.com/bytom/crypto/sha3pool"
18         "github.com/bytom/protocol/bc"
19         "github.com/bytom/protocol/bc/types"
20         "github.com/bytom/protocol/vm/vmutil"
21 )
22
23 // annotateTxs adds asset data to transactions
24 func annotateTxsAsset(w *Wallet, txs []*query.AnnotatedTx) {
25         for i, tx := range txs {
26                 for j, input := range tx.Inputs {
27                         alias, definition := w.getAliasDefinition(input.AssetID)
28                         txs[i].Inputs[j].AssetAlias, txs[i].Inputs[j].AssetDefinition = alias, &definition
29                 }
30                 for k, output := range tx.Outputs {
31                         alias, definition := w.getAliasDefinition(output.AssetID)
32                         txs[i].Outputs[k].AssetAlias, txs[i].Outputs[k].AssetDefinition = alias, &definition
33                 }
34         }
35 }
36
37 func (w *Wallet) getExternalDefinition(assetID *bc.AssetID) json.RawMessage {
38         definitionByte := w.DB.Get(asset.CalcExtAssetKey(assetID))
39         if definitionByte == nil {
40                 return nil
41         }
42
43         definitionMap := make(map[string]interface{})
44         if err := json.Unmarshal(definitionByte, &definitionMap); err != nil {
45                 return nil
46         }
47
48         saveAlias := assetID.String()
49         storeBatch := w.DB.NewBatch()
50
51         externalAsset := &asset.Asset{
52                 AssetID:           *assetID,
53                 Alias:             &saveAlias,
54                 DefinitionMap:     definitionMap,
55                 RawDefinitionByte: definitionByte,
56                 Signer:            &signers.Signer{Type: "external"},
57         }
58
59         if rawAsset, err := json.Marshal(externalAsset); err == nil {
60                 log.WithFields(log.Fields{"assetID": assetID.String(), "alias": saveAlias}).Info("index external asset")
61                 storeBatch.Set(asset.Key(assetID), rawAsset)
62         }
63         storeBatch.Set(asset.AliasKey(saveAlias), []byte(assetID.String()))
64         storeBatch.Write()
65
66         return definitionByte
67 }
68
69 func (w *Wallet) getAliasDefinition(assetID bc.AssetID) (string, json.RawMessage) {
70         //btm
71         if assetID.String() == consensus.BTMAssetID.String() {
72                 alias := consensus.BTMAlias
73                 definition := []byte(asset.DefaultNativeAsset.RawDefinitionByte)
74
75                 return alias, definition
76         }
77
78         //local asset and saved external asset
79         if localAsset, err := w.AssetReg.FindByID(nil, &assetID); err == nil {
80                 alias := *localAsset.Alias
81                 definition := []byte(localAsset.RawDefinitionByte)
82                 return alias, definition
83         }
84
85         //external asset
86         if definition := w.getExternalDefinition(&assetID); definition != nil {
87                 return assetID.String(), definition
88         }
89
90         return "", nil
91 }
92
93 // annotateTxs adds account data to transactions
94 func annotateTxsAccount(txs []*query.AnnotatedTx, walletDB db.DB) {
95         for i, tx := range txs {
96                 for j, input := range tx.Inputs {
97                         //issue asset tx input SpentOutputID is nil
98                         if input.SpentOutputID == nil {
99                                 continue
100                         }
101                         localAccount, err := getAccountFromUTXO(*input.SpentOutputID, walletDB)
102                         if localAccount == nil || err != nil {
103                                 continue
104                         }
105                         txs[i].Inputs[j].AccountAlias = localAccount.Alias
106                         txs[i].Inputs[j].AccountID = localAccount.ID
107                 }
108                 for j, output := range tx.Outputs {
109                         localAccount, err := getAccountFromACP(output.ControlProgram, walletDB)
110                         if localAccount == nil || err != nil {
111                                 continue
112                         }
113                         txs[i].Outputs[j].AccountAlias = localAccount.Alias
114                         txs[i].Outputs[j].AccountID = localAccount.ID
115                 }
116         }
117 }
118
119 func getAccountFromUTXO(outputID bc.Hash, walletDB db.DB) (*account.Account, error) {
120         accountUTXO := account.UTXO{}
121         localAccount := account.Account{}
122
123         accountUTXOValue := walletDB.Get(account.StandardUTXOKey(outputID))
124         if accountUTXOValue == nil {
125                 return nil, fmt.Errorf("failed get account utxo:%x ", outputID)
126         }
127
128         if err := json.Unmarshal(accountUTXOValue, &accountUTXO); err != nil {
129                 return nil, err
130         }
131
132         accountValue := walletDB.Get(account.Key(accountUTXO.AccountID))
133         if accountValue == nil {
134                 return nil, fmt.Errorf("failed get account:%s ", accountUTXO.AccountID)
135         }
136         if err := json.Unmarshal(accountValue, &localAccount); err != nil {
137                 return nil, err
138         }
139
140         return &localAccount, nil
141 }
142
143 func getAccountFromACP(program []byte, walletDB db.DB) (*account.Account, error) {
144         var hash common.Hash
145         accountCP := account.CtrlProgram{}
146         localAccount := account.Account{}
147
148         sha3pool.Sum256(hash[:], program)
149
150         rawProgram := walletDB.Get(account.CPKey(hash))
151         if rawProgram == nil {
152                 return nil, fmt.Errorf("failed get account control program:%x ", hash)
153         }
154
155         if err := json.Unmarshal(rawProgram, &accountCP); err != nil {
156                 return nil, err
157         }
158
159         accountValue := walletDB.Get(account.Key(accountCP.AccountID))
160         if accountValue == nil {
161                 return nil, fmt.Errorf("failed get account:%s ", accountCP.AccountID)
162         }
163
164         if err := json.Unmarshal(accountValue, &localAccount); err != nil {
165                 return nil, err
166         }
167
168         return &localAccount, nil
169 }
170
171 var emptyJSONObject = json.RawMessage(`{}`)
172
173 func isValidJSON(b []byte) bool {
174         var v interface{}
175         err := json.Unmarshal(b, &v)
176         return err == nil
177 }
178
179 func (w *Wallet) buildAnnotatedTransaction(orig *types.Tx, b *types.Block, statusFail bool, indexInBlock int) *query.AnnotatedTx {
180         tx := &query.AnnotatedTx{
181                 ID:                     orig.ID,
182                 Timestamp:              b.Timestamp,
183                 BlockID:                b.Hash(),
184                 BlockHeight:            b.Height,
185                 Position:               uint32(indexInBlock),
186                 BlockTransactionsCount: uint32(len(b.Transactions)),
187                 Inputs:                 make([]*query.AnnotatedInput, 0, len(orig.Inputs)),
188                 Outputs:                make([]*query.AnnotatedOutput, 0, len(orig.Outputs)),
189                 StatusFail:             statusFail,
190         }
191         for i := range orig.Inputs {
192                 tx.Inputs = append(tx.Inputs, w.BuildAnnotatedInput(orig, uint32(i)))
193         }
194         for i := range orig.Outputs {
195                 tx.Outputs = append(tx.Outputs, w.BuildAnnotatedOutput(orig, i))
196         }
197         return tx
198 }
199
200 // BuildAnnotatedInput build the annotated input.
201 func (w *Wallet) BuildAnnotatedInput(tx *types.Tx, i uint32) *query.AnnotatedInput {
202         orig := tx.Inputs[i]
203         in := &query.AnnotatedInput{
204                 AssetDefinition: &emptyJSONObject,
205         }
206         if orig.InputType() != types.CoinbaseInputType {
207                 in.AssetID = orig.AssetID()
208                 in.Amount = orig.Amount()
209         }
210
211         id := tx.Tx.InputIDs[i]
212         e := tx.Entries[id]
213         switch e := e.(type) {
214         case *bc.Spend:
215                 in.Type = "spend"
216                 in.ControlProgram = orig.ControlProgram()
217                 in.Address = w.getAddressFromControlProgram(in.ControlProgram)
218                 in.SpentOutputID = e.SpentOutputId
219         case *bc.Issuance:
220                 in.Type = "issue"
221                 in.IssuanceProgram = orig.IssuanceProgram()
222         case *bc.Coinbase:
223                 in.Type = "coinbase"
224                 in.Arbitrary = e.Arbitrary
225         }
226         return in
227 }
228
229 func (w *Wallet) getAddressFromControlProgram(prog []byte) string {
230         if segwit.IsP2WPKHScript(prog) {
231                 if pubHash, err := segwit.GetHashFromStandardProg(prog); err == nil {
232                         return buildP2PKHAddress(pubHash)
233                 }
234         } else if segwit.IsP2WSHScript(prog) {
235                 if scriptHash, err := segwit.GetHashFromStandardProg(prog); err == nil {
236                         return buildP2SHAddress(scriptHash)
237                 }
238         }
239
240         return ""
241 }
242
243 func buildP2PKHAddress(pubHash []byte) string {
244         address, err := common.NewAddressWitnessPubKeyHash(pubHash, consensus.ActiveNetParams)
245         if err != nil {
246                 return ""
247         }
248
249         return address.EncodeAddress()
250 }
251
252 func buildP2SHAddress(scriptHash []byte) string {
253         address, err := common.NewAddressWitnessScriptHash(scriptHash, consensus.ActiveNetParams)
254         if err != nil {
255                 return ""
256         }
257
258         return address.EncodeAddress()
259 }
260
261 // BuildAnnotatedOutput build the annotated output.
262 func (w *Wallet) BuildAnnotatedOutput(tx *types.Tx, idx int) *query.AnnotatedOutput {
263         orig := tx.Outputs[idx]
264         outid := tx.OutputID(idx)
265         out := &query.AnnotatedOutput{
266                 OutputID:        *outid,
267                 Position:        idx,
268                 AssetID:         *orig.AssetId,
269                 AssetDefinition: &emptyJSONObject,
270                 Amount:          orig.Amount,
271                 ControlProgram:  orig.ControlProgram,
272                 Address:         w.getAddressFromControlProgram(orig.ControlProgram),
273         }
274
275         if vmutil.IsUnspendable(out.ControlProgram) {
276                 out.Type = "retire"
277         } else {
278                 out.Type = "control"
279         }
280         return out
281 }