Кто про что, а я про телеграм бота…
Сейчас я работаю в компании Каруна на позиции старшего Go-разработчика. В свободное от работы время стараюсь смотреть по сторонам (нет — не в поиске работы, и да — это корпоративный блог, но пишу про пет-проект 🙂) и интересоваться разными областями IT, абсолютно отличными от того, чем ежедневно занимаюсь на работе.
Примерно полтора года назад я в качестве хобби занимался разработкой универсального телеграм бота для MQTT устройств, о чем уже рассказывал вот тут: (Не)очередной MQTT-телеграм-бот для IoT, а позже мой фокус внимания отошёл от темы IoT и сместился в сторону криптовалют, очень уж эта тема не давала мне покоя. На фоне прошлогоднего шума вокруг Chia захотелось вложить немного свободных средств в другой заинтересовавший меня альткоин и сделать что-нибудь полезное для комьюнити. В этой статье я делюсь исключительно техническими деталями реализации бота и намеренно опускаю любую маркетинговую информацию о блокчейне, дабы не разводить холивар про альткоины. И вас очень попрошу воздержаться!
Итак, задача:
На этом вроде бы и всё, поехали…
Исходный код того, что получилось лежит здесь: github.com/xDWart/signum-explorer-bot
Сам бот работает по адресу @signum_explorer_bot
Язык: Go
База данных: Postgres
Котировки: CoinMarketCap API
Хостинг: был Heroku, но в связи с недавними событиями они ушли из России. Теперь, здравствуй, RUVDS (не реклама).
Теперь немного расскажу детали по фичам.
Блокчейн Signum имеет распределённую сеть нод с публично доступным API (пример: europe.signum.network/api-doc). Все ноды синхронизируются между собой в пределах одного блока (4 минуты). Так что изначально, когда я ещё запускал бота на платформе Heroku, в основу клиента API я заложил рандомный опрос 8 официальных нод блокчейна и их искусственную балансировку: бывает такое, что нода по какой-либо причине может не отвечать или запаздывать с синхронизацией. Я реализовал периодическое ранжирование адресов по времени ответа и текущему блоку ноды, таким образом, в опросе участвовала только лучшая половина:
С переходом на свой VPS я просто поднял локально свою ноду, дал ей приоритет среди других и сократил интервал опроса, чтобы оповещения о транзакциях срабатывали быстрее. Для отслеживания баланса аккаунта достаточно раз в 4 минуты (интервал между блоками) запрашивать информацию об этом аккаунте у любой из нод. Если бот видит новую транзакцию, то пользователю будет отправлена нотификация. Такие нотификации позволяют удобно отслеживать изменение баланса при поступлении выплаты с пула при майнинге или любом другом входящем платеже. Например, в комьюнити есть лотерея, несколько криптоигр и прочих несерьёзных развлекух на смарт контрактах.
Тут я использовал такой же подход, как и в предыдущем своем боте, а именно base64 строка с закодированной протобаф структурой:
Это позволило удобно реализовать обратную связь от пользователя при его навигации по инлайн-меню аккаунта и взаимодействии с переключателями.
График рисуется средствами библиотеки github.com/wcharczuk/go-chart прямо на лету и отправляется пользователю в виде обычного изображения. Сами сэмплы котировок я беру с сайта coinmarketcap.com, у них есть бесплатное API с ограничением на количество запросов в сутки.
Изначально, с целью экономии места в базе данных (т.е на платформе Heroku бесплатно можно использовать только до 10 тысяч строк) я сделал механизм «прореживания» и усреднения старых данных по котировкам, из-за этого график на большом периоде выглядит грубым в начале и более детальным в конце:
Алгоритм по сути костыльно-простой: есть некая линейная функция k*x + b, определяющая минимальный интервал между значениями, и чем сэмпл дальше от текущего момента, тем больше значений он будет усреднять:
Сейчас в этом уже нет необходимости, но я решил так и оставить.
В Signum используется алгоритм консенсуса Proof of Commitment (PoC+) — это смесь классических Proof of Capacity (PoC) и Proof of Stake (PoS), почитать про который можно в статье от одного из нынешних разработчиков: Proof of Commitment (PoC+): a Proof of Capacity Upgrade. Таким образом, заработок при майнинге зависит не только от предоставленного физического объёма жестких дисков, но и от “коммитмента” — объёма замороженных на счету монет.
В калькуляторе для расчётов используются усреднённые значения сложности сети и среднего коммитмента, чтобы нивелировать пилу в течение дня (см. ниже график статистики сети).
Дополнительно в калькуляторе я решил сделать расчёт сразу для всего возможного диапазона коммитмента, чтобы человек видел свою потенциальную доходность от тех или иных вложений. Самое выгодное, что логично — держать коммитмент около среднего по сети. При превышении среднего доходность возрастает уже не так значительно, и это не имеет особого смысла. Разве что монеты деть некуда, а дисков добавить нет возможности.
Также в калькулятор добавлена функция реинвестиционного расчёта, т.е все намайненные монеты вкладываются в свой же коммитмент, понемногу увеличивая коэффициент. Результат выглядит примерно так:
Исходя из механики PoC+, у меня родилась идея проекта по объединению майнеров и инвесторов и получения взаимной выгоды, но об этом уже в следующей истории.
Со статистикой сети всё просто — данные берутся из API ноды раз в 4 минуты, складываются в базу, и строится такой же график, как у цены:
Усреднение более старых значений работает по аналогичному с ценой принципу, чтобы экономить на базе данных. Сложность сети и средний коммитмент напрямую влияют на доходность майнинга. Почему показания так сильно скачут даже в течение дня — мне не понятно, не интересовался этим вопросом.
Сумма, которую можно получить с крана, не представляет какой-либо финансовой ценности (сейчас это 1 Signa за регистрацию в боте и 0.05 Signa еженедельно, что по текущему курсу около $0.004 и $0.0002 соответственно), но может быть полезна для активации аккаунта и подключении к пулу, т.к для подключения к пулу нужно заплатить комиссию за транзакцию в размере минимум 0.00735 Signa (после предстоящего хардфорка минимальная комиссия будет зафиксирована на уровне 0.01 Signa для кратности расчётов).
Но даже такой мизерный кран начали доить 🤦. Следовательно пришлось добавить различные ограничения на получение монет с одного телеграм аккаунта. Одно из них — дёргать кран не чаще одного раза в неделю:
Бот делался в большей степени для себя, т.к я люблю телеграм и хочу иметь удобный инструмент под рукой. Также мой бот не единственный — параллельно другой человек разрабатывает ещё одного бота @Signa_Russia_bot, который имеет схожий функционал и местами даже больший (статистика пулов, оповещения о пропуске блоков при майнинге).
В следующей статье расскажу про попытку сделать проект для объединения майнеров с инвесторами, чтобы люди с жёсткими дисками могли объединяться с людьми с монетами и получать взаимную выгоду.
To be continued...
Сейчас я работаю в компании Каруна на позиции старшего Go-разработчика. В свободное от работы время стараюсь смотреть по сторонам (нет — не в поиске работы, и да — это корпоративный блог, но пишу про пет-проект 🙂) и интересоваться разными областями IT, абсолютно отличными от того, чем ежедневно занимаюсь на работе.
Примерно полтора года назад я в качестве хобби занимался разработкой универсального телеграм бота для MQTT устройств, о чем уже рассказывал вот тут: (Не)очередной MQTT-телеграм-бот для IoT, а позже мой фокус внимания отошёл от темы IoT и сместился в сторону криптовалют, очень уж эта тема не давала мне покоя. На фоне прошлогоднего шума вокруг Chia захотелось вложить немного свободных средств в другой заинтересовавший меня альткоин и сделать что-нибудь полезное для комьюнити. В этой статье я делюсь исключительно техническими деталями реализации бота и намеренно опускаю любую маркетинговую информацию о блокчейне, дабы не разводить холивар про альткоины. И вас очень попрошу воздержаться!
Итак, задача:
- Иметь минимальный функционал эксплорера блокчейна прямо в телеграме: просматривать транзакции и статистику сети.
- Удобно отслеживать баланс нескольких кошельков и получать уведомления о поступлениях/списаниях с кошелька.
- Получать актуальную цену + график.
- Иметь калькулятор доходности майнинга.
- Иметь кран для активации новых кошельков.
На этом вроде бы и всё, поехали…
Исходный код того, что получилось лежит здесь: github.com/xDWart/signum-explorer-bot
Сам бот работает по адресу @signum_explorer_bot
Используемый стек технологий:
Язык: Go
База данных: Postgres
Котировки: CoinMarketCap API
Хостинг: был Heroku, но в связи с недавними событиями они ушли из России. Теперь, здравствуй, RUVDS (не реклама).
Теперь немного расскажу детали по фичам.
Отслеживание баланса + оповещение о транзакциях
Блокчейн Signum имеет распределённую сеть нод с публично доступным API (пример: europe.signum.network/api-doc). Все ноды синхронизируются между собой в пределах одного блока (4 минуты). Так что изначально, когда я ещё запускал бота на платформе Heroku, в основу клиента API я заложил рандомный опрос 8 официальных нод блокчейна и их искусственную балансировку: бывает такое, что нода по какой-либо причине может не отвечать или запаздывать с синхронизацией. Я реализовал периодическое ранжирование адресов по времени ответа и текущему блоку ноды, таким образом, в опросе участвовала только лучшая половина:
// отсортируем API клиентов нод по пингу и текущему блоку
func (c *SignumApiClient) upbuildApiClients() {
clients := make([]*apiClient, 0, len(apiHosts))
for _, host := range apiHosts {
client, err := doRequestForHost(logger, host)
if err != nil {
continue
}
clients = append(clients, client)
}
sort.Slice(clients, func(i, j int) bool {
// allow out of sync in 1 block
if clients[i].blockchainStatus.NumberOfBlocks-1 > clients[j].blockchainStatus.NumberOfBlocks {
return true
}
if clients[i].blockchainStatus.NumberOfBlocks < clients[j].blockchainStatus.NumberOfBlocks-1 {
return false
}
return clients[i].latency < clients[j].latency
})
}
// на каждый запрос перемешиваем первую половину, чтобы сначала запрос шел одной из лучших нод, а уже потом, в случае неудачи, всем остальным
func (c *SignumApiClient) doJsonReq() {
rand.Shuffle(len(apiClients)/2, func(i, j int) {
apiClients[i+offset], apiClients[j+offset] = apiClients[j+offset], apiClients[i+offset]
})
for _, apiClient := range apiClients {
body, err = apiClient.DoJsonReq()
if err != nil {
continue
}
// success, process body
}
}
С переходом на свой VPS я просто поднял локально свою ноду, дал ей приоритет среди других и сократил интервал опроса, чтобы оповещения о транзакциях срабатывали быстрее. Для отслеживания баланса аккаунта достаточно раз в 4 минуты (интервал между блоками) запрашивать информацию об этом аккаунте у любой из нод. Если бот видит новую транзакцию, то пользователю будет отправлена нотификация. Такие нотификации позволяют удобно отслеживать изменение баланса при поступлении выплаты с пула при майнинге или любом другом входящем платеже. Например, в комьюнити есть лотерея, несколько криптоигр и прочих несерьёзных развлекух на смарт контрактах.
Inline клавиатура
Тут я использовал такой же подход, как и в предыдущем своем боте, а именно base64 строка с закодированной протобаф структурой:
type QueryDataType struct {
MessageId int64 // id телеграм сообщения
Account string // Signum аккаунт, с которым производятся действия
Keyboard KeyboardType // тип клавиатуры (аккаунт, график цены, калькулятор и т.п)
Action ActionType // действие
}
func (m QueryDataType) GetBase64ProtoString() string {
bytes, _ := proto.Marshal(&m)
base64str := base64.StdEncoding.EncodeToString(bytes)
return base64str
}
Это позволило удобно реализовать обратную связь от пользователя при его навигации по инлайн-меню аккаунта и взаимодействии с переключателями.
Актуальная цена + график
График рисуется средствами библиотеки github.com/wcharczuk/go-chart прямо на лету и отправляется пользователю в виде обычного изображения. Сами сэмплы котировок я беру с сайта coinmarketcap.com, у них есть бесплатное API с ограничением на количество запросов в сутки.
Изначально, с целью экономии места в базе данных (т.е на платформе Heroku бесплатно можно использовать только до 10 тысяч строк) я сделал механизм «прореживания» и усреднения старых данных по котировкам, из-за этого график на большом периоде выглядит грубым в начале и более детальным в конце:
Алгоритм по сути костыльно-простой: есть некая линейная функция k*x + b, определяющая минимальный интервал между значениями, и чем сэмпл дальше от текущего момента, тем больше значений он будет усреднять:
for i := 1; i < len(scannedPrices); i += 2 {
price0 := scannedPrices[i-1]
price1 := scannedPrices[i]
X := time.Since(price0.CreatedAt) / time.Hour / 24
delayM := pm.config.DelayFuncK*X + pm.config.DelayFuncB
if price1.CreatedAt.Sub(price0.CreatedAt) < delayM {
price0.SignaPrice = (price0.SignaPrice + price1.SignaPrice) / 2
price0.BtcPrice = (price0.BtcPrice + price1.BtcPrice) / 2
pm.db.Save(price0)
pm.db.Unscoped().Delete(price1)
}
}
Сейчас в этом уже нет необходимости, но я решил так и оставить.
Калькулятор доходности майнинга
Очень настоятельно прошу не поднимать тему заработка на майнинге — т.к на данном этапе майнинг конкретно Signum убыточен, и на фоне других блокчейнов здесь даже обсуждать нечего. Хотите зарабатывать — вам в PoW и иже с ними, не нужно тут холивара, все делается на чистом энтузиазме и получения опыта для!
В Signum используется алгоритм консенсуса Proof of Commitment (PoC+) — это смесь классических Proof of Capacity (PoC) и Proof of Stake (PoS), почитать про который можно в статье от одного из нынешних разработчиков: Proof of Commitment (PoC+): a Proof of Capacity Upgrade. Таким образом, заработок при майнинге зависит не только от предоставленного физического объёма жестких дисков, но и от “коммитмента” — объёма замороженных на счету монет.
func Calculate(miningInfo *signumapi.MiningInfo, tib float64, commit float64) *CalcResult {
var calcResult = CalcResult{
TiB: tib,
Commitment: commit,
MyCommitmentPerTiB: commit / tib,
}
e := calcResult.MyCommitmentPerTiB / miningInfo.AverageCommitment
n := math.Pow(e, p)
n = math.Min(8, n)
n = math.Max(.125, n)
calcResult.CapacityMultiplier = n
calcResult.EffectiveCapacity = calcResult.CapacityMultiplier * calcResult.TiB
calcResult.MyDaily = 360 / miningInfo.AverageNetworkDifficulty * float64(miningInfo.LastBlockReward) * calcResult.EffectiveCapacity
calcResult.MyMonthly = calcResult.MyDaily * 30.4
calcResult.MyYearly = calcResult.MyMonthly * 12
return &calcResult
}
В калькуляторе для расчётов используются усреднённые значения сложности сети и среднего коммитмента, чтобы нивелировать пилу в течение дня (см. ниже график статистики сети).
Дополнительно в калькуляторе я решил сделать расчёт сразу для всего возможного диапазона коммитмента, чтобы человек видел свою потенциальную доходность от тех или иных вложений. Самое выгодное, что логично — держать коммитмент около среднего по сети. При превышении среднего доходность возрастает уже не так значительно, и это не имеет особого смысла. Разве что монеты деть некуда, а дисков добавить нет возможности.
Также в калькулятор добавлена функция реинвестиционного расчёта, т.е все намайненные монеты вкладываются в свой же коммитмент, понемногу увеличивая коэффициент. Результат выглядит примерно так:
Исходя из механики PoC+, у меня родилась идея проекта по объединению майнеров и инвесторов и получения взаимной выгоды, но об этом уже в следующей истории.
Статистика сети + график
Со статистикой сети всё просто — данные берутся из API ноды раз в 4 минуты, складываются в базу, и строится такой же график, как у цены:
Усреднение более старых значений работает по аналогичному с ценой принципу, чтобы экономить на базе данных. Сложность сети и средний коммитмент напрямую влияют на доходность майнинга. Почему показания так сильно скачут даже в течение дня — мне не понятно, не интересовался этим вопросом.
Кран для получения копеек для активации аккаунта
Сумма, которую можно получить с крана, не представляет какой-либо финансовой ценности (сейчас это 1 Signa за регистрацию в боте и 0.05 Signa еженедельно, что по текущему курсу около $0.004 и $0.0002 соответственно), но может быть полезна для активации аккаунта и подключении к пулу, т.к для подключения к пулу нужно заплатить комиссию за транзакцию в размере минимум 0.00735 Signa (после предстоящего хардфорка минимальная комиссия будет зафиксирована на уровне 0.01 Signa для кратности расчётов).
Но даже такой мизерный кран начали доить 🤦. Следовательно пришлось добавить различные ограничения на получение монет с одного телеграм аккаунта. Одно из них — дёргать кран не чаще одного раза в неделю:
var accountFaucet models.Faucet
err := user.db.
Where("account = ? OR account_rs = ?", account, account).
Where("amount = ?", amount).
Last(&accountFaucet).Error
if err == nil && time.Since(accountFaucet.CreatedAt) < 24*time.Hour*time.Duration(config.FAUCET_DAYS_PERIOD) {
return false, fmt.Sprintf("🚫 Sorry, you have used the faucet less than %v days ago!", config.FAUCET_DAYS_PERIOD)
}
Заключение
Бот делался в большей степени для себя, т.к я люблю телеграм и хочу иметь удобный инструмент под рукой. Также мой бот не единственный — параллельно другой человек разрабатывает ещё одного бота @Signa_Russia_bot, который имеет схожий функционал и местами даже больший (статистика пулов, оповещения о пропуске блоков при майнинге).
В следующей статье расскажу про попытку сделать проект для объединения майнеров с инвесторами, чтобы люди с жёсткими дисками могли объединяться с людьми с монетами и получать взаимную выгоду.
To be continued...