Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: push blocks to signer set and add /v3/blocks/upload/ #4902

Merged
merged 7 commits into from
Jul 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion libsigner/src/libsigner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,5 +74,5 @@ pub trait MessageSlotID: Sized + Eq + Hash + Debug + Copy {
/// A trait for signer messages used in signer communciation
pub trait SignerMessage<T: MessageSlotID>: StacksMessageCodec {
/// The contract identifier for the message slot in stacker db
fn msg_id(&self) -> T;
fn msg_id(&self) -> Option<T>;
}
65 changes: 36 additions & 29 deletions libsigner/src/v0/messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,19 @@ define_u8_enum!(
/// Enum representing the stackerdb message identifier: this is
/// the contract index in the signers contracts (i.e., X in signers-0-X)
MessageSlotID {
/// Block Proposal message from miners
BlockProposal = 0,
/// Block Response message from signers
BlockResponse = 1
});

define_u8_enum!(
/// Enum representing the slots used by the miner
MinerSlotID {
/// Block proposal from the miner
BlockProposal = 0,
/// Block pushed from the miner
BlockPushed = 1
});

impl MessageSlotIDTrait for MessageSlotID {
fn stacker_db_contract(&self, mainnet: bool, reward_cycle: u64) -> QualifiedContractIdentifier {
NakamotoSigners::make_signers_db_contract_id(reward_cycle, self.to_u32(), mainnet)
Expand All @@ -80,7 +87,7 @@ impl MessageSlotIDTrait for MessageSlotID {
}

impl SignerMessageTrait<MessageSlotID> for SignerMessage {
fn msg_id(&self) -> MessageSlotID {
fn msg_id(&self) -> Option<MessageSlotID> {
self.msg_id()
}
}
Expand All @@ -91,7 +98,9 @@ SignerMessageTypePrefix {
/// Block Proposal message from miners
BlockProposal = 0,
/// Block Response message from signers
BlockResponse = 1
BlockResponse = 1,
/// Block Pushed message from miners
BlockPushed = 2
});

#[cfg_attr(test, mutants::skip)]
Expand Down Expand Up @@ -133,67 +142,65 @@ impl From<&SignerMessage> for SignerMessageTypePrefix {
match message {
SignerMessage::BlockProposal(_) => SignerMessageTypePrefix::BlockProposal,
SignerMessage::BlockResponse(_) => SignerMessageTypePrefix::BlockResponse,
SignerMessage::BlockPushed(_) => SignerMessageTypePrefix::BlockPushed,
}
}
}

/// The messages being sent through the stacker db contracts
#[derive(Clone, PartialEq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum SignerMessage {
/// The block proposal from miners for signers to observe and sign
BlockProposal(BlockProposal),
/// The block response from signers for miners to observe
BlockResponse(BlockResponse),
}

impl Debug for SignerMessage {
#[cfg_attr(test, mutants::skip)]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::BlockProposal(b) => Debug::fmt(b, f),
Self::BlockResponse(b) => Debug::fmt(b, f),
}
}
/// A block pushed from miners to the signers set
BlockPushed(NakamotoBlock),
}

impl SignerMessage {
/// Helper function to determine the slot ID for the provided stacker-db writer id
/// Not every message has a `MessageSlotID`: messages from the miner do not
/// broadcast over `.signers-0-X` contracts.
#[cfg_attr(test, mutants::skip)]
pub fn msg_id(&self) -> MessageSlotID {
pub fn msg_id(&self) -> Option<MessageSlotID> {
match self {
Self::BlockProposal(_) => MessageSlotID::BlockProposal,
Self::BlockResponse(_) => MessageSlotID::BlockResponse,
Self::BlockProposal(_) | Self::BlockPushed(_) => None,
Self::BlockResponse(_) => Some(MessageSlotID::BlockResponse),
}
}
}

impl StacksMessageCodec for SignerMessage {
fn consensus_serialize<W: Write>(&self, fd: &mut W) -> Result<(), CodecError> {
write_next(fd, &(SignerMessageTypePrefix::from(self) as u8))?;
SignerMessageTypePrefix::from(self)
.to_u8()
.consensus_serialize(fd)?;
match self {
SignerMessage::BlockProposal(block_proposal) => {
write_next(fd, block_proposal)?;
}
SignerMessage::BlockResponse(block_response) => {
write_next(fd, block_response)?;
}
};
SignerMessage::BlockProposal(block_proposal) => block_proposal.consensus_serialize(fd),
SignerMessage::BlockResponse(block_response) => block_response.consensus_serialize(fd),
SignerMessage::BlockPushed(block) => block.consensus_serialize(fd),
}?;
Ok(())
}

#[cfg_attr(test, mutants::skip)]
fn consensus_deserialize<R: Read>(fd: &mut R) -> Result<Self, CodecError> {
let type_prefix_byte = read_next::<u8, _>(fd)?;
let type_prefix_byte = u8::consensus_deserialize(fd)?;
let type_prefix = SignerMessageTypePrefix::try_from(type_prefix_byte)?;
let message = match type_prefix {
SignerMessageTypePrefix::BlockProposal => {
let block_proposal = read_next::<BlockProposal, _>(fd)?;
let block_proposal = StacksMessageCodec::consensus_deserialize(fd)?;
SignerMessage::BlockProposal(block_proposal)
}
SignerMessageTypePrefix::BlockResponse => {
let block_response = read_next::<BlockResponse, _>(fd)?;
let block_response = StacksMessageCodec::consensus_deserialize(fd)?;
SignerMessage::BlockResponse(block_response)
}
SignerMessageTypePrefix::BlockPushed => {
let block = StacksMessageCodec::consensus_deserialize(fd)?;
SignerMessage::BlockPushed(block)
}
};
Ok(message)
}
Expand Down
4 changes: 2 additions & 2 deletions libsigner/src/v1/messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,8 @@ impl MessageSlotIDTrait for MessageSlotID {
}

impl SignerMessageTrait<MessageSlotID> for SignerMessage {
fn msg_id(&self) -> MessageSlotID {
self.msg_id()
fn msg_id(&self) -> Option<MessageSlotID> {
Some(self.msg_id())
}
}

Expand Down
24 changes: 15 additions & 9 deletions stacks-signer/src/client/stackerdb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,11 @@ impl<M: MessageSlotID + 'static> StackerDB<M> {
&mut self,
message: T,
) -> Result<StackerDBChunkAckData, ClientError> {
let msg_id = message.msg_id();
let msg_id = message.msg_id().ok_or_else(|| {
ClientError::PutChunkRejected(
"Tried to send a SignerMessage which does not have a corresponding .signers slot identifier".into()
)
})?;
let message_bytes = message.serialize_to_vec();
self.send_message_bytes_with_retry(&msg_id, message_bytes)
}
Expand Down Expand Up @@ -230,7 +234,7 @@ mod tests {

use blockstack_lib::chainstate::nakamoto::{NakamotoBlock, NakamotoBlockHeader};
use clarity::util::hash::{MerkleTree, Sha512Trunc256Sum};
use libsigner::v0::messages::SignerMessage;
use libsigner::v0::messages::{BlockRejection, BlockResponse, RejectCode, SignerMessage};
use libsigner::BlockProposal;
use rand::{thread_rng, RngCore};

Expand Down Expand Up @@ -272,25 +276,27 @@ mod tests {
};
block.header.tx_merkle_root = tx_merkle_root;

let block_proposal = BlockProposal {
block,
burn_height: thread_rng().next_u64(),
reward_cycle: thread_rng().next_u64(),
let block_reject = BlockRejection {
reason: "Did not like it".into(),
reason_code: RejectCode::RejectedInPriorRound,
signer_signature_hash: block.header.signer_signature_hash(),
};
let signer_message = SignerMessage::BlockProposal(block_proposal);
let signer_message = SignerMessage::BlockResponse(BlockResponse::Rejected(block_reject));
let ack = StackerDBChunkAckData {
accepted: true,
reason: None,
metadata: None,
code: None,
};
let mock_server = mock_server_from_config(&config);
let h = spawn(move || stackerdb.send_message_with_retry(signer_message));
debug!("Spawning msg sender");
let sender_thread =
spawn(move || stackerdb.send_message_with_retry(signer_message).unwrap());
let mut response_bytes = b"HTTP/1.1 200 OK\n\n".to_vec();
let payload = serde_json::to_string(&ack).expect("Failed to serialize ack");
response_bytes.extend(payload.as_bytes());
std::thread::sleep(Duration::from_millis(500));
write_response(mock_server, response_bytes.as_slice());
assert_eq!(ack, h.join().unwrap().unwrap());
assert_eq!(ack, sender_thread.join().unwrap());
}
}
19 changes: 19 additions & 0 deletions stacks-signer/src/client/stacks_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ use blockstack_lib::net::api::getinfo::RPCPeerInfoData;
use blockstack_lib::net::api::getpoxinfo::RPCPoxInfoData;
use blockstack_lib::net::api::getsortition::{SortitionInfo, RPC_SORTITION_INFO_PATH};
use blockstack_lib::net::api::getstackers::GetStackersResponse;
use blockstack_lib::net::api::postblock::StacksBlockAcceptedData;
use blockstack_lib::net::api::postblock_proposal::NakamotoBlockProposal;
use blockstack_lib::net::api::postblock_v3;
use blockstack_lib::net::api::postfeerate::{FeeRateEstimateRequestBody, RPCFeeEstimateResponse};
use blockstack_lib::util_lib::boot::{boot_code_addr, boot_code_id};
use clarity::util::hash::to_hex;
Expand Down Expand Up @@ -655,6 +657,23 @@ impl StacksClient {
Ok(unsigned_tx)
}

/// Try to post a completed nakamoto block to our connected stacks-node
/// Returns `true` if the block was accepted or `false` if the block
/// was rejected.
pub fn post_block(&self, block: &NakamotoBlock) -> Result<bool, ClientError> {
let response = self
.stacks_node_client
.post(format!("{}{}", self.http_origin, postblock_v3::PATH))
.header("Content-Type", "application/octet-stream")
.body(block.serialize_to_vec())
.send()?;
if !response.status().is_success() {
return Err(ClientError::RequestFailure(response.status()));
}
let post_block_resp = response.json::<StacksBlockAcceptedData>()?;
Ok(post_block_resp.accepted)
}

/// Helper function to submit a transaction to the Stacks mempool
pub fn submit_transaction(&self, tx: &StacksTransaction) -> Result<Txid, ClientError> {
let txid = tx.txid();
Expand Down
20 changes: 16 additions & 4 deletions stacks-signer/src/v0/signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ use clarity::types::PrivateKey;
use clarity::util::hash::MerkleHashFunc;
use libsigner::v0::messages::{BlockResponse, MessageSlotID, RejectCode, SignerMessage};
use libsigner::{BlockProposal, SignerEvent};
use slog::{slog_debug, slog_error, slog_warn};
use slog::{slog_debug, slog_error, slog_info, slog_warn};
use stacks_common::types::chainstate::StacksAddress;
use stacks_common::{debug, error, warn};
use stacks_common::{debug, error, info, warn};

use crate::client::{SignerSlotID, StackerDB, StacksClient};
use crate::config::SignerConfig;
Expand Down Expand Up @@ -118,8 +118,20 @@ impl SignerTrait<SignerMessage> for Signer {
messages.len();
);
for message in messages {
if let SignerMessage::BlockProposal(block_proposal) = message {
self.handle_block_proposal(stacks_client, block_proposal);
match message {
SignerMessage::BlockProposal(block_proposal) => {
self.handle_block_proposal(stacks_client, block_proposal);
}
SignerMessage::BlockPushed(b) => {
let block_push_result = stacks_client.post_block(&b);
info!(
"{self}: Got block pushed message";
"block_id" => %b.block_id(),
"signer_sighash" => %b.header.signer_signature_hash(),
"push_result" => ?block_push_result,
);
}
_ => {}
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions stackslib/src/net/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ pub mod gettransaction_unconfirmed;
pub mod liststackerdbreplicas;
pub mod postblock;
pub mod postblock_proposal;
#[warn(unused_imports)]
pub mod postblock_v3;
pub mod postfeerate;
pub mod postmempoolquery;
pub mod postmicroblock;
Expand Down Expand Up @@ -129,6 +131,7 @@ impl StacksHttp {
self.register_rpc_endpoint(postblock_proposal::RPCBlockProposalRequestHandler::new(
self.block_proposal_token.clone(),
));
self.register_rpc_endpoint(postblock_v3::RPCPostBlockRequestHandler::default());
self.register_rpc_endpoint(postfeerate::RPCPostFeeRateRequestHandler::new());
self.register_rpc_endpoint(postmempoolquery::RPCMempoolQueryRequestHandler::new());
self.register_rpc_endpoint(postmicroblock::RPCPostMicroblockRequestHandler::new());
Expand Down
Loading
Loading