OSDN Git Service

Merge pull request #24 from Bytom/dev_consensus_engine_and_dpos
[bytom/vapor.git] / wallet / utxo.go
1 package wallet
2
3 import (
4         "encoding/json"
5         "fmt"
6
7         log "github.com/sirupsen/logrus"
8         "github.com/tendermint/tmlibs/db"
9
10         "github.com/vapor/account"
11         "github.com/vapor/consensus"
12         "github.com/vapor/consensus/segwit"
13         "github.com/vapor/crypto/sha3pool"
14         "github.com/vapor/errors"
15         "github.com/vapor/protocol/bc"
16         "github.com/vapor/protocol/bc/types"
17         "github.com/vapor/protocol/vm/vmutil"
18 )
19
20 // GetAccountUtxos return all account unspent outputs
21 func (w *Wallet) GetAccountUtxos(accountID string, id string, unconfirmed, isSmartContract bool) []*account.UTXO {
22         prefix := account.UTXOPreFix
23         if isSmartContract {
24                 prefix = account.SUTXOPrefix
25         }
26
27         accountUtxos := []*account.UTXO{}
28         if unconfirmed {
29                 accountUtxos = w.AccountMgr.ListUnconfirmedUtxo(accountID, isSmartContract)
30         }
31
32         accountUtxoIter := w.DB.IteratorPrefix([]byte(prefix + id))
33         defer accountUtxoIter.Release()
34
35         for accountUtxoIter.Next() {
36                 accountUtxo := &account.UTXO{}
37                 if err := json.Unmarshal(accountUtxoIter.Value(), accountUtxo); err != nil {
38                         log.WithField("err", err).Warn("GetAccountUtxos fail on unmarshal utxo")
39                         continue
40                 }
41
42                 if accountID == accountUtxo.AccountID || accountID == "" {
43                         accountUtxos = append(accountUtxos, accountUtxo)
44                 }
45         }
46         return accountUtxos
47 }
48
49 func (w *Wallet) attachUtxos(batch db.Batch, b *types.Block, txStatus *bc.TransactionStatus) {
50         /*
51                 a := bc.Hash{}
52                 a.UnmarshalText([]byte("bef9c83e5cadc6dbb80b81387f3e3c3fadd76b917e5337f5442b9ef071c06526"))
53                 batch.Delete(account.StandardUTXOKey(a))
54                 a.UnmarshalText([]byte("1a5e2141a12823dabf343b5ace0a181a3d018e24f3dc6e7c3704b66fc040ca7b"))
55                 batch.Delete(account.StandardUTXOKey(a))
56                 a.UnmarshalText([]byte("4647b1e0893f56438f9bbde6134840f1595da799cfc6ece77c4d9aabdf9cfe50"))
57                 batch.Delete(account.StandardUTXOKey(a))
58                 a.UnmarshalText([]byte("928094d14b00aaf674ee291bbfb0c843a4dab53984f6235b998338fe0fa2d688"))
59                 batch.Delete(account.StandardUTXOKey(a))
60                 a.UnmarshalText([]byte("e20aee90018f8b6483d5590786fcf495bccfa7f1a3a5a5a9106c4143f71d49a4"))
61                 batch.Delete(account.StandardUTXOKey(a))
62                 a.UnmarshalText([]byte("2cb18fe2dd3eb8dcf2df43aa6650851dd0b6de291bfffd151a36703c92f8e864"))
63                 batch.Delete(account.StandardUTXOKey(a))
64                 a.UnmarshalText([]byte("48d71e6da11de69983b0cc79787f0f9422a144c94e687dfec11b4a57fdca2832"))
65                 batch.Delete(account.StandardUTXOKey(a))
66         */
67         for txIndex, tx := range b.Transactions {
68                 statusFail, err := txStatus.GetStatus(txIndex)
69                 if err != nil {
70                         log.WithField("err", err).Error("attachUtxos fail on get tx status")
71                         continue
72                 }
73
74                 //hand update the transaction input utxos
75                 inputUtxos := txInToUtxos(tx, statusFail)
76                 for _, inputUtxo := range inputUtxos {
77                         if segwit.IsP2WScript(inputUtxo.ControlProgram) {
78                                 batch.Delete(account.StandardUTXOKey(inputUtxo.OutputID))
79                         } else {
80                                 batch.Delete(account.ContractUTXOKey(inputUtxo.OutputID))
81                         }
82                 }
83
84                 //hand update the transaction output utxos
85                 validHeight := uint64(0)
86                 if txIndex == 0 {
87                         validHeight = b.Height + consensus.CoinbasePendingBlockNumber
88                 }
89                 outputUtxos := txOutToUtxos(tx, statusFail, validHeight)
90                 utxos := w.filterAccountUtxo(outputUtxos)
91                 if err := batchSaveUtxos(utxos, batch); err != nil {
92                         log.WithField("err", err).Error("attachUtxos fail on batchSaveUtxos")
93                 }
94         }
95 }
96
97 func (w *Wallet) detachUtxos(batch db.Batch, b *types.Block, txStatus *bc.TransactionStatus) {
98         for txIndex := len(b.Transactions) - 1; txIndex >= 0; txIndex-- {
99                 tx := b.Transactions[txIndex]
100                 for j := range tx.Outputs {
101                         resOut, err := tx.Output(*tx.ResultIds[j])
102                         if err != nil {
103                                 continue
104                         }
105
106                         if segwit.IsP2WScript(resOut.ControlProgram.Code) {
107                                 batch.Delete(account.StandardUTXOKey(*tx.ResultIds[j]))
108                         } else {
109                                 batch.Delete(account.ContractUTXOKey(*tx.ResultIds[j]))
110                         }
111                 }
112
113                 statusFail, err := txStatus.GetStatus(txIndex)
114                 if err != nil {
115                         log.WithField("err", err).Error("detachUtxos fail on get tx status")
116                         continue
117                 }
118
119                 inputUtxos := txInToUtxos(tx, statusFail)
120                 utxos := w.filterAccountUtxo(inputUtxos)
121                 if err := batchSaveUtxos(utxos, batch); err != nil {
122                         log.WithField("err", err).Error("detachUtxos fail on batchSaveUtxos")
123                         return
124                 }
125         }
126 }
127
128 func (w *Wallet) filterAccountUtxo(utxos []*account.UTXO) []*account.UTXO {
129         outsByScript := make(map[string][]*account.UTXO, len(utxos))
130         var program []byte
131         if w.dposAddress != nil {
132                 redeemContract := w.dposAddress.ScriptAddress()
133                 program, _ = vmutil.P2WPKHProgram(redeemContract)
134         }
135
136         isDposAddress := false
137         for _, utxo := range utxos {
138                 scriptStr := string(utxo.ControlProgram)
139                 outsByScript[scriptStr] = append(outsByScript[scriptStr], utxo)
140         }
141
142         result := make([]*account.UTXO, 0, len(utxos))
143         for s := range outsByScript {
144                 if !segwit.IsP2WScript([]byte(s)) {
145                         for _, utxo := range outsByScript[s] {
146                                 result = append(result, utxo)
147                         }
148                         continue
149                 }
150
151                 var hash [32]byte
152                 sha3pool.Sum256(hash[:], []byte(s))
153                 data := w.DB.Get(account.ContractKey(hash))
154                 if data == nil {
155                         if s == string(program) {
156                                 isDposAddress = true
157                         } else {
158                                 continue
159                         }
160
161                 }
162
163                 if !isDposAddress {
164                         cp := &account.CtrlProgram{}
165                         if err := json.Unmarshal(data, cp); err != nil {
166                                 log.WithField("err", err).Error("filterAccountUtxo fail on unmarshal control program")
167                                 continue
168                         }
169                         for _, utxo := range outsByScript[s] {
170                                 utxo.AccountID = cp.AccountID
171                                 utxo.Address = cp.Address
172                                 utxo.ControlProgramIndex = cp.KeyIndex
173                                 utxo.Change = cp.Change
174                                 result = append(result, utxo)
175                         }
176                 } else {
177                         for _, utxo := range outsByScript[s] {
178                                 utxo.Address = w.dposAddress.EncodeAddress()
179                                 result = append(result, utxo)
180                         }
181                         isDposAddress = false
182                 }
183         }
184         return result
185 }
186
187 func batchSaveUtxos(utxos []*account.UTXO, batch db.Batch) error {
188         for _, utxo := range utxos {
189                 data, err := json.Marshal(utxo)
190                 if err != nil {
191                         return errors.Wrap(err, "failed marshal accountutxo")
192                 }
193
194                 if segwit.IsP2WScript(utxo.ControlProgram) {
195                         batch.Set(account.StandardUTXOKey(utxo.OutputID), data)
196                 } else {
197                         batch.Set(account.ContractUTXOKey(utxo.OutputID), data)
198                 }
199         }
200         return nil
201 }
202
203 func txInToUtxos(tx *types.Tx, statusFail bool) []*account.UTXO {
204         utxos := []*account.UTXO{}
205         for _, inpID := range tx.Tx.InputIDs {
206                 sp, err := tx.Spend(inpID)
207                 if err != nil {
208                         continue
209                 }
210                 resOut, err := tx.Output(*sp.SpentOutputId)
211                 if err != nil {
212                         log.WithField("err", err).Error("txInToUtxos fail on get resOut")
213                         continue
214                 }
215
216                 if statusFail && *resOut.Source.Value.AssetId != *consensus.BTMAssetID {
217                         fmt.Println("statusFail:", statusFail)
218                         continue
219                 }
220
221                 utxos = append(utxos, &account.UTXO{
222                         OutputID:       *sp.SpentOutputId,
223                         AssetID:        *resOut.Source.Value.AssetId,
224                         Amount:         resOut.Source.Value.Amount,
225                         ControlProgram: resOut.ControlProgram.Code,
226                         SourceID:       *resOut.Source.Ref,
227                         SourcePos:      resOut.Source.Position,
228                 })
229         }
230         return utxos
231 }
232
233 func txOutToUtxos(tx *types.Tx, statusFail bool, vaildHeight uint64) []*account.UTXO {
234         utxos := []*account.UTXO{}
235         for i, out := range tx.Outputs {
236                 bcOut, err := tx.Output(*tx.ResultIds[i])
237                 if err != nil {
238                         continue
239                 }
240
241                 if statusFail && *out.AssetAmount.AssetId != *consensus.BTMAssetID {
242                         continue
243                 }
244
245                 utxos = append(utxos, &account.UTXO{
246                         OutputID:       *tx.OutputID(i),
247                         AssetID:        *out.AssetAmount.AssetId,
248                         Amount:         out.Amount,
249                         ControlProgram: out.ControlProgram,
250                         SourceID:       *bcOut.Source.Ref,
251                         SourcePos:      bcOut.Source.Position,
252                         ValidHeight:    vaildHeight,
253                 })
254         }
255         return utxos
256 }