OSDN Git Service

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