add verify_transactions and update utxo

pull/5/head
Minh Doan 6 years ago
parent 93bbce28d8
commit d65fb29835
  1. 5
      blockchain/block_test.go
  2. 45
      blockchain/blockchain.go
  3. 36
      blockchain/blockchain_test.go
  4. 10
      blockchain/transaction.go
  5. 2
      blockchain/transaction_test.go
  6. 80
      blockchain/utxopool.go
  7. 11
      blockchain/utxopool_test.go

@ -6,7 +6,10 @@ import (
) )
func TestBlockSerialize(t *testing.T) { func TestBlockSerialize(t *testing.T) {
cbtx := NewCoinbaseTX("minh", genesisCoinbaseData) cbtx, utxoPool := NewCoinbaseTX("minh", genesisCoinbaseData)
if cbtx == nil || utxoPool == nil {
t.Errorf("Failed to create a coinbase transaction.")
}
block := NewGenesisBlock(cbtx) block := NewGenesisBlock(cbtx)
serializedValue := block.Serialize() serializedValue := block.Serialize()

@ -3,6 +3,7 @@ package blockchain
import ( import (
"bytes" "bytes"
"encoding/hex" "encoding/hex"
"fmt"
) )
// Blockchain keeps a sequence of Blocks // Blockchain keeps a sequence of Blocks
@ -130,20 +131,21 @@ func (bc *Blockchain) NewUTXOTransaction(from, to string, amount int) *Transacti
return &tx return &tx
} }
// AddNewTransferAmount creates a new transaction and a block of that transaction. // AddNewUserTransfer creates a new transaction and a block of that transaction.
// Mostly used for testing. // Mostly used for testing.
func (bc *Blockchain) AddNewTransferAmount(from, to string, amount int) *Blockchain { func (bc *Blockchain) AddNewUserTransfer(utxoPool *UTXOPool, from, to string, amount int) bool {
tx := bc.NewUTXOTransaction(from, to, amount) tx := bc.NewUTXOTransaction(from, to, amount)
if tx != nil { if tx != nil {
newBlock := NewBlock([]*Transaction{tx}, bc.blocks[len(bc.blocks)-1].Hash) newBlock := NewBlock([]*Transaction{tx}, bc.blocks[len(bc.blocks)-1].Hash)
bc.blocks = append(bc.blocks, newBlock) if bc.VerifyNewBlockAndUpdate(utxoPool, newBlock) {
return bc return true
} }
return nil }
return false
} }
// VerifyNewBlock verifies if the new coming block is valid for the current blockchain. // VerifyNewBlockAndUpdate verifies if the new coming block is valid for the current blockchain.
func (bc *Blockchain) VerifyNewBlock(utxopool *UTXOPool, block *Block) bool { func (bc *Blockchain) VerifyNewBlockAndUpdate(utxopool *UTXOPool, block *Block) bool {
length := len(bc.blocks) length := len(bc.blocks)
if bytes.Compare(block.PrevBlockHash, bc.blocks[length-1].Hash) != 0 { if bytes.Compare(block.PrevBlockHash, bc.blocks[length-1].Hash) != 0 {
return false return false
@ -152,40 +154,21 @@ func (bc *Blockchain) VerifyNewBlock(utxopool *UTXOPool, block *Block) bool {
return false return false
} }
if utxopool != nil { if utxopool != nil && !utxopool.VerifyAndUpdate(block.Transactions) {
for _, tx := range block.Transactions { fmt.Println("Minh2")
inTotal := 0
// Calculate the sum of TxInput
for _, in := range tx.TxInput {
inTxID := hex.EncodeToString(in.TxID)
if val, ok := utxopool.utxo[in.Address][inTxID]; ok {
inTotal += val
} else {
return false
}
}
outTotal := 0
// Calculate the sum of TxOutput
for _, out := range tx.TxOutput {
outTotal += out.Value
}
if inTotal != outTotal {
return false return false
} }
}
}
return true return true
} }
// CreateBlockchain creates a new blockchain DB // CreateBlockchain creates a new blockchain DB
func CreateBlockchain(address string) *Blockchain { func CreateBlockchain(address string) (*Blockchain, *UTXOPool) {
// TODO: We assume we have not created any blockchain before. // TODO: We assume we have not created any blockchain before.
// In current bitcoin, we can check if we created a blockchain before accessing local db. // In current bitcoin, we can check if we created a blockchain before accessing local db.
cbtx := NewCoinbaseTX(address, genesisCoinbaseData) cbtx, utxoPool := NewCoinbaseTX(address, genesisCoinbaseData)
genesis := NewGenesisBlock(cbtx) genesis := NewGenesisBlock(cbtx)
bc := Blockchain{[]*Block{genesis}} bc := Blockchain{[]*Block{genesis}}
return &bc return &bc, utxoPool
} }

@ -5,14 +5,14 @@ import (
) )
func TestCreateBlockchain(t *testing.T) { func TestCreateBlockchain(t *testing.T) {
if CreateBlockchain("minh") == nil { if bc, _ := CreateBlockchain("minh"); bc == nil {
t.Errorf("failed to create a blockchain") t.Errorf("failed to create a blockchain")
} }
} }
func TestFindSpendableOutputs(t *testing.T) { func TestFindSpendableOutputs(t *testing.T) {
requestAmount := 3 requestAmount := 3
bc := CreateBlockchain("minh") bc, _ := CreateBlockchain("minh")
accumulated, unspentOutputs := bc.FindSpendableOutputs("minh", requestAmount) accumulated, unspentOutputs := bc.FindSpendableOutputs("minh", requestAmount)
if accumulated < DefaultCoinbaseValue { if accumulated < DefaultCoinbaseValue {
t.Error("Failed to find enough unspent ouptuts") t.Error("Failed to find enough unspent ouptuts")
@ -24,7 +24,7 @@ func TestFindSpendableOutputs(t *testing.T) {
} }
func TestFindUTXO(t *testing.T) { func TestFindUTXO(t *testing.T) {
bc := CreateBlockchain("minh") bc, _ := CreateBlockchain("minh")
utxo := bc.FindUTXO("minh") utxo := bc.FindUTXO("minh")
total := 0 total := 0
@ -40,32 +40,26 @@ func TestFindUTXO(t *testing.T) {
} }
} }
func TestAddNewTransferAmount(t *testing.T) { func TestAddNewUserTransfer(t *testing.T) {
bc := CreateBlockchain("minh") bc, utxoPool := CreateBlockchain("minh")
bc = bc.AddNewTransferAmount("minh", "alok", 3) if !bc.AddNewUserTransfer(utxoPool, "minh", "alok", 3) {
t.Error("Failed to add new transfer to alok.")
if bc == nil {
t.Error("Failed to add new transfer to alok")
} }
bc = bc.AddNewTransferAmount("minh", "rj", 100) if !bc.AddNewUserTransfer(utxoPool, "minh", "rj", 100) {
t.Error("Failed to add new transfer to rj.")
if bc == nil {
t.Error("Failed to add new transfer to rj")
} }
bc = bc.AddNewTransferAmount("minh", "stephen", DefaultCoinbaseValue-102) if bc.AddNewUserTransfer(utxoPool, "minh", "stephen", DefaultCoinbaseValue-102) {
t.Error("minh should not have enough fun to make the transfer.")
if bc != nil {
t.Error("minh should not have enough fun to make the transfer")
} }
} }
func TestVerifyNewBlock(t *testing.T) { func TestVerifyNewBlock(t *testing.T) {
bc := CreateBlockchain("minh") bc, utxoPool := CreateBlockchain("minh")
bc = bc.AddNewTransferAmount("minh", "alok", 3) bc.AddNewUserTransfer(utxoPool, "minh", "alok", 3)
bc = bc.AddNewTransferAmount("minh", "rj", 100) bc.AddNewUserTransfer(utxoPool, "minh", "rj", 100)
tx := bc.NewUTXOTransaction("minh", "mark", 10) tx := bc.NewUTXOTransaction("minh", "mark", 10)
if tx == nil { if tx == nil {
@ -73,7 +67,7 @@ func TestVerifyNewBlock(t *testing.T) {
} }
newBlock := NewBlock([]*Transaction{tx}, bc.blocks[len(bc.blocks)-1].Hash) newBlock := NewBlock([]*Transaction{tx}, bc.blocks[len(bc.blocks)-1].Hash)
if !bc.VerifyNewBlock(nil, newBlock) { if !bc.VerifyNewBlockAndUpdate(utxoPool, newBlock) {
t.Error("failed to add a new valid block.") t.Error("failed to add a new valid block.")
} }
} }

@ -47,7 +47,7 @@ func (tx *Transaction) SetID() {
} }
// NewCoinbaseTX creates a new coinbase transaction // NewCoinbaseTX creates a new coinbase transaction
func NewCoinbaseTX(to, data string) *Transaction { func NewCoinbaseTX(to, data string) (*Transaction, *UTXOPool) {
if data == "" { if data == "" {
data = fmt.Sprintf("Reward to '%s'", to) data = fmt.Sprintf("Reward to '%s'", to)
} }
@ -56,8 +56,14 @@ func NewCoinbaseTX(to, data string) *Transaction {
txout := TXOutput{DefaultCoinbaseValue, to} txout := TXOutput{DefaultCoinbaseValue, to}
tx := Transaction{nil, []TXInput{txin}, []TXOutput{txout}} tx := Transaction{nil, []TXInput{txin}, []TXOutput{txout}}
tx.SetID() tx.SetID()
txID := hex.EncodeToString(tx.ID)
return &tx var utxoPool UTXOPool
utxoPool.utxo = make(map[string]map[string]int)
utxoPool.utxo[to] = make(map[string]int)
utxoPool.utxo[to][txID] = DefaultCoinbaseValue
return &tx, &utxoPool
} }
// Used for debuging. // Used for debuging.

@ -5,7 +5,7 @@ import (
) )
func TestNewCoinbaseTX(t *testing.T) { func TestNewCoinbaseTX(t *testing.T) {
if NewCoinbaseTX("minh", genesisCoinbaseData) == nil { if cbtx, utxoPool := NewCoinbaseTX("minh", genesisCoinbaseData); cbtx == nil || utxoPool == nil {
t.Errorf("failed to create a coinbase transaction.") t.Errorf("failed to create a coinbase transaction.")
} }
} }

@ -1,8 +1,88 @@
package blockchain package blockchain
import "encoding/hex"
// UTXOPool stores transactions and balance associated with each address. // UTXOPool stores transactions and balance associated with each address.
type UTXOPool struct { type UTXOPool struct {
// Mapping from address to a map of transaction id to that balance. // Mapping from address to a map of transaction id to that balance.
// The assumption here is that one address only appears once output array in a transaction. // The assumption here is that one address only appears once output array in a transaction.
utxo map[string]map[string]int utxo map[string]map[string]int
} }
// VerifyTransactions verifies if a list of transactions valid.
func (utxopool *UTXOPool) VerifyTransactions(transactions []*Transaction) bool {
spentTXOs := make(map[string]map[string]bool)
if utxopool != nil {
for _, tx := range transactions {
inTotal := 0
// Calculate the sum of TxInput
for _, in := range tx.TxInput {
inTxID := hex.EncodeToString(in.TxID)
// Check if the transaction with the addres is spent or not.
if val, ok := spentTXOs[in.Address][inTxID]; ok {
if val {
return false
}
}
// Mark the transactions with the addres spent.
if _, ok := spentTXOs[in.Address]; ok {
spentTXOs[in.Address][inTxID] = true
} else {
spentTXOs[in.Address] = make(map[string]bool)
spentTXOs[in.Address][inTxID] = true
}
// Sum the balance up to the inTotal.
if val, ok := utxopool.utxo[in.Address][inTxID]; ok {
inTotal += val
} else {
return false
}
}
outTotal := 0
// Calculate the sum of TxOutput
for _, out := range tx.TxOutput {
outTotal += out.Value
}
if inTotal != outTotal {
return false
}
}
}
return true
}
// VerifyAndUpdate verifies a list of transactions and update utxoPool.
func (utxopool *UTXOPool) VerifyAndUpdate(transactions []*Transaction) bool {
if utxopool.VerifyTransactions(transactions) {
utxopool.Update(transactions)
return true
}
return false
}
// Update utxo balances with a list of new transactions.
func (utxopool *UTXOPool) Update(transactions []*Transaction) {
if utxopool != nil {
for _, tx := range transactions {
curTxID := hex.EncodeToString(tx.ID)
// Remove
for _, in := range tx.TxInput {
inTxID := hex.EncodeToString(in.TxID)
delete(utxopool.utxo[in.Address], inTxID)
}
// Update
for _, out := range tx.TxOutput {
if _, ok := utxopool.utxo[out.Address]; ok {
utxopool.utxo[out.Address][curTxID] = out.Value
} else {
utxopool.utxo[out.Address] = make(map[string]int)
utxopool.utxo[out.Address][curTxID] = out.Value
}
}
}
}
}

@ -0,0 +1,11 @@
package blockchain
import (
"testing"
)
func TestVerifyTransactions(t *testing.T) {
if cbtx, utxoPool := NewCoinbaseTX("minh", genesisCoinbaseData); cbtx == nil || utxoPool == nil {
t.Errorf("failed to create a coinbase transaction.")
}
}
Loading…
Cancel
Save