Select Git revision

xavier.perret authored
main.go 21.14 KiB
package main
import (
"encoding/json"
"fmt"
"gopkg.in/yaml.v3"
"io/ioutil"
"math/rand"
"net"
. "node/types"
"os"
"strconv"
"strings"
"time"
)
/**
* @file
* @brief Distributed Systems - Blockchain
* @author Xavier Perret
* @date 28/09/2022 - 05/10/2022
*/
// Config - is the configuration of a node, it's id, address, port and neighbours
type Config struct {
ID int `yaml:"id"`
Address string `yaml:"address"`
Port int `yaml:"port"`
Neighbours []struct {
ID int `yaml:"id"`
Address string `yaml:"address"`
Port int `yaml:"port"`
} `yaml:"neighbours"`
}
// ServerReady is true if the server can listen to new connection attempt, used for initialization
var ServerReady = false
// DB in memory database
var DB Database
// ConnectionType is the type of connection used by the server
var ConnectionType = "tcp"
// a node generates a transaction “trans”, stores it in its object-based storage
// and sends it to all nodes of the network. This function relies on the broadcast by wave distributed
// algorithm.
// trans -
func createTransaction(trans Transaction) {
}
func isTransactionValid(trans Transaction) bool {
return false
}
// A node sends a request to all nodes to check if the transaction trans, stored locally,
// is the same as the ones stored in the other nodes. The result (rate) is a percentage representing the
// transactions which are the same as the one stored locally. This function relies on the broadcast by wave
// with ACK distributed algorithm. However, some adjustments are needed to implement this function.
func vote(server net.Listener, trans Transaction, config Config, parentAddress string, stimulatedByClient bool) AckTransaction {
var ack AckTransaction
ack.Id = ""
ack.TotalNodes = 0
ack.AmountOfCorrectNode = 0
var transactionToRate Transaction
for _, transactionInDatabase := range DB {
if transactionInDatabase.Id == trans.Id {
ack.Id = trans.Id
transactionToRate = transactionInDatabase
}
}
// todo add stuff
if ack.Id != "" {
if transactionToRate.Receiver != trans.Receiver {
ack.AmountOfCorrectNode = 0
ack.TotalNodes = 1
}
if transactionToRate.Sender != trans.Sender {
ack.AmountOfCorrectNode = 0
ack.TotalNodes = 1
}
if transactionToRate.Amount != trans.Amount {
ack.AmountOfCorrectNode = 0
ack.TotalNodes = 1
}
}
if ack.TotalNodes == 0 {
ack.AmountOfCorrectNode = 1
ack.TotalNodes = 1
}
var newAck AckTransaction
reach := false
count := 0
limit := len(config.Neighbours)
if stimulatedByClient {
reach = true
go sendVoteToAllNeighbours(config, trans, parentAddress)
}
var mess Message
for count < limit {
if count != 0 || reach {
fmt.Println("Count is ", count, " to ", limit)
connection, err := server.Accept()
if err != nil {
fmt.Println("Error accepting: ", err.Error())
err = nil
continue
}
fmt.Println("*****************************************")
fmt.Println("Processing client request number ", count)
fmt.Println("The connection is ", connection)
fmt.Println("The remote address is ", connection.RemoteAddr())
fmt.Println("The local address is ", connection.LocalAddr())
fmt.Println("*****************************************")
err = json.NewDecoder(connection).Decode(&mess)
if err != nil {
fmt.Println("Error while decoding the transactionInDatabase", err)
err = nil
continue
}
if mess.MessageType == "AckResponse" {
var body map[string]interface{} = mess.MessageBody.(map[string]interface{})
newAck.Id = body["id"].(string)
newAck.AmountOfCorrectNode = body["amountOfCorrectNode"].(int)
newAck.TotalNodes = body["totalNodes"].(int)
if ack.Id == newAck.Id {
ack.TotalNodes += newAck.TotalNodes
ack.AmountOfCorrectNode += newAck.AmountOfCorrectNode
}
fmt.Println("Node correct : ", ack.AmountOfCorrectNode, "out of ", ack.TotalNodes)
}
}
count++ // received message
if !reach {
reach = true
go sendVoteToAllNeighbours(config, trans, parentAddress)
}
}
fmt.Println("***********************************")
fmt.Println("Transaction has been rated")
fmt.Println("Node correct : ", ack.AmountOfCorrectNode, "out of ", ack.TotalNodes)
fmt.Println("***********************************")
if !stimulatedByClient {
address := parentAddress
for _, neighbour := range config.Neighbours {
fmt.Println("Comparing ", neighbour.Address, " with ", parentAddress)
if neighbour.Address == parentAddress {
address = address + ":" + strconv.Itoa(neighbour.Port)
fmt.Println("Calculated address is ", address)
}
}
fmt.Println("Sending to ", address)
go sendAckToNeighbour(config, ack, address)
}
return ack
}
func sendVoteToAllNeighbours(config Config, trans Transaction, parentIp string) {
for neighbour := range config.Neighbours {
ip := config.Neighbours[neighbour].Address
port := strconv.Itoa(config.Neighbours[neighbour].Port)
address := ip + ":" + port
if parentIp != ip {
go sendVoteToNeighbour(config, trans, address)
}
}
}
func fake(authenticTrans Transaction, fakeTrans Transaction) {
}
func printTransaction(trans Transaction) {
fmt.Println("--------------------")
fmt.Println("Printing transaction")
fmt.Println("The id is ", trans.Id)
fmt.Println("The sender is ", trans.Sender)
fmt.Println("The receiver is ", trans.Receiver)
fmt.Println("The amount is ", trans.Amount)
fmt.Println("--------------------")
}
func listAllTransactions() {
for i, transaction := range DB {
fmt.Println("***********INDEX N=,", i, "****************")
printTransaction(transaction)
fmt.Println("***********************************")
}
}
// userCreatedTransaction is a function to interactively create a transaction using the terminal
func userCreatedTransaction(config Config) Transaction {
fmt.Println("--------------------STARTING USER CREATED TRANSACTION--------------------")
var trans Transaction
now := time.Now().String()
randomString := strconv.Itoa(rand.Int())
trans.Id = now + randomString
fmt.Println()
var receiver string
fmt.Print("\nPlease enter the receiver")
_, err := fmt.Scanln(&receiver)
if err != nil {
return Transaction{}
}
trans.Receiver = receiver
var sender string
fmt.Print("\nPlease enter the sender")
_, err = fmt.Scanln(&sender)
if err != nil {
return Transaction{}
}
trans.Sender = sender
fmt.Println("Creating a transaction with sender id:", trans.Sender)
fmt.Println("transaction id is ", trans.Id)
fmt.Print("\nEnter amount :")
_, err = fmt.Scanln(&trans.Amount)
if err != nil {
fmt.Println("error")
os.Exit(1)
}
for neighbour := range config.Neighbours {
fmt.Println("Neighbour ", config.Neighbours[neighbour].ID)
fmt.Println("address ", config.Neighbours[neighbour].Address)
fmt.Println("port", config.Neighbours[neighbour].Port)
}
fmt.Println("--------------------FINISHED USER CREATED TRANSACTION--------------------")
return trans
}
// sendTransactionToNeighbour is a function to send a transaction to a neighbour
func sendTransactionToNeighbour(config Config, trans Transaction, destinationIp string, destinationPort string) {
fmt.Println()
ipAddr := destinationIp + ":" + destinationPort
fmt.Println("Trying to connect to ", destinationIp, ":", destinationPort)
conn, err := net.Dial(ConnectionType, ipAddr)
if err != nil {
fmt.Println("Error while connecting to the neighbour", err)
os.Exit(1)
}
mess := Message{
MessageType: "transaction",
MessageBody: trans,
}
fmt.Println("Sending message to neighbour", mess)
encoder := json.NewEncoder(conn)
err = encoder.Encode(mess)
if err != nil {
fmt.Println("Error while encoding the transaction", err)
os.Exit(1)
}
conn.Close()
fmt.Println("MessageBody successfully sent to neighbour")
}
func sendTransactionToAllNeighbours(config Config, trans Transaction) {
for neighbour := range config.Neighbours {
fmt.Println("Sending transaction to neighbour ", config.Neighbours[neighbour].ID)
ip := config.Neighbours[neighbour].Address
port := strconv.Itoa(config.Neighbours[neighbour].Port)
sendTransactionToNeighbour(config, trans, ip, port)
}
}
func printAllNeighbours(config Config) {
fmt.Println("Listing neighbours :")
for neighbour := range config.Neighbours {
fmt.Println("Neighbour ", config.Neighbours[neighbour].ID)
}
}
func processClient(connection net.Conn, server net.Listener, config Config, stimulatedByClient bool) {
fmt.Println("Processing client")
fmt.Println("The connection is ", connection)
fmt.Println("The remote address is ", connection.RemoteAddr())
fmt.Println("The local address is ", connection.LocalAddr())
var mess Message
jsonDecoder := json.NewDecoder(connection)
err := jsonDecoder.Decode(&mess)
if err != nil {
fmt.Println("Error while decoding the message", err)
}
fmt.Println("The message is ", mess)
fmt.Println("A client has connected")
if mess.MessageType == "error" {
fmt.Println("Error while decoding the message", err)
return
} else if mess.MessageType == "transaction" {
fmt.Println("Received a transaction")
var trans Transaction
var body map[string]interface{} = mess.MessageBody.(map[string]interface{})
trans.Id = body["id"].(string)
trans.Receiver = body["receiver"].(string)
trans.Sender = body["sender"].(string)
trans.Amount = body["amount"].(string)
for _, transaction := range DB {
if transaction.Id == trans.Id {
return
}
}
processTransaction(server, config, trans, stimulatedByClient)
} else if mess.MessageType == "rate" {
fmt.Println("Received a rate")
var trans Transaction
var body map[string]interface{} = mess.MessageBody.(map[string]interface{})
trans.Id = body["id"].(string)
trans.Receiver = body["receiver"].(string)
trans.Sender = body["sender"].(string)
trans.Amount = body["amount"].(string)
printTransaction(trans)
// todo change this for cloud
address := strings.Split(connection.RemoteAddr().String(), ":")[0]
vote(server, trans, config, address, stimulatedByClient)
} else if mess.MessageType == "fake" {
fmt.Println("Received a fake")
var fakeTransaction Transaction
var body map[string]interface{} = mess.MessageBody.(map[string]interface{})
fakeTransaction.Id = body["id"].(string)
fakeTransaction.Receiver = body["receiver"].(string)
fakeTransaction.Sender = body["sender"].(string)
fakeTransaction.Amount = body["amount"].(string)
for transactionIndex := range DB {
if DB[transactionIndex].Id == fakeTransaction.Id {
DB[transactionIndex].Sender = fakeTransaction.Sender
DB[transactionIndex].Receiver = fakeTransaction.Receiver
DB[transactionIndex].Amount = fakeTransaction.Amount
fmt.Println("Successfully replaced the legitimate transaction with a fake one!")
}
}
} else if mess.MessageType == "list" {
fmt.Println("Received a list all transactions order")
listAllTransactionsToClient(connection)
} else {
fmt.Println("Unknown message type")
}
}
func listAllTransactionsToClient(conn net.Conn) {
mess := Message{
MessageType: "list",
MessageBody: DB,
}
fmt.Println("Sending message to neighbour", mess)
encoder := json.NewEncoder(conn)
err := encoder.Encode(mess)
if err != nil {
fmt.Println("Error while encoding the transaction", err)
os.Exit(1)
}
conn.Close()
}
func processTransaction(server net.Listener, config Config, trans Transaction, stimulatedByClient bool) {
fmt.Println("Processing transaction")
printTransaction(trans)
fmt.Println("The transaction has been added")
reach := false
count := 0
limit := len(config.Neighbours)
if stimulatedByClient {
reach = true
addTransactionToDb(trans)
go sendTransactionToAllNeighbours(config, trans)
}
for count < limit {
if count != 0 || reach {
fmt.Println("Count is ", count, " to ", limit)
connection, err := server.Accept()
if err != nil {
fmt.Println("Error accepting: ", err.Error())
err = nil
continue
}
fmt.Println("*****************************************")
fmt.Println("Processing client request number ", count)
fmt.Println("The connection is ", connection)
fmt.Println("The remote address is ", connection.RemoteAddr())
fmt.Println("The local address is ", connection.LocalAddr())
fmt.Println("*****************************************")
err = json.NewDecoder(connection).Decode(&trans)
if err != nil {
fmt.Println("Error while decoding the transaction", err)
err = nil
continue
}
fmt.Println("Received back a transaction")
printTransaction(trans)
}
count++ // received message
if !reach {
reach = true
addTransactionToDb(trans)
go sendTransactionToAllNeighbours(config, trans)
}
}
fmt.Println("***********************************")
fmt.Println("All transactions have been received")
fmt.Println("***********************************")
}
func addTransactionToDb(trans Transaction) {
for _, transaction := range DB {
if transaction.Id == trans.Id {
return
}
}
DB = append(DB, trans)
}
func serverLoop(config Config, stimulatedByClient bool) {
addr := "0.0.0.0"
port := strconv.FormatInt(int64(config.Port), 10)
server, err := net.Listen("tcp", addr+":"+port)
if err != nil {
fmt.Println("Error while creating the server", err)
os.Exit(1)
}
fmt.Println("Server is ready to accept connections")
fmt.Println("listening on ", addr, ":", port)
ServerReady = true
for {
// Listening for connections
connection, err := server.Accept()
if err != nil {
fmt.Println("Error accepting: ", err.Error())
} else {
processClient(connection, server, config, stimulatedByClient)
}
}
}
func userInputLoop(config Config, isAlsoServer bool) {
for true {
var operation string
if !ServerReady {
continue
}
fmt.Println()
fmt.Println()
fmt.Println("Please enter the operation you want to do")
fmt.Println("1. Create a transaction")
fmt.Println("2. Rate a transaction (from the client)")
fmt.Println("3. Fabricate a fake transaction")
fmt.Println("4. Print all transactions")
fmt.Println("8. Exit")
fmt.Print("Your choice: ")
_, err := fmt.Scanln(&operation)
if err != nil {
fmt.Println("error :", err.Error())
os.Exit(1)
}
switch operation {
case "1":
fmt.Println("You chose to create a transaction")
if isAlsoServer {
fmt.Println("Not yet implemented!")
//newTrans := userCreatedTransaction(config)
//createTransaction(newTrans)
} else {
newTrans := userCreatedTransaction(config)
addTransactionToDb(newTrans)
printAllNeighbours(config)
fmt.Println("TRANSACTION READY TO BE SENT")
fmt.Println("Please enter the ID of the neighbour you want to send the transaction to")
var neighbourID string
_, err := fmt.Scanln(&neighbourID)
if err != nil {
fmt.Println("error :", err.Error())
os.Exit(1)
}
neighbourIDInt, err := strconv.ParseInt(neighbourID, 10, 64)
if err != nil {
fmt.Println("error :", err.Error())
os.Exit(1)
}
sendTransactionToNeighbour(config, newTrans, config.Neighbours[neighbourIDInt].Address, strconv.Itoa(config.Neighbours[neighbourIDInt].Port))
}
break
case "2":
fmt.Println("You chose to rate a transaction")
listAllTransactions()
fmt.Print("\nPlease enter the index of the transaction you want to rate:")
var transID string
_, err := fmt.Scanln(&transID)
if err != nil {
fmt.Println("error :", err.Error())
os.Exit(1)
}
transIDInt, err := strconv.ParseInt(transID, 10, 64)
if err != nil {
fmt.Println("error :", err.Error())
os.Exit(1)
}
printAllNeighbours(config)
fmt.Println("Please enter the ID of the neighbour you want to send the transaction to")
var neighbourID string
_, err = fmt.Scanln(&neighbourID)
if err != nil {
fmt.Println("error :", err.Error())
os.Exit(1)
}
neighbourIDInt, err := strconv.ParseInt(neighbourID, 10, 64)
if err != nil {
fmt.Println("error :", err.Error())
os.Exit(1)
}
address := config.Neighbours[neighbourIDInt].Address + ":" + strconv.Itoa(config.Neighbours[neighbourIDInt].Port)
fmt.Println("Sending rate demand to ", address)
sendVoteToNeighbour(config, DB[transIDInt], address)
break
case "3":
fmt.Println("You chose to fabricate a fake transaction")
fmt.Println("You chose to fake a transaction")
listAllTransactions()
fmt.Print("\nPlease enter the index of the transaction you want to overwrite by faking:")
var transID string
_, err := fmt.Scanln(&transID)
if err != nil {
fmt.Println("error :", err.Error())
os.Exit(1)
}
transIDInt, err := strconv.ParseInt(transID, 10, 64)
if err != nil {
fmt.Println("error :", err.Error())
os.Exit(1)
}
printAllNeighbours(config)
fmt.Println("Please enter the ID of the neighbour you to ask to fake the given transaction")
var neighbourID string
_, err = fmt.Scanln(&neighbourID)
if err != nil {
fmt.Println("error :", err.Error())
os.Exit(1)
}
neighbourIDInt, err := strconv.ParseInt(neighbourID, 10, 64)
if err != nil {
fmt.Println("error :", err.Error())
os.Exit(1)
}
tmpFakeTrans := userCreatedTransaction(config)
transToFake := DB[transIDInt]
fakeTrans := Transaction{
Id: transToFake.Id,
Sender: tmpFakeTrans.Sender,
Receiver: tmpFakeTrans.Receiver,
Amount: tmpFakeTrans.Amount,
}
ip := config.Neighbours[neighbourIDInt].Address
port := strconv.Itoa(config.Neighbours[neighbourIDInt].Port)
address := ip + ":" + port
sendFakeTransactionToNeighbour(fakeTrans, address)
break
case "4":
fmt.Println("You chose to print all transactions")
listAllTransactions()
break
case "5":
fmt.Println("You chose to ask for all transactions of a given node")
break
case "6":
break
case "7":
break
case "8":
fmt.Println("You chose to exit")
os.Exit(0)
default:
fmt.Println("You chose an invalid option")
break
}
}
}
func sendFakeTransactionToNeighbour(trans Transaction, address string) {
fmt.Println()
fmt.Println("Trying to connect to ", address)
conn, err := net.Dial(ConnectionType, address)
if err != nil {
fmt.Println("Error while connecting to the neighbour", err)
os.Exit(1)
}
mess := Message{
MessageType: "fake",
MessageBody: trans,
}
fmt.Println("Sending message to neighbour", mess)
encoder := json.NewEncoder(conn)
err = encoder.Encode(mess)
if err != nil {
fmt.Println("Error while encoding the transaction", err)
os.Exit(1)
}
conn.Close()
fmt.Println("MessageBody successfully sent to neighbour")
}
func sendVoteToNeighbour(config Config, trans Transaction, address string) {
fmt.Println()
fmt.Println("Trying to connect to ", address)
conn, err := net.Dial(ConnectionType, address)
if err != nil {
fmt.Println("Error while connecting to the neighbour", err)
os.Exit(1)
}
mess := Message{
MessageType: "rate",
MessageBody: trans,
}
fmt.Println("Sending message to neighbour", mess)
encoder := json.NewEncoder(conn)
err = encoder.Encode(mess)
if err != nil {
fmt.Println("Error while encoding the transaction", err)
os.Exit(1)
}
conn.Close()
fmt.Println("MessageBody successfully sent to neighbour")
}
func sendAckToNeighbour(config Config, ack AckTransaction, address string) {
fmt.Println("*******************STARTING TO SEND ACK*******************")
fmt.Println("Trying to connect to ", address)
var ackToSend AckTransaction
ackToSend.Id = ack.Id
ackToSend.AmountOfCorrectNode = ack.AmountOfCorrectNode
ackToSend.TotalNodes = ack.TotalNodes
conn, err := net.Dial(ConnectionType, address)
if err != nil {
fmt.Println("Error while connecting to the neighbour", err)
os.Exit(1)
}
mess := Message{
MessageType: "AckResponse",
MessageBody: ackToSend,
}
fmt.Println("Sending message to neighbour", mess)
encoder := json.NewEncoder(conn)
err = encoder.Encode(mess)
if err != nil {
fmt.Println("Error while encoding the transaction", err)
os.Exit(1)
}
conn.Close()
fmt.Println("MessageBody successfully sent to neighbour")
fmt.Println("*******************FINISHED ACK*******************")
}
func main() {
if len(os.Args) <= 2 {
fmt.Println("First argument should be the path of the config file '--path=<config.yaml>'")
fmt.Println("Second argument should be either '--server' or '--client'")
os.Exit(1)
}
// INIT CONFIG
args := os.Args[1:]
fmt.Println("Program started with arguments", args)
fmt.Println("config file path is ", args[0][7:])
buf, err := ioutil.ReadFile(args[0][7:])
if err != nil {
fmt.Println("Error while reading the config file", err)
fmt.Println("Exiting...")
os.Exit(1)
}
var c Config
err = yaml.Unmarshal(buf, &c)
if err != nil {
os.Exit(1)
}
fmt.Println("The config is ", c)
fmt.Println("The ID is ", c.ID)
fmt.Println("The port is ", c.Port)
fmt.Println("The neighbours are :")
for neighbour := range c.Neighbours {
fmt.Println("\tThe neighbour is ", c.Neighbours[neighbour].ID)
fmt.Println("\tThe neighbour address is ", c.Neighbours[neighbour].Address)
fmt.Println("\tThe neighbour port is ", c.Neighbours[neighbour].Port)
fmt.Println()
}
fmt.Println()
// INIT SERVER AND/OR CLIENT LOOP
isStimulatedByClient := false
if len(os.Args) == 4 {
if os.Args[3] == "--root" {
isStimulatedByClient = true
}
}
if os.Args[2] == "--server" {
fmt.Println("Starting server")
go serverLoop(c, isStimulatedByClient)
userInputLoop(c, true)
}
if os.Args[2] == "--client" {
fmt.Println("Starting client")
go serverLoop(c, isStimulatedByClient)
userInputLoop(c, false)
}
}