Исчерпывающее руководство по кодированию онлайн-сообщества на основе блокчейна

1656605303 ischerpyvayushhee rukovodstvo po kodirovaniyu onlajn soobshhestva na osnove blokchejna

от Sandeep Panda

9QuQpuu9tTZ0grKUIhj-EVRAHVTXXWmqhbjZ

В Hashnode мы много экспериментировали с блокчейном и их вариантами использования. Мы сами руководим сообществом разработчиков, и идея «децентрализованных сообществ» меня очень увлекает. Тот факт, что каждый владеет данными и контролирует платформу, может породить новые типы социальных приложений и нарушить традиционный способ создания онлайн-сообществ.

Такие платформы, как Steemit, доказали, что можно создавать такие сообщества и вознаграждать пользователей за их вклад. Но как кому-то подойти к копированию и запуску собственной децентрализованной социальной платформы на основе блокчейна?

Чтобы ответить на этот вопрос, я принялся за создание децентрализованной версии HackerNews.

В ходе этого процесса я оценил несколько платформ и, наконец, остановился на протоколе под названием Tendermint. Используя Tendermint, я создал прототип под названием Mint, который может служить шаблоном для создания социальных приложений на основе блокчейна.

Кодовая база находится на GitHub. Вы можете просмотреть следующие ссылки для кода и демонстрации:

Итак, что нужно, чтобы построить социальное сообщество на основе блокчейна, где данные, созданные пользователями, децентрализованы? Если вы ищете ответ, вы попали в нужное место. Читайте дальше.

Предварительные наблюдения

Сначала я думал об использовании существующей платформы для создания программы. Платформы Smart Contract, как Ethereum, NEM, NEOи так далее предлагают хранение активов, но они не предназначены для хранения большого количества данных.

HyperLedger Fabric является убедительным, но разработан для развертывания в частных сетях блокчейн. Hashgraph звучит интересно, но сейчас он экспериментален.

Другие потенциальные решения были: Лиск Sidechains, Loom Network, и BigChainDB. Первые два находятся в приватной альфа-версии (только приглашение), тогда как BigChainDB работает на Tendermint.

Поэтому вместо использования BigChainDB я решил поиграть прямо с Tendermint и посмотреть, что возможно.

Почему Tendermint

Tendermint — это протокол, заботящийся об уровне консенсуса с помощью алгоритма BFT, а вы только сосредоточены на написании бизнес-логики.

Прелесть протокола заключается в том, что вы буквально свободны выбирать любой язык программирования для создания интерфейса (Application Blockchain Interface или просто ABCI), взаимодействующего с блокчейном.

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

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

Что нужно?

Вот что вам понадобится:

  • Сервер Macbook/Ubuntu
  • Голанг
  • Tendermint
  • MongoDB
  • И пиво… (Кофеманы могут заменить это кофе)

Настройка машины

Tendermint написан на Go. Итак, сначала нам нужно установить язык Go. Перейдите по этой ссылке, чтобы просмотреть несколько вариантов загрузки. Если вы используете Ubuntu, вы можете следовать этому руководству.

По умолчанию Go выбирает $HOME/go как ваше рабочее пространство. Если вы хотите использовать другое местоположение в качестве рабочей области, вы можете установить GOPATH переменная в ~/.profile . Теперь мы будем называть это место как GOPATH.

Во как ~/.profile файл смотрится на моей машине:

export GOPATH="$HOME/go" export PATH=~/.yarn/bin:$GOPATH/bin:$PATHexport GOBIN="$GOPATH/bin"

Не забудьте установить GOBIN переменной, как показано выше. Здесь будут установлены двоичные файлы Go.

Не забудьте запустить source ~/.profile после обновления файла.

Теперь мы можем установить Tendermint. Вот шаги:

  • cd $GOPATH/src/github.com
  • mkdir tendermint
  • cd tendermint

И, наконец,

git clone https://github.com/tendermint/tendermint

Это установит последнюю версию Tendermint. Поскольку я проверил свой код v0.19.7Давайте проверим конкретный выпуск.

cd tendermintgit checkout v0.19.7

Это поставит вас на v0.19.7. Чтобы продолжить установку, выполните следующие команды:

make get_tools make get_vendor_depsmake install

Поздравляю! Вы успешно установили Tendermint. Если все было установлено по назначению, команда tendermint version распечатает версию Tendermint.

Теперь вам нужно установить MongoDB.

Кодирование блокчейна

Если вы хотите понять, как работает Tendermint, просмотрите это руководство. Вам также может быть полезна следующая диаграмма.

rJyiZxm-1rAXkSFkV9XY-TtVoqvoS2forja5
Источник: Tendermint Docs

Здесь я очеркну несколько важных понятий:

  • Ядро Tendermint обрабатывает консенсусную часть.
  • Вам нужно написать сервер ABCI, обрабатывающий бизнес-логику, проверки и т.д. Хотя вы можете написать это на любом языке, на нашем языке будет Go.
  • Ядро Tendermint будет взаимодействовать с вашим сервером ABCI через соединение сокетов.
  • Сервер ABCI имеет множество методов (разработчики JS могут рассматривать их как обратные вызовы), которые будут вызываться ядром Tendermint во время различных событий.
  • Два важных метода: CheckTx и DeliverTx. Первый вызывается для проверки транзакции, а второй вызывается, когда Tx подтверждается.
  • DeliverTx помогает выполнять необходимые действия на основе подтвержденных транзакций. В нашем случае мы будем использовать это для создания и обновления нашего глобального состояния, хранящегося в MongoDB.
  • Tendermint использует консенсус BFT. Это означает, что более 2/3 валидаторов должны иметь консенсус для осуществления транзакции. Таким образом, даже если 1/3 валидатора станут неисправными, блокчейн все равно будет работать.
  • В реальном мире (по крайней мере в публичном развертывании) вы, скорее всего, добавите какой-нибудь консенсус, такой как PoS (доказательство состояния) в дополнение к консенсусу BFT. В этом случае мы просто продлим простой консенсус BFT. Добавление PoS я оставлю вам.

Я предлагаю вам клонировать сервер ABCI blockchain (под кодовым названием mint) из GitHub. Но прежде чем продолжить, нам нужно установить инструмент управления зависимостями под названием dep.

Если вы используете Mac, вы можете просто запустить brew install dep . Для Ubuntu выполните следующую команду.

curl  | sh

Теперь можно клонировать кодовую базу mint.

cd $GOPATH/srcgit clone  mintdep ensurego install mint

Сладкий! Теперь вы установили mint, являющийся сервером ABCI и работающий вместе с ядром Tendermint.

Теперь позвольте мне провести вас через все настройки и весь код.

Точка входа

Вы можете найти код (и точку входа) на GitHub здесь.

Точкой входа программы является mint.go . Важнейшей частью файла является следующий раздел:

app = jsonstore.NewJSONStoreApplication(db)srv, err := server.NewServer("tcp://0.0.0.0:46658", "socket", app) if err != nil {  return err }

Вся бизнес-логика, методы и т.п. jsonstore . Приведенный выше код просто создает сервер TCP на порту 46658 принимающий подключение сокетов от ядра Tendermint.

Теперь рассмотрим jsonstoreпакет.

Бизнес-логика

Вот jsonstore репо.

Наш сервер ABCI выполняет две важные вещи:

  • Проверка входных транзакций. Если транзакция недействительна, она возвращает код ошибки и транзакция отклоняется.
  • После того, как транзакция фиксируется (подтверждается > 2/3 валидаторов) и сохраняется в LevelDB, сервер ABCI обновляет свое глобальное состояние, хранящееся в MongoDB.

Мы собираемся использовать mgo для взаимодействия с MongoDB. Поэтому, jsonstore.go определяет 5 моделей, соответствующих 5 разным коллекциям MongoDB.

Код выглядит следующим образом:

// Post ...type Post struct {    ID          bson.ObjectId `bson:"_id" json:"_id"`    Title       string        `bson:"title" json:"title"`    URL         string        `bson:"url" json:"url"`    Text        string        `bson:"text" json:"text"`    Author      bson.ObjectId `bson:"author" json:"author"`    Upvotes     int           `bson:"upvotes" json:"upvotes"`    Date        time.Time     `bson:"date" json:"date"`    Score       float64       `bson:"score" json:"score"`    NumComments int           `bson:"numComments" json:"numComments"`    AskUH       bool          `bson:"askUH" json:"askUH"`    ShowUH      bool          `bson:"showUH" json:"showUH"`    Spam        bool          `bson:"spam" json:"spam"`}
// Comment ...type Comment struct {    ID              bson.ObjectId `bson:"_id" json:"_id"`    Content         string        `bson:"content" json:"content"`    Author          bson.ObjectId `bson:"author" json:"author"`    Upvotes         int           `bson:"upvotes" json:"upvotes"`    Score           float64       `bson:"score" json:"score"`    Date            time.Time    PostID          bson.ObjectId `bson:"postID" json:"postID"`    ParentCommentID bson.ObjectId `bson:"parentCommentId,omitempty" json:"parentCommentId"`}
// User ...type User struct {    ID        bson.ObjectId `bson:"_id" json:"_id"`    Name      string        `bson:"name" json:"name"`    Username  string        `bson:"username" json:"username"`    PublicKey string        `bson:"publicKey" json:"publicKey"`}
// UserPostVote ...type UserPostVote struct {    ID     bson.ObjectId `bson:"_id" json:"_id"`    UserID bson.ObjectId `bson:"userID" json:"userID"`    PostID bson.ObjectId `bson:"postID" json:"postID"`}
// UserCommentVote ...type UserCommentVote struct {    ID        bson.ObjectId `bson:"_id" json:"_id"`    UserID    bson.ObjectId `bson:"userID" json:"userID"`    CommentID bson.ObjectId `bson:"commentID" json:"commentID"`}

Мы также определяем несколько вспомогательных функций, например:

func byteToHex(input []byte) string {    var hexValue string    for _, v := range input {        hexValue += fmt.Sprintf("%02x", v)    }    return hexValue}
func findTotalDocuments(db *mgo.Database) int64 {    collections := [5]string{"posts", "comments", "users", "userpostvotes", "usercommentvotes"}    var sum int64
for _, collection := range collections {        count, _ := db.C(collection).Find(nil).Count()        sum += int64(count)    }
return sum}
func hotScore(votes int, date time.Time) float64 {    gravity := 1.8    hoursAge := float64(date.Unix() * 3600)    return float64(votes-1) / math.Pow(hoursAge+2, gravity)}
// FindTimeFromObjectID ... Convert ObjectID string to Timefunc FindTimeFromObjectID(id string) time.Time {    ts, _ := strconv.ParseInt(id[0:8], 16, 64)    return time.Unix(ts, 0)}

Они будут использованы впоследствии в коде.

Внутри CheckTx

Теперь перейдем к части проверки. Как мы принимаем или отклоняем транзакцию? Скажем, кто пытается зарегистрироваться, но не выбирает действительное имя пользователя. Как наша программа может это проверить?

Это делается через CheckTx функция. Подпись выглядит следующим образом:

func (app *JSONStoreApplication) CheckTx(tx []byte) types.ResponseCheckTx {
 // ... Validation logic}

Когда узел Tendermint получает транзакцию, он вызываетCheckTx сервера ABCI и проходы tx данные как а byte аргумент массива. Если CheckTx возвращает ненулевой код, транзакция отклоняется.

В нашем случае клиенты посылают объекты JSON с кодировкой Base64 в узел Tendermint по запросу RPC. Итак, наша работа состоит в том, чтобы декодировать tx и расмаршировать строку в объекте JSON.

Делается это так:

var temp interface{}err := json.Unmarshal(tx, &temp)if err != nil {  panic(err)}message := temp.(map[string]interface{})

message Объект обычно выглядит следующим образом:

{  body: {... Message body},  publicKey: <Public Key of Sender>,  signature: <message.body is signed with the Private Key>}

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

Лучший способ проверки — попросить клиентов подписать тело сообщения с помощью частного пользовательского ключа и присоединить открытый ключ и подпись к полезной нагрузке. Мы воспользуемся ed25519 алгоритм для создания ключей и подписания сообщения в браузере и нажатия конечной точки RPC. В CheckTx функцию, которую мы снова воспользуемся ed25519 и проверить сообщение с помощью открытого пользовательского ключа.

Делается это так:

pubKeyBytes, err := base64.StdEncoding.DecodeString(message["publicKey"].(string))
sigBytes, err := hex.DecodeString(message["signature"].(string))
messageBytes := []byte(message["body"].(string))isCorrect := ed25519.Verify(pubKeyBytes, messageBytes, sigBytes)if isCorrect != true {  return types.ResponseCheckTx{Code: code.CodeTypeBadSignature}}

В приведенном выше примере мы используем ed25519 пакет для проверки сообщения. Различные коды, такие как code.CodeTypeBadSignature определяются внутри code пакет. Это просто целые числа. Просто помните, что если вы хотите отклонить транзакцию, вы должны вернуть ненулевой код. В нашем случае, если мы обнаруживаем, что подпись сообщения недействительна, мы возвращаем CodeTypeBadSignature какой есть 4.

Следующий раздел о CheckTx имеет дело с разными проверками данных, такими как:

  • Если пользователь отправляет любую транзакцию, кроме createUser (Sign up), мы сначала проверяем, есть ли открытый ключ пользователя в нашей базе данных.
  • Если пользователь пытается создать сообщение или комментарий, он должен иметь действительные данные, например, не пуст title , content и так дальше.
  • Если пользователь пытается зарегистрироваться, то имя пользователя должно содержать допустимые символы.

Код выглядит следующим образом:

// ==== Does the user really exist? ======if body["type"] != "createUser" { publicKey := strings.ToUpper(byteToHex(pubKeyBytes))
 count, _ := db.C("users").Find(bson.M{"publicKey": publicKey}).Count()
 if count == 0 {  return types.ResponseCheckTx{Code: code.CodeTypeBadData} }}// ==== Does the user really exist? ======
codeType := code.CodeTypeOK
// ===== Data Validation =======switch body["type"] {case "createPost": entity := body["entity"].(map[string]interface{})
  if (entity["id"] == nil) || (bson.IsObjectIdHex(entity["id"]. (string)) != true) {  codeType = code.CodeTypeBadData  break }
if entity["title"] == nil || strings.TrimSpace(entity["title"].(string)) == "" {  codeType = code.CodeTypeBadData  break }
if (entity["url"] != nil) && (strings.TrimSpace(entity["url"].(string)) != "") {  _, err := url.ParseRequestURI(entity["url"].(string))  if err != nil {   codeType = code.CodeTypeBadData   break  } }case "createUser": entity := body["entity"].(map[string]interface{})
if (entity["id"] == nil) || (bson.IsObjectIdHex(entity["id"].(string)) != true) {  codeType = code.CodeTypeBadData  break }
r, _ := regexp.Compile("^[A-Za-z_0-9]+$")
if (entity["username"] == nil) || (strings.TrimSpace(entity["username"].(string)) == "") || (r.MatchString(entity["username"].(string)) != true) {  codeType = code.CodeTypeBadData  break }
if (entity["name"] == nil) || (strings.TrimSpace(entity["name"].(string)) == "") {  codeType = code.CodeTypeBadData  break }case "createComment": entity := body["entity"].(map[string]interface{})
if (entity["id"] == nil) || (bson.IsObjectIdHex(entity["id"].(string)) != true) {  codeType = code.CodeTypeBadData  break }
if (entity["postId"] == nil) || (bson.IsObjectIdHex(entity["postId"].(string)) != true) {  codeType = code.CodeTypeBadData  break }
if (entity["content"] == nil) || (strings.TrimSpace(entity["content"].(string)) == "") {  codeType = code.CodeTypeBadData  break }}
// ===== Data Validation =======return types.ResponseCheckTx{Code: codeType}

Код действительно прост и достаточно ясен. Поэтому я не буду вдаваться в детали, а оставлю вам читать и исследовать дальше.

Внутри DeliverTx

После подтверждения транзакции и ее применения к блокчейну, ядро ​​Tendermint звонит DeliverTx и передает транзакцию как массив б. Сигнатура функции выглядит так:

func (app *JSONStoreApplication) DeliverTx(tx []byte) types.ResponseDeliverTx {  // ... Code goes here}

Мы будем использовать эту функцию для создания глобального положения на основе MongoDB. Мы делаем это, чтобы пользователи нашего сайта могли легко читать данные.

Эта функция обширна и имеет несколько случаев. В этой главе я расскажу только об одном случае, который называется «После создания». Поскольку остальные коды похожи, я оставлю вам возможность копнуть глубже и исследовать полный код.

Во-первых, мы продолжим распоряжаться txданные в объекте JSON:

var temp interface{}err := json.Unmarshal(tx, &temp)
if err != nil { panic(err)}
message := temp.(map[string]interface{})
var bodyTemp interface{}
errBody := json.Unmarshal([]byte(message["body"].(string)), &bodyTemp)
if errBody != nil { panic(errBody)}
body := bodyTemp.(map[string]interface{})

Для создания публикации объект сообщения выглядит следующим образом:

{body: {  type: "createPost",  entity: {    id: id,    title: title,    url: url,    text: text,    author: author  }},signature: signature,publicKey: publicKey}

А вот как DeliverTx функция создает новую запись в базе данных, когда выполняется транзакция «createPost»:

entity := body["entity"].(map[string]interface{})
var post Postpost.ID = bson.ObjectIdHex(entity["id"].(string))post.Title = entity["title"].(string)
if entity["url"] != nil { post.URL = entity["url"].(string)}if entity["text"] != nil { post.Text = entity["text"].(string)}
if strings.Index(post.Title, "Show UH:") == 0 { post.ShowUH = true} else if strings.Index(post.Title, "Ask UH:") == 0 { post.AskUH = true}
pubKeyBytes, errDecode := base64.StdEncoding.DecodeString(message["publicKey"].(string))
if errDecode != nil { panic(errDecode)}
publicKey := strings.ToUpper(byteToHex(pubKeyBytes))
var user Usererr := db.C("users").Find(bson.M{"publicKey": publicKey}).One(&user)if err != nil { panic(err)}post.Author = user.ID
post.Date = FindTimeFromObjectID(post.ID.Hex())
post.Upvotes = 1
post.NumComments = 0
// Calculate hot rankpost.Score = hotScore(post.Upvotes, post.Date)
// While replaying the transaction, check if it has been marked as spam
spamCount, _ := db.C("spams").Find(bson.M{"postID": post.ID}).Count()
if spamCount > 0 { post.Spam = true}
dbErr := db.C("posts").Insert(post)
if dbErr != nil { panic(dbErr)}
var document UserPostVotedocument.ID = bson.NewObjectId()document.UserID = user.IDdocument.PostID = post.ID
db.C("userpostvotes").Insert(document)

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

Теперь, когда мы рассмотрели два важных аспекта сервера ABCI, давайте попробуем запустить как ядро ​​Tendermint, так и наш сервер и посмотрим, как отправлять транзакции.

Чтобы запустить программу, выполните следующие команды из двух разных терминалов.

Сначала запустите:

mint

Если команда выполнена успешно, вы увидите следующий вывод в терминале:

ai-JWS7ryQg6Ks0tavletYx274e-0i6830jS
Выход монетного двора

Перед запуском убедитесь, что MongoDB уже запущен mint. Если ваш терминал не может распознать mint команду, обязательно запустите source ~/.profile .

Затем запустите Tendermint в другом терминале:

tendermint node --consensus.create_empty_blocks=false

По умолчанию Tendermint создает новые блоки каждые 3 секунды, даже если нет транзакций.

Чтобы предотвратить это, мы используем флаг:

consensus.create_empty_blocks=false

Теперь, когда Tendermint запущен, вы можете начать отправлять ему транзакции. Вам нужен клиент, который может генерировать ed25519 ключи, подпишите свои запросы и нажмите конечную точку RPC, открывшуюся Tendermint.

Пример запроса (Node.js) выглядит так:

const base64Data = req.body.base64Data;
let headers = {    'Content-Type': 'text/plain',    'Accept':'application/json-rpc'}
let options = {    url: "    method: 'POST',    headers: headers,    json: true,    body: {"jsonrpc":"2.0","method":"broadcast_tx_commit","params": { "tx" : base64Data } ,"id":"something"}}
request(options, function (error, response, body) {    res.json({ body: response.body });});

Обратите внимание, что конечная точка RPC доступна на порту 46657 .

Формирование и подписание запросов вручную может быть утомительным. Поэтому я предлагаю вам использовать Uphack (веб-сайт в стиле HackerNews, взаимодействующий с блокчейном), чтобы получить полную картину.

Чтобы установить Uphack, выполните следующие действия.

git clone  Uphackyarngulp less // make sure gulp is installed globallynode server.js

Вы можете получить доступ к веб-сайту на . На моей машине это выглядит так:

r4KTf9O7sjlqQah6IgOJ5aNWRDME0mNlpR81
Uphack

Поскольку у вас нет данных, сначала они будут выглядеть пустыми. Не стесняйтесь зарегистрировать аккаунт и отправить несколько публикаций, чтобы представить процесс.

При использовании приложения откройте вкладку сети в браузере и просмотрите раздел XHR. The /rpc URL принимает данные base64 и запрашивает конечную точку RPC Tendermint на стороне сервера. Вы можете скопировать данные base64 и вставить их в декодер base64, чтобы увидеть фактические данные, которые отправляются.

SAZpTI3shyASXIL89fVB4dvPP0ucvsNLI1FP

Притворство в детали Uphack выходит за рамки этого учебника. Однако, как упоминалось выше, код для Uphack (клиент) является открытым исходным кодом, и логика проста. Если вы просмотрите кодовую базу и изучите различные конечные точки, вы лучше поймете весь процесс.

Подведению

Подводя итог, мы построили a блокчейн который хранит данные JSON в цепи и принимает транзакции в форме base64. Чтобы продемонстрировать использование, мы также кратко рассмотрели Uphack, веб-сайт в стиле HackerNews который взаимодействует с блокчейном.

Однако вот несколько вещей, которые вы должны знать:

  • Вы создали сеть с одним узлом. Это означает, что вы единственный валидатор. Если вам интересно многоузловое развертывание, просмотрите документацию Mint. Пока мы развернули 4-узловую сеть, и если вы хотите стать валидатором и поиграть с блокчейном, не стесняйтесь обращаться ко мне.
  • Эта договоренность использует консенсус BFT. В реальном мире вам понадобится определенный алгоритм консенсуса, например, Proof of Stake, Delegated Proof of Stake и т.д.
  • Конечная точка RPC блокчейну слушает 46657 и сервер ABCI работает 46658 . В любое время вы можете проверить состояние блокчейна, посетив страницу localhost:46657/status .
  • На данный момент нет стимула стать валидатором и создавать блоки. В настройках (D)PoS производители блоков должны получать определенный токен каждый раз, когда они предлагают блок. Это осталось для вас как упражнение.
  • Сервер ABCI может быть написан на любом языке. К примеру, проверьте js-abci.

В заключение я хотел бы объяснить, что я не специалист по блокчейну. Я учусь и просто делюсь тем, что мне интересно. Хранение данных на цепочке меня увлекает, и я верю децентрализованные социальные сообщества является одним из основных вариантов использования блокчейна.

Если вы заметили какие-либо неточности в кодовой базе или статье, не стесняйтесь указать на это. Я буду благодарен, если вы воспользуетесь mint & Uphack и предоставите свой отзыв. PR всегда рады!

Дайте мне знать, что вы думаете в комментариях ниже!

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *