This commit is contained in:
noahsalvadordenjo 2019-11-04 20:26:58 +01:00 committed by GitHub
commit 94cd44f1cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 446 additions and 77 deletions

View File

@ -4,6 +4,7 @@ A blockchain implementation in Go, as described in these articles:
1. [Basic Prototype](https://jeiwan.cc/posts/building-blockchain-in-go-part-1/) 1. [Basic Prototype](https://jeiwan.cc/posts/building-blockchain-in-go-part-1/)
2. [Proof-of-Work](https://jeiwan.cc/posts/building-blockchain-in-go-part-2/) 2. [Proof-of-Work](https://jeiwan.cc/posts/building-blockchain-in-go-part-2/)
2. [Persistence and CLI](https://jeiwan.cc/posts/building-blockchain-in-go-part-3/) 3. [Persistence and CLI](https://jeiwan.cc/posts/building-blockchain-in-go-part-3/)
3. [Transactions 1](https://jeiwan.cc/posts/building-blockchain-in-go-part-4/) 4. [Transactions 1](https://jeiwan.cc/posts/building-blockchain-in-go-part-4/)
3. [Addresses](https://jeiwan.cc/posts/building-blockchain-in-go-part-5/) 5. [Addresses](https://jeiwan.cc/posts/building-blockchain-in-go-part-5/)
6. [Transactions 2](https://jeiwan.cc/posts/building-blockchain-in-go-part-6/)

View File

@ -2,7 +2,6 @@ package main
import ( import (
"bytes" "bytes"
"crypto/sha256"
"encoding/gob" "encoding/gob"
"log" "log"
"time" "time"
@ -36,15 +35,14 @@ func NewGenesisBlock(coinbase *Transaction) *Block {
// HashTransactions returns a hash of the transactions in the block // HashTransactions returns a hash of the transactions in the block
func (b *Block) HashTransactions() []byte { func (b *Block) HashTransactions() []byte {
var txHashes [][]byte var transactions [][]byte
var txHash [32]byte
for _, tx := range b.Transactions { for _, tx := range b.Transactions {
txHashes = append(txHashes, tx.Hash()) transactions = append(transactions, tx.Serialize())
} }
txHash = sha256.Sum256(bytes.Join(txHashes, []byte{})) mTree := NewMerkleTree(transactions)
return txHash[:] return mTree.RootNode.Data
} }
// Serialize serializes the block // Serialize serializes the block

View File

@ -68,7 +68,7 @@ func CreateBlockchain(address string) *Blockchain {
} }
// NewBlockchain creates a new Blockchain with genesis Block // NewBlockchain creates a new Blockchain with genesis Block
func NewBlockchain(address string) *Blockchain { func NewBlockchain() *Blockchain {
if dbExists() == false { if dbExists() == false {
fmt.Println("No existing blockchain found. Create one first.") fmt.Println("No existing blockchain found. Create one first.")
os.Exit(1) os.Exit(1)
@ -95,31 +95,6 @@ func NewBlockchain(address string) *Blockchain {
return &bc return &bc
} }
// FindSpendableOutputs finds and returns unspent outputs to reference in inputs
func (bc *Blockchain) FindSpendableOutputs(pubKeyHash []byte, amount int) (int, map[string][]int) {
unspentOutputs := make(map[string][]int)
unspentTXs := bc.FindUnspentTransactions(pubKeyHash)
accumulated := 0
Work:
for _, tx := range unspentTXs {
txID := hex.EncodeToString(tx.ID)
for outIdx, out := range tx.Vout {
if out.IsLockedWithKey(pubKeyHash) && accumulated < amount {
accumulated += out.Value
unspentOutputs[txID] = append(unspentOutputs[txID], outIdx)
if accumulated >= amount {
break Work
}
}
}
}
return accumulated, unspentOutputs
}
// FindTransaction finds a transaction by its ID // FindTransaction finds a transaction by its ID
func (bc *Blockchain) FindTransaction(ID []byte) (Transaction, error) { func (bc *Blockchain) FindTransaction(ID []byte) (Transaction, error) {
bci := bc.Iterator() bci := bc.Iterator()
@ -141,9 +116,9 @@ func (bc *Blockchain) FindTransaction(ID []byte) (Transaction, error) {
return Transaction{}, errors.New("Transaction is not found") return Transaction{}, errors.New("Transaction is not found")
} }
// FindUnspentTransactions returns a list of transactions containing unspent outputs // FindUTXO finds all unspent transaction outputs and returns transactions with spent outputs removed
func (bc *Blockchain) FindUnspentTransactions(pubKeyHash []byte) []Transaction { func (bc *Blockchain) FindUTXO() map[string]TXOutputs {
var unspentTXs []Transaction UTXO := make(map[string]TXOutputs)
spentTXOs := make(map[string][]int) spentTXOs := make(map[string][]int)
bci := bc.Iterator() bci := bc.Iterator()
@ -164,43 +139,25 @@ func (bc *Blockchain) FindUnspentTransactions(pubKeyHash []byte) []Transaction {
} }
} }
if out.IsLockedWithKey(pubKeyHash) { outs := UTXO[txID]
unspentTXs = append(unspentTXs, *tx) outs.Outputs = append(outs.Outputs, out)
} UTXO[txID] = outs
} }
if tx.IsCoinbase() == false { if tx.IsCoinbase() == false {
for _, in := range tx.Vin { for _, in := range tx.Vin {
if in.UsesKey(pubKeyHash) {
inTxID := hex.EncodeToString(in.Txid) inTxID := hex.EncodeToString(in.Txid)
spentTXOs[inTxID] = append(spentTXOs[inTxID], in.Vout) spentTXOs[inTxID] = append(spentTXOs[inTxID], in.Vout)
} }
} }
} }
}
if len(block.PrevBlockHash) == 0 { if len(block.PrevBlockHash) == 0 {
break break
} }
} }
return unspentTXs return UTXO
}
// FindUTXO finds and returns all unspent transaction outputs
func (bc *Blockchain) FindUTXO(pubKeyHash []byte) []TXOutput {
var UTXOs []TXOutput
unspentTransactions := bc.FindUnspentTransactions(pubKeyHash)
for _, tx := range unspentTransactions {
for _, out := range tx.Vout {
if out.IsLockedWithKey(pubKeyHash) {
UTXOs = append(UTXOs, out)
}
}
}
return UTXOs
} }
// Iterator returns a BlockchainIterat // Iterator returns a BlockchainIterat
@ -211,7 +168,7 @@ func (bc *Blockchain) Iterator() *BlockchainIterator {
} }
// MineBlock mines a new block with the provided transactions // MineBlock mines a new block with the provided transactions
func (bc *Blockchain) MineBlock(transactions []*Transaction) { func (bc *Blockchain) MineBlock(transactions []*Transaction) *Block {
var lastHash []byte var lastHash []byte
for _, tx := range transactions { for _, tx := range transactions {
@ -251,6 +208,8 @@ func (bc *Blockchain) MineBlock(transactions []*Transaction) {
if err != nil { if err != nil {
log.Panic(err) log.Panic(err)
} }
return newBlock
} }
// SignTransaction signs inputs of a Transaction // SignTransaction signs inputs of a Transaction
@ -270,6 +229,10 @@ func (bc *Blockchain) SignTransaction(tx *Transaction, privKey ecdsa.PrivateKey)
// VerifyTransaction verifies transaction input signatures // VerifyTransaction verifies transaction input signatures
func (bc *Blockchain) VerifyTransaction(tx *Transaction) bool { func (bc *Blockchain) VerifyTransaction(tx *Transaction) bool {
if tx.IsCoinbase() {
return true
}
prevTXs := make(map[string]Transaction) prevTXs := make(map[string]Transaction)
for _, vin := range tx.Vin { for _, vin := range tx.Vin {

13
cli.go
View File

@ -17,6 +17,7 @@ func (cli *CLI) printUsage() {
fmt.Println(" getbalance -address ADDRESS - Get balance of ADDRESS") fmt.Println(" getbalance -address ADDRESS - Get balance of ADDRESS")
fmt.Println(" listaddresses - Lists all addresses from the wallet file") fmt.Println(" listaddresses - Lists all addresses from the wallet file")
fmt.Println(" printchain - Print all the blocks of the blockchain") fmt.Println(" printchain - Print all the blocks of the blockchain")
fmt.Println(" reindexutxo - Rebuilds the UTXO set")
fmt.Println(" send -from FROM -to TO -amount AMOUNT - Send AMOUNT of coins from FROM address to TO") fmt.Println(" send -from FROM -to TO -amount AMOUNT - Send AMOUNT of coins from FROM address to TO")
} }
@ -35,8 +36,9 @@ func (cli *CLI) Run() {
createBlockchainCmd := flag.NewFlagSet("createblockchain", flag.ExitOnError) createBlockchainCmd := flag.NewFlagSet("createblockchain", flag.ExitOnError)
createWalletCmd := flag.NewFlagSet("createwallet", flag.ExitOnError) createWalletCmd := flag.NewFlagSet("createwallet", flag.ExitOnError)
listAddressesCmd := flag.NewFlagSet("listaddresses", flag.ExitOnError) listAddressesCmd := flag.NewFlagSet("listaddresses", flag.ExitOnError)
sendCmd := flag.NewFlagSet("send", flag.ExitOnError)
printChainCmd := flag.NewFlagSet("printchain", flag.ExitOnError) printChainCmd := flag.NewFlagSet("printchain", flag.ExitOnError)
reindexUTXOCmd := flag.NewFlagSet("reindexutxo", flag.ExitOnError)
sendCmd := flag.NewFlagSet("send", flag.ExitOnError)
getBalanceAddress := getBalanceCmd.String("address", "", "The address to get balance for") getBalanceAddress := getBalanceCmd.String("address", "", "The address to get balance for")
createBlockchainAddress := createBlockchainCmd.String("address", "", "The address to send genesis block reward to") createBlockchainAddress := createBlockchainCmd.String("address", "", "The address to send genesis block reward to")
@ -75,6 +77,11 @@ func (cli *CLI) Run() {
if err != nil { if err != nil {
log.Panic(err) log.Panic(err)
} }
case "reindexutxo":
err := reindexUTXOCmd.Parse(os.Args[2:])
if err != nil {
log.Panic(err)
}
default: default:
cli.printUsage() cli.printUsage()
os.Exit(1) os.Exit(1)
@ -108,6 +115,10 @@ func (cli *CLI) Run() {
cli.printChain() cli.printChain()
} }
if reindexUTXOCmd.Parsed() {
cli.reindexUTXO()
}
if sendCmd.Parsed() { if sendCmd.Parsed() {
if *sendFrom == "" || *sendTo == "" || *sendAmount <= 0 { if *sendFrom == "" || *sendTo == "" || *sendAmount <= 0 {
sendCmd.Usage() sendCmd.Usage()

View File

@ -10,6 +10,10 @@ func (cli *CLI) createBlockchain(address string) {
log.Panic("ERROR: Address is not valid") log.Panic("ERROR: Address is not valid")
} }
bc := CreateBlockchain(address) bc := CreateBlockchain(address)
bc.db.Close() defer bc.db.Close()
UTXOSet := UTXOSet{bc}
UTXOSet.Reindex()
fmt.Println("Done!") fmt.Println("Done!")
} }

View File

@ -9,13 +9,14 @@ func (cli *CLI) getBalance(address string) {
if !ValidateAddress(address) { if !ValidateAddress(address) {
log.Panic("ERROR: Address is not valid") log.Panic("ERROR: Address is not valid")
} }
bc := NewBlockchain(address) bc := NewBlockchain()
UTXOSet := UTXOSet{bc}
defer bc.db.Close() defer bc.db.Close()
balance := 0 balance := 0
pubKeyHash := Base58Decode([]byte(address)) pubKeyHash := Base58Decode([]byte(address))
pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-4] pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-4]
UTXOs := bc.FindUTXO(pubKeyHash) UTXOs := UTXOSet.FindUTXO(pubKeyHash)
for _, out := range UTXOs { for _, out := range UTXOs {
balance += out.Value balance += out.Value

View File

@ -6,7 +6,7 @@ import (
) )
func (cli *CLI) printChain() { func (cli *CLI) printChain() {
bc := NewBlockchain("") bc := NewBlockchain()
defer bc.db.Close() defer bc.db.Close()
bci := bc.Iterator() bci := bc.Iterator()

12
cli_reindexutxo.go Normal file
View File

@ -0,0 +1,12 @@
package main
import "fmt"
func (cli *CLI) reindexUTXO() {
bc := NewBlockchain()
UTXOSet := UTXOSet{bc}
UTXOSet.Reindex()
count := UTXOSet.CountTransactions()
fmt.Printf("Done! There are %d transactions in the UTXO set.\n", count)
}

View File

@ -13,10 +13,15 @@ func (cli *CLI) send(from, to string, amount int) {
log.Panic("ERROR: Recipient address is not valid") log.Panic("ERROR: Recipient address is not valid")
} }
bc := NewBlockchain(from) bc := NewBlockchain()
UTXOSet := UTXOSet{bc}
defer bc.db.Close() defer bc.db.Close()
tx := NewUTXOTransaction(from, to, amount, bc) tx := NewUTXOTransaction(from, to, amount, &UTXOSet)
bc.MineBlock([]*Transaction{tx}) cbTx := NewCoinbaseTX(from, "")
txs := []*Transaction{cbTx, tx}
newBlock := bc.MineBlock(txs)
UTXOSet.Update(newBlock)
fmt.Println("Success!") fmt.Println("Success!")
} }

65
merkle_tree.go Normal file
View File

@ -0,0 +1,65 @@
package main
import (
"crypto/sha256"
)
// MerkleTree represent a Merkle tree
type MerkleTree struct {
RootNode *MerkleNode
}
// MerkleNode represent a Merkle tree node
type MerkleNode struct {
Left *MerkleNode
Right *MerkleNode
Data []byte
}
// NewMerkleTree creates a new Merkle tree from a sequence of data
func NewMerkleTree(data [][]byte) *MerkleTree {
var nodes []MerkleNode
if len(data)%2 != 0 {
data = append(data, data[len(data)-1])
}
for _, datum := range data {
node := NewMerkleNode(nil, nil, datum)
nodes = append(nodes, *node)
}
for i := 0; i < len(data)/2; i++ {
var newLevel []MerkleNode
for j := 0; j < len(nodes); j += 2 {
node := NewMerkleNode(&nodes[j], &nodes[j+1], nil)
newLevel = append(newLevel, *node)
}
nodes = newLevel
}
mTree := MerkleTree{&nodes[0]}
return &mTree
}
// NewMerkleNode creates a new Merkle tree node
func NewMerkleNode(left, right *MerkleNode, data []byte) *MerkleNode {
mNode := MerkleNode{}
if left == nil && right == nil {
hash := sha256.Sum256(data)
mNode.Data = hash[:]
} else {
prevHashes := append(left.Data, right.Data...)
hash := sha256.Sum256(prevHashes)
mNode.Data = hash[:]
}
mNode.Left = left
mNode.Right = right
return &mNode
}

75
merkle_tree_test.go Normal file
View File

@ -0,0 +1,75 @@
package main
import (
"encoding/hex"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewMerkleNode(t *testing.T) {
data := [][]byte{
[]byte("node1"),
[]byte("node2"),
[]byte("node3"),
}
// Level 1
n1 := NewMerkleNode(nil, nil, data[0])
n2 := NewMerkleNode(nil, nil, data[1])
n3 := NewMerkleNode(nil, nil, data[2])
n4 := NewMerkleNode(nil, nil, data[2])
// Level 2
n5 := NewMerkleNode(n1, n2, nil)
n6 := NewMerkleNode(n3, n4, nil)
// Level 3
n7 := NewMerkleNode(n5, n6, nil)
assert.Equal(
t,
"64b04b718d8b7c5b6fd17f7ec221945c034cfce3be4118da33244966150c4bd4",
hex.EncodeToString(n5.Data),
"Level 1 hash 1 is correct",
)
assert.Equal(
t,
"08bd0d1426f87a78bfc2f0b13eccdf6f5b58dac6b37a7b9441c1a2fab415d76c",
hex.EncodeToString(n6.Data),
"Level 1 hash 2 is correct",
)
assert.Equal(
t,
"4e3e44e55926330ab6c31892f980f8bfd1a6e910ff1ebc3f778211377f35227e",
hex.EncodeToString(n7.Data),
"Root hash is correct",
)
}
func TestNewMerkleTree(t *testing.T) {
data := [][]byte{
[]byte("node1"),
[]byte("node2"),
[]byte("node3"),
}
// Level 1
n1 := NewMerkleNode(nil, nil, data[0])
n2 := NewMerkleNode(nil, nil, data[1])
n3 := NewMerkleNode(nil, nil, data[2])
n4 := NewMerkleNode(nil, nil, data[2])
// Level 2
n5 := NewMerkleNode(n1, n2, nil)
n6 := NewMerkleNode(n3, n4, nil)
// Level 3
n7 := NewMerkleNode(n5, n6, nil)
rootHash := fmt.Sprintf("%x", n7.Data)
mTree := NewMerkleTree(data)
assert.Equal(t, rootHash, fmt.Sprintf("%x", mTree.RootNode.Data), "Merkle tree root hash is correct")
}

View File

@ -12,7 +12,7 @@ var (
maxNonce = math.MaxInt64 maxNonce = math.MaxInt64
) )
const targetBits = 24 const targetBits = 16
// ProofOfWork represents a proof-of-work // ProofOfWork represents a proof-of-work
type ProofOfWork struct { type ProofOfWork struct {

View File

@ -173,7 +173,13 @@ func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool {
// NewCoinbaseTX creates a new coinbase transaction // NewCoinbaseTX creates a new coinbase transaction
func NewCoinbaseTX(to, data string) *Transaction { func NewCoinbaseTX(to, data string) *Transaction {
if data == "" { if data == "" {
data = fmt.Sprintf("Reward to '%s'", to) randData := make([]byte, 20)
_, err := rand.Read(randData)
if err != nil {
log.Panic(err)
}
data = fmt.Sprintf("%x", randData)
} }
txin := TXInput{[]byte{}, -1, nil, []byte(data)} txin := TXInput{[]byte{}, -1, nil, []byte(data)}
@ -185,7 +191,7 @@ func NewCoinbaseTX(to, data string) *Transaction {
} }
// NewUTXOTransaction creates a new transaction // NewUTXOTransaction creates a new transaction
func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transaction { func NewUTXOTransaction(from, to string, amount int, UTXOSet *UTXOSet) *Transaction {
var inputs []TXInput var inputs []TXInput
var outputs []TXOutput var outputs []TXOutput
@ -195,7 +201,7 @@ func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transactio
} }
wallet := wallets.GetWallet(from) wallet := wallets.GetWallet(from)
pubKeyHash := HashPubKey(wallet.PublicKey) pubKeyHash := HashPubKey(wallet.PublicKey)
acc, validOutputs := bc.FindSpendableOutputs(pubKeyHash, amount) acc, validOutputs := UTXOSet.FindSpendableOutputs(pubKeyHash, amount)
if acc < amount { if acc < amount {
log.Panic("ERROR: Not enough funds") log.Panic("ERROR: Not enough funds")
@ -222,7 +228,7 @@ func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transactio
tx := Transaction{nil, inputs, outputs} tx := Transaction{nil, inputs, outputs}
tx.ID = tx.Hash() tx.ID = tx.Hash()
bc.SignTransaction(&tx, wallet.PrivateKey) UTXOSet.Blockchain.SignTransaction(&tx, wallet.PrivateKey)
return &tx return &tx
} }

View File

@ -1,6 +1,10 @@
package main package main
import "bytes" import (
"bytes"
"encoding/gob"
"log"
)
// TXOutput represents a transaction output // TXOutput represents a transaction output
type TXOutput struct { type TXOutput struct {
@ -27,3 +31,34 @@ func NewTXOutput(value int, address string) *TXOutput {
return txo return txo
} }
// TXOutputs collects TXOutput
type TXOutputs struct {
Outputs []TXOutput
}
// Serialize serializes TXOutputs
func (outs TXOutputs) Serialize() []byte {
var buff bytes.Buffer
enc := gob.NewEncoder(&buff)
err := enc.Encode(outs)
if err != nil {
log.Panic(err)
}
return buff.Bytes()
}
// DeserializeOutputs deserializes TXOutputs
func DeserializeOutputs(data []byte) TXOutputs {
var outputs TXOutputs
dec := gob.NewDecoder(bytes.NewReader(data))
err := dec.Decode(&outputs)
if err != nil {
log.Panic(err)
}
return outputs
}

193
utxo_set.go Normal file
View File

@ -0,0 +1,193 @@
package main
import (
"encoding/hex"
"log"
"github.com/boltdb/bolt"
)
const utxoBucket = "chainstate"
// UTXOSet represents UTXO set
type UTXOSet struct {
Blockchain *Blockchain
}
// 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
db := u.Blockchain.db
err := db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(utxoBucket))
c := b.Cursor()
for k, v := c.First(); k != nil; k, v = c.Next() {
txID := hex.EncodeToString(k)
outs := DeserializeOutputs(v)
for outIdx, out := range outs.Outputs {
if out.IsLockedWithKey(pubkeyHash) && accumulated < amount {
accumulated += out.Value
unspentOutputs[txID] = append(unspentOutputs[txID], outIdx)
}
}
}
return nil
})
if err != nil {
log.Panic(err)
}
return accumulated, unspentOutputs
}
// 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 {
b := tx.Bucket([]byte(utxoBucket))
c := b.Cursor()
for k, v := c.First(); k != nil; k, v = c.Next() {
outs := DeserializeOutputs(v)
for _, out := range outs.Outputs {
if out.IsLockedWithKey(pubKeyHash) {
UTXOs = append(UTXOs, out)
}
}
}
return nil
})
if err != nil {
log.Panic(err)
}
return UTXOs
}
// CountTransactions returns the number of transactions in the UTXO set
func (u UTXOSet) CountTransactions() int {
db := u.Blockchain.db
counter := 0
err := db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(utxoBucket))
c := b.Cursor()
for k, _ := c.First(); k != nil; k, _ = c.Next() {
counter++
}
return nil
})
if err != nil {
log.Panic(err)
}
return counter
}
// Reindex rebuilds the UTXO set
func (u UTXOSet) Reindex() {
db := u.Blockchain.db
bucketName := []byte(utxoBucket)
err := db.Update(func(tx *bolt.Tx) error {
err := tx.DeleteBucket(bucketName)
if err != nil && err != bolt.ErrBucketNotFound {
log.Panic(err)
}
_, err = tx.CreateBucket(bucketName)
if err != nil {
log.Panic(err)
}
return nil
})
if err != nil {
log.Panic(err)
}
UTXO := u.Blockchain.FindUTXO()
err = db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket(bucketName)
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
})
}
// 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) {
db := u.Blockchain.db
err := db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(utxoBucket))
for _, tx := range block.Transactions {
if tx.IsCoinbase() == false {
for _, vin := range tx.Vin {
updatedOuts := TXOutputs{}
outsBytes := b.Get(vin.Txid)
outs := DeserializeOutputs(outsBytes)
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)
}
}
}
}
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)
}
}
return nil
})
if err != nil {
log.Panic(err)
}
}