From b84b79146c09860d3d42229b396c5b229b42772a Mon Sep 17 00:00:00 2001 From: Ivan Gagarkin Date: Sat, 28 Sep 2024 20:46:05 +0200 Subject: [PATCH] #5 Introduce basic REST API --- cmd/node/main.go | 21 ++++++++++++++ go.mod | 2 ++ go.sum | 2 ++ internal/api/handlers/block.go | 37 ++++++++++++++++++++++++ internal/api/handlers/chain.go | 19 ++++++++++++ internal/api/router.go | 20 +++++++++++++ internal/blockchain/blockchain.go | 43 ++++++++++++++++++++++++---- internal/consensus/consensus.go | 2 +- internal/consensus/consensus_test.go | 2 +- internal/consensus/validation.go | 10 ++++--- 10 files changed, 147 insertions(+), 11 deletions(-) create mode 100644 cmd/node/main.go create mode 100644 go.sum create mode 100644 internal/api/handlers/block.go create mode 100644 internal/api/handlers/chain.go create mode 100644 internal/api/router.go diff --git a/cmd/node/main.go b/cmd/node/main.go new file mode 100644 index 0000000..9d9e293 --- /dev/null +++ b/cmd/node/main.go @@ -0,0 +1,21 @@ +// cmd/myblockchain/main.go +package main + +import ( + "blockchain/internal/api" + "blockchain/internal/blockchain" + "blockchain/internal/consensus" + "log" + "net/http" +) + +func main() { + bc := blockchain.NewBlockchain() + + cons := consensus.NewPoWConsensus(&bc) + + router := api.NewRouter(&bc, cons) + + log.Println("Listening on :8080") + log.Fatal(http.ListenAndServe(":8080", router)) +} diff --git a/go.mod b/go.mod index e45d826..c3705da 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module blockchain go 1.23.1 + +require github.com/gorilla/mux v1.8.1 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..7128337 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= diff --git a/internal/api/handlers/block.go b/internal/api/handlers/block.go new file mode 100644 index 0000000..0cd0883 --- /dev/null +++ b/internal/api/handlers/block.go @@ -0,0 +1,37 @@ +package handlers + +import ( + "encoding/json" + "net/http" + "strconv" + + "blockchain/internal/blockchain" + + "github.com/gorilla/mux" +) + +func GetBlockHandler(bc *blockchain.Blockchain) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + indexStr, ok := vars["index"] + if !ok { + http.Error(w, "Index is required", http.StatusBadRequest) + return + } + + index, err := strconv.Atoi(indexStr) + if err != nil { + http.Error(w, "Invalid index format", http.StatusBadRequest) + return + } + + block, err := bc.GetBlock(index) + if err != nil { + http.Error(w, err.Error(), http.StatusNotFound) + return + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(block) + } +} diff --git a/internal/api/handlers/chain.go b/internal/api/handlers/chain.go new file mode 100644 index 0000000..eb95f6b --- /dev/null +++ b/internal/api/handlers/chain.go @@ -0,0 +1,19 @@ +package handlers + +import ( + "encoding/json" + "net/http" + + "blockchain/internal/blockchain" +) + +func GetChainHandler(bc *blockchain.Blockchain) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + response := map[string]interface{}{ + "length": len(bc.Blocks()), + "blocks": bc.Blocks(), + } + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) + } +} diff --git a/internal/api/router.go b/internal/api/router.go new file mode 100644 index 0000000..043f22d --- /dev/null +++ b/internal/api/router.go @@ -0,0 +1,20 @@ +// internal/api/router.go +package api + +import ( + "blockchain/internal/blockchain" + "blockchain/internal/consensus" + + "blockchain/internal/api/handlers" + + "github.com/gorilla/mux" +) + +func NewRouter(bc *blockchain.Blockchain, cons *consensus.PoWConsensus) *mux.Router { + router := mux.NewRouter() + + router.HandleFunc("/chain", handlers.GetChainHandler(bc)).Methods("GET") + router.HandleFunc("/blocks/{index}", handlers.GetBlockHandler(bc)).Methods("GET") + + return router +} diff --git a/internal/blockchain/blockchain.go b/internal/blockchain/blockchain.go index 13f10b4..4a33ea3 100644 --- a/internal/blockchain/blockchain.go +++ b/internal/blockchain/blockchain.go @@ -1,25 +1,58 @@ package blockchain +import ( + "errors" + "sync" +) + type Blockchain struct { - Blocks []Block + blocks []Block Difficulty int + mutex sync.Mutex } func NewBlockchain() Blockchain { return Blockchain{ - Blocks: []Block{GenesisBlock(2)}, + blocks: []Block{GenesisBlock(2)}, Difficulty: 2, + mutex: sync.Mutex{}, } } +func (bc *Blockchain) Blocks() []Block { + bc.mutex.Lock() + defer bc.mutex.Unlock() + + return bc.blocks +} + func (bc *Blockchain) AddBlock(block Block) { - bc.Blocks = append(bc.Blocks, block) + bc.mutex.Lock() + defer bc.mutex.Unlock() + + bc.blocks = append(bc.blocks, block) } func (bc *Blockchain) FirstBlock() Block { - return bc.Blocks[0] + bc.mutex.Lock() + defer bc.mutex.Unlock() + + return bc.blocks[0] } func (bc *Blockchain) LastBlock() Block { - return bc.Blocks[len(bc.Blocks)-1] + bc.mutex.Lock() + defer bc.mutex.Unlock() + + return bc.blocks[len(bc.blocks)-1] +} + +func (bc *Blockchain) GetBlock(index int) (Block, error) { + bc.mutex.Lock() + defer bc.mutex.Unlock() + + if index < 0 || index >= len(bc.blocks) { + return Block{}, errors.New("invalid index") + } + return bc.blocks[index], nil } diff --git a/internal/consensus/consensus.go b/internal/consensus/consensus.go index a85f197..fa12e38 100644 --- a/internal/consensus/consensus.go +++ b/internal/consensus/consensus.go @@ -41,7 +41,7 @@ func (c *PoWConsensus) CreateNewBlock(data string) blockchain.Block { } func (c *PoWConsensus) AdjustDifficulty() int { - return CalculateNewDifficulty(c.blockchain.Blocks) + return CalculateNewDifficulty(c.blockchain.Blocks()) } func (c *PoWConsensus) AddNewBlock(block blockchain.Block) error { diff --git a/internal/consensus/consensus_test.go b/internal/consensus/consensus_test.go index bfa797f..08fc2b6 100644 --- a/internal/consensus/consensus_test.go +++ b/internal/consensus/consensus_test.go @@ -43,7 +43,7 @@ func TestAddNewBlock(t *testing.T) { t.Errorf("failed to add block to blockchain: " + result.Error()) } - if result := ValidateBlockChain(bc); result != nil { + if result := ValidateBlockChain(&bc); result != nil { t.Errorf("failed to validate blockchain: " + result.Error()) } } diff --git a/internal/consensus/validation.go b/internal/consensus/validation.go index 229fef0..be403d0 100644 --- a/internal/consensus/validation.go +++ b/internal/consensus/validation.go @@ -6,11 +6,13 @@ import ( "strings" ) -func ValidateBlockChain(blockchain blockchain.Blockchain) error { +func ValidateBlockChain(blockchain *blockchain.Blockchain) error { + var blocks = blockchain.Blocks() + // skip genesis block - for i := 1; i < len(blockchain.Blocks); i++ { - block := blockchain.Blocks[i] - previousBlock := blockchain.Blocks[i-1] + for i := 1; i < len(blocks); i++ { + block := blocks[i] + previousBlock := blocks[i-1] if err := validateBlock(block); err != nil { return err