mirror of
https://github.com/alexgo-io/stacks-puppet-node.git
synced 2026-01-12 22:43:42 +08:00
feat: introduce puppet mode controller
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -40,6 +40,8 @@ secrets*
|
||||
|
||||
# vim
|
||||
*.swp
|
||||
# idea
|
||||
.idea/
|
||||
|
||||
#Docker
|
||||
Docker/blockstore.ini
|
||||
|
||||
36
testnet/stacks-node/conf/puppetnet-conf.toml
Normal file
36
testnet/stacks-node/conf/puppetnet-conf.toml
Normal file
@@ -0,0 +1,36 @@
|
||||
[node]
|
||||
name = "helium-node"
|
||||
rpc_bind = "127.0.0.1:20443"
|
||||
p2p_bind = "127.0.0.1:20444"
|
||||
use_test_genesis_chainstate = true
|
||||
enable_puppet_mode = true
|
||||
# puppet_bind = "127.0.0.1:20445"
|
||||
|
||||
[connection_options]
|
||||
public_ip_address = "127.0.0.1:20444"
|
||||
|
||||
[burnchain]
|
||||
chain = "bitcoin"
|
||||
mode = "mocknet"
|
||||
commit_anchor_block_within = 0
|
||||
|
||||
# These are addresses from the README.md
|
||||
[[ustx_balance]]
|
||||
# Private key: b8d99fd45da58038d630d9855d3ca2466e8e0f89d3894c4724f0efc9ff4b51f001
|
||||
address = "ST2ZRX0K27GW0SP3GJCEMHD95TQGJMKB7G9Y0X1MH"
|
||||
amount = 100000000
|
||||
|
||||
[[ustx_balance]]
|
||||
# Private key: 3a4e84abb8abe0c1ba37cef4b604e73c82b1fe8d99015cb36b029a65099d373601
|
||||
address = "ST26FVX16539KKXZKJN098Q08HRX3XBAP541MFS0P"
|
||||
amount = 100000000
|
||||
|
||||
[[ustx_balance]]
|
||||
# Private key: 052cc5b8f25b1e44a65329244066f76c8057accd5316c889f476d0ea0329632c01
|
||||
address = "ST3CECAKJ4BH08JYY7W53MC81BYDT4YDA5M7S5F53"
|
||||
amount = 100000000
|
||||
|
||||
[[ustx_balance]]
|
||||
# Private key: 9aef533e754663a453984b69d36f109be817e9940519cc84979419e2be00864801
|
||||
address = "ST31HHVBKYCYQQJ5AQ25ZHA6W2A548ZADDQ6S16GP"
|
||||
amount = 100000000
|
||||
@@ -670,6 +670,10 @@ impl Config {
|
||||
// chainstate fault_injection activation for hide_blocks.
|
||||
// you can't set this in the config file.
|
||||
fault_injection_hide_blocks: false,
|
||||
enable_puppet_mode: node
|
||||
.enable_puppet_mode
|
||||
.unwrap_or(default_node_config.enable_puppet_mode),
|
||||
puppet_bind: node.puppet_bind.unwrap_or(default_node_config.puppet_bind),
|
||||
};
|
||||
(node_config, node.bootstrap_node, node.deny_nodes)
|
||||
}
|
||||
@@ -1455,6 +1459,8 @@ pub struct NodeConfig {
|
||||
// fault injection for hiding blocks.
|
||||
// not part of the config file.
|
||||
pub fault_injection_hide_blocks: bool,
|
||||
pub enable_puppet_mode: bool,
|
||||
pub puppet_bind: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -1698,6 +1704,7 @@ impl NodeConfig {
|
||||
|
||||
let rpc_port = 20443;
|
||||
let p2p_port = 20444;
|
||||
let puppet_port = 20445;
|
||||
|
||||
let mut local_peer_seed = [0u8; 32];
|
||||
rng.fill_bytes(&mut local_peer_seed);
|
||||
@@ -1732,6 +1739,8 @@ impl NodeConfig {
|
||||
always_use_affirmation_maps: true,
|
||||
require_affirmed_anchor_blocks: true,
|
||||
fault_injection_hide_blocks: false,
|
||||
enable_puppet_mode: false,
|
||||
puppet_bind: format!("0.0.0.0:{}", puppet_port),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1933,6 +1942,8 @@ pub struct NodeConfigFile {
|
||||
pub use_test_genesis_chainstate: Option<bool>,
|
||||
pub always_use_affirmation_maps: Option<bool>,
|
||||
pub require_affirmed_anchor_blocks: Option<bool>,
|
||||
pub enable_puppet_mode: Option<bool>,
|
||||
pub puppet_bind: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Debug)]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use super::RunLoopCallbacks;
|
||||
use crate::burnchains::Error as BurnchainControllerError;
|
||||
use crate::run_loop::puppet::PuppetController;
|
||||
use crate::{
|
||||
BitcoinRegtestController, BurnchainController, ChainTip, Config, MocknetController, Node,
|
||||
};
|
||||
@@ -149,12 +150,25 @@ impl RunLoop {
|
||||
leader_tenure = self.node.initiate_new_tenure();
|
||||
}
|
||||
|
||||
// Puppet mode
|
||||
let puppet_controller = if self.config.node.enable_puppet_mode {
|
||||
let mut c = PuppetController::new(&self.config.node.puppet_bind);
|
||||
c.start();
|
||||
Some(c)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Start the runloop
|
||||
round_index = 1;
|
||||
loop {
|
||||
if expected_num_rounds == round_index {
|
||||
return Ok(());
|
||||
}
|
||||
match &puppet_controller {
|
||||
Some(c) => c.block_on(&chain_tip),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
// Run the last initialized tenure
|
||||
let artifacts_from_tenure = match leader_tenure {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
pub mod helium;
|
||||
pub mod neon;
|
||||
pub mod puppet;
|
||||
|
||||
use crate::{BurnchainController, BurnchainTip, ChainTip, Tenure};
|
||||
|
||||
|
||||
199
testnet/stacks-node/src/run_loop/puppet.rs
Normal file
199
testnet/stacks-node/src/run_loop/puppet.rs
Normal file
@@ -0,0 +1,199 @@
|
||||
use crate::ChainTip;
|
||||
use async_std::net::TcpListener;
|
||||
use async_std::stream::StreamExt;
|
||||
use async_std::task::block_on;
|
||||
use http_types::mime::JSON;
|
||||
use http_types::{Method, Response, StatusCode};
|
||||
use std::io;
|
||||
use std::io::{Error, Write};
|
||||
use std::ops::{Add, DerefMut};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread::{sleep, Builder, JoinHandle};
|
||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
|
||||
struct PuppetControl {
|
||||
pub current_block: u64,
|
||||
pub target_block: u64,
|
||||
pub next_block_time: SystemTime,
|
||||
pub block_interval: Duration,
|
||||
}
|
||||
|
||||
impl Default for PuppetControl {
|
||||
fn default() -> Self {
|
||||
let default_interval = Duration::from_secs(600);
|
||||
Self {
|
||||
current_block: 0,
|
||||
target_block: 1,
|
||||
next_block_time: SystemTime::now().add(default_interval),
|
||||
block_interval: default_interval,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PuppetController {
|
||||
inner: Arc<Mutex<PuppetControl>>,
|
||||
bind_addr: String,
|
||||
join_handle: Option<JoinHandle<Result<(), Error>>>,
|
||||
}
|
||||
|
||||
impl PuppetController {
|
||||
pub fn new(bind_addr: &str) -> Self {
|
||||
Self {
|
||||
inner: Arc::new(Mutex::new(PuppetControl::default())),
|
||||
bind_addr: bind_addr.to_string(),
|
||||
join_handle: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(&mut self) {
|
||||
if self.join_handle.is_some() {
|
||||
warn!("Puppet mode control server is already started");
|
||||
return;
|
||||
}
|
||||
info!("Starting puppet mode control server..");
|
||||
let puppet_control = Arc::clone(&self.inner);
|
||||
let puppet_bind = self.bind_addr.to_string();
|
||||
self.join_handle = Some(
|
||||
Builder::new()
|
||||
.name("puppet".into())
|
||||
.spawn(move || {
|
||||
block_on(async {
|
||||
let listener = TcpListener::bind(puppet_bind).await?;
|
||||
info!(
|
||||
"Start puppet mode control server on: {}",
|
||||
listener.local_addr()?
|
||||
);
|
||||
|
||||
// For each incoming TCP connection, spawn a task and call `accept`.
|
||||
let mut incoming = listener.incoming();
|
||||
while let Some(stream) = incoming.next().await {
|
||||
if stream.is_err() {
|
||||
return Err(stream.unwrap_err());
|
||||
}
|
||||
let stream = stream?;
|
||||
async_h1::accept(stream.clone(), |req| async {
|
||||
let mut req = req;
|
||||
match (req.method(), req.url().path()) {
|
||||
(Method::Get, "/") => Ok(Response::new(StatusCode::Ok)),
|
||||
(Method::Post, "/puppet/v1/kick") => {
|
||||
let mut puppet_control = puppet_control.lock().unwrap();
|
||||
if puppet_control.target_block <= puppet_control.current_block {
|
||||
puppet_control.target_block =
|
||||
puppet_control.current_block + 1;
|
||||
}
|
||||
Ok(Response::new(StatusCode::Ok))
|
||||
}
|
||||
(Method::Put, "/puppet/v1/duration") => {
|
||||
let body = req.body_string().await;
|
||||
match body {
|
||||
Ok(x) => {
|
||||
let v = x.parse::<u64>().unwrap_or(0);
|
||||
if v > 0 {
|
||||
println!("Setting duration to {}", v);
|
||||
io::stdout().flush().unwrap();
|
||||
let mut puppet_control =
|
||||
puppet_control.lock().unwrap();
|
||||
puppet_control.block_interval =
|
||||
Duration::from_secs(v);
|
||||
puppet_control.next_block_time =
|
||||
SystemTime::now()
|
||||
.add(puppet_control.block_interval);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
Ok(Response::new(StatusCode::Ok))
|
||||
}
|
||||
(Method::Put, "/puppet/v1/until") => {
|
||||
let body = req.body_string().await;
|
||||
match body {
|
||||
Ok(x) => {
|
||||
let v = x.parse::<u64>().unwrap_or(0);
|
||||
if v > 0 {
|
||||
let mut puppet_control =
|
||||
puppet_control.lock().unwrap();
|
||||
puppet_control.target_block = if puppet_control.current_block >= v {
|
||||
puppet_control.current_block
|
||||
} else {
|
||||
v
|
||||
};
|
||||
println!("Setting target block to {}", puppet_control.target_block);
|
||||
io::stdout().flush().unwrap();
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
Ok(Response::new(StatusCode::Ok))
|
||||
}
|
||||
(Method::Get, "/puppet/v1/status") => {
|
||||
let mut response = Response::new(StatusCode::Ok);
|
||||
let puppet_control = puppet_control.lock().unwrap();
|
||||
response.set_content_type(JSON);
|
||||
response.set_body(
|
||||
format!(
|
||||
"{{\"current_block\":{},\"target_block\":{},\"duration\":{},\"next_block_time\":{}}}",
|
||||
puppet_control.current_block,
|
||||
puppet_control.target_block,
|
||||
puppet_control.block_interval.as_secs(),
|
||||
puppet_control.next_block_time.duration_since(UNIX_EPOCH).unwrap().as_secs()));
|
||||
Ok(response)
|
||||
}
|
||||
_ => {
|
||||
let mut rs = Response::new(StatusCode::BadRequest);
|
||||
rs.set_body(format!(
|
||||
"[{}] {}",
|
||||
req.method(),
|
||||
req.url().path()
|
||||
));
|
||||
Ok(rs)
|
||||
}
|
||||
}
|
||||
})
|
||||
.await
|
||||
.unwrap_or(())
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
})
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
fn with_lock<F, R>(&self, func: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut PuppetControl) -> R,
|
||||
{
|
||||
let mut puppet_control = self.inner.lock().unwrap();
|
||||
func(puppet_control.deref_mut())
|
||||
}
|
||||
|
||||
pub fn block_on(&self, chain_tip: &ChainTip) {
|
||||
if self.join_handle.is_none() {
|
||||
return;
|
||||
}
|
||||
info!(
|
||||
"Waiting on block height {}",
|
||||
chain_tip.metadata.stacks_block_height
|
||||
);
|
||||
|
||||
self.with_lock(|puppet_control| {
|
||||
puppet_control.current_block = chain_tip.metadata.stacks_block_height;
|
||||
});
|
||||
loop {
|
||||
let should_break = self.with_lock(|puppet_control| {
|
||||
if puppet_control.target_block > puppet_control.current_block
|
||||
|| puppet_control.next_block_time.le(&SystemTime::now())
|
||||
{
|
||||
puppet_control.next_block_time =
|
||||
SystemTime::now().add(puppet_control.block_interval);
|
||||
return true;
|
||||
}
|
||||
false
|
||||
});
|
||||
if should_break {
|
||||
break;
|
||||
}
|
||||
sleep(Duration::from_millis(100));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user