466 lines
8.9 KiB
Go
466 lines
8.9 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)
|
|
|
|
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
|
|
}
|