OSDN Git Service

update
[bytom/vapor.git] / wallet / utxo.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/consensus"
10         "github.com/vapor/consensus/segwit"
11         "github.com/vapor/crypto/sha3pool"
12         "github.com/vapor/database"
13         "github.com/vapor/errors"
14         "github.com/vapor/protocol/bc"
15         "github.com/vapor/protocol/bc/types"
16 )
17
18 // GetAccountUtxos return all account unspent outputs
19 func (w *Wallet) GetAccountUtxos(accountID string, id string, unconfirmed, isSmartContract bool, vote bool) []*account.UTXO {
20         prefix := database.UTXOPrefix
21         if isSmartContract {
22                 prefix = database.SUTXOPrefix
23         }
24
25         accountUtxos := []*account.UTXO{}
26         if unconfirmed {
27                 accountUtxos = w.AccountMgr.ListUnconfirmedUtxo(accountID, isSmartContract)
28         }
29
30         rawConfirmedUTXOs := w.store.GetAccountUTXOs(string(prefix) + id)
31         confirmedUTXOs := []*account.UTXO{}
32         for _, rawConfirmedUTXO := range rawConfirmedUTXOs {
33                 confirmedUTXO := new(account.UTXO)
34                 if err := json.Unmarshal(rawConfirmedUTXO, confirmedUTXO); err != nil {
35                         log.WithFields(log.Fields{"module": logModule, "err": err}).Warn("GetAccountUTXOs fail on unmarshal utxo")
36                         continue
37                 }
38                 confirmedUTXOs = append(confirmedUTXOs, confirmedUTXO)
39         }
40         accountUtxos = append(accountUtxos, confirmedUTXOs...)
41
42         newAccountUtxos := []*account.UTXO{}
43         for _, accountUtxo := range accountUtxos {
44                 if vote && accountUtxo.Vote == nil {
45                         continue
46                 }
47
48                 if accountID == accountUtxo.AccountID || accountID == "" {
49                         newAccountUtxos = append(newAccountUtxos, accountUtxo)
50                 }
51         }
52         return newAccountUtxos
53 }
54
55 func (w *Wallet) attachUtxos(b *types.Block, txStatus *bc.TransactionStatus) {
56         for txIndex, tx := range b.Transactions {
57                 statusFail, err := txStatus.GetStatus(txIndex)
58                 if err != nil {
59                         log.WithFields(log.Fields{"module": logModule, "err": err}).Error("attachUtxos fail on get tx status")
60                         continue
61                 }
62
63                 //hand update the transaction input utxos
64                 inputUtxos := txInToUtxos(tx, statusFail)
65                 for _, inputUtxo := range inputUtxos {
66                         if segwit.IsP2WScript(inputUtxo.ControlProgram) {
67                                 w.store.DeleteStardardUTXO(inputUtxo.OutputID)
68                         } else {
69                                 w.store.DeleteContractUTXO(inputUtxo.OutputID)
70                         }
71                 }
72
73                 //hand update the transaction output utxos
74                 outputUtxos := txOutToUtxos(tx, statusFail, b.Height)
75                 utxos := w.filterAccountUtxo(outputUtxos)
76                 if err := w.saveUtxos(utxos); err != nil {
77                         log.WithFields(log.Fields{"module": logModule, "err": err}).Error("attachUtxos fail on saveUtxos")
78                 }
79         }
80 }
81
82 func (w *Wallet) detachUtxos(b *types.Block, txStatus *bc.TransactionStatus) {
83         for txIndex := len(b.Transactions) - 1; txIndex >= 0; txIndex-- {
84                 tx := b.Transactions[txIndex]
85                 for j := range tx.Outputs {
86                         code := []byte{}
87                         switch resOut := tx.Entries[*tx.ResultIds[j]].(type) {
88                         case *bc.IntraChainOutput:
89                                 code = resOut.ControlProgram.Code
90                         case *bc.VoteOutput:
91                                 code = resOut.ControlProgram.Code
92                         default:
93                                 continue
94                         }
95
96                         if segwit.IsP2WScript(code) {
97                                 w.store.DeleteStardardUTXO(*tx.ResultIds[j])
98                         } else {
99                                 w.store.DeleteContractUTXO(*tx.ResultIds[j])
100                         }
101                 }
102
103                 statusFail, err := txStatus.GetStatus(txIndex)
104                 if err != nil {
105                         log.WithFields(log.Fields{"module": logModule, "err": err}).Error("detachUtxos fail on get tx status")
106                         continue
107                 }
108
109                 inputUtxos := txInToUtxos(tx, statusFail)
110                 utxos := w.filterAccountUtxo(inputUtxos)
111                 if err := w.saveUtxos(utxos); err != nil {
112                         log.WithFields(log.Fields{"module": logModule, "err": err}).Error("detachUtxos fail on batchSaveUtxos")
113                         return
114                 }
115         }
116 }
117
118 func (w *Wallet) filterAccountUtxo(utxos []*account.UTXO) []*account.UTXO {
119         outsByScript := make(map[string][]*account.UTXO, len(utxos))
120         for _, utxo := range utxos {
121                 scriptStr := string(utxo.ControlProgram)
122                 outsByScript[scriptStr] = append(outsByScript[scriptStr], utxo)
123         }
124
125         result := make([]*account.UTXO, 0, len(utxos))
126         for s := range outsByScript {
127                 if !segwit.IsP2WScript([]byte(s)) {
128                         for _, utxo := range outsByScript[s] {
129                                 result = append(result, utxo)
130                         }
131                         continue
132                 }
133
134                 var hash [32]byte
135                 sha3pool.Sum256(hash[:], []byte(s))
136                 data := w.store.GetRawProgram(hash)
137                 if data == nil {
138                         continue
139                 }
140
141                 cp := &account.CtrlProgram{}
142                 if err := json.Unmarshal(data, cp); err != nil {
143                         log.WithFields(log.Fields{"module": logModule, "err": err}).Error("filterAccountUtxo fail on unmarshal control program")
144                         continue
145                 }
146
147                 for _, utxo := range outsByScript[s] {
148                         utxo.AccountID = cp.AccountID
149                         utxo.Address = cp.Address
150                         utxo.ControlProgramIndex = cp.KeyIndex
151                         utxo.Change = cp.Change
152                         result = append(result, utxo)
153                 }
154         }
155         return result
156 }
157
158 func (w *Wallet) saveUtxos(utxos []*account.UTXO) error {
159         for _, utxo := range utxos {
160                 data, err := json.Marshal(utxo)
161                 if err != nil {
162                         return errors.Wrap(err, "failed marshal accountutxo")
163                 }
164
165                 if segwit.IsP2WScript(utxo.ControlProgram) {
166                         w.store.SetStandardUTXO(utxo.OutputID, data)
167                 } else {
168                         w.store.SetContractUTXO(utxo.OutputID, data)
169                 }
170         }
171         return nil
172 }
173
174 func txInToUtxos(tx *types.Tx, statusFail bool) []*account.UTXO {
175         utxos := []*account.UTXO{}
176         for _, inpID := range tx.Tx.InputIDs {
177
178                 e, err := tx.Entry(inpID)
179                 if err != nil {
180                         continue
181                 }
182                 utxo := &account.UTXO{}
183                 switch inp := e.(type) {
184                 case *bc.Spend:
185                         resOut, err := tx.IntraChainOutput(*inp.SpentOutputId)
186                         if err != nil {
187                                 log.WithFields(log.Fields{"module": logModule, "err": err}).Error("txInToUtxos fail on get resOut for spedn")
188                                 continue
189                         }
190                         if statusFail && *resOut.Source.Value.AssetId != *consensus.BTMAssetID {
191                                 continue
192                         }
193                         utxo = &account.UTXO{
194                                 OutputID:       *inp.SpentOutputId,
195                                 AssetID:        *resOut.Source.Value.AssetId,
196                                 Amount:         resOut.Source.Value.Amount,
197                                 ControlProgram: resOut.ControlProgram.Code,
198                                 SourceID:       *resOut.Source.Ref,
199                                 SourcePos:      resOut.Source.Position,
200                         }
201                 case *bc.VetoInput:
202                         resOut, err := tx.VoteOutput(*inp.SpentOutputId)
203                         if err != nil {
204                                 log.WithFields(log.Fields{"module": logModule, "err": err}).Error("txInToUtxos fail on get resOut for vetoInput")
205                                 continue
206                         }
207                         if statusFail && *resOut.Source.Value.AssetId != *consensus.BTMAssetID {
208                                 continue
209                         }
210                         utxo = &account.UTXO{
211                                 OutputID:       *inp.SpentOutputId,
212                                 AssetID:        *resOut.Source.Value.AssetId,
213                                 Amount:         resOut.Source.Value.Amount,
214                                 ControlProgram: resOut.ControlProgram.Code,
215                                 SourceID:       *resOut.Source.Ref,
216                                 SourcePos:      resOut.Source.Position,
217                                 Vote:           resOut.Vote,
218                         }
219                 default:
220                         continue
221                 }
222                 utxos = append(utxos, utxo)
223         }
224         return utxos
225 }
226
227 func txOutToUtxos(tx *types.Tx, statusFail bool, blockHeight uint64) []*account.UTXO {
228         validHeight := uint64(0)
229         if tx.Inputs[0].InputType() == types.CoinbaseInputType {
230                 validHeight = blockHeight + consensus.CoinbasePendingBlockNumber
231         }
232
233         utxos := []*account.UTXO{}
234         for i, out := range tx.Outputs {
235                 entryOutput, err := tx.Entry(*tx.ResultIds[i])
236                 if err != nil {
237                         log.WithFields(log.Fields{"module": logModule, "err": err}).Error("txOutToUtxos fail on get entryOutput")
238                         continue
239                 }
240
241                 utxo := &account.UTXO{}
242                 switch bcOut := entryOutput.(type) {
243                 case *bc.IntraChainOutput:
244                         if statusFail && *out.AssetAmount().AssetId != *consensus.BTMAssetID {
245                                 continue
246                         }
247                         utxo = &account.UTXO{
248                                 OutputID:       *tx.OutputID(i),
249                                 AssetID:        *out.AssetAmount().AssetId,
250                                 Amount:         out.AssetAmount().Amount,
251                                 ControlProgram: out.ControlProgram(),
252                                 SourceID:       *bcOut.Source.Ref,
253                                 SourcePos:      bcOut.Source.Position,
254                                 ValidHeight:    validHeight,
255                         }
256
257                 case *bc.VoteOutput:
258                         if statusFail && *out.AssetAmount().AssetId != *consensus.BTMAssetID {
259                                 continue
260                         }
261
262                         voteValidHeight := blockHeight + consensus.VotePendingBlockNumber
263                         if validHeight < voteValidHeight {
264                                 validHeight = voteValidHeight
265                         }
266
267                         utxo = &account.UTXO{
268                                 OutputID:       *tx.OutputID(i),
269                                 AssetID:        *out.AssetAmount().AssetId,
270                                 Amount:         out.AssetAmount().Amount,
271                                 ControlProgram: out.ControlProgram(),
272                                 SourceID:       *bcOut.Source.Ref,
273                                 SourcePos:      bcOut.Source.Position,
274                                 ValidHeight:    validHeight,
275                                 Vote:           bcOut.Vote,
276                         }
277
278                 default:
279                         log.WithFields(log.Fields{"module": logModule}).Warn("txOutToUtxos fail on get bcOut")
280                         continue
281                 }
282
283                 utxos = append(utxos, utxo)
284         }
285         return utxos
286 }