diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..9e4a210068506c9c80685f2972657beb1e7e9baf --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ + +*.pdf diff --git a/README.md b/README.md deleted file mode 100644 index 9c76c49b89aef1cb724c2fb175c9e582b693cdc9..0000000000000000000000000000000000000000 --- a/README.md +++ /dev/null @@ -1,45 +0,0 @@ -# Distributed Systems - -## Lab 1 - -### Developing and deploying a broadcast algorithm for a blockchain management system on a Cloud infrastructure - -- Student : Xavier Perret -- Email : xavier.perret@etu.hesge.ch -- Group : 1 -- <ssh://git@ssh.hesge.ch:10572/xavier.perret/perso-distributed-systems.git> -- <https://gitedu.hesge.ch/xavier.perret/perso-distributed-systems.git> - -### Client - -To launch the client you need to run the following command: - -```bash -go main.go --config=client_config.yaml --client -``` - -Please note that the `client_config.yaml` can be a `neighbor-x.yaml` file if you want to create a transaction and spread -it to neighbours instead on manually entering an ip. - -### Server - -#### Root Server -To lauch the server to which you wish to send commands using the client you need to do - -```bash -go main.go --config=neighbor-0.yaml --server --root -``` - -#### Normal Server -To launch the other server you need to run the following command: - -```bash -go main.go --config=server_config.yaml --server -``` - -### Functionalities - -- Able to create a transaction from the client and then request to root server to broadcast by wave to all the other servers -- Able to send a rate request from client to root server then broadcast by wave to all the other servers (buggy at the moment) -- Not able to fake a transaction yet -- Able to print all local transaction from any server or client \ No newline at end of file diff --git a/app/go.mod b/app/go.mod deleted file mode 100644 index eb7f3bd04ac507a6740d038273ead9ee0d0cc247..0000000000000000000000000000000000000000 --- a/app/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module node - -go 1.19 - -require gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/app/go.sum b/app/go.sum deleted file mode 100644 index 4bc033780105d3921d4aa4e0366ea9cbe59c8995..0000000000000000000000000000000000000000 --- a/app/go.sum +++ /dev/null @@ -1,3 +0,0 @@ -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/app/main.go b/app/main.go deleted file mode 100644 index 667ea612d1edd3306c1a909891233caebed920f4..0000000000000000000000000000000000000000 --- a/app/main.go +++ /dev/null @@ -1,658 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "gopkg.in/yaml.v3" - "io/ioutil" - "math/rand" - "net" - "os" - "strconv" - "time" -) - -/** - * @file - * @brief Distributed Systems - Blockchain - * @author Xavier Perret - * @date 28/09/2022 - 05/10/2022 - */ - -// Message - MessageBody can be of any type but will be use here for Transaction, AckTransaction -type Message struct { - MessageType string `json:"messageType"` - MessageBody interface{} `json:"messageBody"` -} - -// Transaction - represents a transaction in the blockchain -type Transaction struct { - Id string `json:"id"` - Sender string `json:"sender"` - Receiver string `json:"receiver"` - Amount string `json:"amount"` -} - -// AckTransaction - represents an acknowledgement of a transaction @see vote function -type AckTransaction struct { - Id string `json:"id"` - AmountOfCorrectNode int `json:"amountOfCorrectNode"` - TotalNodes int `json:"totalNodes"` -} - -// 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 []Transaction - -// 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) - } - } - - if stimulatedByClient { - fmt.Println("***********************************") - fmt.Println("Transaction has been rated") - fmt.Println("Node correct : ", ack.AmountOfCorrectNode, "out of ", ack.TotalNodes) - fmt.Println("***********************************") - go sendAckToNeighbour(config, ack, parentAddress) - } - 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 address != parentIp { - 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 - vote(server, trans, config, connection.LocalAddr().String(), stimulatedByClient) - } else if mess.MessageType == "fake" { - fmt.Println("Received a fake") - fmt.Println("Not yet implemented") - } else if mess.MessageType == "list" { - fmt.Println("Received a list all transactions order") - listAllTransactions() - } else { - fmt.Println("Unknown message type") - } -} - -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") - break - case "4": - fmt.Println("You chose to print all transactions") - listAllTransactions() - break - case "5": - 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 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() - 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: "AckResponse", - MessageBody: ack, - } - 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 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) - } -} diff --git a/lab1/README.md b/lab1/README.md new file mode 100644 index 0000000000000000000000000000000000000000..ccc326fb2afe93726f0a6b22b3fc8e8b6b926cc0 --- /dev/null +++ b/lab1/README.md @@ -0,0 +1,145 @@ +# Distributed Systems + +## Lab 1 - Deliverable 1.3 + +### Developing and deploying a broadcast algorithm for a blockchain management system on a Cloud infrastructure + +- Student : Xavier Perret +- Email : xavier.perret@etu.hesge.ch +- Group : 1 +- <ssh://git@ssh.hesge.ch:10572/xavier.perret/perso-distributed-systems.git> +- <https://gitedu.hesge.ch/xavier.perret/perso-distributed-systems.git> + +### Client + +To launch the client you need to run the following command: + +```bash +go client.go --config=client_config.yaml +``` + +Please note that the `client_config.yaml` can be a `neighbor-x.yaml` file if you want to create a transaction and spread +it to neighbours instead on manually entering an ip. + +### Server + +#### Root Server +To lauch the server to which you wish to send commands using the client you need to do + +```bash +go server.go --config=neighbor-x.yaml --root +``` + +#### Normal Server +To launch the other server you need to run the following command: + +```bash +go server.go --config=neighbor-x.yaml +``` + +### Functionalities + +- Able to create a transaction from the client and then request to root server to broadcast by wave to all the other servers +- Able to send a rate request from client to root server then broadcast by wave to all the other servers +- Able to fake a transaction on a node by using its command line +- Able to print all local transaction from any server or client on a node by using its command line + +### Object Storage Module + +#### Requirements + +- Go 1.14 +- Azure CLI +- Azure Storage Account configured (like <https://learn.microsoft.com/en-us/azure/storage/blobs/storage-quickstart-blobs-go>) + - For testing you need to change the "hepiadistributedsystems" by the storage account name + ```go + azureCreds := InitAzureCredentials("hepiadistributedsystems") + ``` +- Should not be necessary _Azure Storage Blob Sdk (The right version is already in `go.mod` dependencies)_ + +##### Project Description + +- `test.go` : Contains the main function to launch the tests +- `object-storage/object-storage.go` : Contains the functions to interact with the object storage and the test function +- `types/datastructures.go` : Contains the datastructure useful for this lab (transactions, object storage, ...) + +##### Methods Description + +```go +func addTransactionToBlobStorage(transaction Transaction, database Database, os Blob) Database +``` + +Add transaction to given database and upload it to the blob storage. The database is then returned. + +```go +func fakeTransaction(transactionToFake Transaction, fakeTransaction Transaction, database Database) Database +``` + +Find the given transaction to replace and replace it with the fake transaction. The database is then returned. + +```go +func readDatabaseFromBlobStorage(blob Blob) Database +``` + +Read (Download) the database from the blob storage and return it. + +```go +func readGivenBlobFromContainer(blob Blob, data any) any +``` + +Read given blob that is of type data from the container and return it. + +```go +func writeDatabaseToBlobStorage(database Database, blob Blob) +``` + +Write given database to given blob/file. + +```go +func writeGivenDataToBlob(blob Blob, data any) +``` + +Write given data to given blob/file. + +```go +func InitAzureCredentials(storageAccountName string) AzureCredentials +``` + +Initialize the Azure credentials with the given storage account name. + +```go +func InitializeBlobFromObjectStorageCreds(blobName string, azureCreds AzureCredentials) Blob +``` + +Initialize a container and then a blob/file (for upload/download) with the given blobName and returns it. + +```go +func InitializeContainer(containerName string, azureCreds AzureCredentials) azblob.ContainerClient +``` + +Initialize a container with the given containerName and returns it (to create/delete/manage blob in it). + +```go +func InitializeBlob(blobName string, azureCreds AzureCredentials, containerName string, containerClient azblob.ContainerClient) azblob.BlockBlobClient +``` + +Initialize a blob/file (for upload/download) with the given blobName and returns an object to interact with it (write/read data). + +```go +func ListBlobs(blob Blob) +``` + +List blobs in container + +```go +func PrintingDatabaseToConsole(database Database) +``` + +Print the database to the console. + +```go +func TestObjectStorage() +``` + +Provide tests for the object storage. + diff --git a/lab1/app/client.go b/lab1/app/client.go new file mode 100644 index 0000000000000000000000000000000000000000..2042263d6a9610a1320c230b269e772429b89441 --- /dev/null +++ b/lab1/app/client.go @@ -0,0 +1,48 @@ +package main + +import ( + "fmt" + "gopkg.in/yaml.v3" + command_line "node/command-line" + . "node/types" + "node/utilities" + "os" +) + +/* + * @file + * @brief Distributed Systems - Blockchain + * @author Xavier Perret + * @date 28/09/2022 - 05/10/2022 + */ + +func main() { + argsLen := len(os.Args) + isThereEnoughArgs := argsLen <= 1 + if isThereEnoughArgs { + fmt.Println("First argument should be the path of the config file '--path=<config.yaml>'") + os.Exit(1) + } + + // init configuration + args := os.Args[1:] + fmt.Println("Program started with arguments", args) + fmt.Println("config file path is ", args[0][7:]) + + buf, err := os.ReadFile(args[0][7:]) + if err != nil { + fmt.Println("Error while reading the config file", err) + fmt.Println("Exiting...") + os.Exit(1) + } + + var clientConfig Config + err = yaml.Unmarshal(buf, &clientConfig) + if err != nil { + os.Exit(1) + } + + utilities.PrintConfig(clientConfig) + + command_line.ClientUserInputLoop(clientConfig, false) +} diff --git a/app/client.yaml b/lab1/app/client.yaml similarity index 96% rename from app/client.yaml rename to lab1/app/client.yaml index 63e4ed0a761f3f0084ca6a3fdd95ba0d1e885bb2..418ad76326921714f83b1938306287608d8d2ab6 100644 --- a/app/client.yaml +++ b/lab1/app/client.yaml @@ -1,7 +1,7 @@ id: -1 address: "127.0.0.1" port: 4000 -neighbours: +neighbors: - id: 0 address: "127.0.0.1" port: 5001 diff --git a/lab1/app/command-line/userinput.go b/lab1/app/command-line/userinput.go new file mode 100644 index 0000000000000000000000000000000000000000..78d1f53fe3543fb5c335c9b96490a12e224e3b3a --- /dev/null +++ b/lab1/app/command-line/userinput.go @@ -0,0 +1,268 @@ +package command_line + +import ( + "fmt" + ObjectStorageAPI "node/object-storage" + Sender "node/sender" + . "node/types" + "node/utilities" + "os" + "strconv" +) + +// 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 + trans.Id = utilities.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 +} + +func ServerUserInputLoop(config Config, isAlsoServer bool, objectStorage Blob) { + var database Database + for true { + var operation string + fmt.Println() + fmt.Println() + fmt.Println("Please enter the operation you want to do") + fmt.Println("1. Fabricate a fake transaction") + fmt.Println("2. Print all transactions") + fmt.Println("3. 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 fabricate a fake transaction") + fmt.Println("by giving us the id or index of the transaction you wish to modify (=fake)") + fmt.Println("we will first look for a transaction with the given id then try the index") + database = ObjectStorageAPI.ReadDatabaseFromBlobStorage(objectStorage) + utilities.PrintingDatabaseToConsole(database) + fmt.Print("\nPlease enter the index or id of the transaction you want to overwrite by faking:") + + database = ObjectStorageAPI.ReadDatabaseFromBlobStorage(objectStorage) + + var transID string + _, err := fmt.Scanln(&transID) + if err != nil { + fmt.Println("error :", err.Error()) + os.Exit(1) + } + trans := Transaction{ + Id: transID, + Sender: "", + Receiver: "", + Amount: "", + } + trans = ObjectStorageAPI.GetTransactionInDatabaseById(objectStorage, trans) + if trans.Id == "" { + transIDInt, err := strconv.ParseInt(transID, 10, 64) + if err != nil { + fmt.Println("error :", err.Error()) + os.Exit(1) + } + trans = database[transIDInt] + } + + tmpFakeTrans := userCreatedTransaction(config) + database = ObjectStorageAPI.FakeTransaction(trans, tmpFakeTrans, database) + ObjectStorageAPI.WriteDatabaseToBlobStorage(database, objectStorage) + + database = ObjectStorageAPI.ReadDatabaseFromBlobStorage(objectStorage) + fmt.Println("Database after faking:") + utilities.PrintingDatabaseToConsole(database) + case "2": + fmt.Println("You chose to print all transactions") + database = ObjectStorageAPI.ReadDatabaseFromBlobStorage(objectStorage) + utilities.PrintingDatabaseToConsole(database) + break + case "3": + fmt.Println("You chose to exit") + return + default: + fmt.Println("You chose an invalid option") + break + } + } +} + +func ClientUserInputLoop(clientConfig Config, isAlsoServer bool) { + for true { + var operation string + fmt.Println() + fmt.Println() + fmt.Println("Please enter the operation you want to do") + fmt.Println("1. Create a transaction and ask node to spread it") + fmt.Println("2. Ask node to rate a transaction") + fmt.Println("3. Ask node to fake a transaction") + fmt.Println("4. Ask node to list all transactions") + fmt.Println("5. 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 { + newTrans := userCreatedTransaction(clientConfig) + utilities.PrintNeighbors(clientConfig.Neighbours) + 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) + } + Sender.SendTransactionToNeighbour(clientConfig, newTrans, clientConfig.Neighbours[neighbourIDInt].Address, strconv.Itoa(clientConfig.Neighbours[neighbourIDInt].Port)) + } + break + case "2": + fmt.Println("You chose to rate a transaction") + fmt.Println("We will ask you for an id of a transaction to rate") + fmt.Println("The given node will send a request to all its neighbours to rate the transaction") + fmt.Println("The node will then print the result of the rating") + fmt.Print("\nPlease enter the id 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) + } + + utilities.PrintNeighbors(clientConfig.Neighbours) + fmt.Println("Please enter the ID of the neighbour you want to send the rate request 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 := clientConfig.Neighbours[neighbourIDInt].Address + ":" + strconv.Itoa(clientConfig.Neighbours[neighbourIDInt].Port) + fmt.Println("Sending rate demand to ", address) + + Sender.SendVoteRequestToNode(clientConfig, transIDInt, address) + break + case "3": + fmt.Println("You chose to fake a transaction") + fmt.Println("We will ask you for the id of a transaction to fake") + fmt.Println("and the node you want to ask to fake it") + fmt.Println("The node will receive the request and change the transaction with a fake one") + fmt.Print("\nPlease enter the id of the transaction you want to fake:") + 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) + } + + utilities.PrintNeighbors(clientConfig.Neighbours) + fmt.Println("Please enter the ID of the neighbour you want to send the rate request 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 := clientConfig.Neighbours[neighbourIDInt].Address + ":" + strconv.Itoa(clientConfig.Neighbours[neighbourIDInt].Port) + fmt.Println("Sending rate demand to ", address) + + Sender.SendFakeRequestToNode(clientConfig, transIDInt, address) + break + case "4": + fmt.Println("You chose to ask for all transactions of a given node") + utilities.PrintNeighbors(clientConfig.Neighbours) + fmt.Println("We will ask you for the ID of the neighbour you want to ask for the transactions") + fmt.Println("The node will then print locally its transactions") + fmt.Println("Please enter the ID of the neighbour you want to send the print request 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 := clientConfig.Neighbours[neighbourIDInt].Address + ":" + strconv.Itoa(clientConfig.Neighbours[neighbourIDInt].Port) + fmt.Println("Sending rate demand to ", address) + + Sender.SendListTransactionsRequestToNode(clientConfig, address) + break + case "5": + fmt.Println("You chose to exit") + return + default: + fmt.Println("You chose an invalid option") + break + } + } +} diff --git a/lab1/app/go.mod b/lab1/app/go.mod new file mode 100644 index 0000000000000000000000000000000000000000..18452c92565f3350d5a08379e2e263799ec9aeb3 --- /dev/null +++ b/lab1/app/go.mod @@ -0,0 +1,18 @@ +module node + +go 1.19 + +require ( + github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.12.0 + github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.2.0 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/Azure/azure-sdk-for-go/sdk/azcore v0.20.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.1 // indirect + github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 // indirect + golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 // indirect + golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d // indirect + golang.org/x/text v0.3.7 // indirect +) diff --git a/lab1/app/go.sum b/lab1/app/go.sum new file mode 100644 index 0000000000000000000000000000000000000000..384fbd0070d1a2a873ce8c9e9e89fd83ec604ef4 --- /dev/null +++ b/lab1/app/go.sum @@ -0,0 +1,59 @@ +github.com/Azure/azure-sdk-for-go/sdk/azcore v0.20.0 h1:KQgdWmEOmaJKxaUUZwHAYh12t+b+ZJf8q3friycK1kA= +github.com/Azure/azure-sdk-for-go/sdk/azcore v0.20.0/go.mod h1:ZPW/Z0kLCTdDZaDbYTetxc9Cxl/2lNqxYHYNOF2bti0= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.12.0 h1:VBvHGLJbaY0+c66NZHdS9cgjHVYSH6DDa0XJMyrblsI= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.12.0/go.mod h1:GJzjM4SR9T0KyX5gKCVyz1ytD8FeWeUPCwtFCt1AyfE= +github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.1 h1:BUYIbDf/mMZ8945v3QkG3OuqGVyS4Iek0AOLwdRAYoc= +github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.1/go.mod h1:KLF4gFr6DcKFZwSuH8w8yEK6DpFl3LP5rhdvAb7Yz5I= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.2.0 h1:62Ew5xXg5UCGIXDOM7+y4IL5/6mQJq1nenhBCJAeGX8= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.2.0/go.mod h1:eHWhQKXc1Gv1DvWH//UzgWjWFEo0Pp4pH2vBzjBw8Fc= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= +github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= +github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= +github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 h1:49lOXmGaUpV9Fz3gd7TFZY106KVlPVa5jcYD1gaQf98= +github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E= +golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/lab1/app/manage-connection/manage-connection.go b/lab1/app/manage-connection/manage-connection.go new file mode 100644 index 0000000000000000000000000000000000000000..0022c4c7a06b8f2b7782af504658429a963a063c --- /dev/null +++ b/lab1/app/manage-connection/manage-connection.go @@ -0,0 +1,30 @@ +package manage_connection + +import ( + "fmt" + "net" +) + +func CreateConnection(destinationAddress string) net.Conn { + fmt.Println("Trying to connect to ", destinationAddress) + // connect to ip with tcp and add the ip of the sender to the connection + conn, err := net.Dial("tcp", destinationAddress) + + if err != nil { + fmt.Println("Error while connecting to the neighbour", err) + } + + return conn +} + +func CreateConnectionWithSpecifiedLocalAddress(senderAddress string, destinationAddress string) net.Conn { + dialer := net.Dialer{LocalAddr: &net.TCPAddr{IP: net.ParseIP(senderAddress)}} + + conn, err := dialer.Dial("tcp", destinationAddress) + + if err != nil { + fmt.Println("Error while connecting to the neighbour", err) + } + + return conn +} \ No newline at end of file diff --git a/app/neighbor-0.yaml b/lab1/app/neighbor-0.yaml similarity index 91% rename from app/neighbor-0.yaml rename to lab1/app/neighbor-0.yaml index df1b44e2b6e18cd8388817c9e74d3164eaf6969c..707ffb4adfb74a72b75bdcc8b44238b4ff18430d 100644 --- a/app/neighbor-0.yaml +++ b/lab1/app/neighbor-0.yaml @@ -1,7 +1,7 @@ id: 0 address: "127.0.0.1" port: 5001 -neighbours: +neighbors: - id: 2 address: "127.0.0.3" port: 5003 diff --git a/app/neighbor-1.yaml b/lab1/app/neighbor-1.yaml similarity index 87% rename from app/neighbor-1.yaml rename to lab1/app/neighbor-1.yaml index f7ed16f536c2fc6bb88393fc73fe70c77e4be4f5..f4864c36b207b7594848767e162450379ae80e63 100644 --- a/app/neighbor-1.yaml +++ b/lab1/app/neighbor-1.yaml @@ -1,7 +1,7 @@ id: 1 address: "127.0.0.2" port: 5002 -neighbours: +neighbors: - id: 2 address: "127.0.0.3" port: 5003 \ No newline at end of file diff --git a/app/neighbor-2.yaml b/lab1/app/neighbor-2.yaml similarity index 95% rename from app/neighbor-2.yaml rename to lab1/app/neighbor-2.yaml index d59da5bf12c186b0db29258737c81166cbbed239..f11ce34b0fe5f9f5c4a49b14f514ee4c1d4f79ac 100644 --- a/app/neighbor-2.yaml +++ b/lab1/app/neighbor-2.yaml @@ -1,7 +1,7 @@ id: 2 address: "127.0.0.3" port: 5003 -neighbours: +neighbors: - id: 0 address: "127.0.0.1" port: 5001 diff --git a/app/neighbor-3.yaml b/lab1/app/neighbor-3.yaml similarity index 87% rename from app/neighbor-3.yaml rename to lab1/app/neighbor-3.yaml index b16fb7539b247fafe82cbc588655d8badfb062d6..285e0a3ec5ebff43db5bdf98577a31762bea77ec 100644 --- a/app/neighbor-3.yaml +++ b/lab1/app/neighbor-3.yaml @@ -1,7 +1,7 @@ id: 3 address: "127.0.0.4" port: 5004 -neighbours: +neighbors: - id: 0 address: "127.0.0.1" port: 5001 \ No newline at end of file diff --git a/app/neighbor-4.yaml b/lab1/app/neighbor-4.yaml similarity index 87% rename from app/neighbor-4.yaml rename to lab1/app/neighbor-4.yaml index e153ac11425547d9a5c3bc47d30abf43e6c983db..84841b4fd8c46c8875f538df82a965dde0181f50 100644 --- a/app/neighbor-4.yaml +++ b/lab1/app/neighbor-4.yaml @@ -1,7 +1,7 @@ id: 4 address: "127.0.0.5" port: 5005 -neighbours: +neighbors: - id: 2 address: "127.0.0.3" port: 5003 \ No newline at end of file diff --git a/app/neighbor-5.yaml b/lab1/app/neighbor-5.yaml similarity index 87% rename from app/neighbor-5.yaml rename to lab1/app/neighbor-5.yaml index 8a36b222d55d2444f94940b039b02d949e554292..ca09768ce97dc8c5ff52a9af44c7908e97d333a7 100644 --- a/app/neighbor-5.yaml +++ b/lab1/app/neighbor-5.yaml @@ -1,7 +1,7 @@ id: 5 address: "127.0.0.6" port: 5006 -neighbours: +neighbors: - id: 2 address: "127.0.0.3" port: 5003 \ No newline at end of file diff --git a/lab1/app/object-storage/object-storage.go b/lab1/app/object-storage/object-storage.go new file mode 100644 index 0000000000000000000000000000000000000000..5236f1035604dcd7800a987544533158ac637bb0 --- /dev/null +++ b/lab1/app/object-storage/object-storage.go @@ -0,0 +1,305 @@ +package object_storage + +import ( + "bufio" + "bytes" + "context" + "encoding/json" + "fmt" + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" + "log" + "math/rand" + . "node/types" + . "node/utilities" + "os" + "strconv" + "time" +) + +// AddTransactionToBlobStorage Add transaction to blob storage if it isn't in the database already and returns the database +func AddTransactionToBlobStorage(transaction Transaction, database Database, os Blob) Database { + for _, transactionInDatabase := range database { + if transactionInDatabase.Id == transaction.Id { + return database + } + } + database = append(database, transaction) + WriteDatabaseToBlobStorage(database, os) + return database +} + +func FakeTransaction(transactionToFake Transaction, fakeTransaction Transaction, database Database) Database { + for i, trans := range database { + if trans.Id == transactionToFake.Id { + fakeTransaction.Id = transactionToFake.Id + database[i] = fakeTransaction + break + } + } + return database +} + +func ReadDatabaseFromBlobStorage(blob Blob) Database { + ctx := context.Background() + var database Database + + // Download the blob + get, err := blob.BlockBlobClient.Download(ctx, nil) + if err != nil { + log.Println(err) + return database + } + + downloadedData := &bytes.Buffer{} + reader := get.Body(azblob.RetryReaderOptions{}) + _, err = downloadedData.ReadFrom(reader) + if err != nil { + log.Println(err) + return database + } + err = reader.Close() + if err != nil { + log.Println(err) + return database + } + + err = json.Unmarshal(downloadedData.Bytes(), &database) + if err != nil { + log.Println(err) + return database + } + fmt.Println(downloadedData.String()) + + return database +} + +func readGivenBlobFromContainer(blob Blob, data any) any { + // Download the blob + ctx := context.Background() + get, err := blob.BlockBlobClient.Download(ctx, nil) + if err != nil { + log.Println(err) + } + + downloadedData := &bytes.Buffer{} + reader := get.Body(azblob.RetryReaderOptions{}) + _, err = downloadedData.ReadFrom(reader) + if err != nil { + log.Println(err) + } + err = reader.Close() + if err != nil { + log.Println(err) + } + + err = json.Unmarshal(downloadedData.Bytes(), &data) + if err != nil { + log.Println(err) + } + + fmt.Println(downloadedData.String()) + return data +} + +func WriteDatabaseToBlobStorage(database Database, blob Blob) { + ctx := context.Background() + data, err := json.Marshal(database) + + // Upload to data to blob storage + _, err = blob.BlockBlobClient.UploadBufferToBlockBlob(ctx, data, azblob.HighLevelUploadToBlockBlobOption{}) + + if err != nil { + log.Printf("Failure to upload to blob: %+v", err) + } +} + +func writeGivenDataToBlob(blob Blob, data any) { + ctx := context.Background() + dataBytes, err := json.Marshal(data) + + // Upload to data to blob storage + _, err = blob.BlockBlobClient.UploadBufferToBlockBlob(ctx, dataBytes, azblob.HighLevelUploadToBlockBlobOption{}) + + if err != nil { + log.Printf("Failure to upload to blob: %+v", err) + } +} + +func randomString() string { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + return strconv.Itoa(r.Int()) +} + +func InitAzureCredentials(storageAccountName string) AzureCredentials { + fmt.Printf("Azure Blob storage initialization\n") + + //replace <StorageAccountName> with your Azure storage account name + // hepiadistributedsystems + url := "https://" + storageAccountName + ".blob.core.windows.net/" + + // Create a default request pipeline using your storage account name and account key. + credential, err := azidentity.NewDefaultAzureCredential(nil) + if err != nil { + log.Println("Invalid credentials with error: " + err.Error()) + } + + serviceClient, err := azblob.NewServiceClient(url, credential, nil) + if err != nil { + log.Println("Invalid credentials with error: " + err.Error()) + } + + newAzureCredentials := AzureCredentials{ + Url: url, + Credential: credential, + ServiceClient: serviceClient, + } + return newAzureCredentials +} + +func InitializeContainer(containerName string, azureCreds AzureCredentials) azblob.ContainerClient { + ctx := context.Background() + containerClient := azureCreds.ServiceClient.NewContainerClient(containerName) + _, err := containerClient.Create(ctx, nil) + if err != nil { + log.Fatal(err) + } + return containerClient +} + +func InitializeBlob(blobName string, azureCreds AzureCredentials, containerName string, containerClient azblob.ContainerClient) azblob.BlockBlobClient { + blobClient, err := azblob.NewBlockBlobClient(azureCreds.Url+containerName+"/"+blobName, azureCreds.Credential, nil) + if err != nil { + log.Fatal(err) + } + + return blobClient +} + +func InitializeBlobFromObjectStorageCreds(blobName string, azureCreds AzureCredentials) Blob { + ctx := context.Background() + + // Create the container + containerName := fmt.Sprintf("bc-container-%s", randomString()) + fmt.Printf("Creating a container named %s\n", containerName) + containerClient := azureCreds.ServiceClient.NewContainerClient(containerName) + _, err := containerClient.Create(ctx, nil) + if err != nil { + log.Fatal(err) + } + + blobClient, err := azblob.NewBlockBlobClient(azureCreds.Url+containerName+"/"+blobName, azureCreds.Credential, nil) + if err != nil { + log.Fatal(err) + } + return Blob{ + BlobName: blobName, + ContainerName: containerName, + ContainerClient: containerClient, + BlockBlobClient: blobClient, + } +} + +func ListBlobs(blob Blob) { + // List the blobs in the container + fmt.Println("Listing the blobs in the container:") + ctx := context.Background() + + pager := blob.ContainerClient.ListBlobsFlat(nil) + + for pager.NextPage(ctx) { + resp := pager.PageResponse() + + for _, v := range resp.ContainerListBlobFlatSegmentResult.Segment.BlobItems { + fmt.Println(*v.Name) + } + } + + if err := pager.Err(); err != nil { + log.Printf("Failure to list blobs: %+v", err) + } +} + +func TestObjectStorage() { + ctx := context.Background() + // url := "https://hepiadistributedsystems.blob.core.windows.net/" //replace <StorageAccountName> with your Azure storage account name + azureCreds := InitAzureCredentials("hepiadistributedsystems") + blobName := "database" + "-" + randomString() + objectStorage := InitializeBlobFromObjectStorageCreds(blobName, azureCreds) + + var db Database + t1 := Transaction{ + Id: "1", + Sender: "0", + Receiver: "1", + Amount: "1000", + } + t2 := Transaction{ + Id: "2", + Sender: "0", + Receiver: "1", + Amount: "1000", + } + db = AddTransactionToBlobStorage(t1, db, objectStorage) + db = AddTransactionToBlobStorage(t2, db, objectStorage) + + fmt.Println("Listing files in container") + db1 := ReadDatabaseFromBlobStorage(objectStorage) + PrintingDatabaseToConsole(db1) + fmt.Println("Press enter to continue") + bufio.NewReader(os.Stdin).ReadBytes('\n') + + fmt.Println("Going to fake a transaction (replacing the first with a new one)") + t3 := Transaction{ + Id: "0", + Sender: "0", + Receiver: "1", + Amount: "1000000", + } + db = FakeTransaction(t1, t3, db) + WriteDatabaseToBlobStorage(db, objectStorage) + fmt.Println("Listing files in container") + db2 := ReadDatabaseFromBlobStorage(objectStorage) + PrintingDatabaseToConsole(db2) + fmt.Println("Press enter to continue") + bufio.NewReader(os.Stdin).ReadBytes('\n') + + fmt.Println("Press enter key to delete the blob fils, example container, and exit the application.") + bufio.NewReader(os.Stdin).ReadBytes('\n') + fmt.Println("Cleaning up.") + + // Delete the blob + fmt.Println("Deleting the blob " + blobName) + + _, err := objectStorage.BlockBlobClient.Delete(ctx, nil) + if err != nil { + log.Fatalf("Failure: %+v", err) + } + + // Delete the container + fmt.Println("Deleting the blob " + objectStorage.BlobName) + _, err = objectStorage.ContainerClient.Delete(ctx, nil) + + if err != nil { + log.Fatalf("Failure: %+v", err) + } +} + +func GetTransactionInDatabaseById(objectStorage Blob, trans Transaction) Transaction { + db := ReadDatabaseFromBlobStorage(objectStorage) + for _, t := range db { + if t.Id == trans.Id { + return trans + } + } + return Transaction{ + Id: "", + Sender: "", + Receiver: "", + Amount: "", + } +} + +func IsTransactionInDatabase(objectStorage Blob, trans Transaction) bool { + return GetTransactionInDatabaseById(objectStorage, trans).Id == trans.Id +} diff --git a/lab1/app/process-connection/process-connection.go b/lab1/app/process-connection/process-connection.go new file mode 100644 index 0000000000000000000000000000000000000000..d7563e092cd5087c31b32bf872be1d8b9f101874 --- /dev/null +++ b/lab1/app/process-connection/process-connection.go @@ -0,0 +1,274 @@ +package ProcessConnection + +import ( + "encoding/json" + "fmt" + "net" + ObjectStorageAPI "node/object-storage" + Sender "node/sender" + . "node/types" + "node/utilities" + "os" + "strconv" + "strings" + "sync" +) + +func listAllTransactionsToClient(conn net.Conn, objectStorage Blob) { + database := ObjectStorageAPI.ReadDatabaseFromBlobStorage(objectStorage) + mess := Message{ + MessageType: "list", + MessageBody: database, + } + + 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) + } + + err = conn.Close() + if err != nil { + fmt.Println("Error closing connection", err) + } +} + +func processTransaction(serverListener net.Listener, serverConfig Config, objectStorage Blob, mess Message, amIRoot bool) { + // Convert mess to transaction + fmt.Println("Processing transaction") + trans := utilities.TranslateMessageToTransaction(mess) + utilities.PrintTransaction(trans) + + database := ObjectStorageAPI.ReadDatabaseFromBlobStorage(objectStorage) + fmt.Println("The database before adding the transaction is ") + utilities.PrintingDatabaseToConsole(database) + + reach := false + count := 0 + + limit := len(serverConfig.Neighbours) + if amIRoot { + reach = true + database = ObjectStorageAPI.AddTransactionToBlobStorage(trans, database, objectStorage) + fmt.Println("The database after adding the transaction is ") + utilities.PrintingDatabaseToConsole(database) + go Sender.SendTransactionToAllNeighbours(serverConfig, trans) + } + + for count < limit { + if count != 0 || reach { + fmt.Println("Count is ", count, " to ", limit) + + connection, err := serverListener.Accept() + if err != nil { + fmt.Println("Error accepting: ", err.Error()) + err = nil + continue + } + utilities.PrintConnection(connection, count) + + 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") + utilities.PrintTransaction(trans) + } + + count++ // received message + if !reach { + reach = true + database = ObjectStorageAPI.ReadDatabaseFromBlobStorage(objectStorage) + database = ObjectStorageAPI.AddTransactionToBlobStorage(trans, database, objectStorage) + go Sender.SendTransactionToAllNeighbours(serverConfig, trans) + } + if !amIRoot && limit == 1 { // for end nodes + break + } + } + utilities.PrintTransactionDoneMessage() +} +func processRate(conn net.Conn, serverListener net.Listener, serverConfig Config, objectStorage Blob, mess Message, amIRoot bool) { + trans := utilities.TranslateMessageToTransaction(mess) + utilities.PrintTransaction(trans) + + address := strings.Split(conn.RemoteAddr().String(), ":")[0] + vote(serverListener, serverConfig, trans, address, objectStorage, amIRoot) +} + +// 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, serverConfig Config, trans Transaction, parentAddress string, objectStorage Blob, amIRoot bool) AckTransaction { + ack := AckTransaction{ + Id: "", + TotalNodes: 0, + AmountOfCorrectNode: 0, + } + + var transactionToRate Transaction + + database := ObjectStorageAPI.ReadDatabaseFromBlobStorage(objectStorage) + for _, transactionInDatabase := range database { + if transactionInDatabase.Id == trans.Id { + ack.Id = trans.Id + transactionToRate = transactionInDatabase + } + } + + if ack.Id == "" { + fmt.Println("Error retrieving the transaction") + return ack + } + + ack = utilities.CompareTransactions(transactionToRate, trans, ack) + + var newAck AckTransaction + + reach := false + count := 0 + + limit := len(serverConfig.Neighbours) + if amIRoot { + reach = true + go Sender.SendVoteToAllNeighbours(serverConfig, 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 + } + + utilities.PrintConnection(connection, count) + + err = json.NewDecoder(connection).Decode(&mess) + if err != nil { + fmt.Println("Error while decoding the transactionInDatabase", err) + err = nil + continue + } + + if mess.MessageType == "AckResponse" { + newAck = utilities.TranslateMessageToAckTransaction(mess) + 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 Sender.SendVoteToAllNeighbours(serverConfig, trans, parentAddress) + } + } + fmt.Println("***********************************") + fmt.Println("Transaction has been rated") + fmt.Println("Node correct : ", ack.AmountOfCorrectNode, "out of ", ack.TotalNodes) + fmt.Println("***********************************") + + if !amIRoot { + address := parentAddress + for _, neighbour := range serverConfig.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 Sender.SendAckToNeighbour(serverConfig, ack, address) + } + return ack +} + +func processFakeRequest(conn net.Conn, serverListener net.Listener, serverConfig Config, objectStorage Blob, mess Message, amIRoot bool) { + trans := utilities.TranslateMessageToTransaction(mess) + + utilities.PrintTransaction(trans) + fakeTrans := Transaction{ + Id: trans.Id, + Sender: utilities.RandomString(), + Receiver: utilities.RandomString(), + Amount: utilities.RandomString(), + } + database := ObjectStorageAPI.ReadDatabaseFromBlobStorage(objectStorage) + database = ObjectStorageAPI.FakeTransaction(trans, fakeTrans, database) + ObjectStorageAPI.WriteDatabaseToBlobStorage(database, objectStorage) +} + +func processVoteRequest(conn net.Conn, serverListener net.Listener, serverConfig Config, objectStorage Blob, mess Message, amIRoot bool) { + trans := utilities.TranslateMessageToTransaction(mess) + var transToRate Transaction + + utilities.PrintTransaction(trans) + + database := ObjectStorageAPI.ReadDatabaseFromBlobStorage(objectStorage) + for _, transactionInDatabase := range database { + if trans.Id == transactionInDatabase.Id { + transToRate = transactionInDatabase + } + } + + ack := vote(serverListener, serverConfig, transToRate, "", objectStorage, amIRoot) + utilities.PrintAckTransaction(ack) +} + +func ProcessClient(conn net.Conn, server net.Listener, objectStorage Blob, serverConfig Config, amIRoot bool, mutex *sync.Mutex) { + // mutex.Lock() + // defer mutex.Unlock() + utilities.PrintConnection(conn, 0) + + var mess Message + jsonDecoder := json.NewDecoder(conn) + err := jsonDecoder.Decode(&mess) + if err != nil || mess.MessageType == "error" { + fmt.Println("Error while decoding the message", err) + return + } + + fmt.Println("The message is ", mess) + if mess.MessageType == "transaction" { + fmt.Println("Received a transaction.. processing") + trans := utilities.TranslateMessageToTransaction(mess) + isTransactionInDatabase := ObjectStorageAPI.IsTransactionInDatabase(objectStorage, trans) + if isTransactionInDatabase { + conn.Close() + return // transaction already in database + } + processTransaction(server, serverConfig, objectStorage, mess, amIRoot) + } else if mess.MessageType == "rate" { + fmt.Println("Received a rate") + processRate(conn, server, serverConfig, objectStorage, mess, amIRoot) + } else if mess.MessageType == "list" { + fmt.Println("Received an order to list all transactions") + listAllTransactionsToClient(conn, objectStorage) + } else if mess.MessageType == "fakeRequest" { + fmt.Println("Received a request to fake transaction") + processFakeRequest(conn, server, serverConfig, objectStorage, mess, amIRoot) + } else if mess.MessageType == "voteRequest" { + fmt.Println("Received a request to vote on a given transaction") + processVoteRequest(conn, server, serverConfig, objectStorage, mess, true) + } else if mess.MessageType == "listTransactionsRequest" { + fmt.Println("Received a request to list all transactions") + database := ObjectStorageAPI.ReadDatabaseFromBlobStorage(objectStorage) + utilities.PrintingDatabaseToConsole(database) + } else { + fmt.Println("Unknown message type") + } +} diff --git a/lab1/app/sender/sender.go b/lab1/app/sender/sender.go new file mode 100644 index 0000000000000000000000000000000000000000..b87212507c18274a2cbb5c6dc55e569e855e783d --- /dev/null +++ b/lab1/app/sender/sender.go @@ -0,0 +1,184 @@ +package sender + +import ( + "encoding/json" + "fmt" + manage_connection "node/manage-connection" + . "node/types" + "os" + "strconv" +) + +func SendVoteToNeighbour(config Config, trans Transaction, address string) { + fmt.Println() + fmt.Println("Trying to connect to ", address) + + conn := manage_connection.CreateConnectionWithSpecifiedLocalAddress(config.Address, address) + + 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) + } + + err = conn.Close() + if err != nil { + fmt.Println("Error while closing the process-manage-connection", err) + } + + 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 := manage_connection.CreateConnectionWithSpecifiedLocalAddress(config.Address, address) + + 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 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) + } + } +} + +// SendTransactionToAllNeighbours is a function to send a transaction to all neighbours +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) + } +} + +// SendTransactionToNeighbour is a function to send a transaction to a neighbour, used both by server/client +func SendTransactionToNeighbour(config Config, trans Transaction, destinationIp string, destinationPort string) { + fmt.Println() + ipAddr := destinationIp + ":" + destinationPort + fmt.Println("Trying to connect to ", destinationIp, ":", destinationPort) + + conn := manage_connection.CreateConnectionWithSpecifiedLocalAddress(config.Address, ipAddr) + + 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") +} + +// SendVoteRequestToNode is a function to send a vote request to a node, used strictly by client +func SendVoteRequestToNode(clientConfig Config, transIDInt int64, address string) { + fmt.Println() + fmt.Println("Trying to connect to ", address) + + conn := manage_connection.CreateConnection(address) + + trans := Transaction{ + Id: strconv.FormatInt(transIDInt, 10), + Sender: "", + Receiver: "", + Amount: "", + } + mess := Message{ + MessageType: "voteRequest", + MessageBody: trans, + } + fmt.Println("Sending vote request to node", 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() +} + +// SendFakeRequestToNode is a function to send a fake request to a node, used strictly by client +func SendFakeRequestToNode(clientConfig Config, transIDInt int64, address string) { + fmt.Println() + fmt.Println("Trying to connect to ", address) + + conn := manage_connection.CreateConnection(address) + + trans := Transaction{ + Id: strconv.FormatInt(transIDInt, 10), + Sender: "", + Receiver: "", + Amount: "", + } + mess := Message{ + MessageType: "fakeRequest", + MessageBody: trans, + } + + fmt.Println("Sending vote request to node", 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() +} + +// SendListTransactionsRequestToNode is a function to ask a node to locally list its transaction in its cli, used strictly by client +func SendListTransactionsRequestToNode(clientConfig Config, address string) { + fmt.Println() + fmt.Println("Trying to connect to ", address) + + conn := manage_connection.CreateConnection(address) + + mess := Message{ + MessageType: "listTransactionsRequest", + MessageBody: "", + } + fmt.Println("Sending vote request to node", 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() +} diff --git a/lab1/app/server.go b/lab1/app/server.go new file mode 100644 index 0000000000000000000000000000000000000000..3d99060928b39de7242b1b4d42d1bfb50873b4bb --- /dev/null +++ b/lab1/app/server.go @@ -0,0 +1,112 @@ +package main + +import ( + "context" + "fmt" + "gopkg.in/yaml.v3" + "log" + "net" + command_line "node/command-line" + ObjectStorage "node/object-storage" + ProcessConnection "node/process-connection" + . "node/types" + "node/utilities" + "os" + "strconv" + "sync" +) + +/** + * @file Server.go - Server for the blockchain + * @brief Distributed Systems - Blockchain, each instance of this program is a node in the blockchain + * @author Xavier Perret + * @date 28/10/2022 + * @version 1.3 + */ + +func listenForConnections(serverConfig Config, objectStorage Blob, addressToListenOn string, amIRoot bool) { + port := strconv.FormatInt(int64(serverConfig.Port), 10) + completeAddress := addressToListenOn + ":" + port + + server, err := net.Listen("tcp", completeAddress) + 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 ", completeAddress) + + var mu sync.Mutex + for { + // Listening for connections + conn, err := server.Accept() + if err != nil { + fmt.Println("Error accepting: ", err.Error()) + } else { + ProcessConnection.ProcessClient(conn, server, objectStorage, serverConfig, amIRoot, &mu) + } + } +} + +func main() { + ctx := context.Background() + argsLen := len(os.Args) + isThereEnoughArgs := argsLen <= 1 + if isThereEnoughArgs { + fmt.Println("First argument should be the path of the config file '--path=<config.yaml>'") + os.Exit(1) + } + + // init configuration + args := os.Args[1:] + fmt.Println("Program started with arguments", args) + fmt.Println("config file path is ", args[0][7:]) + + buf, err := os.ReadFile(args[0][7:]) + if err != nil { + fmt.Println("Error while reading the config file", err) + fmt.Println("Exiting...") + os.Exit(1) + } + + var serverConfig Config + err = yaml.Unmarshal(buf, &serverConfig) + if err != nil { + os.Exit(1) + } + + utilities.PrintConfig(serverConfig) + + amIRoot := argsLen == 3 + if amIRoot { + if os.Args[3] == "--root" { + amIRoot = true + } + } + if !amIRoot { + fmt.Println("Third argument is not --root meaning this node is not meant to be directly stimulated by the client") + } + + azureCreds := ObjectStorage.InitAzureCredentials("hepiadistributedsystems") + blobName := "blob-number-" + strconv.Itoa(serverConfig.ID) + objectStorage := ObjectStorage.InitializeBlobFromObjectStorageCreds(blobName, azureCreds) + + addressToListenOn := "0.0.0.0" + go listenForConnections(serverConfig, objectStorage, addressToListenOn, amIRoot) + command_line.ServerUserInputLoop(serverConfig, true, objectStorage) + + _, err = objectStorage.BlockBlobClient.Delete(ctx, nil) + if err != nil { + fmt.Println("Error while deleting the blob", err) + fmt.Println("This previous error is normal if the blob was already deleted or wasn't created") + } + + // Delete the container + fmt.Println("Deleting the container " + objectStorage.BlobName) + _, err = objectStorage.ContainerClient.Delete(ctx, nil) + + if err != nil { + log.Fatalf("Failure: %+v", err) + } +} diff --git a/lab1/app/test.go b/lab1/app/test.go new file mode 100644 index 0000000000000000000000000000000000000000..a455cb955065cca3efbbc5ff9d7e61d75b21ed28 --- /dev/null +++ b/lab1/app/test.go @@ -0,0 +1,11 @@ +package main + +import ( + "fmt" + . "node/object-storage" +) + +func main() { + fmt.Println("Testing file for the application") + TestObjectStorage() +} diff --git a/lab1/app/types/datastructures.go b/lab1/app/types/datastructures.go new file mode 100644 index 0000000000000000000000000000000000000000..e87825c7fd9d968014cc07b57f4ec57af61ec6d0 --- /dev/null +++ b/lab1/app/types/datastructures.go @@ -0,0 +1,56 @@ +package types + +import ( + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" +) + +// Message - MessageBody can be of any type but will be use here for Transaction, AckTransaction +type Message struct { + MessageType string `json:"messageType"` + MessageBody interface{} `json:"messageBody"` +} + +// Transaction - represents a transaction in the blockchain +type Transaction struct { + Id string `json:"id"` + Sender string `json:"sender"` + Receiver string `json:"receiver"` + Amount string `json:"amount"` +} + +// AckTransaction - represents an acknowledgement of a transaction @see vote function +type AckTransaction struct { + Id string `json:"id"` + AmountOfCorrectNode int `json:"amountOfCorrectNode"` + TotalNodes int `json:"totalNodes"` +} + +type Database []Transaction + +type AzureCredentials struct { + Url string + Credential *azidentity.DefaultAzureCredential + ServiceClient azblob.ServiceClient +} + +type Blob struct { + BlobName string + ContainerName string + ContainerClient azblob.ContainerClient + BlockBlobClient azblob.BlockBlobClient +} + +// 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 []Neighbors `yaml:"neighbors"` +} + +type Neighbors struct { + ID int `yaml:"id"` + Address string `yaml:"address"` + Port int `yaml:"port"` +} diff --git a/lab1/app/utilities/utilities.go b/lab1/app/utilities/utilities.go new file mode 100644 index 0000000000000000000000000000000000000000..87d597fb31751124c96c0fac66f8e8e27fa199b0 --- /dev/null +++ b/lab1/app/utilities/utilities.go @@ -0,0 +1,150 @@ +package utilities + +import ( + "fmt" + "math/rand" + "net" + . "node/types" + "strconv" + "time" +) + +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(db Database) { + for i, transaction := range db { + fmt.Println("***********INDEX N=,", i, "****************") + PrintTransaction(transaction) + fmt.Println("***********************************") + } +} + +func PrintNeighbors(neighbors []Neighbors) { + fmt.Println("Printing neighbors") + for i, neighbor := range neighbors { + fmt.Println("***********INDEX N=,", i, "****************") + fmt.Println("The id is ", neighbor.ID) + fmt.Println("The address is ", neighbor.Address) + fmt.Println("The port is ", neighbor.Port) + fmt.Println("***********************************") + } +} + +func PrintConfig(config Config) { + fmt.Println("Printing config") + fmt.Println("The id is ", config.ID) + fmt.Println("The address is ", config.Address) + fmt.Println("The port is ", config.Port) + PrintNeighbors(config.Neighbours) +} + +func PrintingDatabaseToConsole(database Database) { + for i, trans := range database { + fmt.Println("--------------------") + fmt.Printf("\nListing transactions number %d in database\n", i) + fmt.Printf("Transaction id: %s\n", trans.Id) + fmt.Printf("Transaction sender: %s\n", trans.Sender) + fmt.Printf("Transaction receiver: %s\n", trans.Receiver) + fmt.Printf("Transaction amount: %s\n", trans.Amount) + fmt.Println("--------------------") + } +} + +func CompareTransactions(trans1 Transaction, trans2 Transaction, ack AckTransaction) AckTransaction { + newAck := AckTransaction{ + Id: ack.Id, + AmountOfCorrectNode: ack.AmountOfCorrectNode, + TotalNodes: ack.TotalNodes, + } + + if trans1.Id != trans2.Id { + return newAck + } + + areBothTransactionEquals := true + if trans1.Receiver != trans2.Receiver { + areBothTransactionEquals = false + } + if trans1.Sender != trans2.Sender { + areBothTransactionEquals = false + } + if trans1.Amount != trans2.Amount { + areBothTransactionEquals = false + } + + if areBothTransactionEquals { + newAck.AmountOfCorrectNode += 1 + } + newAck.TotalNodes += 1 + + return newAck +} + +func PrintConnection(connection net.Conn, connectionNumber int) { + fmt.Println("*****************************************") + fmt.Println("Processing client request number ", connectionNumber) + fmt.Println("The process-manage-connection is ", connection) + fmt.Println("The remote address is ", connection.RemoteAddr()) + fmt.Println("The local address is ", connection.LocalAddr()) + fmt.Println("*****************************************") +} + +// TranslateMessageToAckTransaction translates a message to an ack transaction +// the geniuses who made unmarshable take int as float64 +func TranslateMessageToAckTransaction(mess Message) AckTransaction { + var transactionId string + var amountOfCorrectNode int + var totalNode int + + var body map[string]interface{} = mess.MessageBody.(map[string]interface{}) + transactionId = body["id"].(string) + amountOfCorrectNode = int(body["amountOfCorrectNode"].(float64)) + totalNode = int(body["totalNodes"].(float64)) + + return AckTransaction{ + Id: transactionId, + AmountOfCorrectNode: amountOfCorrectNode, + TotalNodes: totalNode, + } +} + +func TranslateMessageToTransaction(mess Message) Transaction { + var newTrans Transaction + + var body map[string]interface{} = mess.MessageBody.(map[string]interface{}) + newTrans.Id = body["id"].(string) + newTrans.Sender = body["sender"].(string) + newTrans.Receiver = body["receiver"].(string) + newTrans.Amount = body["amount"].(string) + + return newTrans + +} + +func RandomString() string { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + return strconv.Itoa(r.Int()) +} + +func PrintTransactionDoneMessage() { + fmt.Println("***********************************") + fmt.Println("All transactions have been received") + fmt.Println("***********************************") +} + +func PrintAckTransaction(ack AckTransaction) { + fmt.Println("***********************************") + fmt.Println("Printing ack transaction") + fmt.Println("The id is ", ack.Id) + fmt.Println("The amount of correct node is ", ack.AmountOfCorrectNode) + fmt.Println("The total nodes is ", ack.TotalNodes) + fmt.Println("***********************************") +} diff --git a/lab2/api-pod.yaml b/lab2/api-pod.yaml new file mode 100644 index 0000000000000000000000000000000000000000..8680bb6504725dda42d6422ecece17f81a623ea8 --- /dev/null +++ b/lab2/api-pod.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: Pod +metadata: + name: api + labels: + component: api + app: todo +spec: + containers: + - name: api + image: icclabcna/ccp2-k8s-todo-api + ports: + - containerPort: 8081 + resources: + limits: + cpu: 100m + env: + - name: REDIS_ENDPOINT + value: redis-svc + - name: REDIS_PWD + value: ccp2 \ No newline at end of file diff --git a/lab2/redis-deploy.yaml b/lab2/redis-deploy.yaml new file mode 100644 index 0000000000000000000000000000000000000000..d231076203dcd201278070ec93ad35c219782fe1 --- /dev/null +++ b/lab2/redis-deploy.yaml @@ -0,0 +1,30 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis-deploy + labels: + app: todo +spec: + replicas: 1 + selector: + matchLabels: + app: todo + template: + metadata: + name: redis + labels: + component: redis + app: todo + spec: + containers: + - name: redis + image: redis + ports: + - containerPort: 6379 + resources: + limits: + cpu: 100m + args: + - redis-server + - --requirepass ccp2 + - --appendonly yes \ No newline at end of file diff --git a/lab2/redis-pod.yaml b/lab2/redis-pod.yaml new file mode 100644 index 0000000000000000000000000000000000000000..96b583b5233303d306b524893b07c8bc44ca9f5c --- /dev/null +++ b/lab2/redis-pod.yaml @@ -0,0 +1,20 @@ +apiVersion: v1 +kind: Pod +metadata: + name: redis-pod + labels: + component: redis + app: todo +spec: + containers: + - name: redis-container + image: redis + ports: + - containerPort: 6379 + resources: + limits: + cpu: 100m + args: + - redis-server + - --requirepass ccp2 + - --appendonly yes