blockchain_go/utxo_set.go

194 lines
3.7 KiB
Go
Raw Normal View History

2017-09-16 22:16:14 -05:00
package main
import (
"encoding/hex"
"log"
"github.com/boltdb/bolt"
)
const utxoBucket = "chainstate"
// UTXOSet represents UTXO set
2017-09-16 22:30:30 -05:00
type UTXOSet struct {
Blockchain *Blockchain
}
2017-09-16 22:16:14 -05:00
2017-09-17 00:19:01 -05:00
// FindSpendableOutputs finds and returns unspent outputs to reference in inputs
func (u UTXOSet) FindSpendableOutputs(pubkeyHash []byte, amount int) (int, map[string][]int) {
unspentOutputs := make(map[string][]int)
accumulated := 0
2017-09-16 22:30:30 -05:00
db := u.Blockchain.db
2017-09-16 22:16:14 -05:00
2017-09-17 00:19:01 -05:00
err := db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(utxoBucket))
c := b.Cursor()
2017-09-16 22:16:14 -05:00
2017-09-17 00:19:01 -05:00
for k, v := c.First(); k != nil; k, v = c.Next() {
txID := hex.EncodeToString(k)
outs := DeserializeOutputs(v)
2017-09-16 22:16:14 -05:00
2017-09-17 00:19:01 -05:00
for outIdx, out := range outs.Outputs {
if out.IsLockedWithKey(pubkeyHash) && accumulated < amount {
accumulated += out.Value
unspentOutputs[txID] = append(unspentOutputs[txID], outIdx)
}
}
2017-09-16 22:16:14 -05:00
}
return nil
})
if err != nil {
log.Panic(err)
}
2017-09-17 00:19:01 -05:00
return accumulated, unspentOutputs
}
2017-09-16 22:16:14 -05:00
2017-09-17 00:19:01 -05:00
// FindUTXO finds UTXO for a public key hash
func (u UTXOSet) FindUTXO(pubKeyHash []byte) []TXOutput {
var UTXOs []TXOutput
db := u.Blockchain.db
err := db.View(func(tx *bolt.Tx) error {
2017-09-16 22:16:14 -05:00
b := tx.Bucket([]byte(utxoBucket))
2017-09-17 00:19:01 -05:00
c := b.Cursor()
2017-09-16 22:16:14 -05:00
2017-09-17 00:19:01 -05:00
for k, v := c.First(); k != nil; k, v = c.Next() {
outs := DeserializeOutputs(v)
2017-09-16 22:16:14 -05:00
2017-09-17 00:19:01 -05:00
for _, out := range outs.Outputs {
if out.IsLockedWithKey(pubKeyHash) {
UTXOs = append(UTXOs, out)
}
2017-09-16 22:16:14 -05:00
}
}
return nil
})
2017-09-17 00:19:01 -05:00
if err != nil {
log.Panic(err)
}
return UTXOs
2017-09-16 22:16:14 -05:00
}
// CountTransactions returns the number of transactions in the UTXO set
func (u UTXOSet) CountTransactions() int {
2017-09-16 22:30:30 -05:00
db := u.Blockchain.db
2017-09-16 22:16:14 -05:00
counter := 0
2017-09-16 22:30:30 -05:00
err := db.View(func(tx *bolt.Tx) error {
2017-09-16 22:16:14 -05:00
b := tx.Bucket([]byte(utxoBucket))
c := b.Cursor()
for k, _ := c.First(); k != nil; k, _ = c.Next() {
counter++
}
return nil
})
2017-09-16 22:30:30 -05:00
if err != nil {
log.Panic(err)
}
2017-09-16 22:16:14 -05:00
return counter
}
2017-09-16 23:01:18 -05:00
2017-09-17 00:19:01 -05:00
// Reindex rebuilds the UTXO set
func (u UTXOSet) Reindex() {
2017-09-16 23:01:18 -05:00
db := u.Blockchain.db
2017-09-17 23:01:24 -05:00
bucketName := []byte(utxoBucket)
2017-09-16 23:01:18 -05:00
2017-09-17 00:19:01 -05:00
err := db.Update(func(tx *bolt.Tx) error {
2017-09-17 23:01:24 -05:00
err := tx.DeleteBucket(bucketName)
if err != nil && err != bolt.ErrBucketNotFound {
log.Panic(err)
2017-09-16 23:01:18 -05:00
}
2017-09-17 23:01:24 -05:00
_, err = tx.CreateBucket(bucketName)
2017-09-17 00:19:01 -05:00
if err != nil {
log.Panic(err)
}
2017-09-16 23:01:18 -05:00
return nil
})
if err != nil {
log.Panic(err)
}
2017-09-17 00:44:43 -05:00
UTXO := u.Blockchain.FindUTXO()
2017-09-17 00:19:01 -05:00
err = db.Update(func(tx *bolt.Tx) error {
2017-09-17 23:01:24 -05:00
b := tx.Bucket(bucketName)
2017-09-17 00:19:01 -05:00
for txID, outs := range UTXO {
key, err := hex.DecodeString(txID)
if err != nil {
log.Panic(err)
}
err = b.Put(key, outs.Serialize())
if err != nil {
log.Panic(err)
}
}
return nil
})
2017-09-16 23:01:18 -05:00
}
2017-09-16 23:49:59 -05:00
2017-09-17 00:19:01 -05:00
// Update updates the UTXO set with transactions from the Block
// The Block is considered to be the tip of a blockchain
func (u UTXOSet) Update(block *Block) {
2017-09-16 23:49:59 -05:00
db := u.Blockchain.db
2017-09-17 00:19:01 -05:00
err := db.Update(func(tx *bolt.Tx) error {
2017-09-16 23:49:59 -05:00
b := tx.Bucket([]byte(utxoBucket))
2017-09-17 00:19:01 -05:00
for _, tx := range block.Transactions {
if tx.IsCoinbase() == false {
for _, vin := range tx.Vin {
updatedOuts := TXOutputs{}
2017-09-17 23:01:24 -05:00
outsBytes := b.Get(vin.Txid)
outs := DeserializeOutputs(outsBytes)
2017-09-17 00:19:01 -05:00
for outIdx, out := range outs.Outputs {
if outIdx != vin.Vout {
updatedOuts.Outputs = append(updatedOuts.Outputs, out)
}
}
if len(updatedOuts.Outputs) == 0 {
err := b.Delete(vin.Txid)
if err != nil {
log.Panic(err)
}
} else {
err := b.Put(vin.Txid, updatedOuts.Serialize())
if err != nil {
log.Panic(err)
}
}
2017-09-16 23:49:59 -05:00
}
}
2017-09-17 00:19:01 -05:00
newOutputs := TXOutputs{}
for _, out := range tx.Vout {
newOutputs.Outputs = append(newOutputs.Outputs, out)
}
err := b.Put(tx.ID, newOutputs.Serialize())
if err != nil {
log.Panic(err)
}
2017-09-16 23:49:59 -05:00
}
return nil
})
if err != nil {
log.Panic(err)
}
}