Распрацоўка блокчейна для прамысловасці на Go. Частка 1

Вось ужо чатыры месяцы я займаюся праектам, які назваў "Распрацоўка сродкаў абароны і кіравання дадзенымі ў дзяржаўных і прамысловых сферах на аснове блокчейна".
Цяпер мне хацелася б расказаць, пра тое, як гэты праект я пачынаў, што зараз і апішу падрабязна праграмны код.

Распрацоўка блокчейна для прамысловасці на Go. Частка 1

Гэта першы артыкул з цыклу артыкулаў. Тут я апісваю сервер і пратакол. Насамрэч, чытач нават можа напісаць і свае варыянты гэтых элементаў блокчейна.

А вось другая частка - Пра структуры дадзеных блокчейна і транзакцый, а таксама аб пакеце рэалізуе ўзаемадзеянне з базай дадзеных.

У мінулым годзе на хакатоне Лічбавы прарыў падкінулі ідэю, каб для прамысловасці і лічбавай эканомікі зрабіць карысную сістэму па тэхналогіі размеркаваных рэестраў, гэтак жа на распрацоўку быў выдадзены грант Фондам садзейнічання інавацыям (аб гранце варта мне напісаць асобны артыкул, для тых хто стартапы толькі пачынае рабіць ), а зараз па-парадку.

Распрацоўка адбываецца на мове Go, а база дадзеных у якой захоўваюцца блокі - LevelDB.
Асноўныя часткі гэта пратакол, сервер (які запускае TCP і WebSocket - першы для сінхранізацыі блокчейна, у другі для падлучэння кліентаў, адпраўкі транзакцый і каманд з JavaScript, напрыклад.

Як было згадана гэты блокчейн патрэбен у першую чаргу для аўтаматызацыі і абароны абмену прадукцыяй паміж пастаўшчыкамі і замоўцамі, альбо і тымі і іншымі ў адной асобе. Давяраць адзін аднаму такія не спяшаюцца. Але задача не толькі зрабіць "чэкавую кніжку" з убудаваным калькулятарам, а сістэму з аўтаматызацыяй большасці руцінных задач, якія ўзнікаюць пры працы з жыццёвым цыклам прадукцыі. Байт-код, які за гэтую справу адказвае як і прынята ў блокчейнов захоўваецца ва ўваходах і вынахадах транзакцый (самі транзакцыі сядзяць у блоках, блокі ў LevelDB папярэдне закадаваныя ў фармат GOB). Для пачатку давайце раскажу пра пратакол і сервер (ён жа нода).

Пратакол працуе не складана, увесь яго сэнс у тым каб у адказ на адмысловы радок-каманду пераключыцца ў рэжым загрузкі нейкіх дадзеных, звычайна блока ці транзакцыі, а яшчэ ён патрэбен для абмену інвентаром, гэта каб нода ведала да каго яна падлучаная і як у іх справы (падлучаныя для сесіі сінхранізацыі ноды яшчэ завуць "суседнімі" таму што вядомыя іх IP і захоўваюцца дадзеныя іх стану ў памяці).

Тэчкі (дырэкторыі як іх заве Linux) у разуменні Go-праграмістаў завуцца пакетамі, таму ў пачатку кожнага файла з Go-кодам з гэтай дырэкторыі ў пачатку пішуць package имя_папки_де_сидит_этот_файл. Інакш не атрымаецца скарміць кампілятару пакет. Ну гэта для дасведчаных гэтую мову не сакрэт. Вось гэтыя пакеты:

  • Сеткавае ўзаемадзеянне (server, client, protocol)
  • Структуры захоўваемых і перадаюцца дадзеных (block, transaction)
  • База даных (blockchain)
  • Кансэнсус (consensus)
  • Стэкавая віртуальная машына (xvm)
  • Дапаможныя (crypto, types) пакуль усё.

Вось спасылка на github

Гэта навучальная версія ў ёй адсутнічае міжпрацэснае ўзаемадзеянне і некалькі эксперыментальных кампанентаў, за тое структура адпавядае той над якой вядзецца распрацоўка. Калі ў вас будзе што падказаць у каментарах я з задавальненнем улічу ў далейшай распрацоўцы. А зараз зацемкі па пакетах server і пратакол.

Спачатку паглядзім на server.

Падпраграма server выконвае функцыі сервера дадзеных, які працуе па-над пратаколам TCP з выкарыстаннем структур дадзеных з пакета protocol.

Падпраграма выкарыстоўвае наступныя пакеты: сервер, пратакол, тыпы. У самім пакеце ў tcp_server.go змяшчаецца структура дадзеных Служыць.

type Serve struct {
	Port string
	BufSize int
	ST *types.Settings
}

Яна можа прымаць наступныя параметры:

  • Сеткавы порт, па якім будзе ажыццяўляцца абмен дадзенымі
  • Файл канфігурацыі сервера ў фармаце JSON
  • Сцяг запуску ў рэжыме адладкі (прыватнага блокчэйна)

Ход выканання:

  • Счытваецца канфігурацыя з файла JSON
  • Правяраецца сцяг рэжыму адладкі: калі ён усталяваны, то запуск планавальніка сеткавай сінхранізацыі не ажыццяўляецца і блокчейн не загружаецца
  • Ініцыялізацыя структуры дадзеных канфігурацыі і запуск сервера

сервер

  • Ажыццяўляе спуск TCP сервера і сеткавае ўзаемадзеянне ў адпаведнасці з пратаколам.
  • У ім маецца структура дадзеных Serve якая складаецца з нумара порта, памеру буфера і паказальніка на структуру types.Settings
  • Метад Run запускае сеткавае ўзаемадзеянне (праслухоўванне ўваходзячых злучэнняў на зададзеным порце, пры атрыманні новага злучэння яго апрацоўка перадаецца ў прыватны метад handle у новым струмені)
  • В апрацоўваць дадзеныя са злучэння счытваюцца ў буфер, пераўтворацца ў радковае паданне і перадаюцца ў protocol.Choice
  • protocol.Choice вяртае вынік альбо выклікае памылку. вынік затым перадаецца ў protocol.Interprete, які вяртае intrpr - аб'ект тыпу InterpreteData, альбо выклікае памылку апрацоўкі выніку выбару
  • Затым выконваецца switch па intrpr.Commands[0] у якім правяраецца адно з: result, inv, error і ёсць секцыя дэфолт
  • У секцыі вынік знаходзіцца switch па значэнні intrpr.Commands[1] які правярае значэння bufferlength и версія (у кожным кейсе выклікаецца адпаведная функцыя)

функцыі GetVersion и Даўжыня буфера знаходзяцца ў файле srvlib.go пакета server.

GetVersion(conn net.Conn, version string)

проста друкуе ў кансоль і адпраўляе кліенту перададзеную ў параметры версію:

conn.Write([]byte("result:" + version))

.
Функцыя

BufferLength(conn net.Conn, intrpr *protocol.InterpreteData)

выконвае загрузку блока, транзакцыі ці іншых вызначаных дадзеных наступным чынам:

  • Друкуе на кансолі паказаны ў пратаколе тып дадзеных, якія трэба прыняты:
    fmt.Println("DataType:", intrpr.Commands[2])
  • Счытвае значэнне intrpr.Body у лікавую зменную buf_len
  • Стварае буфер newbuf названага памеру:
    make([]byte, buf_len)
  • Адпраўляе адказ ok:
    conn.Write([]byte("result:ok"))
  • Вырабляе поўнае запаўненне буфера з счытванага струменя:
    io.ReadFull(conn, newbuf)

    .

  • Выводзіць у кансоль змесціва буфера
    fmt.Println(string(newbuf))

    і колькасць прачытаных байтаў

    fmt.Println("Bytes length:", n)
  • Адпраўляе адказ ok:
    conn.Write([]byte("result:ok"))

Метады з пакета server настроены такім чынам, каб апрацоўвалі атрыманыя дадзеныя функцыямі з пакета пратакол.

пратакол

Пратакол служыць сродкам, якое прадстаўляе дадзеныя пры сеткавым абмене.

Choice(str string) (string, error) выконвае першасную апрацоўку прынятых серверам дадзеных, на ўваход атрымлівае радковае прадстаўленне дадзеных і вяртае радок падрыхтаваны для Перакладчык:

  • Уваходны радок разбіваецца на head і body з дапамогай ReqParseN2(str)
  • head разбіваецца на элементы і змяшчаецца ў слайс commands з дапамогай ReqParseHead(head)
  • В switch(commands[0]) выбіраемы атрыманую каманду (cmd, key, address альбо спрацоўвае секцыя дэфолт)
  • У cmd правяраецца 2 каманды switch(commands[1]) - length и getversion.
  • даўжыня правярае тып дадзеных у commands[2] і захоўвае яго ў тып дадзеных
  • Правярае што цела змяшчае радковае значэнне
    len(body) < 1
  • Вяртае радок адказ:
    "result:bufferlength:" + datatype + "/" + body
  • getversion вяртае радок
    return "result:version/auto"

Перакладчык

Змяшчае структуру InterpreteData і выконвае другасную апрацоўку вернутай з Выбар радкі і фарміраванне аб'екта InterpreteData.

type InterpreteData struct {
	Head string
	Commands []string
	Body string
	IsErr bool
	ErrCode int 
	ErrMessage string
}

Функцыя

Interprete(str string) (*InterpreteData, error)

прымае радок вынік і стварае вяртае спасылку на аб'ект InterpreteData.

Ход выканання:

  • аналагічна Выбар здабываецца head і body з дапамогай ReqParseN2(str)
  • head разбіваецца на элементы з дапамогай ReqParseHead(head)
  • Ініцыялізуецца аб'ект InterpreteData і вяртаецца паказальнік на яго:

res := &InterpreteData{
	Head: head,
	Commands: commands,
	Body: body,
}
return res, nil

Гэты аб'ект выкарыстоўваецца ў server.go пакета main.

Кліент

Пакет client змяшчае функцыі TCPConnect и TCPResponseData.

Функцыя

TCPConnect(s *types.Settings, data []byte, payload []byte)

працуе наступным чынам:

  • Выконваецца падлучэнне да паказанага ў перададзеным аб'екце налад злучэння
    net.Dial("tcp", s.Host + ":" + s.Port)
  • Перадаюцца дадзеныя, перададзеныя ў параметры data:
    conn.Write(data)
  • Счытваецца адказ
    resp, n, _ := TCPResponseData(conn, s.BufSize)

    і друкуецца на кансолі

    fmt.Println(string(resp[:n]))
  • Калі перададзены карысная нагрузка тое перадае яго
    conn.Write(payload)

    і таксама счытвае адказ сервера, друкуючы яго на кансолі

Функцыя

 TCPResponseData(conn net.Conn, bufsiz int) ([]byte, int, error)

стварае буфер паказанага памеру, чытае туды адказ сервера і вяртае гэты буфер і колькасць лічаных байтаў, а таксама аб'ект памылкі.

Падпраграма client

Служыць для перадачы каманд серверам нод, а таксама атрыманні кароткай статыстыкі і тэставанні.

Можа прымаць наступныя параметры: файл канфігурацыі ў фармаце JSON, дадзеныя для перадачы серверу ў выглядзе радка, шлях да файла для перадачы яго ў payload, сцяг эмуляцыі планавальніка ноды, тып якія перадаюцца дадзеных у выглядзе лікавага значэння.

  • Атрымліваем канфігурацыю
    st := types.ParseConfig(*config)
  • Калі перададзены сцяг emu запускаецца sheduler
  • Калі адданы сцяг f з указаннем шляху да файла, то загружаем яго дадзеныя ў fdb і выконваецца адпраўка змесціва серверу
    client.TCPConnect(st, []byte(CMD_BUFFER_LENGTH + ":" + strconv.Itoa(*t) + "/" + strconv.Itoa(fdblen)), fdb)
  • Калі файл не зададзены, то проста адпраўляюцца дадзеныя са сцяга. -d:
    client.TCPConnect(st, []byte(*data), nil)

Усё гэта спрошчанае прадстаўленне якое паказвае структуру пратакола. Пры распрацоўцы ў яго структуру дабаўляецца неабходны функцыянал.

У другой частцы я распавяду аб структурах дадзеных для блокаў і транзакцый, у 3 аб WebSocket серверы для падлучэння з JavaScript, у 4 будзе разгледжаны планавальнік сінхранізацыі, далей сцякавая машына апрацоўваючая байткод з уваходаў і вынахадаў, крыптаграфія і пулы для вынахадаў.

Крыніца: habr.com

Дадаць каментар