diff --git a/.github/actions/bitcoin-int-tests/Dockerfile.integrations b/.github/actions/bitcoin-int-tests/Dockerfile.integrations index c875c06de..99d965a28 100644 --- a/.github/actions/bitcoin-int-tests/Dockerfile.integrations +++ b/.github/actions/bitcoin-int-tests/Dockerfile.integrations @@ -12,7 +12,7 @@ ENV RUSTFLAGS="-Zinstrument-coverage" \ COPY . . RUN cargo build --workspace && \ - cargo test --workspace --bin=stacks-node -- --ignored --test-threads 1 + cargo test --workspace --bin=hyperchain-node -- --ignored --test-threads 1 # Generate coverage report and upload it to codecov RUN grcov . --binary-path ./target/debug/ -s . -t lcov --branch --ignore-not-existing --ignore "/*" -o lcov.info diff --git a/.github/actions/bitcoin-int-tests/Dockerfile.stacks-node b/.github/actions/bitcoin-int-tests/Dockerfile.stacks-node new file mode 100644 index 000000000..339942315 --- /dev/null +++ b/.github/actions/bitcoin-int-tests/Dockerfile.stacks-node @@ -0,0 +1,26 @@ +FROM blockstack/stacks-blockchain:2.05.0.1.0-stretch as stacks-node + +FROM rust:stretch AS test + +WORKDIR /build + +RUN rustup override set nightly-2022-01-14 && \ + rustup component add llvm-tools-preview && \ + cargo install grcov + +ENV RUSTFLAGS="-Zinstrument-coverage" \ + LLVM_PROFILE_FILE="stacks-blockchain-%p-%m.profraw" \ + STACKS_NODE_TEST="1" + +COPY --from=stacks-node /bin/stacks-node /bin/ + +COPY . . + +RUN cargo build --workspace && \ + cargo test --workspace --bin=hyperchain-node -- l1_observer_test --test-threads 1 + +# Generate coverage report and upload it to codecov +RUN grcov . --binary-path ./target/debug/ -s . -t lcov --branch --ignore-not-existing --ignore "/*" -o lcov.info + +FROM scratch AS export-stage +COPY --from=test /build/lcov.info / diff --git a/.github/workflows/stacks-blockchain.yml b/.github/workflows/stacks-blockchain.yml index 78edfbe18..26afaa6fe 100644 --- a/.github/workflows/stacks-blockchain.yml +++ b/.github/workflows/stacks-blockchain.yml @@ -53,7 +53,23 @@ jobs: files: ./coverage-output/lcov.info name: unit_tests fail_ci_if_error: true - + # Run tests that require stacks-node + layer-1-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Run units tests (with coverage) + env: + DOCKER_BUILDKIT: 1 + # Remove .dockerignore file so codecov has access to git info + run: | + rm .dockerignore + docker build -o coverage-output -f ./.github/actions/bitcoin-int-tests/Dockerfile.stacks-node . + - uses: codecov/codecov-action@v2 + with: + files: ./coverage-output/lcov.info + name: integration_tests + fail_ci_if_error: true # Run integration tests integration-tests: runs-on: ubuntu-latest diff --git a/Cargo.lock b/Cargo.lock index b3732f344..06b3c64fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1256,6 +1256,35 @@ dependencies = [ "tokio-native-tls", ] +[[package]] +name = "hyperchain-node" +version = "0.1.0" +dependencies = [ + "async-h1", + "async-std", + "backtrace", + "base64 0.12.3", + "blockstack-core", + "clarity", + "http-types", + "lazy_static", + "libc", + "pico-args", + "rand 0.7.3", + "reqwest", + "ring", + "rusqlite", + "serde", + "serde_derive", + "serde_json", + "slog", + "stacks-common", + "stx-genesis", + "tokio", + "toml", + "warp", +] + [[package]] name = "idna" version = "0.2.3" @@ -2670,35 +2699,6 @@ dependencies = [ "time 0.2.27", ] -[[package]] -name = "stacks-node" -version = "0.1.0" -dependencies = [ - "async-h1", - "async-std", - "backtrace", - "base64 0.12.3", - "blockstack-core", - "clarity", - "http-types", - "lazy_static", - "libc", - "pico-args", - "rand 0.7.3", - "reqwest", - "ring", - "rusqlite", - "serde", - "serde_derive", - "serde_json", - "slog", - "stacks-common", - "stx-genesis", - "tokio", - "toml", - "warp", -] - [[package]] name = "standback" version = "0.2.17" diff --git a/src/burnchains/mod.rs b/src/burnchains/mod.rs index 749a804b7..cc0c58dae 100644 --- a/src/burnchains/mod.rs +++ b/src/burnchains/mod.rs @@ -101,6 +101,7 @@ pub struct BurnchainParameters { impl BurnchainParameters { pub fn from_params(chain: &str, network: &str) -> Option { match (chain, network) { + ("mockstack", "mainnet") => Some(BurnchainParameters::hyperchain_mocknet()), ("bitcoin", "mainnet") => Some(BurnchainParameters::bitcoin_mainnet()), ("bitcoin", "testnet") => Some(BurnchainParameters::bitcoin_testnet()), ("bitcoin", "regtest") => Some(BurnchainParameters::bitcoin_regtest()), @@ -110,7 +111,7 @@ impl BurnchainParameters { pub fn hyperchain_mocknet() -> BurnchainParameters { BurnchainParameters { - chain_name: "bitcoin".to_string(), + chain_name: "mockstack".to_string(), network_name: "mainnet".into(), network_id: 0, stable_confirmations: 0, diff --git a/src/chainstate/stacks/boot/contract_tests.rs b/src/chainstate/stacks/boot/contract_tests.rs index 6241da9a3..7c016fda8 100644 --- a/src/chainstate/stacks/boot/contract_tests.rs +++ b/src/chainstate/stacks/boot/contract_tests.rs @@ -35,7 +35,10 @@ use vm::eval; use vm::representations::SymbolicExpression; use vm::test_util::{execute, symbols_from_values, TEST_BURN_STATE_DB, TEST_HEADER_DB}; use vm::types::Value::Response; -use vm::types::{OptionalData, PrincipalData, QualifiedContractIdentifier, ResponseData, StandardPrincipalData, TupleData, TupleTypeSignature, TypeSignature, Value, NONE}; +use vm::types::{ + OptionalData, PrincipalData, QualifiedContractIdentifier, ResponseData, StandardPrincipalData, + TupleData, TupleTypeSignature, TypeSignature, Value, NONE, +}; use crate::{ burnchains::PoxConstants, diff --git a/testnet/stacks-node/Cargo.toml b/testnet/stacks-node/Cargo.toml index 066bed528..8cb8725d4 100644 --- a/testnet/stacks-node/Cargo.toml +++ b/testnet/stacks-node/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "stacks-node" +name = "hyperchain-node" version = "0.1.0" authors = ["Ludo Galabru "] edition = "2018" @@ -35,7 +35,7 @@ version = "=0.24.2" features = ["blob", "serde_json", "i128_blob", "bundled", "trace"] [[bin]] -name = "stacks-node" +name = "hyperchain-node" path = "src/main.rs" [features] diff --git a/testnet/stacks-node/src/burnchains/l1_events.rs b/testnet/stacks-node/src/burnchains/l1_events.rs index 84acdf365..36d0ba7a1 100644 --- a/testnet/stacks-node/src/burnchains/l1_events.rs +++ b/testnet/stacks-node/src/burnchains/l1_events.rs @@ -4,24 +4,20 @@ use std::sync::{Arc, Mutex}; use std::time::Instant; use stacks::burnchains::db::BurnchainDB; -use stacks::burnchains::events::{ContractEvent, NewBlockTxEvent}; -use stacks::burnchains::events::{NewBlock, TxEventType}; +use stacks::burnchains::events::NewBlock; use stacks::burnchains::indexer::{ - BurnBlockIPC, BurnHeaderIPC, BurnchainBlockDownloader, BurnchainBlockParser, BurnchainIndexer, -}; -use stacks::burnchains::{ - Burnchain, BurnchainBlock, Error as BurnchainError, StacksHyperBlock, Txid, + BurnBlockIPC, BurnchainBlockDownloader, BurnchainBlockParser, BurnchainIndexer, }; +use stacks::burnchains::{Burnchain, BurnchainBlock, Error as BurnchainError, StacksHyperBlock}; use stacks::chainstate::burn::db::sortdb::SortitionDB; -use stacks::chainstate::burn::operations::{BlockstackOperationType, LeaderBlockCommitOp}; +use stacks::chainstate::burn::operations::BlockstackOperationType; use stacks::chainstate::coordinator::comm::CoordinatorChannels; use stacks::chainstate::stacks::index::ClarityMarfTrieId; use stacks::core::StacksEpoch; -use stacks::types::chainstate::{BlockHeaderHash, BurnchainHeaderHash, StacksBlockId}; +use stacks::types::chainstate::{BurnchainHeaderHash, StacksBlockId}; use stacks::util::hash::hex_bytes; use stacks::util::sleep_ms; -use stacks::vm::types::{QualifiedContractIdentifier, TupleData}; -use stacks::vm::Value as ClarityValue; +use stacks::vm::types::QualifiedContractIdentifier; use super::mock_events::BlockIPC; use super::{BurnchainChannel, Error}; @@ -36,8 +32,6 @@ pub struct L1Channel { } pub struct L1Controller { - /// This is the simulated contract identifier - contract_identifier: QualifiedContractIdentifier, burnchain: Option, config: Config, indexer: L1Indexer, @@ -49,10 +43,6 @@ pub struct L1Controller { coordinator: CoordinatorChannels, chain_tip: Option, - - /// This will be a unique number for the next burn block. Starts at 1 - next_burn_block: Arc>, - next_commit: Arc>>, } pub struct L1Indexer { @@ -88,7 +78,6 @@ impl L1Channel { lazy_static! { pub static ref STATIC_EVENTS_STREAM: Arc = Arc::new(L1Channel::single_block()); static ref NEXT_BURN_BLOCK: Arc> = Arc::new(Mutex::new(1)); - static ref NEXT_COMMIT: Arc>> = Arc::new(Mutex::new(None)); } /// This outputs a hard-coded value for the hash of the first block created by the @@ -175,7 +164,6 @@ impl L1Controller { let contract_identifier = config.burnchain.contract_identifier.clone(); let indexer = L1Indexer::new(contract_identifier.clone()); L1Controller { - contract_identifier, burnchain: None, config, indexer, @@ -184,8 +172,6 @@ impl L1Controller { should_keep_running: Some(Arc::new(AtomicBool::new(true))), coordinator, chain_tip: None, - next_burn_block: NEXT_BURN_BLOCK.clone(), - next_commit: NEXT_COMMIT.clone(), } } @@ -290,23 +276,12 @@ impl BurnchainController for L1Controller { } fn submit_operation( &mut self, - operation: BlockstackOperationType, + _operation: BlockstackOperationType, _op_signer: &mut BurnchainOpSigner, _attempt: u64, ) -> bool { - match operation { - BlockstackOperationType::LeaderBlockCommit(LeaderBlockCommitOp { - block_header_hash, - .. - }) => { - let mut next_commit = self.next_commit.lock().unwrap(); - if let Some(prior_commit) = next_commit.replace(block_header_hash) { - warn!("Mocknet controller replaced a staged commit"; "prior_commit" => %prior_commit); - } - - true - } - } + // todo(issue #29) + false } fn sync(&mut self, target_block_height_opt: Option) -> Result<(BurnchainTip, u64), Error> { @@ -468,7 +443,9 @@ impl BurnchainIndexer for L1Indexer { } fn get_first_block_header_hash(&self) -> Result { - Ok(BurnchainHeaderHash(make_mock_byte_string_for_first_l1_block())) + Ok(BurnchainHeaderHash( + make_mock_byte_string_for_first_l1_block(), + )) } fn get_first_block_header_timestamp(&self) -> Result { diff --git a/testnet/stacks-node/src/burnchains/mock_events.rs b/testnet/stacks-node/src/burnchains/mock_events.rs index 958a474de..57c74abbb 100644 --- a/testnet/stacks-node/src/burnchains/mock_events.rs +++ b/testnet/stacks-node/src/burnchains/mock_events.rs @@ -468,12 +468,12 @@ pub struct MockParser { watch_contract: QualifiedContractIdentifier, } - #[derive(Clone)] - pub struct MockHeader { - pub height: u64, - pub index_hash: StacksBlockId, - pub parent_index_hash: StacksBlockId, - } +#[derive(Clone)] +pub struct MockHeader { + pub height: u64, + pub index_hash: StacksBlockId, + pub parent_index_hash: StacksBlockId, +} #[derive(Clone)] pub struct BlockIPC(pub NewBlock); diff --git a/testnet/stacks-node/src/config.rs b/testnet/stacks-node/src/config.rs index 79903e859..e0754909a 100644 --- a/testnet/stacks-node/src/config.rs +++ b/testnet/stacks-node/src/config.rs @@ -41,6 +41,9 @@ const LEADER_KEY_TX_ESTIM_SIZE: u64 = 290; const BLOCK_COMMIT_TX_ESTIM_SIZE: u64 = 350; const INV_REWARD_CYCLES_TESTNET: u64 = 6; +pub const BURNCHAIN_NAME_STACKS_L1: &str = "stacks_layer_1"; +pub const BURNCHAIN_NAME_MOCKSTACK: &str = "mockstack"; + #[derive(Clone, Deserialize, Default)] pub struct ConfigFile { pub burnchain: Option, @@ -987,6 +990,11 @@ impl Default for BurnchainConfig { } impl BurnchainConfig { + /// Does this configuration need a L1 observer to be spawned? + pub fn spawn_l1_observer(&self) -> bool { + self.chain == BURNCHAIN_NAME_STACKS_L1 + } + pub fn get_rpc_url(&self) -> String { let scheme = match self.rpc_ssl { true => "https://", @@ -1194,8 +1202,12 @@ impl Config { coordinator: CoordinatorChannels, ) -> Option> { match self.burnchain.chain.as_str() { - "mockstack" => Some(Box::new(MockController::new(self.clone(), coordinator))), - "stacks_layer_1" => Some(Box::new(L1Controller::new(self.clone(), coordinator))), + BURNCHAIN_NAME_MOCKSTACK => { + Some(Box::new(MockController::new(self.clone(), coordinator))) + } + BURNCHAIN_NAME_STACKS_L1 => { + Some(Box::new(L1Controller::new(self.clone(), coordinator))) + } _ => { warn!( "No matching controller for `chain`: {}", diff --git a/testnet/stacks-node/src/run_loop/l1_observer.rs b/testnet/stacks-node/src/run_loop/l1_observer.rs index ae4045867..aa9a4548b 100644 --- a/testnet/stacks-node/src/run_loop/l1_observer.rs +++ b/testnet/stacks-node/src/run_loop/l1_observer.rs @@ -15,10 +15,7 @@ pub const EVENT_OBSERVER_PORT: u16 = 50303; /// Adds in `channel` to downstream functions. fn with_db( channel: Arc, -) -> impl Filter< - Extract = (Arc,), - Error = std::convert::Infallible, -> + Clone { +) -> impl Filter,), Error = std::convert::Infallible> + Clone { warp::any().map(move || channel.clone()) } @@ -46,7 +43,7 @@ async fn serve( let new_blocks = first_part.and_then(handle_new_block); info!("Binding warp server."); - let (addr, server) = warp::serve(new_blocks).bind_with_graceful_shutdown( + let (_addr, server) = warp::serve(new_blocks).bind_with_graceful_shutdown( ([127, 0, 0, 1], EVENT_OBSERVER_PORT), async { signal_receiver.await.ok(); diff --git a/testnet/stacks-node/src/run_loop/neon.rs b/testnet/stacks-node/src/run_loop/neon.rs index 2c53b5850..18be933ee 100644 --- a/testnet/stacks-node/src/run_loop/neon.rs +++ b/testnet/stacks-node/src/run_loop/neon.rs @@ -515,7 +515,11 @@ impl RunLoop { let sortdb = burnchain.sortdb_mut(); let mut sortition_db_height = RunLoop::get_sortition_db_height(&sortdb, &burnchain_config); - let l1_observer_signal = l1_observer::spawn(burnchain.get_channel()); + let l1_observer_signal = if self.config.burnchain.spawn_l1_observer() { + Some(l1_observer::spawn(burnchain.get_channel())) + } else { + None + }; // Start the runloop debug!("Begin run loop"); @@ -549,7 +553,7 @@ impl RunLoop { coordinator_senders.stop_chains_coordinator(); coordinator_thread_handle.join().unwrap(); - l1_observer_signal.send(()).unwrap(); + l1_observer_signal.map(|signal| signal.send(()).unwrap()); node.join(); info!("Exiting stacks-node"); diff --git a/testnet/stacks-node/src/tests/l1_observer_test.rs b/testnet/stacks-node/src/tests/l1_observer_test.rs index 7bfafeebe..53e397a0a 100644 --- a/testnet/stacks-node/src/tests/l1_observer_test.rs +++ b/testnet/stacks-node/src/tests/l1_observer_test.rs @@ -32,9 +32,14 @@ impl StacksL1Controller { } pub fn start_process(&mut self) -> SubprocessResult<()> { - let base_dir = env::var("STACKS_BASE_DIR").expect("couldn't read STACKS_BASE_DIR"); - let bin_file = format!("{}/target/release/stacks-node", &base_dir); - let mut command = Command::new(&bin_file); + let binary = match env::var("STACKS_BASE_DIR") { + Err(_) => { + // assume stacks-node is in path + "stacks-node".into() + } + Ok(path) => path, + }; + let mut command = Command::new(&binary); command .stdout(Stdio::piped()) .arg("start") @@ -86,6 +91,10 @@ impl Drop for StacksL1Controller { /// from the Stacks-L1 chain. #[test] fn l1_observer_test() { + if env::var("STACKS_NODE_TEST") != Ok("1".into()) { + return; + } + // Start Stacks L1. let l1_toml_file = "../../contrib/conf/stacks-l1-mocknet.toml"; let mut stacks_l1_controller = StacksL1Controller::new(l1_toml_file.to_string()); @@ -102,7 +111,7 @@ fn l1_observer_test() { thread::spawn(move || run_loop.start(None, 0)); // Sleep to give the run loop time to listen to blocks. - thread::sleep(Duration::from_millis(30000)); + thread::sleep(Duration::from_millis(45000)); // The burnchain should have registered what the listener recorded. let burnchain = Burnchain::new( diff --git a/testnet/stacks-node/src/tests/neon_integrations.rs b/testnet/stacks-node/src/tests/neon_integrations.rs index c691d8b83..73b3c401b 100644 --- a/testnet/stacks-node/src/tests/neon_integrations.rs +++ b/testnet/stacks-node/src/tests/neon_integrations.rs @@ -24,7 +24,7 @@ use crate::tests::{ }; use crate::{Config, ConfigFile, Keychain}; -pub fn neon_integration_test_conf() -> (Config, StacksAddress) { +pub fn mockstack_test_conf() -> (Config, StacksAddress) { let mut conf = super::new_test_conf(); let keychain = Keychain::default(conf.node.seed.clone()); @@ -32,8 +32,8 @@ pub fn neon_integration_test_conf() -> (Config, StacksAddress) { conf.node.miner = true; conf.node.wait_time_for_microblocks = 500; conf.burnchain.burn_fee_cap = 20000; - - conf.burnchain.mode = "neon".into(); + conf.burnchain.chain = "mockstack".into(); + conf.burnchain.mode = "hyperchain".into(); conf.burnchain.username = Some("neon-tester".into()); conf.burnchain.password = Some("neon-tester-pass".into()); conf.burnchain.peer_host = "127.0.0.1".into(); @@ -456,8 +456,10 @@ fn is_close_f64(a: f64, b: f64) -> bool { #[test] #[ignore] -fn bitcoind_integration_test() { - let (mut conf, miner_account) = neon_integration_test_conf(); +/// Simple test for the mock backend: test that the hyperchain miner +/// is capable of producing blocks +fn mockstack_integration_test() { + let (mut conf, miner_account) = mockstack_test_conf(); let prom_bind = format!("{}:{}", "127.0.0.1", 6000); conf.node.prometheus_bind = Some(prom_bind.clone()); @@ -625,7 +627,7 @@ const FAUCET_CONTRACT: &'static str = " /// processes the blocks #[test] fn faucet_test() { - let (mut conf, miner_account) = neon_integration_test_conf(); + let (mut conf, miner_account) = mockstack_test_conf(); let contract_sk = StacksPrivateKey::from_hex(SK_1).unwrap(); let sk_2 = StacksPrivateKey::from_hex(SK_2).unwrap();