ä»æ¥ã¯ããœãªã¥ãŒã·ã§ã³ã®ç°¡åãªã¬ãã¥ãŒãè¡ããæè²åœ¢åŒã®äžã§ãExonumã§ç°¡åãªãããã¯ãã§ãŒã³ããŒã¹ã®ãããžã§ã¯ããæ§ç¯ããæ¹æ³ãèŠã€ããŸãã 以äžã®ãã¹ãŠã®ã³ãŒãã¯ã GitHub ã®ãªããžããªã«ãããŸãã
/ ãšããœãã ã ãããã¯ãã§ãŒã³ãžã®æ¬¡ã®ã¹ããã / Exonum
äžèšã§èšãã°ãšã¯ãœã³
Exonumãã¬ãŒã ã¯ãŒã¯ã¯ããã©ã€ããŒããããã¯ãã§ãŒã³ã®éçºå°çšã«äœæãããŸããã ããã¯ãäºåå®çŸ©ãããããŒãã®ã°ã«ãŒãã®ã¿ããããã¯ãã§ãŒã³ã«æ°ãããããã¯ãäœæã§ããã·ã¹ãã ã§ãã Bitfuryã®å°é家ã®èŠæã«åºã¥ããŠããããªãã¯ãããã¯ãã§ãŒã³ã«é¡äŒŒããããããã£ïŒä¿¡é Œæ§ãããŒã¿ã®äžå€æ§ãç£æ»ãªã©ïŒã®ã·ã¹ãã ãæ¯èŒçç°¡åã«å®è¡ã§ããããŒã«ãäœæããŸãããä¿å®ãšä¿å®ããã䟿å©ã«ãªããŸãã
ä»®æ³åæ£ãã·ã³ã§ãããäžçäžã®å€ãã®ããŒãã§åæã«å®è¡ãããã€ãŒãµãªã¢ã ãšã¯ç°ãªããExonumã§æ§ç¯ããããããã¯ãã§ãŒã³ã¯ããã®ã·ã¹ãã ã®åäœã«é¢å¿ã®ããæ€èšŒããŒãã®èšç®èœåã§ã®ã¿åäœããä¿¡é Œæ§ã®é«ãåäœãä¿èšŒããŸãã
æå®ã®ããŒãã«ãããã€ãããExonumãã©ã€ããŒããããã¯ãã§ãŒã³ã¯ãå°ãªããšãçªç¶ã®ããŒããã©ãŒã¯ããã©ã³ã¶ã¯ã·ã§ã³ããŒã«ã®è©°ãŸããããã³ãªãŒãã³ãããã¯ãã§ãŒã³ã«ç¹æã®ãã®ä»ã®åé¡ã®å¯èœæ§ãæé€ããããŒããªãã¬ãŒã¿ãŒã¯ãã®æå¹ãªäœæ¥ãç£èŠããŸãïŒãã©ã³ã¶ã¯ã·ã§ã³åŠçã«ãŒã«ã®æŽæ°ãªã©
ããã«ãã€ãŒãµãªã¢ã ã¹ããŒãã³ã³ãã©ã¯ãã®å®è¡ã¯ãæå·é貚ã®å€åã«å€§ããäŸåããŠããŸããã€ãŒãµãŒã¬ãŒãã¯ãããšãã°ãèŠå¶ãããŠããªããã°ã¬ãŒãŸãŒã³ãã«ããé貚ã§ã®ååŒã«æ¯æããã§ããªãæ¿åºæ©é¢ã§ã®äœ¿çšãäºæž¬äžèœã«ããŸãã Exonumã§ã¯ããã®ãããªäŸåé¢ä¿ã¯ååãšããŠååšããŸããã
æåŸã«ãExonumãããã¯ãã§ãŒã³ã¯ããããªãã¯ãããã¯ãã§ãŒã³ïŒãããã³ã€ã³ãã€ãŒãµãªã¢ã ãªã©ïŒãããã¯ããã«é«éã«åäœããŸããã€ãŸãã1ç§ãããæ°åã®ãã©ã³ã¶ã¯ã·ã§ã³ãåŠçããŸãã æŠç¥ã®éžæã¯ããµã€ããã§ãŒã³ãã¯ãããžãŒãä»ããŠçžäºã«äœçšããå€æ°ã®ç¬ç«ãããããã¯ãã§ãŒã³ãäœæãããããªãã¯ãããã¯ãã§ãŒã³ã«ãªã³ã¯ïŒã¢ã³ã«ãŒïŒãããªã©ã®äžè¬çãªåŸåã«ãããã®ã§ãã
Exonumã®äž»èŠã³ã³ããŒãã³ãã¯ããã¶ã³ãã³ã³ã³ã»ã³ãµã¹ãã©ã€ãã¯ã©ã€ã¢ã³ãããããã³ã€ã³ãã€ã³ãã£ã³ã°ããã³ãµãŒãã¹ã§ãã
ã·ã¹ãã ã¯ã ç¹å¥ãªãã¶ã³ãã³åæã¢ã«ãŽãªãºã ã䜿çšããŠãããŒãéã§ããŒã¿ãåæããŸãã ãããã¯ãã€ãã³ã°ãå¿ èŠãšããã«ã誀åäœãæå³çãªæªæã®ããã¢ã¯ãã£ããã£ã«ããæ倧1/3ããŒãã®é害ãçºçããå Žåã§ããããŒã¿ã®æŽåæ§ãšãã©ã³ã¶ã¯ã·ã§ã³ã®æ£ããå®è¡ãä¿èšŒããŸãã
Exonumã®æ¢åã®ã¢ããã°ã«å¯Ÿããå©ç¹ã«ã€ããŠèšãã°ãéçºãããããŒã¿ã¢ãã«ïŒã¹ãã¬ãŒãžïŒã«æ³šç®ã§ããŸããããã¯ãçžäºã®äŸåé¢ä¿ïŒæ¬è³ªçã«ã¯ããŒãã«ïŒãå«ãã€ã³ããã¯ã¹ã§ããç¹å®ã®åé¡ã解決ããããã®å¹æçãªããŒã¿æ§é ãå®è£ ã§ããŸãã ãã®ãããªãããã¯ãã§ãŒã³ã®ã¯ã©ã€ã¢ã³ãã¯ãããŠã³ããŒããããããŒã¿ïŒMerkleããªãŒïŒã®æ£ç¢ºæ§ã®æå·åããã蚌æ ãåãåãããšãã§ããã¯ã©ã€ã¢ã³ãã®ãã·ã³ã§ããŒã«ã«ã«ãã§ãã¯ãããExonumããŒãã®ãªãã¬ãŒã¿ãŒã§ããåœé ããããšã¯ã§ããŸããã
ã©ã€ãã¯ã©ã€ã¢ã³ãã¯ã察象ã®ãããã¯ãã§ãŒã³ã®ããäžéšã®ã¿ããã¹ããããããã¯ãŒã¯ããŒãã§ãã ã¢ãã€ã«ã¢ããªã±ãŒã·ã§ã³ãŸãã¯Webãã©ãŠã¶ãŒã䜿çšããŠãããã¯ãã§ãŒã³ãšå¯Ÿè©±ã§ããŸãã ã¯ã©ã€ã¢ã³ãã¯ãAPIãä»ããŠå®å šã«æ©èœããããŒãã§1ã€ä»¥äžã®ãµãŒãã¹ãšãéä¿¡ãããŸã ã ãã®ãããªã·ã³ã¯ã©ã€ã¢ã³ãã®äœæ¥ã¯ãåã ã®ãµãŒãã¹ã«åºæã§ãããç¹å®ã®ãµãŒãã¹ãå¿ èŠãšããã»ã©å°é£ã«å®è£ ãããŸãã
Exonumã·ã³ã¯ã©ã€ã¢ã³ããšé£æºããŠãšããã³ã¹ãæ§ç¯ããããšã®æ¬è³ªã¯ããããã³ã€ã³ãããã¯ãã§ãŒã³ã«ãªã³ã¯ããŠãããšã³ããŠãŒã¶ãŒããã©ã€ããŒããããã¯ãã§ãŒã³ã®ãªãã¬ãŒã¿ãŒãä¿¡é Œã§ããªãå¯èœæ§ãããããšã§ãã ãããã圌ã衚瀺ããããŒã¿ã¯ããã®ç¹å®ã®ãã©ã€ããŒããããã¯ãã§ãŒã³ã§èŠå®ãããŠããã«ãŒã«ã«åŸã£ãŠååŸãããŠããããšã確èªã§ããŸãã
Exonumã®ã©ã€ãã¯ã©ã€ã¢ã³ãã®ã»ãã¥ãªãã£ã¯ãpermissionless-blockchainã«å¹æµãããã®ã§ãããåè¿°ã®ãããã³ã€ã³ãžã®ãã€ã³ããããããã¢ã³ã«ãŒãªã³ã°ã«ãã£ãŠä¿èšŒãããŠããŸãã ãµãŒãã¹ã¯ããã©ã³ã¶ã¯ã·ã§ã³èšŒææžã®åœ¢åŒã§ããããã¯ããã·ã¥ããããªãã¯ãããã³ã€ã³ãããã¯ãã§ãŒã³ã«å®æçã«éä¿¡ããŸãã ãã®å ŽåãExonumãããã¯ãã§ãŒã³ãæ©èœããªããªã£ãŠããããŒã¿ãæ€èšŒã§ããŸãã ããã«ããã®ãããªãããã¯ãŒã¯ãæ»æããã«ã¯ãæ»æè ã¯äž¡æ¹ã®ãããã¯ãã§ãŒã³ã®ä¿è·ã¡ã«ããºã ãå æããå¿ èŠããããããã«ã¯éåžžã«å€§ããªèšç®èœåãå¿ èŠã§ãã
ãããŠæåŸã«ããµãŒãã¹ã¯Exonumãã¬ãŒã ã¯ãŒã¯ã®åºç€ã§ã ã ä»ã®ãã©ãããã©ãŒã äžã®ã¹ããŒãã³ã³ãã©ã¯ãã«äŒŒãŠããããããã¯ãã§ãŒã³ã¢ããªã±ãŒã·ã§ã³ã®ããžãã¹ããžãã¯ãå«ãŸããŠããŸãã ãã ããã¹ããŒãã³ã³ãã©ã¯ããšã¯ç°ãªããExonumã®ãµãŒãã¹ã¯ä»®æ³ãã·ã³ã§ãããã¯ããããŠããããã³ã³ããåãããŠããŸããã
ããã«ãããããå¹ççãã€æè»ã«ãªããŸãã ãã ãããã®ã¢ãããŒãã§ã¯ãããã°ã©ãã³ã°æã«ããã«æ³šæãå¿ èŠã§ãïŒãµãŒãã¹ã®åé¢ã¯ExonumããŒããããã§ããŒã¯ãããŠããŸãïŒã ãµãŒãã¹ã¯ãã©ã³ã¶ã¯ã·ã§ã³ãåŠçããããã®ã«ãŒã«ã決å®ããããŒã¿ãžã®ã¢ã¯ã»ã¹ãå€éšã¯ã©ã€ã¢ã³ãã«æäŸããŸã ã
äž»èŠãªã³ã³ããŒãã³ãã«ç²ŸéããŠããã®ã§ãäŸã®åæã«é²ãããšãã§ããŸãã
Exonumã§ã®ãµãŒãã¹ã®äœæ
ExonumããŒãžã§ã³0.3ã®ãªãªãŒã¹ã¯11æ2æ¥ã«è¡ãããè¿œå ã®ã¬ã€ãã¯ã·ã¹ãã ã®å€æŽãšæ¹åãèæ ®ããŠæžãããŠããŸãïŒGitHubã®ãªããžããªã§ç¢ºèªã§ããŸãïŒã æå·é貚ãå®è£ ãã1ã€ã®ããŒãã§ãããã¯ãã§ãŒã³ãäœæããŸãã ãããã¯ãŒã¯ã¯ãããŠã©ã¬ãããäœæããããšã1ã€ã®ãŠã©ã¬ããããå¥ã®ãŠã©ã¬ããã«è³éã転éããããšãã2çš®é¡ã®ãã©ã³ã¶ã¯ã·ã§ã³ãåãå ¥ããŸãã
Exonumã¯Rustã§èšè¿°ãããŠãããããã³ã³ãã€ã©ãã€ã³ã¹ããŒã«ããå¿ èŠããããŸãã ãããè¡ãã«ã¯ã ã¬ã€ãã䜿çšã§ããŸãã
ããŒãäœæ
ãŸããæ°ããã¯ã¬ãŒããäœæããŸãïŒ
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"
å¿ èŠãªã¿ã€ãã®ã¯ã¬ãŒããã€ã³ããŒãããŸãã ãããè¡ãã«ã¯ã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;
ãããŠäž»ãªæ©èœïŒ
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ããŸã ãããŸããã ããŒããæ§æããå¿ èŠããããŸã ã æ§æã§ã¯ã ããªããŒã¿ãŒã®å ¬éããŒã®ãªã¹ããæå®ããŸãïŒãã®äŸã§ã¯1ã€ã«ãªããŸãïŒã å®éãåããŒãã«ã¯ãå ¬ééµãšç§å¯éµã®2ã€ã®ãã¢ãå¿ èŠã§ãã1ã€ã¯ã³ã³ã»ã³ãµã¹ã«éããéçšã§ä»ã®ããŒããšå¯Ÿè©±ããããã®ãã®ã§ããã1ã€ã¯ãµãŒãã¹çšã§ãã ãã®äŸã§ã¯ã 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());
次ã«ãå€éšWebãªã¯ãšã¹ããåŠçããããã®REST APIãèšå®ããŸãããã®ããã«ããŒã8000ââãéããŸãããŸããExonumãããã¯ãŒã¯ã®ãã«ããŒããçžäºã«éä¿¡ã§ããããã«ããŒã2000ãéããŸãã
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ïŒ é åºä»ããããæ§é ã宣èšããå€ãã£ãŒã«ãã®å¢çãããŒã¯ããã®ã«åœ¹ç«ã¡ãŸãã ãŠã©ã¬ããã®æ®é«ãå€æŽããå¿ èŠãããããããŠã©ã¬ããã«ã¡ãœãããè¿œå ããŸã ïŒ
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] } }
2ã€ã®å ¬ééµãïŒäž¡æ¹ã®ãŠã©ã¬ããã«å¯ŸããŠïŒããã«ããŒã¯ããã転éãããã³ã€ã³ã®æ°ã ãã©ã³ã¶ã¯ã·ã§ã³ãç¹°ãè¿ãããšãã§ããªãããã« ã ã·ãŒããã£ãŒã«ããè¿œå ãããŸãã ãŸããéä¿¡è ãèªåã«è³éãéã£ãŠããªãããšã確èªããå¿ èŠããããŸãã
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, }
ãã³ãã©ãŒãWebãµãŒããŒã®HTTPãã³ãã©ãŒãšãåéã«ãããããšã¯å€ãããŸããã ãããè¡ãã«ã¯ã wireã¡ãœãããå®è£ ããŸãã 以äžã®äŸã§ã¯ãJSONå ¥åããã©ã³ã¶ã¯ã·ã§ã³ã«å€æãããã³ãã©ãŒãè¿œå ããŸãã
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ãå®è£ ããŸã
ãã©ã³ã¶ã¯ã·ã§ã³ãå®éã«å®è¡ãããŠããããšã確èªã§ããããã«ãã·ã¹ãã ã®ãã¹ãŠã®ãŠã©ã¬ããã«é¢ããæ å ±ãè¿ããããã³å ¬éããŒã«å¯Ÿå¿ããç¹å®ã®ãŠã©ã¬ããã«é¢ããæ å ±ã®ã¿ãè¿ããšãã2çš®é¡ã®èªã¿åãèŠæ±ãå®è£ ããŸãã
ãããè¡ãã«ã¯ããããã¯ãã§ãŒã³ãã£ãŒã«ãã«ã¢ã¯ã»ã¹ããŠãããã¯ãã§ãŒã³ã¹ãã¬ãŒãžããæ å ±ãèªã¿åãCryptocurrencyApiã®ã¡ãœãããããã€ãå®çŸ©ããŸãã
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ã¡ãœããã䜿çšãããšããäºå®ã«æ³šæãæã䟡å€ããããŸãã å®éã®ç¶æ ã§ã¯ãèªã¿åãå°çšã®ã¢ã¯ã»ã¹åœ¢åŒïŒã¹ãããã·ã§ãããåç §ïŒã䜿çšããããšããå§ãããŸãã
ããã«ããã©ã³ã¶ã¯ã·ã§ã³ã«ã€ããŠã¯ã CryptocurrencyApi :: wireïŒïŒã®get_walletsïŒïŒããã³get_walletïŒïŒ ã¡ãœããã䜿çšããŠãªã¯ãšã¹ãåŠçãè¿œå ããŸãã
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ãš ãäžæã®IDãè¿ãservice_idã® 2ã€ã®ã¡ãœããããããŸãã
tx_from_rawã¡ãœããã¯ããã©ã³ã¶ã¯ã·ã§ã³ã®éã·ãªã¢ã«åã«äœ¿çšããã public_api_handlerã¡ãœããã¯ããµã€ããžã®WebèŠæ±ãåŠçããããã®RESTãã³ãã©ãŒã®äœæã«äœ¿çšãããŸãã 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 }
2çªç®ã®ãŠã©ã¬ãããåãããã«åœ¢æãããŸãã äœæåŸãè³éã転éã§ããŸãã 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åã®ã³ã€ã³ãæåã®ãŠã©ã¬ãããã2çªç®ã®ãŠã©ã¬ããã«è»¢éããŸãã 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" } ]
2çªç®ã®ãšã³ããã€ã³ããæ©èœããŸãã ããã確èªããã«ã¯ã次ã®ãªã¯ãšã¹ããéä¿¡ããŸãã
curl "http://127.0.0.1:8000/api/services/cryptocurrency/v1/wallet/\ 03e657ae71e51be60a45b4bd20bcf79ff52f0c037ae6da0540a0e0066132b472"
çããåŸãããŸãã
{ "balance": "90", "name": "Johnny Doe", "pub_key": "03e657ae71e51be60a45b4bd20bcf79ff52f0c037ae6da0540a0e0066132b472" }
ãããã£ãŠãææã®äžéšãšããŠãåçŽãªãããã¯ãã§ãŒã³ã1ã€ã®ããªããŒã¿ãŒãšã©ã®ããã«æ©èœããããèŠã€ããŸããã 次ã®æçš¿ã§ã¯ãExonumã®ãããã¯ãã§ãŒã³ãã€ã³ãã£ã³ã°ãããŒã管çãã³ã³ã»ã³ãµã¹ã«ã€ããŠè©³ãã説æããŸãã