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) {
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)
serializedValue := block.Serialize()

@ -3,6 +3,7 @@ package blockchain
import (
"bytes"
"encoding/hex"
"fmt"
)
// Blockchain keeps a sequence of Blocks
@ -130,20 +131,21 @@ func (bc *Blockchain) NewUTXOTransaction(from, to string, amount int) *Transacti
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.
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)
if tx != nil {
newBlock := NewBlock([]*Transaction{tx}, bc.blocks[len(bc.blocks)-1].Hash)
bc.blocks = append(bc.blocks, newBlock)
return bc
if bc.VerifyNewBlockAndUpdate(utxoPool, newBlock) {
return true
}
return nil
}
return false
}
// VerifyNewBlock verifies if the new coming block is valid for the current blockchain.
func (bc *Blockchain) VerifyNewBlock(utxopool *UTXOPool, block *Block) bool {
// VerifyNewBlockAndUpdate verifies if the new coming block is valid for the current blockchain.
func (bc *Blockchain) VerifyNewBlockAndUpdate(utxopool *UTXOPool, block *Block) bool {
length := len(bc.blocks)
if bytes.Compare(block.PrevBlockHash, bc.blocks[length-1].Hash) != 0 {
return false
@ -152,40 +154,21 @@ func (bc *Blockchain) VerifyNewBlock(utxopool *UTXOPool, block *Block) bool {
return false
}
if utxopool != nil {
for _, tx := range block.Transactions {
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 {
if utxopool != nil && !utxopool.VerifyAndUpdate(block.Transactions) {
fmt.Println("Minh2")
return false
}
}
}
return true
}
// 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.
// 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)
bc := Blockchain{[]*Block{genesis}}
return &bc
return &bc, utxoPool
}

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

@ -47,7 +47,7 @@ func (tx *Transaction) SetID() {
}
// NewCoinbaseTX creates a new coinbase transaction
func NewCoinbaseTX(to, data string) *Transaction {
func NewCoinbaseTX(to, data string) (*Transaction, *UTXOPool) {
if data == "" {
data = fmt.Sprintf("Reward to '%s'", to)
}
@ -56,8 +56,14 @@ func NewCoinbaseTX(to, data string) *Transaction {
txout := TXOutput{DefaultCoinbaseValue, to}
tx := Transaction{nil, []TXInput{txin}, []TXOutput{txout}}
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.

@ -5,7 +5,7 @@ import (
)
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.")
}
}

@ -1,8 +1,88 @@
package blockchain
import "encoding/hex"
// UTXOPool stores transactions and balance associated with each address.
type UTXOPool struct {
// 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.
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