Skip to content

Commit

Permalink
feat: add RPC endpoint /v2/traits/<contract-info>/<trait-info> to det…
Browse files Browse the repository at this point in the history
…ermine if a trait is implemented in a contract
  • Loading branch information
pavitthrap committed Mar 4, 2021
1 parent 7b61252 commit ce26d03
Show file tree
Hide file tree
Showing 4 changed files with 268 additions and 5 deletions.
94 changes: 94 additions & 0 deletions src/net/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ use std::time::SystemTime;
use time;

use chainstate::burn::ConsensusHash;
use net::Error::ClarityError;
use vm::types::{StandardPrincipalData, TraitIdentifier};

lazy_static! {
static ref PATH_GETINFO: Regex = Regex::new(r#"^/v2/info$"#).unwrap();
Expand Down Expand Up @@ -129,6 +131,11 @@ lazy_static! {
*STANDARD_PRINCIPAL_REGEX, *CONTRACT_NAME_REGEX
))
.unwrap();
static ref PATH_GET_IS_TRAIT_IMPLEMENTED: Regex = Regex::new(&format!(
"^/v2/traits/(?P<address>{})/(?P<contract>{})/(?P<traitName>{})/(?P<traitContractAddr>{})/(?P<traitContractName>{})$",
*STANDARD_PRINCIPAL_REGEX, *CONTRACT_NAME_REGEX, *CLARITY_NAME_REGEX, *STANDARD_PRINCIPAL_REGEX, *CONTRACT_NAME_REGEX
))
.unwrap();
static ref PATH_GET_CONTRACT_ABI: Regex = Regex::new(&format!(
"^/v2/contracts/interface/(?P<address>{})/(?P<contract>{})$",
*STANDARD_PRINCIPAL_REGEX, *CONTRACT_NAME_REGEX
Expand Down Expand Up @@ -1503,6 +1510,11 @@ impl HttpRequestType {
&PATH_GET_CONTRACT_SRC,
&HttpRequestType::parse_get_contract_source,
),
(
"GET",
&PATH_GET_IS_TRAIT_IMPLEMENTED,
&HttpRequestType::parse_get_is_trait_implemented,
),
(
"GET",
&PATH_GET_CONTRACT_ABI,
Expand Down Expand Up @@ -1870,6 +1882,45 @@ impl HttpRequestType {
)
}

fn parse_get_is_trait_implemented<R: Read>(
_protocol: &mut StacksHttp,
preamble: &HttpRequestPreamble,
captures: &Captures,
query: Option<&str>,
_fd: &mut R,
) -> Result<HttpRequestType, net_error> {
let tip = HttpRequestType::get_chain_tip_query(query);
if preamble.get_content_length() != 0 {
return Err(net_error::DeserializeError(
"Invalid Http request: expected 0-length body".to_string(),
));
}

let contract_addr = StacksAddress::from_string(&captures["address"]).ok_or_else(|| {
net_error::DeserializeError("Failed to parse contract address".into())
})?;
let contract_name = ContractName::try_from(captures["contract"].to_string())
.map_err(|_e| net_error::DeserializeError("Failed to parse contract name".into()))?;
let trait_name = ClarityName::try_from(captures["traitName"].to_string())
.map_err(|_e| net_error::DeserializeError("Failed to parse trait name".into()))?;
let trait_contract_addr = StacksAddress::from_string(&captures["traitContractAddr"])
.ok_or_else(|| net_error::DeserializeError("Failed to parse contract address".into()))?
.into();
let trait_contract_name = ContractName::try_from(captures["traitContractName"].to_string())
.map_err(|_e| {
net_error::DeserializeError("Failed to parse trait contract name".into())
})?;
let trait_id = TraitIdentifier::new(trait_contract_addr, trait_contract_name, trait_name);

Ok(HttpRequestType::GetIsTraitImplemented(
HttpRequestMetadata::from_preamble(preamble),
contract_addr,
contract_name,
trait_id,
tip,
))
}

fn parse_getblock<R: Read>(
_protocol: &mut StacksHttp,
preamble: &HttpRequestPreamble,
Expand Down Expand Up @@ -2349,6 +2400,7 @@ impl HttpRequestType {
HttpRequestType::GetTransferCost(ref md) => md,
HttpRequestType::GetContractABI(ref md, ..) => md,
HttpRequestType::GetContractSrc(ref md, ..) => md,
HttpRequestType::GetIsTraitImplemented(ref md, ..) => md,
HttpRequestType::CallReadOnlyFunction(ref md, ..) => md,
HttpRequestType::OptionsPreflight(ref md, ..) => md,
HttpRequestType::GetAttachmentsInv(ref md, ..) => md,
Expand All @@ -2375,6 +2427,7 @@ impl HttpRequestType {
HttpRequestType::GetTransferCost(ref mut md) => md,
HttpRequestType::GetContractABI(ref mut md, ..) => md,
HttpRequestType::GetContractSrc(ref mut md, ..) => md,
HttpRequestType::GetIsTraitImplemented(ref mut md, ..) => md,
HttpRequestType::CallReadOnlyFunction(ref mut md, ..) => md,
HttpRequestType::OptionsPreflight(ref mut md, ..) => md,
HttpRequestType::GetAttachmentsInv(ref mut md, ..) => md,
Expand Down Expand Up @@ -2463,6 +2516,21 @@ impl HttpRequestType {
contract_name.as_str(),
HttpRequestType::make_query_string(tip_opt.as_ref(), *with_proof)
),
HttpRequestType::GetIsTraitImplemented(
_,
contract_addr,
contract_name,
trait_id,
tip_opt,
) => format!(
"/v2/traits/{}/{}/{}/{}/{}{}",
contract_addr,
contract_name.as_str(),
trait_id.name.to_string(),
StacksAddress::from(trait_id.clone().contract_identifier.issuer),
trait_id.contract_identifier.name.as_str(),
HttpRequestType::make_query_string(tip_opt.as_ref(), true)
),
HttpRequestType::CallReadOnlyFunction(
_,
contract_addr,
Expand Down Expand Up @@ -2941,6 +3009,10 @@ impl HttpResponseType {
&PATH_GET_CONTRACT_SRC,
&HttpResponseType::parse_get_contract_src,
),
(
&PATH_GET_IS_TRAIT_IMPLEMENTED,
&HttpResponseType::parse_get_is_trait_implemented,
),
(
&PATH_GET_CONTRACT_ABI,
&HttpResponseType::parse_get_contract_abi,
Expand Down Expand Up @@ -3125,6 +3197,21 @@ impl HttpResponseType {
))
}

fn parse_get_is_trait_implemented<R: Read>(
_protocol: &mut StacksHttp,
request_version: HttpVersion,
preamble: &HttpResponsePreamble,
fd: &mut R,
len_hint: Option<usize>,
) -> Result<HttpResponseType, net_error> {
let src_data =
HttpResponseType::parse_json(preamble, fd, len_hint, MAX_MESSAGE_LEN as u64)?;
Ok(HttpResponseType::GetIsTraitImplemented(
HttpResponseMetadata::from_preamble(request_version, preamble),
src_data,
))
}

fn parse_get_contract_abi<R: Read>(
_protocol: &mut StacksHttp,
request_version: HttpVersion,
Expand Down Expand Up @@ -3362,6 +3449,7 @@ impl HttpResponseType {
HttpResponseType::GetAccount(ref md, _) => md,
HttpResponseType::GetContractABI(ref md, _) => md,
HttpResponseType::GetContractSrc(ref md, _) => md,
HttpResponseType::GetIsTraitImplemented(ref md, _) => md,
HttpResponseType::CallReadOnlyFunction(ref md, _) => md,
HttpResponseType::UnconfirmedTransaction(ref md, _) => md,
HttpResponseType::GetAttachment(ref md, _) => md,
Expand Down Expand Up @@ -3454,6 +3542,10 @@ impl HttpResponseType {
HttpResponsePreamble::ok_JSON_from_md(fd, md)?;
HttpResponseType::send_json(protocol, md, fd, data)?;
}
HttpResponseType::GetIsTraitImplemented(ref md, ref data) => {
HttpResponsePreamble::ok_JSON_from_md(fd, md)?;
HttpResponseType::send_json(protocol, md, fd, data)?;
}
HttpResponseType::TokenTransferCost(ref md, ref cost) => {
HttpResponsePreamble::ok_JSON_from_md(fd, md)?;
HttpResponseType::send_json(protocol, md, fd, cost)?;
Expand Down Expand Up @@ -3692,6 +3784,7 @@ impl MessageSequence for StacksHttpMessage {
HttpRequestType::GetTransferCost(_) => "HTTP(GetTransferCost)",
HttpRequestType::GetContractABI(..) => "HTTP(GetContractABI)",
HttpRequestType::GetContractSrc(..) => "HTTP(GetContractSrc)",
HttpRequestType::GetIsTraitImplemented(..) => "HTTP(GetIsTraitImplemented)",
HttpRequestType::CallReadOnlyFunction(..) => "HTTP(CallReadOnlyFunction)",
HttpRequestType::GetAttachment(..) => "HTTP(GetAttachment)",
HttpRequestType::GetAttachmentsInv(..) => "HTTP(GetAttachmentsInv)",
Expand All @@ -3704,6 +3797,7 @@ impl MessageSequence for StacksHttpMessage {
HttpResponseType::GetAccount(_, _) => "HTTP(GetAccount)",
HttpResponseType::GetContractABI(..) => "HTTP(GetContractABI)",
HttpResponseType::GetContractSrc(..) => "HTTP(GetContractSrc)",
HttpResponseType::GetIsTraitImplemented(..) => "HTTP(GetIsTraitImplemented)",
HttpResponseType::CallReadOnlyFunction(..) => "HTTP(CallReadOnlyFunction)",
HttpResponseType::GetAttachment(_, _) => "HTTP(GetAttachment)",
HttpResponseType::GetAttachmentsInv(_, _) => "HTTP(GetAttachmentsInv)",
Expand Down
14 changes: 14 additions & 0 deletions src/net/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1070,6 +1070,11 @@ pub struct ContractSrcResponse {
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,
Expand Down Expand Up @@ -1259,6 +1264,13 @@ pub enum HttpRequestType {
OptionsPreflight(HttpRequestMetadata, String),
GetAttachment(HttpRequestMetadata, Hash160),
GetAttachmentsInv(HttpRequestMetadata, Option<StacksBlockId>, HashSet<u32>),
GetIsTraitImplemented(
HttpRequestMetadata,
StacksAddress,
ContractName,
TraitIdentifier,
Option<StacksBlockId>,
),
/// catch-all for any errors we should surface from parsing
ClientError(HttpRequestMetadata, ClientError),
}
Expand Down Expand Up @@ -1349,6 +1361,7 @@ pub enum HttpResponseType {
GetAccount(HttpResponseMetadata, AccountEntryResponse),
GetContractABI(HttpResponseMetadata, ContractInterface),
GetContractSrc(HttpResponseMetadata, ContractSrcResponse),
GetIsTraitImplemented(HttpResponseMetadata, GetIsTraitImplementedResponse),
UnconfirmedTransaction(HttpResponseMetadata, UnconfirmedTransactionResponse),
GetAttachment(HttpResponseMetadata, GetAttachmentResponse),
GetAttachmentsInv(HttpResponseMetadata, GetAttachmentsInvResponse),
Expand Down Expand Up @@ -1479,6 +1492,7 @@ pub trait ProtocolFamily {
pub struct StacksP2P {}

pub use self::http::StacksHttp;
use vm::types::TraitIdentifier;

// an array in our protocol can't exceed this many items
pub const ARRAY_MAX_LEN: u32 = u32::max_value();
Expand Down
84 changes: 83 additions & 1 deletion src/net/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ use net::http::*;
use net::p2p::PeerMap;
use net::p2p::PeerNetwork;
use net::relay::Relayer;
use net::BlocksData;
use net::ClientError;
use net::Error as net_error;
use net::HttpRequestMetadata;
Expand All @@ -59,6 +58,7 @@ use net::{
AccountEntryResponse, AttachmentPage, CallReadOnlyResponse, ContractSrcResponse,
GetAttachmentResponse, GetAttachmentsInvResponse, MapEntryResponse,
};
use net::{BlocksData, GetIsTraitImplementedResponse};
use net::{RPCNeighbor, RPCNeighborsInfo};
use net::{RPCPeerInfoData, RPCPoxInfoData};
use std::collections::HashMap;
Expand Down Expand Up @@ -102,8 +102,10 @@ use vm::{
ClarityName, ContractName, SymbolicExpression, Value,
};

use chainstate::stacks::db::blocks::CheckError;
use rand::prelude::*;
use rand::thread_rng;
use vm::types::TraitIdentifier;

pub const STREAM_CHUNK_SIZE: u64 = 4096;

Expand Down Expand Up @@ -1240,6 +1242,57 @@ impl ConversationHttp {
response.send(http, fd).map(|_| ())
}

/// Handle a GET to fetch whether or not a contract implements a certain trait
fn handle_get_is_trait_implemented<W: Write>(
http: &mut StacksHttp,
fd: &mut W,
req: &HttpRequestType,
sortdb: &SortitionDB,
chainstate: &mut StacksChainState,
tip: &StacksBlockId,
contract_addr: &StacksAddress,
contract_name: &ContractName,
trait_id: &TraitIdentifier,
) -> Result<(), net_error> {
let response_metadata = HttpResponseMetadata::from(req);
let contract_identifier =
QualifiedContractIdentifier::new(contract_addr.clone().into(), contract_name.clone());

let response =
match chainstate.maybe_read_only_clarity_tx(&sortdb.index_conn(), tip, |clarity_tx| {
clarity_tx.with_clarity_db_readonly(|db| {
let mut analysis = db.load_contract_analysis(&contract_identifier)?;
if analysis.implemented_traits.contains(trait_id) {
Some(GetIsTraitImplementedResponse {
is_implemented: true,
})
} else {
let trait_name = trait_id.name.to_string();
let trait_definition = analysis.get_defined_trait(&trait_name)?;
match analysis.check_trait_compliance(trait_id, trait_definition) {
Err(_) => None,
Ok(_) => Some(GetIsTraitImplementedResponse {
is_implemented: true,
}),
}
}
})
}) {
Ok(Some(Some(data))) => {
HttpResponseType::GetIsTraitImplemented(response_metadata, data)
}
Ok(Some(None)) => HttpResponseType::NotFound(
response_metadata,
"No contract analysis found or trait definition not found".into(),
),
Ok(None) | Err(_) => {
HttpResponseType::NotFound(response_metadata, "Chain tip not found".into())
}
};

response.send(http, fd).map(|_| ())
}

/// Handle a GET to fetch a contract's analysis data, given the chain tip. Note that this isn't
/// something that's anchored to the blockchain, and can be different across different versions
/// of Stacks -- callers must trust the Stacks node to return correct analysis data.
Expand Down Expand Up @@ -2105,6 +2158,35 @@ impl ConversationHttp {
.map(|_| ())?;
None
}
HttpRequestType::GetIsTraitImplemented(
ref _md,
ref contract_addr,
ref contract_name,
ref trait_id,
ref tip_opt,
) => {
if let Some(tip) = ConversationHttp::handle_load_stacks_chain_tip(
&mut self.connection.protocol,
&mut reply,
&req,
tip_opt.as_ref(),
sortdb,
chainstate,
)? {
ConversationHttp::handle_get_is_trait_implemented(
&mut self.connection.protocol,
&mut reply,
&req,
sortdb,
chainstate,
&tip,
contract_addr,
contract_name,
trait_id,
)?;
}
None
}
HttpRequestType::ClientError(ref _md, ref err) => {
let response_metadata = HttpResponseMetadata::from(&req);
let response = match err {
Expand Down
Loading

0 comments on commit ce26d03

Please sign in to comment.