Skip to content

Latest commit

 

History

History
1944 lines (1384 loc) · 35.9 KB

README.md

File metadata and controls

1944 lines (1384 loc) · 35.9 KB

2. INTRODUCTION

2.2 Why Go

  • Only 1 option: for loop
  • Powerful standard library
  • Low learning curve

3. TOUR OF GO

3.0 Creating the Project

go mod init github.com/devgony/gophercoin
ls
> go.mod # like package.json
touch main.go

3.1 Variables in Go

create and update var syntax (should be inside func only)

var name string = "henry"
name := "henry" // same with above, syntax sugar
  • var, const
  • bool, string, int(8,16..64), uint(8,16..64) byte, float(32,64)

3.2 Functions

  • If params have same type, specify only at the last
  • func can return two types
func plus(a, b int, name string) (int, string) {
	return a + b, name
}
  • multiple params
func plus(a ...int) int {
	var total int
	for index, item := range a {
		total += item
	}
	return total
}

3.3 fmt

x := 84375983402
fmt.Printf("%b\n", x)
fmt.Printf(fmt.Sprintf("%b\n", x)) // return fmted string (not print)
fmt.Printf("%o\n", x)
fmt.Printf("%x\n", x)
fmt.Printf("%U\n", x)

3.4 Slices and Arrays

  • array is declarative and limited in go
foods := [3]string{"p", "o", "s"}
for i := 0; i < len(foods); i++ {
    fmt.Println(foods[i])
}
  • slice is growable and infinited
foods := []string{"p", "o", "s"}
fmt.Printf("%v\n", foods)
foods = append(foods, "t") // returns appended slice (should set to var manually)
fmt.Printf("%v\n", foods)

3.5 Pointers

a := 2
b := a  // copy
c := &a // borrow
a = 9
fmt.Println(a, b, *c) // 9 2 9

3.6 Structs

struct (like class)

type person struct {
	name string
	age  int
}

receiver function (like methods)

  • choose first letter of type as a alias
func (p person) sayHello() {
	// give access to instance
	fmt.Printf("Hello! My name is %s and I'm %d", p.name, p.age)
}

3.7 Structs with Pointers

mkdir person
touch person/person.go
package person

type Person struct {
	name string
	age  int
}

func (p Person) SetDetails(name string, age int) {
    // p is copy
	p.name = name
	p.age = age
}

Receiver pointer function

  • basically receiver function's p is copy -> use only for read
  • we don't want to mutate copied, but origin
  • use * to mutate original
func (p *Person) SetDetails(name string, age int) {
    // p is the origin
	p.name = name
	p.age = age

4. BLOCKCHAIN

4.0 Introduction

  • Concentrate to blockchain concept, solve side problem later

4.1 Our First Block

  • if any block is edited, invalid
b1Hash = (data + "")
b2Hash = (data + b1Hash)
b3Hash = (data + b2Hash)
  • sha256 needs slice of bytes: cuz string is immutable
genesisBlock := block{"Genesis Block", "", ""}
hash := sha256.Sum256([]byte(genesisBlock.data + genesisBlock.prevHash))
hexHash := fmt.Sprintf("%x", hash)
genesisBlock.hash = hexHash
secondBlocks := block{"Second Blocks", "", genesisBlock.hash}

4.2 Our First Blockchain

type block struct {
	data     string
	hash     string
	prevHash string
}

type blockchain struct {
	blocks []block
}

func (b *blockchain) getLastHash() string {
	if len(b.blocks) > 0 {
		return b.blocks[len(b.blocks)-1].hash
	}
	return ""
}

func (b *blockchain) addBlock(data string) {
	newBlock := block{data, "", b.getLastHash()}
	hash := sha256.Sum256([]byte(newBlock.data + newBlock.prevHash))
	newBlock.hash = fmt.Sprintf("%x", hash)
	b.blocks = append(b.blocks, newBlock)
}

func (b *blockchain) listBlocks() {
	for _, block := range b.blocks {
		fmt.Printf("Data: %s\n", block.data)
		fmt.Printf("Hash: %s\n", block.hash)
		fmt.Printf("PrevHash: %s\n", block.prevHash)
	}
}

func main() {
	chain := blockchain{}
	chain.addBlock("Genesis Block")
	chain.addBlock("Second Block")
	chain.addBlock("Third Block")
	chain.listBlocks()
}

4.3 Singleton Pattern

mkdir blockchain
touch blockchain/blockchain.go
  • singletone: share only 1 instance
// blockchain/blockchain.go
package blockchain

type block struct {
	data     string
	hash     string
	prevHash string
}

type blockchain struct {
	blocks []block
}

var b *blockchain

func GetBlockchain() *blockchain {
	if b == nil {
		b = &blockchain{}
	}
	return b
}

4.4 Refactoring part One

  • Package sync.once: keep running once though ran by goroutine
once.Do(func() {
			b = &blockchain{}
			b.blocks = append(b.blocks, createBlock(("Genesis Block")))
})
  • Blockchain should be a slice of pointer with borrow (it will be way longer)

4.5 Refactoring part Two

package blockchain

import (
	"crypto/sha256"
	"fmt"
	"sync"
)

type block struct {
	Data     string
	Hash     string
	PrevHash string
}

type blockchain struct {
	blocks []*block
}

var b *blockchain
var once sync.Once

func (b *block) calculateHash() {
	Hash := sha256.Sum256([]byte(b.Data + b.PrevHash))
	b.Hash = fmt.Sprintf("%x", Hash)
}

func getLastHash() string {
	totalBlocks := len(GetBlockchain().blocks)
	if totalBlocks == 0 {
		return ""
	}
	return GetBlockchain().blocks[totalBlocks-1].Hash
}

func createBlock(Data string) *block {
	newBlock := block{Data, "", getLastHash()}
	newBlock.calculateHash()
	return &newBlock
}

func (b *blockchain) AddBlock(data string) {
	b.blocks = append(b.blocks, createBlock((data)))

}

func GetBlockchain() *blockchain {
	if b == nil {
		once.Do(func() {
			b = &blockchain{}
			b.AddBlock(("Genesis"))
		})
	}
	return b
}

func (b *blockchain) AllBlocks() []*block {
	return b.blocks
}

5. EXPLORER

5.0 Setup

  • server side rendering only with std lib
const port string = ":4000"

func home(rw http.ResponseWriter, r *http.Request) {
	fmt.Fprint(rw, "Hello from home!") // print to writer
}

func main() {
	http.HandleFunc("/", home)
	fmt.Printf("Listening on http://localhost%s\n", port)
	log.Fatal(http.ListenAndServe(port, nil)) // if failed exit 1 else none
}

5.1 Rendering Templates

mkdir templates
touch templates/home.html
  • template.Must
tmpl, err := template.ParseFiles("templates/home.html")
if err != nil {
		log.Fatal((err))
}
tmpl := template.Must(template.ParseFiles("templates/home.html"))
  • template
type homeData struct {
	PageTitle string
	Blocks    []*blockchain.Block
}

func home(rw http.ResponseWriter, r *http.Request) {
	tmpl := template.Must(template.ParseFiles("templates/home.html"))
	data := homeData{"Home", blockchain.GetBlockchain().AllBlocks()}
	tmpl.Execute(rw, data)
}

5.2 Rendering Blocks

<link rel="stylesheet" href="https://unpkg.com/mvp.css" />
  • just copy & paste html? => partials => glob import
mkdir templates/partials
touch templates/partials/footer.gohtml
touch templates/partials/head.gohtml
touch templates/partials/header.gohtml
touch templates/partials/block.gohtml

mkdir templates/pages
move templates/home.gohtml templates/pages/home.gohtml
touch templates/pages/add.gohtml

5.3 Using Partials

touch partials/block.gohtml
  • load
{{template "head"}}
  • load glob
    • can't use **/ so that parseGlob n times
templates = template.Must(template.ParseGlob(templateDir + "pages/*.gohtml"))
templates = template.Must(templates.ParseGlob(templateDir + "partials/*.gohtml")) // template^s

5.4 Adding A Block

Cursor synatax .: passing struct

  1. template of template use .
// home.gohtml
{{template "header" .PageTitle}}
// header.gohtml
...
<h1>{{.}}</h1>
...
  1. inside loop, use .
{{range .Blocks}}
	{{template "block" .}}
{{end}}
  • switch case "GET", "POST"
func add(rw http.ResponseWriter, r *http.Request) {
	switch r.Method {
	case "GET":
		templates.ExecuteTemplate(rw, "add", nil)
	case "POST":
		r.ParseForm()
		data := r.Form.Get("blockData")
		blockchain.GetBlockchain().AddBlock(data)
		http.Redirect(rw, r, "/", http.StatusPermanentRedirect)
	}
}

5.5 Refactoring

mkdir explorer
mv templates explorer/
cp main.go explorer/explorer.go
// explorer/explorer.go
	templateDir string = "explorer/templates/"

6. REST API

6.0 Setup

  • REST API
mkdir utils
touch utils/utils.go

func HandleErr(err error) {
	if err != nil {
		log.Panic(err)
	}
}

json.Marshal(data)

  • Marshal: convert from goInterface to JSON

6.1 Marshal and Field Tags

  • manual marshal
rw.Header().Add("Content-Type", "application/json")
b, err := json.Marshal(data)
utils.HandleErr(err)
fmt.Fprintf(rw, "%s", b)
  • simple marshal
json.NewEncoder(rw).Encode(data)
Description string `json:"description"` // make lowercase
Payload     string `json:"payload,omitempty"` // omit if empty
...
Payload:     "data:string", // write data on body

6.2 MarshalText

Interface

  • if impl Stringer, can control fmt
func (u URLDescription) String() string {
	return "Hello I'm a URL Description"
}
  • prepend http://localhost
func (u URL) MarshalText() ([]byte, error) {
	url := fmt.Sprintf("http://localhost%s%s", port, u)
	return []byte(url), nil
}

6.3 JSON Decode

  • Install VSC extension: REST client
## touch api.http
http://localhost:4000/blocks
## send request by clicking
POST http://localhost:4000/blocks
{
    "message": "Data for my block"
}
  • should pass pointer(address) to decode
utils.HandleErr(json.NewDecoder(r.Body).Decode(&addBlockBody))

6.4 NewServeMux

  • quick refactoring
mkdir rest
sed 's/main()/Start()/; s/package main/package rest/' main.go > rest/rest.go
echo "package main\nfunc main() {}" > main.go
  • dynamic port
var port string
...
func Start(aPort int) {
	port = fmt.Sprint(":%d", aPort)
// explorer.go
fmt.Printf("Listening on http://localhost:%d\n", port)
log.Fatal(http.ListenAndServe(fmt.Sprint(":%d", port), nil)

use NewServeMux

  • to solve duped route
  • nil -> defaultServeMux, handler -> NewServeMux
// rest.go
handler := http.NewServeMux()
...
handler.HandleFunc("/", documentation)
handler.HandleFunc("/blocks", blocks)
...
log.Fatal(http.ListenAndServe(port, handler))

6.5 Gorilla Mux

  • can handle params
go get -u github.com/gorilla/mux
router := mux.NewRouter()
...
vars := mux.Vars(r)

6.6 Atoi

  • string to int
id, err := strconv.Atoi(vars["height"])

6.7 Error Handling

  • new error
var ErrNotFound = errors.New("block not found")
  • new errorResponse
type errorResponse struct {
	ErrorMessage string `json:"errorMessage"`
}

6.8 Middlewares

  • Middleware is a function to call before final destination

adapter pattern

  • Handler is an interface implementing method called ServerHTTP
  • HandlerFunc is type (adapter)
    • HandlerFunc(): constructing a type
    • adaptor ask us to send correct argument and adaptor implement everything we need

7. CLI

7.0 Introduction

  • flag
  • cobra

7.1 Parsing Commands

  • os.Args gives array of commands
go run main.go someCMD
-> [.../exe/main someCMD]
  • to exit, use os.Exit(0)

7.2 FlagSet

  • flagSet is useful if one command has many flags
rest := flag.NewFlagSet("rest", flag.ExitOnError)
portFlag := rest.Int("port", 4000, "Sets the port of the server")
...
rest.Parse(os.Arge[2:])
...
if rest.Parsed() {
		fmt.Println(*portFlag)
		fmt.Println("Start server")
}

7.3 Flag

  • easier than flagSet
port := flag.Int("port", 4000, "Set port of the server")
mode := flag.String("mode", "rest", "Choose between 'html' and 'rest'"
  • refactor main.go > cli/cli.go
mkdir cli
sed 's/main()/Start()/; s/package main/package cli/' main.go > cli/cli.go
echo "package main\nfunc main() {}" > main.go
  • challenge: make command to run both with differen port and goroutine

8. PERSISTENCE

8.0 Introduction to Bolt

  • currently everthing is on memory (slice of block)
  • bolt: key/value database specified for get/set
    • eg) "sdkfljsdlfjds": {"data: PrvHash"}

8.1 Creating the Database

  • There will be no immediate Start so that start coding from db/db.go
mkdir db
touch db/db.go
go get github.com/boltdb/bolt
const (
	dbName       = "blockchain.db"
	dataBucket   = "data"
	blocksBucket = "blocks"
)

var db *bolt.DB

func DB() *bolt.DB {
	if db == nil {
		dbPointer, err := bolt.Open(dbName, 0600, nil)
		db = dbPointer
		utils.HandleErr(err)
		err = db.Update(func(t *bolt.Tx) error {
			_, err := t.CreateBucketIfNotExists([]byte(dataBucket))
			utils.HandleErr(err)
			_, err = t.CreateBucketIfNotExists([]byte(blocksBucket))
			return err
		})
		utils.HandleErr(err)
	}
	return db
}

8.2 A New Blockchain

  • divide & conquer
mv blockchain/blockchain.go blockchain/chain.go
touch blockchain/block.go

8.3 Saving A Block

  • gob: encode/decode data<->byte
  • buffer: place to put byte with write/read
// blockchain/block.go
func (b *Block) toBytes() []byte {
	var blockBuffer bytes.Buffer
	encoder := gob.NewEncoder(&blockBuffer)
	utils.HandleErr(encoder.Encode(b))
	return blockBuffer.Bytes()
}

func (b *Block) persist() {
	db.SaveBlock(b.Hash, b.toBytes())
}
  • SaveBlock: bolt can save only byte
// db/db.go
func SaveBlock(hash string, data []byte) {
	fmt.Printf("Saving Block %s\nData: %b\n", hash, data)
	err := DB().Update(func(t *bolt.Tx) error {
		bucket := t.Bucket([]byte(blocksBucket))
		err := bucket.Put([]byte(hash), data)
		return err
	})
	utils.HandleErr(err)
}

8.4 Persisting The Blockchain

  • move ToBytes from block.go to utils.go
    • inferface -> can get any type
// utils/utils.go
func ToBytes(i interface{}) []byte {
	var aBuffer bytes.Buffer
	encoder := gob.NewEncoder(&aBuffer)
	HandleErr(encoder.Encode(i))
	return aBuffer.Bytes()
}

8.5 Restoring the Blockchain

  • when we start, should restore chain from checkpoint
  • restore from byte to data
func (b *blockchain) restore(data []byte) {
	decoder := gob.NewDecoder(bytes.NewReader(data))
	utils.HandleErr(decoder.Decode(b)) // with pointer, modify the origin value from byte to data
}
  • select checkpoint
func Checkpoint() []byte {
	var data []byte
	DB().View(func(t *bolt.Tx) error {
		bucket := t.Bucket(([]byte(dataBucket)))
		data = bucket.Get([]byte(checkpoint))
		return nil
	})
	return data
}

8.6 Restoring Block

  • Add func FromBytes at utils.go
  • Refactor func restore with func FromBytes at chain.go
  • Add func Block at db/db.go
  • Add ErrNotFound, func restore, func FindBlock at block.go
  • Omit GET, POST case at rest.go for test

8.7 All Blocks

  • Close at db/db.go
  • defer Close() at main.go
    • if there is Goexit, deferred calls will be executed
  • Add func Blocks at chain.go
  • Recover GET, POST case at rest.go

8.8 Recap

  • Refactor SaveBlockchain -> SaveCheckpoint
  • bolt, get/set data to bucket
  • singletone -> if no checkpoint -> create genesis
  • func persist -> save to bolt database

9 MINING

9.0 Introduction to PoW

  • Proof Of Work
  • Add properties to Block struct at block.go
	Difficulty int    `json:"difficulty"`
	Nonce      int    `json:"nonce"`
  • Delete db
rm blockchain.db

9.1 PoW Proof Of Concept

  • Mining prototype
difficulty := 2
target := strings.Repeat("0", difficulty)
nonce := 1
for {
	hash := fmt.Sprintf("%x", sha256.Sum256([]byte("hello"+fmt.Sprint(nonce))))
	fmt.Printf("Hash:%s\nTarget:%s\nNonce:%d\n\n", hash, target, nonce)
	if strings.HasPrefix(hash, target) {
		return
	} else {
		nonce++
	}
}

9.2 Mine Block

  • func mine at block.go
  • Modify way get hash -> block.mine() at block.go

9.3 Difficulty part One

  • Refactor Hash from block.go to utils.go
s := fmt.Sprintf("%v", i)
// v means default formatter
  • Add timestamp on each mining at block.go
b.Timestamp = int(time.Now().Unix())
  • Add func difficulty at chain.go
func (b *blockchain) difficulty() int {
	if b.Height == 0 {
		return defaultDifficulty
	} else if b.Height%difficultyInterval == 0 {
		// recalculate the difficulty
	} else {
		return b.CurrentDifficulty
	}
}

9.4 Difficulty part Two

  • Add const blockInterval, allowedRange at chain.go
  • Whenever create new block, b.CurrentDifficulty = block.Difficulty
  • Adjust allowedRange rather than constant value
if actualTime <= (expectedTime - allowedRange) {
	return b.CurrentDifficulty + 1
} else if actualTime >= (expectedTime + allowedRange) {
	return b.CurrentDifficulty - 1
}
return b.CurrentDifficulty

9.5 Conclusions

  • Add status route at rest.go
  • Refactor Handling errors for encoder at rest.go
  • Cheack status of difficulty grows each 5 blocks

10 TRANSACTIONS

10.0 Introduction

  • Course ~#9 were all about Protecting Data
  • Here going to learn Moving value between our user
  • uTxOut: Unspent Transaction Output mpdel?

10.1 Introduction to Transactions

  • Tx
    • TxIn[$5(me)]: money that i have
    • TxOut[$0(me), $5(you)]: money that every body has by the end of Tx
    • Just change owner
  • What if currency scale is different? change?
    • TxIn[$10(me)]
    • TxOut[$5(me), $5(you)]
  • Just find last Tx
  • Coinbase input: created by blockchain, to miner
    • TxIn[$10(blockchain)]
    • TxOut[$10(miner)]

10.2 Coinbase Transaction

touch blockchain/transactions.go
  • Add struct Tx, TxIn, TxOut, func makeCoinbaseTx at transactions.go
  • Remove Data, Add Transaction to struct Block at block.go
  • Refactor to remove Data at rest.go, explorer.go, block.go, chain.go

10.3 Balances

  • Add func txOuts, TxOutsByAddress, BalanceByAddress at chain.go
  • Add struct balanceResponse, func balance, router balance at rest.go

10.4 Mempool

  • Memory pool: where we put unconfirmed transaction
  • After confirmed -> Part of Block

10.5 AddTx

  • Cuz Mempool is on the memory, we don't need to initialize
  • Add struct mempool, var Mempool, func makeTx, AddTx at transactions.go
  • Add router mempool at rest.go

10.6 makeTx

  • Improve func maskTx at transactions.go:
    • BalanceByAddress < amount -> error
    • Until total >= mount, append &TxIn{txOut.Owner, txOut.Amount} to txIns
    • change := total - amoun -> append to txOuts
    • append &TxOut{to, amount} to txOuts
  • Add struct addTxPayload, router transactions at rest.go

#10.7 Confirm Transactions

  • Refactor Transactions to after block.mine() at block.go
block.mine()
block.Transactions = Mempool.TxToConfirm()
  • Add func TxToConfirm at transactions.go
  • Duplication bug: we should check if the coin of txOut was already used or not

10.8 uTxOuts

  • We need to find which one is duplicated
Tx1
	TxIns[COINBASE]
	TxOuts[$5(you)] <- Spent TxOut
Tx2
	TxIns[Tx1.TxOuts[0]]
	TxOuts[$5(me)] <- unspent TxOut to Spent
Tx3
	TxIns[Tx2.TxOuts[0]]
	TxOuts[$3(you), $2(me)] <- uTxOut * 2
  • Modify and Add struct TxIn, UTxOut at transactions.go with
TxID   string
Index  int
Amount int
  • Remove func txOuts, Rename TxOutsByAddress to UTxOutsByAddress at chain.go

10.9 UTxOutsByAddress

  • Implement UTxOutsByAddress at chain.go
    • In all inputs, if address equal to the owner, append TxId to creatorTxs
    • In all outputs, if address is equal to the owner and TxId is not in creatorTxs, append to uTxOuts
  • Refactor Tx.Id, getId => ID at transactions.go

10.10 makeTx part Two

  • Impl makeTx at transactions.go
  • But, It stil copy the coin if the Tx is on mempool
    • it checkes spent or unspent only (confirmed)
    • Should check unconfirmed Tx too.

10.11 isOnMempool

  • Impl func isOnMempool at transactions.go
  • Add !isOnMempool condition to UTxOutsByAddress at chain.go
  • looks like working but why error message is "not enough funds" not "not enough money"?
    • does rest.go convert error automatically?

10.12 Refactoring

Refactor redundant loop

  1. return true to kill func way
func isOnMempool(uTxOut *UTxOut) bool {
	for _, tx := range Mempool.Txs {
		for _, input := range tx.TxIns {
			if input.TxID == uTxOut.TxID && input.Index == uTxOut.Index {
				return true
			}
		}
	}
	return false
}
  1. break Outer labeled loop way
func isOnMempool(uTxOut *UTxOut) bool {
	exists := false
Outer:
	for _, tx := range Mempool.Txs {
		for _, input := range tx.TxIns {
			if input.TxID == uTxOut.TxID && input.Index == uTxOut.Index {
				exists = true
				break Outer
			}
		}
	}
	return exists
}

Method Vs Function

  • If it is mutating struct -> method
  • Else, -> normal func with struct as input param
  • Sort method first, func last

10.13 Deadlock

  • Current func Blockchain at chain.go is recursive
  • Because no call to Do returns until the one call to f returns, if f causes Do to be called, it will deadlock.
  • Modify logic
    • Remove nil condition of func Blockchain at chain.go
    • func createBlock receives diff param at block.go
    • Rename difficulty -> getDifficulty at chain.go

11 WALLETS

11.0 Introduction

  • If he owns unspent output
  • If he approved the transaction
  1. How signature, verification works
  2. Persistance to db
  3. Impl signature, verification with tracsaction
mkdir wallet
touch wallet/wallet.go

11.1 Private and Public Keys

  1. Hash the msg
"i love you" -> hash(x) -> "hashed_message"
  1. Generate key pair
keypair (privateKey, publicKey)
	(save privateK to a file -> wallet)
  1. Sign the hash
("hashed_message" + privateKey) -> "signature"
  1. Verify with publicKey
("hashed_message" + "signature" + publicKey) -> true / false

11.2 Signing Messages

  • ecdsa: Elliptic Curve Digital Signature Algorithm
privateKey = ecdsa.GenerateKey
hashAsBytes = hex.DecodeString(Hash(message))
r, s = ecdsa.Sign

11.3 Verifying Messages

  • Refactor generated values to constant var
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) // bigint
keyAsBytes, err := x509.MarshalECPrivateKey(privateKey) // bigint -> hex
utils.HandleErr(err)
fmt.Printf("privateKey = %x\n\n", keyAsBytes)

message := "i love you"
hashedMessage := utils.Hash(message)
fmt.Printf("hashedMessage = %s\n\n", hashedMessage)

hashAsBytes, err := hex.DecodeString(hashedMessage)
utils.HandleErr(err)
r, s, err := ecdsa.Sign(rand.Reader, privateKey, hashAsBytes)
signature := append(r.Bytes(), s.Bytes()...) // [32]byte + [32]byte
fmt.Printf("signature = %x\n\n", signature) // [64]byte -> hex

utils.HandleErr(err)
ok := ecdsa.Verify(&privateKey.PublicKey, hashAsBytes, r, s)
fmt.Println(ok)
const (
	privateKey    string = "30770201010420270623da3768df6fc3c3439b8e0319621318b1dec6199052f49faefdd9d80548a00a06082a8648ce3d030107a1440342000462ded99b11da850eec19a908aa57effbec88541aa04da07d0a2cabf046b2502dd061eccc9860c7922ea758a2e8ac1e5f6d044d7a6af03060aa5dcb13cafc8a73"
	hashedMessage string = "1c5863cd55b5a4413fd59f054af57ba3c75c0698b3851d70f99b8de2d5c7338f"
	signature     string = "6d56582490ff9a54b44df6bf9fa991c0432f2fd25f32760bf540b10049b50a048ca986d7e9f0ee7745bce735dcd0db951f21664f054e94b0a03d87046857a3ca"
)

11.4 Restoring

privByte, err := hex.DecodeString(privateKey)
utils.HandleErr(err)

privateKey, err := x509.ParseECPrivateKey(privByte)
utils.HandleErr(err)

sigBytes, err := hex.DecodeString(signature)

rBytes := sigBytes[:len(sigBytes)/2]
sBytes := sigBytes[len(sigBytes)/2:]

var bigR, bigS = big.Int{}, big.Int{}
bigR.SetBytes(rBytes)
bigS.SetBytes(sBytes)

hashBytes, err := hex.DecodeString(hashedMessage)
utils.HandleErr(err)
ok := ecdsa.Verify(&privateKey.PublicKey, hashBytes, &bigR, &bigS)
fmt.Println(ok)

11.5 Wallet Backend

  • Add func hasWalletFile at wallet.go
  • Singletone pattern func Wallet skeleton at wallet.go

11.6 Persit Wallet

  • Add func createPrivKey, persistKey at wallet.go
  • If there is new var togather, we can recreate err with newVar err :=
    • (actually updating)
  1. pure update way
bytes, err := x509.MarshalECPrivateKey(key)
utils.HandleErr(err)
err = os.WriteFile(fileName, bytes, 0644)
...
  1. recreate way
bytes, err := x509.MarshalECPrivateKey(key)
utils.HandleErr(err)
newVar2, err := os.WriteFile(fileName, bytes, 0644)

11.7 Restore Wallet

  • Add func restoreKey at wallet.go
  • Names return: good for short func, easy to understand only with signature, bad for long function's retunry empty and lookup above again
func restoreKey() (key *ecdsa.PrivateKey ){
	keyAsBytes, err := os.ReadFile(fileName)
	utils.HandleErr(err)
	key, err = x509.ParseECPrivateKey(keyAsBytes)
	utils.HandleErr(err)
	return
}

11.8 Addresses

  • Add func aFromK, sign at wallet.go
  • Replace "nico" to wallet.Wallet().Address at transactions.go

11.9 Verification Function

  • Add func restoreBigInts, verify at wallet.go
  • restoreBigints can handle signature, address(to publicKey)

11.10 Recap

  • Add func encodeBigInts at wallet.go
  • Refactor with encodeBigInts

11.11 Transaction Signing

  • Add func Txs, FindTx at chain.go
  • Rename TxIn.Owner -> Signature, TxOut.Owner -> Address at transactions.go
  • Add func sign at transactions.go

11.12 Transaction Verification

transactions.go

Impl func validate

  • To validate New Tx(in),
    • Find prev Txout referencing same TxID with New Tx
    • Check if address is same (same owner)
    • Verify signature created by private key of the owner

Modify func makeT

  • check valid, return ErrorNotValid

chain.go

Modify func UTxOutsByAddress

input.Onwer == address

⬇️

if FindTx(b, input.TxID).TxOuts[input.Index].Address == address

11.13 Conclusions

rest.go

Add router wallet

Add func myWallet with Anonymous struct

func myWallet(rw http.ResponseWriter, r *http.Request) {
	address := wallet.Wallet().Address
	json.NewEncoder(rw).Encode(struct {
		Address string `json:"address"`
	}{Address: address})

}

Q

  • Don't we need to use address when we send coin instead of name?
  • Study more (signature - address - name) relationship
  • How to filter mempool-ed Tx?

11.14 Recap

rest.go

func transactions: Superfluous response.WriteHeader error

  • Should return http.StatusBadRequest(404)
  • Should use err.Error() to convey original error instead of literal error
json.NewEncoder(rw).Encode(errorResponse{err.Error()})
  • return to finish function

Recap

(TxOut1(publickKey), TxOut2)

Tx
	TxIn[
		(TxOut1)
		(TxOut2)
	]
	Sign with my privateKey

TxIn.Sign + TxOut1.Address -> true / false

12 P2P

12.0 Introduction

  • Lean Peer To Peer by simple Chatting app

12.1 Why Go Routines

Goroutine

  • Running function at separate parallel dimension
  • Can't assign or return value to variable immediately;
go countToTen()

12.2 Channels

Channels

  • Deadlock: Channel should not receive more than coroutine

12.3 Raead, Receive and Close

func countToTen(c chan<- int) { // send only chan<-
	for i := range [10]int{} {
		time.Sleep(1 * time.Second)
		fmt.Printf("sending %d\n", i)
		c <- i
	}
	close(c)
}

func receive(c <-chan int) { // receive only <-chan
	for {
		a, ok := <-c // blocking by getting next value
		if !ok {
			fmt.Println("Done")
			break
		}
		fmt.Printf("received %d\n", a)
	}
}

func main() {
	c := make(chan int)
	go countToTen(c)
	receive(c)
}

12.4 Buffered Channels

c := make(chan int, NumOfBuffer)
  • Don't block first N values, then block/wait the queue like normal Unbuffered channel

12.5 WebSocket Upgrades

  • HTTP: stateless
  • WS: statefull, connected
  • Upgrade Go with WS
mkdir p2p
touch p2p/p2p.go
touch chat.html
go get github.com/gorilla/websocket
go run -race main.go run -mode=rest -port=3000

chat.html

const socket = new WebSocket("ws://localhost:4000/ws");

p2p/p2p.go

  • Add func Upgrade

rest/rest.go

  • Add ws to URL, router
  • Add func loggerMiddleware

12.6 ReadMessage

p2p/p2p.go

func Upgrade

  • Add CheckOrigin
  • Add conn.ReadMessage with for loop
    • conn.ReadMessage is a receiver Channel for socket

12.7 WriteMessage

chat.html

  • Add form, send, receive eventListener

p2p/p2p.go

  • Add conn.WriteMessage

12.8 Connections

  • Connect client to client through server
  • http.ListenAndServe uses goroutine

p2p/p2p.go

  • Add slice of conn, append conn, send to other conns,

To-do

  • If browser is refreshed, gets error -> How to handle closed connection?
  • A Message should not block others -> How to separate?

12.9 Peers

  • Should connect peer and peer not through server

p2p/p2p.go

  • Add func AddPeer

rest.go

  • Add struct addPeerPayload, func peers, router peers

12.10 initPeer

p2p/peer.go

touch p2p/peer.go
  • Add struct peer, func initPeer

rest.go

  • Add GET to router peers

12.11 openPort

p2p/p2p.go

func AddPeer

  • send openPort with URL

func Upgrade

  • get openPort from URL

rest.go

func peers

  • p2p.Addpeer(..., port)

12.12 Recap

utils.go

  • Add func Spliiter

p2p.go

  • Refactor with utils.Splitter

#12.13 Read Peer

p2p/peer.go

  • Add method read
  • Add go p.read() at func initPeer

p2p/p2p.go

  • Add conn.WriteMessage at func Upgrade

12.14 Inbox

  • Instead of writing once, use coroutine and channel

p2p/peer.go

  • Add method write

func initPeer

  • make channel initiating peer
  • go p.write()

p2p/p2p.go

  • send message to inbox(channel)

12.15 Cleanup

p2p/p2p.go

  • Delete Hellos, Add initPeer()

p2p/peer.go

  • Add key, address, port to struct peer
  • Add func close
  • Add defer p.close() at read, write

12.16 Data Race

  • Stable bolt has socket hangs up error with race
  • Change to bbolt
go get go.etcd.io/bbolt
// db.go
import (bolt "go.etcd.io/bbolt")
  • Data Race: When more than two goroutine access to the same block
  • When we read and modify peers at the same time, gets Data race error

12.17 Mutex

p2p/peer.go

  • Add type peers with sync.Mutex
type peers struct {
	v map[string]*peer
	m sync.Mutex
}

var Peers peers = peers{
	v: make(map[string]*peer),
}
  • Should lock before read of delete
func (p *peer) close() {
	Peers.m.Lock()
	defer Peers.m.Unlock()
	p.conn.Close()
	delete(Peers.v, p.key)
}
  • Add func AllPeers and convert output from object to array of keys
    • Cuz we don't modify peers but just read -> not method but func -> use getter
{
  "127.0.0.1:4000": {},
  "...": {}
}

⬇️

[
	"127.0.0.1:4000",
	...
]

12.18 Demonstration

  • Can use func with defer to delay between lock and unlock
    • -> Can Demonstrate the mutex
func (p *peer) close() {
	Peers.m.Lock()
	defer func() {
		time.Sleep((20 * time.Second))
		Peers.m.Unlock()
	}()
	p.conn.Close()
	delete(Peers.v, p.key)
}

12.19 Messages

Messaging Scenario

Behind case

  1. :4000 sends newest block to :3000
  2. :3000 realizes that :3000 is behind, ask to :4000 latest all blocks
  3. :4000 gives latest all blocks

Ahead case

  1. :4000 sends newest block to :3000
  2. :3000 realizes that :3000 is ahead, send newest block to :4000
  3. :4000 realizes that :4000 is behind, ask to :3000 latest all blocks
  4. :3000 gives latest all blocks

p2p/messages.go

touch p2p/messages.go

iota

  • Auto increament sequence
const (
	MessageNewestBlock MessageKind = iota
	MessageAllBlocksRequest
	MessageAllBlocksResponse
)

p2p/peer.go

  • Let's Communicate with json
  • Change from ReadMessage to ReadJSON

12.20 Newest Block

p2p/messages.go

  • Add func addPayload, makeMessage, sendNewestBlock
  • Why should we json.Marshal twice?
    • cuz Payload is public, it can be already JSON
    • Both Payload and Message should be json.Marshal-ed

p2p/p2p.go

  • sendNewstBlock in addPeer

12.22 Handle Message

utils.go

  • Add func ToJSON

p2p/messages.go

  • Add func handleMsg with Unmarshal
  • Remove func addPayload, Use ToJSON instead.

p2p/peer.go

  • handleMsg whenever read()

12.23 Recap

p2p/peer.go

  • Datarace: Though there is lock at close(), opposite side stil add Peer twice -> Add Lock at initPeer

12.24 All Blocks

db/db.go

  • Each client should use different db
  • Add func getDbName: get db name with port number

p2p/messages.go

  • Add func requestAllBlocks, sendAllBlocks

func handleMsg

  • if payload.Height >= b.Height requestAllBlocks()

    • To handle case when height is equal, used some illgical trick
  • Add case MessageAllBlocksRequest, MessageAllBlocksresponse

  • Why don't we just get height with blockchain.Height?

12.25 Recap

  • Add console print

Case1

  1. 4000: wants to connect to port 3000
  2. 3000: 4000 wants an upgrade
  3. 4000: Sending newest block to 127.0.0.1:3000
  4. 3000: Received the newest block from 127.0.0.1:4000
  5. 4000: 127.0.0.1:3000 wants all the blocks.
  6. 3000: Received all the blocks from 127.0.0.1:4000

Case2

  • After mining blocks at 3000,
  1. 4000: wants to connect to port 3000
  2. 3000: 4000 wants an upgrade
  3. 4000: Sending newest block to 127.0.0.1:3000
  4. 3000: Received the newest block from 127.0.0.1:4000 // 3000 have more than 4000
  5. 3000: Sending newest block to 127.0.0.1:4000
  6. 4000: Received the newest block from 127.0.0.1:3000 // realize 4000 is behind 3000
  7. 4000: Requesting all blocks to 127.0.0.1:3000
  8. 3000: 127.0.0.1:4000 wants all the blocks.
  9. 4000: Received all the blocks from 127.0.0.1:3000

12.26 Replace Blockchain

  • Can syncronize and persist now!

/blockchain/block.go

  • Rename method persist -> func persitBlock

/blockchain/chain.go

  • Add method Replace
    • Renew blockchain
    • EmptyBlocks and persist newBlocks

db/db.go

  • Add funcEmptyBlocks()

p2p/messages.go

  • blockchain.Blockchain().Replace(payload) at the end of handleMsg

12.27 Broadcast Block

Solve datarace: cover blockchain with mutex

blockchain/chain.go

  • Add m to struct blockchain
  • Lock & unlock at func Blocks, Status, Replace

Broadcast: send same msg to everybody

blockchain/chain.go

  • func AddBlock returns newBlock
  • Add func Status: show status of blockchain at /status

p2p/messages.go

  • Add iota MessageNewBlockNotify
  • Add func notifyNewBlock, BroadcastNewBlock

rest/rest.go

  • /blocks: after AddBlock, BroadcastNewBlock
  • /status: blockchain.Status

12.28 AddPeerBlock

  • Now connecting peers manually, right after mining, it broadcasts!

blockchain/chain.go

  • Add method AddPeerBlock

p2p/messages.go

  • MessageNewBlockNotify -> AddPeerBlock

12.29 Broadcast Transactions

  • Now right after Tx, even mempool is synced!

blockchain/transactions.go

  • Add Mutex to mempool
  • Add memOnce to mempool to init
  • Add method AddPeerTx

p2p/messages.go

  • Add iota MessageNewTxNotify, func notifyNewTx, handleMsg case MessageNewTxNotify

p2p/p2p.go

  • Add func BroadcastNewTx

rest/rest.go

func transactions

  • Add p2p.BroadcastNewTx(tx)

12.30 Global Memory Pool

Todo: Not only mining but also broadcasting should empty mempool as well

blockchain/transactions.go

  • Change mempool.Txs from slice to map (similar with peer)
    • nil should be empty map

blockchain/chain.go/AddPeerBlock()

  • Rename block -> newBlock
  • Add delete tx logic

12.31 Broadcast New Peer

  • If more than 3 peers, should inform there is new peer

p2p/p2p.go

  • Add func broadcastNewPeer
  • If broadcast is true, broadcastNewPeer() at AddPeer
  • To AddPeer, we need to know previous openPort -> send it when broadcastNewPeer

p2p/messages.go

  • Add iota MessageNewPeerNotify, func notifyNewPeer

12.32 Network Accomplished

p2p/messages.go

  • Split payload with :
  1. 2000: wants to connect to port 3000
  2. 3000: 2000 wants an upgrade
  3. 2000: wants to connect to port 4000
  4. 4000: 2000 wants an upgrade
  5. 4000: 3000 wants an upgrade
  6. 4000:
    Received the newest block from 127.0.0.1:3000
    Requesting all blocks to 127.0.0.1:3000
    Received all the blocks from 127.0.0.1:3000
    
  7. 3000:
    3000 wants to connect to port 4000
    Sending newest block to 127.0.0.1:4000
    127.0.0.1:4000 wants all the blocks.
    

12.33 Recap

  1. AddPeer(payload.Address, payload.Port, port[1:], true)
  2. broadcastNewPeer
  3. notifyNewPeer: broadcast MessageNewPeerNotify to all peers aside of newpeer
  4. if got MessageNewPeerNotify, AddPeer(.., false)