Go (golang) нужно всего 180 строк кода для разработки блокчейна

задняя часть Go алгоритм блокчейн

В этой статье вы создадите свой собственный блокчейн в 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Может помочь нам прочитать корневой каталог проекта.envconfig, поэтому вам не нужно жестко прописывать в код такие вещи, как 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 каждого блока равно хеш-значению предыдущего блока, так что цепочка строится в правильный порядок блоков:

180行go代码让你彻底理解区块链是什么

Хеширование блокчейна и генерация новых блоков

Зачем нам хеширование? В основном по двум причинам:

  • Уникально идентифицирует данные с целью экономии места. Хэш вычисляется с использованием данных всего блока, в нашем случае данные всего блока вычисляются с помощью 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
}

Помимо проверки блоков, у нас есть еще проблема: обе ноды генерируют блоки и добавляют их в соответствующие цепочки, так кого брать? Мы оставляем детали здесь для следующей статьи, Давайте сначала запомним принцип: всегда выбирайте самую длинную цепочку:

180行go代码让你彻底理解区块链是什么

Вообще говоря, более длинная цепочка означает, что ее данные (состояние) обновляются, поэтому нам нужна функция, которая может помочь нам переключить локальную цепочку с истекшим сроком действия на последнюю цепочку:

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

В терминале мы можем увидеть информацию журнала запуска веб-сервера и распечатать информацию о блоке генезиса:

180行go代码让你彻底理解区块链是什么

Затем мы открываем браузер и посещаем адрес localhost: 8080. Мы видим, что на странице отображается текущая информация о всей цепочке блоков (конечно, в настоящее время существует только один блок генезиса):

180行go代码让你彻底理解区块链是什么

Затем мы отправляем несколько POST-запросов через POSTMAN:

180行go代码让你彻底理解区块链是什么

Обновите страницу прямо сейчас, теперь в цепочке есть еще несколько блоков, которые являются именно теми, что мы только что сгенерировали, и вы можете видеть, что порядок и хеш-значение блоков правильные.

180行go代码让你彻底理解区块链是什么

Суммировать

Мы только что завершили собственную цепочку блоков, хотя она очень проста (уродливая), но имеет базовые возможности, такие как генерация блоков, вычисление хэшей и проверка блоков. Затем вы можете продолжить углубленное изучение Другие важные знания о блокчейне, такие как алгоритмы консенсуса, такие как Proof of Work, Proof of Stake или смарт-контракты, Dapps, сайдчейны и т. д.

В настоящее время эта реализация не включает в себя какой-либо сетевой контент P2P, мы дополним эту часть в следующей статье, конечно же, мы рекомендуем вам попрактиковаться на этой основе!

если хочешьЭффективныйЧтобы узнать о разработке Ethereum DApp, вы можете посетитьсамый популярныйОнлайн интерактивный учебник:

Также можно получить доступ к другому контентуэтот эфириум блог.

оригинал:Code your own blockchain in less than 200 lines of Go!