OSDN Git Service

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