Как стать автором
Обновить
68.45

Signum Explorer Telegram Bot — разработка open-source pet-project телеграм бота для блокчейна Signum

Время на прочтение7 мин
Количество просмотров4.5K
Кто про что, а я про телеграм бота…

Сейчас я работаю в компании Каруна на позиции старшего Go-разработчика. В свободное от работы время стараюсь смотреть по сторонам (нет — не в поиске работы, и да — это корпоративный блог, но пишу про пет-проект 🙂) и интересоваться разными областями IT, абсолютно отличными от того, чем ежедневно занимаюсь на работе.

Примерно полтора года назад я в качестве хобби занимался разработкой универсального телеграм бота для MQTT устройств, о чем уже рассказывал вот тут: (Не)очередной MQTT-телеграм-бот для IoT, а позже мой фокус внимания отошёл от темы IoT и сместился в сторону криптовалют, очень уж эта тема не давала мне покоя. На фоне прошлогоднего шума вокруг Chia захотелось вложить немного свободных средств в другой заинтересовавший меня альткоин и сделать что-нибудь полезное для комьюнити. В этой статье я делюсь исключительно техническими деталями реализации бота и намеренно опускаю любую маркетинговую информацию о блокчейне, дабы не разводить холивар про альткоины. И вас очень попрошу воздержаться!

Итак, задача:
  1. Иметь минимальный функционал эксплорера блокчейна прямо в телеграме: просматривать транзакции и статистику сети.
  2. Удобно отслеживать баланс нескольких кошельков и получать уведомления о поступлениях/списаниях с кошелька.
  3. Получать актуальную цену + график.
  4. Иметь калькулятор доходности майнинга.
  5. Иметь кран для активации новых кошельков.

На этом вроде бы и всё, поехали…

Исходный код того, что получилось лежит здесь: 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...
Теги:
Хабы:
Всего голосов 15: ↑14 и ↓1+13
Комментарии3

Публикации

Информация

Сайт
karuna.group
Дата регистрации
Дата основания
Численность
201–500 человек
Местоположение
Россия