OSDN Git Service

versoin1.1.9 (#594)
[bytom/vapor.git] / wallet / annotated.go
1 package wallet
2
3 import (
4         "encoding/json"
5
6         log "github.com/sirupsen/logrus"
7
8         "github.com/bytom/vapor/account"
9         "github.com/bytom/vapor/asset"
10         "github.com/bytom/vapor/blockchain/query"
11         "github.com/bytom/vapor/common"
12         "github.com/bytom/vapor/consensus"
13         "github.com/bytom/vapor/consensus/segwit"
14         "github.com/bytom/vapor/crypto/sha3pool"
15         "github.com/bytom/vapor/protocol/bc"
16         "github.com/bytom/vapor/protocol/bc/types"
17 )
18
19 // annotateTxs adds asset data to transactions
20 func annotateTxsAsset(w *Wallet, txs []*query.AnnotatedTx) {
21         for i, tx := range txs {
22                 for j, input := range tx.Inputs {
23                         alias, definition := w.getAliasDefinition(input.AssetID)
24                         txs[i].Inputs[j].AssetAlias, txs[i].Inputs[j].AssetDefinition = alias, &definition
25                 }
26                 for k, output := range tx.Outputs {
27                         alias, definition := w.getAliasDefinition(output.AssetID)
28                         txs[i].Outputs[k].AssetAlias, txs[i].Outputs[k].AssetDefinition = alias, &definition
29                 }
30         }
31 }
32
33 func (w *Wallet) getExternalDefinition(assetID *bc.AssetID) json.RawMessage {
34         externalAsset, err := w.Store.GetAsset(assetID)
35         if err != nil && err != ErrGetAsset {
36                 log.WithFields(log.Fields{"module": logModule, "err": err, "assetID": assetID.String()}).Info("fail on get asset definition.")
37         }
38         if externalAsset == nil {
39                 return nil
40         }
41
42         if err := w.AssetReg.SaveAsset(externalAsset, *externalAsset.Alias); err != nil {
43                 log.WithFields(log.Fields{"module": logModule, "err": err, "assetAlias": *externalAsset.Alias}).Info("fail on save external asset to internal asset DB")
44         }
45         return json.RawMessage(externalAsset.RawDefinitionByte)
46 }
47
48 func (w *Wallet) getAliasDefinition(assetID bc.AssetID) (string, json.RawMessage) {
49         //btm
50         if assetID.String() == consensus.BTMAssetID.String() {
51                 alias := consensus.BTMAlias
52                 definition := []byte(asset.DefaultNativeAsset.RawDefinitionByte)
53
54                 return alias, definition
55         }
56
57         //local asset and saved external asset
58         if localAsset, err := w.AssetReg.FindByID(nil, &assetID); err == nil {
59                 alias := *localAsset.Alias
60                 definition := []byte(localAsset.RawDefinitionByte)
61                 return alias, definition
62         }
63
64         //external asset
65         if definition := w.getExternalDefinition(&assetID); definition != nil {
66                 return assetID.String(), definition
67         }
68
69         return "", nil
70 }
71
72 // annotateTxs adds account data to transactions
73 func (w *Wallet) annotateTxsAccount(txs []*query.AnnotatedTx) {
74         for i, tx := range txs {
75                 for j, input := range tx.Inputs {
76                         //issue asset tx input SpentOutputID is nil
77                         if input.SpentOutputID == nil {
78                                 continue
79                         }
80                         localAccount, err := w.getAccountFromACP(input.ControlProgram)
81                         if localAccount == nil || err != nil {
82                                 continue
83                         }
84                         txs[i].Inputs[j].AccountAlias = localAccount.Alias
85                         txs[i].Inputs[j].AccountID = localAccount.ID
86                 }
87                 for j, output := range tx.Outputs {
88                         localAccount, err := w.getAccountFromACP(output.ControlProgram)
89                         if localAccount == nil || err != nil {
90                                 continue
91                         }
92                         txs[i].Outputs[j].AccountAlias = localAccount.Alias
93                         txs[i].Outputs[j].AccountID = localAccount.ID
94                 }
95         }
96 }
97
98 func (w *Wallet) getAccountFromACP(program []byte) (*account.Account, error) {
99         var hash [32]byte
100         sha3pool.Sum256(hash[:], program)
101         accountCP, err := w.AccountMgr.GetControlProgram(bc.NewHash(hash))
102         if err != nil {
103                 return nil, err
104         }
105
106         account, err := w.AccountMgr.FindByID(accountCP.AccountID)
107         if err != nil {
108                 return nil, err
109         }
110
111         return account, nil
112 }
113
114 var emptyJSONObject = json.RawMessage(`{}`)
115
116 func (w *Wallet) buildAnnotatedTransaction(orig *types.Tx, b *types.Block, statusFail bool, indexInBlock int) *query.AnnotatedTx {
117         tx := &query.AnnotatedTx{
118                 ID:                     orig.ID,
119                 Timestamp:              b.Timestamp,
120                 BlockID:                b.Hash(),
121                 BlockHeight:            b.Height,
122                 Position:               uint32(indexInBlock),
123                 BlockTransactionsCount: uint32(len(b.Transactions)),
124                 Inputs:                 make([]*query.AnnotatedInput, 0, len(orig.Inputs)),
125                 Outputs:                make([]*query.AnnotatedOutput, 0, len(orig.Outputs)),
126                 StatusFail:             statusFail,
127                 Size:                   orig.SerializedSize,
128         }
129         for i := range orig.Inputs {
130                 tx.Inputs = append(tx.Inputs, w.BuildAnnotatedInput(orig, uint32(i)))
131         }
132         for i := range orig.Outputs {
133                 tx.Outputs = append(tx.Outputs, w.BuildAnnotatedOutput(orig, i))
134         }
135         return tx
136 }
137
138 // BuildAnnotatedInput build the annotated input.
139 func (w *Wallet) BuildAnnotatedInput(tx *types.Tx, i uint32) *query.AnnotatedInput {
140         orig := tx.Inputs[i]
141         in := &query.AnnotatedInput{
142                 AssetDefinition: &emptyJSONObject,
143         }
144         if orig.InputType() != types.CoinbaseInputType {
145                 in.AssetID = orig.AssetID()
146                 in.Amount = orig.Amount()
147         } else {
148                 in.AssetID = *consensus.BTMAssetID
149         }
150
151         id := tx.Tx.InputIDs[i]
152         in.InputID = id
153         e := tx.Entries[id]
154         switch e := e.(type) {
155         case *bc.VetoInput:
156                 in.Type = "veto"
157                 in.ControlProgram = orig.ControlProgram()
158                 in.Address = w.getAddressFromControlProgram(in.ControlProgram, false)
159                 in.SpentOutputID = e.SpentOutputId
160                 arguments := orig.Arguments()
161                 for _, arg := range arguments {
162                         in.WitnessArguments = append(in.WitnessArguments, arg)
163                 }
164
165         case *bc.CrossChainInput:
166                 in.Type = "cross_chain_in"
167                 in.ControlProgram = orig.ControlProgram()
168                 in.Address = w.getAddressFromControlProgram(in.ControlProgram, true)
169                 in.SpentOutputID = e.MainchainOutputId
170                 _, assetDefinition := w.getAliasDefinition(in.AssetID)
171                 in.AssetDefinition = &assetDefinition
172                 arguments := orig.Arguments()
173                 for _, arg := range arguments {
174                         in.WitnessArguments = append(in.WitnessArguments, arg)
175                 }
176
177         case *bc.Spend:
178                 in.Type = "spend"
179                 in.ControlProgram = orig.ControlProgram()
180                 in.Address = w.getAddressFromControlProgram(in.ControlProgram, false)
181                 in.SpentOutputID = e.SpentOutputId
182                 arguments := orig.Arguments()
183                 for _, arg := range arguments {
184                         in.WitnessArguments = append(in.WitnessArguments, arg)
185                 }
186
187         case *bc.Coinbase:
188                 in.Type = "coinbase"
189                 in.Arbitrary = e.Arbitrary
190         }
191         return in
192 }
193
194 func (w *Wallet) getAddressFromControlProgram(prog []byte, isMainchain bool) string {
195         netParams := &consensus.ActiveNetParams
196         if isMainchain {
197                 netParams = consensus.BytomMainNetParams(&consensus.ActiveNetParams)
198         }
199
200         if segwit.IsP2WPKHScript(prog) {
201                 if pubHash, err := segwit.GetHashFromStandardProg(prog); err == nil {
202                         return BuildP2PKHAddress(pubHash, netParams)
203                 }
204         } else if segwit.IsP2WSHScript(prog) {
205                 if scriptHash, err := segwit.GetHashFromStandardProg(prog); err == nil {
206                         return BuildP2SHAddress(scriptHash, netParams)
207                 }
208         }
209
210         return ""
211 }
212
213 func BuildP2PKHAddress(pubHash []byte, netParams *consensus.Params) string {
214         address, err := common.NewAddressWitnessPubKeyHash(pubHash, netParams)
215         if err != nil {
216                 return ""
217         }
218
219         return address.EncodeAddress()
220 }
221
222 func BuildP2SHAddress(scriptHash []byte, netParams *consensus.Params) string {
223         address, err := common.NewAddressWitnessScriptHash(scriptHash, netParams)
224         if err != nil {
225                 return ""
226         }
227
228         return address.EncodeAddress()
229 }
230
231 // BuildAnnotatedOutput build the annotated output.
232 func (w *Wallet) BuildAnnotatedOutput(tx *types.Tx, idx int) *query.AnnotatedOutput {
233         orig := tx.Outputs[idx]
234         outid := tx.OutputID(idx)
235         out := &query.AnnotatedOutput{
236                 OutputID:        *outid,
237                 Position:        idx,
238                 AssetID:         *orig.AssetAmount().AssetId,
239                 AssetDefinition: &emptyJSONObject,
240                 Amount:          orig.AssetAmount().Amount,
241                 ControlProgram:  orig.ControlProgram(),
242         }
243
244         var isMainchainAddress bool
245         switch e := tx.Entries[*outid].(type) {
246         case *bc.IntraChainOutput:
247                 out.Type = "control"
248                 isMainchainAddress = false
249
250         case *bc.CrossChainOutput:
251                 out.Type = "cross_chain_out"
252                 isMainchainAddress = true
253
254         case *bc.VoteOutput:
255                 out.Type = "vote"
256                 out.Vote = e.Vote
257                 isMainchainAddress = false
258         }
259
260         out.Address = w.getAddressFromControlProgram(orig.ControlProgram(), isMainchainAddress)
261         return out
262 }