OSDN Git Service

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