From 80b928d8cc7a7bbc3cc99106c200bd84e7eb12e4 Mon Sep 17 00:00:00 2001 From: theghostmac Date: Sun, 13 Aug 2023 19:43:53 +0100 Subject: [PATCH] feature: implemented the bootloading node | @jim-nnamdi will be proud --- .gitignore | 4 +- README.md | 8 +-- cmd/main.go | 60 +++++++++++----- explanation.md | 89 ++++++++++++++++++++++-- internal/network/local_transport.go | 6 +- internal/server/runner.go | 22 ------ internal/server/server.go | 67 ++++++++++-------- webserver.md | 102 ++++++++++++++++++++++++++++ 8 files changed, 275 insertions(+), 83 deletions(-) delete mode 100644 internal/server/runner.go create mode 100644 webserver.md diff --git a/.gitignore b/.gitignore index 723ef36..40e0332 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -.idea \ No newline at end of file +.idea + +explanation.md \ No newline at end of file diff --git a/README.md b/README.md index 9aaa216..40d38ae 100644 --- a/README.md +++ b/README.md @@ -3,12 +3,6 @@ What is a blockchain? [Here](https://en.wikipedia.org/wiki/Blockchain). ![Pluto is active](pluto.png) -Quoting Wikipedia: -Logically, a blockchain can be seen as consisting of several layers: -- infrastructure (hardware) -- networking (node discovery, information propagation and verification) -- consensus (proof of work, proof of stake) -- data (blocks, transactions) -- application (smart contracts/decentralized applications, if applicable) +Lowest layer of the blockchain is the Network Layer. ![Tests Pass](testspass.png) \ No newline at end of file diff --git a/cmd/main.go b/cmd/main.go index 7d9622e..a8218d7 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -1,31 +1,59 @@ package main import ( - "flag" + "fmt" + "github.com/theghostmac/pluto/internal/network" "github.com/theghostmac/pluto/internal/server" - "log" - "os" - "os/signal" - "syscall" + "time" ) func main() { - listenAddr := flag.String("listenAddress", ":8080", "listening on the default port") - flag.Parse() + // two testing nodes. + node1 := network.NewLocalTransport("LOCAL NODE 1") + node2 := network.NewLocalTransport("LOCAL NODE 2") - startServer := server.RunServer{ - ListenAddr: *listenAddr, + // Creating the bootloading node with a specific address + bootloadingNode := network.NewLocalTransport("BOOTLOADER") + // Remember to: + // Initialize the blockchain next, and other networks + // blockchain := InitializeBlockchain() + // consensus := InitializeConsensusMechanism() + // I can set any node to be the second node: + err := bootloadingNode.Connect(node1) + if err != nil { + return } + // TODO: delete the bootloadingNode impl. + + err = node1.Connect(node2) + if err != nil { + return + } + err = node2.Connect(node1) + if err != nil { + return + } + go func() { - err := startServer.Run() - if err != nil { - log.Fatalf("Server failed to start: %v", err) + for { + err := node2.SendAMessage(node1.Address(), []byte("Hey, Node1, I need some cash!")) + if err != nil { + fmt.Printf("Failed to send a message to node 2 due to: %v", err) + } + time.Sleep(1 * time.Second) } }() - stopChan := make(chan os.Signal, 1) - signal.Notify(stopChan, syscall.SIGINT, syscall.SIGTERM) - <-stopChan + // ------> RPC Server <------ + ops := server.ServerOperations{ + Transports: []network.Transporter{node1}, + } + + serve := server.NewServer(ops) + + // Run server in a separate goroutine, add go in front of this: + serve.StartServer() - startServer.Shutdown() + // Send a signal to the quitChannel to stop the server. + serve.QuitChannel <- struct{}{} } diff --git a/explanation.md b/explanation.md index 498669f..0d22d5d 100644 --- a/explanation.md +++ b/explanation.md @@ -1,7 +1,88 @@ -# Explanation +# Blockchain Engineering from first principles +This article is for people who already know what the blockchain is, and probably already build software +around it. It is inspired by my journey to relearn blockchain technology beyond the surface. While I do +this, I try to build a blockchain from scratch using the Go programming language. -The runner and server (learned from a friend) is a way to startup and shut down a server gracefully. +# Introducing blockchain +Blockchain is a peer-to-peer, distributed ledger that is cryptographically secure, append-only, immutable, +and update-able only via consensus among peers. +## Definition of terms. +1. Peer-to-peer means there is no central controller of the network, and all participants (called nodes) +talk to each other directly. +2. Distributed ledger means that the ledger is spread across the network among all peers in the network, +each peer holding a copy of the complete ledger. +3. Cryptographically secure means that the ledger is secured from tampering and misuse using +cryptographic algorithms. +4. Append-only means that data can only be added, and not modified or tampered. Also, the data +is added to the blockchain in time-sequential order. +5. Update-able via consensus means that a consensus mechanism is used to enable decentralization +on the blockchain. -Nodes communicate via remote procedure calls (RPCs), so the local blockchain network will use RPC. Benefit is that -it abstracts network details, supports interoperability (bridging communication between nodes), serialize & deserialize, etc. +From the following fundamental definition of blockchain, it is obvious that a blockchain +engineer must be versed in: +1. distributed systems - I recommend Distributed Systems for Practitioners book +2. writing immutable code, +3. cryptography - I recommend Cryptographic Algorithms +4. mathematics, and +5. computer networking. + +In addition to all of these recommendations, you can add Wikipedia, and any other resources you find. +It's helpful to listen to experts talk, so videos might be good too. + +# Blockchain Architecture +Blockchain is a network with different layers. It is similar to HTTP, FTP, etc., which +runs on the TCP/IP model. Just as the TCP/IP networking model has 4 layers, the blockchain networking model +has 6 layers: +- Application: + - Smart contracts + - Decentralized applications + - Decentralized autonomous organizations + - Autonomous agents +- Execution: + - Virtual machines + - Blocks + - Transactions +- Consensus: + - State machine replication + - Proof-based consensus + - Traditional Byzantine fault-tolerant protocols +- Cryptography: + - Public key cryptography + - Digital signatures + - Hash functions +- P2P: + - Gossip protocols / Epidemic protocols + - Routing protocols + - Flooding protocols +- Network: + - The internet + - TCP/IP + +From the list, it is obvious that the Network layer is the lowest layer. So in building a +blockchain from scratch, it is best to start from the Network layer. + +## The Network layer +Blockchain nodes communicate using remote procedure calls. + +Network connections are also built on top of hardware that will also fail at some +point and we should design our systems accordingly. + +### Basics and Mechanics +The mechanics of the blockchain can be broken down into the following: + +1. **Node Communication**: In a blockchain, nodes communicate via a peer-to-peer network protocol. While it's not exactly like a normal web server, you can think of it as a distributed network where nodes share information directly. + +2. **Initial Information**: New nodes can bootstrap by connecting to existing nodes, which provide them with information about the blockchain's current state. + +3. **Coin Transactions**: People buy coins by interacting with the blockchain's protocol. In some cases, they might exchange value through these interactions. + +4. **Storing Data**: The blockchain's data structure stores transaction data in blocks, which are linked together in a chain. Each node maintains a copy of this data. + +5. **RPC Endpoints**: RPC endpoints allow external applications to communicate with nodes for retrieving data or submitting transactions. + +6. **Central Servers**: While blockchains are decentralized, the concept of a central server doesn't apply in the same way. However, some blockchains might have centralized components like explorer websites that display blockchain data. + +7. **Data Propagation**: Nodes in a blockchain network communicate to propagate new transactions and blocks. This is done through a consensus protocol, ensuring that all nodes eventually agree on the state of the blockchain. + +8. **Writing Code**: Writing code is indeed an essential part of becoming a blockchain engineer, but understanding the underlying concepts is equally important. You're on the right track by digging into the fundamentals. diff --git a/internal/network/local_transport.go b/internal/network/local_transport.go index a7a6007..075f94c 100644 --- a/internal/network/local_transport.go +++ b/internal/network/local_transport.go @@ -5,7 +5,7 @@ import ( "sync" ) -func NewLocalTransport(address NetworkAddress) *LocalTransport { +func NewLocalTransport(address NetworkAddress) Transporter { return &LocalTransport{ address: address, consumerChannel: make(chan RPC, 1024), @@ -18,7 +18,7 @@ func (lt *LocalTransport) Consume() <-chan RPC { return lt.consumerChannel } -func (lt *LocalTransport) Connect(tr *LocalTransport) error { +func (lt *LocalTransport) Connect(tr Transporter) error { lt.lock.Lock() defer lt.lock.Unlock() @@ -26,7 +26,7 @@ func (lt *LocalTransport) Connect(tr *LocalTransport) error { return fmt.Errorf("peer with address %v already exists", tr.Address()) } - lt.Peers[tr.Address()] = tr + lt.Peers[tr.Address()] = tr.(*LocalTransport) return nil } diff --git a/internal/server/runner.go b/internal/server/runner.go deleted file mode 100644 index d708b3d..0000000 --- a/internal/server/runner.go +++ /dev/null @@ -1,22 +0,0 @@ -package server - -import "os" - -type RunServer struct { - ListenAddr string - Server *GracefulShutdown -} - -func (runner *RunServer) Run() error { - server := &GracefulShutdown{ - ListenAddr: runner.ListenAddr, - } - runner.Server = server - server.Start() - return nil -} - -func (runner *RunServer) Shutdown() { - runner.Server.Shutdown() - os.Exit(0) -} diff --git a/internal/server/server.go b/internal/server/server.go index 1c198d0..4f4d06b 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -1,46 +1,53 @@ package server import ( - "errors" - "github.com/gorilla/mux" - "github.com/sirupsen/logrus" - "net/http" - "os" + "fmt" + "github.com/theghostmac/pluto/internal/network" + "time" ) -type GracefulShutdown struct { - ListenAddr string - BaseHandler http.Handler - httpServer *http.Server +type ServerOperations struct { + Transports []network.Transporter } -func (server *GracefulShutdown) getRouter() *mux.Router { - router := mux.NewRouter() - router.SkipClean(true) - router.Handle("/", server.BaseHandler) - return router +type Server struct { + ServerOperations + RPCChannel chan network.RPC + QuitChannel chan struct{} } -func (server *GracefulShutdown) Start() { - router := server.getRouter() - server.httpServer = &http.Server{ - Addr: server.ListenAddr, - Handler: router, +func NewServer(operations ServerOperations) *Server { + return &Server{ + ServerOperations: operations, + RPCChannel: make(chan network.RPC), + QuitChannel: make(chan struct{}, 1), } - logrus.Infof("Server listening on %s ", server.ListenAddr) - logrus.Info("Pluto is active 🚀") +} + +func (s *Server) StartServer() { + s.InitializeTransports() + ticker := time.NewTicker(5 * time.Second) - if err := server.httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { - logrus.Fatalf("Server error: %v", err) +free: + for { + select { + case rpc := <-s.RPCChannel: + fmt.Printf("%+v", rpc) + case <-s.QuitChannel: + break free + case <-ticker.C: + fmt.Println("Interacting with blocks every 500 milli seconds.") + } } + fmt.Println("Server shutdown...") } -func (server *GracefulShutdown) Shutdown() { - logrus.Info("Shutting down server...") - - if err := server.httpServer.Shutdown(nil); err != nil { - logrus.Errorf("Error during server shutdown: %v", err) +func (s *Server) InitializeTransports() { + for _, trans := range s.Transports { + go func(trans network.Transporter) { + for rpc := range trans.Consume() { + s.RPCChannel <- rpc + } + }(trans) } - logrus.Info("Server shutdown complete.") - os.Exit(0) } diff --git a/webserver.md b/webserver.md new file mode 100644 index 0000000..a7841fc --- /dev/null +++ b/webserver.md @@ -0,0 +1,102 @@ +runner.go: +```go +package server + +import "os" + +type RunServer struct { + ListenAddr string + Server *GracefulShutdown +} + +func (runner *RunServer) Run() error { + server := &GracefulShutdown{ + ListenAddr: runner.ListenAddr, + } + runner.Server = server + server.Start() + return nil +} + +func (runner *RunServer) Shutdown() { + runner.Server.Shutdown() + os.Exit(0) +} + +``` + +server.go: +```go +package server + +import ( + "errors" + "github.com/gorilla/mux" + "github.com/sirupsen/logrus" + "net/http" + "os" +) + +type GracefulShutdown struct { + ListenAddr string + BaseHandler http.Handler + httpServer *http.Server +} + +func (server *GracefulShutdown) getRouter() *mux.Router { + router := mux.NewRouter() + router.SkipClean(true) + router.Handle("/", server.BaseHandler) + return router +} + +func (server *GracefulShutdown) Start() { + router := server.getRouter() + server.httpServer = &http.Server{ + Addr: server.ListenAddr, + Handler: router, + } + logrus.Infof("Server listening on %s ", server.ListenAddr) + logrus.Info("Pluto is active 🚀") + + if err := server.httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + logrus.Fatalf("Server error: %v", err) + } +} + +func (server *GracefulShutdown) Shutdown() { + logrus.Info("Shutting down server...") + + if err := server.httpServer.Shutdown(nil); err != nil { + logrus.Errorf("Error during server shutdown: %v", err) + } + logrus.Info("Server shutdown complete.") + os.Exit(0) +} + +``` + +main.go: +```go +func main() { + listenAddr := flag.String("listenAddress", ":8080", "listening on the default port") + flag.Parse() + + startServer := server.RunServer{ + ListenAddr: *listenAddr, + } + go func () { + err := startServer.Run() + if err != nil { + log.Fatalf("Server failed to start: %v", err) + } + }() + + + stopChan := make(chan os.Signal, 1) + signal.Notify(stopChan, syscall.SIGINT, syscall.SIGTERM) + <-stopChan + + startServer.Shutdown() +} +``` \ No newline at end of file