mirror of
https://github.com/alexgo-io/stacks-puppet-node.git
synced 2026-05-27 08:31:53 +08:00
Revisit testnet module structure
This commit is contained in:
@@ -1,2 +1,2 @@
|
||||
[alias]
|
||||
testnet = "run --package stacks-helium --"
|
||||
testnet = "run --package stacks-testnet --"
|
||||
|
||||
@@ -85,4 +85,4 @@ default = ["developer-mode", "asm"]
|
||||
|
||||
|
||||
[workspace]
|
||||
members = [".", "testnet/helium/"]
|
||||
members = [".", "testnet/"]
|
||||
|
||||
@@ -1,745 +0,0 @@
|
||||
use vm::{
|
||||
database::{ HeadersDB, ClaritySerializable },
|
||||
types::{QualifiedContractIdentifier, TupleData, PrincipalData},
|
||||
analysis::{mem_type_check, contract_interface_builder::{build_contract_interface, ContractInterface}},
|
||||
clarity::ClarityConnection,
|
||||
Value, ClarityName, ContractName, errors::RuntimeErrorType, errors::Error as ClarityError };
|
||||
use chainstate::stacks::{
|
||||
db::StacksChainState, C32_ADDRESS_VERSION_TESTNET_SINGLESIG,
|
||||
StacksMicroblockHeader, StacksPrivateKey, TransactionSpendingCondition, TransactionAuth, TransactionVersion,
|
||||
StacksPublicKey, TransactionPayload, StacksTransactionSigner,
|
||||
TokenTransferMemo, CoinbasePayload, TransactionPostConditionMode,
|
||||
StacksTransaction, TransactionSmartContract, TransactionContractCall, StacksAddress };
|
||||
use chainstate::burn::VRFSeed;
|
||||
use burnchains::Address;
|
||||
use address::AddressHashMode;
|
||||
use net::{Error as NetError, StacksMessageCodec, AccountEntryResponse, ContractSrcResponse, CallReadOnlyRequestBody};
|
||||
use util::{log, strings::StacksString, hash::hex_bytes, hash::to_hex};
|
||||
use std::collections::HashMap;
|
||||
use util::db::{DBConn, FromRow};
|
||||
|
||||
use std::{thread, time};
|
||||
|
||||
use testnet;
|
||||
use testnet::helium::{
|
||||
mem_pool::MemPool,
|
||||
config::InitialBalance
|
||||
};
|
||||
|
||||
use reqwest;
|
||||
|
||||
pub fn serialize_sign_standard_single_sig_tx(payload: TransactionPayload,
|
||||
sender: &StacksPrivateKey, nonce: u64, fee_rate: u64) -> Vec<u8> {
|
||||
let mut spending_condition = TransactionSpendingCondition::new_singlesig_p2pkh(StacksPublicKey::from_private(sender))
|
||||
.expect("Failed to create p2pkh spending condition from public key.");
|
||||
spending_condition.set_nonce(nonce);
|
||||
spending_condition.set_fee_rate(fee_rate);
|
||||
let auth = TransactionAuth::Standard(spending_condition);
|
||||
let mut unsigned_tx = StacksTransaction::new(TransactionVersion::Testnet, auth, payload);
|
||||
unsigned_tx.post_condition_mode = TransactionPostConditionMode::Allow;
|
||||
|
||||
let mut tx_signer = StacksTransactionSigner::new(&unsigned_tx);
|
||||
tx_signer.sign_origin(sender).unwrap();
|
||||
|
||||
let mut buf = vec![];
|
||||
tx_signer.get_tx().unwrap().consensus_serialize(&mut buf).unwrap();
|
||||
buf
|
||||
}
|
||||
|
||||
pub fn make_contract_publish(sender: &StacksPrivateKey, nonce: u64, fee_rate: u64,
|
||||
contract_name: &str, contract_content: &str) -> Vec<u8> {
|
||||
let name = ContractName::from(contract_name);
|
||||
let code_body = StacksString::from_string(&contract_content.to_string()).unwrap();
|
||||
|
||||
let payload = TransactionSmartContract { name, code_body };
|
||||
|
||||
serialize_sign_standard_single_sig_tx(payload.into(), sender, nonce, fee_rate)
|
||||
}
|
||||
|
||||
pub fn make_stacks_transfer(sender: &StacksPrivateKey, nonce: u64, fee_rate: u64,
|
||||
recipient: &PrincipalData, amount: u64) -> Vec<u8> {
|
||||
let payload = TransactionPayload::TokenTransfer(recipient.clone(), amount, TokenTransferMemo([0; 34]));
|
||||
serialize_sign_standard_single_sig_tx(payload.into(), sender, nonce, fee_rate)
|
||||
}
|
||||
|
||||
pub fn make_poison(sender: &StacksPrivateKey, nonce: u64, fee_rate: u64,
|
||||
header_1: StacksMicroblockHeader, header_2: StacksMicroblockHeader) -> Vec<u8> {
|
||||
let payload = TransactionPayload::PoisonMicroblock(header_1, header_2);
|
||||
serialize_sign_standard_single_sig_tx(payload.into(), sender, nonce, fee_rate)
|
||||
}
|
||||
|
||||
pub fn make_coinbase(sender: &StacksPrivateKey, nonce: u64, fee_rate: u64) -> Vec<u8> {
|
||||
let payload = TransactionPayload::Coinbase(CoinbasePayload([0; 32]));
|
||||
serialize_sign_standard_single_sig_tx(payload.into(), sender, nonce, fee_rate)
|
||||
}
|
||||
|
||||
pub fn make_contract_call(
|
||||
sender: &StacksPrivateKey, nonce: u64, fee_rate: u64,
|
||||
contract_addr: &StacksAddress, contract_name: &str,
|
||||
function_name: &str, function_args: &[Value]) -> Vec<u8> {
|
||||
|
||||
let contract_name = ContractName::from(contract_name);
|
||||
let function_name = ClarityName::from(function_name);
|
||||
|
||||
let payload = TransactionContractCall {
|
||||
address: contract_addr.clone(),
|
||||
contract_name, function_name,
|
||||
function_args: function_args.iter().map(|x| x.clone()).collect()
|
||||
};
|
||||
|
||||
serialize_sign_standard_single_sig_tx(payload.into(), sender, nonce, fee_rate)
|
||||
}
|
||||
|
||||
pub fn to_addr(sk: &StacksPrivateKey) -> StacksAddress {
|
||||
StacksAddress::from_public_keys(
|
||||
C32_ADDRESS_VERSION_TESTNET_SINGLESIG, &AddressHashMode::SerializeP2PKH, 1, &vec![StacksPublicKey::from_private(sk)])
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
const GET_INFO_CONTRACT: &'static str = "
|
||||
(define-map block-data
|
||||
((height uint))
|
||||
((stacks-hash (buff 32))
|
||||
(id-hash (buff 32))
|
||||
(btc-hash (buff 32))
|
||||
(vrf-seed (buff 32))
|
||||
(burn-block-time uint)
|
||||
(stacks-miner principal)))
|
||||
(define-private (test-1) (get-block-info? time u1))
|
||||
(define-private (test-2) (get-block-info? time block-height))
|
||||
(define-private (test-3) (get-block-info? time u100000))
|
||||
(define-private (test-4 (x uint)) (get-block-info? header-hash x))
|
||||
(define-private (test-5) (get-block-info? header-hash (- block-height u1)))
|
||||
(define-private (test-6) (get-block-info? burnchain-header-hash u1))
|
||||
(define-private (test-7) (get-block-info? vrf-seed u1))
|
||||
(define-private (test-8) (get-block-info? miner-address u1))
|
||||
(define-private (test-9) (get-block-info? miner-address block-height))
|
||||
(define-private (test-10) (get-block-info? miner-address u100000))
|
||||
|
||||
(define-private (get-block-id-hash (height uint)) (unwrap-panic
|
||||
(get id-hash (map-get? block-data ((height height))))))
|
||||
|
||||
;; should always return true!
|
||||
;; evaluates 'block-height' at the block in question.
|
||||
;; NOTABLY, this would fail if the MARF couldn't figure out
|
||||
;; the height of the 'current chain tip'.
|
||||
(define-private (exotic-block-height (height uint))
|
||||
(is-eq (at-block (get-block-id-hash height) block-height)
|
||||
height))
|
||||
(define-read-only (get-exotic-data-info (height uint))
|
||||
(unwrap-panic (map-get? block-data { height: height })))
|
||||
|
||||
(define-private (exotic-data-checks (height uint))
|
||||
(let ((block-to-check (unwrap-panic (get-block-info? id-header-hash height)))
|
||||
(block-info (unwrap-panic (map-get? block-data ((height (- height u1)))))))
|
||||
(and (is-eq (print (unwrap-panic (at-block block-to-check (get-block-info? id-header-hash (- block-height u1)))))
|
||||
(print (get id-hash block-info)))
|
||||
(is-eq (print (unwrap-panic (at-block block-to-check (get-block-info? header-hash (- block-height u1)))))
|
||||
(print (unwrap-panic (get-block-info? header-hash (- height u1))))
|
||||
(print (get stacks-hash block-info)))
|
||||
(is-eq (print (unwrap-panic (at-block block-to-check (get-block-info? vrf-seed (- block-height u1)))))
|
||||
(print (unwrap-panic (get-block-info? vrf-seed (- height u1))))
|
||||
(print (get vrf-seed block-info)))
|
||||
(is-eq (print (unwrap-panic (at-block block-to-check (get-block-info? burnchain-header-hash (- block-height u1)))))
|
||||
(print (unwrap-panic (get-block-info? burnchain-header-hash (- height u1))))
|
||||
(print (get btc-hash block-info)))
|
||||
(is-eq (print (unwrap-panic (at-block block-to-check (get-block-info? time (- block-height u1)))))
|
||||
(print (unwrap-panic (get-block-info? time (- height u1))))
|
||||
(print (get burn-block-time block-info)))
|
||||
(is-eq (print (unwrap-panic (at-block block-to-check (get-block-info? miner-address (- block-height u1)))))
|
||||
(print (unwrap-panic (get-block-info? miner-address (- height u1))))
|
||||
(print (get stacks-miner block-info))))))
|
||||
|
||||
(define-private (inner-update-info (height uint))
|
||||
(let ((value (tuple
|
||||
(stacks-hash (unwrap-panic (get-block-info? header-hash height)))
|
||||
(id-hash (unwrap-panic (get-block-info? id-header-hash height)))
|
||||
(btc-hash (unwrap-panic (get-block-info? burnchain-header-hash height)))
|
||||
(vrf-seed (unwrap-panic (get-block-info? vrf-seed height)))
|
||||
(burn-block-time (unwrap-panic (get-block-info? time height)))
|
||||
(stacks-miner (unwrap-panic (get-block-info? miner-address height))))))
|
||||
(ok (map-set block-data ((height height)) value))))
|
||||
|
||||
(define-public (update-info)
|
||||
(begin
|
||||
(inner-update-info (- block-height u2))
|
||||
(inner-update-info (- block-height u1))))
|
||||
";
|
||||
|
||||
const SK_1: &'static str = "a1289f6438855da7decf9b61b852c882c398cff1446b2a0f823538aa2ebef92e01";
|
||||
const SK_2: &'static str = "4ce9a8f7539ea93753a36405b16e8b57e15a552430410709c2b6d65dca5c02e201";
|
||||
const SK_3: &'static str = "cb95ddd0fe18ec57f4f3533b95ae564b3f1ae063dbf75b46334bd86245aef78501";
|
||||
|
||||
const ADDR_4: &'static str = "SP31DA6FTSJX2WGTZ69SFY11BH51NZMB0ZW97B5P0";
|
||||
|
||||
use std::sync::Mutex;
|
||||
|
||||
lazy_static! {
|
||||
static ref http_binding: Mutex<Option<String>> = Mutex::new(None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn integration_test_get_info() {
|
||||
let mut conf = testnet::helium::tests::new_test_conf();
|
||||
let spender_addr = to_addr(&StacksPrivateKey::from_hex(SK_3).unwrap()).into();
|
||||
|
||||
conf.initial_balances.push(InitialBalance {
|
||||
address: spender_addr,
|
||||
amount: 100300
|
||||
});
|
||||
|
||||
conf.burnchain.block_time = 1500;
|
||||
|
||||
let num_rounds = 4;
|
||||
|
||||
let mut run_loop = testnet::helium::RunLoop::new(conf);
|
||||
|
||||
{
|
||||
let mut http_opt = http_binding.lock().unwrap();
|
||||
http_opt.replace(format!("http://{}", &run_loop.node.config.node.rpc_bind));
|
||||
}
|
||||
|
||||
run_loop.apply_on_new_tenures(|round, tenure| {
|
||||
let contract_sk = StacksPrivateKey::from_hex(SK_1).unwrap();
|
||||
let principal_sk = StacksPrivateKey::from_hex(SK_2).unwrap();
|
||||
let spender_sk = StacksPrivateKey::from_hex(SK_3).unwrap();
|
||||
|
||||
if round == 1 { // block-height = 2
|
||||
let publish_tx = make_contract_publish(&contract_sk, 0, 0, "get-info", GET_INFO_CONTRACT);
|
||||
eprintln!("Tenure in 1 started!");
|
||||
tenure.mem_pool.submit(publish_tx);
|
||||
} else if round >= 2 { // block-height > 2
|
||||
let tx = make_contract_call(&principal_sk, (round - 2).into(), 0, &to_addr(&contract_sk), "get-info", "update-info", &[]);
|
||||
tenure.mem_pool.submit(tx);
|
||||
}
|
||||
|
||||
if round >= 1 {
|
||||
let tx_xfer = make_stacks_transfer(&spender_sk, (round - 1).into(), 0,
|
||||
&StacksAddress::from_string(ADDR_4).unwrap().into(), 100);
|
||||
tenure.mem_pool.submit(tx_xfer);
|
||||
}
|
||||
|
||||
return
|
||||
});
|
||||
|
||||
run_loop.apply_on_new_chain_states(|round, chain_state, block, chain_tip_info, _events| {
|
||||
let contract_addr = to_addr(&StacksPrivateKey::from_hex(SK_1).unwrap());
|
||||
let contract_identifier =
|
||||
QualifiedContractIdentifier::parse(&format!("{}.{}", &contract_addr, "get-info")).unwrap();
|
||||
|
||||
let http_origin = {
|
||||
http_binding.lock().unwrap().clone().unwrap()
|
||||
};
|
||||
|
||||
match round {
|
||||
1 => {
|
||||
// - Chain length should be 2.
|
||||
let mut blocks = StacksChainState::list_blocks(&chain_state.blocks_db).unwrap();
|
||||
blocks.sort();
|
||||
assert!(chain_tip_info.block_height == 2);
|
||||
|
||||
// Block #1 should have 3 txs
|
||||
assert!(block.txs.len() == 3);
|
||||
|
||||
let parent = block.header.parent_block;
|
||||
let bhh = &chain_tip_info.index_block_hash();
|
||||
eprintln!("Current Block: {} Parent Block: {}", bhh, parent);
|
||||
let parent_val = Value::buff_from(parent.as_bytes().to_vec()).unwrap();
|
||||
|
||||
// find header metadata
|
||||
let mut headers = vec![];
|
||||
for block in blocks.iter() {
|
||||
let header = StacksChainState::get_anchored_block_header_info(&chain_state.headers_db, &block.0, &block.1).unwrap().unwrap();
|
||||
headers.push(header);
|
||||
}
|
||||
|
||||
let _tip_header_info = headers.last().unwrap();
|
||||
|
||||
// find miner metadata
|
||||
let mut miners = vec![];
|
||||
for block in blocks.iter() {
|
||||
let miner = StacksChainState::get_miner_info(&chain_state.headers_db, &block.0, &block.1).unwrap().unwrap();
|
||||
miners.push(miner);
|
||||
}
|
||||
|
||||
let _tip_miner = miners.last().unwrap();
|
||||
|
||||
assert_eq!(
|
||||
chain_state.clarity_eval_read_only(
|
||||
bhh, &contract_identifier, "block-height"),
|
||||
Value::UInt(2));
|
||||
|
||||
assert_eq!(
|
||||
chain_state.clarity_eval_read_only(
|
||||
bhh, &contract_identifier, "(test-1)"),
|
||||
Value::some(Value::UInt(headers[0].burn_header_timestamp as u128)).unwrap());
|
||||
|
||||
assert_eq!(
|
||||
chain_state.clarity_eval_read_only(
|
||||
bhh, &contract_identifier, "(test-2)"),
|
||||
Value::none());
|
||||
|
||||
assert_eq!(
|
||||
chain_state.clarity_eval_read_only(
|
||||
bhh, &contract_identifier, "(test-3)"),
|
||||
Value::none());
|
||||
|
||||
assert_eq!(
|
||||
chain_state.clarity_eval_read_only(
|
||||
bhh, &contract_identifier, "(test-4 u1)"),
|
||||
Value::some(parent_val.clone()).unwrap());
|
||||
|
||||
assert_eq!(
|
||||
chain_state.clarity_eval_read_only(
|
||||
bhh, &contract_identifier, "(test-5)"),
|
||||
Value::some(parent_val).unwrap());
|
||||
|
||||
// test-6 and test-7 return the block at height 1's VRF-seed,
|
||||
// which in this integration test, should be blocks[0]
|
||||
let last_tip = blocks[0];
|
||||
eprintln!("Last block info: stacks: {}, burn: {}", last_tip.1, last_tip.0);
|
||||
let last_block = StacksChainState::load_block(&chain_state.blocks_path, &last_tip.0, &last_tip.1).unwrap().unwrap();
|
||||
assert_eq!(parent, last_block.header.block_hash());
|
||||
|
||||
let last_vrf_seed = VRFSeed::from_proof(&last_block.header.proof).as_bytes().to_vec();
|
||||
let last_burn_header = last_tip.0.as_bytes().to_vec();
|
||||
|
||||
assert_eq!(
|
||||
chain_state.clarity_eval_read_only(
|
||||
bhh, &contract_identifier, "(test-6)"),
|
||||
Value::some(Value::buff_from(last_burn_header).unwrap()).unwrap());
|
||||
assert_eq!(
|
||||
chain_state.clarity_eval_read_only(
|
||||
bhh, &contract_identifier, "(test-7)"),
|
||||
Value::some(Value::buff_from(last_vrf_seed).unwrap()).unwrap());
|
||||
|
||||
// verify that we can get the block miner
|
||||
assert_eq!(
|
||||
chain_state.clarity_eval_read_only(
|
||||
bhh, &contract_identifier, "(test-8)"),
|
||||
Value::some(Value::Principal(miners[0].address.to_account_principal())).unwrap());
|
||||
|
||||
assert_eq!(
|
||||
chain_state.clarity_eval_read_only(
|
||||
bhh, &contract_identifier, "(test-9)"),
|
||||
Value::none());
|
||||
|
||||
assert_eq!(
|
||||
chain_state.clarity_eval_read_only(
|
||||
bhh, &contract_identifier, "(test-10)"),
|
||||
Value::none());
|
||||
|
||||
},
|
||||
3 => {
|
||||
let bhh = &chain_tip_info.index_block_hash();
|
||||
|
||||
assert_eq!(Value::Bool(true), chain_state.clarity_eval_read_only(
|
||||
bhh, &contract_identifier, "(exotic-block-height u1)"));
|
||||
assert_eq!(Value::Bool(true), chain_state.clarity_eval_read_only(
|
||||
bhh, &contract_identifier, "(exotic-block-height u2)"));
|
||||
assert_eq!(Value::Bool(true), chain_state.clarity_eval_read_only(
|
||||
bhh, &contract_identifier, "(exotic-block-height u3)"));
|
||||
|
||||
assert_eq!(Value::Bool(true), chain_state.clarity_eval_read_only(
|
||||
bhh, &contract_identifier, "(exotic-data-checks u2)"));
|
||||
assert_eq!(Value::Bool(true), chain_state.clarity_eval_read_only(
|
||||
bhh, &contract_identifier, "(exotic-data-checks u3)"));
|
||||
|
||||
let client = reqwest::blocking::Client::new();
|
||||
let path = format!("{}/v2/map_entry/{}/{}/{}",
|
||||
&http_origin, &contract_addr, "get-info", "block-data");
|
||||
|
||||
let key: Value = TupleData::from_data(vec![("height".into(), Value::UInt(1))])
|
||||
.unwrap().into();
|
||||
|
||||
eprintln!("Test: POST {}", path);
|
||||
let res = client.post(&path)
|
||||
.json(&key.serialize())
|
||||
.send()
|
||||
.unwrap().json::<HashMap<String, String>>().unwrap();
|
||||
let result_data = Value::try_deserialize_hex_untyped(&res["data"][2..]).unwrap();
|
||||
let expected_data = chain_state.clarity_eval_read_only(bhh, &contract_identifier,
|
||||
"(some (get-exotic-data-info u1))");
|
||||
assert!(res.get("proof").is_some());
|
||||
|
||||
assert_eq!(result_data, expected_data);
|
||||
|
||||
let key: Value = TupleData::from_data(vec![("height".into(), Value::UInt(100))])
|
||||
.unwrap().into();
|
||||
|
||||
eprintln!("Test: POST {}", path);
|
||||
let res = client.post(&path)
|
||||
.json(&key.serialize())
|
||||
.send()
|
||||
.unwrap().json::<HashMap<String, String>>().unwrap();
|
||||
let result_data = Value::try_deserialize_hex_untyped(&res["data"][2..]).unwrap();
|
||||
assert_eq!(result_data, Value::none());
|
||||
|
||||
let sender_addr = to_addr(&StacksPrivateKey::from_hex(SK_3).unwrap());
|
||||
|
||||
// now, let's use a query string to get data without a proof
|
||||
let path = format!("{}/v2/map_entry/{}/{}/{}?proof=0",
|
||||
&http_origin, &contract_addr, "get-info", "block-data");
|
||||
|
||||
let key: Value = TupleData::from_data(vec![("height".into(), Value::UInt(1))])
|
||||
.unwrap().into();
|
||||
|
||||
eprintln!("Test: POST {}", path);
|
||||
let res = client.post(&path)
|
||||
.json(&key.serialize())
|
||||
.send()
|
||||
.unwrap().json::<HashMap<String, String>>().unwrap();
|
||||
|
||||
assert!(res.get("proof").is_none());
|
||||
let result_data = Value::try_deserialize_hex_untyped(&res["data"][2..]).unwrap();
|
||||
let expected_data = chain_state.clarity_eval_read_only(bhh, &contract_identifier,
|
||||
"(some (get-exotic-data-info u1))");
|
||||
eprintln!("{}", serde_json::to_string(&res).unwrap());
|
||||
|
||||
assert_eq!(result_data, expected_data);
|
||||
|
||||
// now, let's use a query string to get data _with_ a proof
|
||||
let path = format!("{}/v2/map_entry/{}/{}/{}?proof=1",
|
||||
&http_origin, &contract_addr, "get-info", "block-data");
|
||||
|
||||
let key: Value = TupleData::from_data(vec![("height".into(), Value::UInt(1))])
|
||||
.unwrap().into();
|
||||
|
||||
eprintln!("Test: POST {}", path);
|
||||
let res = client.post(&path)
|
||||
.json(&key.serialize())
|
||||
.send()
|
||||
.unwrap().json::<HashMap<String, String>>().unwrap();
|
||||
|
||||
assert!(res.get("proof").is_some());
|
||||
let result_data = Value::try_deserialize_hex_untyped(&res["data"][2..]).unwrap();
|
||||
let expected_data = chain_state.clarity_eval_read_only(bhh, &contract_identifier,
|
||||
"(some (get-exotic-data-info u1))");
|
||||
eprintln!("{}", serde_json::to_string(&res).unwrap());
|
||||
|
||||
assert_eq!(result_data, expected_data);
|
||||
|
||||
// account with a nonce entry + a balance entry
|
||||
let path = format!("{}/v2/accounts/{}",
|
||||
&http_origin, &sender_addr);
|
||||
eprintln!("Test: GET {}", path);
|
||||
let res = client.get(&path).send().unwrap().json::<AccountEntryResponse>().unwrap();
|
||||
assert_eq!(u128::from_str_radix(&res.balance[2..], 16).unwrap(), 100000);
|
||||
assert_eq!(res.nonce, 3);
|
||||
assert!(res.nonce_proof.is_some());
|
||||
assert!(res.balance_proof.is_some());
|
||||
|
||||
// account with a nonce entry but not a balance entry
|
||||
let path = format!("{}/v2/accounts/{}",
|
||||
&http_origin, &contract_addr);
|
||||
eprintln!("Test: GET {}", path);
|
||||
let res = client.get(&path).send().unwrap().json::<AccountEntryResponse>().unwrap();
|
||||
assert_eq!(u128::from_str_radix(&res.balance[2..], 16).unwrap(), 0);
|
||||
assert_eq!(res.nonce, 1);
|
||||
assert!(res.nonce_proof.is_some());
|
||||
assert!(res.balance_proof.is_some());
|
||||
|
||||
// account with a balance entry but not a nonce entry
|
||||
let path = format!("{}/v2/accounts/{}",
|
||||
&http_origin, ADDR_4);
|
||||
eprintln!("Test: GET {}", path);
|
||||
let res = client.get(&path).send().unwrap().json::<AccountEntryResponse>().unwrap();
|
||||
assert_eq!(u128::from_str_radix(&res.balance[2..], 16).unwrap(), 300);
|
||||
assert_eq!(res.nonce, 0);
|
||||
assert!(res.nonce_proof.is_some());
|
||||
assert!(res.balance_proof.is_some());
|
||||
|
||||
// account with neither!
|
||||
let path = format!("{}/v2/accounts/{}.get-info",
|
||||
&http_origin, &contract_addr);
|
||||
eprintln!("Test: GET {}", path);
|
||||
let res = client.get(&path).send().unwrap().json::<AccountEntryResponse>().unwrap();
|
||||
assert_eq!(u128::from_str_radix(&res.balance[2..], 16).unwrap(), 0);
|
||||
assert_eq!(res.nonce, 0);
|
||||
assert!(res.nonce_proof.is_some());
|
||||
assert!(res.balance_proof.is_some());
|
||||
|
||||
let path = format!("{}/v2/accounts/{}?proof=0",
|
||||
&http_origin, ADDR_4);
|
||||
eprintln!("Test: GET {}", path);
|
||||
let res = client.get(&path).send().unwrap().json::<AccountEntryResponse>().unwrap();
|
||||
assert_eq!(u128::from_str_radix(&res.balance[2..], 16).unwrap(), 300);
|
||||
assert_eq!(res.nonce, 0);
|
||||
assert!(res.nonce_proof.is_none());
|
||||
assert!(res.balance_proof.is_none());
|
||||
|
||||
let path = format!("{}/v2/accounts/{}?proof=1",
|
||||
&http_origin, ADDR_4);
|
||||
eprintln!("Test: GET {}", path);
|
||||
let res = client.get(&path).send().unwrap().json::<AccountEntryResponse>().unwrap();
|
||||
assert_eq!(u128::from_str_radix(&res.balance[2..], 16).unwrap(), 300);
|
||||
assert_eq!(res.nonce, 0);
|
||||
assert!(res.nonce_proof.is_some());
|
||||
assert!(res.balance_proof.is_some());
|
||||
|
||||
// let's try getting the transfer cost
|
||||
let path = format!("{}/v2/fees/transfer", &http_origin);
|
||||
eprintln!("Test: GET {}", path);
|
||||
let res = client.get(&path).send().unwrap().json::<u64>().unwrap();
|
||||
assert!(res > 0);
|
||||
|
||||
// let's get a contract ABI
|
||||
|
||||
let path = format!("{}/v2/contracts/interface/{}/{}", &http_origin, &contract_addr, "get-info");
|
||||
eprintln!("Test: GET {}", path);
|
||||
let res = client.get(&path).send().unwrap().json::<ContractInterface>().unwrap();
|
||||
|
||||
let contract_analysis = mem_type_check(GET_INFO_CONTRACT).unwrap().1;
|
||||
let expected_interface = build_contract_interface(&contract_analysis);
|
||||
|
||||
eprintln!("{}", serde_json::to_string(&expected_interface).unwrap());
|
||||
|
||||
assert_eq!(res, expected_interface);
|
||||
|
||||
// a missing one?
|
||||
|
||||
let path = format!("{}/v2/contracts/interface/{}/{}", &http_origin, &contract_addr, "not-there");
|
||||
eprintln!("Test: GET {}", path);
|
||||
assert_eq!(client.get(&path).send().unwrap().status(), 404);
|
||||
|
||||
// let's get a contract SRC
|
||||
|
||||
let path = format!("{}/v2/contracts/source/{}/{}", &http_origin, &contract_addr, "get-info");
|
||||
eprintln!("Test: GET {}", path);
|
||||
let res = client.get(&path).send().unwrap().json::<ContractSrcResponse>().unwrap();
|
||||
|
||||
assert_eq!(res.source, GET_INFO_CONTRACT);
|
||||
assert_eq!(res.publish_height, 2);
|
||||
assert!(res.marf_proof.is_some());
|
||||
|
||||
|
||||
let path = format!("{}/v2/contracts/source/{}/{}?proof=0", &http_origin, &contract_addr, "get-info");
|
||||
eprintln!("Test: GET {}", path);
|
||||
let res = client.get(&path).send().unwrap().json::<ContractSrcResponse>().unwrap();
|
||||
|
||||
assert_eq!(res.source, GET_INFO_CONTRACT);
|
||||
assert_eq!(res.publish_height, 2);
|
||||
assert!(res.marf_proof.is_none());
|
||||
|
||||
// a missing one?
|
||||
|
||||
let path = format!("{}/v2/contracts/source/{}/{}", &http_origin, &contract_addr, "not-there");
|
||||
eprintln!("Test: GET {}", path);
|
||||
assert_eq!(client.get(&path).send().unwrap().status(), 404);
|
||||
|
||||
|
||||
// how about a read-only function call!
|
||||
let path = format!("{}/v2/contracts/call-read/{}/{}/{}", &http_origin, &contract_addr, "get-info", "get-exotic-data-info");
|
||||
eprintln!("Test: POST {}", path);
|
||||
|
||||
let body = CallReadOnlyRequestBody {
|
||||
sender: "'SP139Q3N9RXCJCD1XVA4N5RYWQ5K9XQ0T9PKQ8EE5".into(),
|
||||
arguments: vec![Value::UInt(1).serialize()]
|
||||
};
|
||||
|
||||
let res = client.post(&path)
|
||||
.json(&body)
|
||||
.send()
|
||||
.unwrap().json::<serde_json::Value>().unwrap();
|
||||
assert!(res.get("cause").is_none());
|
||||
assert!(res["okay"].as_bool().unwrap());
|
||||
|
||||
let result_data = Value::try_deserialize_hex_untyped(&res["result"].as_str().unwrap()[2..]).unwrap();
|
||||
let expected_data = chain_state.clarity_eval_read_only(bhh, &contract_identifier,
|
||||
"(get-exotic-data-info u1)");
|
||||
assert_eq!(result_data, expected_data);
|
||||
|
||||
// let's have a runtime error!
|
||||
let path = format!("{}/v2/contracts/call-read/{}/{}/{}", &http_origin, &contract_addr, "get-info", "get-exotic-data-info");
|
||||
eprintln!("Test: POST {}", path);
|
||||
|
||||
let body = CallReadOnlyRequestBody {
|
||||
sender: "'SP139Q3N9RXCJCD1XVA4N5RYWQ5K9XQ0T9PKQ8EE5".into(),
|
||||
arguments: vec![Value::UInt(100).serialize()]
|
||||
};
|
||||
|
||||
let res = client.post(&path)
|
||||
.json(&body)
|
||||
.send()
|
||||
.unwrap().json::<serde_json::Value>().unwrap();
|
||||
|
||||
assert!(res.get("result").is_none());
|
||||
assert!(!res["okay"].as_bool().unwrap());
|
||||
assert!(res["cause"].as_str().unwrap().contains("UnwrapFailure"));
|
||||
|
||||
// let's have a runtime error!
|
||||
let path = format!("{}/v2/contracts/call-read/{}/{}/{}", &http_origin, &contract_addr, "get-info", "update-info");
|
||||
eprintln!("Test: POST {}", path);
|
||||
|
||||
let body = CallReadOnlyRequestBody {
|
||||
sender: "'SP139Q3N9RXCJCD1XVA4N5RYWQ5K9XQ0T9PKQ8EE5".into(),
|
||||
arguments: vec![]
|
||||
};
|
||||
|
||||
let res = client.post(&path)
|
||||
.json(&body)
|
||||
.send()
|
||||
.unwrap().json::<serde_json::Value>().unwrap();
|
||||
|
||||
eprintln!("{}", res["cause"].as_str().unwrap());
|
||||
assert!(res.get("result").is_none());
|
||||
assert!(!res["okay"].as_bool().unwrap());
|
||||
assert!(res["cause"].as_str().unwrap().contains("NotReadOnly"));
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
});
|
||||
|
||||
run_loop.start(num_rounds);
|
||||
}
|
||||
|
||||
const FAUCET_CONTRACT: &'static str = "
|
||||
(define-public (spout)
|
||||
(let ((recipient tx-sender))
|
||||
(print (as-contract (stx-transfer? u1 .faucet recipient)))))
|
||||
";
|
||||
|
||||
#[test]
|
||||
fn contract_stx_transfer() {
|
||||
let mut conf = testnet::helium::tests::new_test_conf();
|
||||
|
||||
let sk_3 = StacksPrivateKey::from_hex(SK_3).unwrap();
|
||||
let addr_3 = to_addr(&sk_3);
|
||||
|
||||
conf.burnchain.block_time = 1500;
|
||||
conf.add_initial_balance(addr_3.to_string(), 100000);
|
||||
|
||||
let num_rounds = 5;
|
||||
|
||||
let mut run_loop = testnet::helium::RunLoop::new(conf);
|
||||
run_loop.apply_on_new_tenures(|round, tenure| {
|
||||
let contract_sk = StacksPrivateKey::from_hex(SK_1).unwrap();
|
||||
let sk_2 = StacksPrivateKey::from_hex(SK_2).unwrap();
|
||||
let sk_3 = StacksPrivateKey::from_hex(SK_3).unwrap();
|
||||
|
||||
let contract_identifier =
|
||||
QualifiedContractIdentifier::parse(&format!("{}.{}",
|
||||
to_addr(
|
||||
&StacksPrivateKey::from_hex(SK_1).unwrap()).to_string(),
|
||||
"faucet")).unwrap();
|
||||
|
||||
if round == 1 { // block-height = 2
|
||||
let xfer_to_contract = make_stacks_transfer(&sk_3, 0, 0, &contract_identifier.into(), 1000);
|
||||
tenure.mem_pool.submit(xfer_to_contract);
|
||||
} else if round == 2 { // block-height > 2
|
||||
let publish_tx = make_contract_publish(&contract_sk, 0, 0, "faucet", FAUCET_CONTRACT);
|
||||
tenure.mem_pool.submit(publish_tx);
|
||||
} else if round == 3 {
|
||||
// try to publish again
|
||||
// TODO: disabled, pending resolution of issue #1376
|
||||
// let publish_tx = make_contract_publish(&contract_sk, 1, 0, "faucet", FAUCET_CONTRACT);
|
||||
// tenure.mem_pool.submit(publish_tx);
|
||||
|
||||
let tx = make_contract_call(&sk_2, 0, 0, &to_addr(&contract_sk), "faucet", "spout", &[]);
|
||||
tenure.mem_pool.submit(tx);
|
||||
} else if round == 4 {
|
||||
// transfer to the contract again.
|
||||
let xfer_to_contract = make_stacks_transfer(&sk_3, 1, 0, &contract_identifier.into(), 1000);
|
||||
tenure.mem_pool.submit(xfer_to_contract);
|
||||
}
|
||||
|
||||
return
|
||||
});
|
||||
|
||||
run_loop.apply_on_new_chain_states(|round, chain_state, block, chain_tip_info, _events| {
|
||||
let contract_identifier =
|
||||
QualifiedContractIdentifier::parse(&format!("{}.{}",
|
||||
to_addr(
|
||||
&StacksPrivateKey::from_hex(SK_1).unwrap()).to_string(),
|
||||
"faucet")).unwrap();
|
||||
|
||||
match round {
|
||||
1 => {
|
||||
assert!(chain_tip_info.block_height == 2);
|
||||
// Block #1 should have 2 txs -- coinbase + transfer
|
||||
assert!(block.txs.len() == 2);
|
||||
|
||||
let cur_tip = (chain_tip_info.burn_header_hash.clone(), chain_tip_info.anchored_header.block_hash());
|
||||
// check that 1000 stx _was_ transfered to the contract principal
|
||||
assert_eq!(
|
||||
chain_state.with_read_only_clarity_tx(&cur_tip.0, &cur_tip.1, |conn| {
|
||||
conn.with_clarity_db_readonly(|db| {
|
||||
db.get_account_stx_balance(&contract_identifier.clone().into())
|
||||
})
|
||||
}),
|
||||
1000);
|
||||
// check that 1000 stx _was_ debited from SK_3
|
||||
let sk_3 = StacksPrivateKey::from_hex(SK_3).unwrap();
|
||||
let addr_3 = to_addr(&sk_3).into();
|
||||
assert_eq!(
|
||||
chain_state.with_read_only_clarity_tx(&cur_tip.0, &cur_tip.1, |conn| {
|
||||
conn.with_clarity_db_readonly(|db| {
|
||||
db.get_account_stx_balance(&addr_3)
|
||||
})
|
||||
}),
|
||||
99000);
|
||||
},
|
||||
2 => {
|
||||
assert!(chain_tip_info.block_height == 3);
|
||||
// Block #2 should have 2 txs -- coinbase + publish
|
||||
assert!(block.txs.len() == 2);
|
||||
},
|
||||
3 => {
|
||||
assert!(chain_tip_info.block_height == 4);
|
||||
// Block #3 should have 2 txs -- coinbase + contract-call,
|
||||
// the second publish _should have been rejected_
|
||||
assert!(block.txs.len() == 2);
|
||||
|
||||
// check that 1 stx was transfered to SK_2 via the contract-call
|
||||
let cur_tip = (chain_tip_info.burn_header_hash.clone(), chain_tip_info.anchored_header.block_hash());
|
||||
|
||||
let sk_2 = StacksPrivateKey::from_hex(SK_2).unwrap();
|
||||
let addr_2 = to_addr(&sk_2).into();
|
||||
assert_eq!(
|
||||
chain_state.with_read_only_clarity_tx(&cur_tip.0, &cur_tip.1, |conn| {
|
||||
conn.with_clarity_db_readonly(|db| {
|
||||
db.get_account_stx_balance(&addr_2)
|
||||
})
|
||||
}),
|
||||
1);
|
||||
|
||||
assert_eq!(
|
||||
chain_state.with_read_only_clarity_tx(&cur_tip.0, &cur_tip.1, |conn| {
|
||||
conn.with_clarity_db_readonly(|db| {
|
||||
db.get_account_stx_balance(&contract_identifier.clone().into())
|
||||
})
|
||||
}),
|
||||
999);
|
||||
},
|
||||
4 => {
|
||||
assert!(chain_tip_info.block_height == 5);
|
||||
assert!(block.txs.len() == 2);
|
||||
|
||||
let cur_tip = (chain_tip_info.burn_header_hash.clone(), chain_tip_info.anchored_header.block_hash());
|
||||
|
||||
// check that 1000 stx were sent to the contract
|
||||
assert_eq!(
|
||||
chain_state.with_read_only_clarity_tx(&cur_tip.0, &cur_tip.1, |conn| {
|
||||
conn.with_clarity_db_readonly(|db| {
|
||||
db.get_account_stx_balance(&contract_identifier.clone().into())
|
||||
})
|
||||
}),
|
||||
1999);
|
||||
// check that 1000 stx _was_ debited from SK_3
|
||||
let sk_3 = StacksPrivateKey::from_hex(SK_3).unwrap();
|
||||
let addr_3 = to_addr(&sk_3).into();
|
||||
assert_eq!(
|
||||
chain_state.with_read_only_clarity_tx(&cur_tip.0, &cur_tip.1, |conn| {
|
||||
conn.with_clarity_db_readonly(|db| {
|
||||
db.get_account_stx_balance(&addr_3)
|
||||
})
|
||||
}),
|
||||
98000);
|
||||
},
|
||||
|
||||
_ => {},
|
||||
}
|
||||
});
|
||||
|
||||
run_loop.start(num_rounds);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "stacks-helium"
|
||||
name = "stacks-testnet"
|
||||
version = "0.1.0"
|
||||
authors = ["Ludo Galabru <ludovic@blockstack.com>"]
|
||||
edition = "2018"
|
||||
@@ -13,9 +13,9 @@ secp256k1 = { version = "0.11.5" }
|
||||
serde = "1"
|
||||
serde_derive = "1"
|
||||
serde_json = { version = "1.0", features = ["arbitrary_precision"] }
|
||||
stacks = { package = "blockstack-core", path = "../../." }
|
||||
stacks = { package = "blockstack-core", path = "../." }
|
||||
toml = "0.5.6"
|
||||
|
||||
[[bin]]
|
||||
name = "stacks-helium"
|
||||
name = "stacks-testnet"
|
||||
path = "src/main.rs"
|
||||
@@ -1,6 +1,7 @@
|
||||
[node]
|
||||
name = "helium-node"
|
||||
rpc_bind = "127.0.0.1:9000"
|
||||
working_dir = "/tmp/stacks-helium"
|
||||
|
||||
## Settings for local testnet, relying on a local bitcoind server
|
||||
## running with the following bitcoin.conf:
|
||||
@@ -20,7 +20,6 @@ pub mod event_dispatcher;
|
||||
pub mod operations;
|
||||
pub mod burnchains;
|
||||
|
||||
pub use self::run_loop::{RunLoop};
|
||||
pub use self::mem_pool::{MemPool, MemPoolFS};
|
||||
pub use self::keychain::{Keychain};
|
||||
pub use self::node::{Node, ChainTip};
|
||||
@@ -28,6 +27,8 @@ pub use self::burnchains::{MocknetController, BitcoinRegtestController, Burnchai
|
||||
pub use self::tenure::{Tenure};
|
||||
pub use self::config::{Config, ConfigFile};
|
||||
pub use self::event_dispatcher::{EventDispatcher};
|
||||
pub use self::run_loop::{neon, helium};
|
||||
|
||||
|
||||
use std::env;
|
||||
|
||||
@@ -50,9 +51,27 @@ fn main() {
|
||||
|
||||
println!("*** Mempool path: {}", conf.mempool.path);
|
||||
|
||||
let mut run_loop = RunLoop::new(conf);
|
||||
|
||||
// ^C2020-04-14T15:36:48Z tor: Thread interrupt
|
||||
// 2020-04-14T15:36:48Z torcontrol thread exit
|
||||
// 2020-04-14T15:36:48Z opencon thread exit
|
||||
// 2020-04-14T15:36:48Z addcon thread exit
|
||||
// 2020-04-14T15:36:48Z Shutdown: In progress...
|
||||
// 2020-04-14T15:36:48Z net thread exit
|
||||
// 2020-04-14T15:36:48Z msghand thread exit
|
||||
// 2020-04-14T15:36:48Z scheduler thread interrupt
|
||||
// 2020-04-14T15:36:48Z Dumped mempool: 9e-06s to copy, 0.002823s to dump
|
||||
// 2020-04-14T15:36:48Z [default wallet] Releasing wallet
|
||||
// 2020-04-14T15:36:48Z Shutdown: done
|
||||
|
||||
let num_round: u64 = 0; // Infinite number of rounds
|
||||
run_loop.start(num_round);
|
||||
if conf.burnchain.mode == "helium" {
|
||||
let mut run_loop = helium::RunLoop::new(conf);
|
||||
run_loop.start(num_round);
|
||||
} else if conf.burnchain.mode == "neon" {
|
||||
let mut run_loop = neon::RunLoop::new(conf);
|
||||
run_loop.start(num_round);
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ impl Node {
|
||||
Some(initial_balances),
|
||||
boot_block_exec) {
|
||||
Ok(res) => res,
|
||||
Err(_) => panic!("Error while opening chain state at path {:?}", config.get_chainstate_path())
|
||||
Err(err) => panic!("Error while opening chain state at path {}: {:?}", config.get_chainstate_path(), err)
|
||||
};
|
||||
|
||||
let mem_pool = MemPoolFS::new(&config.mempool.path);
|
||||
@@ -1,36 +1,15 @@
|
||||
use super::{Config, Node, BurnchainController, MocknetController, BitcoinRegtestController, BurnchainTip, ChainTip, Tenure};
|
||||
use crate::{Config, Node, BurnchainController, MocknetController, BitcoinRegtestController, ChainTip};
|
||||
|
||||
use stacks::chainstate::stacks::db::{StacksChainState, ClarityTx};
|
||||
use stacks::chainstate::stacks::{TransactionAuth, TransactionSpendingCondition, TransactionPayload};
|
||||
use stacks::chainstate::stacks::db::ClarityTx;
|
||||
|
||||
use super::RunLoopCallbacks;
|
||||
|
||||
/// RunLoop is coordinating a simulated burnchain and some simulated nodes
|
||||
/// taking turns in producing blocks.
|
||||
pub struct RunLoop {
|
||||
config: Config,
|
||||
pub node: Node,
|
||||
burnchain_initialized_callback: Option<fn(&mut Box<dyn BurnchainController>)>,
|
||||
new_burnchain_state_callback: Option<fn(u64, &BurnchainTip, &ChainTip)>,
|
||||
new_tenure_callback: Option<fn(u64, &Tenure)>,
|
||||
new_chain_state_callback: Option<fn(u64, &mut StacksChainState, &ChainTip, &BurnchainTip)>,
|
||||
}
|
||||
|
||||
macro_rules! info_blue {
|
||||
($($arg:tt)*) => ({
|
||||
eprintln!("\x1b[0;96m{}\x1b[0m", format!($($arg)*));
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(unused_macros)]
|
||||
macro_rules! info_yellow {
|
||||
($($arg:tt)*) => ({
|
||||
eprintln!("\x1b[0;33m{}\x1b[0m", format!($($arg)*));
|
||||
})
|
||||
}
|
||||
|
||||
macro_rules! info_green {
|
||||
($($arg:tt)*) => ({
|
||||
eprintln!("\x1b[0;32m{}\x1b[0m", format!($($arg)*));
|
||||
})
|
||||
pub callbacks: RunLoopCallbacks,
|
||||
}
|
||||
|
||||
impl RunLoop {
|
||||
@@ -48,10 +27,7 @@ impl RunLoop {
|
||||
Self {
|
||||
config,
|
||||
node,
|
||||
burnchain_initialized_callback: None,
|
||||
new_burnchain_state_callback: None,
|
||||
new_tenure_callback: None,
|
||||
new_chain_state_callback: None,
|
||||
callbacks: RunLoopCallbacks::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +41,7 @@ impl RunLoop {
|
||||
|
||||
// Initialize and start the burnchain.
|
||||
let mut burnchain: Box<dyn BurnchainController> = match &self.config.burnchain.mode[..] {
|
||||
"helium" | "neon" => {
|
||||
"helium" => {
|
||||
BitcoinRegtestController::generic(self.config.clone())
|
||||
},
|
||||
"mocknet" => {
|
||||
@@ -74,14 +50,12 @@ impl RunLoop {
|
||||
_ => unreachable!()
|
||||
};
|
||||
|
||||
RunLoop::handle_burnchain_initialized_cb(
|
||||
&self.burnchain_initialized_callback,
|
||||
&mut burnchain);
|
||||
self.callbacks.invoke_burn_chain_initialized(&mut burnchain);
|
||||
|
||||
let genesis_state = burnchain.start();
|
||||
let initial_state = burnchain.start();
|
||||
|
||||
// Update each node with the genesis block.
|
||||
self.node.process_burnchain_state(&genesis_state);
|
||||
self.node.process_burnchain_state(&initial_state);
|
||||
|
||||
// make first non-genesis block, with initial VRF keys
|
||||
self.node.setup(&mut burnchain);
|
||||
@@ -92,7 +66,8 @@ impl RunLoop {
|
||||
|
||||
// Sync and update node with this new block.
|
||||
let burnchain_tip = burnchain.sync();
|
||||
self.node.process_burnchain_state(&burnchain_tip);
|
||||
self.node.process_burnchain_state(&burnchain_tip); // todo(ludo): should return genesis?
|
||||
let mut chain_tip = ChainTip::genesis();
|
||||
|
||||
if self.config.burnchain.mode == "mocknet" {
|
||||
self.node.spawn_peer_server();
|
||||
@@ -106,7 +81,7 @@ impl RunLoop {
|
||||
None => panic!("Error while initiating genesis tenure")
|
||||
};
|
||||
|
||||
RunLoop::handle_new_tenure_cb(&self.new_tenure_callback, round_index, &first_tenure);
|
||||
self.callbacks.invoke_new_tenure(round_index, &burnchain_tip, &chain_tip, &first_tenure);
|
||||
|
||||
// Run the tenure, keep the artifacts
|
||||
let artifacts_from_1st_tenure = match first_tenure.run() {
|
||||
@@ -125,38 +100,30 @@ impl RunLoop {
|
||||
artifacts_from_1st_tenure.burn_fee);
|
||||
|
||||
let mut burnchain_tip = burnchain.sync();
|
||||
|
||||
RunLoop::handle_burnchain_state_cb(
|
||||
&self.new_burnchain_state_callback,
|
||||
round_index,
|
||||
&burnchain_tip,
|
||||
&ChainTip::genesis());
|
||||
self.callbacks.invoke_new_burn_chain_state(round_index, &burnchain_tip, &chain_tip);
|
||||
|
||||
let mut leader_tenure = None;
|
||||
|
||||
// Have each node process the new block, that should include a sortition thanks to the
|
||||
// 1st tenure.
|
||||
let (last_sortitioned_block, won_sortition) = match self.node.process_burnchain_state(&burnchain_tip) {
|
||||
(Some(sortitioned_block), won_sortition) => (sortitioned_block, won_sortition),
|
||||
(None, _) => panic!("Node should have a sortitioned block")
|
||||
};
|
||||
|
||||
// Have each node process the previous tenure.
|
||||
// Have the node process its own tenure.
|
||||
// We should have some additional checks here, and ensure that the previous artifacts are legit.
|
||||
|
||||
let mut chain_tip = self.node.process_tenure(
|
||||
chain_tip = self.node.process_tenure(
|
||||
&artifacts_from_1st_tenure.anchored_block,
|
||||
&last_sortitioned_block.block_snapshot.burn_header_hash,
|
||||
&last_sortitioned_block.block_snapshot.parent_burn_header_hash,
|
||||
artifacts_from_1st_tenure.microblocks.clone(),
|
||||
burnchain.burndb_mut());
|
||||
|
||||
RunLoop::handle_new_chain_state_cb(
|
||||
&self.new_chain_state_callback,
|
||||
self.callbacks.invoke_new_stacks_chain_state(
|
||||
round_index,
|
||||
&mut self.node.chain_state,
|
||||
&chain_tip,
|
||||
&burnchain_tip);
|
||||
&burnchain_tip,
|
||||
&chain_tip,
|
||||
&mut self.node.chain_state);
|
||||
|
||||
// If the node we're looping on won the sortition, initialize and configure the next tenure
|
||||
if won_sortition {
|
||||
@@ -173,7 +140,7 @@ impl RunLoop {
|
||||
// Run the last initialized tenure
|
||||
let artifacts_from_tenure = match leader_tenure {
|
||||
Some(mut tenure) => {
|
||||
RunLoop::handle_new_tenure_cb(&self.new_tenure_callback, round_index, &tenure);
|
||||
self.callbacks.invoke_new_tenure(round_index, &burnchain_tip, &chain_tip, &tenure);
|
||||
tenure.run()
|
||||
},
|
||||
None => None
|
||||
@@ -192,11 +159,7 @@ impl RunLoop {
|
||||
}
|
||||
|
||||
burnchain_tip = burnchain.sync();
|
||||
RunLoop::handle_burnchain_state_cb(
|
||||
&self.new_burnchain_state_callback,
|
||||
round_index,
|
||||
&burnchain_tip,
|
||||
&chain_tip);
|
||||
self.callbacks.invoke_new_burn_chain_state(round_index, &burnchain_tip, &chain_tip);
|
||||
|
||||
leader_tenure = None;
|
||||
|
||||
@@ -210,7 +173,7 @@ impl RunLoop {
|
||||
// Pass if we're missing the artifacts from the current tenure.
|
||||
None => continue,
|
||||
Some(ref artifacts) => {
|
||||
// Have each node process the previous tenure.
|
||||
// Have the node process its tenure.
|
||||
// We should have some additional checks here, and ensure that the previous artifacts are legit.
|
||||
chain_tip = self.node.process_tenure(
|
||||
&artifacts.anchored_block,
|
||||
@@ -219,17 +182,15 @@ impl RunLoop {
|
||||
artifacts.microblocks.clone(),
|
||||
burnchain.burndb_mut());
|
||||
|
||||
RunLoop::handle_new_chain_state_cb(
|
||||
&self.new_chain_state_callback,
|
||||
round_index,
|
||||
&mut self.node.chain_state,
|
||||
&chain_tip,
|
||||
&burnchain_tip
|
||||
);
|
||||
self.callbacks.invoke_new_stacks_chain_state(
|
||||
round_index,
|
||||
&burnchain_tip,
|
||||
&chain_tip,
|
||||
&mut self.node.chain_state);
|
||||
},
|
||||
};
|
||||
|
||||
// If the node we're looping on won the sortition, initialize and configure the next tenure
|
||||
// If won sortition, initialize and configure the next tenure
|
||||
if won_sortition {
|
||||
leader_tenure = self.node.initiate_new_tenure();
|
||||
}
|
||||
@@ -237,60 +198,4 @@ impl RunLoop {
|
||||
round_index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_once_burnchain_initialized(&mut self, f: fn(&mut Box<dyn BurnchainController>)) {
|
||||
self.burnchain_initialized_callback = Some(f);
|
||||
}
|
||||
|
||||
pub fn apply_on_new_burnchain_states(&mut self, f: fn(u64, &BurnchainTip, &ChainTip)) {
|
||||
self.new_burnchain_state_callback = Some(f);
|
||||
}
|
||||
|
||||
pub fn apply_on_new_tenures(&mut self, f: fn(u64, &Tenure)) {
|
||||
self.new_tenure_callback = Some(f);
|
||||
}
|
||||
|
||||
pub fn apply_on_new_chain_states(&mut self, f: fn(u64, &mut StacksChainState, &ChainTip, &BurnchainTip)) {
|
||||
self.new_chain_state_callback = Some(f);
|
||||
}
|
||||
|
||||
fn handle_burnchain_initialized_cb(burnchain_initialized_callback: &Option<fn(&mut Box<dyn BurnchainController>)>, burnchain_controller: &mut Box<dyn BurnchainController>) {
|
||||
burnchain_initialized_callback.map(|cb| cb(burnchain_controller));
|
||||
}
|
||||
|
||||
fn handle_new_tenure_cb(new_tenure_callback: &Option<fn(u64, &Tenure)>,
|
||||
round_index: u64, tenure: &Tenure) {
|
||||
new_tenure_callback.map(|cb| cb(round_index, tenure));
|
||||
}
|
||||
|
||||
fn handle_burnchain_state_cb(burn_callback: &Option<fn(u64, &BurnchainTip, & ChainTip)>,
|
||||
round_index: u64, burnchain_tip: &BurnchainTip, chain_tip: &ChainTip) {
|
||||
info_blue!("Burnchain block #{} ({}) was produced with sortition #{}",
|
||||
burnchain_tip.block_snapshot.block_height,
|
||||
burnchain_tip.block_snapshot.burn_header_hash,
|
||||
burnchain_tip.block_snapshot.sortition_hash);
|
||||
burn_callback.map(|cb| cb(round_index, burnchain_tip, chain_tip));
|
||||
}
|
||||
|
||||
fn handle_new_chain_state_cb(chain_state_callback: &Option<fn(u64, &mut StacksChainState, &ChainTip, &BurnchainTip)>,
|
||||
round_index: u64, state: &mut StacksChainState, chain_tip: &ChainTip, burnchain_tip: &BurnchainTip) {
|
||||
info_green!("Stacks block #{} ({}) successfully produced, including {} transactions",
|
||||
chain_tip.metadata.block_height,
|
||||
chain_tip.metadata.index_block_hash(),
|
||||
chain_tip.block.txs.len());
|
||||
for tx in chain_tip.block.txs.iter() {
|
||||
match &tx.auth {
|
||||
TransactionAuth::Standard(TransactionSpendingCondition::Singlesig(auth)) => println!("-> Tx issued by {:?} (fee: {}, nonce: {})", auth.signer, auth.fee_rate, auth.nonce),
|
||||
_ => println!("-> Tx {:?}", tx.auth)
|
||||
}
|
||||
match &tx.payload {
|
||||
TransactionPayload::Coinbase(_) => println!(" Coinbase"),
|
||||
TransactionPayload::SmartContract(contract) => println!(" Publish smart contract\n**************************\n{:?}\n**************************", contract.code_body),
|
||||
TransactionPayload::TokenTransfer(recipent, amount, _) => println!(" Transfering {} µSTX to {}", amount, recipent.to_string()),
|
||||
_ => println!(" {:?}", tx.payload)
|
||||
}
|
||||
}
|
||||
chain_state_callback.map(|cb| cb(round_index, state, chain_tip, burnchain_tip));
|
||||
}
|
||||
|
||||
}
|
||||
107
testnet/src/run_loop/mod.rs
Normal file
107
testnet/src/run_loop/mod.rs
Normal file
@@ -0,0 +1,107 @@
|
||||
pub mod helium;
|
||||
pub mod neon;
|
||||
|
||||
use crate::{BurnchainController, BurnchainTip, ChainTip, Tenure};
|
||||
|
||||
use stacks::chainstate::stacks::{TransactionAuth, TransactionSpendingCondition, TransactionPayload};
|
||||
use stacks::chainstate::stacks::db::StacksChainState;
|
||||
|
||||
macro_rules! info_blue {
|
||||
($($arg:tt)*) => ({
|
||||
eprintln!("\x1b[0;96m{}\x1b[0m", format!($($arg)*));
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(unused_macros)]
|
||||
macro_rules! info_yellow {
|
||||
($($arg:tt)*) => ({
|
||||
eprintln!("\x1b[0;33m{}\x1b[0m", format!($($arg)*));
|
||||
})
|
||||
}
|
||||
|
||||
macro_rules! info_green {
|
||||
($($arg:tt)*) => ({
|
||||
eprintln!("\x1b[0;32m{}\x1b[0m", format!($($arg)*));
|
||||
})
|
||||
}
|
||||
|
||||
pub struct RunLoopCallbacks {
|
||||
on_burn_chain_initialized: Option<fn(&mut Box<dyn BurnchainController>)>,
|
||||
on_new_burn_chain_state: Option<fn(u64, &BurnchainTip, &ChainTip)>,
|
||||
on_new_stacks_chain_state: Option<fn(u64, &BurnchainTip, &ChainTip, &mut StacksChainState)>,
|
||||
on_new_tenure: Option<fn(u64, &BurnchainTip, &ChainTip, &Tenure)>,
|
||||
}
|
||||
|
||||
impl RunLoopCallbacks {
|
||||
pub fn new() -> RunLoopCallbacks {
|
||||
RunLoopCallbacks {
|
||||
on_burn_chain_initialized: None,
|
||||
on_new_burn_chain_state: None,
|
||||
on_new_stacks_chain_state: None,
|
||||
on_new_tenure: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_burn_chain_initialized(&mut self, callback: fn(&mut Box<dyn BurnchainController>)) {
|
||||
self.on_burn_chain_initialized = Some(callback);
|
||||
}
|
||||
|
||||
pub fn on_new_burn_chain_state(&mut self, callback: fn(u64, &BurnchainTip, &ChainTip)) {
|
||||
self.on_new_burn_chain_state = Some(callback);
|
||||
}
|
||||
|
||||
pub fn on_new_stacks_chain_state(&mut self, callback: fn(u64, &BurnchainTip, &ChainTip, &mut StacksChainState)) {
|
||||
self.on_new_stacks_chain_state = Some(callback);
|
||||
}
|
||||
|
||||
pub fn on_new_tenure(&mut self, callback: fn(u64, &BurnchainTip, &ChainTip, &Tenure)) {
|
||||
self.on_new_tenure = Some(callback);
|
||||
}
|
||||
|
||||
pub fn invoke_burn_chain_initialized(&self, burnchain: &mut Box<dyn BurnchainController>) {
|
||||
if let Some(cb) = self.on_burn_chain_initialized {
|
||||
cb(burnchain);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn invoke_new_burn_chain_state(&self, round: u64, burnchain_tip: &BurnchainTip, chain_tip: &ChainTip) {
|
||||
info_blue!("Burnchain block #{} ({}) was produced with sortition #{}",
|
||||
burnchain_tip.block_snapshot.block_height,
|
||||
burnchain_tip.block_snapshot.burn_header_hash,
|
||||
burnchain_tip.block_snapshot.sortition_hash);
|
||||
|
||||
if let Some(cb) = self.on_new_burn_chain_state {
|
||||
cb(round, burnchain_tip, chain_tip);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn invoke_new_stacks_chain_state(&self, round: u64, burnchain_tip: &BurnchainTip, chain_tip: &ChainTip, chain_state: &mut StacksChainState) {
|
||||
info_green!("Stacks block #{} ({}) successfully produced, including {} transactions",
|
||||
chain_tip.metadata.block_height,
|
||||
chain_tip.metadata.index_block_hash(),
|
||||
chain_tip.block.txs.len());
|
||||
for tx in chain_tip.block.txs.iter() {
|
||||
match &tx.auth {
|
||||
TransactionAuth::Standard(TransactionSpendingCondition::Singlesig(auth)) => println!("-> Tx issued by {:?} (fee: {}, nonce: {})", auth.signer, auth.fee_rate, auth.nonce),
|
||||
_ => println!("-> Tx {:?}", tx.auth)
|
||||
}
|
||||
match &tx.payload {
|
||||
TransactionPayload::Coinbase(_) => println!(" Coinbase"),
|
||||
TransactionPayload::SmartContract(contract) => println!(" Publish smart contract\n**************************\n{:?}\n**************************", contract.code_body),
|
||||
TransactionPayload::TokenTransfer(recipent, amount, _) => println!(" Transfering {} µSTX to {}", amount, recipent.to_string()),
|
||||
_ => println!(" {:?}", tx.payload)
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(cb) = self.on_new_stacks_chain_state {
|
||||
cb(round, burnchain_tip, chain_tip, chain_state);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn invoke_new_tenure(&self, round: u64, burnchain_tip: &BurnchainTip, chain_tip: &ChainTip, tenure: &Tenure) {
|
||||
if let Some(cb) = self.on_new_tenure {
|
||||
cb(round, burnchain_tip, chain_tip, tenure);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
125
testnet/src/run_loop/neon.rs
Normal file
125
testnet/src/run_loop/neon.rs
Normal file
@@ -0,0 +1,125 @@
|
||||
use crate::{Config, Node, BurnchainController, BitcoinRegtestController, ChainTip};
|
||||
|
||||
use stacks::chainstate::stacks::db::ClarityTx;
|
||||
use super::RunLoopCallbacks;
|
||||
|
||||
/// Coordinating a node running in neon mode.
|
||||
pub struct RunLoop {
|
||||
config: Config,
|
||||
pub node: Node,
|
||||
pub callbacks: RunLoopCallbacks,
|
||||
}
|
||||
|
||||
impl RunLoop {
|
||||
pub fn new(config: Config) -> Self {
|
||||
RunLoop::new_with_boot_exec(config, |_| {})
|
||||
}
|
||||
|
||||
/// Sets up a runloop and node, given a config.
|
||||
pub fn new_with_boot_exec<F>(config: Config, boot_exec: F) -> Self
|
||||
where F: Fn(&mut ClarityTx) -> () {
|
||||
|
||||
// Build node based on config
|
||||
let node = Node::new(config.clone(), boot_exec);
|
||||
|
||||
Self {
|
||||
config,
|
||||
node,
|
||||
callbacks: RunLoopCallbacks::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Starts the testnet runloop.
|
||||
///
|
||||
/// This function will block by looping infinitely.
|
||||
/// It will start the burnchain (separate thread), set-up a channel in
|
||||
/// charge of coordinating the new blocks coming from the burnchain and
|
||||
/// the nodes, taking turns on tenures.
|
||||
pub fn start(&mut self, expected_num_rounds: u64) {
|
||||
|
||||
// Initialize and start the burnchain.
|
||||
let mut burnchain: Box<dyn BurnchainController> = BitcoinRegtestController::generic(self.config.clone());
|
||||
|
||||
self.callbacks.invoke_burn_chain_initialized(&mut burnchain);
|
||||
|
||||
let mut burnchain_tip = burnchain.start();
|
||||
|
||||
// TODO: enable this once available
|
||||
// self.node.spawn_peer_server();
|
||||
|
||||
// todo(ludo): ensure that burnchain_state and chain_state are consistent
|
||||
// todo(ludo): node should retrieve prior keys, thanks to burnchain
|
||||
self.node.process_burnchain_state(&burnchain_tip);
|
||||
|
||||
self.node.setup(&mut burnchain);
|
||||
|
||||
let mut round_index: u64 = 1;
|
||||
|
||||
let mut leader_tenure = None;
|
||||
|
||||
let mut chain_tip = ChainTip::genesis(); // todo(ludo): fix
|
||||
|
||||
// Start the runloop
|
||||
loop {
|
||||
if expected_num_rounds == round_index {
|
||||
return;
|
||||
}
|
||||
|
||||
// Run the last initialized tenure
|
||||
let artifacts_from_tenure = match leader_tenure {
|
||||
Some(mut tenure) => {
|
||||
self.callbacks.invoke_new_tenure(round_index, &burnchain_tip, &chain_tip, &tenure);
|
||||
tenure.run()
|
||||
},
|
||||
None => None
|
||||
};
|
||||
|
||||
match artifacts_from_tenure {
|
||||
Some(ref artifacts) => {
|
||||
self.node.commit_artifacts(
|
||||
&artifacts.anchored_block,
|
||||
&artifacts.parent_block,
|
||||
&mut burnchain,
|
||||
artifacts.burn_fee);
|
||||
},
|
||||
None => {}
|
||||
}
|
||||
|
||||
burnchain_tip = burnchain.sync();
|
||||
self.callbacks.invoke_new_burn_chain_state(round_index, &burnchain_tip, &chain_tip);
|
||||
|
||||
leader_tenure = None;
|
||||
|
||||
// Have each node process the new block, that can include, or not, a sortition.
|
||||
let (sortitioned_block, won_sortition) = self.node.process_burnchain_state(&burnchain_tip);
|
||||
|
||||
match (artifacts_from_tenure, sortitioned_block) {
|
||||
// Pass if we're missing the artifacts from the current tenure.
|
||||
(Some(ref artifacts), Some(ref last_sortitioned_block)) => {
|
||||
// Have each node process the previous tenure.
|
||||
// We should have some additional checks here, and ensure that the previous artifacts are legit.
|
||||
chain_tip = self.node.process_tenure(
|
||||
&artifacts.anchored_block,
|
||||
&last_sortitioned_block.block_snapshot.burn_header_hash,
|
||||
&last_sortitioned_block.block_snapshot.parent_burn_header_hash,
|
||||
artifacts.microblocks.clone(),
|
||||
burnchain.burndb_mut());
|
||||
|
||||
self.callbacks.invoke_new_stacks_chain_state(
|
||||
round_index,
|
||||
&burnchain_tip,
|
||||
&chain_tip,
|
||||
&mut self.node.chain_state);
|
||||
},
|
||||
(_, _) => continue,
|
||||
};
|
||||
|
||||
// If the node we're looping on won the sortition, initialize and configure the next tenure
|
||||
if won_sortition {
|
||||
leader_tenure = self.node.initiate_new_tenure();
|
||||
}
|
||||
|
||||
round_index += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::process::{Command, Stdio, Child};
|
||||
|
||||
use super::{Config, RunLoop, MemPool};
|
||||
use crate::{Config, MemPool};
|
||||
use crate::helium::RunLoop;
|
||||
|
||||
use stacks::chainstate::burn::operations::BlockstackOperationType::{LeaderBlockCommit, LeaderKeyRegister};
|
||||
use stacks::util::hash::{hex_bytes};
|
||||
@@ -98,14 +99,14 @@ fn simple_test() {
|
||||
let num_rounds = 6;
|
||||
let mut run_loop = RunLoop::new(conf);
|
||||
|
||||
run_loop.apply_once_burnchain_initialized(|burnchain_controller| {
|
||||
run_loop.callbacks.on_burn_chain_initialized(|burnchain_controller| {
|
||||
// todo(ludo): we need to wait for bitcoind to be ready.
|
||||
sleep_ms(5000);
|
||||
burnchain_controller.bootstrap_chain();
|
||||
});
|
||||
|
||||
// In this serie of tests, the callback is fired post-burnchain-sync, pre-stacks-sync
|
||||
run_loop.apply_on_new_burnchain_states(|round, burnchain_tip, chain_tip| {
|
||||
run_loop.callbacks.on_new_burn_chain_state(|round, burnchain_tip, chain_tip| {
|
||||
match round {
|
||||
0 => {
|
||||
let block = &burnchain_tip.block_snapshot;
|
||||
@@ -267,7 +268,7 @@ fn simple_test() {
|
||||
});
|
||||
|
||||
// Use tenure's hook for submitting transactions
|
||||
run_loop.apply_on_new_tenures(|round, tenure| {
|
||||
run_loop.callbacks.on_new_tenure(|round, _burnchain_tip, _chain_tip, tenure| {
|
||||
match round {
|
||||
1 => {
|
||||
// On round 1, publish the KV contract
|
||||
@@ -319,7 +320,7 @@ fn simple_test() {
|
||||
|
||||
// Use block's hook for asserting expectations
|
||||
// In this serie of tests, the callback is fired post-burnchain-sync, post-stacks-sync
|
||||
run_loop.apply_on_new_chain_states(|round, _chain_state, chain_tip, burnchain_tip| {
|
||||
run_loop.callbacks.on_new_stacks_chain_state(|round, burnchain_tip, chain_tip, _chain_state| {
|
||||
match round {
|
||||
0 => {
|
||||
// Inspecting the chain at round 0.
|
||||
@@ -18,7 +18,9 @@ use stacks::net::{StacksMessageCodec, AccountEntryResponse, ContractSrcResponse,
|
||||
use stacks::util::{strings::StacksString};
|
||||
use stacks::vm::clarity::ClarityConnection;
|
||||
|
||||
use super::super::{MemPool, RunLoop, config::InitialBalance};
|
||||
use crate::{MemPool, config::InitialBalance};
|
||||
use crate::helium::RunLoop;
|
||||
|
||||
|
||||
use reqwest;
|
||||
|
||||
@@ -193,7 +195,7 @@ fn integration_test_get_info() {
|
||||
http_opt.replace(format!("http://{}", &run_loop.node.config.node.rpc_bind));
|
||||
}
|
||||
|
||||
run_loop.apply_on_new_tenures(|round, tenure| {
|
||||
run_loop.callbacks.on_new_tenure(|round, _burnchain_tip, _chain_tip, tenure| {
|
||||
let contract_sk = StacksPrivateKey::from_hex(SK_1).unwrap();
|
||||
let principal_sk = StacksPrivateKey::from_hex(SK_2).unwrap();
|
||||
let spender_sk = StacksPrivateKey::from_hex(SK_3).unwrap();
|
||||
@@ -216,7 +218,7 @@ fn integration_test_get_info() {
|
||||
return
|
||||
});
|
||||
|
||||
run_loop.apply_on_new_chain_states(|round, chain_state, chain_tip, _burnchain_tip| {
|
||||
run_loop.callbacks.on_new_stacks_chain_state(|round, _burnchain_tip, chain_tip, chain_state| {
|
||||
let contract_addr = to_addr(&StacksPrivateKey::from_hex(SK_1).unwrap());
|
||||
let contract_identifier =
|
||||
QualifiedContractIdentifier::parse(&format!("{}.{}", &contract_addr, "get-info")).unwrap();
|
||||
@@ -606,7 +608,7 @@ fn contract_stx_transfer() {
|
||||
let num_rounds = 5;
|
||||
|
||||
let mut run_loop = RunLoop::new(conf);
|
||||
run_loop.apply_on_new_tenures(|round, tenure| {
|
||||
run_loop.callbacks.on_new_tenure(|round, _burnchain_tip, _chain_tip, tenure| {
|
||||
let contract_sk = StacksPrivateKey::from_hex(SK_1).unwrap();
|
||||
let sk_2 = StacksPrivateKey::from_hex(SK_2).unwrap();
|
||||
let sk_3 = StacksPrivateKey::from_hex(SK_3).unwrap();
|
||||
@@ -640,7 +642,7 @@ fn contract_stx_transfer() {
|
||||
return
|
||||
});
|
||||
|
||||
run_loop.apply_on_new_chain_states(|round, chain_state, chain_tip, _burnchain_tip| {
|
||||
run_loop.callbacks.on_new_stacks_chain_state(|round, _burnchain_tip, chain_tip, chain_state| {
|
||||
let contract_identifier =
|
||||
QualifiedContractIdentifier::parse(&format!("{}.{}",
|
||||
to_addr(
|
||||
@@ -16,7 +16,8 @@ use stacks::chainstate::stacks::{
|
||||
TokenTransferMemo,
|
||||
StacksTransaction, StacksAddress };
|
||||
|
||||
use super::super::{Keychain, MemPool, RunLoop};
|
||||
use crate::{Keychain, MemPool};
|
||||
use crate::helium::RunLoop;
|
||||
|
||||
const FOO_CONTRACT: &'static str = "(define-public (foo) (ok 1))
|
||||
(define-public (bar (x uint)) (ok x))";
|
||||
@@ -58,7 +59,7 @@ fn mempool_setup_chainstate() {
|
||||
|
||||
let mut run_loop = RunLoop::new(conf);
|
||||
|
||||
run_loop.apply_on_new_tenures(|round, tenure| {
|
||||
run_loop.callbacks.on_new_tenure(|round, _burnchain_tip, _chain_tip, tenure| {
|
||||
let contract_sk = StacksPrivateKey::from_hex(SK_1).unwrap();
|
||||
if round == 0 { // block-height = 2
|
||||
let publish_tx = make_contract_publish(&contract_sk, 0, 100, "foo_contract", FOO_CONTRACT);
|
||||
@@ -67,8 +68,8 @@ fn mempool_setup_chainstate() {
|
||||
}
|
||||
});
|
||||
|
||||
run_loop.apply_on_new_chain_states(|round, chainstate, chain_tip, _burnchain_tip| {
|
||||
// run_loop.apply_on_new_chain_states(|round, ref mut chainstate, bhh| {
|
||||
run_loop.callbacks.on_new_stacks_chain_state(|round, _burnchain_tip, chain_tip, chainstate| {
|
||||
// run_loop.callbacks.on_new_stacks_chain_state(|round, ref mut chainstate, bhh| {
|
||||
let contract_sk = StacksPrivateKey::from_hex(SK_1).unwrap();
|
||||
let contract_addr = to_addr(&contract_sk);
|
||||
|
||||
@@ -6,7 +6,8 @@ use stacks::chainstate::stacks::events::{StacksTransactionEvent, STXEventType};
|
||||
use stacks::chainstate::stacks::{TransactionPayload};
|
||||
use stacks::util::hash::{hex_bytes};
|
||||
|
||||
use super::{MemPool, Config, RunLoop};
|
||||
use super::{MemPool, Config};
|
||||
use crate::helium::RunLoop;
|
||||
use super::node::{TESTNET_CHAIN_ID};
|
||||
|
||||
pub fn new_test_conf() -> Config {
|
||||
@@ -28,7 +29,7 @@ fn should_succeed_mining_valid_txs() {
|
||||
let mut run_loop = RunLoop::new(conf);
|
||||
|
||||
// Use tenure's hook for submitting transactions
|
||||
run_loop.apply_on_new_tenures(|round, tenure| {
|
||||
run_loop.callbacks.on_new_tenure(|round, _burnchain_tip, _chain_tip, tenure| {
|
||||
match round {
|
||||
1 => {
|
||||
// On round 1, publish the KV contract
|
||||
@@ -79,7 +80,7 @@ fn should_succeed_mining_valid_txs() {
|
||||
});
|
||||
|
||||
// Use block's hook for asserting expectations
|
||||
run_loop.apply_on_new_chain_states(|round, _chain_state, chain_tip, _burnchain_tip| {
|
||||
run_loop.callbacks.on_new_stacks_chain_state(|round, _burnchain_tip, chain_tip, _chain_state| {
|
||||
match round {
|
||||
0 => {
|
||||
// Inspecting the chain at round 0.
|
||||
@@ -272,7 +273,7 @@ fn should_succeed_handling_malformed_and_valid_txs() {
|
||||
let mut run_loop = RunLoop::new(conf);
|
||||
|
||||
// Use tenure's hook for submitting transactions
|
||||
run_loop.apply_on_new_tenures(|round, tenure| {
|
||||
run_loop.callbacks.on_new_tenure(|round, _burnchain_tip, _chain_tip, tenure| {
|
||||
match round {
|
||||
1 => {
|
||||
// On round 1, publish the KV contract
|
||||
@@ -319,7 +320,7 @@ fn should_succeed_handling_malformed_and_valid_txs() {
|
||||
});
|
||||
|
||||
// Use block's hook for asserting expectations
|
||||
run_loop.apply_on_new_chain_states(|round, _chain_state, chain_tip, _burnchain_tip| {
|
||||
run_loop.callbacks.on_new_stacks_chain_state(|round, _burnchain_tip, chain_tip, _chain_state| {
|
||||
match round {
|
||||
0 => {
|
||||
// Inspecting the chain at round 0.
|
||||
Reference in New Issue
Block a user