Skip to content

Commit

Permalink
Merge pull request #5523 from stacks-network/fix/stackerdb-audit-coin…
Browse files Browse the repository at this point in the history
…fabrik

Fix/stackerdb audit coinfabrik
  • Loading branch information
jcnelson authored Dec 9, 2024
2 parents 9999941 + 7b0d6b5 commit 3b25d99
Show file tree
Hide file tree
Showing 8 changed files with 255 additions and 92 deletions.
10 changes: 7 additions & 3 deletions stackslib/src/net/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

use std::collections::VecDeque;
use std::collections::{HashMap, VecDeque};
use std::io::{Read, Write};
use std::ops::{Deref, DerefMut};
use std::sync::mpsc::{
Expand All @@ -24,7 +24,7 @@ use std::time::Duration;
use std::{io, net};

use clarity::vm::costs::ExecutionCost;
use clarity::vm::types::BOUND_VALUE_SERIALIZATION_HEX;
use clarity::vm::types::{QualifiedContractIdentifier, BOUND_VALUE_SERIALIZATION_HEX};
use stacks_common::codec::{StacksMessageCodec, MAX_MESSAGE_LEN};
use stacks_common::types::net::PeerAddress;
use stacks_common::util::hash::to_hex;
Expand All @@ -44,7 +44,8 @@ use crate::net::neighbors::{
WALK_SEED_PROBABILITY, WALK_STATE_TIMEOUT,
};
use crate::net::{
Error as net_error, MessageSequence, Preamble, ProtocolFamily, RelayData, StacksHttp, StacksP2P,
Error as net_error, MessageSequence, NeighborAddress, Preamble, ProtocolFamily, RelayData,
StacksHttp, StacksP2P,
};

/// Receiver notification handle.
Expand Down Expand Up @@ -433,6 +434,8 @@ pub struct ConnectionOptions {
pub nakamoto_unconfirmed_downloader_interval_ms: u128,
/// The authorization token to enable privileged RPC endpoints
pub auth_token: Option<String>,
/// StackerDB replicas to talk to for a particular smart contract
pub stackerdb_hint_replicas: HashMap<QualifiedContractIdentifier, Vec<NeighborAddress>>,

// fault injection
/// Disable neighbor walk and discovery
Expand Down Expand Up @@ -565,6 +568,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,
stackerdb_hint_replicas: HashMap::new(),

// no faults on by default
disable_neighbor_walk: false,
Expand Down
2 changes: 1 addition & 1 deletion stackslib/src/net/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3141,7 +3141,7 @@ pub mod test {
&mut stacks_node.chainstate,
&sortdb,
old_stackerdb_configs,
config.connection_opts.num_neighbors,
&config.connection_opts,
)
.expect("Failed to refresh stackerdb configs");

Expand Down
5 changes: 4 additions & 1 deletion stackslib/src/net/p2p.rs
Original file line number Diff line number Diff line change
Expand Up @@ -841,6 +841,9 @@ impl PeerNetwork {
) -> usize {
let mut count = 0;
for (_, convo) in self.peers.iter() {
if !convo.is_authenticated() {
continue;
}
if !convo.is_outbound() {
continue;
}
Expand Down Expand Up @@ -4158,7 +4161,7 @@ impl PeerNetwork {
chainstate,
sortdb,
stacker_db_configs,
self.connection_opts.num_neighbors,
&self.connection_opts,
)?;
Ok(())
}
Expand Down
181 changes: 99 additions & 82 deletions stackslib/src/net/stackerdb/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,94 @@ impl StackerDBConfig {
Ok(ret)
}

/// Evaluate contract-given hint-replicas
fn eval_hint_replicas(
contract_id: &QualifiedContractIdentifier,
hint_replicas_list: Vec<ClarityValue>,
) -> Result<Vec<NeighborAddress>, NetError> {
let mut hint_replicas = vec![];
for hint_replica_value in hint_replicas_list.into_iter() {
let hint_replica_data = hint_replica_value.expect_tuple()?;

let addr_byte_list = hint_replica_data
.get("addr")
.expect("FATAL: missing 'addr'")
.clone()
.expect_list()?;
let port = hint_replica_data
.get("port")
.expect("FATAL: missing 'port'")
.clone()
.expect_u128()?;
let pubkey_hash_bytes = hint_replica_data
.get("public-key-hash")
.expect("FATAL: missing 'public-key-hash")
.clone()
.expect_buff_padded(20, 0)?;

let mut addr_bytes = vec![];
for byte_val in addr_byte_list.into_iter() {
let byte = byte_val.expect_u128()?;
if byte > (u8::MAX as u128) {
let reason = format!(
"Contract {} stipulates an addr byte above u8::MAX",
contract_id
);
warn!("{}", &reason);
return Err(NetError::InvalidStackerDBContract(
contract_id.clone(),
reason,
));
}
addr_bytes.push(byte as u8);
}
if addr_bytes.len() != 16 {
let reason = format!(
"Contract {} did not stipulate a full 16-octet IP address",
contract_id
);
warn!("{}", &reason);
return Err(NetError::InvalidStackerDBContract(
contract_id.clone(),
reason,
));
}

if port < 1024 || port > u128::from(u16::MAX - 1) {
let reason = format!(
"Contract {} stipulates a port lower than 1024 or above u16::MAX - 1",
contract_id
);
warn!("{}", &reason);
return Err(NetError::InvalidStackerDBContract(
contract_id.clone(),
reason,
));
}
// NOTE: port is now known to be in range [1024, 65535]

let mut pubkey_hash_slice = [0u8; 20];
pubkey_hash_slice.copy_from_slice(&pubkey_hash_bytes[0..20]);

let peer_addr = PeerAddress::from_slice(&addr_bytes).expect("FATAL: not 16 bytes");
if peer_addr.is_in_private_range() {
debug!(
"Ignoring private IP address '{}' in hint-replicas",
&peer_addr.to_socketaddr(port as u16)
);
continue;
}

let naddr = NeighborAddress {
addrbytes: peer_addr,
port: port as u16,
public_key_hash: Hash160(pubkey_hash_slice),
};
hint_replicas.push(naddr);
}
Ok(hint_replicas)
}

/// Evaluate the contract to get its config
fn eval_config(
chainstate: &mut StacksChainState,
Expand All @@ -293,6 +381,7 @@ impl StackerDBConfig {
tip: &StacksBlockId,
signers: Vec<(StacksAddress, u32)>,
local_max_neighbors: u64,
local_hint_replicas: Option<Vec<NeighborAddress>>,
) -> Result<StackerDBConfig, NetError> {
let value =
chainstate.eval_read_only(burn_dbconn, tip, contract_id, "(stackerdb-get-config)")?;
Expand Down Expand Up @@ -394,91 +483,17 @@ impl StackerDBConfig {
max_neighbors = u128::from(local_max_neighbors);
}

let hint_replicas_list = config_tuple
.get("hint-replicas")
.expect("FATAL: missing 'hint-replicas'")
.clone()
.expect_list()?;
let mut hint_replicas = vec![];
for hint_replica_value in hint_replicas_list.into_iter() {
let hint_replica_data = hint_replica_value.expect_tuple()?;

let addr_byte_list = hint_replica_data
.get("addr")
.expect("FATAL: missing 'addr'")
let hint_replicas = if let Some(replicas) = local_hint_replicas {
replicas.clone()
} else {
let hint_replicas_list = config_tuple
.get("hint-replicas")
.expect("FATAL: missing 'hint-replicas'")
.clone()
.expect_list()?;
let port = hint_replica_data
.get("port")
.expect("FATAL: missing 'port'")
.clone()
.expect_u128()?;
let pubkey_hash_bytes = hint_replica_data
.get("public-key-hash")
.expect("FATAL: missing 'public-key-hash")
.clone()
.expect_buff_padded(20, 0)?;

let mut addr_bytes = vec![];
for byte_val in addr_byte_list.into_iter() {
let byte = byte_val.expect_u128()?;
if byte > (u8::MAX as u128) {
let reason = format!(
"Contract {} stipulates an addr byte above u8::MAX",
contract_id
);
warn!("{}", &reason);
return Err(NetError::InvalidStackerDBContract(
contract_id.clone(),
reason,
));
}
addr_bytes.push(byte as u8);
}
if addr_bytes.len() != 16 {
let reason = format!(
"Contract {} did not stipulate a full 16-octet IP address",
contract_id
);
warn!("{}", &reason);
return Err(NetError::InvalidStackerDBContract(
contract_id.clone(),
reason,
));
}

if port < 1024 || port > u128::from(u16::MAX - 1) {
let reason = format!(
"Contract {} stipulates a port lower than 1024 or above u16::MAX - 1",
contract_id
);
warn!("{}", &reason);
return Err(NetError::InvalidStackerDBContract(
contract_id.clone(),
reason,
));
}
// NOTE: port is now known to be in range [1024, 65535]

let mut pubkey_hash_slice = [0u8; 20];
pubkey_hash_slice.copy_from_slice(&pubkey_hash_bytes[0..20]);

let peer_addr = PeerAddress::from_slice(&addr_bytes).expect("FATAL: not 16 bytes");
if peer_addr.is_in_private_range() {
debug!(
"Ignoring private IP address '{}' in hint-replicas",
&peer_addr.to_socketaddr(port as u16)
);
continue;
}

let naddr = NeighborAddress {
addrbytes: peer_addr,
port: port as u16,
public_key_hash: Hash160(pubkey_hash_slice),
};
hint_replicas.push(naddr);
}
Self::eval_hint_replicas(contract_id, hint_replicas_list)?
};

Ok(StackerDBConfig {
chunk_size: chunk_size as u64,
Expand All @@ -497,6 +512,7 @@ impl StackerDBConfig {
sortition_db: &SortitionDB,
contract_id: &QualifiedContractIdentifier,
max_neighbors: u64,
local_hint_replicas: Option<Vec<NeighborAddress>>,
) -> Result<StackerDBConfig, NetError> {
let chain_tip =
NakamotoChainState::get_canonical_block_header(chainstate.db(), sortition_db)?
Expand Down Expand Up @@ -578,6 +594,7 @@ impl StackerDBConfig {
&chain_tip_hash,
signers,
max_neighbors,
local_hint_replicas,
)?;
Ok(config)
}
Expand Down
8 changes: 7 additions & 1 deletion stackslib/src/net/stackerdb/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ use crate::chainstate::burn::db::sortdb::SortitionDB;
use crate::chainstate::nakamoto::NakamotoChainState;
use crate::chainstate::stacks::boot::MINERS_NAME;
use crate::chainstate::stacks::db::StacksChainState;
use crate::net::connection::ConnectionOptions;
use crate::net::neighbors::NeighborComms;
use crate::net::p2p::PeerNetwork;
use crate::net::{
Expand Down Expand Up @@ -285,8 +286,9 @@ impl StackerDBs {
chainstate: &mut StacksChainState,
sortdb: &SortitionDB,
stacker_db_configs: HashMap<QualifiedContractIdentifier, StackerDBConfig>,
num_neighbors: u64,
connection_opts: &ConnectionOptions,
) -> Result<HashMap<QualifiedContractIdentifier, StackerDBConfig>, net_error> {
let num_neighbors = connection_opts.num_neighbors;
let existing_contract_ids = self.get_stackerdb_contract_ids()?;
let mut new_stackerdb_configs = HashMap::new();
let tip = SortitionDB::get_canonical_burn_chain_tip(sortdb.conn())?;
Expand Down Expand Up @@ -314,6 +316,10 @@ impl StackerDBs {
&sortdb,
&stackerdb_contract_id,
num_neighbors,
connection_opts
.stackerdb_hint_replicas
.get(&stackerdb_contract_id)
.cloned(),
)
.unwrap_or_else(|e| {
if matches!(e, net_error::NoSuchStackerDB(_)) && stackerdb_contract_id.is_boot()
Expand Down
Loading

0 comments on commit 3b25d99

Please sign in to comment.