OSDN Git Service

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