Files
stacks-puppet-node/src/net/mod.rs
2022-08-31 13:41:12 -04:00

3633 lines
126 KiB
Rust

// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation
// Copyright (C) 2020 Stacks Open Internet Foundation
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// 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::borrow::Borrow;
use std::cmp::PartialEq;
use std::collections::{HashMap, HashSet};
use std::convert::From;
use std::convert::TryFrom;
use std::error;
use std::fmt;
use std::hash::Hash;
use std::hash::Hasher;
use std::io;
use std::io::prelude::*;
use std::io::{Read, Write};
use std::net::IpAddr;
use std::net::Ipv4Addr;
use std::net::Ipv6Addr;
use std::net::SocketAddr;
use std::ops::Deref;
use std::str::FromStr;
use rand::thread_rng;
use rand::RngCore;
use regex::Regex;
use rusqlite;
use serde::de::Error as de_Error;
use serde::ser::Error as ser_Error;
use serde::{Deserialize, Serialize};
use serde_json;
use url;
use crate::burnchains::Txid;
use crate::chainstate::burn::ConsensusHash;
use crate::chainstate::coordinator::Error as coordinator_error;
use crate::chainstate::stacks::db::blocks::MemPoolRejection;
use crate::chainstate::stacks::index::Error as marf_error;
use crate::chainstate::stacks::Error as chainstate_error;
use crate::chainstate::stacks::{
Error as chain_error, StacksBlock, StacksMicroblock, StacksPublicKey, StacksTransaction,
TransactionPayload,
};
use crate::clarity_vm::clarity::Error as clarity_error;
use crate::core::mempool::*;
use crate::core::POX_REWARD_CYCLE_LENGTH;
use crate::net::atlas::{Attachment, AttachmentInstance};
use crate::net::http::HttpReservedHeader;
pub use crate::net::http::StacksBlockAcceptedData;
use crate::util_lib::bloom::{BloomFilter, BloomNodeHasher};
use crate::util_lib::boot::boot_code_tx_auth;
use crate::util_lib::db::DBConn;
use crate::util_lib::db::Error as db_error;
use crate::util_lib::strings::UrlString;
use clarity::vm::types::TraitIdentifier;
use clarity::vm::{
analysis::contract_interface_builder::ContractInterface, types::PrincipalData, ClarityName,
ContractName, Value,
};
use stacks_common::codec::Error as codec_error;
use stacks_common::codec::StacksMessageCodec;
use stacks_common::codec::{read_next, write_next};
use stacks_common::util::get_epoch_time_secs;
use stacks_common::util::hash::Hash160;
use stacks_common::util::hash::DOUBLE_SHA256_ENCODED_SIZE;
use stacks_common::util::hash::HASH160_ENCODED_SIZE;
use stacks_common::util::hash::{hex_bytes, to_hex};
use stacks_common::util::log;
use stacks_common::util::secp256k1::MessageSignature;
use stacks_common::util::secp256k1::Secp256k1PublicKey;
use stacks_common::util::secp256k1::MESSAGE_SIGNATURE_ENCODED_SIZE;
use crate::chainstate::stacks::StacksBlockHeader;
use crate::codec::BURNCHAIN_HEADER_HASH_ENCODED_SIZE;
use crate::cost_estimates::FeeRateEstimate;
use crate::types::chainstate::BlockHeaderHash;
use crate::types::chainstate::PoxId;
use crate::types::chainstate::{BurnchainHeaderHash, StacksAddress, StacksBlockId};
use crate::types::StacksPublicKeyBuffer;
use crate::util::hash::Sha256Sum;
use crate::vm::costs::ExecutionCost;
use self::dns::*;
pub use self::http::StacksHttp;
use crate::core::StacksEpoch;
/// Implements `ASEntry4` object, which is used in db.rs to store the AS number of an IP address.
pub mod asn;
/// Implements the Atlas network. This network uses the infrastructure created in `src/net` to
/// discover peers, query attachment inventories, and download attachments.
pub mod atlas;
/// Implements the `ConversationP2P` object, a host-to-host session abstraction which allows
/// the node to recieve `StacksMessage` instances. The downstream consumer of this API is `PeerNetwork`.
/// To use OSI terminology, this module implements the session & presentation layers of the P2P network.
/// Other functionality includes (but is not limited to):
/// * set up & tear down of sessions
/// * dealing with and responding to invalid messages
/// * rate limiting messages
pub mod chat;
/// Implements serialization and deserialization for `StacksMessage` types.
/// Also has functionality to sign, verify, and ensure well-formedness of messages.
pub mod codec;
pub mod connection;
pub mod db;
/// Implements `DNSResolver`, a simple DNS resolver state machine. Also implements `DNSClient`,
/// which serves as an API for `DNSResolver`.
pub mod dns;
pub mod download;
pub mod http;
pub mod inv;
pub mod neighbors;
pub mod p2p;
/// Implements wrapper around `mio` crate, which itself is a wrapper around Linux's `epoll(2)` syscall.
/// Creates a pollable interface for sockets, and provides an API for registering and deregistering
/// sockets. This is used to control how many sockets are allocated for the two network servers: the
/// p2p server and the http server.
pub mod poll;
pub mod prune;
pub mod relay;
pub mod rpc;
pub mod server;
#[derive(Debug)]
pub enum Error {
/// Failed to encode
SerializeError(String),
/// Failed to read
ReadError(io::Error),
/// Failed to decode
DeserializeError(String),
/// Failed to write
WriteError(io::Error),
/// Underflow -- not enough bytes to form the message
UnderflowError(String),
/// Overflow -- message too big
OverflowError(String),
/// Wrong protocol family
WrongProtocolFamily,
/// Array is too big
ArrayTooLong,
/// Receive timed out
RecvTimeout,
/// Error signing a message
SigningError(String),
/// Error verifying a message
VerifyingError(String),
/// Read stream is drained. Try again
TemporarilyDrained,
/// Read stream has reached EOF (socket closed, end-of-file reached, etc.)
PermanentlyDrained,
/// Failed to read from the FS
FilesystemError,
/// Database error
DBError(db_error),
/// Socket mutex was poisoned
SocketMutexPoisoned,
/// Socket not instantiated
SocketNotConnectedToPeer,
/// Not connected to peer
ConnectionBroken,
/// Connection could not be (re-)established
ConnectionError,
/// Too many outgoing messages
OutboxOverflow,
/// Too many incoming messages
InboxOverflow,
/// Send error
SendError(String),
/// Recv error
RecvError(String),
/// Invalid message
InvalidMessage,
/// Invalid network handle
InvalidHandle,
/// Network handle is full
FullHandle,
/// Invalid handshake
InvalidHandshake,
/// Stale neighbor
StaleNeighbor,
/// No such neighbor
NoSuchNeighbor,
/// Failed to bind
BindError,
/// Failed to poll
PollError,
/// Failed to accept
AcceptError,
/// Failed to register socket with poller
RegisterError,
/// Failed to query socket metadata
SocketError,
/// server is not bound to a socket
NotConnected,
/// Remote peer is not connected
PeerNotConnected,
/// Too many peers
TooManyPeers,
/// Peer already connected
AlreadyConnected(usize, NeighborKey),
/// Message already in progress
InProgress,
/// Peer is denied
Denied,
/// Data URL is not known
NoDataUrl,
/// Peer is transmitting too fast
PeerThrottled,
/// Error resolving a DNS name
LookupError(String),
/// MARF error, percolated up from chainstate
MARFError(marf_error),
/// Clarity VM error, percolated up from chainstate
ClarityError(clarity_error),
/// Catch-all for chainstate errors that don't map cleanly into network errors
ChainstateError(String),
/// Catch-all for errors that a client should receive more information about
ClientError(ClientError),
/// Coordinator hung up
CoordinatorClosed,
/// view of state is stale (e.g. from the sortition db)
StaleView,
/// Tried to connect to myself
ConnectionCycle,
/// Requested data not found
NotFoundError,
/// Transient error (akin to EAGAIN)
Transient(String),
/// Expected end-of-stream, but had more data
ExpectedEndOfStream,
}
impl From<codec_error> for Error {
fn from(e: codec_error) -> Self {
match e {
codec_error::SerializeError(s) => Error::SerializeError(s),
codec_error::ReadError(e) => Error::ReadError(e),
codec_error::DeserializeError(s) => Error::DeserializeError(s),
codec_error::WriteError(e) => Error::WriteError(e),
codec_error::UnderflowError(s) => Error::UnderflowError(s),
codec_error::OverflowError(s) => Error::OverflowError(s),
codec_error::ArrayTooLong => Error::ArrayTooLong,
}
}
}
/// Enum for passing data for ClientErrors
#[derive(Debug, Clone, PartialEq)]
pub enum ClientError {
/// Catch-all
Message(String),
/// 404
NotFound(String),
}
impl error::Error for ClientError {
fn cause(&self) -> Option<&dyn error::Error> {
None
}
}
impl fmt::Display for ClientError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ClientError::Message(s) => write!(f, "{}", s),
ClientError::NotFound(s) => write!(f, "HTTP path not matched: {}", s),
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Error::SerializeError(ref s) => fmt::Display::fmt(s, f),
Error::DeserializeError(ref s) => fmt::Display::fmt(s, f),
Error::ReadError(ref io) => fmt::Display::fmt(io, f),
Error::WriteError(ref io) => fmt::Display::fmt(io, f),
Error::UnderflowError(ref s) => fmt::Display::fmt(s, f),
Error::OverflowError(ref s) => fmt::Display::fmt(s, f),
Error::WrongProtocolFamily => write!(f, "Improper use of protocol family"),
Error::ArrayTooLong => write!(f, "Array too long"),
Error::RecvTimeout => write!(f, "Packet receive timeout"),
Error::SigningError(ref s) => fmt::Display::fmt(s, f),
Error::VerifyingError(ref s) => fmt::Display::fmt(s, f),
Error::TemporarilyDrained => {
write!(f, "Temporarily out of bytes to read; try again later")
}
Error::PermanentlyDrained => write!(f, "Out of bytes to read"),
Error::FilesystemError => write!(f, "Disk I/O error"),
Error::DBError(ref e) => fmt::Display::fmt(e, f),
Error::SocketMutexPoisoned => write!(f, "socket mutex was poisoned"),
Error::SocketNotConnectedToPeer => write!(f, "not connected to peer"),
Error::ConnectionBroken => write!(f, "connection to peer node is broken"),
Error::ConnectionError => write!(f, "connection to peer could not be (re-)established"),
Error::OutboxOverflow => write!(f, "too many outgoing messages queued"),
Error::InboxOverflow => write!(f, "too many messages pending"),
Error::SendError(ref s) => fmt::Display::fmt(s, f),
Error::RecvError(ref s) => fmt::Display::fmt(s, f),
Error::InvalidMessage => write!(f, "invalid message (malformed or bad signature)"),
Error::InvalidHandle => write!(f, "invalid network handle"),
Error::FullHandle => write!(f, "network handle is full and needs to be drained"),
Error::InvalidHandshake => write!(f, "invalid handshake from remote peer"),
Error::StaleNeighbor => write!(f, "neighbor is too far behind the chain tip"),
Error::NoSuchNeighbor => write!(f, "no such neighbor"),
Error::BindError => write!(f, "Failed to bind to the given address"),
Error::PollError => write!(f, "Failed to poll"),
Error::AcceptError => write!(f, "Failed to accept connection"),
Error::RegisterError => write!(f, "Failed to register socket with poller"),
Error::SocketError => write!(f, "Socket error"),
Error::NotConnected => write!(f, "Not connected to peer network"),
Error::PeerNotConnected => write!(f, "Remote peer is not connected to us"),
Error::TooManyPeers => write!(f, "Too many peer connections open"),
Error::AlreadyConnected(ref _id, ref _nk) => write!(f, "Peer already connected"),
Error::InProgress => write!(f, "Message already in progress"),
Error::Denied => write!(f, "Peer is denied"),
Error::NoDataUrl => write!(f, "No data URL available"),
Error::PeerThrottled => write!(f, "Peer is transmitting too fast"),
Error::LookupError(ref s) => fmt::Display::fmt(s, f),
Error::ChainstateError(ref s) => fmt::Display::fmt(s, f),
Error::ClarityError(ref e) => fmt::Display::fmt(e, f),
Error::MARFError(ref e) => fmt::Display::fmt(e, f),
Error::ClientError(ref e) => write!(f, "ClientError: {}", e),
Error::CoordinatorClosed => write!(f, "Coordinator hung up"),
Error::StaleView => write!(f, "State view is stale"),
Error::ConnectionCycle => write!(f, "Tried to connect to myself"),
Error::NotFoundError => write!(f, "Requested data not found"),
Error::Transient(ref s) => write!(f, "Transient network error: {}", s),
Error::ExpectedEndOfStream => write!(f, "Expected end-of-stream"),
}
}
}
impl error::Error for Error {
fn cause(&self) -> Option<&dyn error::Error> {
match *self {
Error::SerializeError(ref _s) => None,
Error::ReadError(ref io) => Some(io),
Error::DeserializeError(ref _s) => None,
Error::WriteError(ref io) => Some(io),
Error::UnderflowError(ref _s) => None,
Error::OverflowError(ref _s) => None,
Error::WrongProtocolFamily => None,
Error::ArrayTooLong => None,
Error::RecvTimeout => None,
Error::SigningError(ref _s) => None,
Error::VerifyingError(ref _s) => None,
Error::TemporarilyDrained => None,
Error::PermanentlyDrained => None,
Error::FilesystemError => None,
Error::DBError(ref e) => Some(e),
Error::SocketMutexPoisoned => None,
Error::SocketNotConnectedToPeer => None,
Error::ConnectionBroken => None,
Error::ConnectionError => None,
Error::OutboxOverflow => None,
Error::InboxOverflow => None,
Error::SendError(ref _s) => None,
Error::RecvError(ref _s) => None,
Error::InvalidMessage => None,
Error::InvalidHandle => None,
Error::FullHandle => None,
Error::InvalidHandshake => None,
Error::StaleNeighbor => None,
Error::NoSuchNeighbor => None,
Error::BindError => None,
Error::PollError => None,
Error::AcceptError => None,
Error::RegisterError => None,
Error::SocketError => None,
Error::NotConnected => None,
Error::PeerNotConnected => None,
Error::TooManyPeers => None,
Error::AlreadyConnected(ref _id, ref _nk) => None,
Error::InProgress => None,
Error::Denied => None,
Error::NoDataUrl => None,
Error::PeerThrottled => None,
Error::LookupError(ref _s) => None,
Error::ChainstateError(ref _s) => None,
Error::ClientError(ref e) => Some(e),
Error::ClarityError(ref e) => Some(e),
Error::MARFError(ref e) => Some(e),
Error::CoordinatorClosed => None,
Error::StaleView => None,
Error::ConnectionCycle => None,
Error::NotFoundError => None,
Error::Transient(ref _s) => None,
Error::ExpectedEndOfStream => None,
}
}
}
impl From<chain_error> for Error {
fn from(e: chain_error) -> Error {
match e {
chain_error::InvalidStacksBlock(s) => {
Error::ChainstateError(format!("Invalid stacks block: {}", s))
}
chain_error::InvalidStacksMicroblock(msg, hash) => {
Error::ChainstateError(format!("Invalid stacks microblock {:?}: {}", hash, msg))
}
chain_error::InvalidStacksTransaction(s, _) => {
Error::ChainstateError(format!("Invalid stacks transaction: {}", s))
}
chain_error::PostConditionFailed(s) => {
Error::ChainstateError(format!("Postcondition failed: {}", s))
}
chain_error::ClarityError(e) => Error::ClarityError(e),
chain_error::DBError(e) => Error::DBError(e),
chain_error::NetError(e) => e,
chain_error::MARFError(e) => Error::MARFError(e),
chain_error::ReadError(e) => Error::ReadError(e),
chain_error::WriteError(e) => Error::WriteError(e),
_ => Error::ChainstateError(format!("Stacks chainstate error: {:?}", &e)),
}
}
}
impl From<db_error> for Error {
fn from(e: db_error) -> Error {
Error::DBError(e)
}
}
impl From<rusqlite::Error> for Error {
fn from(e: rusqlite::Error) -> Error {
Error::DBError(db_error::SqliteError(e))
}
}
#[cfg(test)]
impl PartialEq for Error {
/// (make I/O errors comparable for testing purposes)
fn eq(&self, other: &Self) -> bool {
let s1 = format!("{:?}", self);
let s2 = format!("{:?}", other);
s1 == s2
}
}
/// A container for an IPv4 or IPv6 address.
/// Rules:
/// -- If this is an IPv6 address, the octets are in network byte order
/// -- If this is an IPv4 address, the octets must encode an IPv6-to-IPv4-mapped address
pub struct PeerAddress([u8; 16]);
impl_array_newtype!(PeerAddress, u8, 16);
impl_array_hexstring_fmt!(PeerAddress);
impl_byte_array_newtype!(PeerAddress, u8, 16);
impl Serialize for PeerAddress {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
let inst = format!("{}", self.to_socketaddr(0).ip());
s.serialize_str(inst.as_str())
}
}
impl<'de> Deserialize<'de> for PeerAddress {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<PeerAddress, D::Error> {
let inst = String::deserialize(d)?;
let ip = inst.parse::<IpAddr>().map_err(de_Error::custom)?;
Ok(PeerAddress::from_ip(&ip))
}
}
impl PeerAddress {
pub fn from_slice(bytes: &[u8]) -> Option<PeerAddress> {
if bytes.len() != 16 {
return None;
}
let mut bytes16 = [0u8; 16];
bytes16.copy_from_slice(&bytes[0..16]);
Some(PeerAddress(bytes16))
}
/// Is this an IPv4 address?
pub fn is_ipv4(&self) -> bool {
self.ipv4_octets().is_some()
}
/// Get the octet representation of this peer address as an IPv4 address.
/// The last 4 bytes of the list contain the IPv4 address.
/// This method returns None if the bytes don't encode a valid IPv4-mapped address (i.e. ::ffff:0:0/96)
pub fn ipv4_octets(&self) -> Option<[u8; 4]> {
if self.0[0..12]
!= [
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
]
{
return None;
}
let mut ret = [0u8; 4];
ret.copy_from_slice(&self.0[12..16]);
Some(ret)
}
/// Return the bit representation of this peer address as an IPv4 address, in network byte
/// order. Return None if this is not an IPv4 address.
pub fn ipv4_bits(&self) -> Option<u32> {
let octets_opt = self.ipv4_octets();
if octets_opt.is_none() {
return None;
}
let octets = octets_opt.unwrap();
Some(
((octets[0] as u32) << 24)
| ((octets[1] as u32) << 16)
| ((octets[2] as u32) << 8)
| (octets[3] as u32),
)
}
/// Convert to SocketAddr
pub fn to_socketaddr(&self, port: u16) -> SocketAddr {
if self.is_ipv4() {
SocketAddr::new(
IpAddr::V4(Ipv4Addr::new(
self.0[12], self.0[13], self.0[14], self.0[15],
)),
port,
)
} else {
let addr_words: [u16; 8] = [
((self.0[0] as u16) << 8) | (self.0[1] as u16),
((self.0[2] as u16) << 8) | (self.0[3] as u16),
((self.0[4] as u16) << 8) | (self.0[5] as u16),
((self.0[6] as u16) << 8) | (self.0[7] as u16),
((self.0[8] as u16) << 8) | (self.0[9] as u16),
((self.0[10] as u16) << 8) | (self.0[11] as u16),
((self.0[12] as u16) << 8) | (self.0[13] as u16),
((self.0[14] as u16) << 8) | (self.0[15] as u16),
];
SocketAddr::new(
IpAddr::V6(Ipv6Addr::new(
addr_words[0],
addr_words[1],
addr_words[2],
addr_words[3],
addr_words[4],
addr_words[5],
addr_words[6],
addr_words[7],
)),
port,
)
}
}
/// Convert from socket address
pub fn from_socketaddr(addr: &SocketAddr) -> PeerAddress {
PeerAddress::from_ip(&addr.ip())
}
/// Convert from IP address
pub fn from_ip(addr: &IpAddr) -> PeerAddress {
match addr {
IpAddr::V4(ref addr) => {
let octets = addr.octets();
PeerAddress([
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
octets[0], octets[1], octets[2], octets[3],
])
}
IpAddr::V6(ref addr) => {
let words = addr.segments();
PeerAddress([
(words[0] >> 8) as u8,
(words[0] & 0xff) as u8,
(words[1] >> 8) as u8,
(words[1] & 0xff) as u8,
(words[2] >> 8) as u8,
(words[2] & 0xff) as u8,
(words[3] >> 8) as u8,
(words[3] & 0xff) as u8,
(words[4] >> 8) as u8,
(words[4] & 0xff) as u8,
(words[5] >> 8) as u8,
(words[5] & 0xff) as u8,
(words[6] >> 8) as u8,
(words[6] & 0xff) as u8,
(words[7] >> 8) as u8,
(words[7] & 0xff) as u8,
])
}
}
}
/// Convert from ipv4 octets
pub fn from_ipv4(o1: u8, o2: u8, o3: u8, o4: u8) -> PeerAddress {
PeerAddress([
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, o1, o2, o3, o4,
])
}
/// Is this the any-network address? i.e. 0.0.0.0 (v4) or :: (v6)?
pub fn is_anynet(&self) -> bool {
self.0 == [0x00; 16] || self == &PeerAddress::from_ipv4(0, 0, 0, 0)
}
/// Is this a private IP address?
pub fn is_in_private_range(&self) -> bool {
if self.is_ipv4() {
// 10.0.0.0/8, 172.16.0.0/12, or 192.168.0.0/16
self.0[12] == 10
|| (self.0[12] == 172 && self.0[13] >= 16 && self.0[13] <= 31)
|| (self.0[12] == 192 && self.0[13] == 168)
} else {
self.0[0] >= 0xfc
}
}
}
pub const STACKS_PUBLIC_KEY_ENCODED_SIZE: u32 = 33;
/// supported HTTP content types
#[derive(Debug, Clone, PartialEq)]
pub enum HttpContentType {
Bytes,
Text,
JSON,
}
impl fmt::Display for HttpContentType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl HttpContentType {
pub fn as_str(&self) -> &'static str {
match *self {
HttpContentType::Bytes => "application/octet-stream",
HttpContentType::Text => "text/plain",
HttpContentType::JSON => "application/json",
}
}
}
impl FromStr for HttpContentType {
type Err = codec_error;
fn from_str(header: &str) -> Result<HttpContentType, codec_error> {
let s = header.to_string().to_lowercase();
if s == "application/octet-stream" {
Ok(HttpContentType::Bytes)
} else if s == "text/plain" {
Ok(HttpContentType::Text)
} else if s == "application/json" {
Ok(HttpContentType::JSON)
} else {
Err(codec_error::DeserializeError(
"Unsupported HTTP content type".to_string(),
))
}
}
}
/// HTTP request preamble
#[derive(Debug, Clone, PartialEq)]
pub struct HttpRequestPreamble {
pub version: HttpVersion,
pub verb: String,
pub path: String,
pub host: PeerHost,
pub content_type: Option<HttpContentType>,
pub content_length: Option<u32>,
pub keep_alive: bool,
pub headers: HashMap<String, String>,
}
/// HTTP response preamble
#[derive(Debug, Clone, PartialEq)]
pub struct HttpResponsePreamble {
pub status_code: u16,
pub reason: String,
pub keep_alive: bool,
pub content_length: Option<u32>, // if not given, then content will be transfer-encoed: chunked
pub content_type: HttpContentType, // required header
pub request_id: u32, // X-Request-ID
pub headers: HashMap<String, String>,
}
/// Maximum size an HTTP request or response preamble can be (within reason)
pub const HTTP_PREAMBLE_MAX_ENCODED_SIZE: u32 = 4096;
pub const HTTP_PREAMBLE_MAX_NUM_HEADERS: usize = 64;
/// P2P message preamble -- included in all p2p network messages
#[derive(Debug, Clone, PartialEq)]
pub struct Preamble {
pub peer_version: u32, // software version
pub network_id: u32, // mainnet, testnet, etc.
pub seq: u32, // message sequence number -- pairs this message to a request
pub burn_block_height: u64, // last-seen block height (at chain tip)
pub burn_block_hash: BurnchainHeaderHash, // hash of the last-seen burn block
pub burn_stable_block_height: u64, // latest stable block height (e.g. chain tip minus 7)
pub burn_stable_block_hash: BurnchainHeaderHash, // latest stable burnchain header hash.
pub additional_data: u32, // RESERVED; pointer to additional data (should be all 0's if not used)
pub signature: MessageSignature, // signature from the peer that sent this
pub payload_len: u32, // length of the following payload, including relayers vector
}
/// Request for a block inventory or a list of blocks.
/// Aligned to a PoX reward cycle.
#[derive(Debug, Clone, PartialEq)]
pub struct GetBlocksInv {
pub consensus_hash: ConsensusHash, // consensus hash at the start of the reward cycle
pub num_blocks: u16, // number of blocks to ask for
}
/// A bit vector that describes which block and microblock data node has data for in a given burn
/// chain block range. Sent in reply to a GetBlocksInv.
#[derive(Debug, Clone, PartialEq)]
pub struct BlocksInvData {
pub bitlen: u16, // number of bits represented in bitvec (not to exceed PoX reward cycle length). Bits correspond to sortitions on the canonical burn chain fork.
pub block_bitvec: Vec<u8>, // bitmap of which blocks the peer has, in sortition order. block_bitvec[i] & (1 << j) != 0 means that this peer has the block for sortition 8*i + j
pub microblocks_bitvec: Vec<u8>, // bitmap of which confirmed micrblocks the peer has, in sortition order. microblocks_bitvec[i] & (1 << j) != 0 means that this peer has the microblocks produced by sortition 8*i + j
}
/// Request for a PoX bitvector range.
/// Requests bits for [start_reward_cycle, start_reward_cycle + num_anchor_blocks)
#[derive(Debug, Clone, PartialEq)]
pub struct GetPoxInv {
pub consensus_hash: ConsensusHash,
pub num_cycles: u16, // how many bits to expect
}
/// Response to a GetPoxInv request
#[derive(Debug, Clone, PartialEq)]
pub struct PoxInvData {
pub bitlen: u16, // number of bits represented
pub pox_bitvec: Vec<u8>, // a bit will be '1' if the node knows for sure the status of its reward cycle's anchor block; 0 if not.
}
#[derive(Debug, Clone, PartialEq)]
pub struct BlocksDatum(pub ConsensusHash, pub StacksBlock);
/// Blocks pushed
#[derive(Debug, Clone, PartialEq)]
pub struct BlocksData {
pub blocks: Vec<BlocksDatum>,
}
/// Microblocks pushed
#[derive(Debug, Clone, PartialEq)]
pub struct MicroblocksData {
pub index_anchor_block: StacksBlockId,
pub microblocks: Vec<StacksMicroblock>,
}
/// Block available hint
#[derive(Debug, Clone, PartialEq)]
pub struct BlocksAvailableData {
pub available: Vec<(ConsensusHash, BurnchainHeaderHash)>,
}
/// A descriptor of a peer
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct NeighborAddress {
#[serde(rename = "ip")]
pub addrbytes: PeerAddress,
pub port: u16,
pub public_key_hash: Hash160, // used as a hint; useful for when a node trusts another node to be honest about this
}
impl fmt::Display for NeighborAddress {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{:?}://{:?}",
&self.public_key_hash,
&self.addrbytes.to_socketaddr(self.port)
)
}
}
impl fmt::Debug for NeighborAddress {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{:?}://{:?}",
&self.public_key_hash,
&self.addrbytes.to_socketaddr(self.port)
)
}
}
impl NeighborAddress {
pub fn clear_public_key(&mut self) -> () {
self.public_key_hash = Hash160([0u8; 20]);
}
pub fn from_neighbor_key(nk: NeighborKey, pkh: Hash160) -> NeighborAddress {
NeighborAddress {
addrbytes: nk.addrbytes,
port: nk.port,
public_key_hash: pkh,
}
}
}
/// A descriptor of a list of known peers
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct NeighborsData {
pub neighbors: Vec<NeighborAddress>,
}
/// Handshake request -- this is the first message sent to a peer.
/// The remote peer will reply a HandshakeAccept with just a preamble
/// if the peer accepts. Otherwise it will get a HandshakeReject with just
/// a preamble.
///
/// To keep peer knowledge fresh, nodes will send handshakes to each other
/// as heartbeat messages.
#[derive(Debug, Clone, PartialEq)]
pub struct HandshakeData {
pub addrbytes: PeerAddress,
pub port: u16,
pub services: u16, // bit field representing services this node offers
pub node_public_key: StacksPublicKeyBuffer,
pub expire_block_height: u64, // burn block height after which this node's key will be revoked,
pub data_url: UrlString,
}
#[repr(u8)]
pub enum ServiceFlags {
RELAY = 0x01,
RPC = 0x02,
}
#[derive(Debug, Clone, PartialEq)]
pub struct HandshakeAcceptData {
pub handshake: HandshakeData, // this peer's handshake information
pub heartbeat_interval: u32, // hint as to how long this peer will remember you
}
#[derive(Debug, Clone, PartialEq)]
pub struct NackData {
pub error_code: u32,
}
pub mod NackErrorCodes {
pub const HandshakeRequired: u32 = 1;
pub const NoSuchBurnchainBlock: u32 = 2;
pub const Throttled: u32 = 3;
pub const InvalidPoxFork: u32 = 4;
pub const InvalidMessage: u32 = 5;
}
#[derive(Debug, Clone, PartialEq)]
pub struct PingData {
pub nonce: u32,
}
#[derive(Debug, Clone, PartialEq)]
pub struct PongData {
pub nonce: u32,
}
#[derive(Debug, Clone, PartialEq)]
pub struct NatPunchData {
pub addrbytes: PeerAddress,
pub port: u16,
pub nonce: u32,
}
define_u8_enum!(MemPoolSyncDataID {
BloomFilter = 0x01,
TxTags = 0x02
});
#[derive(Debug, Clone, PartialEq)]
pub enum MemPoolSyncData {
BloomFilter(BloomFilter<BloomNodeHasher>),
TxTags([u8; 32], Vec<TxTag>),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct RelayData {
pub peer: NeighborAddress,
pub seq: u32,
}
/// All P2P message types
#[derive(Debug, Clone, PartialEq)]
pub enum StacksMessageType {
Handshake(HandshakeData),
HandshakeAccept(HandshakeAcceptData),
HandshakeReject,
GetNeighbors,
Neighbors(NeighborsData),
GetBlocksInv(GetBlocksInv),
BlocksInv(BlocksInvData),
GetPoxInv(GetPoxInv),
PoxInv(PoxInvData),
BlocksAvailable(BlocksAvailableData),
MicroblocksAvailable(BlocksAvailableData),
Blocks(BlocksData),
Microblocks(MicroblocksData),
Transaction(StacksTransaction),
Nack(NackData),
Ping(PingData),
Pong(PongData),
NatPunchRequest(u32),
NatPunchReply(NatPunchData),
}
/// Peer address variants
#[derive(Clone, PartialEq)]
pub enum PeerHost {
DNS(String, u16),
IP(PeerAddress, u16),
}
impl fmt::Display for PeerHost {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
PeerHost::DNS(ref s, ref p) => write!(f, "{}:{}", s, p),
PeerHost::IP(ref a, ref p) => write!(f, "{}", a.to_socketaddr(*p)),
}
}
}
impl fmt::Debug for PeerHost {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
PeerHost::DNS(ref s, ref p) => write!(f, "PeerHost::DNS({},{})", s, p),
PeerHost::IP(ref a, ref p) => write!(f, "PeerHost::IP({:?},{})", a, p),
}
}
}
impl Hash for PeerHost {
fn hash<H: Hasher>(&self, state: &mut H) {
match *self {
PeerHost::DNS(ref name, ref port) => {
"DNS".hash(state);
name.hash(state);
port.hash(state);
}
PeerHost::IP(ref addrbytes, ref port) => {
"IP".hash(state);
addrbytes.hash(state);
port.hash(state);
}
}
}
}
impl PeerHost {
pub fn hostname(&self) -> String {
match *self {
PeerHost::DNS(ref s, _) => s.clone(),
PeerHost::IP(ref a, ref p) => format!("{}", a.to_socketaddr(*p).ip()),
}
}
pub fn port(&self) -> u16 {
match *self {
PeerHost::DNS(_, ref p) => *p,
PeerHost::IP(_, ref p) => *p,
}
}
pub fn from_host_port(host: String, port: u16) -> PeerHost {
// try as IP, and fall back to DNS
match host.parse::<IpAddr>() {
Ok(addr) => PeerHost::IP(PeerAddress::from_ip(&addr), port),
Err(_) => PeerHost::DNS(host, port),
}
}
pub fn from_socketaddr(socketaddr: &SocketAddr) -> PeerHost {
PeerHost::IP(PeerAddress::from_socketaddr(socketaddr), socketaddr.port())
}
pub fn try_from_url(url_str: &UrlString) -> Option<PeerHost> {
let url = match url_str.parse_to_block_url() {
Ok(url) => url,
Err(_e) => {
return None;
}
};
let port = match url.port_or_known_default() {
Some(port) => port,
None => {
return None;
}
};
match url.host() {
Some(url::Host::Domain(name)) => Some(PeerHost::DNS(name.to_string(), port)),
Some(url::Host::Ipv4(addr)) => Some(PeerHost::from_socketaddr(&SocketAddr::new(
IpAddr::V4(addr),
port,
))),
Some(url::Host::Ipv6(addr)) => Some(PeerHost::from_socketaddr(&SocketAddr::new(
IpAddr::V6(addr),
port,
))),
None => None,
}
}
pub fn to_host_port(&self) -> (String, u16) {
match *self {
PeerHost::DNS(ref s, ref p) => (s.clone(), *p),
PeerHost::IP(ref i, ref p) => (format!("{}", i.to_socketaddr(0).ip()), *p),
}
}
}
/// The data we return on GET /v2/info
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RPCPeerInfoData {
pub peer_version: u32,
pub pox_consensus: ConsensusHash,
pub burn_block_height: u64,
pub stable_pox_consensus: ConsensusHash,
pub stable_burn_block_height: u64,
pub server_version: String,
pub network_id: u32,
pub parent_network_id: u32,
pub stacks_tip_height: u64,
pub stacks_tip: BlockHeaderHash,
pub stacks_tip_consensus_hash: ConsensusHash,
pub genesis_chainstate_hash: Sha256Sum,
pub unanchored_tip: Option<StacksBlockId>,
pub unanchored_seq: Option<u16>,
pub exit_at_block_height: Option<u64>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub node_public_key: Option<StacksPublicKeyBuffer>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub node_public_key_hash: Option<Hash160>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RPCPoxCurrentCycleInfo {
pub id: u64,
pub min_threshold_ustx: u64,
pub stacked_ustx: u64,
pub is_pox_active: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RPCPoxNextCycleInfo {
pub id: u64,
pub min_threshold_ustx: u64,
pub min_increment_ustx: u64,
pub stacked_ustx: u64,
pub prepare_phase_start_block_height: u64,
pub blocks_until_prepare_phase: i64,
pub reward_phase_start_block_height: u64,
pub blocks_until_reward_phase: u64,
pub ustx_until_pox_rejection: u64,
}
/// The data we return on GET /v2/pox
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RPCPoxInfoData {
pub contract_id: String,
pub pox_activation_threshold_ustx: u64,
pub first_burnchain_block_height: u64,
pub prepare_phase_block_length: u64,
pub reward_phase_block_length: u64,
pub reward_slots: u64,
pub rejection_fraction: u64,
pub total_liquid_supply_ustx: u64,
pub current_cycle: RPCPoxCurrentCycleInfo,
pub next_cycle: RPCPoxNextCycleInfo,
// below are included for backwards-compatibility
pub min_amount_ustx: u64,
pub prepare_cycle_length: u64,
pub reward_cycle_id: u64,
pub reward_cycle_length: u64,
pub rejection_votes_left_required: u64,
pub next_reward_cycle_in: u64,
}
/// Headers response payload
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ExtendedStacksHeader {
pub consensus_hash: ConsensusHash,
#[serde(
serialize_with = "ExtendedStacksHeader_StacksBlockHeader_serialize",
deserialize_with = "ExtendedStacksHeader_StacksBlockHeader_deserialize"
)]
pub header: StacksBlockHeader,
pub parent_block_id: StacksBlockId,
}
/// In ExtendedStacksHeader, encode the StacksBlockHeader as a hex string
fn ExtendedStacksHeader_StacksBlockHeader_serialize<S: serde::Serializer>(
header: &StacksBlockHeader,
s: S,
) -> Result<S::Ok, S::Error> {
let bytes = header.serialize_to_vec();
let header_hex = to_hex(&bytes);
s.serialize_str(&header_hex.as_str())
}
/// In ExtendedStacksHeader, encode the StacksBlockHeader as a hex string
fn ExtendedStacksHeader_StacksBlockHeader_deserialize<'de, D: serde::Deserializer<'de>>(
d: D,
) -> Result<StacksBlockHeader, D::Error> {
let header_hex = String::deserialize(d)?;
let header_bytes = hex_bytes(&header_hex).map_err(de_Error::custom)?;
StacksBlockHeader::consensus_deserialize(&mut &header_bytes[..]).map_err(de_Error::custom)
}
impl StacksMessageCodec for ExtendedStacksHeader {
fn consensus_serialize<W: Write>(&self, fd: &mut W) -> Result<(), codec_error> {
write_next(fd, &self.consensus_hash)?;
write_next(fd, &self.header)?;
write_next(fd, &self.parent_block_id)?;
Ok(())
}
fn consensus_deserialize<R: Read>(fd: &mut R) -> Result<ExtendedStacksHeader, codec_error> {
let ch = read_next(fd)?;
let bh = read_next(fd)?;
let pbid = read_next(fd)?;
Ok(ExtendedStacksHeader {
consensus_hash: ch,
header: bh,
parent_block_id: pbid,
})
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RPCFeeEstimate {
pub fee_rate: f64,
pub fee: u64,
}
impl RPCFeeEstimate {
pub fn estimate_fees(scalar: u64, fee_rates: FeeRateEstimate) -> Vec<RPCFeeEstimate> {
let estimated_fees_f64 = fee_rates.clone() * (scalar as f64);
vec![
RPCFeeEstimate {
fee: estimated_fees_f64.low as u64,
fee_rate: fee_rates.low,
},
RPCFeeEstimate {
fee: estimated_fees_f64.middle as u64,
fee_rate: fee_rates.middle,
},
RPCFeeEstimate {
fee: estimated_fees_f64.high as u64,
fee_rate: fee_rates.high,
},
]
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RPCFeeEstimateResponse {
pub estimated_cost: ExecutionCost,
pub estimated_cost_scalar: u64,
pub estimations: Vec<RPCFeeEstimate>,
pub cost_scalar_change_by_byte: f64,
}
#[derive(Debug, Clone, PartialEq, Copy, Hash)]
#[repr(u8)]
pub enum HttpVersion {
Http10 = 0x10,
Http11 = 0x11,
}
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct HttpRequestMetadata {
pub version: HttpVersion,
pub peer: PeerHost,
pub keep_alive: bool,
pub canonical_stacks_tip_height: Option<u64>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DataVarResponse {
pub data: String,
#[serde(rename = "proof")]
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub marf_proof: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct MapEntryResponse {
pub data: String,
#[serde(rename = "proof")]
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub marf_proof: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ContractSrcResponse {
pub source: String,
pub publish_height: u32,
#[serde(rename = "proof")]
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub marf_proof: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct GetIsTraitImplementedResponse {
pub is_implemented: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CallReadOnlyResponse {
pub okay: bool,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub cause: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AccountEntryResponse {
pub balance: String,
pub locked: String,
pub unlock_height: u64,
pub nonce: u64,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub balance_proof: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub nonce_proof: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum UnconfirmedTransactionStatus {
Microblock {
block_hash: BlockHeaderHash,
seq: u16,
},
Mempool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct UnconfirmedTransactionResponse {
pub tx: String,
pub status: UnconfirmedTransactionStatus,
}
#[derive(Serialize, Deserialize)]
pub struct PostTransactionRequestBody {
pub tx: String,
pub attachment: Option<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct GetAttachmentResponse {
pub attachment: Attachment,
}
impl Serialize for GetAttachmentResponse {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
let hex_encoded = to_hex(&self.attachment.content[..]);
s.serialize_str(hex_encoded.as_str())
}
}
impl<'de> Deserialize<'de> for GetAttachmentResponse {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<GetAttachmentResponse, D::Error> {
let payload = String::deserialize(d)?;
let hex_encoded = payload.parse::<String>().map_err(de_Error::custom)?;
let bytes = hex_bytes(&hex_encoded).map_err(de_Error::custom)?;
let attachment = Attachment::new(bytes);
Ok(GetAttachmentResponse { attachment })
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct GetAttachmentsInvResponse {
pub block_id: StacksBlockId,
pub pages: Vec<AttachmentPage>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AttachmentPage {
pub index: u32,
pub inventory: Vec<u8>,
}
/// Request ID to use or expect from non-Stacks HTTP clients.
/// In particular, if a HTTP response does not contain the x-request-id header, then it's assumed
/// to be this value. This is needed to support fetching immutables like block and microblock data
/// from non-Stacks nodes (like Gaia hubs, CDNs, vanilla HTTP servers, and so on).
pub const HTTP_REQUEST_ID_RESERVED: u32 = 0;
impl HttpRequestMetadata {
pub fn new(
host: String,
port: u16,
canonical_stacks_tip_height: Option<u64>,
) -> HttpRequestMetadata {
HttpRequestMetadata {
version: HttpVersion::Http11,
peer: PeerHost::from_host_port(host, port),
keep_alive: true,
canonical_stacks_tip_height,
}
}
pub fn from_host(
peer_host: PeerHost,
canonical_stacks_tip_height: Option<u64>,
) -> HttpRequestMetadata {
HttpRequestMetadata {
version: HttpVersion::Http11,
peer: peer_host,
keep_alive: true,
canonical_stacks_tip_height,
}
}
pub fn from_preamble(preamble: &HttpRequestPreamble) -> HttpRequestMetadata {
let mut canonical_stacks_tip_height = None;
for header in &preamble.headers {
if let Some(HttpReservedHeader::CanonicalStacksTipHeight(h)) =
HttpReservedHeader::try_from_str(&header.0, &header.1)
{
canonical_stacks_tip_height = Some(h);
break;
}
}
HttpRequestMetadata {
version: preamble.version,
peer: preamble.host.clone(),
keep_alive: preamble.keep_alive,
canonical_stacks_tip_height,
}
}
}
#[derive(Serialize, Deserialize)]
pub struct CallReadOnlyRequestBody {
pub sender: String,
pub arguments: Vec<String>,
}
#[derive(Serialize, Deserialize)]
pub struct FeeRateEstimateRequestBody {
#[serde(default)]
pub estimated_len: Option<u64>,
pub transaction_payload: String,
}
/// Items in the NeighborsInfo -- combines NeighborKey and NeighborAddress
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RPCNeighbor {
pub network_id: u32,
pub peer_version: u32,
#[serde(rename = "ip")]
pub addrbytes: PeerAddress,
pub port: u16,
pub public_key_hash: Hash160,
pub authenticated: bool,
}
impl RPCNeighbor {
pub fn from_neighbor_key_and_pubkh(nk: NeighborKey, pkh: Hash160, auth: bool) -> RPCNeighbor {
RPCNeighbor {
network_id: nk.network_id,
peer_version: nk.peer_version,
addrbytes: nk.addrbytes,
port: nk.port,
public_key_hash: pkh,
authenticated: auth,
}
}
}
/// Struct given back from a call to `/v2/neighbors`.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RPCNeighborsInfo {
pub sample: Vec<RPCNeighbor>,
pub inbound: Vec<RPCNeighbor>,
pub outbound: Vec<RPCNeighbor>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum TipRequest {
UseLatestAnchoredTip,
UseLatestUnconfirmedTip,
SpecificTip(StacksBlockId),
}
/// All HTTP request paths we support, and the arguments they carry in their paths
#[derive(Debug, Clone, PartialEq)]
pub enum HttpRequestType {
GetInfo(HttpRequestMetadata),
GetPoxInfo(HttpRequestMetadata, TipRequest),
GetNeighbors(HttpRequestMetadata),
GetHeaders(HttpRequestMetadata, u64, TipRequest),
GetBlock(HttpRequestMetadata, StacksBlockId),
GetMicroblocksIndexed(HttpRequestMetadata, StacksBlockId),
GetMicroblocksConfirmed(HttpRequestMetadata, StacksBlockId),
GetMicroblocksUnconfirmed(HttpRequestMetadata, StacksBlockId, u16),
GetTransactionUnconfirmed(HttpRequestMetadata, Txid),
PostTransaction(HttpRequestMetadata, StacksTransaction, Option<Attachment>),
PostBlock(HttpRequestMetadata, ConsensusHash, StacksBlock),
PostMicroblock(HttpRequestMetadata, StacksMicroblock, TipRequest),
GetAccount(HttpRequestMetadata, PrincipalData, TipRequest, bool),
GetDataVar(
HttpRequestMetadata,
StacksAddress,
ContractName,
ClarityName,
TipRequest,
bool,
),
GetMapEntry(
HttpRequestMetadata,
StacksAddress,
ContractName,
ClarityName,
Value,
TipRequest,
bool,
),
FeeRateEstimate(HttpRequestMetadata, TransactionPayload, u64),
CallReadOnlyFunction(
HttpRequestMetadata,
StacksAddress,
ContractName,
PrincipalData,
ClarityName,
Vec<Value>,
TipRequest,
),
GetTransferCost(HttpRequestMetadata),
GetContractSrc(
HttpRequestMetadata,
StacksAddress,
ContractName,
TipRequest,
bool,
),
GetContractABI(HttpRequestMetadata, StacksAddress, ContractName, TipRequest),
OptionsPreflight(HttpRequestMetadata, String),
GetAttachment(HttpRequestMetadata, Hash160),
GetAttachmentsInv(HttpRequestMetadata, StacksBlockId, HashSet<u32>),
GetIsTraitImplemented(
HttpRequestMetadata,
StacksAddress,
ContractName,
TraitIdentifier,
TipRequest,
),
MemPoolQuery(HttpRequestMetadata, MemPoolSyncData, Option<Txid>),
/// catch-all for any errors we should surface from parsing
ClientError(HttpRequestMetadata, ClientError),
}
/// The fields that Actually Matter to http responses
#[derive(Debug, Clone, PartialEq)]
pub struct HttpResponseMetadata {
pub client_version: HttpVersion,
pub client_keep_alive: bool,
pub request_id: u32,
pub content_length: Option<u32>,
pub canonical_stacks_tip_height: Option<u64>,
}
impl HttpResponseMetadata {
pub fn make_request_id() -> u32 {
let mut rng = thread_rng();
let mut request_id = HTTP_REQUEST_ID_RESERVED;
while request_id == HTTP_REQUEST_ID_RESERVED {
request_id = rng.next_u32();
}
request_id
}
pub fn new(
client_version: HttpVersion,
request_id: u32,
content_length: Option<u32>,
client_keep_alive: bool,
canonical_stacks_tip_height: Option<u64>,
) -> HttpResponseMetadata {
HttpResponseMetadata {
client_version: client_version,
client_keep_alive: client_keep_alive,
request_id: request_id,
content_length: content_length,
canonical_stacks_tip_height: canonical_stacks_tip_height,
}
}
pub fn from_preamble(
request_version: HttpVersion,
preamble: &HttpResponsePreamble,
) -> HttpResponseMetadata {
let mut canonical_stacks_tip_height = None;
for header in &preamble.headers {
if let Some(HttpReservedHeader::CanonicalStacksTipHeight(h)) =
HttpReservedHeader::try_from_str(&header.0, &header.1)
{
canonical_stacks_tip_height = Some(h);
break;
}
}
HttpResponseMetadata {
client_version: request_version,
client_keep_alive: preamble.keep_alive,
request_id: preamble.request_id,
content_length: preamble.content_length.clone(),
canonical_stacks_tip_height: canonical_stacks_tip_height,
}
}
pub fn empty_error() -> HttpResponseMetadata {
HttpResponseMetadata {
client_version: HttpVersion::Http11,
client_keep_alive: false,
request_id: HttpResponseMetadata::make_request_id(),
content_length: Some(0),
canonical_stacks_tip_height: None,
}
}
fn from_http_request_type(
req: &HttpRequestType,
canonical_stacks_tip_height: Option<u64>,
) -> HttpResponseMetadata {
let metadata = req.metadata();
HttpResponseMetadata::new(
metadata.version,
HttpResponseMetadata::make_request_id(),
None,
metadata.keep_alive,
canonical_stacks_tip_height,
)
}
}
/// All data-plane message types a peer can reply with.
#[derive(Debug, Clone, PartialEq)]
pub enum HttpResponseType {
PeerInfo(HttpResponseMetadata, RPCPeerInfoData),
PoxInfo(HttpResponseMetadata, RPCPoxInfoData),
Neighbors(HttpResponseMetadata, RPCNeighborsInfo),
Headers(HttpResponseMetadata, Vec<ExtendedStacksHeader>),
HeaderStream(HttpResponseMetadata),
Block(HttpResponseMetadata, StacksBlock),
BlockStream(HttpResponseMetadata),
Microblocks(HttpResponseMetadata, Vec<StacksMicroblock>),
MicroblockStream(HttpResponseMetadata),
TransactionID(HttpResponseMetadata, Txid),
StacksBlockAccepted(HttpResponseMetadata, StacksBlockId, bool),
MicroblockHash(HttpResponseMetadata, BlockHeaderHash),
TokenTransferCost(HttpResponseMetadata, u64),
GetDataVar(HttpResponseMetadata, DataVarResponse),
GetMapEntry(HttpResponseMetadata, MapEntryResponse),
CallReadOnlyFunction(HttpResponseMetadata, CallReadOnlyResponse),
GetAccount(HttpResponseMetadata, AccountEntryResponse),
GetContractABI(HttpResponseMetadata, ContractInterface),
GetContractSrc(HttpResponseMetadata, ContractSrcResponse),
GetIsTraitImplemented(HttpResponseMetadata, GetIsTraitImplementedResponse),
UnconfirmedTransaction(HttpResponseMetadata, UnconfirmedTransactionResponse),
GetAttachment(HttpResponseMetadata, GetAttachmentResponse),
GetAttachmentsInv(HttpResponseMetadata, GetAttachmentsInvResponse),
MemPoolTxStream(HttpResponseMetadata),
MemPoolTxs(HttpResponseMetadata, Option<Txid>, Vec<StacksTransaction>),
OptionsPreflight(HttpResponseMetadata),
TransactionFeeEstimation(HttpResponseMetadata, RPCFeeEstimateResponse),
// peer-given error responses
BadRequest(HttpResponseMetadata, String),
BadRequestJSON(HttpResponseMetadata, serde_json::Value),
Unauthorized(HttpResponseMetadata, String),
PaymentRequired(HttpResponseMetadata, String),
Forbidden(HttpResponseMetadata, String),
NotFound(HttpResponseMetadata, String),
ServerError(HttpResponseMetadata, String),
ServiceUnavailable(HttpResponseMetadata, String),
Error(HttpResponseMetadata, u16, String),
}
#[derive(Debug, Clone, PartialEq, Copy)]
pub enum UrlScheme {
Http,
Https,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum StacksMessageID {
Handshake = 0,
HandshakeAccept = 1,
HandshakeReject = 2,
GetNeighbors = 3,
Neighbors = 4,
GetBlocksInv = 5,
BlocksInv = 6,
GetPoxInv = 7,
PoxInv = 8,
BlocksAvailable = 9,
MicroblocksAvailable = 10,
Blocks = 11,
Microblocks = 12,
Transaction = 13,
Nack = 14,
Ping = 15,
Pong = 16,
NatPunchRequest = 17,
NatPunchReply = 18,
// reserved
Reserved = 255,
}
/// Message type for all P2P Stacks network messages
#[derive(Debug, Clone, PartialEq)]
pub struct StacksMessage {
pub preamble: Preamble,
pub relayers: Vec<RelayData>,
pub payload: StacksMessageType,
}
/// Message type for HTTP
#[derive(Debug, Clone, PartialEq)]
pub enum StacksHttpMessage {
Request(HttpRequestType),
Response(HttpResponseType),
}
/// HTTP message preamble
#[derive(Debug, Clone, PartialEq)]
pub enum StacksHttpPreamble {
Request(HttpRequestPreamble),
Response(HttpResponsePreamble),
}
/// Network messages implement this to have multiple messages in flight.
pub trait MessageSequence {
fn request_id(&self) -> u32;
fn get_message_name(&self) -> &'static str;
}
pub trait ProtocolFamily {
type Preamble: StacksMessageCodec + Send + Sync + Clone + PartialEq + std::fmt::Debug;
type Message: MessageSequence + Send + Sync + Clone + PartialEq + std::fmt::Debug;
/// Return the maximum possible length of the serialized Preamble type
fn preamble_size_hint(&mut self) -> usize;
/// Determine how long the message payload will be, given the Preamble (may return None if the
/// payload length cannot be determined solely by the Preamble).
fn payload_len(&mut self, preamble: &Self::Preamble) -> Option<usize>;
/// Given a byte buffer of a length at last that of the value returned by preamble_size_hint,
/// parse a Preamble and return both the Preamble and the number of bytes actually consumed by it.
fn read_preamble(&mut self, buf: &[u8]) -> Result<(Self::Preamble, usize), Error>;
/// Given a preamble and a byte buffer, parse out a message and return both the message and the
/// number of bytes actually consumed by it. Only used if the message is _not_ streamed. The
/// buf slice is guaranteed to have at least `payload_len()` bytes if `payload_len()` returns
/// Some(...).
fn read_payload(
&mut self,
preamble: &Self::Preamble,
buf: &[u8],
) -> Result<(Self::Message, usize), Error>;
/// Given a preamble and a Read, attempt to stream a message. This will be called if
/// `payload_len()` returns None. This method will be repeatedly called with new data until a
/// message can be obtained; therefore, the ProtocolFamily implementation will need to do its
/// own bufferring and state-tracking.
fn stream_payload<R: Read>(
&mut self,
preamble: &Self::Preamble,
fd: &mut R,
) -> Result<(Option<(Self::Message, usize)>, usize), Error>;
/// Given a public key, a preamble, and the yet-to-be-parsed message bytes, verify the message
/// authenticity. Not all protocols need to do this.
fn verify_payload_bytes(
&mut self,
key: &StacksPublicKey,
preamble: &Self::Preamble,
bytes: &[u8],
) -> Result<(), Error>;
/// Given a Write and a Message, write it out. This method is also responsible for generating
/// and writing out a Preamble for its Message.
fn write_message<W: Write>(&mut self, fd: &mut W, message: &Self::Message)
-> Result<(), Error>;
}
// these implement the ProtocolFamily trait
#[derive(Debug, Clone, PartialEq)]
pub struct StacksP2P {}
// an array in our protocol can't exceed this many items
pub const ARRAY_MAX_LEN: u32 = u32::MAX;
// maximum number of neighbors in a NeighborsData
pub const MAX_NEIGHBORS_DATA_LEN: u32 = 128;
// number of peers to relay to, depending on outbound or inbound
pub const MAX_BROADCAST_OUTBOUND_RECEIVERS: usize = 8;
pub const MAX_BROADCAST_INBOUND_RECEIVERS: usize = 16;
// maximum number of blocks that can be announced as available
pub const BLOCKS_AVAILABLE_MAX_LEN: u32 = 32;
// maximum number of PoX reward cycles we can ask about
#[cfg(not(test))]
pub const GETPOXINV_MAX_BITLEN: u64 = 4096;
#[cfg(test)]
pub const GETPOXINV_MAX_BITLEN: u64 = 8;
// maximum number of blocks that can be pushed at once (even if the entire message is undersized).
// This bound is needed since it bounds the amount of I/O a peer can be asked to do to validate the
// message.
pub const BLOCKS_PUSHED_MAX: u32 = 32;
impl_byte_array_message_codec!(PeerAddress, 16);
impl_byte_array_message_codec!(Txid, 32);
/// neighbor identifier
#[derive(Clone, Eq, PartialOrd, Ord)]
pub struct NeighborKey {
pub peer_version: u32,
pub network_id: u32,
pub addrbytes: PeerAddress,
pub port: u16,
}
impl Hash for NeighborKey {
fn hash<H: Hasher>(&self, state: &mut H) {
// ignores peer version and network ID -- we don't accept or deal with messages that have
// incompatible versions or network IDs in the first place
let peer_major_version = self.peer_version & 0xff000000;
peer_major_version.hash(state);
self.addrbytes.hash(state);
self.port.hash(state);
}
}
impl PartialEq for NeighborKey {
fn eq(&self, other: &NeighborKey) -> bool {
// only check major version byte in peer_version
self.network_id == other.network_id
&& (self.peer_version & 0xff000000) == (other.peer_version & 0xff000000)
&& self.addrbytes == other.addrbytes
&& self.port == other.port
}
}
impl fmt::Display for NeighborKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let peer_version_str = if self.peer_version > 0 {
format!("{:08x}", self.peer_version)
} else {
"UNKNOWN".to_string()
};
let network_id_str = if self.network_id > 0 {
format!("{:08x}", self.network_id)
} else {
"UNKNOWN".to_string()
};
write!(
f,
"{}+{}://{:?}",
peer_version_str,
network_id_str,
&self.addrbytes.to_socketaddr(self.port)
)
}
}
impl fmt::Debug for NeighborKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
impl NeighborKey {
pub fn empty() -> NeighborKey {
NeighborKey {
peer_version: 0,
network_id: 0,
addrbytes: PeerAddress([0u8; 16]),
port: 0,
}
}
pub fn from_neighbor_address(
peer_version: u32,
network_id: u32,
na: &NeighborAddress,
) -> NeighborKey {
NeighborKey {
peer_version: peer_version,
network_id: network_id,
addrbytes: na.addrbytes.clone(),
port: na.port,
}
}
}
/// Entry in the neighbor set
#[derive(Debug, Clone, PartialEq)]
pub struct Neighbor {
pub addr: NeighborKey,
// fields below this can change at runtime
pub public_key: Secp256k1PublicKey,
pub expire_block: u64,
pub last_contact_time: u64, // time when we last authenticated with this peer via a Handshake
pub allowed: i64, // allow deadline (negative == "forever")
pub denied: i64, // deny deadline (negative == "forever")
pub asn: u32, // AS number
pub org: u32, // organization identifier
pub in_degree: u32, // number of peers who list this peer as a neighbor
pub out_degree: u32, // number of neighbors this peer has
}
impl Neighbor {
pub fn is_allowed(&self) -> bool {
self.allowed < 0 || (self.allowed as u64) > get_epoch_time_secs()
}
pub fn is_always_allowed(&self) -> bool {
self.allowed < 0
}
pub fn is_denied(&self) -> bool {
self.denied < 0 || (self.denied as u64) > get_epoch_time_secs()
}
}
impl fmt::Display for Neighbor {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}@{}", self.public_key.to_hex(), self.addr)
}
}
pub const NUM_NEIGHBORS: usize = 32;
// maximum number of unconfirmed microblocks can get streamed to us
pub const MAX_MICROBLOCKS_UNCONFIRMED: usize = 1024;
// maximum number of block headers we'll get streamed to us
pub const MAX_HEADERS: usize = 2100;
// how long a peer will be denied for if it misbehaves
#[cfg(test)]
pub const DENY_BAN_DURATION: u64 = 30; // seconds
#[cfg(not(test))]
pub const DENY_BAN_DURATION: u64 = 86400; // seconds (1 day)
pub const DENY_MIN_BAN_DURATION: u64 = 2;
/// Result of doing network work
pub struct NetworkResult {
pub download_pox_id: Option<PoxId>, // PoX ID as it was when we begin downloading blocks (set if we have downloaded new blocks)
pub unhandled_messages: HashMap<NeighborKey, Vec<StacksMessage>>,
pub blocks: Vec<(ConsensusHash, StacksBlock, u64)>, // blocks we downloaded, and time taken
pub confirmed_microblocks: Vec<(ConsensusHash, Vec<StacksMicroblock>, u64)>, // confiremd microblocks we downloaded, and time taken
pub pushed_transactions: HashMap<NeighborKey, Vec<(Vec<RelayData>, StacksTransaction)>>, // all transactions pushed to us and their message relay hints
pub pushed_blocks: HashMap<NeighborKey, Vec<BlocksData>>, // all blocks pushed to us
pub pushed_microblocks: HashMap<NeighborKey, Vec<(Vec<RelayData>, MicroblocksData)>>, // all microblocks pushed to us, and the relay hints from the message
pub uploaded_transactions: Vec<StacksTransaction>, // transactions sent to us by the http server
pub uploaded_blocks: Vec<BlocksData>, // blocks sent to us via the http server
pub uploaded_microblocks: Vec<MicroblocksData>, // microblocks sent to us by the http server
pub attachments: Vec<(AttachmentInstance, Attachment)>,
pub synced_transactions: Vec<StacksTransaction>, // transactions we downloaded via a mempool sync
pub num_state_machine_passes: u64,
pub num_inv_sync_passes: u64,
pub num_download_passes: u64,
}
impl NetworkResult {
pub fn new(
num_state_machine_passes: u64,
num_inv_sync_passes: u64,
num_download_passes: u64,
) -> NetworkResult {
NetworkResult {
unhandled_messages: HashMap::new(),
download_pox_id: None,
blocks: vec![],
confirmed_microblocks: vec![],
pushed_transactions: HashMap::new(),
pushed_blocks: HashMap::new(),
pushed_microblocks: HashMap::new(),
uploaded_transactions: vec![],
uploaded_blocks: vec![],
uploaded_microblocks: vec![],
attachments: vec![],
synced_transactions: vec![],
num_state_machine_passes: num_state_machine_passes,
num_inv_sync_passes: num_inv_sync_passes,
num_download_passes: num_download_passes,
}
}
pub fn has_blocks(&self) -> bool {
self.blocks.len() > 0 || self.pushed_blocks.len() > 0
}
pub fn has_microblocks(&self) -> bool {
self.confirmed_microblocks.len() > 0
|| self.pushed_microblocks.len() > 0
|| self.uploaded_microblocks.len() > 0
}
pub fn has_transactions(&self) -> bool {
self.pushed_transactions.len() > 0
|| self.uploaded_transactions.len() > 0
|| self.synced_transactions.len() > 0
}
pub fn has_attachments(&self) -> bool {
self.attachments.len() > 0
}
pub fn transactions(&self) -> Vec<StacksTransaction> {
self.pushed_transactions
.values()
.flat_map(|pushed_txs| pushed_txs.iter().map(|(_, tx)| tx.clone()))
.chain(self.uploaded_transactions.iter().map(|x| x.clone()))
.chain(self.synced_transactions.iter().map(|x| x.clone()))
.collect()
}
pub fn has_data_to_store(&self) -> bool {
self.has_blocks()
|| self.has_microblocks()
|| self.has_transactions()
|| self.has_attachments()
}
pub fn consume_unsolicited(
&mut self,
unhandled_messages: HashMap<NeighborKey, Vec<StacksMessage>>,
) -> () {
for (neighbor_key, messages) in unhandled_messages.into_iter() {
for message in messages.into_iter() {
match message.payload {
StacksMessageType::Blocks(block_data) => {
if let Some(blocks_msgs) = self.pushed_blocks.get_mut(&neighbor_key) {
blocks_msgs.push(block_data);
} else {
self.pushed_blocks
.insert(neighbor_key.clone(), vec![block_data]);
}
}
StacksMessageType::Microblocks(mblock_data) => {
if let Some(mblocks_msgs) = self.pushed_microblocks.get_mut(&neighbor_key) {
mblocks_msgs.push((message.relayers, mblock_data));
} else {
self.pushed_microblocks.insert(
neighbor_key.clone(),
vec![(message.relayers, mblock_data)],
);
}
}
StacksMessageType::Transaction(tx_data) => {
if let Some(tx_msgs) = self.pushed_transactions.get_mut(&neighbor_key) {
tx_msgs.push((message.relayers, tx_data));
} else {
self.pushed_transactions
.insert(neighbor_key.clone(), vec![(message.relayers, tx_data)]);
}
}
_ => {
// forward along
if let Some(messages) = self.unhandled_messages.get_mut(&neighbor_key) {
messages.push(message);
} else {
self.unhandled_messages
.insert(neighbor_key.clone(), vec![message]);
}
}
}
}
}
}
pub fn consume_http_uploads(&mut self, mut msgs: Vec<StacksMessageType>) -> () {
for msg in msgs.drain(..) {
match msg {
StacksMessageType::Transaction(tx_data) => {
self.uploaded_transactions.push(tx_data);
}
StacksMessageType::Blocks(block_data) => {
self.uploaded_blocks.push(block_data);
}
StacksMessageType::Microblocks(mblock_data) => {
self.uploaded_microblocks.push(mblock_data);
}
_ => {
// drop
warn!("Dropping unknown HTTP message");
}
}
}
}
}
pub trait Requestable: std::fmt::Display {
fn get_url(&self) -> &UrlString;
fn make_request_type(&self, peer_host: PeerHost) -> HttpRequestType;
}
#[cfg(test)]
pub mod test {
use std::collections::HashMap;
use std::fs;
use std::io;
use std::io::Cursor;
use std::io::ErrorKind;
use std::io::Read;
use std::io::Write;
use std::net::*;
use std::ops::Deref;
use std::ops::DerefMut;
use std::sync::mpsc::sync_channel;
use std::sync::Mutex;
use std::thread;
use clarity::vm::ast::ASTRules;
use mio;
use rand;
use rand::RngCore;
use crate::burnchains::bitcoin::address::*;
use crate::burnchains::bitcoin::keys::*;
use crate::burnchains::bitcoin::*;
use crate::burnchains::burnchain::*;
use crate::burnchains::db::BurnchainDB;
use crate::burnchains::test::*;
use crate::burnchains::*;
use crate::chainstate::burn::db::sortdb;
use crate::chainstate::burn::db::sortdb::*;
use crate::chainstate::burn::operations::*;
use crate::chainstate::burn::*;
use crate::chainstate::coordinator::tests::*;
use crate::chainstate::coordinator::*;
use crate::chainstate::stacks::boot::*;
use crate::chainstate::stacks::db::StacksChainState;
use crate::chainstate::stacks::db::*;
use crate::chainstate::stacks::miner::test::*;
use crate::chainstate::stacks::miner::*;
use crate::chainstate::stacks::*;
use crate::chainstate::*;
use crate::core::NETWORK_P2P_PORT;
use crate::net::asn::*;
use crate::net::atlas::*;
use crate::net::chat::*;
use crate::net::codec::*;
use crate::net::connection::*;
use crate::net::db::*;
use crate::net::neighbors::*;
use crate::net::p2p::*;
use crate::net::poll::*;
use crate::net::relay::*;
use crate::net::rpc::RPCHandlerArgs;
use crate::net::Error as net_error;
use crate::util_lib::strings::*;
use clarity::vm::costs::ExecutionCost;
use clarity::vm::database::STXBalance;
use clarity::vm::types::*;
use stacks_common::address::*;
use stacks_common::util::get_epoch_time_secs;
use stacks_common::util::hash::*;
use stacks_common::util::secp256k1::*;
use stacks_common::util::uint::*;
use stacks_common::util::vrf::*;
use super::*;
use crate::chainstate::stacks::boot::test::get_parent_tip;
use crate::chainstate::stacks::StacksMicroblockHeader;
use crate::chainstate::stacks::{db::accounts::MinerReward, events::StacksTransactionReceipt};
use crate::core::StacksEpochExtension;
use crate::util_lib::boot::boot_code_test_addr;
use stacks_common::codec::StacksMessageCodec;
use stacks_common::types::chainstate::TrieHash;
impl StacksMessageCodec for BlockstackOperationType {
fn consensus_serialize<W: Write>(&self, fd: &mut W) -> Result<(), codec_error> {
match self {
BlockstackOperationType::LeaderKeyRegister(ref op) => op.consensus_serialize(fd),
BlockstackOperationType::LeaderBlockCommit(ref op) => op.consensus_serialize(fd),
BlockstackOperationType::UserBurnSupport(ref op) => op.consensus_serialize(fd),
BlockstackOperationType::TransferStx(_)
| BlockstackOperationType::PreStx(_)
| BlockstackOperationType::StackStx(_) => Ok(()),
}
}
fn consensus_deserialize<R: Read>(
fd: &mut R,
) -> Result<BlockstackOperationType, codec_error> {
panic!("not used");
}
}
// emulate a socket
pub struct NetCursor<T> {
c: Cursor<T>,
closed: bool,
block: bool,
read_error: Option<io::ErrorKind>,
write_error: Option<io::ErrorKind>,
}
impl<T> NetCursor<T> {
pub fn new(inner: T) -> NetCursor<T> {
NetCursor {
c: Cursor::new(inner),
closed: false,
block: false,
read_error: None,
write_error: None,
}
}
pub fn close(&mut self) -> () {
self.closed = true;
}
pub fn block(&mut self) -> () {
self.block = true;
}
pub fn unblock(&mut self) -> () {
self.block = false;
}
pub fn set_read_error(&mut self, e: Option<io::ErrorKind>) -> () {
self.read_error = e;
}
pub fn set_write_error(&mut self, e: Option<io::ErrorKind>) -> () {
self.write_error = e;
}
}
impl<T> Deref for NetCursor<T> {
type Target = Cursor<T>;
fn deref(&self) -> &Cursor<T> {
&self.c
}
}
impl<T> DerefMut for NetCursor<T> {
fn deref_mut(&mut self) -> &mut Cursor<T> {
&mut self.c
}
}
impl<T> Read for NetCursor<T>
where
T: AsRef<[u8]>,
{
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if self.block {
return Err(io::Error::from(ErrorKind::WouldBlock));
}
if self.closed {
return Ok(0);
}
match self.read_error {
Some(ref e) => {
return Err(io::Error::from((*e).clone()));
}
None => {}
}
let sz = self.c.read(buf)?;
if sz == 0 {
// when reading from a non-blocking socket, a return value of 0 indicates the
// remote end was closed. For this reason, when we're out of bytes to read on our
// inner cursor, but still have bytes, we need to re-interpret this as EWOULDBLOCK.
return Err(io::Error::from(ErrorKind::WouldBlock));
} else {
return Ok(sz);
}
}
}
impl Write for NetCursor<&mut [u8]> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
if self.block {
return Err(io::Error::from(ErrorKind::WouldBlock));
}
if self.closed {
return Err(io::Error::from(ErrorKind::Other)); // EBADF
}
match self.write_error {
Some(ref e) => {
return Err(io::Error::from((*e).clone()));
}
None => {}
}
self.c.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.c.flush()
}
}
impl Write for NetCursor<&mut Vec<u8>> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.c.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.c.flush()
}
}
impl Write for NetCursor<Vec<u8>> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.c.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.c.flush()
}
}
/// make a TCP server and a pair of TCP client sockets
pub fn make_tcp_sockets() -> (
mio::tcp::TcpListener,
mio::tcp::TcpStream,
mio::tcp::TcpStream,
) {
let mut rng = rand::thread_rng();
let (std_listener, port) = {
let std_listener;
let mut next_port;
loop {
next_port = 1024 + (rng.next_u32() % (65535 - 1024));
let hostport = format!("127.0.0.1:{}", next_port);
std_listener = match std::net::TcpListener::bind(
&hostport.parse::<std::net::SocketAddr>().unwrap(),
) {
Ok(sock) => sock,
Err(e) => match e.kind() {
io::ErrorKind::AddrInUse => {
continue;
}
_ => {
assert!(false, "TcpListener::bind({}): {:?}", &hostport, &e);
unreachable!();
}
},
};
break;
}
(std_listener, next_port)
};
let std_sock_1 = std::net::TcpStream::connect(
&format!("127.0.0.1:{}", port)
.parse::<std::net::SocketAddr>()
.unwrap(),
)
.unwrap();
let sock_1 = mio::tcp::TcpStream::from_stream(std_sock_1).unwrap();
let (std_sock_2, _) = std_listener.accept().unwrap();
let sock_2 = mio::tcp::TcpStream::from_stream(std_sock_2).unwrap();
sock_1.set_nodelay(true).unwrap();
sock_2.set_nodelay(true).unwrap();
let listener = mio::tcp::TcpListener::from_std(std_listener).unwrap();
(listener, sock_1, sock_2)
}
#[derive(Clone)]
pub struct TestEventObserverBlock {
pub block: StacksBlock,
pub metadata: StacksHeaderInfo,
pub receipts: Vec<StacksTransactionReceipt>,
pub parent: StacksBlockId,
pub winner_txid: Txid,
pub matured_rewards: Vec<MinerReward>,
pub matured_rewards_info: Option<MinerRewardInfo>,
}
pub struct TestEventObserver {
blocks: Mutex<Vec<TestEventObserverBlock>>,
}
impl TestEventObserver {
pub fn get_blocks(&self) -> Vec<TestEventObserverBlock> {
self.blocks.lock().unwrap().deref().to_vec()
}
pub fn new() -> TestEventObserver {
TestEventObserver {
blocks: Mutex::new(vec![]),
}
}
}
impl BlockEventDispatcher for TestEventObserver {
fn announce_block(
&self,
block: &StacksBlock,
metadata: &StacksHeaderInfo,
receipts: &Vec<events::StacksTransactionReceipt>,
parent: &StacksBlockId,
winner_txid: Txid,
matured_rewards: &Vec<accounts::MinerReward>,
matured_rewards_info: Option<&MinerRewardInfo>,
parent_burn_block_hash: BurnchainHeaderHash,
parent_burn_block_height: u32,
parent_burn_block_timestamp: u64,
_anchor_block_cost: &ExecutionCost,
_confirmed_mblock_cost: &ExecutionCost,
) {
self.blocks.lock().unwrap().push(TestEventObserverBlock {
block: block.clone(),
metadata: metadata.clone(),
receipts: receipts.clone(),
parent: parent.clone(),
winner_txid,
matured_rewards: matured_rewards.clone(),
matured_rewards_info: matured_rewards_info.map(|info| info.clone()),
})
}
fn announce_burn_block(
&self,
_burn_block: &BurnchainHeaderHash,
_burn_block_height: u64,
_rewards: Vec<(StacksAddress, u64)>,
_burns: u64,
_reward_recipients: Vec<StacksAddress>,
) {
// pass
}
fn dispatch_boot_receipts(&mut self, _receipts: Vec<events::StacksTransactionReceipt>) {
// pass
}
}
// describes a peer's initial configuration
#[derive(Debug, Clone)]
pub struct TestPeerConfig {
pub network_id: u32,
pub peer_version: u32,
pub current_block: u64,
pub private_key: Secp256k1PrivateKey,
pub private_key_expire: u64,
pub initial_neighbors: Vec<Neighbor>,
pub asn4_entries: Vec<ASEntry4>,
pub burnchain: Burnchain,
pub connection_opts: ConnectionOptions,
pub server_port: u16,
pub http_port: u16,
pub asn: u32,
pub org: u32,
pub allowed: i64,
pub denied: i64,
pub data_url: UrlString,
pub test_name: String,
pub initial_balances: Vec<(PrincipalData, u64)>,
pub initial_lockups: Vec<ChainstateAccountLockup>,
pub spending_account: TestMiner,
pub setup_code: String,
pub epochs: Option<Vec<StacksEpoch>>,
}
impl TestPeerConfig {
pub fn default() -> TestPeerConfig {
let conn_opts = ConnectionOptions::default();
let start_block = 0;
let mut burnchain = Burnchain::default_unittest(
start_block,
&BurnchainHeaderHash::from_hex(
"0000000000000000000000000000000000000000000000000000000000000000",
)
.unwrap(),
);
burnchain.pox_constants = PoxConstants::new(5, 3, 3, 25, 5, u64::MAX, u64::MAX);
let mut spending_account = TestMinerFactory::new().next_miner(
&burnchain,
1,
1,
AddressHashMode::SerializeP2PKH,
);
spending_account.test_with_tx_fees = false; // manually set transaction fees
TestPeerConfig {
network_id: 0x80000000,
peer_version: 0x01020304,
current_block: start_block + (burnchain.consensus_hash_lifetime + 1) as u64,
private_key: Secp256k1PrivateKey::new(),
private_key_expire: start_block + conn_opts.private_key_lifetime,
initial_neighbors: vec![],
asn4_entries: vec![],
burnchain: burnchain,
connection_opts: conn_opts,
server_port: 32000,
http_port: 32001,
asn: 0,
org: 0,
allowed: 0,
denied: 0,
data_url: "".into(),
test_name: "".into(),
initial_balances: vec![],
initial_lockups: vec![],
spending_account: spending_account,
setup_code: "".into(),
epochs: None,
}
}
pub fn from_port(p: u16) -> TestPeerConfig {
let mut config = TestPeerConfig {
server_port: p,
http_port: p + 1,
..TestPeerConfig::default()
};
config.data_url =
UrlString::try_from(format!("http://127.0.0.1:{}", config.http_port).as_str())
.unwrap();
config
}
pub fn new(test_name: &str, p2p_port: u16, rpc_port: u16) -> TestPeerConfig {
let mut config = TestPeerConfig {
test_name: test_name.into(),
server_port: p2p_port,
http_port: rpc_port,
..TestPeerConfig::default()
};
config.data_url =
UrlString::try_from(format!("http://127.0.0.1:{}", config.http_port).as_str())
.unwrap();
config
}
pub fn add_neighbor(&mut self, n: &Neighbor) -> () {
self.initial_neighbors.push(n.clone());
}
pub fn to_neighbor(&self) -> Neighbor {
Neighbor {
addr: NeighborKey {
peer_version: self.peer_version,
network_id: self.network_id,
addrbytes: PeerAddress([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 127, 0, 0, 1,
]),
port: self.server_port,
},
public_key: Secp256k1PublicKey::from_private(&self.private_key),
expire_block: self.private_key_expire,
// not known yet
last_contact_time: 0,
allowed: self.allowed,
denied: self.denied,
asn: self.asn,
org: self.org,
in_degree: 0,
out_degree: 0,
}
}
pub fn to_peer_host(&self) -> PeerHost {
PeerHost::IP(
PeerAddress([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 127, 0, 0, 1]),
self.http_port,
)
}
}
pub fn dns_thread_start(max_inflight: u64) -> (DNSClient, thread::JoinHandle<()>) {
let (mut resolver, client) = DNSResolver::new(max_inflight);
let jh = thread::spawn(move || {
resolver.thread_main();
});
(client, jh)
}
pub fn dns_thread_shutdown(dns_client: DNSClient, thread_handle: thread::JoinHandle<()>) {
drop(dns_client);
thread_handle.join().unwrap();
}
pub struct TestPeer<'a> {
pub config: TestPeerConfig,
pub network: PeerNetwork,
pub sortdb: Option<SortitionDB>,
pub miner: TestMiner,
pub stacks_node: Option<TestStacksNode>,
pub relayer: Relayer,
pub mempool: Option<MemPoolDB>,
pub chainstate_path: String,
pub coord: ChainsCoordinator<'a, TestEventObserver, (), OnChainRewardSetProvider, (), ()>,
}
impl<'a> TestPeer<'a> {
pub fn new(config: TestPeerConfig) -> TestPeer<'a> {
TestPeer::new_with_observer(config, None)
}
pub fn test_path(config: &TestPeerConfig) -> String {
format!(
"/tmp/stacks-node-tests/units-test-peer/{}-{}",
&config.test_name, config.server_port
)
}
pub fn new_with_observer(
mut config: TestPeerConfig,
observer: Option<&'a TestEventObserver>,
) -> TestPeer<'a> {
let test_path = TestPeer::test_path(&config);
match fs::metadata(&test_path) {
Ok(_) => {
fs::remove_dir_all(&test_path).unwrap();
}
Err(_) => {}
};
fs::create_dir_all(&test_path).unwrap();
let mut miner_factory = TestMinerFactory::new();
let mut miner =
miner_factory.next_miner(&config.burnchain, 1, 1, AddressHashMode::SerializeP2PKH);
// manually set fees
miner.test_with_tx_fees = false;
config.burnchain.working_dir = get_burnchain(&test_path, None).working_dir;
let epochs = config.epochs.clone().unwrap_or_else(|| {
StacksEpoch::unit_test_pre_2_05(config.burnchain.first_block_height)
});
let mut sortdb = SortitionDB::connect(
&config.burnchain.get_db_path(),
config.burnchain.first_block_height,
&config.burnchain.first_block_hash,
0,
&epochs,
true,
)
.unwrap();
let first_burnchain_block_height = config.burnchain.first_block_height;
let first_burnchain_block_hash = config.burnchain.first_block_hash;
let _burnchain_blocks_db = BurnchainDB::connect(
&config.burnchain.get_burnchaindb_path(),
first_burnchain_block_height,
&first_burnchain_block_hash,
0,
true,
)
.unwrap();
let chainstate_path = get_chainstate_path_str(&test_path);
let peerdb_path = format!("{}/peers.sqlite", &test_path);
let mut peerdb = PeerDB::connect(
&peerdb_path,
true,
config.network_id,
config.burnchain.network_id,
None,
config.private_key_expire,
PeerAddress::from_ipv4(127, 0, 0, 1),
config.server_port,
config.data_url.clone(),
&config.asn4_entries,
Some(&config.initial_neighbors),
)
.unwrap();
{
// bootstrap nodes *always* allowed
let mut tx = peerdb.tx_begin().unwrap();
for initial_neighbor in config.initial_neighbors.iter() {
PeerDB::set_allow_peer(
&mut tx,
initial_neighbor.addr.network_id,
&initial_neighbor.addr.addrbytes,
initial_neighbor.addr.port,
-1,
)
.unwrap();
}
tx.commit().unwrap();
}
let atlasdb_path = format!("{}/atlas.sqlite", &test_path);
let atlasdb =
AtlasDB::connect(AtlasConfig::default(false), &atlasdb_path, true).unwrap();
let conf = config.clone();
let post_flight_callback = move |clarity_tx: &mut ClarityTx| {
let mut receipts = vec![];
if conf.setup_code.len() > 0 {
let receipt = clarity_tx.connection().as_transaction(|clarity| {
let boot_code_addr = boot_code_test_addr();
let boot_code_account = StacksAccount {
principal: boot_code_addr.to_account_principal(),
nonce: 0,
stx_balance: STXBalance::zero(),
};
let boot_code_auth = boot_code_tx_auth(boot_code_addr);
debug!(
"Instantiate test-specific boot code contract '{}.{}' ({} bytes)...",
&boot_code_addr.to_string(),
&conf.test_name,
conf.setup_code.len()
);
let smart_contract =
TransactionPayload::SmartContract(TransactionSmartContract {
name: ContractName::try_from(conf.test_name.as_str())
.expect("FATAL: invalid boot-code contract name"),
code_body: StacksString::from_str(&conf.setup_code)
.expect("FATAL: invalid boot code body"),
});
let boot_code_smart_contract = StacksTransaction::new(
TransactionVersion::Testnet,
boot_code_auth.clone(),
smart_contract,
);
StacksChainState::process_transaction_payload(
clarity,
&boot_code_smart_contract,
&boot_code_account,
ASTRules::PrecheckSize,
)
.unwrap()
});
receipts.push(receipt);
}
debug!("Bootup receipts: {:?}", &receipts);
};
let mut boot_data = ChainStateBootData::new(
&config.burnchain,
config.initial_balances.clone(),
Some(Box::new(post_flight_callback)),
);
if !config.initial_lockups.is_empty() {
let lockups = config.initial_lockups.clone();
boot_data.get_bulk_initial_lockups =
Some(Box::new(move || Box::new(lockups.into_iter().map(|e| e))));
}
let (chainstate, _) = StacksChainState::open_and_exec(
false,
config.network_id,
&chainstate_path,
Some(&mut boot_data),
None,
)
.unwrap();
let (tx, _) = sync_channel(100000);
let mut coord = ChainsCoordinator::test_new_with_observer(
&config.burnchain,
config.network_id,
&test_path,
OnChainRewardSetProvider(),
tx,
observer,
);
coord.handle_new_burnchain_block().unwrap();
let mut stacks_node = TestStacksNode::from_chainstate(chainstate);
{
// pre-populate burnchain, if running on bitcoin
let prev_snapshot = SortitionDB::get_first_block_snapshot(sortdb.conn()).unwrap();
let mut fork = TestBurnchainFork::new(
prev_snapshot.block_height,
&prev_snapshot.burn_header_hash,
&prev_snapshot.index_root,
0,
);
for i in prev_snapshot.block_height..config.current_block {
let burn_block = {
let ic = sortdb.index_conn();
let mut burn_block = fork.next_block(&ic);
stacks_node.add_key_register(&mut burn_block, &mut miner);
burn_block
};
fork.append_block(burn_block);
fork.mine_pending_blocks_pox(&mut sortdb, &config.burnchain, &mut coord);
}
}
let local_addr =
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), config.server_port);
let http_local_addr =
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), config.http_port);
{
let mut tx = peerdb.tx_begin().unwrap();
PeerDB::set_local_ipaddr(
&mut tx,
&PeerAddress::from_socketaddr(&SocketAddr::new(
IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
config.server_port,
)),
config.server_port,
)
.unwrap();
PeerDB::set_local_private_key(
&mut tx,
&config.private_key,
config.private_key_expire,
)
.unwrap();
tx.commit().unwrap();
}
let local_peer = PeerDB::get_local_peer(peerdb.conn()).unwrap();
let burnchain_view = {
let chaintip = SortitionDB::get_canonical_burn_chain_tip(&sortdb.conn()).unwrap();
SortitionDB::get_burnchain_view(&sortdb.conn(), &config.burnchain, &chaintip)
.unwrap()
};
let mut peer_network = PeerNetwork::new(
peerdb,
atlasdb,
local_peer,
config.peer_version,
config.burnchain.clone(),
burnchain_view,
config.connection_opts.clone(),
epochs.clone(),
);
peer_network.bind(&local_addr, &http_local_addr).unwrap();
let relayer = Relayer::from_p2p(&mut peer_network);
let mempool = MemPoolDB::open_test(false, config.network_id, &chainstate_path).unwrap();
TestPeer {
config: config,
network: peer_network,
sortdb: Some(sortdb),
miner: miner,
stacks_node: Some(stacks_node),
relayer: relayer,
mempool: Some(mempool),
chainstate_path: chainstate_path,
coord: coord,
}
}
pub fn connect_initial(&mut self) -> Result<(), net_error> {
let local_peer = PeerDB::get_local_peer(self.network.peerdb.conn()).unwrap();
let chain_view = match self.sortdb {
Some(ref mut sortdb) => {
let chaintip =
SortitionDB::get_canonical_burn_chain_tip(sortdb.conn()).unwrap();
SortitionDB::get_burnchain_view(
&sortdb.conn(),
&self.config.burnchain,
&chaintip,
)
.unwrap()
}
None => panic!("Misconfigured peer: no sortdb"),
};
self.network.local_peer = local_peer;
self.network.chain_view = chain_view;
for n in self.config.initial_neighbors.iter() {
self.network.connect_peer(&n.addr).and_then(|e| Ok(()))?;
}
Ok(())
}
pub fn local_peer(&self) -> &LocalPeer {
&self.network.local_peer
}
pub fn step(&mut self) -> Result<NetworkResult, net_error> {
let mut sortdb = self.sortdb.take().unwrap();
let mut stacks_node = self.stacks_node.take().unwrap();
let mut mempool = self.mempool.take().unwrap();
let ret = self.network.run(
&mut sortdb,
&mut stacks_node.chainstate,
&mut mempool,
None,
false,
false,
100,
&RPCHandlerArgs::default(),
&mut HashSet::new(),
);
self.sortdb = Some(sortdb);
self.stacks_node = Some(stacks_node);
self.mempool = Some(mempool);
ret
}
pub fn step_dns(&mut self, dns_client: &mut DNSClient) -> Result<NetworkResult, net_error> {
let mut sortdb = self.sortdb.take().unwrap();
let mut stacks_node = self.stacks_node.take().unwrap();
let mut mempool = self.mempool.take().unwrap();
let ret = self.network.run(
&mut sortdb,
&mut stacks_node.chainstate,
&mut mempool,
Some(dns_client),
false,
false,
100,
&RPCHandlerArgs::default(),
&mut HashSet::new(),
);
self.sortdb = Some(sortdb);
self.stacks_node = Some(stacks_node);
self.mempool = Some(mempool);
ret
}
pub fn for_each_convo_p2p<F, R>(&mut self, mut f: F) -> Vec<Result<R, net_error>>
where
F: FnMut(usize, &mut ConversationP2P) -> Result<R, net_error>,
{
let mut ret = vec![];
for (event_id, convo) in self.network.peers.iter_mut() {
let res = f(*event_id, convo);
ret.push(res);
}
ret
}
pub fn next_burnchain_block(
&mut self,
blockstack_ops: Vec<BlockstackOperationType>,
) -> (u64, BurnchainHeaderHash, ConsensusHash) {
self.inner_next_burnchain_block(blockstack_ops, true, true)
}
pub fn next_burnchain_block_raw(
&mut self,
blockstack_ops: Vec<BlockstackOperationType>,
) -> (u64, BurnchainHeaderHash, ConsensusHash) {
self.inner_next_burnchain_block(blockstack_ops, false, false)
}
pub fn set_ops_consensus_hash(
blockstack_ops: &mut Vec<BlockstackOperationType>,
ch: &ConsensusHash,
) {
for op in blockstack_ops.iter_mut() {
match op {
BlockstackOperationType::LeaderKeyRegister(ref mut data) => {
data.consensus_hash = (*ch).clone();
}
BlockstackOperationType::UserBurnSupport(ref mut data) => {
data.consensus_hash = (*ch).clone();
}
_ => {}
}
}
}
pub fn set_ops_burn_header_hash(
blockstack_ops: &mut Vec<BlockstackOperationType>,
bhh: &BurnchainHeaderHash,
) {
for op in blockstack_ops.iter_mut() {
op.set_burn_header_hash(bhh.clone());
}
}
fn inner_next_burnchain_block(
&mut self,
mut blockstack_ops: Vec<BlockstackOperationType>,
set_consensus_hash: bool,
set_burn_hash: bool,
) -> (u64, BurnchainHeaderHash, ConsensusHash) {
let sortdb = self.sortdb.take().unwrap();
let (block_height, block_hash) = {
let tip = SortitionDB::get_canonical_burn_chain_tip(&sortdb.conn()).unwrap();
if set_consensus_hash {
TestPeer::set_ops_consensus_hash(&mut blockstack_ops, &tip.consensus_hash);
}
// quick'n'dirty hash of all operations and block height
let mut op_buf = vec![];
for op in blockstack_ops.iter() {
op.consensus_serialize(&mut op_buf).unwrap();
}
op_buf.append(&mut (tip.block_height + 1).to_be_bytes().to_vec());
let h = Sha512Trunc256Sum::from_data(&op_buf);
let mut hash_buf = [0u8; 32];
hash_buf.copy_from_slice(&h.0);
let block_header_hash = BurnchainHeaderHash(hash_buf);
let block_header = BurnchainBlockHeader::from_parent_snapshot(
&tip,
block_header_hash.clone(),
blockstack_ops.len() as u64,
);
if set_burn_hash {
TestPeer::set_ops_burn_header_hash(&mut blockstack_ops, &block_header_hash);
}
let mut burnchain_db =
BurnchainDB::open(&self.config.burnchain.get_burnchaindb_path(), true).unwrap();
burnchain_db
.raw_store_burnchain_block(block_header.clone(), blockstack_ops)
.unwrap();
(block_header.block_height, block_header_hash)
};
self.coord.handle_new_burnchain_block().unwrap();
let pox_id = {
let ic = sortdb.index_conn();
let tip_sort_id = SortitionDB::get_canonical_sortition_tip(sortdb.conn()).unwrap();
let sortdb_reader = SortitionHandleConn::open_reader(&ic, &tip_sort_id).unwrap();
sortdb_reader.get_pox_id().unwrap()
};
test_debug!(
"\n\n{:?}: after burn block {:?}, tip PoX ID is {:?}\n\n",
&self.to_neighbor().addr,
&block_hash,
&pox_id
);
let tip = SortitionDB::get_canonical_burn_chain_tip(&sortdb.conn()).unwrap();
self.sortdb = Some(sortdb);
(block_height, block_hash, tip.consensus_hash)
}
pub fn preprocess_stacks_block(&mut self, block: &StacksBlock) -> Result<bool, String> {
let sortdb = self.sortdb.take().unwrap();
let mut node = self.stacks_node.take().unwrap();
let res = {
let sn = {
let ic = sortdb.index_conn();
let tip = SortitionDB::get_canonical_burn_chain_tip(&ic).unwrap();
let sn_opt = SortitionDB::get_block_snapshot_for_winning_stacks_block(
&ic,
&tip.sortition_id,
&block.block_hash(),
)
.unwrap();
if sn_opt.is_none() {
return Err(format!(
"No such block in canonical burn fork: {}",
&block.block_hash()
));
}
sn_opt.unwrap()
};
let parent_sn = {
let db_handle = sortdb.index_handle(&sn.sortition_id);
let parent_sn = db_handle
.get_block_snapshot(&sn.parent_burn_header_hash)
.unwrap();
parent_sn.unwrap()
};
let ic = sortdb.index_conn();
node.chainstate
.preprocess_anchored_block(
&ic,
&sn.consensus_hash,
block,
&parent_sn.consensus_hash,
5,
)
.map_err(|e| format!("Failed to preprocess anchored block: {:?}", &e))
};
if res.is_ok() {
let pox_id = {
let ic = sortdb.index_conn();
let tip_sort_id =
SortitionDB::get_canonical_sortition_tip(sortdb.conn()).unwrap();
let sortdb_reader =
SortitionHandleConn::open_reader(&ic, &tip_sort_id).unwrap();
sortdb_reader.get_pox_id().unwrap()
};
test_debug!(
"\n\n{:?}: after stacks block {:?}, tip PoX ID is {:?}\n\n",
&self.to_neighbor().addr,
&block.block_hash(),
&pox_id
);
self.coord.handle_new_stacks_block().unwrap();
}
self.sortdb = Some(sortdb);
self.stacks_node = Some(node);
res
}
pub fn preprocess_stacks_microblocks(
&mut self,
microblocks: &Vec<StacksMicroblock>,
) -> Result<bool, String> {
assert!(microblocks.len() > 0);
let sortdb = self.sortdb.take().unwrap();
let mut node = self.stacks_node.take().unwrap();
let res = {
let anchor_block_hash = microblocks[0].header.prev_block.clone();
let sn = {
let ic = sortdb.index_conn();
let tip = SortitionDB::get_canonical_burn_chain_tip(&ic).unwrap();
let sn_opt = SortitionDB::get_block_snapshot_for_winning_stacks_block(
&ic,
&tip.sortition_id,
&anchor_block_hash,
)
.unwrap();
if sn_opt.is_none() {
return Err(format!(
"No such block in canonical burn fork: {}",
&anchor_block_hash
));
}
sn_opt.unwrap()
};
let mut res = Ok(true);
for mblock in microblocks.iter() {
res = node
.chainstate
.preprocess_streamed_microblock(
&sn.consensus_hash,
&anchor_block_hash,
mblock,
)
.map_err(|e| format!("Failed to preprocess microblock: {:?}", &e));
if res.is_err() {
break;
}
}
res
};
self.sortdb = Some(sortdb);
self.stacks_node = Some(node);
res
}
pub fn process_stacks_epoch_at_tip(
&mut self,
block: &StacksBlock,
microblocks: &Vec<StacksMicroblock>,
) -> () {
let sortdb = self.sortdb.take().unwrap();
let mut node = self.stacks_node.take().unwrap();
{
let ic = sortdb.index_conn();
let tip = SortitionDB::get_canonical_burn_chain_tip(&ic).unwrap();
node.chainstate
.preprocess_stacks_epoch(&ic, &tip, block, microblocks)
.unwrap();
}
self.coord.handle_new_stacks_block().unwrap();
let pox_id = {
let ic = sortdb.index_conn();
let tip_sort_id = SortitionDB::get_canonical_sortition_tip(sortdb.conn()).unwrap();
let sortdb_reader = SortitionHandleConn::open_reader(&ic, &tip_sort_id).unwrap();
sortdb_reader.get_pox_id().unwrap()
};
test_debug!(
"\n\n{:?}: after stacks block {:?}, tip PoX ID is {:?}\n\n",
&self.to_neighbor().addr,
&block.block_hash(),
&pox_id
);
self.sortdb = Some(sortdb);
self.stacks_node = Some(node);
}
fn inner_process_stacks_epoch_at_tip(
&mut self,
sortdb: &SortitionDB,
node: &mut TestStacksNode,
block: &StacksBlock,
microblocks: &Vec<StacksMicroblock>,
) -> Result<(), coordinator_error> {
{
let ic = sortdb.index_conn();
let tip = SortitionDB::get_canonical_burn_chain_tip(&ic)?;
node.chainstate
.preprocess_stacks_epoch(&ic, &tip, block, microblocks)?;
}
self.coord.handle_new_stacks_block()?;
let pox_id = {
let ic = sortdb.index_conn();
let tip_sort_id = SortitionDB::get_canonical_sortition_tip(sortdb.conn())?;
let sortdb_reader = SortitionHandleConn::open_reader(&ic, &tip_sort_id)?;
sortdb_reader.get_pox_id()?;
};
test_debug!(
"\n\n{:?}: after stacks block {:?}, tip PoX ID is {:?}\n\n",
&self.to_neighbor().addr,
&block.block_hash(),
&pox_id
);
Ok(())
}
pub fn process_stacks_epoch_at_tip_checked(
&mut self,
block: &StacksBlock,
microblocks: &Vec<StacksMicroblock>,
) -> Result<(), coordinator_error> {
let sortdb = self.sortdb.take().unwrap();
let mut node = self.stacks_node.take().unwrap();
let res =
self.inner_process_stacks_epoch_at_tip(&sortdb, &mut node, block, microblocks);
self.sortdb = Some(sortdb);
self.stacks_node = Some(node);
res
}
pub fn process_stacks_epoch(
&mut self,
block: &StacksBlock,
consensus_hash: &ConsensusHash,
microblocks: &Vec<StacksMicroblock>,
) -> () {
let sortdb = self.sortdb.take().unwrap();
let mut node = self.stacks_node.take().unwrap();
{
let ic = sortdb.index_conn();
Relayer::process_new_anchored_block(
&ic,
&mut node.chainstate,
consensus_hash,
block,
0,
)
.unwrap();
let block_hash = block.block_hash();
for mblock in microblocks.iter() {
node.chainstate
.preprocess_streamed_microblock(consensus_hash, &block_hash, mblock)
.unwrap();
}
}
self.coord.handle_new_stacks_block().unwrap();
let pox_id = {
let ic = sortdb.index_conn();
let tip_sort_id = SortitionDB::get_canonical_sortition_tip(sortdb.conn()).unwrap();
let sortdb_reader = SortitionHandleConn::open_reader(&ic, &tip_sort_id).unwrap();
sortdb_reader.get_pox_id().unwrap()
};
test_debug!(
"\n\n{:?}: after stacks block {:?}, tip PoX ID is {:?}\n\n",
&self.to_neighbor().addr,
&block.block_hash(),
&pox_id
);
self.sortdb = Some(sortdb);
self.stacks_node = Some(node);
}
pub fn add_empty_burnchain_block(&mut self) -> (u64, BurnchainHeaderHash, ConsensusHash) {
self.next_burnchain_block(vec![])
}
pub fn mempool(&mut self) -> &mut MemPoolDB {
self.mempool.as_mut().unwrap()
}
pub fn chainstate(&mut self) -> &mut StacksChainState {
&mut self.stacks_node.as_mut().unwrap().chainstate
}
pub fn sortdb(&mut self) -> &mut SortitionDB {
self.sortdb.as_mut().unwrap()
}
pub fn with_db_state<F, R>(&mut self, f: F) -> Result<R, net_error>
where
F: FnOnce(
&mut SortitionDB,
&mut StacksChainState,
&mut Relayer,
&mut MemPoolDB,
) -> Result<R, net_error>,
{
let mut sortdb = self.sortdb.take().unwrap();
let mut stacks_node = self.stacks_node.take().unwrap();
let mut mempool = self.mempool.take().unwrap();
let res = f(
&mut sortdb,
&mut stacks_node.chainstate,
&mut self.relayer,
&mut mempool,
);
self.stacks_node = Some(stacks_node);
self.sortdb = Some(sortdb);
self.mempool = Some(mempool);
res
}
pub fn with_mining_state<F, R>(&mut self, f: F) -> Result<R, net_error>
where
F: FnOnce(
&mut SortitionDB,
&mut TestMiner,
&mut TestMiner,
&mut TestStacksNode,
) -> Result<R, net_error>,
{
let mut stacks_node = self.stacks_node.take().unwrap();
let mut sortdb = self.sortdb.take().unwrap();
let res = f(
&mut sortdb,
&mut self.miner,
&mut self.config.spending_account,
&mut stacks_node,
);
self.sortdb = Some(sortdb);
self.stacks_node = Some(stacks_node);
res
}
pub fn with_network_state<F, R>(&mut self, f: F) -> Result<R, net_error>
where
F: FnOnce(
&mut SortitionDB,
&mut StacksChainState,
&mut PeerNetwork,
&mut Relayer,
&mut MemPoolDB,
) -> Result<R, net_error>,
{
let mut sortdb = self.sortdb.take().unwrap();
let mut stacks_node = self.stacks_node.take().unwrap();
let mut mempool = self.mempool.take().unwrap();
let res = f(
&mut sortdb,
&mut stacks_node.chainstate,
&mut self.network,
&mut self.relayer,
&mut mempool,
);
self.stacks_node = Some(stacks_node);
self.sortdb = Some(sortdb);
self.mempool = Some(mempool);
res
}
pub fn with_peer_state<F, R>(&mut self, f: F) -> Result<R, net_error>
where
F: FnOnce(
&mut TestPeer,
&mut SortitionDB,
&mut StacksChainState,
&mut MemPoolDB,
) -> Result<R, net_error>,
{
let mut sortdb = self.sortdb.take().unwrap();
let mut stacks_node = self.stacks_node.take().unwrap();
let mut mempool = self.mempool.take().unwrap();
let res = f(self, &mut sortdb, &mut stacks_node.chainstate, &mut mempool);
self.stacks_node = Some(stacks_node);
self.sortdb = Some(sortdb);
self.mempool = Some(mempool);
res
}
/// Make a tenure with the given transactions. Creates a coinbase tx with the given nonce, and then increments
/// the provided reference.
pub fn tenure_with_txs(
&mut self,
txs: &[StacksTransaction],
coinbase_nonce: &mut usize,
) -> StacksBlockId {
let microblock_privkey = StacksPrivateKey::new();
let microblock_pubkeyhash =
Hash160::from_node_public_key(&StacksPublicKey::from_private(&microblock_privkey));
let tip =
SortitionDB::get_canonical_burn_chain_tip(&self.sortdb.as_ref().unwrap().conn())
.unwrap();
let (burn_ops, stacks_block, microblocks) = self.make_tenure(
|ref mut miner,
ref mut sortdb,
ref mut chainstate,
vrf_proof,
ref parent_opt,
ref parent_microblock_header_opt| {
let parent_tip = get_parent_tip(parent_opt, chainstate, sortdb);
let coinbase_tx = make_coinbase(miner, *coinbase_nonce);
let mut block_txs = vec![coinbase_tx];
block_txs.extend_from_slice(txs);
let block_builder = StacksBlockBuilder::make_regtest_block_builder(
&parent_tip,
vrf_proof,
tip.total_burn,
microblock_pubkeyhash,
)
.unwrap();
let (anchored_block, _size, _cost) =
StacksBlockBuilder::make_anchored_block_from_txs(
block_builder,
chainstate,
&sortdb.index_conn(),
block_txs,
)
.unwrap();
(anchored_block, vec![])
},
);
let (_, _, consensus_hash) = self.next_burnchain_block(burn_ops);
self.process_stacks_epoch_at_tip(&stacks_block, &microblocks);
*coinbase_nonce += 1;
StacksBlockId::new(&consensus_hash, &stacks_block.block_hash())
}
// Make a tenure
pub fn make_tenure<F>(
&mut self,
mut tenure_builder: F,
) -> (
Vec<BlockstackOperationType>,
StacksBlock,
Vec<StacksMicroblock>,
)
where
F: FnMut(
&mut TestMiner,
&mut SortitionDB,
&mut StacksChainState,
VRFProof,
Option<&StacksBlock>,
Option<&StacksMicroblockHeader>,
) -> (StacksBlock, Vec<StacksMicroblock>),
{
let mut sortdb = self.sortdb.take().unwrap();
let mut burn_block = {
let sn = SortitionDB::get_canonical_burn_chain_tip(sortdb.conn()).unwrap();
TestBurnchainBlock::new(&sn, 0)
};
let last_sortition_block =
SortitionDB::get_canonical_burn_chain_tip(sortdb.conn()).unwrap(); // no forks here
let mut stacks_node = self.stacks_node.take().unwrap();
let parent_block_opt = stacks_node.get_last_anchored_block(&self.miner);
let parent_microblock_header_opt =
get_last_microblock_header(&stacks_node, &self.miner, parent_block_opt.as_ref());
let last_key = stacks_node.get_last_key(&self.miner);
let network_id = self.config.network_id;
let chainstate_path = self.chainstate_path.clone();
let burn_block_height = burn_block.block_height;
let proof = self
.miner
.make_proof(
&last_key.public_key,
&burn_block.parent_snapshot.sortition_hash,
)
.expect(&format!(
"FATAL: no private key for {}",
last_key.public_key.to_hex()
));
let (stacks_block, microblocks) = tenure_builder(
&mut self.miner,
&mut sortdb,
&mut stacks_node.chainstate,
proof,
parent_block_opt.as_ref(),
parent_microblock_header_opt.as_ref(),
);
let mut block_commit_op = stacks_node.make_tenure_commitment(
&mut sortdb,
&mut burn_block,
&mut self.miner,
&stacks_block,
&microblocks,
1000,
&last_key,
Some(&last_sortition_block),
);
let leader_key_op = stacks_node.add_key_register(&mut burn_block, &mut self.miner);
// patch in reward set info
match get_next_recipients(
&last_sortition_block,
&mut stacks_node.chainstate,
&mut sortdb,
&self.config.burnchain,
&OnChainRewardSetProvider(),
) {
Ok(recipients) => {
block_commit_op.commit_outs = match recipients {
Some(info) => {
let mut recipients = info
.recipients
.into_iter()
.map(|x| x.0)
.collect::<Vec<StacksAddress>>();
if recipients.len() == 1 {
recipients.push(StacksAddress::burn_address(false));
}
recipients
}
None => vec![],
};
test_debug!(
"Block commit at height {} has {} recipients: {:?}",
block_commit_op.block_height,
block_commit_op.commit_outs.len(),
&block_commit_op.commit_outs
);
}
Err(e) => {
panic!("Failure fetching recipient set: {:?}", e);
}
};
self.stacks_node = Some(stacks_node);
self.sortdb = Some(sortdb);
(
vec![
BlockstackOperationType::LeaderKeyRegister(leader_key_op),
BlockstackOperationType::LeaderBlockCommit(block_commit_op),
],
stacks_block,
microblocks,
)
}
// have this peer produce an anchored block and microblock tail using its internal miner.
pub fn make_default_tenure(
&mut self,
) -> (
Vec<BlockstackOperationType>,
StacksBlock,
Vec<StacksMicroblock>,
) {
let mut sortdb = self.sortdb.take().unwrap();
let mut burn_block = {
let sn = SortitionDB::get_canonical_burn_chain_tip(sortdb.conn()).unwrap();
TestBurnchainBlock::new(&sn, 0)
};
let mut stacks_node = self.stacks_node.take().unwrap();
let parent_block_opt = stacks_node.get_last_anchored_block(&self.miner);
let parent_microblock_header_opt =
get_last_microblock_header(&stacks_node, &self.miner, parent_block_opt.as_ref());
let last_key = stacks_node.get_last_key(&self.miner);
let network_id = self.config.network_id;
let chainstate_path = self.chainstate_path.clone();
let burn_block_height = burn_block.block_height;
let (stacks_block, microblocks, block_commit_op) = stacks_node.mine_stacks_block(
&mut sortdb,
&mut self.miner,
&mut burn_block,
&last_key,
parent_block_opt.as_ref(),
1000,
|mut builder, ref mut miner, ref sortdb| {
let (mut miner_chainstate, _) =
StacksChainState::open(false, network_id, &chainstate_path, None).unwrap();
let sort_iconn = sortdb.index_conn();
let mut miner_epoch_info = builder
.pre_epoch_begin(&mut miner_chainstate, &sort_iconn)
.unwrap();
let mut epoch = builder
.epoch_begin(&sort_iconn, &mut miner_epoch_info)
.unwrap()
.0;
let (stacks_block, microblocks) =
mine_smart_contract_block_contract_call_microblock(
&mut epoch,
&mut builder,
miner,
burn_block_height as usize,
parent_microblock_header_opt.as_ref(),
);
builder.epoch_finish(epoch);
(stacks_block, microblocks)
},
);
let leader_key_op = stacks_node.add_key_register(&mut burn_block, &mut self.miner);
self.stacks_node = Some(stacks_node);
self.sortdb = Some(sortdb);
(
vec![
BlockstackOperationType::LeaderKeyRegister(leader_key_op),
BlockstackOperationType::LeaderBlockCommit(block_commit_op),
],
stacks_block,
microblocks,
)
}
pub fn to_neighbor(&self) -> Neighbor {
self.config.to_neighbor()
}
pub fn to_peer_host(&self) -> PeerHost {
self.config.to_peer_host()
}
pub fn get_public_key(&self) -> Secp256k1PublicKey {
let local_peer = PeerDB::get_local_peer(&self.network.peerdb.conn()).unwrap();
Secp256k1PublicKey::from_private(&local_peer.private_key)
}
pub fn get_peerdb_conn(&self) -> &DBConn {
self.network.peerdb.conn()
}
pub fn get_burnchain_view(&mut self) -> Result<BurnchainView, db_error> {
let sortdb = self.sortdb.take().unwrap();
let view_res = {
let chaintip = SortitionDB::get_canonical_burn_chain_tip(&sortdb.conn()).unwrap();
SortitionDB::get_burnchain_view(&sortdb.conn(), &self.config.burnchain, &chaintip)
};
self.sortdb = Some(sortdb);
view_res
}
pub fn dump_frontier(&self) -> () {
let conn = self.network.peerdb.conn();
let peers = PeerDB::get_all_peers(conn).unwrap();
debug!("--- BEGIN ALL PEERS ({}) ---", peers.len());
debug!("{:#?}", &peers);
debug!("--- END ALL PEERS ({}) -----", peers.len());
}
}
pub fn to_addr(sk: &StacksPrivateKey) -> StacksAddress {
StacksAddress::from_public_keys(
C32_ADDRESS_VERSION_TESTNET_SINGLESIG,
&AddressHashMode::SerializeP2PKH,
1,
&vec![StacksPublicKey::from_private(sk)],
)
.unwrap()
}
}