Merge 2e06c0a637
into 201e7a1652
This commit is contained in:
commit
94cd44f1cc
|
@ -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/)
|
||||||
|
|
10
block.go
10
block.go
|
@ -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
|
||||||
|
|
|
@ -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
13
cli.go
|
@ -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()
|
||||||
|
|
|
@ -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!")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
11
cli_send.go
11
cli_send.go
|
@ -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!")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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")
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue