6 log "github.com/sirupsen/logrus"
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/database"
13 "github.com/vapor/errors"
14 "github.com/vapor/protocol/bc"
15 "github.com/vapor/protocol/bc/types"
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 := database.UTXOPrefix
22 prefix = database.SUTXOPrefix
25 accountUtxos := []*account.UTXO{}
27 accountUtxos = w.AccountMgr.ListUnconfirmedUtxo(accountID, isSmartContract)
30 rawConfirmedUTXOs := w.store.GetAccountUTXOs(string(prefix) + id)
31 confirmedUTXOs := []*account.UTXO{}
32 for _, rawConfirmedUTXO := range rawConfirmedUTXOs {
33 confirmedUTXO := new(account.UTXO)
34 if err := json.Unmarshal(rawConfirmedUTXO, confirmedUTXO); err != nil {
35 log.WithFields(log.Fields{"module": logModule, "err": err}).Warn("GetAccountUTXOs fail on unmarshal utxo")
38 confirmedUTXOs = append(confirmedUTXOs, confirmedUTXO)
40 accountUtxos = append(accountUtxos, confirmedUTXOs...)
42 newAccountUtxos := []*account.UTXO{}
43 for _, accountUtxo := range accountUtxos {
44 if vote && accountUtxo.Vote == nil {
48 if accountID == accountUtxo.AccountID || accountID == "" {
49 newAccountUtxos = append(newAccountUtxos, accountUtxo)
52 return newAccountUtxos
55 func (w *Wallet) attachUtxos(b *types.Block, txStatus *bc.TransactionStatus) {
56 for txIndex, tx := range b.Transactions {
57 statusFail, err := txStatus.GetStatus(txIndex)
59 log.WithFields(log.Fields{"module": logModule, "err": err}).Error("attachUtxos fail on get tx status")
63 //hand update the transaction input utxos
64 inputUtxos := txInToUtxos(tx, statusFail)
65 for _, inputUtxo := range inputUtxos {
66 if segwit.IsP2WScript(inputUtxo.ControlProgram) {
67 w.store.DeleteStardardUTXO(inputUtxo.OutputID)
69 w.store.DeleteContractUTXO(inputUtxo.OutputID)
73 //hand update the transaction output utxos
74 outputUtxos := txOutToUtxos(tx, statusFail, b.Height)
75 utxos := w.filterAccountUtxo(outputUtxos)
76 if err := w.saveUtxos(utxos); err != nil {
77 log.WithFields(log.Fields{"module": logModule, "err": err}).Error("attachUtxos fail on saveUtxos")
82 func (w *Wallet) detachUtxos(b *types.Block, txStatus *bc.TransactionStatus) {
83 for txIndex := len(b.Transactions) - 1; txIndex >= 0; txIndex-- {
84 tx := b.Transactions[txIndex]
85 for j := range tx.Outputs {
87 switch resOut := tx.Entries[*tx.ResultIds[j]].(type) {
88 case *bc.IntraChainOutput:
89 code = resOut.ControlProgram.Code
91 code = resOut.ControlProgram.Code
96 if segwit.IsP2WScript(code) {
97 w.store.DeleteStardardUTXO(*tx.ResultIds[j])
99 w.store.DeleteContractUTXO(*tx.ResultIds[j])
103 statusFail, err := txStatus.GetStatus(txIndex)
105 log.WithFields(log.Fields{"module": logModule, "err": err}).Error("detachUtxos fail on get tx status")
109 inputUtxos := txInToUtxos(tx, statusFail)
110 utxos := w.filterAccountUtxo(inputUtxos)
111 if err := w.saveUtxos(utxos); err != nil {
112 log.WithFields(log.Fields{"module": logModule, "err": err}).Error("detachUtxos fail on batchSaveUtxos")
118 func (w *Wallet) filterAccountUtxo(utxos []*account.UTXO) []*account.UTXO {
119 outsByScript := make(map[string][]*account.UTXO, len(utxos))
120 for _, utxo := range utxos {
121 scriptStr := string(utxo.ControlProgram)
122 outsByScript[scriptStr] = append(outsByScript[scriptStr], utxo)
125 result := make([]*account.UTXO, 0, len(utxos))
126 for s := range outsByScript {
127 if !segwit.IsP2WScript([]byte(s)) {
128 for _, utxo := range outsByScript[s] {
129 result = append(result, utxo)
135 sha3pool.Sum256(hash[:], []byte(s))
136 data := w.store.GetRawProgram(hash)
141 cp := &account.CtrlProgram{}
142 if err := json.Unmarshal(data, cp); err != nil {
143 log.WithFields(log.Fields{"module": logModule, "err": err}).Error("filterAccountUtxo fail on unmarshal control program")
147 for _, utxo := range outsByScript[s] {
148 utxo.AccountID = cp.AccountID
149 utxo.Address = cp.Address
150 utxo.ControlProgramIndex = cp.KeyIndex
151 utxo.Change = cp.Change
152 result = append(result, utxo)
158 func (w *Wallet) saveUtxos(utxos []*account.UTXO) error {
159 for _, utxo := range utxos {
160 data, err := json.Marshal(utxo)
162 return errors.Wrap(err, "failed marshal accountutxo")
165 if segwit.IsP2WScript(utxo.ControlProgram) {
166 w.store.SetStandardUTXO(utxo.OutputID, data)
168 w.store.SetContractUTXO(utxo.OutputID, data)
174 func txInToUtxos(tx *types.Tx, statusFail bool) []*account.UTXO {
175 utxos := []*account.UTXO{}
176 for _, inpID := range tx.Tx.InputIDs {
178 e, err := tx.Entry(inpID)
182 utxo := &account.UTXO{}
183 switch inp := e.(type) {
185 resOut, err := tx.IntraChainOutput(*inp.SpentOutputId)
187 log.WithFields(log.Fields{"module": logModule, "err": err}).Error("txInToUtxos fail on get resOut for spedn")
190 if statusFail && *resOut.Source.Value.AssetId != *consensus.BTMAssetID {
193 utxo = &account.UTXO{
194 OutputID: *inp.SpentOutputId,
195 AssetID: *resOut.Source.Value.AssetId,
196 Amount: resOut.Source.Value.Amount,
197 ControlProgram: resOut.ControlProgram.Code,
198 SourceID: *resOut.Source.Ref,
199 SourcePos: resOut.Source.Position,
202 resOut, err := tx.VoteOutput(*inp.SpentOutputId)
204 log.WithFields(log.Fields{"module": logModule, "err": err}).Error("txInToUtxos fail on get resOut for vetoInput")
207 if statusFail && *resOut.Source.Value.AssetId != *consensus.BTMAssetID {
210 utxo = &account.UTXO{
211 OutputID: *inp.SpentOutputId,
212 AssetID: *resOut.Source.Value.AssetId,
213 Amount: resOut.Source.Value.Amount,
214 ControlProgram: resOut.ControlProgram.Code,
215 SourceID: *resOut.Source.Ref,
216 SourcePos: resOut.Source.Position,
222 utxos = append(utxos, utxo)
227 func txOutToUtxos(tx *types.Tx, statusFail bool, blockHeight uint64) []*account.UTXO {
228 validHeight := uint64(0)
229 if tx.Inputs[0].InputType() == types.CoinbaseInputType {
230 validHeight = blockHeight + consensus.CoinbasePendingBlockNumber
233 utxos := []*account.UTXO{}
234 for i, out := range tx.Outputs {
235 entryOutput, err := tx.Entry(*tx.ResultIds[i])
237 log.WithFields(log.Fields{"module": logModule, "err": err}).Error("txOutToUtxos fail on get entryOutput")
241 utxo := &account.UTXO{}
242 switch bcOut := entryOutput.(type) {
243 case *bc.IntraChainOutput:
244 if statusFail && *out.AssetAmount().AssetId != *consensus.BTMAssetID {
247 utxo = &account.UTXO{
248 OutputID: *tx.OutputID(i),
249 AssetID: *out.AssetAmount().AssetId,
250 Amount: out.AssetAmount().Amount,
251 ControlProgram: out.ControlProgram(),
252 SourceID: *bcOut.Source.Ref,
253 SourcePos: bcOut.Source.Position,
254 ValidHeight: validHeight,
258 if statusFail && *out.AssetAmount().AssetId != *consensus.BTMAssetID {
262 voteValidHeight := blockHeight + consensus.VotePendingBlockNumber
263 if validHeight < voteValidHeight {
264 validHeight = voteValidHeight
267 utxo = &account.UTXO{
268 OutputID: *tx.OutputID(i),
269 AssetID: *out.AssetAmount().AssetId,
270 Amount: out.AssetAmount().Amount,
271 ControlProgram: out.ControlProgram(),
272 SourceID: *bcOut.Source.Ref,
273 SourcePos: bcOut.Source.Position,
274 ValidHeight: validHeight,
279 log.WithFields(log.Fields{"module": logModule}).Warn("txOutToUtxos fail on get bcOut")
283 utxos = append(utxos, utxo)