Skip to content

Commit

Permalink
feature: implemented the bootloading node | @jim-nnamdi will be proud
Browse files Browse the repository at this point in the history
  • Loading branch information
theghostmac committed Aug 13, 2023
1 parent e9beddc commit 80b928d
Show file tree
Hide file tree
Showing 8 changed files with 275 additions and 83 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
.idea
.idea

explanation.md
8 changes: 1 addition & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
60 changes: 44 additions & 16 deletions cmd/main.go
Original file line number Diff line number Diff line change
@@ -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{}{}
}
89 changes: 85 additions & 4 deletions explanation.md
Original file line number Diff line number Diff line change
@@ -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.

6 changes: 3 additions & 3 deletions internal/network/local_transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -18,15 +18,15 @@ 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()

if _, exists := lt.Peers[tr.Address()]; exists {
return fmt.Errorf("peer with address %v already exists", tr.Address())
}

lt.Peers[tr.Address()] = tr
lt.Peers[tr.Address()] = tr.(*LocalTransport)

return nil
}
Expand Down
22 changes: 0 additions & 22 deletions internal/server/runner.go

This file was deleted.

67 changes: 37 additions & 30 deletions internal/server/server.go
Original file line number Diff line number Diff line change
@@ -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)
}
Loading

0 comments on commit 80b928d

Please sign in to comment.