Как я 8 месяцев переписывал Dcoin на Go… про Катю, в общем

от автора


В этой части я дойду до момента, когда пришла смс-ка «Не звони и не пиши мне больше!!!!»

Первая часть тут, вторая тут.
4,5 года назад я имел неосторожность начать писать свою криптовалюту на совсем неподходящем для этого дела языке — на PHP. В итоге, конечно, написал (я упрямый), но получился костыль на костыле и то, что оно вообще работало было просто какой-то магией.
Сразу хочу предупредить, программер я самоучка-недоучка и пишу код, мягко сказать, неидеально.

Началось всё с того, что я расстался с девушкой, по имени Катя и в этот же день (4 апреля 2015-го) решил изучить Go и переписать свою криптовалюту. Писать про Катю не под спойлерами не могу, т.к. хабр всё же для IT-шных статей, а не для любовных рассказов и суровые айтишники, которым интересна тема Go, могут просто не обращать внимание на спойлеры «про Катю».

Итог 8 месяцев: приложение работает на Win (64/32), OSX(64/32), Linux(64/32), FreeBSD(64/32), Android, IOS (будет круто, если кто-то закинет в App Store).
Общего кода ~73к строк, кода под разные ОС где-то несколько сотен строчек.
40к — обработка/генерация блоков/тр-ий, 17.5к — контроллеры для интерфейса, 15.5к — шаблоны
Поддерживаются PostgreSQL, SQLite, MySQL.

Тех, кто будет тестировать мое творение, предупреждаю — могут быть баги, и если у Вас есть время, чиркните о них, пожалуйста, на darwin@dcoin.club или в личку на хабре. Пожелания и советы тоже приветствуются.

В первых двух частях я рассказал про то, как в dcoin функционирует веб-сервер и про html/template.

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

Про Катю

Всю ночь ни я, ни она не могли заснуть, просто молча лежали. В моей голове были мысли «нафиг её, другую найду, неадекватная она». О чем думала она, я не знаю.
Утром почему-то я снова почувствовал к ней влюбленность. Накормил завтраком и упаковал суп в контейнер, чтобы Катя на работе поела.

Базы данных

В dcoin я сделал поддержку sqlite, postgres, mysql. Для обычных десктопных приложений по умолчанию выбрана sqlite, т.к. не нужно ничего ставить дополнительно. Тем, кто будет поднимать свой dcoin-пул лучше выбирать между postgres и mysql.
За работу с SQL базами данных в golang отвечает пакет database/sql. К нему нужно подключать драйвер БД sqlite, postgresql, mysql.

Вначале я использовал только sqlite, затем решил подключить postgresql и mysql. Оказалось не очень сложно. Изменить пришлось только параметры для подключения к БД в sql.Open() и учесть различия в синтаксисе Sql-запросов
Тут моя реализация подключения к БД.
Обертки для работы с БД я не использовал, т.к. голые sql-запросы мне было удобнее переносить из старой версии Dcoin, по этой же причине в коде есть довольно убогие конструкции с преобразованием типов данных, полученных из БД.

Пара грабель, на которые я наступил:
1. Если забыть после db.Query(«sql») вызвать rows.Close(), то будет куча незакрытых коннектов.
2. defer нельзя вызывать до обработки ошибки, т.к. если будет ошибка, то row будет nil, а вызов rows.Close() приведет к панике. Вот так делать нельзя:

rows, err := db.Query("SELECT * FROM table") defer rows.Close() if err != nil { 	return err } 

Вот так верно:

rows, err := db.Query("SELECT * FROM table") if err != nil { 	return err } defer rows.Close() 

Про Катю

Дальше она снова много раз переносила встречу. Не знаю почему, но с каждым днем я всё сильнее и сильнее влюблялся в неё. Я хотел только её, думал только о ней. В какой-то момент поймал себя на мысли, что хочу на ней жениться. Случайно нашел стихи Кати на сайте stihi.ru, после одного стиха даже прослезился, там было про то, как с ней плохо обходились её бывшие парни. Я сходил с ума, это чувство, что я испытывал к ней было для меня странно и не понятно, у меня такого состояния еще никогда не было.

Json

type minersDataType struct { 	hosts    string          `json:"hosts"` } func main() { 	result_ := minersDataType{hosts: "pool.dcoin.club"} 	result, _ := json.Marshal(result_) } 

result будет содержать "{}"
Если заменить hosts на Hosts, то всё будет работать и в result появится {«hosts»:«111111»}

type minersDataType struct { 	Hosts    string          `json:"hosts"` } func main() { 	result_ := minersDataType{Hosts: "pool.dcoin.club"} 	result, _ := json.Marshal(result_) } 

Я с этим долго тупил, поэтому делаюсь инфой, чтобы кто-то не наступил на те же грабли

Про Катю

Написал Кате, что люблю её, хочу быть только с ней. Свидания она всё равно продолжала переносить. Надоело, купил её любимые цветы и поехал к ней, чтобы увидеться. Зачем-то даже выучил один из её стихов. Приехал, дома никого нет. Телефон не отвечает.

Сигналы

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

SigChan = make(chan os.Signal, 1) C.waitSig() // про это напишу ниже go func() { 	signal.Notify(SigChan, os.Interrupt, os.Kill, syscall.SIGTERM) 	// ждем, пока придет сигнал 	<-SigChan 	// сообщаем демонам, что надо срочно завершать работу 	for i := 0; i < countDaemons; i++ { 		daemons.DaemonCh <- true 		answer := <-daemons.AnswerDaemonCh 	} 	// демоны завершили работу, теперь можно закрыть соединение с БД 	DB.Close() }() 

C.waitSig() — это нужно для windows, т.к. сигналы в windows Go не видит.

/* #include <stdio.h> #include <signal.h>  extern void go_callback_int(); static inline void SigBreak_Handler(int n_signal){   go_callback_int(); } static inline void waitSig() {     #if (WIN32 || WIN64)     signal(SIGBREAK, &SigBreak_Handler);     signal(SIGINT, &SigBreak_Handler);     #endif } */ import ( 	"C" ) //export go_callback_int func go_callback_int() { 	SigChan <- syscall.Signal(1) } 

Если приходит SIGBREAK или SIGINT, то вызывается сишный SigBreak_Handler, который вызывает go_callback_int, который шлет в канал SigChan инфу, что был сигнал о завершении.
В Dcoin обработка сигналов реализована тут

Про Катю

Написал ей в ВК, сказала, что тел дома забыла, а сейчас у подруги. Я написал, что подожду её. После чего получил «Не звони и не пиши мне больше!!!!». Вопросов задавать не стал, позвонил в соседнюю квартиру, попросил передать цветы Кате, когда она будет дома. Приехал домой, через несколько часов принял решение переписать Dcoin на Go.

Шифрование

Мне понадобилось зашифровать при помощи AES ключ в Go, а расшифровать в JS. Мучился где-то 2 дня. Оказывается IV надо передавать вместе с самим зашифрованным текстом. У меня эта функция находится тут

func Encrypt(password, text []byte) ([]byte, error) { 	iv := []byte(RandSeq(aes.BlockSize)) 	c, err := aes.NewCipher(password) 	if err != nil { 		return nil, ErrInfo(err) 	} 	plaintext := PKCS5Padding([]byte(text), c.BlockSize()) 	cfbdec := cipher.NewCBCEncrypter(c, iv) 	EncPrivateKeyBin := make([]byte, len(plaintext)) 	cfbdec.CryptBlocks(EncPrivateKeyBin, plaintext) 	EncPrivateKeyBin = append(iv, EncPrivateKeyBin...) 	return EncPrivateKeyBin, nil } 

и расшифровка в JS:

            ivAndText = atob(text);             iv = ivAndText.substr(0, 16);             encText = ivAndText.substr(16);             cipherParams = CryptoJS.lib.CipherParams.create({                 ciphertext: CryptoJS.enc.Base64.parse(btoa(encText))             });              password = CryptoJS.enc.Latin1.parse(hex_md5(password));             var decrypted = CryptoJS.AES.decrypt(cipherParams, password, {mode: CryptoJS.mode.CBC, iv: CryptoJS.enc.Utf8.parse(iv), padding: CryptoJS.pad.Iso10126 });             var decryptedText = hex2a(decrypted.toString()); 

Расшифровку я делаю в worker-е, иначе из-за синхронности ненадолго вешается браузер.

Парсинг блоков

Непосредственно за разбор блоков и занесение данных в таблицы отвечает пакет dcparser
Рассмотрим например, транзакцию регистрации нового юзерского ключа, по сути нового Dcoin-пользователя — new_user.go
NewUserInit отвечает за заполнение переменных, которые содержат данные этой транзакции. Тут их всего две — public_key и sign
NewUserFront отвечает за проверку данных. В частности, проверяет, является пользователь, который сгенерировал эту транзакцию майнером. Проверяет, нет ли превышения лимитов и пр.
NewUser заносит данные в БД
NewUserRollbackFront откатывает изменения, которые были занесены методом NewUserFront
NewUserRollback откатывает данные, которые были занесены методом NewUser
Аналогичные 4 метода есть для каждого из 70-и типов транзакций.
На минуточку представьте как парсятся > 270 000 блоков. 70 методов заносят данные в 180 таблиц и 1100 разных колонок. А затем то, что меня завораживает больше всего — откат всех данных по одному блоку в обратном порядке через Rollback и RollbackFront. Такой полный откат делается для тестирования корректности работы dcparser. Если где-то как-то неверно занеслись данные, то это сразу станет видно.

Про Катю

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

Заключение

В следующих статьях я расскажу про то, как я, немного изменил gomobile, добавив уведомления и работу в фоне для IOS и Android приложений.

ссылка на оригинал статьи http://habrahabr.ru/post/274885/


Комментарии

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

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