OSDN Git Service

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