Skip to content

Commit

Permalink
[json-rpc] fix fuzzing test
Browse files Browse the repository at this point in the history
  • Loading branch information
Xiao Li authored and bors-libra committed Aug 27, 2020
1 parent 6525726 commit bb235ce
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 56 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions json-rpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ edition = "2018"
anyhow = "1.0.32"
futures = "0.3.5"
hex = "0.4.2"
hyper = "0.13.7"
once_cell = "1.4.0"
serde_json = "1.0.57"
serde = { version = "1.0.114", default-features = false }
Expand Down
100 changes: 66 additions & 34 deletions json-rpc/src/fuzzing.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
// Copyright (c) The Libra Core Contributors
// SPDX-License-Identifier: Apache-2.0

use crate::test_bootstrap;
use libra_config::utils;
use libra_mempool::mocks::MockSharedMempool;
use crate::{methods, runtime, tests};
use futures::{channel::mpsc::channel, StreamExt};
use libra_config::config;
use libra_proptest_helpers::ValueGenerator;
use libra_temppath::TempPath;
use libra_types::transaction::SignedTransaction;
use libradb::{test_helper::arb_mock_genesis, LibraDB};
use reqwest::blocking::Client;
use std::sync::Arc;
use storage_interface::DbWriter;
use warp::reply::Reply;

#[test]
fn test_json_rpc_service_fuzzer() {
Expand All @@ -22,32 +18,16 @@ fn test_json_rpc_service_fuzzer() {
/// generate_corpus produces an arbitrary transaction to submit to JSON RPC service
pub fn generate_corpus(gen: &mut ValueGenerator) -> Vec<u8> {
// use proptest to generate a SignedTransaction
let txn = gen.generate(proptest::arbitrary::any::<SignedTransaction>());
let txn = gen.generate(proptest::arbitrary::any::<
libra_types::transaction::SignedTransaction,
>());
let payload = hex::encode(lcs::to_bytes(&txn).unwrap());
let request =
serde_json::json!({"jsonrpc": "2.0", "method": "submit", "params": [payload], "id": 1});
serde_json::to_vec(&request).expect("failed to convert JSON to byte array")
}

pub fn fuzzer(data: &[u8]) {
// set up mock Shared Mempool
let smp = MockSharedMempool::new(None);

// set up JSON RPC service
let tmp_dir = TempPath::new();
let db = LibraDB::new_for_test(&tmp_dir);
// initialize DB with baseline ledger info - otherwise server will fail on attempting to retrieve initial ledger info
let (genesis_txn_to_commit, ledger_info_with_sigs) =
ValueGenerator::new().generate(arb_mock_genesis());
db.save_transactions(&[genesis_txn_to_commit], 0, Some(&ledger_info_with_sigs))
.unwrap();

let port = utils::get_available_port();
let address = format!("0.0.0.0:{}", port);
let _runtime = test_bootstrap(address.parse().unwrap(), Arc::new(db), smp.ac_client);

let client = Client::new();
let url = format!("http://{}", address);
let json_request = match serde_json::from_slice::<serde_json::Value>(data) {
Err(_) => {
// should not throw error or panic on invalid fuzzer inputs
Expand All @@ -58,21 +38,73 @@ pub fn fuzzer(data: &[u8]) {
}
Ok(request) => request,
};
// set up mock Shared Mempool
let (mp_sender, mut mp_events) = channel(1);

let db = tests::MockLibraDB {
version: 1 as u64,
genesis: std::collections::HashMap::new(),
all_accounts: std::collections::HashMap::new(),
all_txns: vec![],
events: vec![],
account_state_with_proof: vec![],
timestamps: vec![1598223353000000],
};
let registry = Arc::new(methods::build_registry());
let service = methods::JsonRpcService::new(
Arc::new(db),
mp_sender,
config::RoleType::Validator,
libra_types::chain_id::ChainId::test(),
config::DEFAULT_BATCH_SIZE_LIMIT,
config::DEFAULT_PAGE_SIZE_LIMIT,
);
let mut rt = tokio::runtime::Builder::new()
.basic_scheduler()
.enable_all()
.build()
.unwrap();

let response = client
.post(&url) // address
.json(&json_request)
.send()
.expect("failed to send request to JSON RPC server");
rt.spawn(async move {
if let Some((_, cb)) = mp_events.next().await {
cb.send(Ok((
libra_types::mempool_status::MempoolStatus::new(
libra_types::mempool_status::MempoolStatusCode::Accepted,
),
None,
)))
.unwrap();
}
});
let body = rt.block_on(async {
let reply = runtime::rpc_endpoint(json_request, service, registry)
.await
.unwrap();

let resp = reply.into_response();
let (_, body) = resp.into_parts();
hyper::body::to_bytes(body).await.unwrap()
});

let response: serde_json::Value = serde_json::from_slice(body.as_ref()).expect("json");

let response: serde_json::Value = response.json().expect("failed to convert response to JSON");
match response {
serde_json::Value::Array(batch_response) => {
for resp in batch_response {
assert_response(resp)
}
}
_ => assert_response(response),
};
}

fn assert_response(response: serde_json::Value) {
let json_rpc_protocol = response.get("jsonrpc");
assert_eq!(
json_rpc_protocol,
Some(&serde_json::Value::String("2.0".to_string())),
"JSON RPC response with incorrect protocol: {:?}",
json_rpc_protocol
response
);
if response.get("error").is_some() && cfg!(test) {
panic!(
Expand Down
50 changes: 29 additions & 21 deletions json-rpc/src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ pub fn bootstrap_from_config(
/// JSON RPC entry point
/// Handles all incoming rpc requests
/// Performs routing based on methods defined in `registry`
async fn rpc_endpoint(
pub(crate) async fn rpc_endpoint(
data: Value,
service: JsonRpcService,
registry: Arc<RpcRegistry>,
Expand Down Expand Up @@ -157,7 +157,7 @@ async fn rpc_endpoint_without_metrics(
warp::reply::json(&Value::Array(responses))
}
Err(err) => {
let mut resp = Map::new();
let mut resp = init_response(&service, &ledger_info);
set_response_error(&mut resp, err);

warp::reply::json(&resp)
Expand All @@ -182,25 +182,7 @@ async fn rpc_request_handler(
request_type_label: &str,
) -> Value {
let request: Map<String, Value>;
let mut response = Map::new();
let version = ledger_info.ledger_info().version();
let timestamp = ledger_info.ledger_info().timestamp_usecs();

// set defaults: protocol version to 2.0, request id to null
response.insert("jsonrpc".to_string(), Value::String("2.0".to_string()));
response.insert("id".to_string(), Value::Null);
response.insert(
JSONRPC_LIBRA_LEDGER_VERSION.to_string(),
Value::Number(version.into()),
);
response.insert(
JSONRPC_LIBRA_LEDGER_TIMESTAMPUSECS.to_string(),
Value::Number(timestamp.into()),
);
response.insert(
JSONRPC_LIBRA_CHAIN_ID.to_string(),
Value::Number(service.chain_id().id().into()),
);
let mut response = init_response(&service, &ledger_info);

match req {
Value::Object(data) => {
Expand Down Expand Up @@ -331,6 +313,32 @@ fn verify_protocol(request: &Map<String, Value>) -> Result<(), JsonRpcError> {
Err(JsonRpcError::invalid_request())
}

fn init_response(
service: &JsonRpcService,
ledger_info: &LedgerInfoWithSignatures,
) -> Map<String, Value> {
let mut response = Map::new();
let version = ledger_info.ledger_info().version();
let timestamp = ledger_info.ledger_info().timestamp_usecs();

// set defaults: protocol version to 2.0, request id to null
response.insert("jsonrpc".to_string(), Value::String("2.0".to_string()));
response.insert("id".to_string(), Value::Null);
response.insert(
JSONRPC_LIBRA_LEDGER_VERSION.to_string(),
Value::Number(version.into()),
);
response.insert(
JSONRPC_LIBRA_LEDGER_TIMESTAMPUSECS.to_string(),
Value::Number(timestamp.into()),
);
response.insert(
JSONRPC_LIBRA_CHAIN_ID.to_string(),
Value::Number(service.chain_id().id().into()),
);
response
}

/// Warp rejection types
#[derive(Debug)]
struct DatabaseError;
Expand Down
2 changes: 2 additions & 0 deletions json-rpc/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ mod unit_tests;
mod utils;

pub use utils::test_bootstrap;
#[cfg(any(test, feature = "fuzzing"))]
pub use utils::MockLibraDB;
2 changes: 1 addition & 1 deletion json-rpc/src/tests/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ pub fn test_bootstrap(

/// Lightweight mock of LibraDB
#[derive(Clone)]
pub(crate) struct MockLibraDB {
pub struct MockLibraDB {
pub version: u64,
pub genesis: HashMap<AccountAddress, AccountStateBlob>,
pub all_accounts: HashMap<AccountAddress, AccountStateBlob>,
Expand Down

0 comments on commit bb235ce

Please sign in to comment.