chronicle/admin/
mod.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
use anyhow::Result;
use axum::extract::State;
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use futures::channel::mpsc;
use futures::{FutureExt, StreamExt};
use serde::Serialize;
use std::future::IntoFuture;
use std::sync::Arc;
use time_primitives::admin::Config;
use time_primitives::ShardId;
use tokio::net::TcpListener;
use tokio::sync::Mutex;

#[derive(Clone)]
pub enum AdminMsg {
	SetConfig(Config),
	SetShards(Vec<ShardId>),
	NewBlock(u64),
	NewTargetBlock(u64),
	NewGasPrice(u128),
}

#[derive(Default)]
struct InnerState {
	shards: Vec<ShardId>,
	blocks: Blocks,
	gas_price: u128,
}

#[derive(Default, Serialize)]
struct Blocks {
	timechain_block: u64,
	target_block: u64,
}

#[derive(Clone, Default)]
struct AppState {
	config: Arc<Mutex<Option<Config>>>,
	inner: Arc<Mutex<InnerState>>,
}

impl AppState {
	async fn apply(&self, msg: AdminMsg) {
		match msg {
			AdminMsg::SetConfig(config) => {
				let mut gconfig = self.config.lock().await;
				*gconfig = Some(config);
			},
			AdminMsg::SetShards(shards) => {
				let mut inner = self.inner.lock().await;
				inner.shards = shards;
			},
			AdminMsg::NewBlock(timechain_block) => {
				let mut inner = self.inner.lock().await;
				inner.blocks.timechain_block = timechain_block;
			},
			AdminMsg::NewTargetBlock(target_block) => {
				let mut inner = self.inner.lock().await;
				inner.blocks.target_block = target_block;
			},
			AdminMsg::NewGasPrice(gas_price) => {
				let mut inner = self.inner.lock().await;
				inner.gas_price = gas_price;
			},
		}
	}
}

pub async fn listen(port: u16, mut admin: mpsc::Receiver<AdminMsg>) -> Result<()> {
	let state = AppState::default();

	let addr = std::net::SocketAddr::from(([0, 0, 0, 0], port));
	tracing::info!("Loading admin interface: {}", addr);

	let app = axum::routing::Router::new()
		.route("/config", axum::routing::get(config))
		.route("/shards", axum::routing::get(shards))
		.route("/blocks", axum::routing::get(blocks))
		.route("/gas_price", axum::routing::get(gas_price))
		.with_state(state.clone());

	let mut listen = axum::serve(TcpListener::bind(&addr).await?, app).into_future();
	loop {
		futures::select! {
			r = (&mut listen).fuse() => r?,
			msg = admin.next() => {
				if let Some(msg) = msg {
					state.apply(msg).await;
				}
			}
		}
	}
}

// `/config`
async fn config(State(state): State<AppState>) -> Response {
	let config = state.config.lock().await;
	if let Some(config) = &*config {
		axum::Json(&config).into_response()
	} else {
		StatusCode::SERVICE_UNAVAILABLE.into_response()
	}
}

// `/shards`
async fn shards(State(state): State<AppState>) -> Response {
	let inner = state.inner.lock().await;
	axum::Json(&inner.shards).into_response()
}

// GET `/blocks`
async fn blocks(State(state): State<AppState>) -> Response {
	let inner = state.inner.lock().await;
	axum::Json(&inner.blocks).into_response()
}

// GET `/gas_price`
async fn gas_price(State(state): State<AppState>) -> Response {
	let inner = state.inner.lock().await;
	axum::Json(&inner.gas_price).into_response()
}