OSDN Git Service

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