From 8c3c6d42848d155f25ba955aaef36e726a2e9d41 Mon Sep 17 00:00:00 2001 From: SeongChan Lee Date: Fri, 15 Nov 2019 17:14:00 +0900 Subject: [PATCH 1/3] Implement snapshot service --- codechain/run_node.rs | 14 ++++ core/src/client/mod.rs | 1 + core/src/client/snapshot_notify.rs | 81 +++++++++++++++++++++++ core/src/consensus/mod.rs | 3 + core/src/lib.rs | 1 + sync/src/lib.rs | 1 + sync/src/snapshot/mod.rs | 101 +++++++++++++++++++++++++++++ util/merkle/src/snapshot/error.rs | 26 ++++++++ util/merkle/src/snapshot/mod.rs | 2 + 9 files changed, 230 insertions(+) create mode 100644 core/src/client/snapshot_notify.rs create mode 100644 sync/src/snapshot/mod.rs diff --git a/codechain/run_node.rs b/codechain/run_node.rs index ea31281846..a2fe31670f 100644 --- a/codechain/run_node.rs +++ b/codechain/run_node.rs @@ -19,6 +19,7 @@ use std::path::Path; use std::sync::{Arc, Weak}; use std::time::{SystemTime, UNIX_EPOCH}; +use ccore::snapshot_notify; use ccore::{ AccountProvider, AccountProviderError, BlockId, ChainNotify, Client, ClientConfig, ClientService, EngineInfo, EngineType, Miner, MinerService, Scheme, Stratum, StratumConfig, StratumError, NUM_COLUMNS, @@ -30,6 +31,7 @@ use ckeystore::KeyStore; use clap::ArgMatches; use clogger::{self, EmailAlarm, EmailAlarmConfig, LoggerConfig}; use cnetwork::{Filters, NetworkConfig, NetworkControl, NetworkService, RoutingTable, SocketAddr}; +use csync::snapshot::Service as SnapshotService; use csync::{BlockSyncExtension, BlockSyncSender, TransactionSyncExtension}; use ctimer::TimerLoop; use ctrlc::CtrlC; @@ -360,6 +362,18 @@ pub fn run_node(matches: &ArgMatches) -> Result<(), String> { stratum_start(&config.stratum_config(), &miner, client.client())? } + let _snapshot_service = { + if !config.snapshot.disable.unwrap() { + let client = client.client(); + let (tx, rx) = snapshot_notify::create(); + client.engine().register_snapshot_notify_sender(tx); + let service = Arc::new(SnapshotService::new(client, rx, config.snapshot.path.unwrap())); + Some(service) + } else { + None + } + }; + // drop the scheme to free up genesis state. drop(scheme); diff --git a/core/src/client/mod.rs b/core/src/client/mod.rs index ac14869656..43054c7470 100644 --- a/core/src/client/mod.rs +++ b/core/src/client/mod.rs @@ -20,6 +20,7 @@ mod client; mod config; mod error; mod importer; +pub mod snapshot_notify; mod test_client; pub use self::chain_notify::ChainNotify; diff --git a/core/src/client/snapshot_notify.rs b/core/src/client/snapshot_notify.rs new file mode 100644 index 0000000000..8e0a372cbb --- /dev/null +++ b/core/src/client/snapshot_notify.rs @@ -0,0 +1,81 @@ +use ctypes::BlockHash; + +use parking_lot::RwLock; +use std::sync::mpsc::{sync_channel, Receiver, RecvError, SyncSender}; +use std::sync::{Arc, Weak}; + +pub fn create() -> (NotifySender, NotifyReceiverSource) { + let (tx, rx) = sync_channel(1); + let tx = Arc::new(RwLock::new(Some(tx))); + let tx_weak = Arc::downgrade(&tx); + ( + NotifySender { + tx, + }, + NotifyReceiverSource( + ReceiverCanceller { + tx: tx_weak, + }, + NotifyReceiver { + rx, + }, + ), + ) +} + +pub struct NotifySender { + tx: Arc>>>, +} + +impl NotifySender { + pub fn notify(&self, block_hash: BlockHash) { + let guard = self.tx.read(); + if let Some(tx) = guard.as_ref() { + // TODO: Ignore the error. Receiver thread might be terminated or congested. + let _ = tx.try_send(block_hash); + } else { + // ReceiverCanceller is dropped. + } + } +} + +pub struct NotifyReceiverSource(pub ReceiverCanceller, pub NotifyReceiver); + +/// Dropping this makes the receiver stopped. +/// +/// `recv()` method of the `Receiver` will stop and return `RecvError` when corresponding `Sender` is dropped. +/// This is an inherited behaviour of `std::sync::mpsc::{Sender, Receiver}`. +/// However, we need another way to stop the `Receiver`, since `Sender` is usually shared throughout our codes. +/// We can't collect them all and destory one by one. We need a kill switch. +/// +/// `ReceiverCanceller` holds weak reference to the `Sender`, so it doesn't prohibit the default behaviour. +/// Then, we can upgrade the weak reference and get the shared reference to `Sender` itself, and manually drop it with this. +pub struct ReceiverCanceller { + tx: Weak>>>, +} + +impl Drop for ReceiverCanceller { + fn drop(&mut self) { + if let Some(tx) = self.tx.upgrade() { + let mut guard = tx.write(); + if let Some(sender) = guard.take() { + drop(sender) + } + } else { + // All NotifySender is dropped. No droppable Sender. + } + } +} + +/// Receiver is dropped when +/// 1. There are no NotifySenders out there. +/// 2. ReceiverCanceller is dropped. See the comment of `ReceiverCanceller`. +pub struct NotifyReceiver { + rx: Receiver, +} + +impl NotifyReceiver { + pub fn recv(&self) -> Result { + self.rx.recv() + } +} diff --git a/core/src/consensus/mod.rs b/core/src/consensus/mod.rs index 8ba08b2069..3c15f88b58 100644 --- a/core/src/consensus/mod.rs +++ b/core/src/consensus/mod.rs @@ -51,6 +51,7 @@ use primitives::{Bytes, U256}; use self::bit_set::BitSet; use crate::account_provider::AccountProvider; use crate::block::{ExecutedBlock, SealedBlock}; +use crate::client::snapshot_notify::NotifySender as SnapshotNotifySender; use crate::client::ConsensusClient; use crate::codechain_machine::CodeChainMachine; use crate::error::Error; @@ -262,6 +263,8 @@ pub trait ConsensusEngine: Sync + Send { fn register_chain_notify(&self, _: &Client) {} + fn register_snapshot_notify_sender(&self, _sender: SnapshotNotifySender) {} + fn get_best_block_from_best_proposal_header(&self, header: &HeaderView) -> BlockHash { header.hash() } diff --git a/core/src/lib.rs b/core/src/lib.rs index 26cf864d8f..257bf2cfc2 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -84,6 +84,7 @@ mod tests; pub use crate::account_provider::{AccountProvider, Error as AccountProviderError}; pub use crate::block::Block; +pub use crate::client::snapshot_notify; pub use crate::client::Error::Database; pub use crate::client::{ AccountData, AssetClient, BlockChainClient, BlockChainTrait, ChainNotify, Client, ClientConfig, DatabaseClient, diff --git a/sync/src/lib.rs b/sync/src/lib.rs index b89deb036d..5a0b1c5910 100644 --- a/sync/src/lib.rs +++ b/sync/src/lib.rs @@ -46,6 +46,7 @@ extern crate trie_standardmap; extern crate util_error; mod block; +pub mod snapshot; mod transaction; pub use crate::block::{BlockSyncEvent, BlockSyncExtension, BlockSyncSender}; diff --git a/sync/src/snapshot/mod.rs b/sync/src/snapshot/mod.rs new file mode 100644 index 0000000000..79b55e70b0 --- /dev/null +++ b/sync/src/snapshot/mod.rs @@ -0,0 +1,101 @@ +// Copyright 2019 Kodebox, Inc. +// This file is part of CodeChain. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use std::fs::{create_dir_all, File}; +use std::path::PathBuf; +use std::sync::Arc; +use std::thread::{spawn, JoinHandle}; + +use ccore::snapshot_notify::{NotifyReceiverSource, ReceiverCanceller}; +use ccore::{BlockChainTrait, BlockId, Client}; +use cmerkle::snapshot::{ChunkCompressor, Error as SnapshotError, Snapshot}; +use ctypes::BlockHash; +use hashdb::{AsHashDB, HashDB}; +use primitives::H256; +use std::ops::Deref; + +pub struct Service { + join_handle: Option>, + canceller: Option, +} + +impl Service { + pub fn new(client: Arc, notify_receiver_source: NotifyReceiverSource, root_dir: String) -> Self { + let NotifyReceiverSource(canceller, receiver) = notify_receiver_source; + let join_handle = spawn(move || { + cinfo!(SYNC, "Snapshot service is on"); + while let Ok(block_hash) = receiver.recv() { + cinfo!(SYNC, "Snapshot is requested for block: {}", block_hash); + let state_root = if let Some(header) = client.block_header(&BlockId::Hash(block_hash)) { + header.state_root() + } else { + cerror!(SYNC, "There isn't corresponding header for the requested block hash: {}", block_hash,); + continue + }; + let db_lock = client.state_db().read(); + if let Some(err) = snapshot(db_lock.as_hashdb(), block_hash, state_root, &root_dir).err() { + cerror!( + SYNC, + "Snapshot request failed for block: {}, chunk_root: {}, err: {}", + block_hash, + state_root, + err + ); + } + } + cinfo!(SYNC, "Snapshot service is stopped") + }); + + Self { + canceller: Some(canceller), + join_handle: Some(join_handle), + } + } +} + +fn snapshot(db: &dyn HashDB, block_hash: BlockHash, chunk_root: H256, root_dir: &str) -> Result<(), SnapshotError> { + let snapshot_dir = { + let mut res = PathBuf::new(); + res.push(root_dir); + res.push(format!("{:x}", block_hash.deref())); + res + }; + create_dir_all(&snapshot_dir)?; + + for chunk in Snapshot::from_hashdb(db, chunk_root) { + let mut chunk_path = snapshot_dir.clone(); + chunk_path.push(format!("{:x}", chunk.root)); + let chunk_file = File::create(chunk_path)?; + let compressor = ChunkCompressor::new(chunk_file); + compressor.compress_chunk(&chunk)?; + } + + Ok(()) +} + +impl Drop for Service { + fn drop(&mut self) { + if let Some(canceller) = self.canceller.take() { + // The thread corresponding to the `self.join_handle` waits for the `self.canceller` is dropped. + // It must be dropped first not to make deadlock at `handle.join()`. + drop(canceller); + } + + if let Some(handle) = self.join_handle.take() { + handle.join().expect("Snapshot service thread shouldn't panic"); + } + } +} diff --git a/util/merkle/src/snapshot/error.rs b/util/merkle/src/snapshot/error.rs index 19f6876b06..5077312818 100644 --- a/util/merkle/src/snapshot/error.rs +++ b/util/merkle/src/snapshot/error.rs @@ -20,6 +20,7 @@ use primitives::H256; use rlp::DecoderError as RlpDecoderError; use crate::TrieError; +use std::fmt::{Display, Formatter}; #[derive(Debug)] pub enum Error { @@ -53,6 +54,17 @@ impl From for Error { } } +impl Display for Error { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { + match self { + Error::IoError(err) => write!(f, "IoError: {}", err), + Error::RlpDecoderError(err) => write!(f, "RlpDecoderError: {}", err), + Error::TrieError(err) => write!(f, "TrieError: {}", err), + Error::ChunkError(err) => write!(f, "ChunkError: {}", err), + } + } +} + #[derive(Debug)] pub enum ChunkError { TooBig, @@ -63,3 +75,17 @@ pub enum ChunkError { }, InvalidContent, } + +impl Display for ChunkError { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { + match self { + ChunkError::TooBig => write!(f, "Chunk has too many elements"), + ChunkError::InvalidHeight => write!(f, "Chunk height is unexpected height"), + ChunkError::ChunkRootMismatch { + expected, + actual, + } => write!(f, "Chunk root is different from expected. expected: {}, actual: {}", expected, actual), + ChunkError::InvalidContent => write!(f, "Chunk content is invalid"), + } + } +} diff --git a/util/merkle/src/snapshot/mod.rs b/util/merkle/src/snapshot/mod.rs index 914e87fbd8..c3ff1a5980 100644 --- a/util/merkle/src/snapshot/mod.rs +++ b/util/merkle/src/snapshot/mod.rs @@ -26,6 +26,8 @@ use hashdb::HashDB; use primitives::H256; use self::chunk::{Chunk, RecoveredChunk, UnresolvedChunk}; +pub use self::compress::{ChunkCompressor, ChunkDecompressor}; +pub use self::error::Error; use self::ordered_heap::OrderedHeap; use crate::nibbleslice::NibbleSlice; From 21a58c9b815a7b97e870c1c528aecc8b08cb8129 Mon Sep 17 00:00:00 2001 From: SeongChan Lee Date: Fri, 15 Nov 2019 20:41:04 +0900 Subject: [PATCH 2/3] Add RPC devel_snapshot --- core/src/client/client.rs | 10 +++++++++- core/src/client/mod.rs | 4 ++++ core/src/consensus/mod.rs | 2 ++ core/src/consensus/solo/mod.rs | 18 +++++++++++++++++- core/src/lib.rs | 4 ++-- rpc/src/v1/impls/devel.rs | 11 ++++++++--- rpc/src/v1/traits/devel.rs | 4 ++++ spec/JSON-RPC.md | 28 ++++++++++++++++++++++++++++ 8 files changed, 74 insertions(+), 7 deletions(-) diff --git a/core/src/client/client.rs b/core/src/client/client.rs index 3f6306bc66..9b03a0c83f 100644 --- a/core/src/client/client.rs +++ b/core/src/client/client.rs @@ -45,7 +45,7 @@ use super::{ }; use crate::block::{ClosedBlock, IsBlock, OpenBlock, SealedBlock}; use crate::blockchain::{BlockChain, BlockProvider, BodyProvider, HeaderProvider, InvoiceProvider, TransactionAddress}; -use crate::client::{ConsensusClient, TermInfo}; +use crate::client::{ConsensusClient, SnapshotClient, TermInfo}; use crate::consensus::{CodeChainEngine, EngineError}; use crate::encoded; use crate::error::{BlockImportError, Error, ImportError, SchemeError}; @@ -948,3 +948,11 @@ impl FindActionHandler for Client { self.engine.find_action_handler_for(id) } } + +impl SnapshotClient for Client { + fn notify_snapshot(&self, id: BlockId) { + if let Some(header) = self.block_header(&id) { + self.engine.send_snapshot_notify(header.hash()) + } + } +} diff --git a/core/src/client/mod.rs b/core/src/client/mod.rs index 43054c7470..82f39070b7 100644 --- a/core/src/client/mod.rs +++ b/core/src/client/mod.rs @@ -347,3 +347,7 @@ pub trait StateInfo { /// is unknown. fn state_at(&self, id: BlockId) -> Option; } + +pub trait SnapshotClient { + fn notify_snapshot(&self, id: BlockId); +} diff --git a/core/src/consensus/mod.rs b/core/src/consensus/mod.rs index 3c15f88b58..f343634a3e 100644 --- a/core/src/consensus/mod.rs +++ b/core/src/consensus/mod.rs @@ -265,6 +265,8 @@ pub trait ConsensusEngine: Sync + Send { fn register_snapshot_notify_sender(&self, _sender: SnapshotNotifySender) {} + fn send_snapshot_notify(&self, _block_hash: BlockHash) {} + fn get_best_block_from_best_proposal_header(&self, header: &HeaderView) -> BlockHash { header.hash() } diff --git a/core/src/consensus/solo/mod.rs b/core/src/consensus/solo/mod.rs index 915e6485f9..93aa13e495 100644 --- a/core/src/consensus/solo/mod.rs +++ b/core/src/consensus/solo/mod.rs @@ -20,12 +20,14 @@ use std::sync::Arc; use ckey::Address; use cstate::{ActionHandler, HitHandler}; -use ctypes::{CommonParams, Header}; +use ctypes::{BlockHash, CommonParams, Header}; +use parking_lot::RwLock; use self::params::SoloParams; use super::stake; use super::{ConsensusEngine, Seal}; use crate::block::{ExecutedBlock, IsBlock}; +use crate::client::snapshot_notify::NotifySender; use crate::codechain_machine::CodeChainMachine; use crate::consensus::{EngineError, EngineType}; use crate::error::Error; @@ -35,6 +37,7 @@ pub struct Solo { params: SoloParams, machine: CodeChainMachine, action_handlers: Vec>, + snapshot_notify_sender: Arc>>, } impl Solo { @@ -50,6 +53,7 @@ impl Solo { params, machine, action_handlers, + snapshot_notify_sender: Arc::new(RwLock::new(None)), } } } @@ -135,6 +139,18 @@ impl ConsensusEngine for Solo { 1 } + fn register_snapshot_notify_sender(&self, sender: NotifySender) { + let mut guard = self.snapshot_notify_sender.write(); + assert!(guard.is_none(), "snapshot_notify_sender is registered twice"); + *guard = Some(sender); + } + + fn send_snapshot_notify(&self, block_hash: BlockHash) { + if let Some(sender) = self.snapshot_notify_sender.read().as_ref() { + sender.notify(block_hash) + } + } + fn action_handlers(&self) -> &[Arc] { &self.action_handlers } diff --git a/core/src/lib.rs b/core/src/lib.rs index 257bf2cfc2..3b6138a2e8 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -88,8 +88,8 @@ pub use crate::client::snapshot_notify; pub use crate::client::Error::Database; pub use crate::client::{ AccountData, AssetClient, BlockChainClient, BlockChainTrait, ChainNotify, Client, ClientConfig, DatabaseClient, - EngineClient, EngineInfo, ExecuteClient, ImportBlock, MiningBlockChainClient, Shard, StateInfo, TermInfo, - TestBlockChainClient, TextClient, + EngineClient, EngineInfo, ExecuteClient, ImportBlock, MiningBlockChainClient, Shard, SnapshotClient, StateInfo, + TermInfo, TestBlockChainClient, TextClient, }; pub use crate::consensus::{EngineType, TimeGapParams}; pub use crate::db::{COL_STATE, NUM_COLUMNS}; diff --git a/rpc/src/v1/impls/devel.rs b/rpc/src/v1/impls/devel.rs index 0fa2e47a05..b101714456 100644 --- a/rpc/src/v1/impls/devel.rs +++ b/rpc/src/v1/impls/devel.rs @@ -23,7 +23,7 @@ use std::vec::Vec; use ccore::{ BlockId, DatabaseClient, EngineClient, EngineInfo, MinerService, MiningBlockChainClient, SignedTransaction, - TermInfo, COL_STATE, + SnapshotClient, TermInfo, COL_STATE, }; use ccrypto::Blake; use cjson::bytes::Bytes; @@ -33,7 +33,7 @@ use csync::BlockSyncEvent; use ctypes::transaction::{ Action, AssetMintOutput, AssetOutPoint, AssetTransferInput, AssetTransferOutput, Transaction, }; -use ctypes::{Tracker, TxHash}; +use ctypes::{BlockHash, Tracker, TxHash}; use jsonrpc_core::Result; use kvdb::KeyValueDB; use primitives::{H160, H256}; @@ -70,7 +70,7 @@ where impl Devel for DevelClient where - C: DatabaseClient + EngineInfo + EngineClient + MiningBlockChainClient + TermInfo + 'static, + C: DatabaseClient + EngineInfo + EngineClient + MiningBlockChainClient + TermInfo + SnapshotClient + 'static, M: MinerService + 'static, { fn get_state_trie_keys(&self, offset: usize, limit: usize) -> Result> { @@ -108,6 +108,11 @@ where } } + fn snapshot(&self, block_hash: BlockHash) -> Result<()> { + self.client.notify_snapshot(BlockId::Hash(block_hash)); + Ok(()) + } + fn test_tps(&self, setting: TPSTestSetting) -> Result { let common_params = self.client.common_params(BlockId::Latest).unwrap(); let mint_fee = common_params.min_asset_mint_cost(); diff --git a/rpc/src/v1/traits/devel.rs b/rpc/src/v1/traits/devel.rs index e8604e910e..565a331976 100644 --- a/rpc/src/v1/traits/devel.rs +++ b/rpc/src/v1/traits/devel.rs @@ -17,6 +17,7 @@ use std::net::SocketAddr; use cjson::bytes::Bytes; +use ctypes::BlockHash; use jsonrpc_core::Result; use primitives::H256; @@ -39,6 +40,9 @@ pub trait Devel { #[rpc(name = "devel_getBlockSyncPeers")] fn get_block_sync_peers(&self) -> Result>; + #[rpc(name = "devel_snapshot")] + fn snapshot(&self, hash: BlockHash) -> Result<()>; + #[rpc(name = "devel_testTPS")] fn test_tps(&self, setting: TPSTestSetting) -> Result; } diff --git a/spec/JSON-RPC.md b/spec/JSON-RPC.md index cefee266dc..870192c22b 100644 --- a/spec/JSON-RPC.md +++ b/spec/JSON-RPC.md @@ -367,6 +367,7 @@ When `Transaction` is included in any response, there will be an additional fiel *** * [devel_getStateTrieKeys](#devel_getstatetriekeys) * [devel_getStateTrieValue](#devel_getstatetrievalue) + * [devel_snapshot](#devel_snapshot) * [devel_startSealing](#devel_startsealing) * [devel_stopSealing](#devel_stopsealing) * [devel_getBlockSyncPeers](#devel_getblocksyncpeers) @@ -2979,6 +2980,33 @@ Gets the value of the state trie with the given key. [Back to **List of methods**](#list-of-methods) +## devel_snapshot +Snapshot the state of the given block hash. + +### Params + 1. key: `H256` + +### Returns + +### Request Example +``` + curl \ + -H 'Content-Type: application/json' \ + -d '{"jsonrpc": "2.0", "method": "devel_snapshot", "params": ["0xfc196ede542b03b55aee9f106004e7e3d7ea6a9600692e964b4735a260356b50"], "id": null}' \ + localhost:8080 +``` + +### Response Example +``` +{ + "jsonrpc":"2.0", + "result":[], + "id":null +} +``` + +[Back to **List of methods**](#list-of-methods) + ## devel_startSealing Starts and enables sealing blocks by the miner. From 8f7b484ed408230261f6f5f3a4b50f01a877c9af Mon Sep 17 00:00:00 2001 From: SeongChan Lee Date: Mon, 18 Nov 2019 14:52:26 +0900 Subject: [PATCH 3/3] Add basic e2e test for snapshot --- test/src/e2e/snapshot.test.ts | 72 +++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 test/src/e2e/snapshot.test.ts diff --git a/test/src/e2e/snapshot.test.ts b/test/src/e2e/snapshot.test.ts new file mode 100644 index 0000000000..0c1c5cf794 --- /dev/null +++ b/test/src/e2e/snapshot.test.ts @@ -0,0 +1,72 @@ +// Copyright 2018-2019 Kodebox, Inc. +// This file is part of CodeChain. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +import { expect } from "chai"; +import * as fs from "fs"; +import "mocha"; +import * as path from "path"; + +import { aliceAddress } from "../helper/constants"; +import CodeChain from "../helper/spawn"; + +const SNAPSHOT_PATH = `${__dirname}/../../../snapshot/`; + +describe("Snapshot", async function() { + let node: CodeChain; + before(async function() { + node = new CodeChain({ + argv: ["--snapshot-path", SNAPSHOT_PATH] + }); + await node.start(); + }); + + it("can make a snapshot when it is requsted with devel rpc", async function() { + const pay = await node.sendPayTx({ + quantity: 100, + recipient: aliceAddress + }); + + const blockHash = (await node.sdk.rpc.chain.getTransaction(pay.hash()))! + .blockHash!; + await node.sdk.rpc.sendRpcRequest("devel_snapshot", [ + blockHash.toJSON() + ]); + // Wait for 1 secs + await new Promise(resolve => setTimeout(resolve, 1000)); + + const stateRoot = (await node.sdk.rpc.chain.getBlock(blockHash))! + .stateRoot; + expect( + fs.existsSync( + path.join( + SNAPSHOT_PATH, + blockHash.toString(), + stateRoot.toString() + ) + ) + ); + }); + + afterEach(function() { + if (this.currentTest!.state === "failed") { + node.keepLogs(); + } + }); + + after(async function() { + await node.clean(); + }); +});