4 "github.com/vapor/consensus"
5 "github.com/vapor/database/storage"
6 "github.com/vapor/errors"
7 "github.com/vapor/protocol/bc"
10 // UtxoViewpoint represents a view into the set of unspent transaction outputs
11 type UtxoViewpoint struct {
12 Entries map[bc.Hash]*storage.UtxoEntry
15 // NewUtxoViewpoint returns a new empty unspent transaction output view.
16 func NewUtxoViewpoint() *UtxoViewpoint {
17 return &UtxoViewpoint{
18 Entries: make(map[bc.Hash]*storage.UtxoEntry),
22 func (view *UtxoViewpoint) ApplyTransaction(block *bc.Block, tx *bc.Tx, statusFail bool) error {
23 if err := view.applyCrossChainUtxo(block, tx); err != nil {
27 if err := view.applySpendUtxo(block, tx, statusFail); err != nil {
31 return view.applyOutputUtxo(block, tx, statusFail)
34 func (view *UtxoViewpoint) ApplyBlock(block *bc.Block, txStatus *bc.TransactionStatus) error {
35 for i, tx := range block.Transactions {
36 statusFail, err := txStatus.GetStatus(i)
41 if err := view.ApplyTransaction(block, tx, statusFail); err != nil {
48 func (view *UtxoViewpoint) CanSpend(hash *bc.Hash) bool {
49 entry := view.Entries[*hash]
50 return entry != nil && !entry.Spent
53 func (view *UtxoViewpoint) DetachTransaction(tx *bc.Tx, statusFail bool) error {
54 if err := view.detachCrossChainUtxo(tx); err != nil {
58 if err := view.detachSpendUtxo(tx, statusFail); err != nil {
62 return view.detachOutputUtxo(tx, statusFail)
65 func (view *UtxoViewpoint) DetachBlock(block *bc.Block, txStatus *bc.TransactionStatus) error {
66 for i := len(block.Transactions) - 1; i >= 0; i-- {
67 statusFail, err := txStatus.GetStatus(i)
72 if err := view.DetachTransaction(block.Transactions[i], statusFail); err != nil {
79 func (view *UtxoViewpoint) HasUtxo(hash *bc.Hash) bool {
80 _, ok := view.Entries[*hash]
84 func (view *UtxoViewpoint) applyCrossChainUtxo(block *bc.Block, tx *bc.Tx) error {
85 for _, prevout := range tx.MainchainOutputIDs {
86 entry, ok := view.Entries[prevout]
88 return errors.New("fail to find mainchain output entry")
92 return errors.New("mainchain output has been spent")
95 entry.BlockHeight = block.Height
101 func (view *UtxoViewpoint) applyOutputUtxo(block *bc.Block, tx *bc.Tx, statusFail bool) error {
102 for _, id := range tx.TxHeader.ResultIds {
103 entryOutput, err := tx.Entry(*id)
108 var assetID bc.AssetID
109 utxoType := storage.NormalUTXOType
110 switch output := entryOutput.(type) {
111 case *bc.IntraChainOutput:
112 if output.Source.Value.Amount == uint64(0) {
115 assetID = *output.Source.Value.AssetId
117 assetID = *output.Source.Value.AssetId
118 utxoType = storage.VoteUTXOType
120 // due to it's a retirement, utxo doesn't care this output type so skip it
124 if statusFail && assetID != *consensus.BTMAssetID {
128 if block != nil && len(block.Transactions) > 0 && block.Transactions[0].ID == tx.ID {
129 utxoType = storage.CoinbaseUTXOType
131 view.Entries[*id] = storage.NewUtxoEntry(utxoType, block.Height, false)
136 func (view *UtxoViewpoint) applySpendUtxo(block *bc.Block, tx *bc.Tx, statusFail bool) error {
137 for _, prevout := range tx.SpentOutputIDs {
138 entryOutput, err := tx.Entry(prevout)
143 var assetID bc.AssetID
144 switch output := entryOutput.(type) {
145 case *bc.IntraChainOutput:
146 assetID = *output.Source.Value.AssetId
148 assetID = *output.Source.Value.AssetId
150 return errors.Wrapf(bc.ErrEntryType, "entry %x has unexpected type %T", prevout.Bytes(), entryOutput)
153 if statusFail && assetID != *consensus.BTMAssetID {
157 entry, ok := view.Entries[prevout]
159 return errors.New("fail to find utxo entry")
163 return errors.New("utxo has been spent")
167 case storage.CoinbaseUTXOType:
168 if (entry.BlockHeight + consensus.CoinbasePendingBlockNumber) > block.Height {
169 return errors.New("coinbase utxo is not ready for use")
172 case storage.VoteUTXOType:
173 if (entry.BlockHeight + consensus.VotePendingBlockNumber) > block.Height {
174 return errors.New("Coin is within the voting lock time")
183 func (view *UtxoViewpoint) detachCrossChainUtxo(tx *bc.Tx) error {
184 for _, prevout := range tx.MainchainOutputIDs {
185 entry, ok := view.Entries[prevout]
187 return errors.New("fail to find mainchain output entry")
191 return errors.New("mainchain output is unspent")
194 entry.UnspendOutput()
199 func (view *UtxoViewpoint) detachOutputUtxo(tx *bc.Tx, statusFail bool) error {
200 for _, id := range tx.TxHeader.ResultIds {
201 entryOutput, err := tx.Entry(*id)
206 var assetID bc.AssetID
207 utxoType := storage.NormalUTXOType
208 switch output := entryOutput.(type) {
209 case *bc.IntraChainOutput:
210 if output.Source.Value.Amount == uint64(0) {
213 assetID = *output.Source.Value.AssetId
215 assetID = *output.Source.Value.AssetId
216 utxoType = storage.VoteUTXOType
218 // due to it's a retirement, utxo doesn't care this output type so skip it
222 if statusFail && assetID != *consensus.BTMAssetID {
226 view.Entries[*id] = storage.NewUtxoEntry(utxoType, 0, true)
231 func (view *UtxoViewpoint) detachSpendUtxo(tx *bc.Tx, statusFail bool) error {
232 for _, prevout := range tx.SpentOutputIDs {
233 entryOutput, err := tx.Entry(prevout)
238 var assetID bc.AssetID
239 utxoType := storage.NormalUTXOType
240 switch output := entryOutput.(type) {
241 case *bc.IntraChainOutput:
242 assetID = *output.Source.Value.AssetId
244 assetID = *output.Source.Value.AssetId
245 utxoType = storage.VoteUTXOType
247 return errors.Wrapf(bc.ErrEntryType, "entry %x has unexpected type %T", prevout.Bytes(), entryOutput)
250 if statusFail && assetID != *consensus.BTMAssetID {
254 entry, ok := view.Entries[prevout]
255 if ok && !entry.Spent {
256 return errors.New("try to revert an unspent utxo")
260 view.Entries[prevout] = storage.NewUtxoEntry(utxoType, 0, false)
263 entry.UnspendOutput()