Skip to content

Commit a9246a8

Browse files
authored
Merge pull request #26 from firstbatchxyz/erhant/fix-timeouts
added timeouts, add arweave workflow parse test
2 parents 8edc2df + 895ab43 commit a9246a8

File tree

12 files changed

+95
-40
lines changed

12 files changed

+95
-40
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "dkn-oracle"
33
description = "Dria Knowledge Network Oracle Node"
4-
version = "0.1.9"
4+
version = "0.1.10"
55
edition = "2021"
66
license = "Apache-2.0"
77
readme = "README.md"

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,9 @@ You can terminate the application from the terminal as usual (e.g. CTRL+C) to qu
8181

8282
#### Using Arweave
8383

84-
To save from gas fees, an Oracle node can upload its response to Arweave and then store the transaction id of that upload to the contract instead. This is differentiated by looking at the response, and see that it is exactly 64 characters
84+
To save from gas fees, an Oracle node can upload its response to Arweave and then store the transaction id of that upload to the contract instead. This is differentiated by looking at the response, and see that it is exactly 64 hexadecimal characters. It is then decoded from hex and encoded to `base64url` format, which can then be used to access the data at `https//arweave.net/{txid-here}`. This **requires** an Arweave wallet.
85+
86+
Following the same logic, the Oracle node can read task inputs from Arweave as well. This **does not require** an Arweave a wallet.
8587

8688
### Viewing Tasks
8789

misc/arweave.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,7 @@ const inputDecoded = Buffer.from(input, "hex").toString();
2727
const arweaveTxid = Buffer.from(inputDecoded, "hex").toString("base64url");
2828

2929
// download the actual response from Arweave
30-
const res = await fetch(`https://arweave.net/${arweaveTxid}`);
30+
const url = `https://arweave.net/${arweaveTxid}`;
31+
console.log(url);
32+
const res = await fetch(url);
3133
console.log(await res.text());

src/cli.rs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::str::FromStr;
2+
13
use crate::{commands, contracts::OracleKind, DriaOracle, DriaOracleConfig};
24
use alloy::{
35
eips::BlockNumberOrTag,
@@ -24,6 +26,18 @@ fn parse_secret_key(value: &str) -> Result<B256> {
2426
B256::from_hex(value).map_err(Into::into)
2527
}
2628

29+
/// `value parser` to parse a `str` to `BlockNumberOrTag`
30+
/// where if it can be parsed as `u64`, we call `BlockNumberOrTag::from_u64`
31+
/// otherwise we call `BlockNumberOrTag::from_str`.
32+
fn parse_block_number_or_tag(value: &str) -> Result<BlockNumberOrTag> {
33+
match value.parse::<u64>() {
34+
// parse block no from its decimal representation
35+
Ok(block_number) => Ok(BlockNumberOrTag::from(block_number)),
36+
// parse block no from hex, or parse its tag
37+
Err(_) => BlockNumberOrTag::from_str(value).map_err(Into::into),
38+
}
39+
}
40+
2741
// https://docs.rs/clap/latest/clap/_derive/index.html#arg-attributes
2842
#[derive(Subcommand)]
2943
enum Commands {
@@ -49,21 +63,22 @@ enum Commands {
4963
Start {
5064
#[arg(
5165
long,
52-
help = "Starting block number to listen for, defaults to 'latest'."
66+
help = "Starting block number to listen for, defaults to 'latest'.",
67+
value_parser = parse_block_number_or_tag
5368
)]
5469
from: Option<BlockNumberOrTag>,
5570
#[arg(help = "The oracle kinds to handle tasks as.", required = false)]
5671
kinds: Vec<OracleKind>,
57-
#[arg(short, long = "model", help = "The models to serve.", required = true, value_parser=parse_model)]
72+
#[arg(short, long = "model", help = "The models to serve.", required = true, value_parser = parse_model)]
5873
models: Vec<Model>,
5974
},
6075
/// View status of a given task.
6176
View { task_id: U256 },
6277
/// View tasks between specific blocks.
6378
Tasks {
64-
#[arg(long, help = "Starting block number, defaults to 'earliest'.")]
79+
#[arg(long, help = "Starting block number, defaults to 'earliest'.", value_parser = parse_block_number_or_tag)]
6580
from: Option<BlockNumberOrTag>,
66-
#[arg(long, help = "Ending block number, defaults to 'latest'.")]
81+
#[arg(long, help = "Ending block number, defaults to 'latest'.", value_parser = parse_block_number_or_tag)]
6782
to: Option<BlockNumberOrTag>,
6883
},
6984
/// Request a task.

src/commands/coordinator.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,10 +145,18 @@ pub async fn view_task_events(
145145
from_block: impl Into<BlockNumberOrTag> + Clone,
146146
to_block: impl Into<BlockNumberOrTag> + Clone,
147147
) -> Result<()> {
148+
let from_block: BlockNumberOrTag = from_block.clone().into();
149+
let to_block: BlockNumberOrTag = to_block.clone().into();
148150
log::info!(
149151
"Viewing task ids & statuses between blocks: {} - {}",
150-
from_block.clone().into(),
151-
to_block.clone().into()
152+
from_block
153+
.as_number()
154+
.map(|n| n.to_string())
155+
.unwrap_or(from_block.to_string()),
156+
to_block
157+
.as_number()
158+
.map(|n| n.to_string())
159+
.unwrap_or(to_block.to_string())
152160
);
153161

154162
let task_events = node.get_tasks_in_range(from_block, to_block).await?;

src/compute/workflows/requests/mod.rs

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,8 @@ impl Request {
161161

162162
#[cfg(test)]
163163
mod tests {
164+
use alloy::hex::FromHex;
165+
164166
use super::*;
165167

166168
// only implemented for testing purposes
@@ -257,25 +259,15 @@ mod tests {
257259
}
258260

259261
#[tokio::test]
260-
#[ignore = "run this manually with GPT4oMini vs GPT4o"]
261-
async fn test_erroneous() {
262-
dotenvy::dotenv().unwrap();
263-
let _ = env_logger::builder()
264-
.filter_level(log::LevelFilter::Debug)
265-
.is_test(true)
266-
.try_init();
267-
268-
// this looks like a workflow, but its not parseable so it should give an errror
269-
// let input = "{\"config\":{\"max_steps\":50,\"max_time\":200,\"tools\":[\"ALL\"]},\"external_memory\":{\"backstory\":\"dark hacker\\nlives in the basement of his parents' house\",\"objective\":\"Hacking systems\",\"behaviour\":\"closed but strong\",\"state\":\"\",\"inventory\":[\"Empty Inventory\"]},\"tasks\":[{\"id\":\"simulate\",\"name\":\"State\",\"description\":\"Simulates from the given state to obtain a new state with respect to the given inputs.\",\"prompt\":\"You are a sophisticated 317-dimensional alien world simulator capable of simulating any fictional or non-fictional world with excellent detail. Your task is to simulate one day in the life of a character based on the provided inputs, taking into account every given detail to accurately mimic the created world.\\n\\n---------------------\\n\\nYou just woke up to a new day. When you look at mirror as you wake up, you reflect on yourself and who you are. You are:\\n<backstory>\\n{{backstory}}\\n</backstory>\\n\\nYou remember vividly what drove you in your life. You feel a strong urge to:\\n<objective>\\n{{objective}}\\n</objective>\\n\\n\\nTo be strong and coherent, you repeat out loud how you behave in front of the mirror.\\n<behaviour>\\n{{behaviour}}\\n</behaviour>\\n\\nAs you recall who you are, what you do and your drive is, you write down to a notebook your current progress with your goal:\\n<current_state>\\n{{state}}\\n</current_state>\\n\\nYou look through and see the items in your inventory.\\n<inventory>\\n{{inventory}}\\n</inventory>\\n\\nFirst, an omnipotent being watches you through out the day outlining what you've been through today within your world in <observe> tags. This being is beyond time and space can understand slightest intentions also the complex infinite parameter world around you.\\n\\nYou live another day... It's been a long day and you write down your journal what you've achieved so far today, and what is left with your ambitions. It's only been a day, so you know that you can achieve as much that is possible within a day. \\n\\nWrite this between <journal> tags.\\nStart now:\\n\",\"inputs\":[{\"name\":\"backstory\",\"value\":{\"type\":\"read\",\"key\":\"backstory\"},\"required\":true},{\"name\":\"state\",\"value\":{\"type\":\"read\",\"key\":\"state\"},\"required\":true},{\"name\":\"inventory\",\"value\":{\"type\":\"get_all\",\"key\":\"inventory\"},\"required\":true},{\"name\":\"behaviour\",\"value\":{\"type\":\"read\",\"key\":\"behaviour\"},\"required\":true},{\"name\":\"objective\",\"value\":{\"type\":\"read\",\"key\":\"objective\"},\"required\":true}],\"operator\":\"generation\",\"outputs\":[{\"type\":\"write\",\"key\":\"new_state\",\"value\":\"__result\"}]},{\"id\":\"_end\",\"name\":\"Task\",\"description\":\"Task Description\",\"prompt\":\"\",\"inputs\":[],\"operator\":\"end\",\"outputs\":[]}],\"steps\":[{\"source\":\"simulate\",\"target\":\"_end\"}],\"return_value\":{\"input\":{\"type\":\"read\",\"key\":\"new_state\"},\"to_json\":false}}";
270-
let input = Bytes::from_static(&hex_literal::hex!("36623630613364623161396663353163313532383663396539393664363531626633306535626438363730386262396134636339633863636632393236623266"));
271-
272-
let mut request = Request::try_parse_bytes(&input).await.unwrap();
273-
// this is a wrong Workflow object, so instead of being parsed as Request::Workflow it is parsed as Request::String!
274-
// small models like GPT4oMini will not be able to handle this, and will output mumbo-jumbo at random times, sometimes will return the input itself
275-
// Gpt4o will be able to handle this, it actually understands the task
276-
277-
let output = request.execute(Model::GPT4o, None).await.unwrap();
278-
279-
println!("Output:\n{}", output);
262+
async fn test_arweave_workflow_parser() {
263+
// task 21402 input
264+
// 0x30306234343365613266393739626263353263613565363131376534646366353634366662316365343265663566643363643564646638373533643538323463
265+
let input_bytes = Bytes::from_hex("30306234343365613266393739626263353263613565363131376534646366353634366662316365343265663566643363643564646638373533643538323463").unwrap();
266+
let workflow = Request::try_parse_bytes(&input_bytes).await.unwrap();
267+
if let Request::Workflow(_) = workflow {
268+
/* do nothing */
269+
} else {
270+
panic!("Expected workflow, got something else");
271+
}
280272
}
281273
}

src/configurations/mod.rs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@ use eyre::{Context, Result};
77
use std::env;
88

99
/// Configuration for the Dria Oracle.
10-
///
11-
/// Stores the `EthereumWallet` instance along with the used RPC url.
1210
#[derive(Debug, Clone)]
1311
pub struct DriaOracleConfig {
12+
/// Wallet for the oracle.
1413
pub wallet: EthereumWallet,
14+
/// RPC URL for the oracle, decides the connected chain.
1515
pub rpc_url: Url,
16+
/// Optional transaction timeout, is useful to avoid getting stuck at `get_receipt()` when making a transaction.
17+
pub tx_timeout: Option<std::time::Duration>,
1618
}
1719

1820
impl Default for DriaOracleConfig {
@@ -27,7 +29,19 @@ impl DriaOracleConfig {
2729
PrivateKeySigner::from_bytes(secret_key).wrap_err("Could not parse private key")?;
2830
let wallet = EthereumWallet::from(signer);
2931

30-
Ok(Self { wallet, rpc_url })
32+
Ok(Self {
33+
wallet,
34+
rpc_url,
35+
tx_timeout: None,
36+
})
37+
}
38+
39+
/// Change the transaction timeout.
40+
/// This will make transaction wait for the given duration before timing out,
41+
/// otherwise the node may get stuck waiting for a lost transaction.
42+
pub fn with_tx_timeout(mut self, tx_timeout: std::time::Duration) -> Self {
43+
self.tx_timeout = Some(tx_timeout);
44+
self
3145
}
3246

3347
/// Creates the config from the environment variables.

src/data/arweave.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ impl OracleExternalData for Arweave {
149149
let b64_key = Self::hex_to_base64(key.as_str())?;
150150

151151
let url = self.base_url.join(&b64_key)?;
152+
log::debug!("Fetching from Arweave: {}", url);
152153
let response = self
153154
.client
154155
.get(url)

src/node/coordinator.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,10 @@ impl DriaOracle {
3838
.wrap_err("could not request task")?;
3939

4040
log::info!("Hash: {:?}", tx.tx_hash());
41-
let receipt = tx.get_receipt().await?;
41+
let receipt = tx
42+
.with_timeout(self.config.tx_timeout)
43+
.get_receipt()
44+
.await?;
4245
Ok(receipt)
4346
}
4447

@@ -91,7 +94,10 @@ impl DriaOracle {
9194
let tx = req.send().await.map_err(contract_error_report)?;
9295

9396
log::info!("Hash: {:?}", tx.tx_hash());
94-
let receipt = tx.get_receipt().await?;
97+
let receipt = tx
98+
.with_timeout(self.config.tx_timeout)
99+
.get_receipt()
100+
.await?;
95101
Ok(receipt)
96102
}
97103

@@ -108,7 +114,10 @@ impl DriaOracle {
108114
let tx = req.send().await.map_err(contract_error_report)?;
109115

110116
log::info!("Hash: {:?}", tx.tx_hash());
111-
let receipt = tx.get_receipt().await?;
117+
let receipt = tx
118+
.with_timeout(self.config.tx_timeout)
119+
.get_receipt()
120+
.await?;
112121
Ok(receipt)
113122
}
114123

0 commit comments

Comments
 (0)