4 "github.com/bytom/vapor/consensus"
5 "github.com/bytom/vapor/database/storage"
6 "github.com/bytom/vapor/errors"
7 "github.com/bytom/vapor/protocol/bc"
8 "github.com/bytom/vapor/toolbar/measure"
11 // UtxoViewpoint represents a view into the set of unspent transaction outputs
12 type UtxoViewpoint struct {
13 Entries map[bc.Hash]*storage.UtxoEntry
16 // NewUtxoViewpoint returns a new empty unspent transaction output view.
17 func NewUtxoViewpoint() *UtxoViewpoint {
18 return &UtxoViewpoint{
19 Entries: make(map[bc.Hash]*storage.UtxoEntry),
23 func (view *UtxoViewpoint) ApplyTransaction(block *bc.Block, tx *bc.Tx, statusFail bool) error {
24 if err := view.applyCrossChainUtxo(block, tx); err != nil {
28 if err := view.applySpendUtxo(block, tx, statusFail); err != nil {
32 return view.applyOutputUtxo(block, tx, statusFail)
35 func (view *UtxoViewpoint) ApplyBlock(block *bc.Block, txStatus *bc.TransactionStatus) error {
39 for i, tx := range block.Transactions {
40 statusFail, err := txStatus.GetStatus(i)
45 if err := view.ApplyTransaction(block, tx, statusFail); err != nil {
52 func (view *UtxoViewpoint) CanSpend(hash *bc.Hash) bool {
53 entry := view.Entries[*hash]
54 return entry != nil && !entry.Spent
57 func (view *UtxoViewpoint) DetachTransaction(tx *bc.Tx, statusFail bool) error {
58 if err := view.detachCrossChainUtxo(tx); err != nil {
62 if err := view.detachSpendUtxo(tx, statusFail); err != nil {
66 return view.detachOutputUtxo(tx, statusFail)
69 func (view *UtxoViewpoint) DetachBlock(block *bc.Block, txStatus *bc.TransactionStatus) error {
70 for i := len(block.Transactions) - 1; i >= 0; i-- {
71 statusFail, err := txStatus.GetStatus(i)
76 if err := view.DetachTransaction(block.Transactions[i], statusFail); err != nil {
83 func (view *UtxoViewpoint) HasUtxo(hash *bc.Hash) bool {
84 _, ok := view.Entries[*hash]
88 func (view *UtxoViewpoint) applyCrossChainUtxo(block *bc.Block, tx *bc.Tx) error {
89 for _, prevout := range tx.MainchainOutputIDs {
90 entry, ok := view.Entries[prevout]
92 return errors.New("fail to find mainchain output entry")
96 return errors.New("mainchain output has been spent")
99 entry.BlockHeight = block.Height
105 func (view *UtxoViewpoint) applyOutputUtxo(block *bc.Block, tx *bc.Tx, statusFail bool) error {
106 for _, id := range tx.TxHeader.ResultIds {
107 entryOutput, err := tx.Entry(*id)
112 var assetID bc.AssetID
113 utxoType := storage.NormalUTXOType
114 switch output := entryOutput.(type) {
115 case *bc.IntraChainOutput:
116 if output.Source.Value.Amount == uint64(0) {
119 assetID = *output.Source.Value.AssetId
121 assetID = *output.Source.Value.AssetId
122 utxoType = storage.VoteUTXOType
124 // due to it's a retirement, utxo doesn't care this output type so skip it
128 if statusFail && assetID != *consensus.BTMAssetID {
132 if block != nil && len(block.Transactions) > 0 && block.Transactions[0].ID == tx.ID {
133 utxoType = storage.CoinbaseUTXOType
135 view.Entries[*id] = storage.NewUtxoEntry(utxoType, block.Height, false)
140 func (view *UtxoViewpoint) applySpendUtxo(block *bc.Block, tx *bc.Tx, statusFail bool) error {
141 for _, prevout := range tx.SpentOutputIDs {
142 entryOutput, err := tx.Entry(prevout)
147 var assetID bc.AssetID
148 switch output := entryOutput.(type) {
149 case *bc.IntraChainOutput:
150 assetID = *output.Source.Value.AssetId
152 assetID = *output.Source.Value.AssetId
154 return errors.Wrapf(bc.ErrEntryType, "entry %x has unexpected type %T", prevout.Bytes(), entryOutput)
157 if statusFail && assetID != *consensus.BTMAssetID {
161 entry, ok := view.Entries[prevout]
163 return errors.New("fail to find utxo entry")
167 return errors.New("utxo has been spent")
171 case storage.CoinbaseUTXOType:
172 if (entry.BlockHeight + consensus.ActiveNetParams.CoinbasePendingBlockNumber) > block.Height {
173 return errors.New("coinbase utxo is not ready for use")
176 case storage.VoteUTXOType:
177 if (entry.BlockHeight + consensus.ActiveNetParams.VotePendingBlockNumber) > block.Height {
178 return errors.New("Coin is within the voting lock time")
187 func (view *UtxoViewpoint) detachCrossChainUtxo(tx *bc.Tx) error {
188 for _, prevout := range tx.MainchainOutputIDs {
189 entry, ok := view.Entries[prevout]
191 return errors.New("fail to find mainchain output entry")
195 return errors.New("mainchain output is unspent")
198 entry.UnspendOutput()
203 func (view *UtxoViewpoint) detachOutputUtxo(tx *bc.Tx, statusFail bool) error {
204 for _, id := range tx.TxHeader.ResultIds {
205 entryOutput, err := tx.Entry(*id)
210 var assetID bc.AssetID
211 utxoType := storage.NormalUTXOType
212 switch output := entryOutput.(type) {
213 case *bc.IntraChainOutput:
214 if output.Source.Value.Amount == uint64(0) {
217 assetID = *output.Source.Value.AssetId
219 assetID = *output.Source.Value.AssetId
220 utxoType = storage.VoteUTXOType
222 // due to it's a retirement, utxo doesn't care this output type so skip it
226 if statusFail && assetID != *consensus.BTMAssetID {
230 view.Entries[*id] = storage.NewUtxoEntry(utxoType, 0, true)
235 func (view *UtxoViewpoint) detachSpendUtxo(tx *bc.Tx, statusFail bool) error {
236 for _, prevout := range tx.SpentOutputIDs {
237 entryOutput, err := tx.Entry(prevout)
242 var assetID bc.AssetID
243 utxoType := storage.NormalUTXOType
244 switch output := entryOutput.(type) {
245 case *bc.IntraChainOutput:
246 assetID = *output.Source.Value.AssetId
248 assetID = *output.Source.Value.AssetId
249 utxoType = storage.VoteUTXOType
251 return errors.Wrapf(bc.ErrEntryType, "entry %x has unexpected type %T", prevout.Bytes(), entryOutput)
254 if statusFail && assetID != *consensus.BTMAssetID {
258 entry, ok := view.Entries[prevout]
259 if ok && !entry.Spent {
260 return errors.New("try to revert an unspent utxo")
264 view.Entries[prevout] = storage.NewUtxoEntry(utxoType, 0, false)
267 entry.UnspendOutput()