Exonum — это фреймворк с открытым исходным кодом для создания приложений на основе блокчейна. Он ориентирован на работу с закрытыми блокчейнами и применим в любых сферах: FinTech, GovTech и LegalTech.

Сегодня мы проведем небольшой обзор решения, а также в рамках образовательного формата разберемся с тем, как построить простую криптовалюту с использованием Exonum. Весь код, приведенный ниже, вы найдете в репозитории на GitHub.


/ Exonum. Your next step to blockchain / Exonum

Exonum в двух словах


Фреймворк Exonum создавался специально для разработки приватных блокчейнов. Это система, в которой создавать новые блоки в блокчейне может только предварительно определенная группа узлов. В еe основе лежит желание специалистов Bitfury создать инструмент, который бы позволил относительно просто запустить систему, схожую по свойствам с публичными блокчейнами (надежность, неизменяемость данных, аудитируемость и т. д.), но при этом был бы более удобным в поддержании и обслуживании.

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

Развернутый на заранее определенных узлах приватный блокчейн Exonum как минимум иcключает возможность его внезапного хард-форка, засорения пула транзакций и других проблем, характерных для открытых блокчейнов, а операторы узлов следят за его эффективной работой: обновляют правила обработки транзакций и т. п.

Помимо этого, выполнение смарт-контрактов на Ethereum сильно зависит от колебания курса криптовалюты — эфира, что делает его непредсказуемым для использования, например, в государственных структурах, которые не могут оплачивать выполнение транзакций валютой, находящейся в нерегулируемой «серой зоне». В Exonum подобные зависимости отсутствуют в принципе.

И наконец Exonum-блокчейн работает существенно быстрее, чем публичные блокчейны (Bitcoin, Ethereum и др.), а именно обрабатывает несколько тысяч транзакций в секунду против нескольких десятков, обрабатываемых последними. Выбор стратегии обусловлен общей тенденцией к созданию большого количества независимых блокчейнов, которые бы взаимодействовали между собой посредством технологий сайдчейнов, привязки к публичным блокчейнам (анкоринг) и т. д.

Главными компонентами Exonum являются: византийский консенсус, легкие клиенты, привязка к биткойну и сервисы.

В системе используется особый алгоритм византийского консенсуса для синхронизации данных среди узлов. Он гарантирует целостность данных и корректное выполнение транзакций даже в случае выхода из строя вплоть до 1/3 узлов из-за неисправности или намеренной зловредной деятельности, при этом не требуя майнинга блоков.

Говоря о преимуществах Exonum над существующими аналогами можно отметить развитую модель данных (storage), которая представляет собой индексы, содержащие зависимости друг от друга (по сути таблицы) — они позволяют реализовать эффективную структуру данных, направленную на решение частных задач. Клиенты такого блокчейна могут получать криптографические доказательства корректности загружаемых данных (деревья Меркла), которые проверяются локально на машине клиента, и не могут быть подделаны даже оператором узла Exonum.

Легкие клиенты — это узлы сети, которые хранят у себя только небольшую часть блокчейна, представляющую интерес. Они позволяют взаимодействовать с блокчейном с помощью мобильных приложений или веб-браузеров. Клиенты «общаются» с одним и более сервисами на полнофункциональном узле через API. Работа таких тонких клиентов специфична для каждого отдельного сервиса и реализована настолько сложно, насколько того требует конкретный сервис.

Суть работы тонких клиентов Exonum и построения доказательств сводится к тому, что конечный пользователь, который осуществил привязку к биткойн-блокчейну, может не доверять оператору приватного блокчейна. Но может быть уверен, что данные, которые у него отображаются, получены в соответствии с правилами, заложенными в этот конкретный приватный блокчейн.

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

И, наконец, сервисы — это основа фреймворка Exonum. Они напоминают смарт-контракты на других платформах и содержат бизнес-логику блокчейн-приложений. Но, в отличие от умных контрактов, сервисы в Exonum не «заперты» в виртуальной машине и не контейнеризованы.

Это делает их более эффективными и гибкими. Однако такой подход требует большей осторожности при программировании (изоляция сервисов отмечена на дорожной карте Exonum). Сервисы определяют правила обработки транзакций, а также открывают доступ к данным внешним клиентам.

Ознакомившись с основными компонентами, мы можем переходить к разбору примера.

Создание сервисов в Exonum


Второго ноября состоялся релиз версии Exonum 0.3, и дальнейшее руководство написано с учетом внесенных изменений и усовершенствований в систему (о них вы можете прочитать в репозитории на GitHub). Мы создадим блокчейн с одним узлом, который реализует криптовалюту. Сеть будет принимать два типа транзакций: «создать кошелек» и «перевести средства с одного кошелька на другой».

Exonum написан на Rust, поэтому вам нужно установить компилятор. Для этого вы можете воспользоваться нашим руководством.

Создание узла

Для начала создадим новый crate:

cargo new --bin cryptocurrency

И добавим необходимые зависимости в созданный cargo.toml:

[package]
name = "cryptocurrency"
version = "0.3.0"
authors = ["Your Name <your@email.com>"]

[dependencies]
iron = "0.5.1"
bodyparser = "0.7.0"
router = "0.5.1"
serde = "1.0"
serde_json = "1.0"
serde_derive = "1.0"
exonum = "0.3.0"

Импортируем crate с необходимыми типами. Для этого нужно подредактировать файл src/main.rs:

extern crate serde;
extern crate serde_json;
#[macro_use] extern crate serde_derive;
#[macro_use] extern crate exonum;
extern crate router;
extern crate bodyparser;
extern crate iron;

use exonum::blockchain::{Blockchain, Service, GenesisConfig,
                         ValidatorKeys, Transaction, ApiContext};
use exonum::node::{Node, NodeConfig, NodeApiConfig, TransactionSend,
                   ApiSender };
use exonum::messages::{RawTransaction, FromRaw, Message};
use exonum::storage::{Fork, MemoryDB, MapIndex};
use exonum::crypto::{PublicKey, Hash, HexValue};
use exonum::encoding::{self, Field};
use exonum::api::{Api, ApiError};
use iron::prelude::*;
use iron::Handler;
use router::Router;

Определим константы:

// Service identifier
const SERVICE_ID: u16 = 1;
// Identifier for wallet creation transaction type
const TX_CREATE_WALLET_ID: u16 = 1;
// Identifier for coins transfer transaction type
const TX_TRANSFER_ID: u16 = 2;
// Starting balance of a newly created wallet
const INIT_BALANCE: u64 = 100;


И функцию main:

fn main() {
    exonum::helpers::init_logger().unwrap();
}

Все это позволяет настроить логгер, который будет выводить информацию об активности узлов Exonum в консоль.

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

let db = MemoryDB::new();
let services: Vec<Box<Service>> = vec![ ];
let blockchain = Blockchain::new(Box::new(db), services);

По сути, блокчейн готов, однако взаимодействовать с ним не получится — у нас еще нет узла и API для обращения к нему. Узел потребуется сконфигурировать. В конфигурации указывается список открытых ключей валидаторов (в нашем случае он будет один). По сути каждому узлу требуется две пары открытых и закрытых ключей: одна для взаимодействия с другими узлами в процессе достижения консенсуса, а вторая — для сервисов. Для нашего примера создаем временные открытые ключи командой exonum::crypto::gen_keypair() и прописываем их в файл конфигурации.

let validator_keys = ValidatorKeys {
    consensus_key: consensus_public_key,
    service_key: service_public_key,
};
let genesis = GenesisConfig::new(vec![validator_keys].into_iter());

Далее, настраиваем REST API для работы с внешними веб-запросами — для этого открываем порт 8000. Также откроем порт 2000, чтобы полные узлы Exonum-сети могли общаться друг с другом.

let api_address = "0.0.0.0:8000".parse().unwrap();
let api_cfg = NodeApiConfig {
    public_api_address: Some(api_address),
    ..Default::default()
};

let peer_address = "0.0.0.0:2000".parse().unwrap();

// Complete node configuration
let node_cfg = NodeConfig {
    listen_address: peer_address,
    peers: vec![],
    service_public_key,
    service_secret_key,
    consensus_public_key,
    consensus_secret_key,
    genesis,
    external_address: None,
    network: Default::default(),
    whitelist: Default::default(),
    api: api_cfg,
    mempool: Default::default(),
    services_configs: Default::default(),
};

let node = Node::new(blockchain, node_cfg);
node.run().unwrap();

Объявляем данные

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

encoding_struct! {
    struct Wallet {
        const SIZE = 48;

        field pub_key:            &PublicKey  [00 => 32]
        field name:               &str        [32 => 40]
        field balance:            u64         [40 => 48]
    }
}

Макрос encoding_struct! помогает объявить упорядочиваемую структуру и обозначить границы полей значений. Нам нужно изменять баланс кошелька, потому добавим методы в Wallet:

impl Wallet {
    pub fn increase(self, amount: u64) -> Self {
        let balance = self.balance() + amount;
        Self::new(self.pub_key(), self.name(), balance)
    }

    pub fn decrease(self, amount: u64) -> Self {
        let balance = self.balance() - amount;
        Self::new(self.pub_key(), self.name(), balance)
    }
}

Также нужно сформировать хранилище «ключ-значение» в MemoryDB. Для этого мы используем форк, чтобы иметь возможность в крайнем случае откатить все изменения.

pub struct CurrencySchema<'a> {
    view: &'a mut Fork,
}

Однако форк дает доступ к любой информации в базе данных. Чтобы изолировать кошельки, добавим уникальный префикс и используем карту абстракций MapIndex.

impl<'a> CurrencySchema<'a> {
    pub fn wallets(&mut self) -> MapIndex<&mut Fork, PublicKey, Wallet> {
        let prefix = blockchain::gen_prefix(SERVICE_ID, 0, &());
        MapIndex::new("cryptocurrency.wallets", self.view)
    }

    // Utility method to quickly get a separate wallet from the storage
    pub fn wallet(&mut self, pub_key: &PublicKey) -> Option<Wallet> {
        self.wallets().get(pub_key)
    }
}

Определяем транзакции

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

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

message! {
    struct TxCreateWallet {
        const TYPE = SERVICE_ID;
        const ID = TX_CREATE_WALLET_ID;
        const SIZE = 40;

        field pub_key:     &PublicKey  [00 => 32]
        field name:        &str        [32 => 40]
    }
}

Перед созданием кошелька будем проверять его уникальность. Также зачислим на него 100 монет.

impl Transaction for TxCreateWallet {
    fn verify(&self) -> bool {
        self.verify_signature(self.pub_key())
    }

    fn execute(&self, view: &mut Fork) {
        let mut schema = CurrencySchema { view };
        if schema.wallet(self.pub_key()).is_none() {
            let wallet = Wallet::new(self.pub_key(),
                                     self.name(),
                                     INIT_BALANCE);
            println!("Create the wallet: {:?}", wallet);
            schema.wallets().put(self.pub_key(), wallet)
        }
    }
}

Транзакция для перевода денег выглядит так:

message! {
    struct TxTransfer {
        const TYPE = SERVICE_ID;
        const ID = TX_TRANSFER_ID;
        const SIZE = 80;

        field from:        &PublicKey  [00 => 32]
        field to:          &PublicKey  [32 => 64]
        field amount:      u64         [64 => 72]
        field seed:        u64         [72 => 80]
    }
}

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

impl Transaction for TxTransfer {
    fn verify(&self) -> bool {
         (*self.from() != *self.to()) &&
             self.verify_signature(self.from())
    }

    fn execute(&self, view: &mut Fork) {
        let mut schema = CurrencySchema { view };
        let sender = schema.wallet(self.from());
        let receiver = schema.wallet(self.to());
        if let (Some(mut sender), Some(mut receiver)) = (sender, receiver) {
            let amount = self.amount();
            if sender.balance() >= amount {
                let sender.decrease(amount);
                let receiver.increase(amount);
                println!("Transfer between wallets: {:?} => {:?}",
                         sender,
                         receiver);
                let mut wallets = schema.wallets();
                wallets.put(self.from(), sender);
                wallets.put(self.to(), receiver);
            }
        }
    }
}

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

impl Transaction for TxCreateWallet {
    // `verify()` and `execute()` code...

   
   fn info(&self) -> serde_json::Value {
       serde_json::to_value(&self)
           .expect("Cannot serialize transaction to JSON")
   }
}

Реализуем API для транзакций

Для этого создадим структуру с каналом и экземпляром блокчейна, который будет необходим для реализации запросов на чтение:

#[derive(Clone)]
struct CryptocurrencyApi {
    channel: ApiSender,
    blockchain: Blockchain,
}

Чтобы упростить обработку процессов, добавим TransactionRequest enum, объединяющий оба типа транзакций: «создать кошелек» и «перевести средства».

#[serde(untagged)]
#[derive(Clone, Serialize, Deserialize)]
enum TransactionRequest {
    CreateWallet(TxCreateWallet),
    Transfer(TxTransfer),
}

impl Into<Box<Transaction>> for TransactionRequest {
    fn into(self) -> Box<Transaction> {
        match self {
            TransactionRequest::CreateWallet(trans) => Box::new(trans),
            TransactionRequest::Transfer(trans) => Box::new(trans),
        }
    }
}

#[derive(Serialize, Deserialize)]
struct TransactionResponse {
    tx_hash: Hash,
}

Осталось «подружить» наш обработчик с HTTP-обработчиком веб-сервера. Для этого реализуем метод wire. В приведенном ниже примере мы добавим обработчик, который конвертирует ввод JSON в Transaction.

impl Api for CryptocurrencyApi {
    fn wire(&self, router: &mut Router) {
        let self_ = self.clone();
        let tx_handler = move |req: &mut Request| -> IronResult<Response> {
            match req.get::<bodyparser::Struct<TransactionRequest>>() {
                Ok(Some(tx)) => {
                    let tx: Box<Transaction> = tx.into();
                    let tx_hash = tx.hash();
                    self_.channel.send(tx).map_err(ApiError::from)?;
                    let json = TransactionResponse { tx_hash };
                    self_.ok_response(&serde_json::to_value(&json).unwrap())
                }
                Ok(None) => Err(ApiError::IncorrectRequest(
                    "Empty request body".into()))?,
                Err(e) => Err(ApiError::IncorrectRequest(Box::new(e)))?,
            }
        };

        // (Read request processing skipped)

        // Bind the transaction handler to a specific route.
        
        router.post("/v1/wallets/transaction", transaction, "transaction");
        // (Read request binding skipped)
    }
}

Реализуем API для запросов на чтение

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

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

impl CryptocurrencyApi {
    fn get_wallet(&self, pub_key: &PublicKey) -> Option<Wallet> {
        let mut view = self.blockchain.fork();
        let mut schema = CurrencySchema { view: &mut view };
        schema.wallet(pub_key)
    }

    fn get_wallets(&self) -> Option<Vec<Wallet>> {
        let mut view = self.blockchain.fork();
        let mut schema = CurrencySchema { view: &mut view };
        let idx = schema.wallets();
        let wallets: Vec<Wallet> = idx.values().collect();
        if wallets.is_empty() {
            None
        } else {
            Some(wallets)
        }
    }
}

Стоит обратить внимание на то, что в данном случае мы используем метод fork, несмотря на то, что он дает доступ на запись и чтение данных (чтобы не перегружать пример). В реальных условиях целесообразно использовать формат доступа только на чтение (обращаясь к снапшотам).

Далее, также как и для транзакций, добавляем обработку запросов при помощи методов get_wallets() и get_wallet() в CryptocurrencyApi::wire().

impl Api for CryptocurrencyApi {
    fn wire(&self, router: &mut Router) {
        let self_ = self.clone();

        // (Transaction processing skipped)

        // Gets status of all wallets in the database.
        let self_ = self.clone();
        let wallets_info = move |_: &mut Request| -> IronResult<Response> {
            if let Some(wallets) = self_.get_wallets() {
                self_.ok_response(&serde_json::to_value(wallets).unwrap())
            } else {
                self_.not_found_response(
                    &serde_json::to_value("Wallets database is empty")
                        .unwrap(),
                )
            }
        };

        // Gets status of the wallet corresponding to the public key.
        let self_ = self.clone();
        let wallet_info = move |req: &mut Request| -> IronResult<Response> {
            // Get the hex public key as the last URL component;
            // return an error if the public key cannot be parsed.
            let path = req.url.path();
            let wallet_key = path.last().unwrap();
            let public_key = PublicKey::from_hex(wallet_key)
                .map_err(ApiError::FromHex)?;

            if let Some(wallet) = self_.get_wallet(&public_key) {
                self_.ok_response(&serde_json::to_value(wallet).unwrap())
            } else {
                self_.not_found_response(
                    &serde_json::to_value("Wallet not found").unwrap(),
                )
            }
        };

        // (Transaction binding skipped)
        // Bind read request endpoints.
        router.get("/v1/wallets", wallets_info, "wallets_info");
        router.get("/v1/wallet/:pub_key", wallet_info, "wallet_info");
    }
 

Определяем сервис

Чтобы превратить структуру CurrencyService в блокчейн-сервис, мы должны назначить ей свойство Service. Оно имеет два метода: service_name, который возвращает имя нашего сервиса, и service_id, возвращающий его уникальный ID.
Метод tx_from_raw будет использоваться для десериализации транзакций, а метод public_api_handler — для создания REST Handler для обработки веб-запросов к узлу. Он будет применять логику, уже определенную в CryptocurrencyApi.

impl Service for CurrencyService {
    fn service_name(&self) -> &'static str { "cryptocurrency" }

    fn service_id(&self) -> u16 { SERVICE_ID }

    fn tx_from_raw(&self, raw: RawTransaction)
        -> Result<Box<Transaction>, encoding::Error> {

        let trans: Box<Transaction> = match raw.message_type() {
            TX_TRANSFER_ID => Box::new(TxTransfer::from_raw(raw)?),
            TX_CREATE_WALLET_ID => Box::new(TxCreateWallet::from_raw(raw)?),
            _ => {
                return Err(encoding::Error::IncorrectMessageType {
                    message_type: raw.message_type()
                });
            },
        };
        Ok(trans)
    }

    fn public_api_handler(&self, ctx: &ApiContext) -> Option<Box<Handler>> {
        let mut router = Router::new();
        let api = CryptocurrencyApi {
            channel: ctx.node_channel().clone(),
            blockchain: ctx.blockchain().clone(),
        };
        api.wire(&mut router);
        Some(Box::new(router))
    }
}

Мы реализовали все части нашего мини-блокчейна. Теперь осталось добавить CryptocyrrencyService в список сервисов блокчейна и запустить демо:

let services: Vec<Box<Service>> = vec![
    Box::new(CurrencyService),
];

cargo run

Тестирование сервисов

Exonum позволяет протестировать работу сервисов. Для этого используется пакет Sandbox — он симулирует работу сети. Мы можем отправить запрос к узлу и получить ответ, а затем пронаблюдать за происходящими изменениями в блокчейне. Инстанс Sandbox создается методом sandbox_with_services, позволяющим специфицировать сервисы для тестирования. Например, вот так:

let s = sandbox_with_services(vec![Box::new(CurrencyService::new()),
                                   Box::new(ConfigUpdateService::new())]);

В целом Sandbox может симулировать процесс получения сообщения узлом, проверять, какой узел его отправил, и что в нем находилось. Также «песочница» может работать со временем, например, моделировать истечение какого-либо временного периода.

Отправка транзакций

Теперь попробуем отправить несколько транзакций в нашей демоверсии блокчейна. Сперва создаем кошелек. Так будет выглядеть файл ?create-wallet-1.json:

{
  "body": {
    "pub_key": "03e657ae71e51be60a45b4bd20bcf79ff52f0c037ae6da0540a0e0066132b472",
    "name": "Johnny Doe"
  },
  "network_id": 0,
  "protocol_version": 0,
  "service_id": 1,
  "message_id": 1,
  "signature": "ad5efdb52e48309df9aa582e67372bb3ae67828c5eaa1a7a5e387597174055d315eaa7879912d0509acf17f06a23b7f13f242017b354f682d85930fa28240402"
}

Используем команду curl, чтобы отправить транзакцию по HTTP:

curl -H "Content-Type: application/json" -X POST -d @create-wallet-1.json     http://127.0.0.1:8000/api/services/cryptocurrency/v1/wallets/transaction

После этого в консоли мы увидим, что кошелек был создан:

Create the wallet: Wallet { pub_key: PublicKey(3E657AE),
                            name: "Johnny Doe", balance: 100 }

Второй кошелек формируется аналогично. После его создания можем перевести средства. Файл transfer-funds.json выглядит так:

{
  "body": {
    "from": "03e657ae71e51be60a45b4bd20bcf79ff52f0c037ae6da0540a0e0066132b472",
    "to": "d1e877472a4585d515b13f52ae7bfded1ccea511816d7772cb17e1ab20830819",
    "amount": "10",
    "seed": "12623766328194547469"
  },
  "network_id": 0,
  "protocol_version": 0,
  "service_id": 1,
  "message_id": 2,
  "signature": "2c5e9eee1b526299770b3677ffd0d727f693ee181540e1914f5a84801dfd410967fce4c22eda621701c2b9c676ed62bc48df9c973462a8514ffb32bec202f103"
}

Эта транзакция переводит 10 монет из первого кошелька на второй. Отправим команду узлу с помощью curl:

curl -H "Content-Type: application/json" -X POST -d @transfer-funds.json     http://127.0.0.1:8000/api/services/cryptocurrency/v1/wallets/transaction

Узел покажет, что сумма успешно переведена:

Transfer between wallets: Wallet { pub_key: PublicKey(3E657AE),
                                   name: "Johnny Doe", balance: 90 }
                       => Wallet { pub_key: PublicKey(D1E87747),
                                   name: "Janie Roe", balance: 110 }


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

curl http://127.0.0.1:8000/api/services/cryptocurrency/v1/wallets

Данный запрос выдаст информацию о кошельках в таком виде:

[
  {
    "balance": "90",
    "name": "Johnny Doe",
    "pub_key": "03e657ae71e51be60a45b4bd20bcf79ff52f0c037ae6da0540a0e0066132b472"
  },
  {
    "balance": "110",
    "name": "Janie Roe",
    "pub_key": "d1e877472a4585d515b13f52ae7bfded1ccea511816d7772cb17e1ab20830819"
  }
]

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

curl "http://127.0.0.1:8000/api/services/cryptocurrency/v1/wallet/03e657ae71e51be60a45b4bd20bcf79ff52f0c037ae6da0540a0e0066132b472"

Получаем ответ:

{
  "balance": "90",
  "name": "Johnny Doe",
  "pub_key": "03e657ae71e51be60a45b4bd20bcf79ff52f0c037ae6da0540a0e0066132b472"
}

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

Комментарии (12)


  1. Sovigod
    12.11.2017 21:07

    Почему из всех популярных языков программирования вы выбрали Rust?


    1. alinatestova Автор
      12.11.2017 21:27

      Мы выбрали Rust, потому что он позволяет воплощать в жизнь безопасные blockchain-based решения. При этом Rust на некоторых задачах работает быстрее, чем Java, Go, C и C++ и постоянно совершенствуется.


    1. Gorthauer87
      13.11.2017 12:35

      Rust за счет концепции времени жизни и владения, устраняет несколько классов ошибок, в результате отстрелить себе ногу становится намного сложнее. Думаю я, что в блокчейн продуктах ошибки типа heartbleed могут дорого обойтись и хотелось бы себя обезопасить от них по максимуму. К тому же за все это не приходится платить жирным рантаймом, а проблемы находятся во время компиляции программы, а не во время ее тестирования.
      К тому же Rust обладает отличным пакетным менеджером cargo и развитыми средствами метапрограммирования.
      Все эти качестве вместе делают его прекрасным языком для написания любого софта с повышенными требованиями к безопасности и производительности.


  1. denismaster
    12.11.2017 21:42

    Интересная и хорошая статья. У самого витают мысли по созданию блокчейн-фреймворка для .NET Standard. В таких статьях можно заметить некоторые нюансы и трудности, решения к ним.


  1. dreka5
    13.11.2017 09:54

    какая цель в создании новой крипты?
    вот тут список из 900 штук
    если каждый начнет создавать свою крипту, то к чему это приведёт?


    1. niko1aev
      13.11.2017 10:37

      На Nasdaq торгуются акции 3200 компаний. Потенциально каждой из них может потребоваться своя «крипта». Зачем? Например, для расчетов между подразделениями без банков, для бонусных программ, для взаимодействий с поставщиками, для выплат бонусов и премий сотрудникам и т.д. И нет в этом ничего страшного. Нас же не смущают мили авиакомпаний, баллы «спасибо» от Сбербанка, рокетрубли от Рокетбанка, баллы OZON и т.д. и т.п. Крипта в этом случае — всего лишь инструмент, который может добавить этим внутренним валютам еще методов применения и ликвидности)


      1. dreka5
        13.11.2017 10:53

        баллы «спасибо» от Сбербанка

        по законадательству РФ запрещено поощрять клиета деньгами, оттого и придуманы суррогаты в виде баллов. их нельзя у компании обменять на деньги. о слова совсем. крипта и акции вращаются на биржах. их свободно можно покупать продавать.

        если сравнивать с акциями — акции изначально были хоть чем то подкреплены. крипта ввисит только на вере. вон как биток сегодня скачет. так что сравнение и тут не верное.

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


        1. niko1aev
          13.11.2017 11:09

          Ограничение, что нельзя обменять на деньги — фиктивное. Рокетрублями можно компенсировать любую покупку от 3000р и эти деньги вернуться настоящими рублями на ваш счет. Да, сначала надо заплатить рублями, а потом «компенсировать». Но по факту это те же рубли. Баллами спасибо от сбербанка можно заплатить в сотнях магазинов. Не всегда всю сумму, но тем не менее — если мне сейчас выдадут 100 000 баллов спасибо, я буду относиться к ним как с деньгам. И если Amazon выдаст премию сотрудникам в виде баллов, которые можно потратить только в Amazon — это неплохая экономия денег на налогах и комиссиях банков.

          Я знаю в РФ не одну крупную компанию, которая выдает премии и подарки в виде подарочных карт своих магазинов.

          Ну и почему Зимбабве с ВВП в $16млрд/год может иметь свою валюту, а Apple у которых $250 млрд в оффшорах нет? Или Alibaba которая сделала оборот $25 млрд за сутки на своей распродаже?

          Поэтому, я думаю, что появление у корпораций своих валют — дело времени. Более того, я уверен, что в большинстве уже вовсю идет работа в этом направлении)

          Ну и последний пункт: ряд стран тоже хотят выпустить свою «крипту» и тут им тоже понадобится решение на рынке)


        1. beatleboy
          13.11.2017 12:39

          Криптовалюта это частный случай децентрализованного криптореестра. Смотрите шире, выдавая криптофантик ты не даешь никаких благ, ты просто вносишь в распределенный реестр информацию о владельце этого фантика. Другое дело, что этот фантик ввиду своей популярности (я о BTC) у тебя кто-то хочет обменять за фиатные деньги.

          Но и к примеру мою машину тоже хотят обменять на фиатные деньги. Посути машина это тоже денежный суррогат. Любые блага это денежный суррогат. И их спокойно можно менять на деньги, баллы также можно обменять на деньги, например я могу у Вас их купить если захочу.


          1. dreka5
            13.11.2017 13:19

            баллы также можно обменять на деньги,

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

            во время гражданской войны, каждое правительство выпускало свои деньги. Моржовки, Керенки, Донской рубль…
            вполне себе фиатные деньги. но вот количество и качество их не делало их сильными, а скорее наоборот вызывало недоверие ко всем подобным банкнотам.

            собсно вопрос о том и был, каков смысл вводить ещё over9000 новых криптозаписей.
            если кто то хочет поощрить сотрудника, уже есть масса других средств. тот же эфир.

            создав несчётное число подобных криптоблоков люди будут путаться.


      1. tyratam
        14.11.2017 15:43

        это не ответ.
        для чего во всех этих проектах система даже не распределенная, а задублированная на куче узлов?


    1. ftdgoodluck
      14.11.2017 19:49

      Цель туториала – не создание новой крипты, а демонстрация того, как на базе Exonum реализуется сервис с простейшей логикой (создать кошелек, перевести деньги).

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