blockchain_go/server.go

463 lines
9.0 KiB
Go

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)
// TODO: how to update UTXOSet when the order of new blocks is not correct?
// UTXOSet := UTXOSet{bc}
// UTXOSet.Update(block)
fmt.Printf("Added block %x\n", block.Hash)
fmt.Printf("Added block %d\n", block.Height)
fmt.Println(blocksInTransit)
if len(blocksInTransit) > 0 {
blockHash := blocksInTransit[0]
sendGetData(payload.AddrFrom, "block", blockHash)
blocksInTransit = blocksInTransit[1:]
}
}
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 {
var txs []*Transaction
for _, tx := range mempool {
// TODO: remove this check after improving MineBlock
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.Update(newBlock)
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})
}
}
}
}
}
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
}