Skip to content

Commit 5cfb854

Browse files
committed
Added elements support to /block-template.
1 parent ef15bf9 commit 5cfb854

9 files changed

Lines changed: 1260 additions & 105 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
target
2+
.DS_Store
23
*db/
34
*.log
45
*.sublime*

README.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,18 @@ See `$ cargo run --release --bin electrs -- --help` for the full list of options
8484
### Mining-related HTTP endpoints
8585

8686
`GET /block-template` is available only with `--enable-mining-rest`. It proxies
87-
the daemon's `getblocktemplate` template-mode response and caches successful
88-
responses for 15 seconds, invalidating early when electrs indexes a new tip.
89-
Callers that require fresher templates should account for this cache behavior.
87+
the daemon's `getblocktemplate` response unchanged on Bitcoin-compatible chains.
88+
On Liquid, it instead decodes the complete proposal returned by
89+
`getnewblockhex` and projects the recoverable header, transaction, fee,
90+
coinbase, and witness-commitment data into the same response shape. Fields that
91+
have no equivalent mining semantics for signed dynafed blocks use compatibility
92+
defaults or are omitted. The Liquid response is intended for template inspection
93+
and distribution, not block reconstruction or federation signing.
94+
95+
Successful responses are cached for 15 seconds and invalidated early when
96+
electrs indexes a new tip. Cache misses are coalesced into one daemon request.
97+
Responses use `Cache-Control: no-store`, so downstream caches do not extend the
98+
internal lifetime.
9099

91100
## License
92101

src/daemon.rs

Lines changed: 78 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,40 @@ pub trait CookieGetter: Send + Sync {
222222
fn get(&self) -> Result<Vec<u8>>;
223223
}
224224

225+
#[derive(Clone)]
226+
struct ConnectionConfig {
227+
addr: SocketAddr,
228+
fallback: Option<SocketAddr>,
229+
cookie_getter: Arc<dyn CookieGetter>,
230+
signal: Waiter,
231+
max_age: Option<Duration>,
232+
}
233+
234+
impl ConnectionConfig {
235+
fn connect(&self) -> Result<Connection> {
236+
Connection::new(
237+
self.addr,
238+
self.fallback,
239+
Arc::clone(&self.cookie_getter),
240+
self.signal.clone(),
241+
self.max_age,
242+
)
243+
}
244+
245+
fn connect_once(&self) -> Result<Connection> {
246+
let (conn, active_addr) = tcp_connect_once(self.addr, self.fallback)?;
247+
Connection::from_stream(
248+
conn,
249+
active_addr,
250+
self.addr,
251+
self.fallback,
252+
Arc::clone(&self.cookie_getter),
253+
self.signal.clone(),
254+
None, // a one-shot connection never needs proactive recycling
255+
)
256+
}
257+
}
258+
225259
struct Connection {
226260
tx: TcpStream,
227261
rx: Lines<BufReader<TcpStream>>,
@@ -516,6 +550,7 @@ pub struct Daemon {
516550
daemon_dir: PathBuf,
517551
blocks_dir: PathBuf,
518552
network: Network,
553+
connection_config: ConnectionConfig,
519554
conn: Mutex<Connection>,
520555
message_id: Counter, // for monotonic JSONRPC 'id'
521556
signal: Waiter,
@@ -542,17 +577,20 @@ impl Daemon {
542577
metrics: &Metrics,
543578
conn_max_age: Option<Duration>,
544579
) -> Result<Daemon> {
580+
let connection_config = ConnectionConfig {
581+
addr: daemon_rpc_addr,
582+
fallback: daemon_rpc_fallback_addr,
583+
cookie_getter,
584+
signal: signal.clone(),
585+
max_age: conn_max_age,
586+
};
587+
let conn = connection_config.connect()?;
545588
let daemon = Daemon {
546589
daemon_dir: daemon_dir.clone(),
547590
blocks_dir: blocks_dir.clone(),
548591
network,
549-
conn: Mutex::new(Connection::new(
550-
daemon_rpc_addr,
551-
daemon_rpc_fallback_addr,
552-
cookie_getter,
553-
signal.clone(),
554-
conn_max_age,
555-
)?),
592+
connection_config,
593+
conn: Mutex::new(conn),
556594
message_id: Counter::new(),
557595
signal: signal.clone(),
558596
conn_max_age,
@@ -616,6 +654,7 @@ impl Daemon {
616654
daemon_dir: self.daemon_dir.clone(),
617655
blocks_dir: self.blocks_dir.clone(),
618656
network: self.network,
657+
connection_config: self.connection_config.clone(),
619658
conn: Mutex::new(self.conn.lock().unwrap().reconnect()?),
620659
message_id: Counter::new(),
621660
signal: self.signal.clone(),
@@ -664,8 +703,12 @@ impl Daemon {
664703
}
665704

666705
#[trace]
667-
fn call_jsonrpc(&self, method: &str, request: &Value) -> Result<Value> {
668-
let mut conn = self.conn.lock().unwrap();
706+
fn call_jsonrpc_on_connection(
707+
&self,
708+
method: &str,
709+
request: &Value,
710+
conn: &mut Connection,
711+
) -> Result<Value> {
669712
// Proactively recycle connections older than the configured max age. Re-establishing
670713
// the TCP connection lets a fronting load balancer (e.g. a Kubernetes ClusterSetIP)
671714
// re-select a backend, so a long-lived connection does not stay pinned to a stale
@@ -711,6 +754,12 @@ impl Daemon {
711754
Ok(result)
712755
}
713756

757+
#[trace]
758+
fn call_jsonrpc(&self, method: &str, request: &Value) -> Result<Value> {
759+
let mut conn = self.conn.lock().unwrap();
760+
self.call_jsonrpc_on_connection(method, request, &mut conn)
761+
}
762+
714763
#[trace(method = %method)]
715764
fn handle_request(&self, method: &str, params: &Value) -> Result<Value> {
716765
let id = self.message_id.next();
@@ -746,9 +795,15 @@ impl Daemon {
746795
self.retry_request(method, &params)
747796
}
748797

798+
/// Perform one RPC on a fresh connection isolated from singleton RPC users.
799+
/// Connection and warmup failures are returned to the caller without retrying.
749800
#[trace]
750-
fn request_no_retry(&self, method: &str, params: Value) -> Result<Value> {
751-
self.handle_request(method, &params)
801+
fn request_once(&self, method: &str, params: Value) -> Result<Value> {
802+
let id = self.message_id.next();
803+
let req = json!({"method": method, "params": params, "id": id});
804+
let mut conn = self.connection_config.connect_once()?;
805+
let reply = self.call_jsonrpc_on_connection(method, &req, &mut conn)?;
806+
parse_jsonrpc_reply(reply, method, id)
752807
}
753808

754809
#[trace]
@@ -938,9 +993,20 @@ impl Daemon {
938993
Ok(serde_json::from_value(res).chain_err(|| "invalid getrawmempool reply")?)
939994
}
940995

996+
#[cfg(not(feature = "liquid"))]
941997
#[trace]
942998
pub fn getblocktemplate(&self, rules: &[&str]) -> Result<Value> {
943-
self.request_no_retry("getblocktemplate", json!([{ "rules": rules }]))
999+
self.request_once("getblocktemplate", json!([{ "rules": rules }]))
1000+
}
1001+
1002+
#[cfg(feature = "liquid")]
1003+
#[trace]
1004+
pub fn getnewblockhex(&self) -> Result<String> {
1005+
let value = self.request_once("getnewblockhex", json!([]))?;
1006+
value
1007+
.as_str()
1008+
.map(str::to_owned)
1009+
.chain_err(|| "non-string getnewblockhex response")
9441010
}
9451011

9461012
#[trace]

0 commit comments

Comments
 (0)