Skip to content

Commit

Permalink
feat(rpc): add protocol config rpc endpoint (#3919)
Browse files Browse the repository at this point in the history
Add `EXPERIMENTAL_protocol_config` rpc endpoint that returns the protocol config given some block, since protocol config can change after genesis due to protocol upgrades. This will allow users to get the up-to-date protocol-level configs. Fixes #3918.

Test plan
----------
`test_protocol_config_rpc`
  • Loading branch information
bowenwang1996 committed Feb 18, 2021
1 parent 4ea58d9 commit 0aa0823
Show file tree
Hide file tree
Showing 17 changed files with 347 additions and 13 deletions.
5 changes: 4 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions chain/chain/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ use crate::types::{ApplyTransactionResult, BlockHeaderInfo, ChainGenesis};
#[cfg(feature = "protocol_feature_block_header_v3")]
use crate::Doomslug;
use crate::{BlockHeader, DoomslugThresholdMode, RuntimeAdapter};
use near_chain_configs::ProtocolConfig;
#[cfg(feature = "protocol_feature_block_header_v3")]
use near_primitives::block_header::{Approval, ApprovalInner};

Expand Down Expand Up @@ -1014,6 +1015,10 @@ impl RuntimeAdapter for KeyValueRuntime {
// See https://github.com/ethereum-lists/chains/blob/master/_data/chains/1313161555.json
1313161555
}

fn get_protocol_config(&self, _epoch_id: &EpochId) -> Result<ProtocolConfig, Error> {
unreachable!("get_protocol_config should not be called in KeyValueRuntime");
}
}

pub fn setup() -> (Chain, Arc<KeyValueRuntime>, Arc<InMemoryValidatorSigner>) {
Expand Down
4 changes: 3 additions & 1 deletion chain/chain/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use chrono::{DateTime, Utc};
use num_rational::Rational;
use serde::Serialize;

use near_chain_configs::GenesisConfig;
use near_chain_configs::{GenesisConfig, ProtocolConfig};
use near_chain_primitives::Error;
use near_crypto::Signature;
use near_pool::types::PoolIterator;
Expand Down Expand Up @@ -604,6 +604,8 @@ pub trait RuntimeAdapter: Send + Sync {
#[cfg(feature = "protocol_feature_evm")]
fn evm_chain_id(&self) -> u64;

fn get_protocol_config(&self, epoch_id: &EpochId) -> Result<ProtocolConfig, Error>;

/// Build receipts hashes.
// Due to borsh serialization constraints, we have to use `&Vec<Receipt>` instead of `&[Receipt]`
// here.
Expand Down
2 changes: 2 additions & 0 deletions chain/client-primitives/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ strum = { version = "0.20", features = ["derive"] }
thiserror = "1.0"

near-chain-primitives = { path = "../chain-primitives" }
near-chain-configs = { path = "../../core/chain-configs" }

near-chunks = { path = "../chunks" }
near-network = { path = "../network" }
near-primitives = { path = "../../core/primitives" }
Expand Down
31 changes: 31 additions & 0 deletions chain/client-primitives/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use actix::Message;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};

use near_chain_configs::ProtocolConfigView;
use near_network::types::{AccountOrPeerIdOrHash, KnownProducer};
use near_network::PeerInfo;
use near_primitives::errors::InvalidTxError;
Expand Down Expand Up @@ -449,3 +450,33 @@ impl From<near_chain_primitives::Error> for GetReceiptError {
impl Message for GetReceipt {
type Result = Result<Option<ReceiptView>, GetReceiptError>;
}

pub struct GetProtocolConfig(pub BlockReference);

impl Message for GetProtocolConfig {
type Result = Result<ProtocolConfigView, GetProtocolConfigError>;
}

#[derive(thiserror::Error, Debug)]
pub enum GetProtocolConfigError {
#[error("IO Error: {0}")]
IOError(String),
#[error("Block has never been observed: {0}")]
UnknownBlock(String),
// NOTE: Currently, the underlying errors are too broad, and while we tried to handle
// expected cases, we cannot statically guarantee that no other errors will be returned
// in the future.
// TODO #3851: Remove this variant once we can exhaustively match all the underlying errors
#[error("It is a bug if you receive this error type, please, report this incident: https://github.com/near/nearcore/issues/new/choose. Details: {0}")]
Unreachable(String),
}

impl From<near_chain_primitives::Error> for GetProtocolConfigError {
fn from(error: near_chain_primitives::Error) -> Self {
match error.kind() {
near_chain_primitives::ErrorKind::IOErr(s) => Self::IOError(s),
near_chain_primitives::ErrorKind::DBNotFoundErr(s) => Self::UnknownBlock(s),
_ => Self::Unreachable(error.to_string()),
}
}
}
6 changes: 3 additions & 3 deletions chain/client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ extern crate lazy_static;
pub use near_client_primitives::types::{
Error, GetBlock, GetBlockProof, GetBlockProofResponse, GetBlockWithMerkleTree, GetChunk,
GetExecutionOutcome, GetExecutionOutcomeResponse, GetExecutionOutcomesForBlock, GetGasPrice,
GetNetworkInfo, GetNextLightClientBlock, GetReceipt, GetStateChanges, GetStateChangesInBlock,
GetStateChangesWithCauseInBlock, GetValidatorInfo, GetValidatorOrdered, Query, Status,
StatusResponse, SyncStatus, TxStatus, TxStatusError,
GetNetworkInfo, GetNextLightClientBlock, GetProtocolConfig, GetReceipt, GetStateChanges,
GetStateChangesInBlock, GetStateChangesWithCauseInBlock, GetValidatorInfo, GetValidatorOrdered,
Query, Status, StatusResponse, SyncStatus, TxStatus, TxStatusError,
};

pub use crate::client::Client;
Expand Down
41 changes: 38 additions & 3 deletions chain/client/src/view_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use near_chain::{
get_epoch_block_producers_view, Chain, ChainGenesis, ChainStoreAccess, DoomslugThresholdMode,
ErrorKind, RuntimeAdapter,
};
use near_chain_configs::ClientConfig;
use near_chain_configs::{ClientConfig, ProtocolConfigView};
#[cfg(feature = "adversarial")]
use near_network::types::NetworkAdversarialMessage;
use near_network::types::{
Expand Down Expand Up @@ -49,8 +49,9 @@ use crate::{
};
use near_client_primitives::types::{
Error, GetBlock, GetBlockError, GetBlockProof, GetBlockProofResponse, GetBlockWithMerkleTree,
GetChunkError, GetExecutionOutcome, GetExecutionOutcomesForBlock, GetGasPrice, GetReceipt,
GetReceiptError, GetStateChangesWithCauseInBlock, Query, TxStatus, TxStatusError,
GetChunkError, GetExecutionOutcome, GetExecutionOutcomesForBlock, GetGasPrice,
GetProtocolConfig, GetProtocolConfigError, GetReceipt, GetReceiptError,
GetStateChangesWithCauseInBlock, Query, TxStatus, TxStatusError,
};
use near_performance_metrics_macros::perf;
use near_performance_metrics_macros::perf_with_debug;
Expand Down Expand Up @@ -804,6 +805,40 @@ impl Handler<GetBlockProof> for ViewClientActor {
}
}

impl Handler<GetProtocolConfig> for ViewClientActor {
type Result = Result<ProtocolConfigView, GetProtocolConfigError>;

#[perf]
fn handle(&mut self, msg: GetProtocolConfig, _: &mut Self::Context) -> Self::Result {
let block_header = match msg.0 {
BlockReference::Finality(finality) => {
let block_hash = self.get_block_hash_by_finality(&finality)?;
self.chain.get_block_header(&block_hash).map(Clone::clone)
}
BlockReference::BlockId(BlockId::Height(height)) => {
self.chain.get_header_by_height(height).map(Clone::clone)
}
BlockReference::BlockId(BlockId::Hash(hash)) => {
self.chain.get_block_header(&hash).map(Clone::clone)
}
BlockReference::SyncCheckpoint(sync_checkpoint) => {
if let Some(block_hash) =
self.get_block_hash_by_sync_checkpoint(&sync_checkpoint)?
{
self.chain.get_block_header(&block_hash).map(Clone::clone)
} else {
return Err(GetProtocolConfigError::UnknownBlock(format!(
"{:?}",
sync_checkpoint
)));
}
}
}?;
let config = self.runtime_adapter.get_protocol_config(block_header.epoch_id())?;
Ok(config.into())
}
}

impl Handler<NetworkViewClientMessages> for ViewClientActor {
type Result = NetworkViewClientResponses;

Expand Down
1 change: 1 addition & 0 deletions chain/jsonrpc-primitives/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ uuid = { version = "~0.8", features = ["v4"] }
near-client-primitives = { path = "../client-primitives" }
near-primitives = { path = "../../core/primitives" }
near-metrics = { path = "../../core/metrics" }
near-chain-configs = { path = "../../core/chain-configs" }
78 changes: 78 additions & 0 deletions chain/jsonrpc-primitives/src/types/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
use crate::types::blocks::BlockReference;
use serde::{Deserialize, Serialize};
use serde_json::Value;

#[derive(Serialize, Deserialize)]
pub struct RpcProtocolConfigRequest {
#[serde(flatten)]
pub block_reference: BlockReference,
}

impl RpcProtocolConfigRequest {
pub fn parse(
value: Option<Value>,
) -> Result<RpcProtocolConfigRequest, crate::errors::RpcParseError> {
crate::utils::parse_params::<BlockReference>(value)
.map(|block_reference| RpcProtocolConfigRequest { block_reference })
}
}

#[derive(Serialize, Deserialize)]
pub struct RpcProtocolConfigResponse {
#[serde(flatten)]
pub config_view: near_chain_configs::ProtocolConfigView,
}

#[derive(thiserror::Error, Debug)]
pub enum RpcProtocolConfigError {
#[error("Block has never been observed: {0}")]
UnknownBlock(String),
#[error("The node reached its limits. Try again later. More details: {0}")]
InternalError(String),
// NOTE: Currently, the underlying errors are too broad, and while we tried to handle
// expected cases, we cannot statically guarantee that no other errors will be returned
// in the future.
// TODO #3851: Remove this variant once we can exhaustively match all the underlying errors
#[error("It is a bug if you receive this error type, please, report this incident: https://github.com/near/nearcore/issues/new/choose. Details: {0}")]
Unreachable(String),
}

impl From<near_client_primitives::types::GetProtocolConfigError> for RpcProtocolConfigError {
fn from(error: near_client_primitives::types::GetProtocolConfigError) -> Self {
match error {
near_client_primitives::types::GetProtocolConfigError::UnknownBlock(s) => {
Self::UnknownBlock(s)
}
near_client_primitives::types::GetProtocolConfigError::IOError(s) => {
Self::InternalError(s)
}
near_client_primitives::types::GetProtocolConfigError::Unreachable(s) => {
near_metrics::inc_counter_vec(
&crate::metrics::RPC_UNREACHABLE_ERROR_COUNT,
&["RpcProtocolConfigError", &s],
);
Self::Unreachable(s)
}
}
}
}

impl From<actix::MailboxError> for RpcProtocolConfigError {
fn from(error: actix::MailboxError) -> Self {
Self::InternalError(error.to_string())
}
}

impl From<RpcProtocolConfigError> for crate::errors::RpcError {
fn from(error: RpcProtocolConfigError) -> Self {
let error_data = match error {
RpcProtocolConfigError::UnknownBlock(hash) => {
Some(Value::String(format!("Block Not Found: {}", hash)))
}
RpcProtocolConfigError::Unreachable(s) => Some(Value::String(s)),
RpcProtocolConfigError::InternalError(_) => Some(Value::String(error.to_string())),
};

Self::new(-32_000, "Server error".to_string(), error_data)
}
}
1 change: 1 addition & 0 deletions chain/jsonrpc-primitives/src/types/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod blocks;
pub mod chunks;
pub mod config;
pub mod receipts;
8 changes: 8 additions & 0 deletions chain/jsonrpc/client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,14 @@ impl JsonRpcClient {
) -> RpcRequest<near_jsonrpc_primitives::types::receipts::RpcReceiptResponse> {
call_method(&self.client, &self.server_addr, "EXPERIMENTAL_receipt", request)
}

#[allow(non_snake_case)]
pub fn EXPERIMENTAL_protocol_config(
&self,
request: near_jsonrpc_primitives::types::config::RpcProtocolConfigRequest,
) -> RpcRequest<near_jsonrpc_primitives::types::config::RpcProtocolConfigResponse> {
call_method(&self.client, &self.server_addr, "EXPERIMENTAL_protocol_config", request)
}
}

fn create_client() -> Client {
Expand Down
28 changes: 26 additions & 2 deletions chain/jsonrpc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ use tokio::time::{sleep, timeout};
use near_chain_configs::GenesisConfig;
use near_client::{
ClientActor, GetBlock, GetBlockProof, GetChunk, GetExecutionOutcome, GetGasPrice,
GetNetworkInfo, GetNextLightClientBlock, GetReceipt, GetStateChanges, GetStateChangesInBlock,
GetValidatorInfo, GetValidatorOrdered, Query, Status, TxStatus, TxStatusError, ViewClientActor,
GetNetworkInfo, GetNextLightClientBlock, GetProtocolConfig, GetReceipt, GetStateChanges,
GetStateChangesInBlock, GetValidatorInfo, GetValidatorOrdered, Query, Status, TxStatus,
TxStatusError, ViewClientActor,
};
pub use near_jsonrpc_client as client;
use near_jsonrpc_primitives::errors::RpcError;
Expand All @@ -29,6 +30,7 @@ use near_jsonrpc_primitives::rpc::{
RpcStateChangesInBlockResponse, RpcStateChangesRequest, RpcStateChangesResponse,
RpcValidatorsOrderedRequest, TransactionInfo,
};
use near_jsonrpc_primitives::types::config::RpcProtocolConfigResponse;
use near_metrics::{Encoder, TextEncoder};
#[cfg(feature = "adversarial")]
use near_network::types::{NetworkAdversarialMessage, NetworkViewClientMessages};
Expand Down Expand Up @@ -248,6 +250,14 @@ impl JsonRpcHandler {
"EXPERIMENTAL_changes_in_block" => self.changes_in_block(request.params).await,
"EXPERIMENTAL_check_tx" => self.check_tx(request.params).await,
"EXPERIMENTAL_genesis_config" => self.genesis_config().await,
"EXPERIMENTAL_protocol_config" => {
let rpc_protocol_config_request =
near_jsonrpc_primitives::types::config::RpcProtocolConfigRequest::parse(
request.params,
)?;
let config = self.protocol_config(rpc_protocol_config_request).await?;
serde_json::to_value(config).map_err(|err| RpcError::parse_error(err.to_string()))
}
"EXPERIMENTAL_light_client_proof" => {
self.light_client_execution_outcome_proof(request.params).await
}
Expand Down Expand Up @@ -533,6 +543,20 @@ impl JsonRpcHandler {
jsonify(Ok(Ok(&self.genesis_config)))
}

pub async fn protocol_config(
&self,
request_data: near_jsonrpc_primitives::types::config::RpcProtocolConfigRequest,
) -> Result<
near_jsonrpc_primitives::types::config::RpcProtocolConfigResponse,
near_jsonrpc_primitives::types::config::RpcProtocolConfigError,
> {
let config_view = self
.view_client_addr
.send(GetProtocolConfig(request_data.block_reference.into()))
.await??;
Ok(RpcProtocolConfigResponse { config_view })
}

async fn query(&self, params: Option<Value>) -> Result<Value, RpcError> {
let query_request = if let Ok((path, data)) =
parse_params::<(String, String)>(params.clone())
Expand Down
Loading

0 comments on commit 0aa0823

Please sign in to comment.