This commit is contained in:
sandeepreddyjnv 2021-06-29 03:57:09 -04:00 committed by GitHub
commit 8b0d700dec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 2279 additions and 42 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*.db
*.dat

11
README.md Normal file
View File

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

52
base58.go Normal file
View File

@ -0,0 +1,52 @@
package main
import (
"bytes"
"math/big"
)
var b58Alphabet = []byte("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz")
// Base58Encode encodes a byte array to Base58
func Base58Encode(input []byte) []byte {
var result []byte
x := big.NewInt(0).SetBytes(input)
base := big.NewInt(int64(len(b58Alphabet)))
zero := big.NewInt(0)
mod := &big.Int{}
for x.Cmp(zero) != 0 {
x.DivMod(x, base, mod)
result = append(result, b58Alphabet[mod.Int64()])
}
// https://en.bitcoin.it/wiki/Base58Check_encoding#Version_bytes
if input[0] == 0x00 {
result = append(result, b58Alphabet[0])
}
ReverseBytes(result)
return result
}
// Base58Decode decodes Base58-encoded data
func Base58Decode(input []byte) []byte {
result := big.NewInt(0)
for _, b := range input {
charIndex := bytes.IndexByte(b58Alphabet, b)
result.Mul(result, big.NewInt(58))
result.Add(result, big.NewInt(int64(charIndex)))
}
decoded := result.Bytes()
if input[0] == b58Alphabet[0] {
decoded = append([]byte{0x00}, decoded...)
}
return decoded
}

24
base58_test.go Normal file
View File

@ -0,0 +1,24 @@
package main
import (
"encoding/hex"
"log"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestBase58(t *testing.T) {
rawHash := "00010966776006953D5567439E5E39F86A0D273BEED61967F6"
hash, err := hex.DecodeString(rawHash)
if err != nil {
log.Fatal(err)
}
encoded := Base58Encode(hash)
assert.Equal(t, "16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM", string(encoded))
decoded := Base58Decode([]byte("16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM"))
assert.Equal(t, strings.ToLower("00010966776006953D5567439E5E39F86A0D273BEED61967F6"), hex.EncodeToString(decoded))
}

View File

@ -2,36 +2,72 @@ package main
import (
"bytes"
"crypto/sha256"
"strconv"
"encoding/gob"
"log"
"time"
)
// Block keeps block headers
// Block represents a block in the blockchain
type Block struct {
Timestamp int64
Data []byte
Transactions []*Transaction
PrevBlockHash []byte
Hash []byte
}
// SetHash calculates and sets block hash
func (b *Block) SetHash() {
timestamp := []byte(strconv.FormatInt(b.Timestamp, 10))
headers := bytes.Join([][]byte{b.PrevBlockHash, b.Data, timestamp}, []byte{})
hash := sha256.Sum256(headers)
b.Hash = hash[:]
Nonce int
Height int
}
// NewBlock creates and returns Block
func NewBlock(data string, prevBlockHash []byte) *Block {
block := &Block{time.Now().Unix(), []byte(data), prevBlockHash, []byte{}}
block.SetHash()
func NewBlock(transactions []*Transaction, prevBlockHash []byte, height int) *Block {
block := &Block{time.Now().Unix(), transactions, prevBlockHash, []byte{}, 0, height}
pow := NewProofOfWork(block)
nonce, hash := pow.Run()
block.Hash = hash[:]
block.Nonce = nonce
return block
}
// NewGenesisBlock creates and returns genesis Block
func NewGenesisBlock() *Block {
return NewBlock("Genesis Block", []byte{})
func NewGenesisBlock(coinbase *Transaction) *Block {
return NewBlock([]*Transaction{coinbase}, []byte{}, 0)
}
// HashTransactions returns a hash of the transactions in the block
func (b *Block) HashTransactions() []byte {
var transactions [][]byte
for _, tx := range b.Transactions {
transactions = append(transactions, tx.Serialize())
}
mTree := NewMerkleTree(transactions)
return mTree.RootNode.Data
}
// Serialize serializes the block
func (b *Block) Serialize() []byte {
var result bytes.Buffer
encoder := gob.NewEncoder(&result)
err := encoder.Encode(b)
if err != nil {
log.Panic(err)
}
return result.Bytes()
}
// DeserializeBlock deserializes a block
func DeserializeBlock(d []byte) *Block {
var block Block
decoder := gob.NewDecoder(bytes.NewReader(d))
err := decoder.Decode(&block)
if err != nil {
log.Panic(err)
}
return &block
}

View File

@ -1,18 +1,360 @@
package main
// Blockchain keeps a sequence of Blocks
import (
"bytes"
"crypto/ecdsa"
"encoding/hex"
"errors"
"fmt"
"log"
"os"
"github.com/boltdb/bolt"
)
const dbFile = "blockchain_%s.db"
const blocksBucket = "blocks"
const genesisCoinbaseData = "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks"
// Blockchain implements interactions with a DB
type Blockchain struct {
blocks []*Block
tip []byte
db *bolt.DB
}
// AddBlock saves provided data as a block in the blockchain
func (bc *Blockchain) AddBlock(data string) {
prevBlock := bc.blocks[len(bc.blocks)-1]
newBlock := NewBlock(data, prevBlock.Hash)
bc.blocks = append(bc.blocks, newBlock)
// CreateBlockchain creates a new blockchain DB
func CreateBlockchain(address, nodeID string) *Blockchain {
dbFile := fmt.Sprintf(dbFile, nodeID)
if dbExists(dbFile) {
fmt.Println("Blockchain already exists.")
os.Exit(1)
}
var tip []byte
cbtx := NewCoinbaseTX(address, genesisCoinbaseData)
genesis := NewGenesisBlock(cbtx)
db, err := bolt.Open(dbFile, 0600, nil)
if err != nil {
log.Panic(err)
}
err = db.Update(func(tx *bolt.Tx) error {
b, err := tx.CreateBucket([]byte(blocksBucket))
if err != nil {
log.Panic(err)
}
err = b.Put(genesis.Hash, genesis.Serialize())
if err != nil {
log.Panic(err)
}
err = b.Put([]byte("l"), genesis.Hash)
if err != nil {
log.Panic(err)
}
tip = genesis.Hash
return nil
})
if err != nil {
log.Panic(err)
}
bc := Blockchain{tip, db}
return &bc
}
// NewBlockchain creates a new Blockchain with genesis Block
func NewBlockchain() *Blockchain {
return &Blockchain{[]*Block{NewGenesisBlock()}}
func NewBlockchain(nodeID string) *Blockchain {
dbFile := fmt.Sprintf(dbFile, nodeID)
if dbExists(dbFile) == false {
fmt.Println("No existing blockchain found. Create one first.")
os.Exit(1)
}
var tip []byte
db, err := bolt.Open(dbFile, 0600, nil)
if err != nil {
log.Panic(err)
}
err = db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(blocksBucket))
tip = b.Get([]byte("l"))
return nil
})
if err != nil {
log.Panic(err)
}
bc := Blockchain{tip, db}
return &bc
}
// AddBlock saves the block into the blockchain
func (bc *Blockchain) AddBlock(block *Block) {
err := bc.db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(blocksBucket))
blockInDb := b.Get(block.Hash)
if blockInDb != nil {
return nil
}
blockData := block.Serialize()
err := b.Put(block.Hash, blockData)
if err != nil {
log.Panic(err)
}
lastHash := b.Get([]byte("l"))
lastBlockData := b.Get(lastHash)
lastBlock := DeserializeBlock(lastBlockData)
if block.Height > lastBlock.Height {
err = b.Put([]byte("l"), block.Hash)
if err != nil {
log.Panic(err)
}
bc.tip = block.Hash
}
return nil
})
if err != nil {
log.Panic(err)
}
}
// FindTransaction finds a transaction by its ID
func (bc *Blockchain) FindTransaction(ID []byte) (Transaction, error) {
bci := bc.Iterator()
for {
block := bci.Next()
for _, tx := range block.Transactions {
if bytes.Compare(tx.ID, ID) == 0 {
return *tx, nil
}
}
if len(block.PrevBlockHash) == 0 {
break
}
}
return Transaction{}, errors.New("Transaction is not found")
}
// FindUTXO finds all unspent transaction outputs and returns transactions with spent outputs removed
func (bc *Blockchain) FindUTXO() map[string]TXOutputs {
UTXO := make(map[string]TXOutputs)
spentTXOs := make(map[string][]int)
bci := bc.Iterator()
for {
block := bci.Next()
for _, tx := range block.Transactions {
txID := hex.EncodeToString(tx.ID)
Outputs:
for outIdx, out := range tx.Vout {
// Was the output spent?
if spentTXOs[txID] != nil {
for _, spentOutIdx := range spentTXOs[txID] {
if spentOutIdx == outIdx {
continue Outputs
}
}
}
outs := UTXO[txID]
outs.Outputs = append(outs.Outputs, out)
UTXO[txID] = outs
}
if tx.IsCoinbase() == false {
for _, in := range tx.Vin {
inTxID := hex.EncodeToString(in.Txid)
spentTXOs[inTxID] = append(spentTXOs[inTxID], in.Vout)
}
}
}
if len(block.PrevBlockHash) == 0 {
break
}
}
return UTXO
}
// Iterator returns a BlockchainIterat
func (bc *Blockchain) Iterator() *BlockchainIterator {
bci := &BlockchainIterator{bc.tip, bc.db}
return bci
}
// GetBestHeight returns the height of the latest block
func (bc *Blockchain) GetBestHeight() int {
var lastBlock Block
err := bc.db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(blocksBucket))
lastHash := b.Get([]byte("l"))
blockData := b.Get(lastHash)
lastBlock = *DeserializeBlock(blockData)
return nil
})
if err != nil {
log.Panic(err)
}
return lastBlock.Height
}
// GetBlock finds a block by its hash and returns it
func (bc *Blockchain) GetBlock(blockHash []byte) (Block, error) {
var block Block
err := bc.db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(blocksBucket))
blockData := b.Get(blockHash)
if blockData == nil {
return errors.New("Block is not found.")
}
block = *DeserializeBlock(blockData)
return nil
})
if err != nil {
return block, err
}
return block, nil
}
// GetBlockHashes returns a list of hashes of all the blocks in the chain
func (bc *Blockchain) GetBlockHashes() [][]byte {
var blocks [][]byte
bci := bc.Iterator()
for {
block := bci.Next()
blocks = append(blocks, block.Hash)
if len(block.PrevBlockHash) == 0 {
break
}
}
return blocks
}
// MineBlock mines a new block with the provided transactions
func (bc *Blockchain) MineBlock(transactions []*Transaction) *Block {
var lastHash []byte
var lastHeight int
for _, tx := range transactions {
// TODO: ignore transaction if it's not valid
if bc.VerifyTransaction(tx) != true {
log.Panic("ERROR: Invalid transaction")
}
}
err := bc.db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(blocksBucket))
lastHash = b.Get([]byte("l"))
blockData := b.Get(lastHash)
block := DeserializeBlock(blockData)
lastHeight = block.Height
return nil
})
if err != nil {
log.Panic(err)
}
newBlock := NewBlock(transactions, lastHash, lastHeight+1)
err = bc.db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(blocksBucket))
err := b.Put(newBlock.Hash, newBlock.Serialize())
if err != nil {
log.Panic(err)
}
err = b.Put([]byte("l"), newBlock.Hash)
if err != nil {
log.Panic(err)
}
bc.tip = newBlock.Hash
return nil
})
if err != nil {
log.Panic(err)
}
return newBlock
}
// SignTransaction signs inputs of a Transaction
func (bc *Blockchain) SignTransaction(tx *Transaction, privKey ecdsa.PrivateKey) {
prevTXs := make(map[string]Transaction)
for _, vin := range tx.Vin {
prevTX, err := bc.FindTransaction(vin.Txid)
if err != nil {
log.Panic(err)
}
prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX
}
tx.Sign(privKey, prevTXs)
}
// VerifyTransaction verifies transaction input signatures
func (bc *Blockchain) VerifyTransaction(tx *Transaction) bool {
if tx.IsCoinbase() {
return true
}
prevTXs := make(map[string]Transaction)
for _, vin := range tx.Vin {
prevTX, err := bc.FindTransaction(vin.Txid)
if err != nil {
log.Panic(err)
}
prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX
}
return tx.Verify(prevTXs)
}
func dbExists(dbFile string) bool {
if _, err := os.Stat(dbFile); os.IsNotExist(err) {
return false
}
return true
}

34
blockchain_iterator.go Normal file
View File

@ -0,0 +1,34 @@
package main
import (
"log"
"github.com/boltdb/bolt"
)
// BlockchainIterator is used to iterate over blockchain blocks
type BlockchainIterator struct {
currentHash []byte
db *bolt.DB
}
// Next returns next block starting from the tip
func (i *BlockchainIterator) Next() *Block {
var block *Block
err := i.db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(blocksBucket))
encodedBlock := b.Get(i.currentHash)
block = DeserializeBlock(encodedBlock)
return nil
})
if err != nil {
log.Panic(err)
}
i.currentHash = block.PrevBlockHash
return block
}

155
cli.go Normal file
View File

@ -0,0 +1,155 @@
package main
import (
"flag"
"fmt"
"log"
"os"
)
// CLI responsible for processing command line arguments
type CLI struct{}
func (cli *CLI) printUsage() {
fmt.Println("Usage:")
fmt.Println(" createblockchain -address ADDRESS - Create a blockchain and send genesis block reward to ADDRESS")
fmt.Println(" createwallet - Generates a new key-pair and saves it into the wallet file")
fmt.Println(" getbalance -address ADDRESS - Get balance of ADDRESS")
fmt.Println(" listaddresses - Lists all addresses from the wallet file")
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 -mine - Send AMOUNT of coins from FROM address to TO. Mine on the same node, when -mine is set.")
fmt.Println(" startnode -miner ADDRESS - Start a node with ID specified in NODE_ID env. var. -miner enables mining")
}
func (cli *CLI) validateArgs() {
if len(os.Args) < 2 {
cli.printUsage()
os.Exit(1)
}
}
// Run parses command line arguments and processes commands
func (cli *CLI) Run() {
cli.validateArgs()
nodeID := os.Getenv("NODE_ID")
if nodeID == "" {
fmt.Printf("NODE_ID env. var is not set!")
os.Exit(1)
}
getBalanceCmd := flag.NewFlagSet("getbalance", flag.ExitOnError)
createBlockchainCmd := flag.NewFlagSet("createblockchain", flag.ExitOnError)
createWalletCmd := flag.NewFlagSet("createwallet", flag.ExitOnError)
listAddressesCmd := flag.NewFlagSet("listaddresses", flag.ExitOnError)
printChainCmd := flag.NewFlagSet("printchain", flag.ExitOnError)
reindexUTXOCmd := flag.NewFlagSet("reindexutxo", flag.ExitOnError)
sendCmd := flag.NewFlagSet("send", flag.ExitOnError)
startNodeCmd := flag.NewFlagSet("startnode", flag.ExitOnError)
getBalanceAddress := getBalanceCmd.String("address", "", "The address to get balance for")
createBlockchainAddress := createBlockchainCmd.String("address", "", "The address to send genesis block reward to")
sendFrom := sendCmd.String("from", "", "Source wallet address")
sendTo := sendCmd.String("to", "", "Destination wallet address")
sendAmount := sendCmd.Int("amount", 0, "Amount to send")
sendMine := sendCmd.Bool("mine", false, "Mine immediately on the same node")
startNodeMiner := startNodeCmd.String("miner", "", "Enable mining mode and send reward to ADDRESS")
switch os.Args[1] {
case "getbalance":
err := getBalanceCmd.Parse(os.Args[2:])
if err != nil {
log.Panic(err)
}
case "createblockchain":
err := createBlockchainCmd.Parse(os.Args[2:])
if err != nil {
log.Panic(err)
}
case "createwallet":
err := createWalletCmd.Parse(os.Args[2:])
if err != nil {
log.Panic(err)
}
case "listaddresses":
err := listAddressesCmd.Parse(os.Args[2:])
if err != nil {
log.Panic(err)
}
case "printchain":
err := printChainCmd.Parse(os.Args[2:])
if err != nil {
log.Panic(err)
}
case "reindexutxo":
err := reindexUTXOCmd.Parse(os.Args[2:])
if err != nil {
log.Panic(err)
}
case "send":
err := sendCmd.Parse(os.Args[2:])
if err != nil {
log.Panic(err)
}
case "startnode":
err := startNodeCmd.Parse(os.Args[2:])
if err != nil {
log.Panic(err)
}
default:
cli.printUsage()
os.Exit(1)
}
if getBalanceCmd.Parsed() {
if *getBalanceAddress == "" {
getBalanceCmd.Usage()
os.Exit(1)
}
cli.getBalance(*getBalanceAddress, nodeID)
}
if createBlockchainCmd.Parsed() {
if *createBlockchainAddress == "" {
createBlockchainCmd.Usage()
os.Exit(1)
}
cli.createBlockchain(*createBlockchainAddress, nodeID)
}
if createWalletCmd.Parsed() {
cli.createWallet(nodeID)
}
if listAddressesCmd.Parsed() {
cli.listAddresses(nodeID)
}
if printChainCmd.Parsed() {
cli.printChain(nodeID)
}
if reindexUTXOCmd.Parsed() {
cli.reindexUTXO(nodeID)
}
if sendCmd.Parsed() {
if *sendFrom == "" || *sendTo == "" || *sendAmount <= 0 {
sendCmd.Usage()
os.Exit(1)
}
cli.send(*sendFrom, *sendTo, *sendAmount, nodeID, *sendMine)
}
if startNodeCmd.Parsed() {
nodeID := os.Getenv("NODE_ID")
if nodeID == "" {
startNodeCmd.Usage()
os.Exit(1)
}
cli.startNode(nodeID, *startNodeMiner)
}
}

19
cli_createblockchain.go Normal file
View File

@ -0,0 +1,19 @@
package main
import (
"fmt"
"log"
)
func (cli *CLI) createBlockchain(address, nodeID string) {
if !ValidateAddress(address) {
log.Panic("ERROR: Address is not valid")
}
bc := CreateBlockchain(address, nodeID)
defer bc.db.Close()
UTXOSet := UTXOSet{bc}
UTXOSet.Reindex()
fmt.Println("Done!")
}

11
cli_createwallet.go Normal file
View File

@ -0,0 +1,11 @@
package main
import "fmt"
func (cli *CLI) createWallet(nodeID string) {
wallets, _ := NewWallets(nodeID)
address := wallets.CreateWallet()
wallets.SaveToFile(nodeID)
fmt.Printf("Your new address: %s\n", address)
}

26
cli_getbalance.go Normal file
View File

@ -0,0 +1,26 @@
package main
import (
"fmt"
"log"
)
func (cli *CLI) getBalance(address, nodeID string) {
if !ValidateAddress(address) {
log.Panic("ERROR: Address is not valid")
}
bc := NewBlockchain(nodeID)
UTXOSet := UTXOSet{bc}
defer bc.db.Close()
balance := 0
pubKeyHash := Base58Decode([]byte(address))
pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-4]
UTXOs := UTXOSet.FindUTXO(pubKeyHash)
for _, out := range UTXOs {
balance += out.Value
}
fmt.Printf("Balance of '%s': %d\n", address, balance)
}

18
cli_listaddress.go Normal file
View File

@ -0,0 +1,18 @@
package main
import (
"fmt"
"log"
)
func (cli *CLI) listAddresses(nodeID string) {
wallets, err := NewWallets(nodeID)
if err != nil {
log.Panic(err)
}
addresses := wallets.GetAddresses()
for _, address := range addresses {
fmt.Println(address)
}
}

31
cli_printchain.go Normal file
View File

@ -0,0 +1,31 @@
package main
import (
"fmt"
"strconv"
)
func (cli *CLI) printChain(nodeID string) {
bc := NewBlockchain(nodeID)
defer bc.db.Close()
bci := bc.Iterator()
for {
block := bci.Next()
fmt.Printf("============ Block %x ============\n", block.Hash)
fmt.Printf("Height: %d\n", block.Height)
fmt.Printf("Prev. block: %x\n", block.PrevBlockHash)
pow := NewProofOfWork(block)
fmt.Printf("PoW: %s\n\n", strconv.FormatBool(pow.Validate()))
for _, tx := range block.Transactions {
fmt.Println(tx)
}
fmt.Printf("\n\n")
if len(block.PrevBlockHash) == 0 {
break
}
}
}

12
cli_reindexutxo.go Normal file
View File

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

39
cli_send.go Normal file
View File

@ -0,0 +1,39 @@
package main
import (
"fmt"
"log"
)
func (cli *CLI) send(from, to string, amount int, nodeID string, mineNow bool) {
if !ValidateAddress(from) {
log.Panic("ERROR: Sender address is not valid")
}
if !ValidateAddress(to) {
log.Panic("ERROR: Recipient address is not valid")
}
bc := NewBlockchain(nodeID)
UTXOSet := UTXOSet{bc}
defer bc.db.Close()
wallets, err := NewWallets(nodeID)
if err != nil {
log.Panic(err)
}
wallet := wallets.GetWallet(from)
tx := NewUTXOTransaction(&wallet, to, amount, &UTXOSet)
if mineNow {
cbTx := NewCoinbaseTX(from, "")
txs := []*Transaction{cbTx, tx}
newBlock := bc.MineBlock(txs)
UTXOSet.Update(newBlock)
} else {
sendTx(knownNodes[0], tx)
}
fmt.Println("Success!")
}

18
cli_startnode.go Normal file
View File

@ -0,0 +1,18 @@
package main
import (
"fmt"
"log"
)
func (cli *CLI) startNode(nodeID, minerAddress string) {
fmt.Printf("Starting node %s\n", nodeID)
if len(minerAddress) > 0 {
if ValidateAddress(minerAddress) {
fmt.Println("Mining is on. Address to receive rewards: ", minerAddress)
} else {
log.Panic("Wrong miner address!")
}
}
StartServer(nodeID, minerAddress)
}

17
main.go
View File

@ -1,19 +1,6 @@
package main
import (
"fmt"
)
func main() {
bc := NewBlockchain()
bc.AddBlock("Send 1 BTC to Ivan")
bc.AddBlock("Send 2 more BTC to Ivan")
for _, block := range bc.blocks {
fmt.Printf("Prev. hash: %x\n", block.PrevBlockHash)
fmt.Printf("Data: %s\n", block.Data)
fmt.Printf("Hash: %x\n", block.Hash)
fmt.Println()
}
cli := CLI{}
cli.Run()
}

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")
}

86
proofofwork.go Normal file
View File

@ -0,0 +1,86 @@
package main
import (
"bytes"
"crypto/sha256"
"fmt"
"math"
"math/big"
)
var (
maxNonce = math.MaxInt64
)
const targetBits = 16
// ProofOfWork represents a proof-of-work
type ProofOfWork struct {
block *Block
target *big.Int
}
// NewProofOfWork builds and returns a ProofOfWork
func NewProofOfWork(b *Block) *ProofOfWork {
target := big.NewInt(1)
target.Lsh(target, uint(256-targetBits))
pow := &ProofOfWork{b, target}
return pow
}
func (pow *ProofOfWork) prepareData(nonce int) []byte {
data := bytes.Join(
[][]byte{
pow.block.PrevBlockHash,
pow.block.HashTransactions(),
IntToHex(pow.block.Timestamp),
IntToHex(int64(targetBits)),
IntToHex(int64(nonce)),
},
[]byte{},
)
return data
}
// Run performs a proof-of-work
func (pow *ProofOfWork) Run() (int, []byte) {
var hashInt big.Int
var hash [32]byte
nonce := 0
fmt.Printf("Mining a new block")
for nonce < maxNonce {
data := pow.prepareData(nonce)
hash = sha256.Sum256(data)
if math.Remainder(float64(nonce), 100000) == 0 {
fmt.Printf("\r%x", hash)
}
hashInt.SetBytes(hash[:])
if hashInt.Cmp(pow.target) == -1 {
break
} else {
nonce++
}
}
fmt.Print("\n\n")
return nonce, hash[:]
}
// Validate validates block's PoW
func (pow *ProofOfWork) Validate() bool {
var hashInt big.Int
data := pow.prepareData(pow.block.Nonce)
hash := sha256.Sum256(data)
hashInt.SetBytes(hash[:])
isValid := hashInt.Cmp(pow.target) == -1
return isValid
}

465
server.go Normal file
View File

@ -0,0 +1,465 @@
package main
import (
"bytes"
"encoding/gob"
"encoding/hex"
"fmt"
"io"
"io/ioutil"
"log"
"net"
)
const protocol = "tcp"
const nodeVersion = 1
const commandLength = 12
var nodeAddress string
var miningAddress string
var knownNodes = []string{"localhost:3000"}
var blocksInTransit = [][]byte{}
var mempool = make(map[string]Transaction)
type addr struct {
AddrList []string
}
type block struct {
AddrFrom string
Block []byte
}
type getblocks struct {
AddrFrom string
}
type getdata struct {
AddrFrom string
Type string
ID []byte
}
type inv struct {
AddrFrom string
Type string
Items [][]byte
}
type tx struct {
AddFrom string
Transaction []byte
}
type verzion struct {
Version int
BestHeight int
AddrFrom string
}
func commandToBytes(command string) []byte {
var bytes [commandLength]byte
for i, c := range command {
bytes[i] = byte(c)
}
return bytes[:]
}
func bytesToCommand(bytes []byte) string {
var command []byte
for _, b := range bytes {
if b != 0x0 {
command = append(command, b)
}
}
return fmt.Sprintf("%s", command)
}
func extractCommand(request []byte) []byte {
return request[:commandLength]
}
func requestBlocks() {
for _, node := range knownNodes {
sendGetBlocks(node)
}
}
func sendAddr(address string) {
nodes := addr{knownNodes}
nodes.AddrList = append(nodes.AddrList, nodeAddress)
payload := gobEncode(nodes)
request := append(commandToBytes("addr"), payload...)
sendData(address, request)
}
func sendBlock(addr string, b *Block) {
data := block{nodeAddress, b.Serialize()}
payload := gobEncode(data)
request := append(commandToBytes("block"), payload...)
sendData(addr, request)
}
func sendData(addr string, data []byte) {
conn, err := net.Dial(protocol, addr)
if err != nil {
fmt.Printf("%s is not available\n", addr)
var updatedNodes []string
for _, node := range knownNodes {
if node != addr {
updatedNodes = append(updatedNodes, node)
}
}
knownNodes = updatedNodes
return
}
defer conn.Close()
_, err = io.Copy(conn, bytes.NewReader(data))
if err != nil {
log.Panic(err)
}
}
func sendInv(address, kind string, items [][]byte) {
inventory := inv{nodeAddress, kind, items}
payload := gobEncode(inventory)
request := append(commandToBytes("inv"), payload...)
sendData(address, request)
}
func sendGetBlocks(address string) {
payload := gobEncode(getblocks{nodeAddress})
request := append(commandToBytes("getblocks"), payload...)
sendData(address, request)
}
func sendGetData(address, kind string, id []byte) {
payload := gobEncode(getdata{nodeAddress, kind, id})
request := append(commandToBytes("getdata"), payload...)
sendData(address, request)
}
func sendTx(addr string, tnx *Transaction) {
data := tx{nodeAddress, tnx.Serialize()}
payload := gobEncode(data)
request := append(commandToBytes("tx"), payload...)
sendData(addr, request)
}
func sendVersion(addr string, bc *Blockchain) {
bestHeight := bc.GetBestHeight()
payload := gobEncode(verzion{nodeVersion, bestHeight, nodeAddress})
request := append(commandToBytes("version"), payload...)
sendData(addr, request)
}
func handleAddr(request []byte) {
var buff bytes.Buffer
var payload addr
buff.Write(request[commandLength:])
dec := gob.NewDecoder(&buff)
err := dec.Decode(&payload)
if err != nil {
log.Panic(err)
}
knownNodes = append(knownNodes, payload.AddrList...)
fmt.Printf("There are %d known nodes now!\n", len(knownNodes))
requestBlocks()
}
func handleBlock(request []byte, bc *Blockchain) {
var buff bytes.Buffer
var payload block
buff.Write(request[commandLength:])
dec := gob.NewDecoder(&buff)
err := dec.Decode(&payload)
if err != nil {
log.Panic(err)
}
blockData := payload.Block
block := DeserializeBlock(blockData)
fmt.Println("Recevied a new block!")
bc.AddBlock(block)
fmt.Printf("Added block %x\n", block.Hash)
if len(blocksInTransit) > 0 {
blockHash := blocksInTransit[0]
sendGetData(payload.AddrFrom, "block", blockHash)
blocksInTransit = blocksInTransit[1:]
} else {
UTXOSet := UTXOSet{bc}
UTXOSet.Reindex()
}
}
func handleInv(request []byte, bc *Blockchain) {
var buff bytes.Buffer
var payload inv
buff.Write(request[commandLength:])
dec := gob.NewDecoder(&buff)
err := dec.Decode(&payload)
if err != nil {
log.Panic(err)
}
fmt.Printf("Recevied inventory with %d %s\n", len(payload.Items), payload.Type)
if payload.Type == "block" {
blocksInTransit = payload.Items
blockHash := payload.Items[0]
sendGetData(payload.AddrFrom, "block", blockHash)
newInTransit := [][]byte{}
for _, b := range blocksInTransit {
if bytes.Compare(b, blockHash) != 0 {
newInTransit = append(newInTransit, b)
}
}
blocksInTransit = newInTransit
}
if payload.Type == "tx" {
txID := payload.Items[0]
if mempool[hex.EncodeToString(txID)].ID == nil {
sendGetData(payload.AddrFrom, "tx", txID)
}
}
}
func handleGetBlocks(request []byte, bc *Blockchain) {
var buff bytes.Buffer
var payload getblocks
buff.Write(request[commandLength:])
dec := gob.NewDecoder(&buff)
err := dec.Decode(&payload)
if err != nil {
log.Panic(err)
}
blocks := bc.GetBlockHashes()
sendInv(payload.AddrFrom, "block", blocks)
}
func handleGetData(request []byte, bc *Blockchain) {
var buff bytes.Buffer
var payload getdata
buff.Write(request[commandLength:])
dec := gob.NewDecoder(&buff)
err := dec.Decode(&payload)
if err != nil {
log.Panic(err)
}
if payload.Type == "block" {
block, err := bc.GetBlock([]byte(payload.ID))
if err != nil {
return
}
sendBlock(payload.AddrFrom, &block)
}
if payload.Type == "tx" {
txID := hex.EncodeToString(payload.ID)
tx := mempool[txID]
sendTx(payload.AddrFrom, &tx)
// delete(mempool, txID)
}
}
func handleTx(request []byte, bc *Blockchain) {
var buff bytes.Buffer
var payload tx
buff.Write(request[commandLength:])
dec := gob.NewDecoder(&buff)
err := dec.Decode(&payload)
if err != nil {
log.Panic(err)
}
txData := payload.Transaction
tx := DeserializeTransaction(txData)
mempool[hex.EncodeToString(tx.ID)] = tx
if nodeAddress == knownNodes[0] {
for _, node := range knownNodes {
if node != nodeAddress && node != payload.AddFrom {
sendInv(node, "tx", [][]byte{tx.ID})
}
}
} else {
if len(mempool) >= 2 && len(miningAddress) > 0 {
MineTransactions:
var txs []*Transaction
for id := range mempool {
tx := mempool[id]
if bc.VerifyTransaction(&tx) {
txs = append(txs, &tx)
}
}
if len(txs) == 0 {
fmt.Println("All transactions are invalid! Waiting for new ones...")
return
}
cbTx := NewCoinbaseTX(miningAddress, "")
txs = append(txs, cbTx)
newBlock := bc.MineBlock(txs)
UTXOSet := UTXOSet{bc}
UTXOSet.Reindex()
fmt.Println("New block is mined!")
for _, tx := range txs {
txID := hex.EncodeToString(tx.ID)
delete(mempool, txID)
}
for _, node := range knownNodes {
if node != nodeAddress {
sendInv(node, "block", [][]byte{newBlock.Hash})
}
}
if len(mempool) > 0 {
goto MineTransactions
}
}
}
}
func handleVersion(request []byte, bc *Blockchain) {
var buff bytes.Buffer
var payload verzion
buff.Write(request[commandLength:])
dec := gob.NewDecoder(&buff)
err := dec.Decode(&payload)
if err != nil {
log.Panic(err)
}
myBestHeight := bc.GetBestHeight()
foreignerBestHeight := payload.BestHeight
if myBestHeight < foreignerBestHeight {
sendGetBlocks(payload.AddrFrom)
} else if myBestHeight > foreignerBestHeight {
sendVersion(payload.AddrFrom, bc)
}
// sendAddr(payload.AddrFrom)
if !nodeIsKnown(payload.AddrFrom) {
knownNodes = append(knownNodes, payload.AddrFrom)
}
}
func handleConnection(conn net.Conn, bc *Blockchain) {
request, err := ioutil.ReadAll(conn)
if err != nil {
log.Panic(err)
}
command := bytesToCommand(request[:commandLength])
fmt.Printf("Received %s command\n", command)
switch command {
case "addr":
handleAddr(request)
case "block":
handleBlock(request, bc)
case "inv":
handleInv(request, bc)
case "getblocks":
handleGetBlocks(request, bc)
case "getdata":
handleGetData(request, bc)
case "tx":
handleTx(request, bc)
case "version":
handleVersion(request, bc)
default:
fmt.Println("Unknown command!")
}
conn.Close()
}
// StartServer starts a node
func StartServer(nodeID, minerAddress string) {
nodeAddress = fmt.Sprintf("localhost:%s", nodeID)
miningAddress = minerAddress
ln, err := net.Listen(protocol, nodeAddress)
if err != nil {
log.Panic(err)
}
defer ln.Close()
bc := NewBlockchain(nodeID)
if nodeAddress != knownNodes[0] {
sendVersion(knownNodes[0], bc)
}
for {
conn, err := ln.Accept()
if err != nil {
log.Panic(err)
}
go handleConnection(conn, bc)
}
}
func gobEncode(data interface{}) []byte {
var buff bytes.Buffer
enc := gob.NewEncoder(&buff)
err := enc.Encode(data)
if err != nil {
log.Panic(err)
}
return buff.Bytes()
}
func nodeIsKnown(addr string) bool {
for _, node := range knownNodes {
if node == addr {
return true
}
}
return false
}

245
transaction.go Normal file
View File

@ -0,0 +1,245 @@
package main
import (
"bytes"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"math/big"
"strings"
"encoding/gob"
"encoding/hex"
"fmt"
"log"
)
const subsidy = 10
// Transaction represents a Bitcoin transaction
type Transaction struct {
ID []byte
Vin []TXInput
Vout []TXOutput
}
// IsCoinbase checks whether the transaction is coinbase
func (tx Transaction) IsCoinbase() bool {
return len(tx.Vin) == 1 && len(tx.Vin[0].Txid) == 0 && tx.Vin[0].Vout == -1
}
// Serialize returns a serialized Transaction
func (tx Transaction) Serialize() []byte {
var encoded bytes.Buffer
enc := gob.NewEncoder(&encoded)
err := enc.Encode(tx)
if err != nil {
log.Panic(err)
}
return encoded.Bytes()
}
// Hash returns the hash of the Transaction
func (tx *Transaction) Hash() []byte {
var hash [32]byte
txCopy := *tx
txCopy.ID = []byte{}
hash = sha256.Sum256(txCopy.Serialize())
return hash[:]
}
// Sign signs each input of a Transaction
func (tx *Transaction) Sign(privKey ecdsa.PrivateKey, prevTXs map[string]Transaction) {
if tx.IsCoinbase() {
return
}
for _, vin := range tx.Vin {
if prevTXs[hex.EncodeToString(vin.Txid)].ID == nil {
log.Panic("ERROR: Previous transaction is not correct")
}
}
txCopy := tx.TrimmedCopy()
for inID, vin := range txCopy.Vin {
prevTx := prevTXs[hex.EncodeToString(vin.Txid)]
txCopy.Vin[inID].Signature = nil
txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubKeyHash
dataToSign := fmt.Sprintf("%x\n", txCopy)
r, s, err := ecdsa.Sign(rand.Reader, &privKey, []byte(dataToSign))
if err != nil {
log.Panic(err)
}
signature := append(r.Bytes(), s.Bytes()...)
tx.Vin[inID].Signature = signature
txCopy.Vin[inID].PubKey = nil
}
}
// String returns a human-readable representation of a transaction
func (tx Transaction) String() string {
var lines []string
lines = append(lines, fmt.Sprintf("--- Transaction %x:", tx.ID))
for i, input := range tx.Vin {
lines = append(lines, fmt.Sprintf(" Input %d:", i))
lines = append(lines, fmt.Sprintf(" TXID: %x", input.Txid))
lines = append(lines, fmt.Sprintf(" Out: %d", input.Vout))
lines = append(lines, fmt.Sprintf(" Signature: %x", input.Signature))
lines = append(lines, fmt.Sprintf(" PubKey: %x", input.PubKey))
}
for i, output := range tx.Vout {
lines = append(lines, fmt.Sprintf(" Output %d:", i))
lines = append(lines, fmt.Sprintf(" Value: %d", output.Value))
lines = append(lines, fmt.Sprintf(" Script: %x", output.PubKeyHash))
}
return strings.Join(lines, "\n")
}
// TrimmedCopy creates a trimmed copy of Transaction to be used in signing
func (tx *Transaction) TrimmedCopy() Transaction {
var inputs []TXInput
var outputs []TXOutput
for _, vin := range tx.Vin {
inputs = append(inputs, TXInput{vin.Txid, vin.Vout, nil, nil})
}
for _, vout := range tx.Vout {
outputs = append(outputs, TXOutput{vout.Value, vout.PubKeyHash})
}
txCopy := Transaction{tx.ID, inputs, outputs}
return txCopy
}
// Verify verifies signatures of Transaction inputs
func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool {
if tx.IsCoinbase() {
return true
}
for _, vin := range tx.Vin {
if prevTXs[hex.EncodeToString(vin.Txid)].ID == nil {
log.Panic("ERROR: Previous transaction is not correct")
}
}
txCopy := tx.TrimmedCopy()
curve := elliptic.P256()
for inID, vin := range tx.Vin {
prevTx := prevTXs[hex.EncodeToString(vin.Txid)]
txCopy.Vin[inID].Signature = nil
txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubKeyHash
r := big.Int{}
s := big.Int{}
sigLen := len(vin.Signature)
r.SetBytes(vin.Signature[:(sigLen / 2)])
s.SetBytes(vin.Signature[(sigLen / 2):])
x := big.Int{}
y := big.Int{}
keyLen := len(vin.PubKey)
x.SetBytes(vin.PubKey[:(keyLen / 2)])
y.SetBytes(vin.PubKey[(keyLen / 2):])
dataToVerify := fmt.Sprintf("%x\n", txCopy)
rawPubKey := ecdsa.PublicKey{Curve: curve, X: &x, Y: &y}
if ecdsa.Verify(&rawPubKey, []byte(dataToVerify), &r, &s) == false {
return false
}
txCopy.Vin[inID].PubKey = nil
}
return true
}
// NewCoinbaseTX creates a new coinbase transaction
func NewCoinbaseTX(to, data string) *Transaction {
if data == "" {
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)}
txout := NewTXOutput(subsidy, to)
tx := Transaction{nil, []TXInput{txin}, []TXOutput{*txout}}
tx.ID = tx.Hash()
return &tx
}
// NewUTXOTransaction creates a new transaction
func NewUTXOTransaction(wallet *Wallet, to string, amount int, UTXOSet *UTXOSet) *Transaction {
var inputs []TXInput
var outputs []TXOutput
pubKeyHash := HashPubKey(wallet.PublicKey)
acc, validOutputs := UTXOSet.FindSpendableOutputs(pubKeyHash, amount)
if acc < amount {
log.Panic("ERROR: Not enough funds")
}
// Build a list of inputs
for txid, outs := range validOutputs {
txID, err := hex.DecodeString(txid)
if err != nil {
log.Panic(err)
}
for _, out := range outs {
input := TXInput{txID, out, nil, wallet.PublicKey}
inputs = append(inputs, input)
}
}
// Build a list of outputs
from := fmt.Sprintf("%s", wallet.GetAddress())
outputs = append(outputs, *NewTXOutput(amount, to))
if acc > amount {
outputs = append(outputs, *NewTXOutput(acc-amount, from)) // a change
}
tx := Transaction{nil, inputs, outputs}
tx.ID = tx.Hash()
UTXOSet.Blockchain.SignTransaction(&tx, wallet.PrivateKey)
return &tx
}
// DeserializeTransaction deserializes a transaction
func DeserializeTransaction(data []byte) Transaction {
var transaction Transaction
decoder := gob.NewDecoder(bytes.NewReader(data))
err := decoder.Decode(&transaction)
if err != nil {
log.Panic(err)
}
return transaction
}

18
transaction_input.go Normal file
View File

@ -0,0 +1,18 @@
package main
import "bytes"
// TXInput represents a transaction input
type TXInput struct {
Txid []byte
Vout int
Signature []byte
PubKey []byte
}
// UsesKey checks whether the address initiated the transaction
func (in *TXInput) UsesKey(pubKeyHash []byte) bool {
lockingHash := HashPubKey(in.PubKey)
return bytes.Compare(lockingHash, pubKeyHash) == 0
}

64
transaction_output.go Normal file
View File

@ -0,0 +1,64 @@
package main
import (
"bytes"
"encoding/gob"
"log"
)
// TXOutput represents a transaction output
type TXOutput struct {
Value int
PubKeyHash []byte
}
// Lock signs the output
func (out *TXOutput) Lock(address []byte) {
pubKeyHash := Base58Decode(address)
pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-4]
out.PubKeyHash = pubKeyHash
}
// IsLockedWithKey checks if the output can be used by the owner of the pubkey
func (out *TXOutput) IsLockedWithKey(pubKeyHash []byte) bool {
return bytes.Compare(out.PubKeyHash, pubKeyHash) == 0
}
// NewTXOutput create a new TXOutput
func NewTXOutput(value int, address string) *TXOutput {
txo := &TXOutput{value, nil}
txo.Lock([]byte(address))
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
}

25
utils.go Normal file
View File

@ -0,0 +1,25 @@
package main
import (
"bytes"
"encoding/binary"
"log"
)
// IntToHex converts an int64 to a byte array
func IntToHex(num int64) []byte {
buff := new(bytes.Buffer)
err := binary.Write(buff, binary.BigEndian, num)
if err != nil {
log.Panic(err)
}
return buff.Bytes()
}
// ReverseBytes reverses a byte array
func ReverseBytes(data []byte) {
for i, j := 0, len(data)-1; i < j; i, j = i+1, j-1 {
data[i], data[j] = data[j], data[i]
}
}

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)
}
}

86
wallet.go Normal file
View File

@ -0,0 +1,86 @@
package main
import (
"bytes"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"log"
"golang.org/x/crypto/ripemd160"
)
const version = byte(0x00)
const addressChecksumLen = 4
// Wallet stores private and public keys
type Wallet struct {
PrivateKey ecdsa.PrivateKey
PublicKey []byte
}
// NewWallet creates and returns a Wallet
func NewWallet() *Wallet {
private, public := newKeyPair()
wallet := Wallet{private, public}
return &wallet
}
// GetAddress returns wallet address
func (w Wallet) GetAddress() []byte {
pubKeyHash := HashPubKey(w.PublicKey)
versionedPayload := append([]byte{version}, pubKeyHash...)
checksum := checksum(versionedPayload)
fullPayload := append(versionedPayload, checksum...)
address := Base58Encode(fullPayload)
return address
}
// HashPubKey hashes public key
func HashPubKey(pubKey []byte) []byte {
publicSHA256 := sha256.Sum256(pubKey)
RIPEMD160Hasher := ripemd160.New()
_, err := RIPEMD160Hasher.Write(publicSHA256[:])
if err != nil {
log.Panic(err)
}
publicRIPEMD160 := RIPEMD160Hasher.Sum(nil)
return publicRIPEMD160
}
// ValidateAddress check if address if valid
func ValidateAddress(address string) bool {
pubKeyHash := Base58Decode([]byte(address))
actualChecksum := pubKeyHash[len(pubKeyHash)-addressChecksumLen:]
version := pubKeyHash[0]
pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-addressChecksumLen]
targetChecksum := checksum(append([]byte{version}, pubKeyHash...))
return bytes.Compare(actualChecksum, targetChecksum) == 0
}
// Checksum generates a checksum for a public key
func checksum(payload []byte) []byte {
firstSHA := sha256.Sum256(payload)
secondSHA := sha256.Sum256(firstSHA[:])
return secondSHA[:addressChecksumLen]
}
func newKeyPair() (ecdsa.PrivateKey, []byte) {
curve := elliptic.P256()
private, err := ecdsa.GenerateKey(curve, rand.Reader)
if err != nil {
log.Panic(err)
}
pubKey := append(private.PublicKey.X.Bytes(), private.PublicKey.Y.Bytes()...)
return *private, pubKey
}

98
wallets.go Normal file
View File

@ -0,0 +1,98 @@
package main
import (
"bytes"
"crypto/elliptic"
"encoding/gob"
"fmt"
"io/ioutil"
"log"
"os"
)
const walletFile = "wallet_%s.dat"
// Wallets stores a collection of wallets
type Wallets struct {
Wallets map[string]*Wallet
}
// NewWallets creates Wallets and fills it from a file if it exists
func NewWallets(nodeID string) (*Wallets, error) {
wallets := Wallets{}
wallets.Wallets = make(map[string]*Wallet)
err := wallets.LoadFromFile(nodeID)
return &wallets, err
}
// CreateWallet adds a Wallet to Wallets
func (ws *Wallets) CreateWallet() string {
wallet := NewWallet()
address := fmt.Sprintf("%s", wallet.GetAddress())
ws.Wallets[address] = wallet
return address
}
// GetAddresses returns an array of addresses stored in the wallet file
func (ws *Wallets) GetAddresses() []string {
var addresses []string
for address := range ws.Wallets {
addresses = append(addresses, address)
}
return addresses
}
// GetWallet returns a Wallet by its address
func (ws Wallets) GetWallet(address string) Wallet {
return *ws.Wallets[address]
}
// LoadFromFile loads wallets from the file
func (ws *Wallets) LoadFromFile(nodeID string) error {
walletFile := fmt.Sprintf(walletFile, nodeID)
if _, err := os.Stat(walletFile); os.IsNotExist(err) {
return err
}
fileContent, err := ioutil.ReadFile(walletFile)
if err != nil {
log.Panic(err)
}
var wallets Wallets
gob.Register(elliptic.P256())
decoder := gob.NewDecoder(bytes.NewReader(fileContent))
err = decoder.Decode(&wallets)
if err != nil {
log.Panic(err)
}
ws.Wallets = wallets.Wallets
return nil
}
// SaveToFile saves wallets to a file
func (ws Wallets) SaveToFile(nodeID string) {
var content bytes.Buffer
walletFile := fmt.Sprintf(walletFile, nodeID)
gob.Register(elliptic.P256())
encoder := gob.NewEncoder(&content)
err := encoder.Encode(ws)
if err != nil {
log.Panic(err)
}
err = ioutil.WriteFile(walletFile, content.Bytes(), 0644)
if err != nil {
log.Panic(err)
}
}