banyancomputer / chainlink-proof-validator

lean mean gas saving machine (cryptographic proof verification on EVM is $$. this is way cheaper.)
MIT License
1 stars 1 forks source link

prefix all logs with ID from request #44

Open github-actions[bot] opened 2 years ago

github-actions[bot] commented 2 years ago

/ A success count of 1.

checking that deal is either finished or cancelled

https://github.com/banyancomputer/chainlink-proof-validator/blob/85d3e4ec2e5cdf574c43ba0a55808454db14a135/src/main.rs#L38


#![deny(unused_crate_dependencies)]

//use rust_chainlink_ea_api::validate::*;
pub mod validate;

use anyhow::Result;
use banyan_shared::{eth::EthClient, types::DealID};
use ethers as _;
use rand::Rng;
use rocket::serde::{json::serde_json, json::Json, Deserialize, Serialize};
use rocket::tokio::task::spawn;
use rocket::{post, State};
use std::sync::Arc;
use tokio as _;

pub struct WebserverState {
    pub provider: Arc<EthClient>,
    pub should_be_async: bool,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct ChainlinkEARequest {
    pub id: String,
    pub data: validate::ChainlinkRequestData,
    pub meta: Option<serde_json::Value>,
    pub response_url: Option<String>,
}

fn format_response(
    result: Result<validate::ChainlinkResponse, anyhow::Error>,
) -> Json<serde_json::Value> {
    match result {
        Ok(data) => Json(serde_json::json!(data)),
        Err(e) => Json(serde_json::json!({"error": e.to_string()})),
    }
}

// TODO prefix all logs with ID from request
#[post("/compute", format = "json", data = "<input_data>")]
pub async fn compute(
    webserver_state: &State<WebserverState>,
    input_data: Json<ChainlinkEARequest>,
) -> Json<serde_json::Value> {
    if webserver_state.should_be_async {
        let new_provider = webserver_state.provider.clone();
        spawn(async move {
            let result =
                validate::validate_deal_internal(new_provider, input_data.data.clone()).await;
            // send the result to the chainlink node
            reqwest::Client::new()
                .patch(input_data.into_inner().response_url.unwrap())
                .body(format_response(result).to_string())
                .send()
                .await
                .unwrap();
        });
        Json(serde_json::json!({
            "pending": true
        }))
        // end of thread
    } else {
        format_response(
            validate::validate_deal_internal(
                webserver_state.provider.clone(),
                input_data.data.clone(),
            )
            .await,
        )
    }
}

#[rocket::main]
async fn main() -> Result<()> {
    dotenv::dotenv().ok();
    let should_be_async = std::env::var("SHOULD_BE_ASYNC")
        .map_or_else(|_| false, |n| n.parse::<bool>().unwrap_or(false));

    // create an ethers HTTP provider
    let eth_client = Arc::new(EthClient::default());

    let _ = rocket::build()
        .mount("/", rocket::routes![compute])
        .manage(WebserverState {
            provider: eth_client,
            should_be_async,
        })
        .launch()
        .await?;

    Ok(())
}

/// Helper function for testing inputs to Chainlink EA without having to run a node.
pub async fn rust_chainlink_ea_api_call(
    deal_id: DealID,
    api_url: String,
) -> Result<validate::ChainlinkResponse, anyhow::Error> {
    // Job id when chainlink calls is not random.
    let mut rng = rand::thread_rng();
    let random_job_id: u16 = rng.gen();
    let map = serde_json::json!({
        "id": random_job_id.to_string(),
        "data":
        {
             "deal_id": deal_id.0.to_string()
        }
    });
    let client = reqwest::Client::new();
    let res = client
        .post(api_url)
        .json(&map)
        .send()
        .await?
        .json::<validate::ChainlinkResponse>()
        .await?;
    dbg!("{:?}", &res);
    Ok(res)
}

/*
#[cfg(test)]
mod tests {
    use super::*;
    use banyan_shared::{
        deals::DealProposalBuilder,
        eth::EthClient,
        types::{BlockNum, DealID, DealProposal},
    };
    use ethers::types::Bytes;
    use std::fs::File;

    #[tokio::test]
    /// This test will fail if no deal has ever been created on the contract, or if that deal has valid proofs in it.
    async fn api_call_test() -> Result<(), anyhow::Error> {
        let response_data: validate::ChainlinkResponse =
            rust_chainlink_ea_api_call(DealID(1), "http://127.0.0.1:8000/compute".to_string())
                .await?;
        assert_eq!(response_data.data.success_count, 1);
        Ok(())
    }

    #[tokio::test]
    /// This tests verifies that a deal with no logged proofs will have a success count of 0
    async fn api_no_proofs_test() -> Result<(), anyhow::Error> {
        let file = File::open("test_files/ethereum.pdf").unwrap();
        let deal_proposal = DealProposal::builder().with_file(file).build().unwrap();
        let eth_client = EthClient::default();

        let deal_id: DealID = eth_client
            .propose_deal(deal_proposal, None, None)
            .await
            .expect("Failed to send deal proposal");

        dbg!("Offer Created for Deal ID: {:}", deal_id);
        let deal = eth_client.get_offer(deal_id).await.unwrap();
        // Assert that the deal we read is the same as the one we sent
        dbg!("deal {:?}", deal.clone());
        assert_eq!(deal.deal_length_in_blocks, BlockNum(10));

        let response_data: validate::ChainlinkResponse =
            rust_chainlink_ea_api_call(deal_id, "http://127.0.0.1:8000/compute".to_string())
                .await?;
        assert_eq!(response_data.data.success_count, 0);
        Ok(())
    }

    #[tokio::test]
    /// This test verifies that an eth client can create multiple consecutive deals, which all have no logged proofs.
    async fn multiple_deals_same_file_no_proofs() -> Result<(), anyhow::Error> {
        let file = File::open("test_files/ethereum.pdf").unwrap();
        let deal_proposal = DealProposal::builder().with_file(file).build().unwrap();
        let eth_client = EthClient::default();

        let deal_id: DealID = eth_client
            .propose_deal(deal_proposal.clone(), None, None)
            .await
            .expect("Failed to send deal proposal");

        dbg!("Proof Created for Deal ID: {:}", &deal_id);
        let deal = eth_client.get_offer(deal_id).await.unwrap();
        // Assert that the deal we read is the same as the one we sent
        assert_eq!(deal.deal_length_in_blocks, BlockNum(10));

        let deal_id_2: DealID = eth_client
            .propose_deal(deal_proposal, None, None)
            .await
            .expect("Failed to send deal proposal");
        dbg!("Proof Created for Deal ID: {:}", &deal_id_2);
        let deal = eth_client.get_offer(deal_id_2).await.unwrap();
        assert_eq!(deal.deal_length_in_blocks, BlockNum(10));

        let response_data_1: validate::ChainlinkResponse =
            rust_chainlink_ea_api_call(deal_id, "http://127.0.0.1:8000/compute".to_string())
                .await?;
        assert_eq!(response_data_1.data.success_count, 0);
        let response_data_2: validate::ChainlinkResponse =
            rust_chainlink_ea_api_call(deal_id_2, "http://127.0.0.1:8000/compute".to_string())
                .await?;
        assert_eq!(response_data_2.data.success_count, 0);
        Ok(())
    }

    #[tokio::test]
    /// This test verifies that we can create a deal with one window, submit a proof, and verify it.
    async fn deal_and_proof_one_window() -> Result<(), anyhow::Error> {
        let mut file = File::open("test_files/ethereum.pdf").unwrap();

        let deal_proposal: DealProposal = DealProposalBuilder::new(
            "0x0000000000000000000000000000000000000000".to_string(),
            3,
            3,
            0.0,
            0.0,
            "0x0000000000000000000000000000000000000000".to_string(),
        )
        .with_file(file.try_clone()?)
        .build()
        .unwrap();

        let eth_client = EthClient::default();

        let deal_id: DealID = eth_client
            .propose_deal(deal_proposal.clone(), None, None)
            .await
            .expect("Failed to send deal proposal");

        let deal = eth_client.get_offer(deal_id).await.unwrap();

        // create a proof using the same file we used to create the deal
        let (_hash, proof) = eth_client
            .create_proof_helper(
                deal.deal_start_block,
                &mut file,
                deal.file_size.as_u64(),
                true,
            )
            .await
            .expect("Failed to create proof");

        // Wait one block until current block is no longer the deal start block
        let mut current_block_num = deal.deal_start_block;
        while current_block_num == deal.deal_start_block {
            current_block_num = eth_client
                .get_latest_block_num()
                .await
                .expect("Failed to get current block");
        }

        let target_block = deal.deal_start_block;
        let _block_num: BlockNum = eth_client
            .post_proof(deal_id, proof, target_block, None, None)
            .await
            .expect("Failed to post proof");

        let mut current_block_num_2 = BlockNum(0);
        while !EthClient::deal_over(current_block_num_2, deal.clone()) {
            current_block_num_2 = eth_client
                .get_latest_block_num()
                .await
                .expect("Failed to get current block");
        }

        let response_data: validate::ChainlinkResponse =
            rust_chainlink_ea_api_call(deal_id, "http://127.0.0.1:8000/compute".to_string())
                .await?;

        assert_eq!(response_data.data.success_count, 1);
        Ok(())
    }

    #[tokio::test]
    /// This test verifies that we can create a deal with two window, post one proof and simply not post a second, and recieve
    /// A success count of 1.
    async fn one_proof_window_missing() -> Result<(), anyhow::Error> {
        let mut file = File::open("test_files/ethereum.pdf").unwrap();

        let deal_proposal: DealProposal = DealProposalBuilder::new(
            "0x0000000000000000000000000000000000000000".to_string(),
            6,
            3,
            0.0,
            0.0,
            "0x0000000000000000000000000000000000000000".to_string(),
        )
        .with_file(file.try_clone()?)
        .build()
        .unwrap();
        let eth_client = EthClient::default();

        let deal_id: DealID = eth_client
            .propose_deal(deal_proposal.clone(), None, None)
            .await
            .expect("Failed to send deal proposal");

        let deal = eth_client.get_offer(deal_id).await.unwrap();

        let target_window: usize = eth_client
            .compute_target_window(deal.deal_start_block, deal.proof_frequency_in_blocks)
            .await
            .expect("Failed to compute target window");

        let target_block = EthClient::compute_target_block_start(
            deal.deal_start_block,
            deal.proof_frequency_in_blocks,
            target_window,
        );

        // Wait one block until current block is no longer the deal start block
        let mut current_block_num = deal.deal_start_block;
        while current_block_num == deal.deal_start_block {
            current_block_num = eth_client
                .get_latest_block_num()
                .await
                .expect("Failed to get current block");
        }

        // create a proof using the same file we used to create the deal
        let (_hash, proof) = eth_client
            .create_proof_helper(target_block, &mut file, deal.file_size.as_u64(), true)
            .await
            .expect("Failed to create proof");

        let block_num: BlockNum = eth_client
            .post_proof(deal_id, proof, target_block, None, None)
            .await
            .expect("Failed to post proof");

        // Wait for the second window to start
        // checking that deal is either finished or cancelled
        let mut current_block_num_2 = block_num;
        while !EthClient::deal_over(current_block_num_2, deal.clone()) {
            current_block_num_2 = eth_client
                .get_latest_block_num()
                .await
                .expect("Failed to get current block");
        }

        let response_data: validate::ChainlinkResponse =
            rust_chainlink_ea_api_call(deal_id, "http://127.0.0.1:8000/compute".to_string())
                .await?;

        assert_eq!(response_data.data.success_count, 1);
        assert_eq!(response_data.data.num_windows, 2);
        Ok(())
    }

    #[tokio::test]
    /// This test verifies that we can submit two correct proofs and get two successes.
    async fn two_correct_proofs() -> Result<(), anyhow::Error> {
        let mut file = File::open("test_files/ethereum.pdf").unwrap();

        let deal_proposal: DealProposal = DealProposalBuilder::new(
            "0x0000000000000000000000000000000000000000".to_string(),
            4,
            2,
            0.0,
            0.0,
            "0x0000000000000000000000000000000000000000".to_string(),
        )
        .with_file(file.try_clone()?)
        .build()
        .unwrap();
        let eth_client = EthClient::default();

        let deal_id: DealID = eth_client
            .propose_deal(deal_proposal.clone(), None, None)
            .await
            .expect("Failed to send deal proposal");

        let deal = eth_client.get_offer(deal_id).await.unwrap();

        let target_window: usize = eth_client
            .compute_target_window(deal.deal_start_block, deal.proof_frequency_in_blocks)
            .await
            .expect("Failed to compute target window");

        let target_block = EthClient::compute_target_block_start(
            deal.deal_start_block,
            deal.proof_frequency_in_blocks,
            target_window,
        );

        dbg!("deal start block: {}", deal.deal_start_block);
        dbg!("target block start: {}", target_block);

        let (_hash, proof) = eth_client
            .create_proof_helper(target_block, &mut file, deal.file_size.as_u64(), true)
            .await
            .expect("Failed to create proof");

        let block_num: BlockNum = eth_client
            .post_proof(deal_id, proof, target_block, None, None)
            .await
            .expect("Failed to post proof");

        let target_block_2 = target_block + deal.proof_frequency_in_blocks;

        let mut current_block_num = block_num;
        while current_block_num != target_block_2 {
            current_block_num = eth_client
                .get_latest_block_num()
                .await
                .expect("Failed to get current block");
        }

        let (_hash, bad_proof) = eth_client
            .create_proof_helper(target_block_2, &mut file, deal.file_size.as_u64(), true)
            .await
            .expect("Failed to create proof");

        let block_num_2: BlockNum = eth_client
            .post_proof(deal_id, bad_proof, target_block_2, None, None)
            .await
            .expect("Failed to post proof");

        current_block_num = block_num_2;
        while !EthClient::deal_over(current_block_num, deal.clone()) {
            current_block_num = eth_client
                .get_latest_block_num()
                .await
                .expect("Failed to get current block");
        }

        let response_data: validate::ChainlinkResponse =
            rust_chainlink_ea_api_call(deal_id, "http://127.0.0.1:8000/compute".to_string())
                .await?;

        assert_eq!(response_data.data.success_count, 2);
        assert_eq!(response_data.data.num_windows, 2);
        Ok(())
    }

    #[tokio::test]
    /// This test verifies that we can get a success count of 1 when submitting one correct proof and one incorrect proof
    async fn one_proof_correct_one_incorrect() -> Result<(), anyhow::Error> {
        let mut file = File::open("test_files/ethereum.pdf").unwrap();

        let deal_proposal: DealProposal = DealProposalBuilder::new(
            "0x0000000000000000000000000000000000000000".to_string(),
            4,
            2,
            0.0,
            0.0,
            "0x0000000000000000000000000000000000000000".to_string(),
        )
        .with_file(file.try_clone()?)
        .build()
        .unwrap();
        let eth_client = EthClient::default();

        let deal_id: DealID = eth_client
            .propose_deal(deal_proposal.clone(), None, None)
            .await
            .expect("Failed to send deal proposal");

        let deal = eth_client.get_offer(deal_id).await.unwrap();

        let target_window: usize = eth_client
            .compute_target_window(deal.deal_start_block, deal.proof_frequency_in_blocks)
            .await
            .expect("Failed to compute target window");

        let target_block = EthClient::compute_target_block_start(
            deal.deal_start_block,
            deal.proof_frequency_in_blocks,
            target_window,
        );

        dbg!("deal start block: {}", deal.deal_start_block);
        dbg!("target block start: {}", target_block);

        let (_hash, proof) = eth_client
            .create_proof_helper(target_block, &mut file, deal.file_size.as_u64(), true)
            .await
            .expect("Failed to create proof");

        let block_num: BlockNum = eth_client
            .post_proof(deal_id, proof, target_block, None, None)
            .await
            .expect("Failed to post proof");

        let target_block_2 = target_block + deal.proof_frequency_in_blocks;

        let mut current_block_num = block_num;
        while current_block_num != target_block_2 {
            current_block_num = eth_client
                .get_latest_block_num()
                .await
                .expect("Failed to get current block");
        }
        let (_hash, bad_proof) = eth_client
            .create_proof_helper(target_block_2, &mut file, deal.file_size.as_u64(), false)
            .await
            .expect("Failed to create proof");

        let block_num_2: BlockNum = eth_client
            .post_proof(deal_id, bad_proof, target_block_2, None, None)
            .await
            .expect("Failed to post proof");

        current_block_num = block_num_2;
        while !EthClient::deal_over(current_block_num, deal.clone()) {
            current_block_num = eth_client
                .get_latest_block_num()
                .await
                .expect("Failed to get current block");
        }

        let response_data: validate::ChainlinkResponse =
            rust_chainlink_ea_api_call(deal_id, "http://127.0.0.1:8000/compute".to_string())
                .await?;

        assert_eq!(response_data.data.success_count, 1);
        assert_eq!(response_data.data.num_windows, 2);
        Ok(())
    }

    #[tokio::test]
    /// This test verifies that an empty proof will not be counted as a success.
    async fn empty_proof_unsuccessful() -> Result<(), anyhow::Error> {
        let file = File::open("test_files/ethereum.pdf").unwrap();
        let deal_proposal: DealProposal = DealProposalBuilder::new(
            "0x0000000000000000000000000000000000000000".to_string(),
            1,
            1,
            0.0,
            0.0,
            "0x0000000000000000000000000000000000000000".to_string(),
        )
        .with_file(file)
        .build()
        .unwrap();
        let eth_client = EthClient::default();

        let deal_id: DealID = eth_client
            .propose_deal(deal_proposal.clone(), None, None)
            .await
            .expect("Failed to send deal proposal");

        let deal = eth_client.get_offer(deal_id).await.unwrap();

        // create a proof using the same file we used to create the deal
        let proof = Bytes::from(Vec::new());

        let block_num: BlockNum = eth_client
            .post_proof(deal_id, proof, deal.deal_start_block, None, None)
            .await
            .expect("Failed to post proof");

        let mut current_block_num = block_num;
        while !EthClient::deal_over(current_block_num, deal.clone()) {
            current_block_num = eth_client
                .get_latest_block_num()
                .await
                .expect("Failed to get current block");
        }

        let response_data: validate::ChainlinkResponse =
            rust_chainlink_ea_api_call(deal_id, "http://127.0.0.1:8000/compute".to_string())
                .await?;

        assert_eq!(response_data.data.success_count, 0);
        Ok(())
    }
}
*/