Базовая концепция блокчейна довольно проста: речь идет о распределенной базе данных, поддерживающей постоянно растущий список упорядоченных записей. Запутаться, однако, легко, поскольку всякий раз, когда мы говорим о блокчейнах, мы рассматриваем их с позиции задач, которые пытаемся решить и потому нередко начинаем подмешивать в это определение другие, очень близкие ему понятия. Это справедливо и в случае с популярными блокчейн проектами, такими, как Биткойн и Эфириум. Термин «блокчейн» обычно тесно связан с такими концепциями, как транзакции, умные контракты или криптовалюты.
Все это оправданно, но все же существенно усложняет процесс понимания принципа работы блокчейнов, особенно с точки зрения программного обеспечения с открытым исходным кодом. В этом материале я покажу вам крайне простой блокчейн, написание которого потребовало всего 200 строк Javascript-кода. Я назвал его NaiveChain.
Структура блока
Первым и логичным шагом было бы определиться со структурой блока. Чтобы сохранить максимальную простоту, мы включим в него лишь самое необходимое: индекс, метку времени, данные и хэши текущего и предыдущего блоков.
Хэш предыдущего блока включен в каждый блок для сохранения целостности цепочки
class Block {
constructor(index, previousHash, timestamp, data, hash) {
this.index = index;
this.previousHash = previousHash.toString();
this.timestamp = timestamp;
this.data = data;
this.hash = hash.toString();
}
}
Хэширование блока
Сохранение целостности данных достигается с помощью хэширования блоков. В данном случае применяется алгоритм SHA-256. Следует также отметить, что получаемый хэш никак не связан с процессом майнинга, поскольку наш блокчейн не требует решения каких-либо «Proof Of Work» задач.
var calculateHash = (index, previousHash, timestamp, data) => {
return CryptoJS.SHA256(index + previousHash + timestamp + data).toString();
};
Генерация блока
Для генерации блока мы должны знать хэш предыдущего блока и добавить оставшуюся часть содержимого текущего блока, то есть индекс, хэш, данные и временную метку. Под данными блока понимается какая-либо предоставленная конечными пользователями информация.
var generateNextBlock = (blockData) => {
var previousBlock = getLatestBlock();
var nextIndex = previousBlock.index + 1;
var nextTimestamp = new Date().getTime() / 1000;
var nextHash = calculateHash(nextIndex, previousBlock.hash, nextTimestamp, blockData);
return new Block(nextIndex, previousBlock.hash, nextTimestamp, blockData, nextHash);
};
Хранение блоков
Блокчейн хранится в оперативной памяти в форме Javascript массива. Первый блок, как это обычно бывает во всех блокчейнах, получил название genesis-block (блок-прародитель). Его содержимое прописано заранее и не подлежит изменению.
var getGenesisBlock = () => {
return new Block(0, "0", 1465154705, "my genesis block!!", "816534932c2b7154836da6afc367695e6337db8a921823784c14378abed4f7d7");
};
var blockchain = [getGenesisBlock()];
Проверка целостности блоков
В любой момент мы должны иметь возможность проверить, действительно ли тот или иной блок или цепочка блоков содержит правильно сформированную и достоверную информацию. Это особенно важно, когда мы получаем новые блоки от других узлов и должны принять решение относительно их подлинности.
var isValidNewBlock = (newBlock, previousBlock) => {
if (previousBlock.index + 1 !== newBlock.index) {
console.log('invalid index');
return false;
} else if (previousBlock.hash !== newBlock.previousHash) {
console.log('invalid previoushash');
return false;
} else if (calculateHashForBlock(newBlock) !== newBlock.hash) {
console.log('invalid hash: ' + calculateHashForBlock(newBlock) + ' ' + newBlock.hash);
return false;
}
return true;
};
Выбор самой длинной цепочки
В любой момент времени в блокчейне должен существовать только один достоверный набор блоков. В случае конфликта (например, два узла генерируют блок номер 72) мы выбираем цепочку, содержащую наибольшее количество блоков.
var replaceChain = (newBlocks) => {
if (isValidChain(newBlocks) && newBlocks.length > blockchain.length) {
console.log('Received blockchain is valid. Replacing current blockchain with received blockchain');
blockchain = newBlocks;
broadcast(responseLatestMsg());
} else {
console.log('Received blockchain invalid');
}
};
Обмен информацией с другими узлами
Одна из ключевых составляющих деятельности узла заключается в обмене информации о блокчейне и синхронизации данных с другими узлами.
- Когда узел генерирует новый блок, он оповещает об этом всех остальных участников сети.
- Когда узел соединяется с другим новым узлом, он опрашивает его о последнем известном ему блоке.
- Когда узел узнает о блоке с показателем индекса более высоким, нежели у последнего известного ему, он добавляет блок в собственную цепочку или отправляет запрос на получение целого блокчейна для сверки.
Некоторые типичные сценарии обмена сообщениями между узлами, предусмотренные описанным протоколом
Автоматическое обнаружение узлов в нашем блокчейне не используется. Расположения (то есть URL’ы) других узлов прописываются вручную.
Управление узлом
Пользователь должен обладать определенными возможности по управлению узлом. Для этого в нашем коде предусмотрена настройка HTTP-сервера.
var initHttpServer = () => {
var app = express();
app.use(bodyParser.json());
app.get('/blocks', (req, res) => res.send(JSON.stringify(blockchain)));
app.post('/mineBlock', (req, res) => {
var newBlock = generateNextBlock(req.body.data);
addBlock(newBlock);
broadcast(responseLatestMsg());
console.log('block added: ' + JSON.stringify(newBlock));
res.send();
});
app.get('/peers', (req, res) => {
res.send(sockets.map(s => s._socket.remoteAddress + ':' + s._socket.remotePort));
});
app.post('/addPeer', (req, res) => {
connectToPeers([req.body.peer]);
res.send();
});
app.listen(http_port, () => console.log('Listening http on port: ' + http_port));
};
Как видно из приведенного выше фрагмента кода, пользователь получает возможность взаимодействовать с узлом следующими способами:
- Получить список всех блоков.
- Создать новый блок с указанным содержимым.
- Получить список всех узлов или добавить в него новые узлы.
Один из вариантов непосредственного управления узлом — использование Curl:
#получаем все блоки от узла
curl http://localhost:3001/blocks
Архитектура
Следует отметить, что работа каждого узла включает в себя использование двух веб-серверов: управляющего узлом HTTP-сервера и осуществляющего коммуникацию с другими узлами Websocket HTTP-сервера.
Основные компоненты NaiveChain
Заключение
NaiveChain был создан в демонстрационных и обучающих целях. В силу того, что в нем нет алгоритма майнинга (PoS или PoW), использовать его для публично доступной сети не представляется возможным. Тем не менее проект воплощает на практике всю необходимую базовую функциональность Блокчейн.
Более подробная техническая информация доступна в Github-репозитории проекта.
Автор: Wirex