Skip to content

Commit f4d5779

Browse files
authored
Merge branch 'develop' into feat/use-burnchain-timeout
2 parents fcc2498 + b103cec commit f4d5779

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+3449
-690
lines changed

.github/workflows/bitcoin-tests.yml

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ jobs:
4545
- tests::neon_integrations::liquid_ustx_integration
4646
- tests::neon_integrations::microblock_fork_poison_integration_test
4747
- tests::neon_integrations::microblock_integration_test
48-
- tests::neon_integrations::microblock_large_tx_integration_test_FLAKY
48+
# Disable this flaky test. Microblocks are no longer supported anyways.
49+
# - tests::neon_integrations::microblock_large_tx_integration_test_FLAKY
4950
- tests::neon_integrations::microblock_limit_hit_integration_test
5051
- tests::neon_integrations::miner_submit_twice
5152
- tests::neon_integrations::mining_events_integration_test
@@ -86,7 +87,6 @@ jobs:
8687
- tests::nakamoto_integrations::nakamoto_attempt_time
8788
- tests::signer::v0::block_proposal_rejection
8889
- tests::signer::v0::miner_gather_signatures
89-
- tests::signer::v0::mine_2_nakamoto_reward_cycles
9090
- tests::signer::v0::end_of_tenure
9191
- tests::signer::v0::forked_tenure_okay
9292
- tests::signer::v0::forked_tenure_invalid
@@ -95,12 +95,16 @@ jobs:
9595
- tests::signer::v0::multiple_miners
9696
- tests::signer::v0::mock_sign_epoch_25
9797
- tests::signer::v0::multiple_miners_mock_sign_epoch_25
98-
- tests::signer::v0::signer_set_rollover
9998
- tests::signer::v0::miner_forking
10099
- tests::signer::v0::reloads_signer_set_in
101100
- tests::signer::v0::signers_broadcast_signed_blocks
102101
- tests::signer::v0::min_gap_between_blocks
103102
- tests::signer::v0::duplicate_signers
103+
- tests::signer::v0::retry_on_rejection
104+
- tests::signer::v0::locally_accepted_blocks_overriden_by_global_rejection
105+
- tests::signer::v0::locally_rejected_blocks_overriden_by_global_acceptance
106+
- tests::signer::v0::reorg_locally_accepted_blocks_across_tenures_succeeds
107+
- tests::signer::v0::miner_recovers_when_broadcast_block_delay_across_tenures_occurs
104108
- tests::nakamoto_integrations::stack_stx_burn_op_integration_test
105109
- tests::nakamoto_integrations::check_block_heights
106110
- tests::nakamoto_integrations::clarity_burn_state
@@ -115,6 +119,8 @@ jobs:
115119
- tests::nakamoto_integrations::utxo_check_on_startup_recover
116120
- tests::signer::v0::multiple_miners_with_nakamoto_blocks
117121
- tests::signer::v0::partial_tenure_fork
122+
- tests::signer::v0::mine_2_nakamoto_reward_cycles
123+
- tests::signer::v0::signer_set_rollover
118124
# Do not run this one until we figure out why it fails in CI
119125
# - tests::neon_integrations::bitcoin_reorg_flap
120126
# - tests::neon_integrations::bitcoin_reorg_flap_with_follower

docs/rpc/openapi.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -568,7 +568,7 @@ paths:
568568
example:
569569
$ref: ./api/core-node/post-block-proposal-req.example.json
570570

571-
/v2/stacker_set/{cycle_number}:
571+
/v3/stacker_set/{cycle_number}:
572572
get:
573573
summary: Fetch the stacker and signer set information for a given cycle.
574574
tags:

libsigner/src/events.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,9 @@ impl<T: SignerEventTrait> EventReceiver<T> for SignerEventReceiver<T> {
314314
process_proposal_response(request)
315315
} else if request.url() == "/new_burn_block" {
316316
process_new_burn_block_event(request)
317+
} else if request.url() == "/shutdown" {
318+
event_receiver.stop_signal.store(true, Ordering::SeqCst);
319+
return Err(EventError::Terminated);
317320
} else {
318321
let url = request.url().to_string();
319322
// `/new_block` is expected, but not specifically handled. do not log.

libsigner/src/v0/messages.rs

Lines changed: 119 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ use blockstack_lib::util_lib::boot::boot_code_id;
4242
use blockstack_lib::util_lib::signed_structured_data::{
4343
make_structured_data_domain, structured_data_message_hash,
4444
};
45+
use clarity::consts::{CHAIN_ID_MAINNET, CHAIN_ID_TESTNET};
4546
use clarity::types::chainstate::{
4647
BlockHeaderHash, ConsensusHash, StacksPrivateKey, StacksPublicKey,
4748
};
@@ -525,7 +526,9 @@ RejectCodeTypePrefix {
525526
/// The block was rejected due to no sortition view
526527
NoSortitionView = 3,
527528
/// The block was rejected due to a mismatch with expected sortition view
528-
SortitionViewMismatch = 4
529+
SortitionViewMismatch = 4,
530+
/// The block was rejected due to a testing directive
531+
TestingDirective = 5
529532
});
530533

531534
impl TryFrom<u8> for RejectCodeTypePrefix {
@@ -545,6 +548,7 @@ impl From<&RejectCode> for RejectCodeTypePrefix {
545548
RejectCode::RejectedInPriorRound => RejectCodeTypePrefix::RejectedInPriorRound,
546549
RejectCode::NoSortitionView => RejectCodeTypePrefix::NoSortitionView,
547550
RejectCode::SortitionViewMismatch => RejectCodeTypePrefix::SortitionViewMismatch,
551+
RejectCode::TestingDirective => RejectCodeTypePrefix::TestingDirective,
548552
}
549553
}
550554
}
@@ -562,6 +566,8 @@ pub enum RejectCode {
562566
RejectedInPriorRound,
563567
/// The block was rejected due to a mismatch with expected sortition view
564568
SortitionViewMismatch,
569+
/// The block was rejected due to a testing directive
570+
TestingDirective,
565571
}
566572

567573
define_u8_enum!(
@@ -615,8 +621,8 @@ impl std::fmt::Display for BlockResponse {
615621
BlockResponse::Rejected(r) => {
616622
write!(
617623
f,
618-
"BlockRejected: signer_sighash = {}, code = {}, reason = {}",
619-
r.reason_code, r.reason, r.signer_signature_hash
624+
"BlockRejected: signer_sighash = {}, code = {}, reason = {}, signature = {}",
625+
r.reason_code, r.reason, r.signer_signature_hash, r.signature
620626
)
621627
}
622628
}
@@ -629,9 +635,14 @@ impl BlockResponse {
629635
Self::Accepted((hash, sig))
630636
}
631637

632-
/// Create a new rejected BlockResponse for the provided block signer signature hash and rejection code
633-
pub fn rejected(hash: Sha512Trunc256Sum, reject_code: RejectCode) -> Self {
634-
Self::Rejected(BlockRejection::new(hash, reject_code))
638+
/// Create a new rejected BlockResponse for the provided block signer signature hash and rejection code and sign it with the provided private key
639+
pub fn rejected(
640+
hash: Sha512Trunc256Sum,
641+
reject_code: RejectCode,
642+
private_key: &StacksPrivateKey,
643+
mainnet: bool,
644+
) -> Self {
645+
Self::Rejected(BlockRejection::new(hash, reject_code, private_key, mainnet))
635646
}
636647
}
637648

@@ -677,16 +688,94 @@ pub struct BlockRejection {
677688
pub reason_code: RejectCode,
678689
/// The signer signature hash of the block that was rejected
679690
pub signer_signature_hash: Sha512Trunc256Sum,
691+
/// The signer's signature across the rejection
692+
pub signature: MessageSignature,
693+
/// The chain id
694+
pub chain_id: u32,
680695
}
681696

682697
impl BlockRejection {
683698
/// Create a new BlockRejection for the provided block and reason code
684-
pub fn new(signer_signature_hash: Sha512Trunc256Sum, reason_code: RejectCode) -> Self {
685-
Self {
699+
pub fn new(
700+
signer_signature_hash: Sha512Trunc256Sum,
701+
reason_code: RejectCode,
702+
private_key: &StacksPrivateKey,
703+
mainnet: bool,
704+
) -> Self {
705+
let chain_id = if mainnet {
706+
CHAIN_ID_MAINNET
707+
} else {
708+
CHAIN_ID_TESTNET
709+
};
710+
let mut rejection = Self {
686711
reason: reason_code.to_string(),
687712
reason_code,
688713
signer_signature_hash,
714+
signature: MessageSignature::empty(),
715+
chain_id,
716+
};
717+
rejection
718+
.sign(private_key)
719+
.expect("Failed to sign BlockRejection");
720+
rejection
721+
}
722+
723+
/// Create a new BlockRejection from a BlockValidateRejection
724+
pub fn from_validate_rejection(
725+
reject: BlockValidateReject,
726+
private_key: &StacksPrivateKey,
727+
mainnet: bool,
728+
) -> Self {
729+
let chain_id = if mainnet {
730+
CHAIN_ID_MAINNET
731+
} else {
732+
CHAIN_ID_TESTNET
733+
};
734+
let mut rejection = Self {
735+
reason: reject.reason,
736+
reason_code: RejectCode::ValidationFailed(reject.reason_code),
737+
signer_signature_hash: reject.signer_signature_hash,
738+
chain_id,
739+
signature: MessageSignature::empty(),
740+
};
741+
rejection
742+
.sign(private_key)
743+
.expect("Failed to sign BlockRejection");
744+
rejection
745+
}
746+
747+
/// The signature hash for the block rejection
748+
pub fn hash(&self) -> Sha256Sum {
749+
let domain_tuple = make_structured_data_domain("block-rejection", "1.0.0", self.chain_id);
750+
let data = Value::buff_from(self.signer_signature_hash.as_bytes().into()).unwrap();
751+
structured_data_message_hash(data, domain_tuple)
752+
}
753+
754+
/// Sign the block rejection and set the internal signature field
755+
fn sign(&mut self, private_key: &StacksPrivateKey) -> Result<(), String> {
756+
let signature_hash = self.hash();
757+
self.signature = private_key.sign(signature_hash.as_bytes())?;
758+
Ok(())
759+
}
760+
761+
/// Verify the rejection's signature against the provided signer public key
762+
pub fn verify(&self, public_key: &StacksPublicKey) -> Result<bool, String> {
763+
if self.signature == MessageSignature::empty() {
764+
return Ok(false);
765+
}
766+
let signature_hash = self.hash();
767+
public_key
768+
.verify(&signature_hash.0, &self.signature)
769+
.map_err(|e| e.to_string())
770+
}
771+
772+
/// Recover the public key from the rejection signature
773+
pub fn recover_public_key(&self) -> Result<StacksPublicKey, &'static str> {
774+
if self.signature == MessageSignature::empty() {
775+
return Err("No signature to recover public key from");
689776
}
777+
let signature_hash = self.hash();
778+
StacksPublicKey::recover_to_pubkey(signature_hash.as_bytes(), &self.signature)
690779
}
691780
}
692781

@@ -695,6 +784,8 @@ impl StacksMessageCodec for BlockRejection {
695784
write_next(fd, &self.reason.as_bytes().to_vec())?;
696785
write_next(fd, &self.reason_code)?;
697786
write_next(fd, &self.signer_signature_hash)?;
787+
write_next(fd, &self.chain_id)?;
788+
write_next(fd, &self.signature)?;
698789
Ok(())
699790
}
700791

@@ -705,24 +796,18 @@ impl StacksMessageCodec for BlockRejection {
705796
})?;
706797
let reason_code = read_next::<RejectCode, _>(fd)?;
707798
let signer_signature_hash = read_next::<Sha512Trunc256Sum, _>(fd)?;
799+
let chain_id = read_next::<u32, _>(fd)?;
800+
let signature = read_next::<MessageSignature, _>(fd)?;
708801
Ok(Self {
709802
reason,
710803
reason_code,
711804
signer_signature_hash,
805+
chain_id,
806+
signature,
712807
})
713808
}
714809
}
715810

716-
impl From<BlockValidateReject> for BlockRejection {
717-
fn from(reject: BlockValidateReject) -> Self {
718-
Self {
719-
reason: reject.reason,
720-
reason_code: RejectCode::ValidationFailed(reject.reason_code),
721-
signer_signature_hash: reject.signer_signature_hash,
722-
}
723-
}
724-
}
725-
726811
impl StacksMessageCodec for RejectCode {
727812
fn consensus_serialize<W: Write>(&self, fd: &mut W) -> Result<(), CodecError> {
728813
write_next(fd, &(RejectCodeTypePrefix::from(self) as u8))?;
@@ -732,7 +817,8 @@ impl StacksMessageCodec for RejectCode {
732817
RejectCode::ConnectivityIssues
733818
| RejectCode::RejectedInPriorRound
734819
| RejectCode::NoSortitionView
735-
| RejectCode::SortitionViewMismatch => {
820+
| RejectCode::SortitionViewMismatch
821+
| RejectCode::TestingDirective => {
736822
// No additional data to serialize / deserialize
737823
}
738824
};
@@ -755,6 +841,7 @@ impl StacksMessageCodec for RejectCode {
755841
RejectCodeTypePrefix::RejectedInPriorRound => RejectCode::RejectedInPriorRound,
756842
RejectCodeTypePrefix::NoSortitionView => RejectCode::NoSortitionView,
757843
RejectCodeTypePrefix::SortitionViewMismatch => RejectCode::SortitionViewMismatch,
844+
RejectCodeTypePrefix::TestingDirective => RejectCode::TestingDirective,
758845
};
759846
Ok(code)
760847
}
@@ -782,6 +869,9 @@ impl std::fmt::Display for RejectCode {
782869
"The block was rejected due to a mismatch with expected sortition view."
783870
)
784871
}
872+
RejectCode::TestingDirective => {
873+
write!(f, "The block was rejected due to a testing directive.")
874+
}
785875
}
786876
}
787877
}
@@ -792,12 +882,6 @@ impl From<BlockResponse> for SignerMessage {
792882
}
793883
}
794884

795-
impl From<BlockValidateReject> for BlockResponse {
796-
fn from(rejection: BlockValidateReject) -> Self {
797-
Self::Rejected(rejection.into())
798-
}
799-
}
800-
801885
#[cfg(test)]
802886
mod test {
803887
use blockstack_lib::chainstate::nakamoto::NakamotoBlockHeader;
@@ -851,14 +935,20 @@ mod test {
851935
let rejection = BlockRejection::new(
852936
Sha512Trunc256Sum([0u8; 32]),
853937
RejectCode::ValidationFailed(ValidateRejectCode::InvalidBlock),
938+
&StacksPrivateKey::new(),
939+
thread_rng().gen_bool(0.5),
854940
);
855941
let serialized_rejection = rejection.serialize_to_vec();
856942
let deserialized_rejection = read_next::<BlockRejection, _>(&mut &serialized_rejection[..])
857943
.expect("Failed to deserialize BlockRejection");
858944
assert_eq!(rejection, deserialized_rejection);
859945

860-
let rejection =
861-
BlockRejection::new(Sha512Trunc256Sum([1u8; 32]), RejectCode::ConnectivityIssues);
946+
let rejection = BlockRejection::new(
947+
Sha512Trunc256Sum([1u8; 32]),
948+
RejectCode::ConnectivityIssues,
949+
&StacksPrivateKey::new(),
950+
thread_rng().gen_bool(0.5),
951+
);
862952
let serialized_rejection = rejection.serialize_to_vec();
863953
let deserialized_rejection = read_next::<BlockRejection, _>(&mut &serialized_rejection[..])
864954
.expect("Failed to deserialize BlockRejection");
@@ -877,6 +967,8 @@ mod test {
877967
let response = BlockResponse::Rejected(BlockRejection::new(
878968
Sha512Trunc256Sum([1u8; 32]),
879969
RejectCode::ValidationFailed(ValidateRejectCode::InvalidBlock),
970+
&StacksPrivateKey::new(),
971+
thread_rng().gen_bool(0.5),
880972
));
881973
let serialized_response = response.serialize_to_vec();
882974
let deserialized_response = read_next::<BlockResponse, _>(&mut &serialized_response[..])

stacks-common/src/bitvec.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,8 @@ impl<const MAX_SIZE: u16> Serialize for BitVec<MAX_SIZE> {
100100

101101
impl<'de, const MAX_SIZE: u16> Deserialize<'de> for BitVec<MAX_SIZE> {
102102
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
103-
let hex: &str = Deserialize::deserialize(deserializer)?;
104-
let bytes = hex_bytes(hex).map_err(serde::de::Error::custom)?;
103+
let hex: String = Deserialize::deserialize(deserializer)?;
104+
let bytes = hex_bytes(hex.as_str()).map_err(serde::de::Error::custom)?;
105105
Self::consensus_deserialize(&mut bytes.as_slice()).map_err(serde::de::Error::custom)
106106
}
107107
}
@@ -412,4 +412,21 @@ mod test {
412412
check_ok_vector(i.as_slice());
413413
}
414414
}
415+
416+
#[test]
417+
fn test_serde() {
418+
let mut bitvec_zero_10 = BitVec::<10>::zeros(10).unwrap();
419+
bitvec_zero_10.set(0, true).unwrap();
420+
bitvec_zero_10.set(5, true).unwrap();
421+
bitvec_zero_10.set(3, true).unwrap();
422+
assert_eq!(
423+
bitvec_zero_10.binary_str(),
424+
"1001010000",
425+
"Binary string should be 1001010000"
426+
);
427+
428+
let serde_bitvec_json = serde_json::to_string(&bitvec_zero_10).unwrap();
429+
let serde_bitvec: BitVec<10> = serde_json::from_str(&serde_bitvec_json).unwrap();
430+
assert_eq!(serde_bitvec, bitvec_zero_10);
431+
}
415432
}

0 commit comments

Comments
 (0)