В этой статье вы создадите свой собственный блокчейн в Go (golang), поймете, как хеш-функция поддерживает целостность блокчейна, освоите создание и добавление новых блоков в Go (golang) и реализуете несколько узлов через Compete для генерации блоков, просмотрите всю цепочку в браузере и узнайте все остальные основы блокчейна.
Однако в статье не будут задействованы алгоритмы консенсуса, такие как Proof of Work (PoW) и Proof of Stake (PoS), в то же время, чтобы вы могли более четко увидеть блокчейн и добавление блоков, мы обсудим процесс сетевого взаимодействия.Упрощенно, содержание P2P-сети, такое как процесс «общего сетевого вещания», будет добавлено в последующих статьях.
Среда разработки языка Go (golang)
Мы предполагаем, что у вас уже есть некоторый опыт разработки на Go. После установки и настройки среды разработки Go нам также необходимо получить следующие зависимости:
~$ go get github.com/davecgh/go-spew/spew
spew
Это может помочь нам просмотреть две структуры данных struct и slice непосредственно в терминале.
~$ go get github.com/gorilla/mux
Гориллыmux
Пакет очень популярен, и мы используем его для написания веб-обработчиков.
~$ go get github.com/joho/godotenv
godotenv
Может помочь нам прочитать корневой каталог проекта.env
config, поэтому вам не нужно жестко прописывать в код такие вещи, как http-порты. Например вот так:
ADDR=8080
Далее мы создаемmain.go
документ. Большая часть работы после этого вращается вокруг этого файла, так что давайте начнем писать код!
импортировать зависимости
Декларативно импортируем все зависимые пакеты:
package main
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"io"
"log"
"net/http"
"os"
"time"
"github.com/davecgh/go-spew/spew"
"github.com/gorilla/mux"
"github.com/joho/godotenv"
)
Модель данных блокчейна
Затем мы определяем структуру, которая представляет модель данных каждого блока, составляющего цепочку блоков:
type Block struct {
Index int
Timestamp string
BPM int
Hash string
PrevHash string
}
- Индекс — это позиция этого блока во всей цепочке
- Отметка времени, очевидно, является отметкой времени, когда блок был сгенерирован.
- Хэш — это хеш-значение, сгенерированное этим блоком с помощью алгоритма SHA256.
- PrevHash представляет хеш SHA256 предыдущего блока.
- BPM ударов в минуту или частота сердечных сокращений
Далее мы определяем структуру для представления всей цепочки. Самое простое представление - ломтик блока:
var Blockchain []Block
Мы используем алгоритм хеширования (SHA256) для определения и поддержания правильного порядка блоков и блоков в цепочке, гарантируя, что значение PrevHash каждого блока равно хеш-значению предыдущего блока, так что цепочка строится в правильный порядок блоков:
Хеширование блокчейна и генерация новых блоков
Зачем нам хеширование? В основном по двум причинам:
- Уникально идентифицирует данные с целью экономии места. Хэш вычисляется с использованием данных всего блока, в нашем случае данные всего блока вычисляются с помощью SHA256 в не поддающуюся подделке строку фиксированной длины.
- Сохраняйте целостность цепи. Сохраняя хэш предыдущего блока, мы можем обеспечить правильный порядок каждого блока в цепочке. Любое вмешательство в данные изменит хэш и разорвет цепочку. Если взять в качестве примера сферу медицины и здравоохранения, которой мы занимаемся, например, злонамеренная третья сторона изменяет нездоровое значение BPM в одном или нескольких блоках, чтобы скорректировать цену «страхования жизни», тогда вся цепочка становится ненадежной.
Затем мы пишем функцию, которая вычисляет хэш SHA256 заданных данных:
func calculateHash(block Block) string {
record := string(block.Index) + block.Timestamp + string(block.BPM) + block.PrevHash
h := sha256.New()
h.Write([]byte(record))
hashed := h.Sum(nil)
return hex.EncodeToString(hashed)
}
Функция calculateHash принимает блок и вычисляет хэш SHA256 на основе значений Index, Timestamp, BPM и PrevHash в блоке. Далее мы можем написать функцию, которая генерирует блок:
func generateBlock(oldBlock Block, BPM int) (Block, error) {
var newBlock Block
t := time.Now()
newBlock.Index = oldBlock.Index + 1
newBlock.Timestamp = t.String()
newBlock.BPM = BPM
newBlock.PrevHash = oldBlock.Hash
newBlock.Hash = calculateHash(newBlock)
return newBlock, nil
}
Среди них индекс получается путем увеличения индекса данного предыдущего блока, отметка времени получается непосредственно с помощью функции time.Now(), значение хэша вычисляется с помощью предыдущей функции calculateHash, а PrevHash - это заданный предыдущий блок. хеш-значение блока.
Блок проверки блокчейна
После получения генерации блока нам нужна функция, которая поможет нам определить, был ли блок подделан. Проверьте индекс, чтобы увидеть, правильно ли увеличен этот блок, проверьте, согласуется ли PrevHash с хэшем предыдущего блока, а затем проверьте правильность значения хэша текущего блока с помощью calculateHash. С помощью этих шагов мы можем написать функцию проверки:
func isBlockValid(newBlock, oldBlock Block) bool {
if oldBlock.Index+1 != newBlock.Index {
return false
}
if oldBlock.Hash != newBlock.PrevHash {
return false
}
if calculateHash(newBlock) != newBlock.Hash {
return false
}
return true
}
Помимо проверки блоков, у нас есть еще проблема: обе ноды генерируют блоки и добавляют их в соответствующие цепочки, так кого брать? Мы оставляем детали здесь для следующей статьи, Давайте сначала запомним принцип: всегда выбирайте самую длинную цепочку:
Вообще говоря, более длинная цепочка означает, что ее данные (состояние) обновляются, поэтому нам нужна функция, которая может помочь нам переключить локальную цепочку с истекшим сроком действия на последнюю цепочку:
func replaceChain(newBlocks []Block) {
if len(newBlocks) > len(Blockchain) {
Blockchain = newBlocks
}
}
На данный момент мы в основном завершили все важные функции. Далее нам нужен удобный и интуитивно понятный способ просмотра нашей цепочки, включая данные и статус. Просмотр веб-страниц через браузер, вероятно, является наиболее подходящим способом!
Языковой веб-сервис Go (golang)
Я предполагаю, что вы хорошо знакомы с традиционными веб-сервисами и разработкой, поэтому вы, вероятно, узнаете эту часть с первого взгляда.
С помощью пакета Gorilla/mux начнем с написания функции для инициализации нашего веб-сервиса:
func run() error {
mux := makeMuxRouter()
httpAddr := os.Getenv("ADDR")
log.Println("Listening on ", os.Getenv("ADDR"))
s := &http.Server{
Addr: ":" + httpAddr,
Handler: mux,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
if err := s.ListenAndServe(); err != nil {
return err
}
return nil
}
Номер порта получается через вышеупомянутый .env, и после добавления некоторых основных параметров конфигурации этот веб-сервис уже может слушать и обслуживать!
Далее давайте определим разные конечные точки и соответствующие обработчики. Например, GET-запрос к «/» позволяет просматривать всю цепочку, а POST-запрос к «/» может создавать блоки.
func makeMuxRouter() http.Handler {
muxRouter := mux.NewRouter()
muxRouter.HandleFunc("/", handleGetBlockchain).Methods("GET")
muxRouter.HandleFunc("/", handleWriteBlock).Methods("POST")
return muxRouter
}
Обработчик запроса GET:
func handleGetBlockchain(w http.ResponseWriter, r *http.Request) {
bytes, err := json.MarshalIndent(Blockchain, "", " ")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
io.WriteString(w, string(bytes))
}
Чтобы упростить, мы возвращаем всю цепочку непосредственно в формате JSON, вы можете посетить localhost:8080 или 127.0.0.1:8080 в своем браузере для просмотра (8080 здесь — номер порта ADDR, который вы определили в .env).
Обработчик POST-запроса немного сложнее.Давайте сначала определим полезную нагрузку POST-запроса:
type Message struct {
BPM int
}
Давайте посмотрим на реализацию обработчика:
func handleWriteBlock(w http.ResponseWriter, r *http.Request) {
var m Message
decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(&m); err != nil {
respondWithJSON(w, r, http.StatusBadRequest, r.Body)
return
}
defer r.Body.Close()
newBlock, err := generateBlock(Blockchain[len(Blockchain)-1], m.BPM)
if err != nil {
respondWithJSON(w, r, http.StatusInternalServerError, m)
return
}
if isBlockValid(newBlock, Blockchain[len(Blockchain)-1]) {
newBlockchain := append(Blockchain, newBlock)
replaceChain(newBlockchain)
spew.Dump(Blockchain)
}
respondWithJSON(w, r, http.StatusCreated, newBlock)
}
Полезная нагрузка, определенная выше, может использоваться в теле нашего запроса POST, например:
{"BPM":75}
Помните функцию generateBlock, которую мы написали ранее? Он принимает параметр «предыдущий блок» и значение BPM. После того, как обработчик POST примет запрос, он может получить значение BPM в теле запроса, а затем сгенерировать новый блок с помощью функции, которая генерирует блок, и функции, которая проверяет блок!
Помимо этого, вы также можете:
- С помощью spew.Dump эта функция может выводить в консоль структуру, слайс и другие данные в очень красивом и легко читаемом виде, что нам удобно для отладки.
- При тестировании POST-запросов можно использовать chrome-плагин POSTMAN, который более интуитивно понятен и удобен, чем curl.
После обработки POST-запроса, независимо от того, успешно создан блок или нет, нам нужно вернуть ответ клиенту:
func respondWithJSON(w http.ResponseWriter, r *http.Request, code int, payload interface{}) {
response, err := json.MarshalIndent(payload, "", " ")
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("HTTP 500: Internal Server Error"))
return
}
w.WriteHeader(code)
w.Write(response)
}
Почти готово.
Далее «собираем» эти функции про блокчейн и веб-сервисы:
func main() {
err := godotenv.Load()
if err != nil {
log.Fatal(err)
}
go func() {
t := time.Now()
genesisBlock := Block{0, t.String(), 0, "", ""}
spew.Dump(genesisBlock)
Blockchain = append(Blockchain, genesisBlock)
}()
log.Fatal(run())
}
Здесь genesisBlock — самая важная часть основной функции, она используется для инициализации блокчейна, ведь PrevHash первого блока пустой.
О, да! законченный
Полный код можно получить отсюда:Github repo
Давайте начнем:
~$ go run main.go
В терминале мы можем увидеть информацию журнала запуска веб-сервера и распечатать информацию о блоке генезиса:
Затем мы открываем браузер и посещаем адрес localhost: 8080. Мы видим, что на странице отображается текущая информация о всей цепочке блоков (конечно, в настоящее время существует только один блок генезиса):
Затем мы отправляем несколько POST-запросов через POSTMAN:
Обновите страницу прямо сейчас, теперь в цепочке есть еще несколько блоков, которые являются именно теми, что мы только что сгенерировали, и вы можете видеть, что порядок и хеш-значение блоков правильные.
Суммировать
Мы только что завершили собственную цепочку блоков, хотя она очень проста (уродливая), но имеет базовые возможности, такие как генерация блоков, вычисление хэшей и проверка блоков. Затем вы можете продолжить углубленное изучение Другие важные знания о блокчейне, такие как алгоритмы консенсуса, такие как Proof of Work, Proof of Stake или смарт-контракты, Dapps, сайдчейны и т. д.
В настоящее время эта реализация не включает в себя какой-либо сетевой контент P2P, мы дополним эту часть в следующей статье, конечно же, мы рекомендуем вам попрактиковаться на этой основе!
если хочешьЭффективныйЧтобы узнать о разработке Ethereum DApp, вы можете посетитьсамый популярныйОнлайн интерактивный учебник:
Также можно получить доступ к другому контентуэтот эфириум блог.
оригинал:Code your own blockchain in less than 200 lines of Go!