Skip to content

Commit

Permalink
Add block_proposal_max_age_secs config option
Browse files Browse the repository at this point in the history
Signed-off-by: Jacinta Ferrant <[email protected]>
  • Loading branch information
jferrant committed Dec 10, 2024
1 parent b108d09 commit 8e75867
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 5 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ and this project adheres to the versioning scheme outlined in the [README.md](RE
## [Unreleased]

### Added
- Added configuration option `connections.block_proposal_max_age_secs` to prevent processing stale block proposals

### Changed
- The RPC endpoint `/v3/block_proposal` no longer will evaluate block proposals more than `block_proposal_max_age_secs` old

## [3.1.0.0.1]

Expand Down
14 changes: 14 additions & 0 deletions stackslib/src/net/api/postblock_proposal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -742,6 +742,20 @@ impl RPCRequestHandler for RPCBlockProposalRequestHandler {
NetError::SendError("Proposal currently being evaluated".into()),
));
}

if block_proposal
.block
.header
.timestamp
.wrapping_add(network.get_connection_opts().block_proposal_max_age_secs)
< get_epoch_time_secs()
{
return Err((
422,
NetError::SendError("Block proposal is too old to process.".into()),
));
}

let (chainstate, _) = chainstate.reopen().map_err(|e| (400, NetError::from(e)))?;
let sortdb = sortdb.reopen().map_err(|e| (400, NetError::from(e)))?;
let receiver = rpc_args
Expand Down
40 changes: 35 additions & 5 deletions stackslib/src/net/api/tests/postblock_proposal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,9 +334,9 @@ fn test_try_make_response() {
request.add_header("authorization".into(), "password".into());
requests.push(request);

// Set the timestamp to a value in the past
// Set the timestamp to a value in the past (but NOT BEFORE timeout)
let mut early_time_block = good_block.clone();
early_time_block.header.timestamp -= 10000;
early_time_block.header.timestamp -= 400;
rpc_test
.peer_1
.miner
Expand Down Expand Up @@ -382,16 +382,42 @@ fn test_try_make_response() {
request.add_header("authorization".into(), "password".into());
requests.push(request);

// Set the timestamp to a value in the past (BEFORE the timeout)
let mut stale_block = good_block.clone();
stale_block.header.timestamp -= 10000;
rpc_test.peer_1.miner.sign_nakamoto_block(&mut stale_block);

// post the invalid block proposal
let proposal = NakamotoBlockProposal {
block: stale_block,
chain_id: 0x80000000,
};

let mut request = StacksHttpRequest::new_for_peer(
rpc_test.peer_1.to_peer_host(),
"POST".into(),
"/v3/block_proposal".into(),
HttpRequestContents::new().payload_json(serde_json::to_value(proposal).unwrap()),
)
.expect("failed to construct request");
request.add_header("authorization".into(), "password".into());
requests.push(request);

// execute the requests
let observer = ProposalTestObserver::new();
let proposal_observer = Arc::clone(&observer.proposal_observer);

info!("Run requests with observer");
let mut responses = rpc_test.run_with_observer(requests, Some(&observer));
let responses = rpc_test.run_with_observer(requests, Some(&observer));

let response = responses.remove(0);
for response in responses.iter().take(3) {
assert_eq!(response.preamble().status_code, 202);
}
let response = &responses[3];
assert_eq!(response.preamble().status_code, 422);

// Wait for the results of all 3 requests
// Wait for the results of all 3 PROCESSED requests
let start = std::time::Instant::now();
loop {
info!("Wait for results to be non-empty");
if proposal_observer
Expand All @@ -407,6 +433,10 @@ fn test_try_make_response() {
} else {
break;
}
assert!(
start.elapsed().as_secs() < 60,
"Timed out waiting for results"
);
}

let observer = proposal_observer.lock().unwrap();
Expand Down
6 changes: 6 additions & 0 deletions stackslib/src/net/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ use crate::net::{
StacksHttp, StacksP2P,
};

/// The default maximum age in seconds of a block that can be validated by the block proposal endpoint
pub const BLOCK_PROPOSAL_MAX_AGE_SECS: u64 = 600;

/// Receiver notification handle.
/// When a message with the expected `seq` value arrives, send it to an expected receiver (possibly
/// in another thread) via the given `receiver_input` channel.
Expand Down Expand Up @@ -434,6 +437,8 @@ pub struct ConnectionOptions {
pub nakamoto_unconfirmed_downloader_interval_ms: u128,
/// The authorization token to enable privileged RPC endpoints
pub auth_token: Option<String>,
/// The maximum age in seconds of a block that can be validated by the block proposal endpoint
pub block_proposal_max_age_secs: u64,
/// StackerDB replicas to talk to for a particular smart contract
pub stackerdb_hint_replicas: HashMap<QualifiedContractIdentifier, Vec<NeighborAddress>>,

Expand Down Expand Up @@ -568,6 +573,7 @@ impl std::default::Default for ConnectionOptions {
nakamoto_inv_sync_burst_interval_ms: 1_000, // wait 1 second after a sortition before running inventory sync
nakamoto_unconfirmed_downloader_interval_ms: 5_000, // run unconfirmed downloader once every 5 seconds
auth_token: None,
block_proposal_max_age_secs: BLOCK_PROPOSAL_MAX_AGE_SECS,
stackerdb_hint_replicas: HashMap::new(),

// no faults on by default
Expand Down
11 changes: 11 additions & 0 deletions testnet/stacks-node/src/tests/nakamoto_integrations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2875,6 +2875,7 @@ fn block_proposal_api_endpoint() {
const HTTP_ACCEPTED: u16 = 202;
const HTTP_TOO_MANY: u16 = 429;
const HTTP_NOT_AUTHORIZED: u16 = 401;
const HTTP_UNPROCESSABLE: u16 = 422;
let test_cases = [
(
"Valid Nakamoto block proposal",
Expand Down Expand Up @@ -2924,6 +2925,16 @@ fn block_proposal_api_endpoint() {
Some(Err(ValidateRejectCode::ChainstateError)),
),
("Not authorized", sign(&proposal), HTTP_NOT_AUTHORIZED, None),
(
"Unprocessable entity",
{
let mut p = proposal.clone();
p.block.header.timestamp = 0;
sign(&p)
},
HTTP_UNPROCESSABLE,
None,
),
];

// Build HTTP client
Expand Down

0 comments on commit 8e75867

Please sign in to comment.