Важные новости

Blockchain на Go

Blockchain на Go. Часть 4: Транзакции, часть 1

Привет, Habr! Представляю вашему вниманию перевод статьи «Building Blockchain in Go. Part 4: Transactions 1».

  1. Blockchain на Go. Часть 1: Прототип
  2. Blockchain на Go. Часть 2: Proof-of-Work
  3. Blockchain на Go. Часть 3: Постоянная память и интерфейс командной строки
  4. Blockchain на Go. Часть 4: Транзакции, часть 1
  5. Blockchain на Go. Часть 5: Адреса
  6. Blockchain на Go. Часть 6: Транзакции, часть 2
  7. Blockchain на Go. Часть 7: Сеть

Вступление

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

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

Ложки нет

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

  1. Нет аккаунтов.
  2. Нет балансов.
  3. Нет адресов.
  4. Нет монет.
  5. Нет отправителей и получателей.

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

Биткоин транзакция

Транзакция представляет собой комбинацию входов и выходов:

Входы новой транзакции ссылаются на выходы предыдущей транзакции (есть исключение, о котором мы поговорим ниже). Выходы — место, где хранятся монеты. Следующая диаграмма демонстрирует взаимосвязь транзакций:

  1. Есть выходы, которые не связаны с входами.
  2. В одной транзакции входы могут ссылаться на выходы нескольких транзакций.
  3. Вход всегда должен ссылаться на выход.

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

Выходы транзакций

Начнем с выходов:

Фактически, это выходы, которые хранят «монеты» (обратите внимание на поле Value выше). Средства блокируются особым пазлом, которая хранится в ScriptPubKey . Внутри Bitcoin использует скриптовый язык Script , который используется для определения логики блокировки и разблокировки выходов. Язык довольно примитивен (это делается намеренно, чтобы избежать возможных взломов), но мы не будем обсуждать его подробно. Вы можете прочитать подробнее о нем здесь.

Одна важная вещь которую нужно знать о выходах, это то, что они неделимы, а это означает, что вы не можете ссылаться на часть своего значения. Когда выход ссылается на новую транзакцию, он расходуется полностью. И если его значение больше, чем требуется, генерируется разница и новое значение отправляется обратно отправителю. Это похоже на реальную ситуацию в мире, когда вы платите, скажем, $5 долларов за то, что стоит $1, и получаете сдачу $4.

Входы транзакций

Как упоминалось ранее, вход ссылается на предыдущий выход: Txid хранит идентификатор такой транзакции, а Vout хранит индекс выхода данной транзакции. ScriptSig — это скрипт, который предоставляет данные, которые будут в дальнейшем использоваться в скрипте ScriptPubKey . Если данные верны, выход можно разблокировать, а его значение можно использовать для генерации новых выходов; если же это не так, вход не может ссылаться на выход. Этот механизм гарантирует, что пользователи не могут тратить монеты, принадлежащие другим людям.

Опять же, поскольку у нас все еще нет адресов, ScriptSig сохранит только произвольный пользовательский адрес кошелька. Мы создадим открытый ключ и проверку подписи в следующей статье.

Подведем итог. Выходы — это место, в котором хранятся «монеты». Каждый выход имеет сценарий разблокировки, который определяет логику разблокировки выхода. Каждая новая транзакция должна иметь как минимум один вход и выход. Вход ссылается на результат предыдущей транзакции и предоставляет данные (поле ScriptSig ), которые используются в сценарии разблокировки выхода, чтобы разблокировать его и использовать его значение для создания новых выходов.

Но что пришло первым: входы или выходы?

В Биткоине, яйцо появилось до цыпленка. Логика inputs-referencing-outputs представляет собой классическую ситуацию «курица или яйцо»: входы производят выходы, а выходы позволяют создавать входы. И в Биткоине выходы всегда появляются перед входами.

Когда майнер начинает майнить блок, он добавляет к нему coinbase транзакцию. Транзакция coinbase — это особый тип транзакции, который не требует ранее существующих выходов. Он создает выходы (т. е. «Монеты») из ниоткуда. Яйцо без курицы. Это награда, которую майнеры получают за добычу новых блоков.

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

Давайте создадим coinbase транзакцию:

В coinbase транзакции имеется только один вход. В нашей реализации Txid пуст, а Vout равен -1. Кроме того, транзакция coinbase не хранит скрипт в ScriptSig . Вместо этого там хранятся произвольные данные.

subsidy — это сумма вознаграждения. В Биткоине это число не хранится нигде и рассчитывается только на основе общего количества блоков: количество блоков делится на 210000. Майнинг блока генезиса приносит 50 BTC, и каждые 210000 блоков награда уменьшается вдвое. В нашей реализации мы будем хранить вознаграждение как константу (по крайней мере на данный момент).

Сохранение транзакций в цепи

С этого момента каждый блок должен хранить как минимум одну транзакцию, и должно стать невозможным майнить блоки без транзакции. Теперь, удалим поле date из Block и вместо этого теперь будем хранить транзакции.

NewBlock и NewGenesisBlock также должны быть соответствующим образом изменены

Теперь создадим функцию CreateBlockchain

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

Proof-of-Work

Алгоритм Proof-of-Work должен рассматривать транзакции, хранящиеся в блоке, чтобы гарантировать согласованность и надежность цепи как хранилища транзакции. Итак, теперь мы должны изменить метод ProofOfWork.prepareData :

Вместо pow.block.Data теперь мы добавим pow.block.HashTransactions () :

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

Проверим правильность работы:

Отлично! Мы получили свою первую награду. Но как нам проверить баланс?

Непотраченные выходы

Нам нужно найти все непотраченные выходы (UTXO). Это означает, что эти выходы не ссылались ни на какие входы. На приведенной выше диаграмме это:

  1. tx0, output 1;
  2. tx1, output 0;
  3. tx3, output 0;
  4. tx4, output 0.

Конечно, когда мы проверяем баланс, нам не нужны они все, нужны только те, которые могут быть разблокированы ключом, которым мы владеем (в настоящее время у нас нет реализованных ключей и вместо них будут использоваться пользовательские адреса). Для начала, давайте определим методы блокировки-разблокировки на входах и выходах:

Здесь мы просто сравниваем поля из ScriptSig с unlockingData . В следующей статье мы их улучшим, после того, как реализуем адреса, основанные на закрытых ключах.

Следующий шаг — поиск транзакций с непотраченными выходами — это уже более сложно:

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

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

Мы пропускаем те, на которые уже ссылаются входы (их значения были перенесены на другие выходы, поэтому мы не можем их подсчитать). После проверки выходов мы собираем все входы, которые могут разблокировать выходы, заблокированные с предоставленным адресом (это не относится к coinbase транзакциям, поскольку они не разблокируют выходы):

Функция возвращает список транзакций, содержащих непотраченные выходы. Для расчета баланса нам нужна еще одна функция, которая берет транзакции и возвращает только выходы:

Готово! Теперь реализуем команду getbalance

Баланс счета — это сумма значений всех непотраченных выходов, заблокированных адресом учетной записи.

Давайте проверим наш баланс после добычи блока генезиса:

Это наши первые монеты!

Отправка монет

Теперь мы хотим отправить несколько монеты кому-то другому. Для этого нам нужно создать новую транзакцию, поместить ее в блок и обработать его. До сих пор мы реализовали только транзакцию coinbase (которая является специальным типом транзакций), теперь нам нужна общая транзакция:

Прежде чем создавать новые выходы, нам для начала нужно найти все непотраченные выходы и убедиться, что в них есть достаточное количество монет. Это метод FindSpendableOutputs . После этого для каждого найденного выхода создается вход, ссылающийся на него. Затем мы создаем два выхода:

  1. Тот, который заблокирован с адресом получателя. Это фактическая передача монет на другой адрес.
  2. Тот, который заблокирован с адресом отправителя. Это разница. Он создается только тогда, когда непотраченные выходы имеют большее значение, чем требуется для новой транзакции. Помните: выходы неделимы.

Метод FindSpendableOutputs основан на методе FindUnspentTransactions , который мы определили ранее:

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

Теперь мы можем изменить метод Blockchain.MineBlock :

Наконец, создадим команду send :

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

Давайте проверим, как работает отправка монет:

Отлично! Теперь давайте создадим больше транзакций и убедимся, что отправка с нескольких выходов работает правильно:

Теперь монеты Хелен заблокированы на двух выходах: один выход от Педро и один от Ивана. Отправим кому-нибудь еще:

Выглядит хорошо! Теперь давайте протестируем исключения:

Заключение

Уф! Это было нелегко, но теперь у нас есть транзакции! Хотя, некоторые ключевые особенности Биткоин-подобной криптовалюты отсутствуют:

  1. Адреса. Пока у нас нет адресов на основе приватного ключа.
  2. Награды. Майнить блоки абсолютно невыгодно!
  3. UTXO. Получение баланса требует сканирования всей цепочки блоков, что может занять очень много времени, когда есть очень много блоков. Кроме того, это займет очень много времени, если мы захотим подтвердить последующие транзакции. UTXO предназначен для решения этих проблем и для быстрой работы с транзакциями.
  4. Mempool. Здесь хранятся транзакции, прежде чем они будут упакованы в блок. В нашей текущей реализации блок содержит только одну транзакцию, и это очень неэффективно.

https://habr.com/ru/post/351752/

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

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