OSDN Git Service

writer close
[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/vapor/account"
9         "github.com/vapor/asset"
10         "github.com/vapor/blockchain/query"
11         "github.com/vapor/common"
12         "github.com/vapor/consensus"
13         "github.com/vapor/consensus/segwit"
14         "github.com/vapor/crypto/sha3pool"
15         "github.com/vapor/protocol/bc"
16         "github.com/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                 arguments := orig.Arguments()
171                 for _, arg := range arguments {
172                         in.WitnessArguments = append(in.WitnessArguments, arg)
173                 }
174
175         case *bc.Spend:
176                 in.Type = "spend"
177                 in.ControlProgram = orig.ControlProgram()
178                 in.Address = w.getAddressFromControlProgram(in.ControlProgram, false)
179                 in.SpentOutputID = e.SpentOutputId
180                 arguments := orig.Arguments()
181                 for _, arg := range arguments {
182                         in.WitnessArguments = append(in.WitnessArguments, arg)
183                 }
184
185         case *bc.Coinbase:
186                 in.Type = "coinbase"
187                 in.Arbitrary = e.Arbitrary
188         }
189         return in
190 }
191
192 func (w *Wallet) getAddressFromControlProgram(prog []byte, isMainchain bool) string {
193         netParams := &consensus.ActiveNetParams
194         if isMainchain {
195                 netParams = consensus.BytomMainNetParams(&consensus.ActiveNetParams)
196         }
197
198         if segwit.IsP2WPKHScript(prog) {
199                 if pubHash, err := segwit.GetHashFromStandardProg(prog); err == nil {
200                         return BuildP2PKHAddress(pubHash, netParams)
201                 }
202         } else if segwit.IsP2WSHScript(prog) {
203                 if scriptHash, err := segwit.GetHashFromStandardProg(prog); err == nil {
204                         return BuildP2SHAddress(scriptHash, netParams)
205                 }
206         }
207
208         return ""
209 }
210
211 func BuildP2PKHAddress(pubHash []byte, netParams *consensus.Params) string {
212         address, err := common.NewAddressWitnessPubKeyHash(pubHash, netParams)
213         if err != nil {
214                 return ""
215         }
216
217         return address.EncodeAddress()
218 }
219
220 func BuildP2SHAddress(scriptHash []byte, netParams *consensus.Params) string {
221         address, err := common.NewAddressWitnessScriptHash(scriptHash, netParams)
222         if err != nil {
223                 return ""
224         }
225
226         return address.EncodeAddress()
227 }
228
229 // BuildAnnotatedOutput build the annotated output.
230 func (w *Wallet) BuildAnnotatedOutput(tx *types.Tx, idx int) *query.AnnotatedOutput {
231         orig := tx.Outputs[idx]
232         outid := tx.OutputID(idx)
233         out := &query.AnnotatedOutput{
234                 OutputID:        *outid,
235                 Position:        idx,
236                 AssetID:         *orig.AssetAmount().AssetId,
237                 AssetDefinition: &emptyJSONObject,
238                 Amount:          orig.AssetAmount().Amount,
239                 ControlProgram:  orig.ControlProgram(),
240         }
241
242         var isMainchainAddress bool
243         switch e := tx.Entries[*outid].(type) {
244         case *bc.IntraChainOutput:
245                 out.Type = "control"
246                 isMainchainAddress = false
247
248         case *bc.CrossChainOutput:
249                 out.Type = "cross_chain_out"
250                 isMainchainAddress = true
251
252         case *bc.VoteOutput:
253                 out.Type = "vote"
254                 out.Vote = e.Vote
255                 isMainchainAddress = false
256         }
257
258         out.Address = w.getAddressFromControlProgram(orig.ControlProgram(), isMainchainAddress)
259         return out
260 }