diff --git a/contracts/nois-gateway/src/contract.rs b/contracts/nois-gateway/src/contract.rs index d7da3de8..9bc33111 100644 --- a/contracts/nois-gateway/src/contract.rs +++ b/contracts/nois-gateway/src/contract.rs @@ -9,8 +9,8 @@ use cosmwasm_std::{ }; use cw_storage_plus::Bound; use nois_protocol::{ - check_order, check_version, InPacket, InPacketAck, OutPacket, OutPacketAck, StdAck, - BEACON_PRICE_PACKET_LIFETIME, IBC_APP_VERSION, WELCOME_PACKET_LIFETIME, + check_order, check_version, InPacket, InPacketAck, OutPacket, OutPacketAck, RequestType, + StdAck, BEACON_PRICE_PACKET_LIFETIME, IBC_APP_VERSION, WELCOME_PACKET_LIFETIME, }; use sha2::{Digest, Sha256}; @@ -344,10 +344,26 @@ pub fn ibc_packet_receive( (|| { let op: InPacket = from_binary(&packet.data)?; match op { - InPacket::RequestBeacon { after, origin } => { - receive_request_beacon(deps, env, channel_id, relayer, after, origin) - } + InPacket::RequestBeacon { after, callback } => receive_job_request( + deps, + env, + channel_id, + relayer, + after, + callback, + RequestType::Randomness, + ), + InPacket::RequestScheduleJob { after, callback } => receive_job_request( + deps, + env, + channel_id, + relayer, + after, + callback, + RequestType::AtJob, + ), InPacket::PullBeaconPrice {} => receive_pull_beacon_price(deps, env), + _ => Err(ContractError::UnsupportedPacketType), } })() @@ -361,15 +377,16 @@ pub fn ibc_packet_receive( }) } -fn receive_request_beacon( +fn receive_job_request( mut deps: DepsMut, env: Env, channel_id: String, relayer: Addr, after: Timestamp, - origin: Binary, + callback: Binary, + request_type: RequestType, ) -> Result { - validate_origin(&origin)?; + validate_origin(&callback)?; let router = RequestRouter::new(); let RoutingReceipt { @@ -382,7 +399,8 @@ fn receive_request_beacon( &env, channel_id.clone(), after, - origin.clone(), + callback.clone(), + request_type, )?; // Store request @@ -390,7 +408,7 @@ fn receive_request_beacon( deps.storage, &channel_id, &RequestLogEntry { - origin, + origin: callback, tx: (env.block.height, env.transaction.map(|ti| ti.index)), source_id, queued, @@ -1045,7 +1063,7 @@ mod tests { "foo", &InPacket::RequestBeacon { after: AFTER2, - origin: origin(1), + callback: origin(1), }, ) .unwrap(); @@ -1076,7 +1094,7 @@ mod tests { "foo", &InPacket::RequestBeacon { after: AFTER3, - origin: origin(i), + callback: origin(i), }, ) .unwrap(); @@ -1106,7 +1124,7 @@ mod tests { "foo", &InPacket::RequestBeacon { after: AFTER4, - origin: origin(i), + callback: origin(i), }, ) .unwrap(); @@ -1206,7 +1224,7 @@ mod tests { "foo", &InPacket::RequestBeacon { after: AFTER1, - origin: origin(1), + callback: origin(1), }, ) .unwrap(); @@ -1240,7 +1258,7 @@ mod tests { "foo", &InPacket::RequestBeacon { after: AFTER1, - origin: origin(2), + callback: origin(2), }, ) .unwrap(); @@ -1262,7 +1280,7 @@ mod tests { "foo", &InPacket::RequestBeacon { after: AFTER2, - origin: origin(i), + callback: origin(i), }, ) .unwrap(); @@ -1367,7 +1385,7 @@ mod tests { CHANNEL, &InPacket::RequestBeacon { after: AFTER1, - origin: origin(1), + callback: origin(1), }, ) .unwrap(); @@ -1386,8 +1404,8 @@ mod tests { origin: origin(1), queued: true, source_id: - "drand:dbd506d6ef76e5f386f41c651dcb808c5bcbd75471cc4eafa3f4df7ad4e4c493:810" - .to_string(), + Some("drand:dbd506d6ef76e5f386f41c651dcb808c5bcbd75471cc4eafa3f4df7ad4e4c493:810" + .to_string()), tx: expected_tx_1 }] } @@ -1399,8 +1417,8 @@ mod tests { origin: origin(1), queued: true, source_id: - "drand:dbd506d6ef76e5f386f41c651dcb808c5bcbd75471cc4eafa3f4df7ad4e4c493:810" - .to_string(), + Some("drand:dbd506d6ef76e5f386f41c651dcb808c5bcbd75471cc4eafa3f4df7ad4e4c493:810" + .to_string()), tx: expected_tx_1 }] } @@ -1411,7 +1429,7 @@ mod tests { CHANNEL, &InPacket::RequestBeacon { after: AFTER2, - origin: origin(2), + callback: origin(2), }, ) .unwrap(); @@ -1431,15 +1449,15 @@ mod tests { origin: origin(1), queued: true, source_id: - "drand:dbd506d6ef76e5f386f41c651dcb808c5bcbd75471cc4eafa3f4df7ad4e4c493:810" - .to_string(), + Some("drand:dbd506d6ef76e5f386f41c651dcb808c5bcbd75471cc4eafa3f4df7ad4e4c493:810" + .to_string()), tx: expected_tx_1 }, RequestLogEntry { origin: origin(2), queued: true, source_id: - "drand:dbd506d6ef76e5f386f41c651dcb808c5bcbd75471cc4eafa3f4df7ad4e4c493:820" - .to_string(), + Some("drand:dbd506d6ef76e5f386f41c651dcb808c5bcbd75471cc4eafa3f4df7ad4e4c493:820" + .to_string()), tx: expected_tx_2 }] } @@ -1451,15 +1469,15 @@ mod tests { origin: origin(2), queued: true, source_id: - "drand:dbd506d6ef76e5f386f41c651dcb808c5bcbd75471cc4eafa3f4df7ad4e4c493:820" - .to_string(), + Some("drand:dbd506d6ef76e5f386f41c651dcb808c5bcbd75471cc4eafa3f4df7ad4e4c493:820" + .to_string()), tx: expected_tx_2 }, RequestLogEntry { origin: origin(1), queued: true, source_id: - "drand:dbd506d6ef76e5f386f41c651dcb808c5bcbd75471cc4eafa3f4df7ad4e4c493:810" - .to_string(), + Some("drand:dbd506d6ef76e5f386f41c651dcb808c5bcbd75471cc4eafa3f4df7ad4e4c493:810" + .to_string()), tx: expected_tx_1 }] } @@ -1653,7 +1671,7 @@ mod tests { CHANNEL_ID, &InPacket::RequestBeacon { after: AFTER1, - origin: origin(1), + callback: origin(1), }, ) .unwrap(); @@ -1670,7 +1688,7 @@ mod tests { CHANNEL_ID, &InPacket::RequestBeacon { after: AFTER1, - origin: origin(2), + callback: origin(2), }, ) .unwrap(); @@ -1685,7 +1703,7 @@ mod tests { CHANNEL_ID, &InPacket::RequestBeacon { after: AFTER2, - origin: origin(i), + callback: origin(i), }, ) .unwrap(); diff --git a/contracts/nois-gateway/src/request_router.rs b/contracts/nois-gateway/src/request_router.rs index 4520ef1a..6ba7712b 100644 --- a/contracts/nois-gateway/src/request_router.rs +++ b/contracts/nois-gateway/src/request_router.rs @@ -4,7 +4,7 @@ use cosmwasm_std::{ to_binary, Binary, CosmosMsg, DepsMut, Env, HexBinary, IbcMsg, StdError, StdResult, Timestamp, }; use drand_common::{time_of_round, valid_round_after, DRAND_CHAIN_HASH}; -use nois_protocol::{InPacketAck, OutPacket, StdAck, DELIVER_BEACON_PACKET_LIFETIME}; +use nois_protocol::{InPacketAck, OutPacket, RequestType, StdAck, DELIVER_BEACON_PACKET_LIFETIME}; use crate::{ drand_archive::{archive_lookup, archive_store}, @@ -24,7 +24,7 @@ const MAX_JOBS_PER_SUBMISSION_WITHOUT_VERIFICATION: u32 = 14; pub struct RoutingReceipt { pub queued: bool, - pub source_id: String, + pub source_id: Option, pub acknowledgement: StdAck, pub msgs: Vec, } @@ -47,10 +47,14 @@ impl RequestRouter { env: &Env, channel: String, after: Timestamp, - origin: Binary, + callback: Binary, + request_type: RequestType, ) -> StdResult { - // Here we currently only have one backend - self.handle_drand(deps, env, channel, after, origin) + match request_type { + RequestType::Randomness => self.handle_drand(deps, env, channel, after, callback), + // For now handle_at_job is almost the same as drand because we'd still be relying on drand-bots trigger + RequestType::AtJob => self.handle_at_job(deps, env, channel, after, callback), + } } fn handle_drand( @@ -95,6 +99,7 @@ impl RequestRouter { source_id: source_id.clone(), }) }; + let source_id = Some(source_id); Ok(RoutingReceipt { queued, @@ -104,6 +109,53 @@ impl RequestRouter { }) } + fn handle_at_job( + &self, + deps: DepsMut, + env: &Env, + channel: String, + after: Timestamp, + origin: Binary, + ) -> StdResult { + let (round, source_id) = commit_to_drand_round(after); + + let existing_randomness = archive_lookup(deps.storage, round); + + let job = Job { + source_id: source_id.clone(), + channel, + origin, + }; + + let mut msgs = Vec::::new(); + + let queued = if let Some(randomness) = existing_randomness { + //If the drand round already exists we send it + increment_processed_drand_jobs(deps.storage, round)?; + let published = time_of_round(round); + let msg = + create_deliver_beacon_ibc_message(env.block.time, job, published, randomness)?; + msgs.push(msg.into()); + false + } else { + unprocessed_drand_jobs_enqueue(deps.storage, round, &job)?; + true + }; + + let acknowledgement = if queued { + StdAck::success(InPacketAck::RequestQueued { source_id }) + } else { + StdAck::success(InPacketAck::RequestProcessed { source_id }) + }; + + Ok(RoutingReceipt { + queued, + source_id: None, + acknowledgement, + msgs, + }) + } + pub fn new_drand( &self, deps: DepsMut, diff --git a/contracts/nois-gateway/src/state/requests_log.rs b/contracts/nois-gateway/src/state/requests_log.rs index 50febc7e..50d611e8 100644 --- a/contracts/nois-gateway/src/state/requests_log.rs +++ b/contracts/nois-gateway/src/state/requests_log.rs @@ -10,7 +10,7 @@ pub struct RequestLogEntry { /// height and tx_index of the transaction in which this was added pub tx: (u64, Option), /// A RNG specific randomness source identifier, e.g. `drand::` - pub source_id: String, + pub source_id: Option, /// This is true if the request was queued, i.e. the randomness is not yet available. /// It is false if the request is already available. pub queued: bool, diff --git a/contracts/nois-proxy/src/contract.rs b/contracts/nois-proxy/src/contract.rs index 8e4c4522..cc602938 100644 --- a/contracts/nois-proxy/src/contract.rs +++ b/contracts/nois-proxy/src/contract.rs @@ -11,8 +11,8 @@ use cosmwasm_std::{ use cosmwasm_std::{entry_point, Empty}; use nois::{NoisCallback, ReceiverExecuteMsg}; use nois_protocol::{ - check_order, check_version, InPacket, InPacketAck, OutPacket, OutPacketAck, StdAck, - REQUEST_BEACON_PACKET_LIFETIME, TRANSFER_PACKET_LIFETIME, + check_order, check_version, InPacket, InPacketAck, OutPacket, OutPacketAck, RequestType, + StdAck, REQUEST_BEACON_PACKET_LIFETIME, TRANSFER_PACKET_LIFETIME, }; use crate::attributes::{ @@ -22,7 +22,7 @@ use crate::error::ContractError; use crate::jobs::{validate_job_id, validate_payment}; use crate::msg::{ AllowlistResponse, ConfigResponse, ExecuteMsg, GatewayChannelResponse, InstantiateMsg, - IsAllowlistedResponse, PriceResponse, PricesResponse, QueryMsg, RequestBeaconOrigin, SudoMsg, + IsAllowlistedResponse, PriceResponse, PricesResponse, QueryMsg, RequestBeaconCallback, SudoMsg, }; use crate::publish_time::{calculate_after, AfterMode}; use crate::state::{Config, OperationalMode, ALLOWLIST, ALLOWLIST_MARKER, CONFIG, GATEWAY_CHANNEL}; @@ -105,7 +105,7 @@ pub fn migrate(deps: DepsMut, env: Env, _msg: Empty) -> StdResult { CONFIG.save(deps.storage, &config)?; - Ok(Response::default().add_attribute(ATTR_ACTION, "migtrate")) + Ok(Response::default().add_attribute(ATTR_ACTION, "migrate")) } #[cfg_attr(not(feature = "library"), entry_point)] @@ -146,6 +146,11 @@ pub fn execute( ExecuteMsg::GetRandomnessAfter { after, job_id } => { execute_get_randomness_after(deps, env, info, after, job_id) } + ExecuteMsg::ScheduleJobAt { + after, + job_id, + callback_addr, + } => execute_schedule_job_at(deps, env, info, after, job_id, callback_addr), ExecuteMsg::Withdraw { denom, amount, @@ -164,6 +169,7 @@ fn execute_get_next_randomness( job_id: String, ) -> Result { let config = CONFIG.load(deps.storage)?; + let callback_address = info.clone().sender; let mode = if config.test_mode { AfterMode::Test @@ -172,14 +178,15 @@ fn execute_get_next_randomness( }; let after = calculate_after(deps.storage, mode)?; - execute_get_randomness_impl( + execute_request_impl( deps, env, info, - "execute_get_next_randomness", config, after, job_id, + callback_address, + RequestType::Randomness, ) } @@ -191,26 +198,54 @@ fn execute_get_randomness_after( job_id: String, ) -> Result { let config = CONFIG.load(deps.storage)?; + let callback_address = info.clone().sender; - execute_get_randomness_impl( + execute_request_impl( deps, env, info, - "execute_get_randomness_after", config, after, job_id, + callback_address, + RequestType::Randomness, ) } -pub fn execute_get_randomness_impl( +fn execute_schedule_job_at( + deps: DepsMut, + env: Env, + info: MessageInfo, + after: Timestamp, + job_id: String, + callback_address: Option, +) -> Result { + let config = CONFIG.load(deps.storage)?; + let callback_address = callback_address.unwrap_or(info.sender.to_string()); + let callback_address = deps.api.addr_validate(&callback_address)?; + + execute_request_impl( + deps, + env, + info, + config, + after, + job_id, + callback_address, + RequestType::AtJob, + ) +} + +#[allow(clippy::too_many_arguments)] +pub fn execute_request_impl( deps: DepsMut, env: Env, info: MessageInfo, - action: &str, config: Config, after: Timestamp, job_id: String, + callback_address: Addr, + request_type: RequestType, ) -> Result { validate_job_id(&job_id)?; validate_payment(&config.prices, &info.funds)?; @@ -230,14 +265,6 @@ pub fn execute_get_randomness_impl( if after > max_after { return Err(ContractError::AfterTooHigh { max_after, after }); } - - let packet = InPacket::RequestBeacon { - after, - origin: to_binary(&RequestBeaconOrigin { - sender: info.sender.into(), - job_id, - })?, - }; let channel_id = get_gateway_channel(deps.storage)?; let mut msgs: Vec = Vec::with_capacity(2); @@ -261,6 +288,36 @@ pub fn execute_get_randomness_impl( } } } + let res = match &request_type { + RequestType::Randomness => { + execute_get_randomness_impl(deps, env, info, &request_type, after, job_id, channel_id) + } + RequestType::AtJob => { + execute_schedule_job_impl(deps, env, &callback_address, after, job_id, channel_id) + } + }?; + + Ok(res) +} + +pub fn execute_get_randomness_impl( + _deps: DepsMut, + env: Env, + info: MessageInfo, + request_type: &RequestType, + after: Timestamp, + job_id: String, + channel_id: String, +) -> Result { + let packet = InPacket::RequestBeacon { + after, + callback: to_binary(&RequestBeaconCallback { + callback_contract: info.sender.into(), + job_id, + })?, + }; + + let mut msgs: Vec = Vec::with_capacity(2); msgs.push( IbcMsg::SendPacket { @@ -277,7 +334,44 @@ pub fn execute_get_randomness_impl( let res = Response::new() .add_messages(msgs) - .add_attribute(ATTR_ACTION, action); + .add_attribute(ATTR_ACTION, request_type.to_string()); + Ok(res) +} + +pub fn execute_schedule_job_impl( + _deps: DepsMut, + env: Env, + callback_address: &Addr, + after: Timestamp, + job_id: String, + channel_id: String, +) -> Result { + let packet = InPacket::RequestScheduleJob { + after, + callback: to_binary(&RequestBeaconCallback { + callback_contract: callback_address.to_string(), + job_id, + })?, + }; + + let mut msgs: Vec = Vec::with_capacity(2); + + msgs.push( + IbcMsg::SendPacket { + channel_id, + data: to_binary(&packet)?, + timeout: env + .block + .time + .plus_seconds(REQUEST_BEACON_PACKET_LIFETIME) + .into(), + } + .into(), + ); + + let res = Response::new() + .add_messages(msgs) + .add_attribute(ATTR_ACTION, "jobAt"); Ok(res) } @@ -709,7 +803,7 @@ pub fn ibc_channel_open( #[cfg_attr(not(feature = "library"), entry_point)] /// Once established we store the channel ID to look up -/// the destination address later. +/// the callback_contract address later. pub fn ibc_channel_connect( deps: DepsMut, _env: Env, @@ -799,13 +893,16 @@ fn receive_deliver_beacon( deps: DepsMut, published: Timestamp, randomness: HexBinary, - origin: Binary, + callback: Binary, ) -> Result { let Config { callback_gas_limit, .. } = CONFIG.load(deps.storage)?; - let RequestBeaconOrigin { sender, job_id } = from_slice(&origin)?; + let RequestBeaconCallback { + callback_contract, + job_id, + } = from_slice(&callback)?; // Create the message for executing the callback. // This can fail for various reasons, like @@ -815,7 +912,7 @@ fn receive_deliver_beacon( // - any other processing error in the callback implementation let msg = SubMsg::reply_on_error( WasmMsg::Execute { - contract_addr: sender, + contract_addr: callback_contract, msg: to_binary(&ReceiverExecuteMsg::NoisReceive { callback: NoisCallback { job_id: job_id.clone(), @@ -1696,8 +1793,8 @@ mod tests { // The proxy -> gateway packet we get the acknowledgement for let packet = InPacket::RequestBeacon { after: Timestamp::from_seconds(321), - origin: to_binary(&RequestBeaconOrigin { - sender: "contract345".to_string(), + callback: to_binary(&RequestBeaconCallback { + callback_contract: "contract345".to_string(), job_id: "hello".to_string(), }) .unwrap(), diff --git a/contracts/nois-proxy/src/msg.rs b/contracts/nois-proxy/src/msg.rs index 891c9957..e7d9f669 100644 --- a/contracts/nois-proxy/src/msg.rs +++ b/contracts/nois-proxy/src/msg.rs @@ -38,6 +38,13 @@ pub enum ExecuteMsg { after: Timestamp, job_id: String, }, + // This will schedule a job to call the dapp contract at a specific date + ScheduleJobAt { + after: Timestamp, + job_id: String, + // if the callback_addr is None, default to info.sender + callback_addr: Option, + }, /// Set the config SetConfig { manager: Option, @@ -202,13 +209,13 @@ pub struct IsAllowlistedResponse { } /// This struct contains information about the origin of the beacon request. It helps the -/// proxy to route the beacon response to the final destination. +/// proxy to route the beacon response to the final callback_contract. /// The IBC communication between proxy and gateway does not need this information. It is /// just passed along. #[cw_serde] -pub struct RequestBeaconOrigin { - /// The address of the dapp that requested the beacon. This is used by the proxy +pub struct RequestBeaconCallback { + /// The address of the dapp that will receive the beacon. This is used by the proxy /// to send the callback. - pub sender: String, + pub callback_contract: String, pub job_id: String, } diff --git a/packages/nois-protocol/src/ibc_msg.rs b/packages/nois-protocol/src/ibc_msg.rs index b99b68c4..5633402f 100644 --- a/packages/nois-protocol/src/ibc_msg.rs +++ b/packages/nois-protocol/src/ibc_msg.rs @@ -10,8 +10,14 @@ pub enum InPacket { RequestBeacon { /// Beacon publish time must be > `after` after: Timestamp, + /// The callback data set by the proxy in a proxy specific format. + callback: Binary, + }, + RequestScheduleJob { + /// Schedule at time + after: Timestamp, /// The origin data set by the proxy in a proxy specific format. - origin: Binary, + callback: Binary, }, /// Requests the current price per beacon. This can change over time and potentially /// change per channel ID. @@ -19,6 +25,19 @@ pub enum InPacket { PullBeaconPrice {}, } +pub enum RequestType { + Randomness, + AtJob, +} +impl RequestType { + pub fn to_string(&self) -> &str { + match self { + Self::Randomness => "randomness request", + Self::AtJob => "at-job request", + } + } +} + #[cw_serde] #[non_exhaustive] pub enum InPacketAck { diff --git a/packages/nois-protocol/src/lib.rs b/packages/nois-protocol/src/lib.rs index 5af44186..21ccf61f 100644 --- a/packages/nois-protocol/src/lib.rs +++ b/packages/nois-protocol/src/lib.rs @@ -4,7 +4,7 @@ mod ibc_msg; use cosmwasm_std::IbcOrder; pub use checks::{check_order, check_version, ChannelError}; -pub use ibc_msg::{InPacket, InPacketAck, OutPacket, OutPacketAck, StdAck}; +pub use ibc_msg::{InPacket, InPacketAck, OutPacket, OutPacketAck, RequestType, StdAck}; pub const IBC_APP_VERSION: &str = "nois-v7"; pub const APP_ORDER: IbcOrder = IbcOrder::Unordered;