Как с нуля построить свою блокчейн сеть
Блокчейн — децентрализованная база данных, хранящая информацию о всех операциях в виде цепи блоков. Особенностью сети является то, что записи находятся не на одном сервере, а на сотнях, из-за чего незаметно подделать их или удалить невозможно. Блокчейн — надежная и безопасная технология, которую можно использовать для обмена данными, деньгами и даже документами.
Какие задачи решает блокчейн
Технологию активно внедряют, потому что она решает несколько задач:
защита конфиденциальности — если банки и платежные системы знают сумму сделки, как зовут отправителей и получателей, то блокчейн сохраняет анонимность. Это обусловлено тем, что для проведения операции нужно знать только публичные и приватные ключи кошельков;
обеспечение безопасности — данные сохраняются на всех блоках в цепи, поэтому полностью удалить или изменить какую-либо запись в системе невозможно;
бесперебойная работа — сеть функционирует благодаря группе участников, занимающихся майнингом. Если центральный сервер из-за технических работ или проблем с электричеством может временно выйти из строя, то блокчейн будет работать всегда. А при увеличении нагрузки система способна масштабироваться.
Кроме того, блокчейн гарантирует прозрачность сделок благодаря смарт-контрактам. Это программный код, в котором зафиксированы условия, например, если пользователь А отправит 100 монет Б, то Б передаст А NFT-картинку. Он позволяет отказаться от лишних посредников, взимающих комиссии. Смарт-контракт невозможно изменить, а операция выполняется автоматически.
Как самостоятельно сделать блокчейн
Чтобы разработать собственный блокчейн, потребуется:
Python 3.6 или новее;
библиотека Flask;
HTTP-клиент (Postman, cURL).
Пишем скелет
Начать работу можно в любом совместимом с Python редакторе, т. е. Integrated Development Environment, например: PyCharm, PyDev, WingWare, Komodo IDE. Создадим файл под названием «Blockchain», чтобы потом не потерять код.
Сначала необходимо прописать класс блокчейна, конструктор которого сделает 2 пустых листа для хранения исходного кода и информации о транзакциях.
Пример чертежа:
class Blockchain(object):
def __init__(self):
self.chain = []
self.current_transactions = []
def new_block(self):
pass
def new_transaction(self):
pass
@staticmethod
def hash(block):
pass
@property
def last_block(self):
pass
Этот класс блокчейна управляет цепью, то есть он будет хранить информацию и иметь несколько методов внесения новых блоков в систему.
Создаем первый блок
Первый блок в цепи называют Genesis block. В нем хранится информация, являющаяся фундаментом всех последующих элементов системы. Генезис необходим для связи блоков между собой и проверки транзакций, так как перевод не будет завершен, пока майнер не сравнит данные в новом блоке с предыдущими.
Важно уделить особое внимание алгоритмам, заложенным в него, чтобы запущенная система работала без сбоев, а пользователи не нашли уязвимости, позволяющие украсть активы других участников.
Чтобы разработать первый блок, нужно запустить следующий программный код:
def create_first_block():
block_data = {}
block_data['index'] = 0
block_data['timestamp'] = date.datetime.now()
block_data['data'] = 'First block data'
block_data['prev_hash'] = None
block = Block(block_data)
return block
Теперь стоит добавить функцию, позволяющую сохранять информацию о транзакциях на локальном диске. Она сделает систему безопаснее, т. к. в случае отключения узла данные не потеряются.
Назовем новую папку «blockchaindata» и допишем программный код:
chaindata_dir = 'chaindata'
if not os.path.exists(blockchaindata_dir):
os.mkdir(blockchaindata_dir)
if os.listdir(blockchaindata_dir) == []:
first_block = create_first_block()
first_block.self_save()
Теперь у каждого блока есть отдельный файл, названный по его уникальному идентификатору. Стоит проверить, содержат ли имена файлов несколько нулей в начале, чтобы они отображались строго по порядку и не перемешались при первой нагрузке на сеть.
Синхронизируем блокчейн
Перед тем как майнить, интерпретировать данные или создавать новые блоки, нужно локально синхронизировать первый узел. Других элементов в системе еще нет, поэтому на нынешнем этапе достаточно разобраться с чтением блоков из локальных файлов, хранящихся на диске.
В будущем синхронизация будет подразумевать не столько прочтение файлов, сколько коммуникацию с пирами для сборки блоков, которые сгенерировались еще до запуска узла.
Программный код:
def sync():
node_blocks = []
chaindata_dir = 'chaindata'
if os.path.exists(blocchaindata_dir):
for filename in os.listdir(blockchaindata_dir):
if filename.endswith('.json'):
filepath = '%s/%s' % (blockchaindata_dir, filename)
with open(filepath, 'r') as block_file:
block_info = json.load(block_file)
block_object = Block(block_info)
node_blocks.append(block_object)
return node_blocks
Стоит сказать, что это примитивный, но рабочий код, так как чтение и загрузка файлов в структуры не требуют сложных решений.
Прописываем отображение через браузер
Пока блокчейн просто находится в памяти, с ним проблематично работать, поэтому цепочку стоит отобразить в браузере. Веб-обозреватель можно использовать для просмотра и совершения операций, таких как отправка цифровых активов или контроль криптокошелька.
Для создания веб-приложения стоит воспользоваться фреймворком Flask. Это легкий и понятный софт, имеющий все необходимые функции для работы. Flask отличается тем, что позволяет быстро разработать приложение, используя только один файл Python. Также подойдут Django, CherryPy, Pyramid, TurboGears.
Код для отображения блокчейна:
node = Flask(__name__)
node_blocks = sync.sync() #inital blocks that are synced
@node.route('/blockchain.json', methods=['GET'])
def blockchain():
node_blocks = sync.sync() #regrab the nodes if they've changed
python_blocks = []
for block in node_blocks:
python_blocks.append(block.__dict__())
json_blocks = json.dumps(python_blocks)
return json_blocks
if __name__ == '__main__':
node.run()
Проигнорируем импорты для экономии места.
Теперь вы можете посмотреть имеющийся блок, запустив программный код и перейдя в localhost:3000/blockchain.json
.
Создание цепочки блоков
Сейчас к сети подключен только genesis block, имея который уже можно работать с блокчейном, но с обработкой большого объема данных он не справится. Рекомендуется еще до запуска проекта продумать, как будут создаваться новые элементы цепи и как они будут соединяться друг с другом.
Сатоши Накамото, создавший Bitcoin, придумал, какой должна быть цепочка в блокчейне: timestamp-сервер (узел) хэширует блок данных и показывает, что информация существовала и потому сохранилась. Каждый хэш состоит не только из новых данных, но и предыдущих. Таким образом, образуется цепь, в которой новые звенья укрепляют предыдущие.
Благодаря связи всех блоков система может проверить корректность нового, определив все хэши и подтвердив порядок. В нашем случае информация нового и предыдущего блоков будет объединяться в одну огромную строчку, в которую включены:
порядковый номер;
хэш предшественника;
данные;
timestamp майнинга.
Для этого нужно прописать команду:
def generate_header(index, prev_hash, data, timestamp):
return str(index) + prev_hash + data + str(timestamp)
Объединять строки необязательно, поэтому программист может выбрать другой вариант для создания хедера блока. Главное требование — прозрачность генерации хедера и хэша. Это необходимо для того, чтобы каждый пользователь сети мог проверить корректность блока и определить связь между другими элементами сети.
Для вычисления валидности хэша будем использовать метод, немного отличающийся от использованного Сатоши Накамото, но запустим хедер посредством функции sha256.
Программный код:
def calculate_hash(index, prev_hash, data, timestamp, nonce):
header_string = generate_header(index, prev_hash, data, timestamp, nonce)
sha = hashlib.sha256()
sha.update(header_string)
return sha.hexdigest()
Для майнинга новых блоков можно внедрить вышеуказанную функцию. Она поможет получить хэш, положить его в новый блок и сохранить последний в директории «blockchaindata».
Код:
node_blocks = sync.sync()
def mine(last_block):
index = int(last_block.index) + 1
timestamp = date.datetime.now()
data = "I block #%s" % (int(last_block.index) + 1) #random string for now, not transactions
prev_hash = last_block.hash
block_hash = calculate_hash(index, prev_hash, data, timestamp)
block_data = {}
block_data['index'] = int(last_block.index) + 1
block_data['timestamp'] = date.datetime.now()
block_data['data'] = "I block #%s" % last_block.index
block_data['prev_hash'] = last_block.hash
block_data['hash'] = block_hash
return Block(block_data)
def save_block(block):
chaindata_dir = 'chaindata'
filename = '%s/%s.json' % (chaindata_dir, block.index)
with open(filename, 'w') as block_file:
print new_block.__dict__()
json.dump(block.__dict__(), block_file)
if __name__ == '__main__':
last_block = node_blocks[-1]
new_block = mine(last_block)
save_block(new_block)
Теперь майнеры могут подключаться к memorypool и обрабатывать информацию. Устройства узнают о транзакции, изучат ее, запишут в блок, посчитают хэш, за что их владельцы будут получать вознаграждение. Однако при таком типе блокчейна компьютеры с самым мощным CPU будут создавать длинные цепи, которые другие узлы определяют как корректные.
Настройка алгоритма подтверждения
Для подтверждения действительности переводов, зафиксированных в блокчейне, разработчики используют несколько механизмов консенсуса. Наиболее популярными являются:
Proof of Work (PoW) — подтверждение работы;
Proof of Stake (PoS) — доказательство доли владения.
Первый вариант считают более безопасным с точки зрения уязвимости, однако актуальность вопроса децентрализации для PoW блокчейнов ежегодно растет. Постепенно майнить становится сложнее, поэтому пользователи объединяются в пулы. В 2021 году было зафиксировано, что 50% мощности сети Bitcoin принадлежит 4 пулам, то есть постепенно происходит стягивание всех процессов к нескольким крупным серверам.
Proof of Stake появился позже. Сейчас его в основном используют в проектах альткоинов. Его особенность в том, что пользователям не нужны огромные мощности, так как PoS не подразумевает майнинг. Приоритеты участников зависят от количества криптовалюты, хранящейся на балансе.
Чтобы получить право проверить блок, пользователь должен заблокировать монеты с помощью смарт-контракта. Этот процесс называют стейкингом. Затем алгоритмы блокчейна выбирают, кто займется валидацией следующего блока.
Рассмотрим все существенные различия механизмов:
Proof of Work | Proof of Stake | |
Кто участвует в майнинге/валидации | Владельцы компьютеров с наибольшей вычислительной мощностью | Пользователи с большим количеством заблокированных монет |
Требующееся оборудование | Оборудование, заточенное под добычу криптовалюты | Любое устройство с постоянным выходом в интернет |
Распределение наград | Их забирают пользователи, добывшие блок первыми | Валидатору выплачивают часть комиссии за переводы, собираемые с подтвержденного блока |
Безопасность | Чем больше хэш, тем защищеннее система | Смарт-контракты блокируют валюту участников |
В нашем случае использован Proof of Work, как у Биткоина. Чтобы настроить механизм, необходимо поставить требования к структуре хэша.
Программный код:
def generate_header(index, prev_hash, data, timestamp, nonce):
return str(index) + prev_hash + data + str(timestamp) + str(nonce)
Теперь майнинг настроен. Стоит сказать, что если хэш блока не содержит достаточного количества нулей, необходимо увеличить значение nonce, создать новый хэдер, пересчитать хэш и перепроверить число нулей.
Код:
NUM_ZEROS = 4
def mine(last_block):
index = int(last_block.index) + 1
timestamp = date.datetime.now()
data = "I block #%s" % (int(last_block.index) + 1) #random string for now, not transactions
prev_hash = last_block.hash
nonce = 0
block_hash = calculate_hash(index, prev_hash, data, timestamp, nonce)
while str(block_hash[0:NUM_ZEROS]) != '0' * NUM_ZEROS:
nonce += 1
block_hash = calculate_hash(index, prev_hash, data, timestamp, nonce)
block_data = {}
block_data['index'] = int(last_block.index) + 1
block_data['timestamp'] = date.datetime.now()
block_data['data'] = "I block #%s" % last_block.index
block_data['prev_hash'] = last_block.hash
block_data['hash'] = block_hash
block_data['nonce'] = nonce
return Block(block_data)
Когда блок получит валидное значение показателя nonce, другие узлы смогут подтвердить хэш.
Как создать пару ключей
Чтобы зашифровать данные, стоит воспользоваться RSA. В результате вы получите пару ключей: публичный и приватный.
Программный код:
from Crypto.PublicKey import RSA
code = 'nooneknows'
key = RSA.generate(2048)
encrypted_key = key.exportKey(
passphrase=code,
pkcs=8,
protection="scryptAndAES128-CBC"
)
with open('my_private_rsa_key.bin', 'wb') as f:
f.write(encrypted_key)
with open('my_rsa_public.pem', 'wb') as f:
f.write(key.publickey().exportKey())
Сначала нужно импортировать RSA из Crypto.PublicKey, а затем создать простой код доступа и сгенерировать ключ RSA на 2048 битов. Чтобы создать приватный ключ, необходимо вызвать метод exportKey и отдать ему код доступа. Последний будет использован стандартом PKCS, схема шифровки которого подходит для защиты конфиденциальной информации.
Полученный файл следует записать на диск. Остается создать приватный ключ через метод publickey, связав его с вызовом exportKey.
Реализация подписей
Перевод необходимо подписать, так как это единственный способ гарантировать его надежность. Если подпись окажется недействительной, то и транзакция не добавится в цепочку. Учитывая, что транзакции открывают предыдущие выходы, распределяют их значения, а затем блокируют новые выходы, необходимо подписать:
хэши открытых ключей в разблокированных выходах, чтобы идентифицировать отправителя;
хэши открытых ключей в новых выходах для подтверждения получателя;
значения новых выходов.
Перед отправкой файлов участник сети генерирует цифровую подпись, используя закрытый ключ аккаунта. А подписание может осуществляться несколькими способами, например, в клиенте блокчейн-платформы.
Алгоритм проверки транзакции:
Получение информации и ЭЦП из новой транзакции.
Сбор хэша.
Расшифровка подписи и открытого ключа с помощью RSA Decode.
Сравнение хэшей, полученных на втором и третьем этапе.
Если значения совпали, значит, информация корректна, а транзакция действительно подписана ключом владельца. В случае одобрения перевода данные добавляются в блок.
Теперь у вас есть работающий блокчейн. Мы не задействовали еще много функций, однако его уже можно использовать для обмена информацией в небольшой компании или между друзьями.