- Only 1 option: for loop
- Powerful standard library
- Low learning curve
go mod init github.com/devgony/gophercoin
ls
> go.mod # like package.json
touch main.go
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)
- 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
}
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)
- 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)
a := 2
b := a // copy
c := &a // borrow
a = 9
fmt.Println(a, b, *c) // 9 2 9
type person struct {
name string
age int
}
- 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)
}
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
}
- 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
- Concentrate to blockchain concept, solve side problem later
- 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}
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()
}
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
}
- 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)
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
}
- 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
}
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)
}
- install extension:
gotemplate-syntax
- mvp.css: https://andybrewer.github.io/mvp/
<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
touch partials/block.gohtml
- load
{{template "head"}}
- load glob
- can't use
**/
so that parseGlob n times
- can't use
templates = template.Must(template.ParseGlob(templateDir + "pages/*.gohtml"))
templates = template.Must(templates.ParseGlob(templateDir + "partials/*.gohtml")) // template^s
- template of template use
.
// home.gohtml
{{template "header" .PageTitle}}
// header.gohtml
...
<h1>{{.}}</h1>
...
- 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)
}
}
mkdir explorer
mv templates explorer/
cp main.go explorer/explorer.go
// explorer/explorer.go
templateDir string = "explorer/templates/"
- REST API
mkdir utils
touch utils/utils.go
func HandleErr(err error) {
if err != nil {
log.Panic(err)
}
}
- Marshal: convert from goInterface to JSON
- 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)
- struct field tag
https://pkg.go.dev/encoding/json#Marshal
Description string `json:"description"` // make lowercase
Payload string `json:"payload,omitempty"` // omit if empty
...
Payload: "data:string", // write data on body
- 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
}
- 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))
- 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)
- 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))
- can handle params
go get -u github.com/gorilla/mux
router := mux.NewRouter()
...
vars := mux.Vars(r)
- string to int
id, err := strconv.Atoi(vars["height"])
- new error
var ErrNotFound = errors.New("block not found")
- new errorResponse
type errorResponse struct {
ErrorMessage string `json:"errorMessage"`
}
- Middleware is a function to call before final destination
Handler
is an interface implementing method called ServerHTTPHandlerFunc
is type (adapter)HandlerFunc()
: constructing a type- adaptor ask us to send correct argument and adaptor implement everything we need
- flag
- cobra
- os.Args gives array of commands
go run main.go someCMD
-> [.../exe/main someCMD]
- to exit, use
os.Exit(0)
- 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")
}
- 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
- currently everthing is on memory (slice of block)
- bolt: key/value database specified for get/set
- eg) "sdkfljsdlfjds": {"data: PrvHash"}
- There will be no immediate
Start
so that start coding fromdb/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
}
- divide & conquer
mv blockchain/blockchain.go blockchain/chain.go
touch blockchain/block.go
- 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)
}
- move ToBytes from
block.go
toutils.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()
}
- 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
}
- 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
- 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
- Refactor SaveBlockchain -> SaveCheckpoint
- bolt, get/set data to bucket
- singletone -> if no checkpoint -> create genesis
- func persist -> save to bolt database
- Proof Of Work
- Add properties to Block struct at
block.go
Difficulty int `json:"difficulty"`
Nonce int `json:"nonce"`
- Delete db
rm blockchain.db
- 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++
}
}
- func mine at
block.go
- Modify way get hash ->
block.mine()
atblock.go
- Refactor Hash from
block.go
toutils.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
}
}
- 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
- Add status route at
rest.go
- Refactor Handling errors for encoder at
rest.go
- Cheack status of difficulty grows each 5 blocks
- Course ~#9 were all about Protecting Data
- Here going to learn Moving value between our user
- uTxOut: Unspent Transaction Output mpdel?
- 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)]
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
- Add func txOuts, TxOutsByAddress, BalanceByAddress at
chain.go
- Add struct balanceResponse, func balance, router balance at
rest.go
- Memory pool: where we put unconfirmed transaction
- After confirmed -> Part of Block
- 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
- 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
- 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
- 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
- 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
attransactions.go
- 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.
- 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?
- 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
}
- 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
}
- If it is mutating struct ->
method
- Else, -> normal
func
with struct as input param - Sort method first, func last
- 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
- Remove nil condition of func Blockchain at
- If he owns unspent output
- If he approved the transaction
- How signature, verification works
- Persistance to db
- Impl signature, verification with tracsaction
mkdir wallet
touch wallet/wallet.go
- Hash the msg
"i love you" -> hash(x) -> "hashed_message"
- Generate key pair
keypair (privateKey, publicKey)
(save privateK to a file -> wallet)
- Sign the hash
("hashed_message" + privateKey) -> "signature"
- Verify with publicKey
("hashed_message" + "signature" + publicKey) -> true / false
- ecdsa: Elliptic Curve Digital Signature Algorithm
privateKey = ecdsa.GenerateKey
hashAsBytes = hex.DecodeString(Hash(message))
r, s = ecdsa.Sign
- 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"
)
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)
- Add func hasWalletFile at
wallet.go
- Singletone pattern func Wallet skeleton at
wallet.go
- Add func createPrivKey, persistKey at
wallet.go
- If there is new var togather, we can recreate err with
newVar err :=
- (actually updating)
- pure update way
bytes, err := x509.MarshalECPrivateKey(key)
utils.HandleErr(err)
err = os.WriteFile(fileName, bytes, 0644)
...
- recreate way
bytes, err := x509.MarshalECPrivateKey(key)
utils.HandleErr(err)
newVar2, err := os.WriteFile(fileName, bytes, 0644)
- 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
}
- Add func aFromK, sign at
wallet.go
- Replace
"nico"
towallet.Wallet().Address
attransactions.go
- Add func restoreBigInts, verify at
wallet.go
- restoreBigints can handle signature, address(to publicKey)
- Add func encodeBigInts at
wallet.go
- Refactor with encodeBigInts
- Add func Txs, FindTx at
chain.go
- Rename TxIn.Owner -> Signature, TxOut.Owner -> Address at
transactions.go
- Add func sign at
transactions.go
- 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
- check valid, return ErrorNotValid
input.Onwer == address
⬇️
if FindTx(b, input.TxID).TxOuts[input.Index].Address == address
func myWallet(rw http.ResponseWriter, r *http.Request) {
address := wallet.Wallet().Address
json.NewEncoder(rw).Encode(struct {
Address string `json:"address"`
}{Address: address})
}
- 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?
- 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
(TxOut1(publickKey), TxOut2)
Tx
TxIn[
(TxOut1)
(TxOut2)
]
Sign with my privateKey
TxIn.Sign + TxOut1.Address -> true / false
- Lean Peer To Peer by simple Chatting app
- Running function at separate parallel dimension
- Can't assign or return value to variable immediately;
go countToTen()
- Deadlock: Channel should not receive more than coroutine
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)
}
c := make(chan int, NumOfBuffer)
- Don't block first N values, then block/wait the queue like normal Unbuffered channel
- 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
const socket = new WebSocket("ws://localhost:4000/ws");
- Add func Upgrade
- Add ws to URL, router
- Add func loggerMiddleware
- Add CheckOrigin
- Add conn.ReadMessage with for loop
- conn.ReadMessage is a receiver Channel for socket
- Add form, send, receive eventListener
- Add conn.WriteMessage
- Connect client to client through server
- http.ListenAndServe uses goroutine
- Add slice of conn, append conn, send to other conns,
- If browser is refreshed, gets error -> How to handle closed connection?
- A Message should not block others -> How to separate?
- Should connect peer and peer not through server
- Add func
AddPeer
- Add struct addPeerPayload, func peers, router peers
touch p2p/peer.go
- Add struct
peer
, funcinitPeer
- Add GET to router peers
- send openPort with URL
- get openPort from URL
- p2p.Addpeer(..., port)
- Add func Spliiter
- Refactor with utils.Splitter
- Add method read
- Add go p.read() at func
initPeer
- Add conn.WriteMessage at func
Upgrade
- Instead of writing once, use coroutine and channel
- Add method
write
- make channel initiating peer
- go p.write()
- send message to inbox(channel)
- Delete Hellos, Add
initPeer()
- Add key, address, port to struct peer
- Add func
close
- Add defer p.close() at
read
,write
- 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
- 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",
...
]
- 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)
}
- :4000 sends newest block to :3000
- :3000 realizes that :3000 is behind, ask to :4000 latest all blocks
- :4000 gives latest all blocks
- :4000 sends newest block to :3000
- :3000 realizes that :3000 is ahead, send newest block to :4000
- :4000 realizes that :4000 is behind, ask to :3000 latest all blocks
- :3000 gives latest all blocks
touch p2p/messages.go
- Auto increament sequence
const (
MessageNewestBlock MessageKind = iota
MessageAllBlocksRequest
MessageAllBlocksResponse
)
- Let's Communicate with json
- Change from
ReadMessage
toReadJSON
- 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
- sendNewstBlock in addPeer
- Add func ToJSON
- Add func handleMsg with Unmarshal
- Remove func addPayload, Use ToJSON instead.
- handleMsg whenever read()
- Datarace: Though there is lock at close(), opposite side stil add Peer twice -> Add Lock at initPeer
- Each client should use different db
- Add func getDbName: get db name with port number
- Add func requestAllBlocks, sendAllBlocks
-
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?
- Add console print
- 4000: wants to connect to port 3000
- 3000: 4000 wants an upgrade
- 4000: Sending newest block to 127.0.0.1:3000
- 3000: Received the newest block from 127.0.0.1:4000
- 4000: 127.0.0.1:3000 wants all the blocks.
- 3000: Received all the blocks from 127.0.0.1:4000
- After mining blocks at 3000,
- 4000: wants to connect to port 3000
- 3000: 4000 wants an upgrade
- 4000: Sending newest block to 127.0.0.1:3000
- 3000: Received the newest block from 127.0.0.1:4000 // 3000 have more than 4000
- 3000: Sending newest block to 127.0.0.1:4000
- 4000: Received the newest block from 127.0.0.1:3000 // realize 4000 is behind 3000
- 4000: Requesting all blocks to 127.0.0.1:3000
- 3000: 127.0.0.1:4000 wants all the blocks.
- 4000: Received all the blocks from 127.0.0.1:3000
- Can syncronize and persist now!
- Rename method persist -> func persitBlock
- Add method Replace
- Renew blockchain
- EmptyBlocks and persist newBlocks
- Add funcEmptyBlocks()
- blockchain.Blockchain().Replace(payload) at the end of handleMsg
- Add m to struct blockchain
- Lock & unlock at func Blocks, Status, Replace
- func AddBlock returns newBlock
- Add func Status: show status of blockchain at
/status
- Add iota MessageNewBlockNotify
- Add func notifyNewBlock, BroadcastNewBlock
/blocks
: after AddBlock, BroadcastNewBlock/status
: blockchain.Status
- Now connecting peers manually, right after mining, it broadcasts!
- Add method AddPeerBlock
- MessageNewBlockNotify -> AddPeerBlock
- Now right after Tx, even mempool is synced!
- Add Mutex to mempool
- Add memOnce to mempool to init
- Add method AddPeerTx
- Add iota MessageNewTxNotify, func notifyNewTx, handleMsg case MessageNewTxNotify
- Add func BroadcastNewTx
- Add p2p.BroadcastNewTx(tx)
- Change mempool.Txs from slice to map (similar with peer)
- nil should be empty map
- Rename block -> newBlock
- Add delete tx logic
- If more than 3 peers, should inform there is new peer
- Add func broadcastNewPeer
- If broadcast is true, broadcastNewPeer() at
AddPeer
- To AddPeer, we need to know previous openPort -> send it when broadcastNewPeer
- Add iota MessageNewPeerNotify, func notifyNewPeer
- Split payload with
:
- 2000: wants to connect to port 3000
- 3000: 2000 wants an upgrade
- 2000: wants to connect to port 4000
- 4000: 2000 wants an upgrade
- 4000: 3000 wants an upgrade
- 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
- 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.
- AddPeer(payload.Address, payload.Port, port[1:], true)
- broadcastNewPeer
- notifyNewPeer: broadcast
MessageNewPeerNotify
to all peers aside of newpeer - if got
MessageNewPeerNotify
, AddPeer(.., false)