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

WIP: Add block state and aggregate signature rejections #5140

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
af802a9
WIP: Add block state and aggregate signature rejections
jferrant Sep 4, 2024
33ec49a
Fix miners to not accept multiple messages from the same signer for t…
jferrant Sep 4, 2024
9d622f3
Merge branch 'fix/5136-node-and-miner' of https://github.com/stacks-n…
jferrant Sep 4, 2024
b675546
test: fix `follower_bootup` integration test
obycode Sep 4, 2024
0ec5688
WIP: broken check_proposal reorg timing test
jferrant Sep 4, 2024
f34c741
fix: fix block proposal integration test
jcnelson Sep 4, 2024
fa4cd9b
Merge branch 'fix/5136-node-and-miner' into fix/5136-signer-block-sta…
jcnelson Sep 4, 2024
3047b5f
chore: address PR feedback
jcnelson Sep 4, 2024
457afc0
Merge pull request #5143 from stacks-network/fix/follower_bootup
jbencin Sep 5, 2024
5487b65
chore: fix get_block_state() and add a unit test
jcnelson Sep 5, 2024
5988c65
Add a log to block miner thread stopping
jferrant Sep 5, 2024
9afe92e
chore: documentation
jcnelson Sep 5, 2024
9e5e389
chore: test_debug --> debug
jcnelson Sep 5, 2024
8f2b2e7
chore: raise initiative on miner failure, in case it's due to a new s…
jcnelson Sep 5, 2024
e1d7f6e
chore: log joined miner thread error
jcnelson Sep 5, 2024
bbdfc66
fix: abort signer waiting if the tenure changes, but only after a tim…
jcnelson Sep 5, 2024
bb4fafe
Merge branch 'fix/5136-signer-block-state-tracking' of https://github…
jcnelson Sep 5, 2024
ce80740
Merge branch 'develop' of https://github.com/stacks-network/stacks-bl…
jcnelson Sep 5, 2024
9f69e03
Merge branch 'develop' into fix/5136-signer-block-state-tracking
jcnelson Sep 5, 2024
7f0a1f3
fix: fix failing follower bootup test
jcnelson Sep 5, 2024
343dc31
Fix check_proposal_reorg_ok test
jferrant Sep 5, 2024
5833a8f
add allow_reorg_locally_accepted_block_if_globally_rejected_succeeds …
jferrant Sep 5, 2024
7d6902f
Fix test description
jferrant Sep 5, 2024
baee54e
chore: fix potential deadlock condition by avoiding a transaction whe…
jcnelson Sep 5, 2024
d9f4a4d
Merge branch 'fix/5136-signer-block-state-tracking' of https://github…
jcnelson Sep 5, 2024
db88eb8
chore: fmt
jcnelson Sep 5, 2024
7217594
Add locally_rejected_nlocks_overriden_by_global_acceptance test
jferrant Sep 5, 2024
cfd2c37
Do not store blocks that fail the initial checks
jferrant Sep 5, 2024
2be05f9
Fix broken build from prior db change commit
jferrant Sep 6, 2024
1a1b076
chore: remove .mined_blocks and replace it with .last_mined_block, an…
jcnelson Sep 6, 2024
ac4c047
Merge branch 'fix/5136-signer-block-state-tracking' of https://github…
jcnelson Sep 6, 2024
a19af95
Add reorg_locally_accepted_blocks_across_tenures_succeeds integration…
jferrant Sep 6, 2024
025cc0d
fix: ignore rejections for other blocks in sign coordinator
obycode Sep 6, 2024
ed3486c
Add miner_recovers_when_broadcast_block_delay_across_tenures_occurs
jferrant Sep 6, 2024
6673113
Merge pull request #5150 from stacks-network/fix/ignore-rejections-fo…
jferrant Sep 6, 2024
55d2f32
fix: make a stackerdb shrink if its signer list becomes smaller than …
jcnelson Sep 6, 2024
bb40d1a
fix: get miner_recovers_when_broadcast_block_delay_across_tenures_occ…
jcnelson Sep 6, 2024
17aa1c4
chore: a NetworkResult has data if it has an uploaded Nakamoto block
jcnelson Sep 8, 2024
835e39b
fix: compiler error (forgot self.)
jcnelson Sep 8, 2024
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
4 changes: 4 additions & 0 deletions .github/workflows/bitcoin-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ jobs:
- tests::signer::v0::signers_broadcast_signed_blocks
- tests::signer::v0::min_gap_between_blocks
- tests::signer::v0::duplicate_signers
- tests::signer::v0::locally_accepted_blocks_overriden_by_global_rejection
- tests::signer::v0::locally_rejected_blocks_overriden_by_global_acceptance
- tests::signer::v0::reorg_locally_accepted_blocks_across_tenures_succeeds
- tests::signer::v0::miner_recovers_when_broadcast_block_delay_across_tenures_occurs
- tests::nakamoto_integrations::stack_stx_burn_op_integration_test
- tests::nakamoto_integrations::check_block_heights
- tests::nakamoto_integrations::clarity_burn_state
Expand Down
146 changes: 119 additions & 27 deletions libsigner/src/v0/messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ use blockstack_lib::util_lib::boot::boot_code_id;
use blockstack_lib::util_lib::signed_structured_data::{
make_structured_data_domain, structured_data_message_hash,
};
use clarity::consts::{CHAIN_ID_MAINNET, CHAIN_ID_TESTNET};
use clarity::types::chainstate::{
BlockHeaderHash, ConsensusHash, StacksPrivateKey, StacksPublicKey,
};
Expand Down Expand Up @@ -525,7 +526,9 @@ RejectCodeTypePrefix {
/// The block was rejected due to no sortition view
NoSortitionView = 3,
/// The block was rejected due to a mismatch with expected sortition view
SortitionViewMismatch = 4
SortitionViewMismatch = 4,
/// The block was rejected due to a testing directive
TestingDirective = 5
});

impl TryFrom<u8> for RejectCodeTypePrefix {
Expand All @@ -545,6 +548,7 @@ impl From<&RejectCode> for RejectCodeTypePrefix {
RejectCode::RejectedInPriorRound => RejectCodeTypePrefix::RejectedInPriorRound,
RejectCode::NoSortitionView => RejectCodeTypePrefix::NoSortitionView,
RejectCode::SortitionViewMismatch => RejectCodeTypePrefix::SortitionViewMismatch,
RejectCode::TestingDirective => RejectCodeTypePrefix::TestingDirective,
}
}
}
Expand All @@ -562,6 +566,8 @@ pub enum RejectCode {
RejectedInPriorRound,
/// The block was rejected due to a mismatch with expected sortition view
SortitionViewMismatch,
/// The block was rejected due to a testing directive
TestingDirective,
}

define_u8_enum!(
Expand Down Expand Up @@ -615,8 +621,8 @@ impl std::fmt::Display for BlockResponse {
BlockResponse::Rejected(r) => {
write!(
f,
"BlockRejected: signer_sighash = {}, code = {}, reason = {}",
r.reason_code, r.reason, r.signer_signature_hash
"BlockRejected: signer_sighash = {}, code = {}, reason = {}, signature = {}",
r.reason_code, r.reason, r.signer_signature_hash, r.signature
)
}
}
Expand All @@ -629,9 +635,14 @@ impl BlockResponse {
Self::Accepted((hash, sig))
}

/// Create a new rejected BlockResponse for the provided block signer signature hash and rejection code
pub fn rejected(hash: Sha512Trunc256Sum, reject_code: RejectCode) -> Self {
Self::Rejected(BlockRejection::new(hash, reject_code))
/// Create a new rejected BlockResponse for the provided block signer signature hash and rejection code and sign it with the provided private key
pub fn rejected(
hash: Sha512Trunc256Sum,
reject_code: RejectCode,
private_key: &StacksPrivateKey,
mainnet: bool,
) -> Self {
Self::Rejected(BlockRejection::new(hash, reject_code, private_key, mainnet))
}
}

Expand Down Expand Up @@ -677,16 +688,94 @@ pub struct BlockRejection {
pub reason_code: RejectCode,
/// The signer signature hash of the block that was rejected
pub signer_signature_hash: Sha512Trunc256Sum,
/// The signer's signature across the rejection
pub signature: MessageSignature,
/// The chain id
pub chain_id: u32,
}

impl BlockRejection {
/// Create a new BlockRejection for the provided block and reason code
pub fn new(signer_signature_hash: Sha512Trunc256Sum, reason_code: RejectCode) -> Self {
Self {
pub fn new(
signer_signature_hash: Sha512Trunc256Sum,
reason_code: RejectCode,
private_key: &StacksPrivateKey,
mainnet: bool,
) -> Self {
let chain_id = if mainnet {
CHAIN_ID_MAINNET
} else {
CHAIN_ID_TESTNET
};
let mut rejection = Self {
reason: reason_code.to_string(),
reason_code,
signer_signature_hash,
signature: MessageSignature::empty(),
chain_id,
};
rejection
.sign(private_key)
.expect("Failed to sign BlockRejection");
rejection
}

/// Create a new BlockRejection from a BlockValidateRejection
pub fn from_validate_rejection(
reject: BlockValidateReject,
private_key: &StacksPrivateKey,
mainnet: bool,
) -> Self {
let chain_id = if mainnet {
CHAIN_ID_MAINNET
} else {
CHAIN_ID_TESTNET
};
let mut rejection = Self {
reason: reject.reason,
reason_code: RejectCode::ValidationFailed(reject.reason_code),
signer_signature_hash: reject.signer_signature_hash,
chain_id,
signature: MessageSignature::empty(),
};
rejection
.sign(private_key)
.expect("Failed to sign BlockRejection");
rejection
}

/// The signature hash for the block rejection
pub fn hash(&self) -> Sha256Sum {
let domain_tuple = make_structured_data_domain("block-rejection", "1.0.0", self.chain_id);
let data = Value::buff_from(self.signer_signature_hash.as_bytes().into()).unwrap();
structured_data_message_hash(data, domain_tuple)
}

/// Sign the block rejection and set the internal signature field
fn sign(&mut self, private_key: &StacksPrivateKey) -> Result<(), String> {
let signature_hash = self.hash();
self.signature = private_key.sign(signature_hash.as_bytes())?;
Ok(())
}

/// Verify the rejection's signature against the provided signer public key
pub fn verify(&self, public_key: &StacksPublicKey) -> Result<bool, String> {
if self.signature == MessageSignature::empty() {
return Ok(false);
}
let signature_hash = self.hash();
public_key
.verify(&signature_hash.0, &self.signature)
.map_err(|e| e.to_string())
}

/// Recover the public key from the rejection signature
pub fn recover_public_key(&self) -> Result<StacksPublicKey, &'static str> {
if self.signature == MessageSignature::empty() {
return Err("No signature to recover public key from");
}
let signature_hash = self.hash();
StacksPublicKey::recover_to_pubkey(signature_hash.as_bytes(), &self.signature)
}
}

Expand All @@ -695,6 +784,8 @@ impl StacksMessageCodec for BlockRejection {
write_next(fd, &self.reason.as_bytes().to_vec())?;
write_next(fd, &self.reason_code)?;
write_next(fd, &self.signer_signature_hash)?;
write_next(fd, &self.chain_id)?;
write_next(fd, &self.signature)?;
Ok(())
}

Expand All @@ -705,24 +796,18 @@ impl StacksMessageCodec for BlockRejection {
})?;
let reason_code = read_next::<RejectCode, _>(fd)?;
let signer_signature_hash = read_next::<Sha512Trunc256Sum, _>(fd)?;
let chain_id = read_next::<u32, _>(fd)?;
let signature = read_next::<MessageSignature, _>(fd)?;
Ok(Self {
reason,
reason_code,
signer_signature_hash,
chain_id,
signature,
})
}
}

impl From<BlockValidateReject> for BlockRejection {
fn from(reject: BlockValidateReject) -> Self {
Self {
reason: reject.reason,
reason_code: RejectCode::ValidationFailed(reject.reason_code),
signer_signature_hash: reject.signer_signature_hash,
}
}
}

impl StacksMessageCodec for RejectCode {
fn consensus_serialize<W: Write>(&self, fd: &mut W) -> Result<(), CodecError> {
write_next(fd, &(RejectCodeTypePrefix::from(self) as u8))?;
Expand All @@ -732,7 +817,8 @@ impl StacksMessageCodec for RejectCode {
RejectCode::ConnectivityIssues
| RejectCode::RejectedInPriorRound
| RejectCode::NoSortitionView
| RejectCode::SortitionViewMismatch => {
| RejectCode::SortitionViewMismatch
| RejectCode::TestingDirective => {
// No additional data to serialize / deserialize
}
};
Expand All @@ -755,6 +841,7 @@ impl StacksMessageCodec for RejectCode {
RejectCodeTypePrefix::RejectedInPriorRound => RejectCode::RejectedInPriorRound,
RejectCodeTypePrefix::NoSortitionView => RejectCode::NoSortitionView,
RejectCodeTypePrefix::SortitionViewMismatch => RejectCode::SortitionViewMismatch,
RejectCodeTypePrefix::TestingDirective => RejectCode::TestingDirective,
};
Ok(code)
}
Expand Down Expand Up @@ -782,6 +869,9 @@ impl std::fmt::Display for RejectCode {
"The block was rejected due to a mismatch with expected sortition view."
)
}
RejectCode::TestingDirective => {
write!(f, "The block was rejected due to a testing directive.")
}
}
}
}
Expand All @@ -792,12 +882,6 @@ impl From<BlockResponse> for SignerMessage {
}
}

impl From<BlockValidateReject> for BlockResponse {
fn from(rejection: BlockValidateReject) -> Self {
Self::Rejected(rejection.into())
}
}

#[cfg(test)]
mod test {
use blockstack_lib::chainstate::nakamoto::NakamotoBlockHeader;
Expand Down Expand Up @@ -851,14 +935,20 @@ mod test {
let rejection = BlockRejection::new(
Sha512Trunc256Sum([0u8; 32]),
RejectCode::ValidationFailed(ValidateRejectCode::InvalidBlock),
&StacksPrivateKey::new(),
thread_rng().gen_bool(0.5),
);
let serialized_rejection = rejection.serialize_to_vec();
let deserialized_rejection = read_next::<BlockRejection, _>(&mut &serialized_rejection[..])
.expect("Failed to deserialize BlockRejection");
assert_eq!(rejection, deserialized_rejection);

let rejection =
BlockRejection::new(Sha512Trunc256Sum([1u8; 32]), RejectCode::ConnectivityIssues);
let rejection = BlockRejection::new(
Sha512Trunc256Sum([1u8; 32]),
RejectCode::ConnectivityIssues,
&StacksPrivateKey::new(),
thread_rng().gen_bool(0.5),
);
let serialized_rejection = rejection.serialize_to_vec();
let deserialized_rejection = read_next::<BlockRejection, _>(&mut &serialized_rejection[..])
.expect("Failed to deserialize BlockRejection");
Expand All @@ -877,6 +967,8 @@ mod test {
let response = BlockResponse::Rejected(BlockRejection::new(
Sha512Trunc256Sum([1u8; 32]),
RejectCode::ValidationFailed(ValidateRejectCode::InvalidBlock),
&StacksPrivateKey::new(),
thread_rng().gen_bool(0.5),
));
let serialized_response = response.serialize_to_vec();
let deserialized_response = read_next::<BlockResponse, _>(&mut &serialized_response[..])
Expand Down
3 changes: 2 additions & 1 deletion stacks-signer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,5 @@ version = "0.24.3"
features = ["serde", "recovery"]

[features]
monitoring_prom = ["libsigner/monitoring_prom", "prometheus", "tiny_http"]
monitoring_prom = ["libsigner/monitoring_prom", "prometheus", "tiny_http"]
testing = []
Loading