mirror of
https://github.com/alexgo-io/stacks-puppet-node.git
synced 2026-01-12 16:53:21 +08:00
Subscribe signer to new Burn block events
Signed-off-by: Jacinta Ferrant <jacinta@trustmachines.co>
This commit is contained in:
@@ -73,6 +73,8 @@ pub enum SignerEvent {
|
||||
BlockValidationResponse(BlockValidateResponse),
|
||||
/// Status endpoint request
|
||||
StatusCheck,
|
||||
/// A new burn block event was received
|
||||
NewBurnBlock,
|
||||
}
|
||||
|
||||
impl StacksMessageCodec for BlockProposalSigners {
|
||||
@@ -281,6 +283,8 @@ impl EventReceiver for SignerEventReceiver {
|
||||
process_stackerdb_event(event_receiver.local_addr, request, is_mainnet)
|
||||
} else if request.url() == "/proposal_response" {
|
||||
process_proposal_response(request)
|
||||
} else if request.url() == "/new_burn_block" {
|
||||
process_new_burn_block_event(request)
|
||||
} else {
|
||||
let url = request.url().to_string();
|
||||
|
||||
@@ -438,6 +442,16 @@ fn process_proposal_response(mut request: HttpRequest) -> Result<SignerEvent, Ev
|
||||
Ok(SignerEvent::BlockValidationResponse(event))
|
||||
}
|
||||
|
||||
/// Process a new burn block event from the node
|
||||
fn process_new_burn_block_event(mut request: HttpRequest) -> Result<SignerEvent, EventError> {
|
||||
debug!("Got burn_block event");
|
||||
let event = SignerEvent::NewBurnBlock;
|
||||
if let Err(e) = request.respond(HttpResponse::empty(200u16)) {
|
||||
error!("Failed to respond to request: {:?}", &e);
|
||||
}
|
||||
Ok(event)
|
||||
}
|
||||
|
||||
fn get_signers_db_signer_set_message_id(name: &str) -> Option<(u32, u32)> {
|
||||
// Splitting the string by '-'
|
||||
let parts: Vec<&str> = name.split('-').collect();
|
||||
|
||||
@@ -23,7 +23,6 @@ use std::time::Duration;
|
||||
|
||||
use clarity::vm::errors::Error as ClarityError;
|
||||
use clarity::vm::types::serialization::SerializationError;
|
||||
use libsigner::RPCError;
|
||||
use libstackerdb::Error as StackerDBError;
|
||||
use slog::slog_debug;
|
||||
pub use stackerdb::*;
|
||||
@@ -48,9 +47,6 @@ pub enum ClientError {
|
||||
/// Failed to sign stacker-db chunk
|
||||
#[error("Failed to sign stacker-db chunk: {0}")]
|
||||
FailToSign(#[from] StackerDBError),
|
||||
/// Failed to write to stacker-db due to RPC error
|
||||
#[error("Failed to write to stacker-db instance: {0}")]
|
||||
PutChunkFailed(#[from] RPCError),
|
||||
/// Stacker-db instance rejected the chunk
|
||||
#[error("Stacker-db rejected the chunk. Reason: {0}")]
|
||||
PutChunkRejected(String),
|
||||
@@ -72,33 +68,18 @@ pub enum ClientError {
|
||||
/// Failed to parse a Clarity value
|
||||
#[error("Received a malformed clarity value: {0}")]
|
||||
MalformedClarityValue(String),
|
||||
/// Invalid Clarity Name
|
||||
#[error("Invalid Clarity Name: {0}")]
|
||||
InvalidClarityName(String),
|
||||
/// Backoff retry timeout
|
||||
#[error("Backoff retry timeout occurred. Stacks node may be down.")]
|
||||
RetryTimeout,
|
||||
/// Not connected
|
||||
#[error("Not connected")]
|
||||
NotConnected,
|
||||
/// Invalid signing key
|
||||
#[error("Signing key not represented in the list of signers")]
|
||||
InvalidSigningKey,
|
||||
/// Clarity interpreter error
|
||||
#[error("Clarity interpreter error: {0}")]
|
||||
ClarityError(#[from] ClarityError),
|
||||
/// Our stacks address does not belong to a registered signer
|
||||
#[error("Our stacks address does not belong to a registered signer")]
|
||||
NotRegistered,
|
||||
/// Reward set not yet calculated for the given reward cycle
|
||||
#[error("Reward set not yet calculated for reward cycle: {0}")]
|
||||
RewardSetNotYetCalculated(u64),
|
||||
/// Malformed reward set
|
||||
#[error("Malformed contract data: {0}")]
|
||||
MalformedContractData(String),
|
||||
/// No reward set exists for the given reward cycle
|
||||
#[error("No reward set exists for reward cycle {0}")]
|
||||
NoRewardSet(u64),
|
||||
/// Stacks node does not support a feature we need
|
||||
#[error("Stacks node does not support a required feature: {0}")]
|
||||
UnsupportedStacksFeature(String),
|
||||
|
||||
@@ -44,12 +44,14 @@ pub struct RunLoopCommand {
|
||||
}
|
||||
|
||||
/// The runloop state
|
||||
#[derive(PartialEq, Debug)]
|
||||
#[derive(PartialEq, Debug, Clone, Copy)]
|
||||
pub enum State {
|
||||
/// The runloop is uninitialized
|
||||
Uninitialized,
|
||||
/// The runloop is initialized
|
||||
Initialized,
|
||||
/// The runloop has no registered signers
|
||||
NoRegisteredSigners,
|
||||
/// The runloop has registered signers
|
||||
RegisteredSigners,
|
||||
}
|
||||
|
||||
/// The runloop for the stacks signer
|
||||
@@ -262,9 +264,9 @@ impl RunLoop {
|
||||
signer.next_signer_slot_ids = new_signer_config.signer_slot_ids.clone();
|
||||
}
|
||||
}
|
||||
self.stacks_signers
|
||||
.insert(reward_index, Signer::from(new_signer_config));
|
||||
debug!("Reward cycle #{reward_cycle} Signer #{signer_id} initialized.");
|
||||
let new_signer = Signer::from(new_signer_config);
|
||||
info!("{new_signer} initialized.");
|
||||
self.stacks_signers.insert(reward_index, new_signer);
|
||||
} else {
|
||||
// TODO: Update `current` here once the signer binary is tracking its own latest burnchain/stacks views.
|
||||
if current {
|
||||
@@ -277,7 +279,6 @@ impl RunLoop {
|
||||
}
|
||||
|
||||
/// Refresh the signer configuration by retrieving the necessary information from the stacks node
|
||||
/// Note: this will trigger DKG if required
|
||||
fn refresh_signers(&mut self, current_reward_cycle: u64) -> Result<(), ClientError> {
|
||||
let next_reward_cycle = current_reward_cycle.saturating_add(1);
|
||||
self.refresh_signer_config(current_reward_cycle, true);
|
||||
@@ -307,28 +308,15 @@ impl RunLoop {
|
||||
signer.coordinator.state = CoordinatorState::Idle;
|
||||
signer.state = SignerState::Idle;
|
||||
}
|
||||
if signer.approved_aggregate_public_key.is_none() {
|
||||
retry_with_exponential_backoff(|| {
|
||||
signer
|
||||
.update_dkg(&self.stacks_client)
|
||||
.map_err(backoff::Error::transient)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
for i in to_delete.into_iter() {
|
||||
if let Some(signer) = self.stacks_signers.remove(&i) {
|
||||
info!("{signer}: Tenure has completed. Removing signer from runloop.",);
|
||||
}
|
||||
for idx in to_delete {
|
||||
self.stacks_signers.remove(&idx);
|
||||
}
|
||||
if self.stacks_signers.is_empty() {
|
||||
info!("Signer is not registered for the current reward cycle ({current_reward_cycle}) or next reward cycle ({next_reward_cycle}). Waiting for confirmed registration...");
|
||||
self.state = State::Uninitialized;
|
||||
return Err(ClientError::NotRegistered);
|
||||
}
|
||||
if self.state != State::Initialized {
|
||||
info!("Signer runloop successfully initialized!");
|
||||
}
|
||||
self.state = State::Initialized;
|
||||
self.state = if self.stacks_signers.is_empty() {
|
||||
State::NoRegisteredSigners
|
||||
} else {
|
||||
State::RegisteredSigners
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -362,19 +350,39 @@ impl SignerRunLoop<Vec<OperationResult>, RunLoopCommand> for RunLoop {
|
||||
.map_err(backoff::Error::transient)
|
||||
}) else {
|
||||
error!("Failed to retrieve current reward cycle");
|
||||
warn!("Ignoring event: {event:?}");
|
||||
return None;
|
||||
};
|
||||
if let Err(e) = self.refresh_signers(current_reward_cycle) {
|
||||
if self.state == State::Uninitialized || event == Some(SignerEvent::NewBurnBlock) {
|
||||
let old_state = self.state;
|
||||
if self.state == State::Uninitialized {
|
||||
// If we were never actually initialized, we cannot process anything. Just return.
|
||||
warn!("Failed to initialize signers. Are you sure this signer is correctly registered for the current or next reward cycle?");
|
||||
warn!("Ignoring event: {event:?}");
|
||||
info!("Initializing signer...");
|
||||
} else {
|
||||
info!("New burn block event received. Refreshing signer state...");
|
||||
}
|
||||
if let Err(e) = self.refresh_signers(current_reward_cycle) {
|
||||
error!("Failed to refresh signers: {e}. Signer may have an outdated view of the network");
|
||||
}
|
||||
if self.state == State::NoRegisteredSigners {
|
||||
let next_reward_cycle = current_reward_cycle.saturating_add(1);
|
||||
info!("Signer is not registered for the current reward cycle ({current_reward_cycle}) or next reward cycle ({next_reward_cycle}). Waiting for confirmed registration...");
|
||||
return None;
|
||||
}
|
||||
error!("Failed to refresh signers: {e}. Signer may have an outdated view of the network. Attempting to process event anyway.");
|
||||
if old_state == State::Uninitialized {
|
||||
info!("Signer successfully initialized.");
|
||||
} else {
|
||||
info!("Signer state successfully refreshed.");
|
||||
};
|
||||
}
|
||||
for signer in self.stacks_signers.values_mut() {
|
||||
if signer.approved_aggregate_public_key.is_none() {
|
||||
if let Err(e) = retry_with_exponential_backoff(|| {
|
||||
signer
|
||||
.update_dkg(&self.stacks_client)
|
||||
.map_err(backoff::Error::transient)
|
||||
}) {
|
||||
error!("{signer}: failed to update DKG: {e}");
|
||||
}
|
||||
}
|
||||
if signer.state == SignerState::TenureCompleted {
|
||||
warn!("{signer}: Signer's tenure has completed. This signer should have been cleaned up during refresh.");
|
||||
continue;
|
||||
@@ -383,12 +391,13 @@ impl SignerRunLoop<Vec<OperationResult>, RunLoopCommand> for RunLoop {
|
||||
Some(SignerEvent::BlockValidationResponse(_)) => Some(current_reward_cycle % 2),
|
||||
// Block proposal events do have reward cycles, but each proposal has its own cycle,
|
||||
// and the vec could be heterogenous, so, don't differentiate.
|
||||
Some(SignerEvent::ProposedBlocks(_)) => None,
|
||||
Some(SignerEvent::ProposedBlocks(_))
|
||||
| Some(SignerEvent::NewBurnBlock)
|
||||
| Some(SignerEvent::StatusCheck)
|
||||
| None => None,
|
||||
Some(SignerEvent::SignerMessages(msg_parity, ..)) => {
|
||||
Some(u64::from(msg_parity) % 2)
|
||||
}
|
||||
Some(SignerEvent::StatusCheck) => None,
|
||||
None => None,
|
||||
};
|
||||
let other_signer_parity = (signer.reward_cycle + 1) % 2;
|
||||
if event_parity == Some(other_signer_parity) {
|
||||
|
||||
@@ -1123,6 +1123,7 @@ impl Signer {
|
||||
/// Update the DKG for the provided signer info, triggering it if required
|
||||
pub fn update_dkg(&mut self, stacks_client: &StacksClient) -> Result<(), ClientError> {
|
||||
let reward_cycle = self.reward_cycle;
|
||||
let old_dkg = self.approved_aggregate_public_key;
|
||||
self.approved_aggregate_public_key =
|
||||
stacks_client.get_approved_aggregate_key(reward_cycle)?;
|
||||
if self.approved_aggregate_public_key.is_some() {
|
||||
@@ -1131,11 +1132,12 @@ impl Signer {
|
||||
// then overwrite our value accordingly. Otherwise, we will be locked out of the round and should not participate.
|
||||
self.coordinator
|
||||
.set_aggregate_public_key(self.approved_aggregate_public_key);
|
||||
// We have an approved aggregate public key. Do nothing further
|
||||
debug!(
|
||||
"{self}: Have updated DKG value to {:?}.",
|
||||
self.approved_aggregate_public_key
|
||||
);
|
||||
if old_dkg != self.approved_aggregate_public_key {
|
||||
debug!(
|
||||
"{self}: updated DKG value to {:?}.",
|
||||
self.approved_aggregate_public_key
|
||||
);
|
||||
}
|
||||
return Ok(());
|
||||
};
|
||||
let coordinator_id = self.coordinator_selector.get_coordinator().0;
|
||||
@@ -1225,6 +1227,9 @@ impl Signer {
|
||||
Some(SignerEvent::StatusCheck) => {
|
||||
debug!("{self}: Received a status check event.")
|
||||
}
|
||||
Some(SignerEvent::NewBurnBlock) => {
|
||||
// Already handled this case in the main loop
|
||||
}
|
||||
None => {
|
||||
// No event. Do nothing.
|
||||
debug!("{self}: No event received")
|
||||
|
||||
@@ -804,7 +804,11 @@ fn setup_stx_btc_node(
|
||||
|
||||
naka_conf.events_observers.insert(EventObserverConfig {
|
||||
endpoint: format!("{}", signer_config.endpoint),
|
||||
events_keys: vec![EventKeyType::StackerDBChunks, EventKeyType::BlockProposal],
|
||||
events_keys: vec![
|
||||
EventKeyType::StackerDBChunks,
|
||||
EventKeyType::BlockProposal,
|
||||
EventKeyType::BurnchainBlocks,
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user