OSDN Git Service

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