OSDN Git Service

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