Skip to content

Commit 29bf94d

Browse files
authored
Merge pull request #8 from rainshowerLabs/0.3.2-bug-fixes-optimizations
0.3.2 bug fixes optimizations
2 parents 4c1b7fd + b4e0478 commit 29bf94d

File tree

10 files changed

+135
-65
lines changed

10 files changed

+135
-65
lines changed

Cargo.lock

Lines changed: 10 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "sothis"
3-
version = "0.3.1"
3+
version = "0.3.2"
44
edition = "2021"
55
authors = ["makemake <vukasin@gostovic.me>"]
66
license = "GPL-3.0-or-later"
@@ -23,3 +23,4 @@ rlp = "0.5.2"
2323
serde = { version = "1.0.163", features = ["derive"] }
2424
serde_json = "1.0.96"
2525
tokio = { version = "1.28.1", features = ["full"] }
26+
url = "2.4.0"

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,10 +106,11 @@ The result is saved to a JSON file that looks like this:
106106
- `--source_rpc`: RPC of the node we are getting data from.
107107
- `--contract_address`: Address of the contract we are reading storage from.
108108
- `--storage_slot`: The storage slot of the contract.
109+
- `--terminal_block`(optional): Final block sothis will track. If not specified, sothis will track until terminated.
109110
- `--filename`(optional): Name of our output file. The default filename is formatted as: `address-{}-slot-{}-timestamp-{}.json`.
110111
- `--path`(optional): Path to our output file. The default path is the current directory.
111112

112-
Once you are done tracking the slot, terminate the process via a `SIGTERM` or a `SIGINT` (ctrl-c), which will terminate execution and write the file.
113+
Once you are done tracking the slot, terminate the process via a `SIGTERM` or a `SIGINT` (ctrl-c), which will terminate execution and write the file. Keep in mind that sothis will check once per new block if you tried to terminate it. If no new block are produced on the source_rpc, sothis will not terminate and nothing will be written if you force close it.
113114

114115
`sothis --mode track --source_rpc http://localhost:8545 --contract_address 0x1c479675ad559DC151F6Ec7ed3FbF8ceE79582B6 --storage_slot 0 --filename siuuu.json --path ~/Desktop
115116
`

dummy_server.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
from http.server import BaseHTTPRequestHandler, HTTPServer
2+
import json
3+
4+
5+
class DummyRPCHandler(BaseHTTPRequestHandler):
6+
def _set_response(self, status_code=200):
7+
self.send_response(status_code)
8+
self.send_header('Content-type', 'application/json')
9+
self.end_headers()
10+
11+
def do_POST(self):
12+
content_length = int(self.headers['Content-Length'])
13+
request_body = self.rfile.read(content_length)
14+
rpc_request = json.loads(request_body)
15+
16+
if rpc_request['method'] == 'eth_blockNumber':
17+
response = {
18+
"jsonrpc": "2.0",
19+
"id": rpc_request['id'],
20+
"result": "0x123456" # Dummy block number
21+
}
22+
else:
23+
response = {
24+
"jsonrpc": "2.0",
25+
"id": rpc_request['id'],
26+
"error": {
27+
"code": -32601,
28+
"message": "Method not found"
29+
}
30+
}
31+
32+
self._set_response()
33+
self.wfile.write(json.dumps(response).encode())
34+
35+
def do_GET(self):
36+
self._set_response(404)
37+
self.wfile.write(b'Not found')
38+
39+
40+
def run_server():
41+
server_address = ('localhost', 8000)
42+
httpd = HTTPServer(server_address, DummyRPCHandler)
43+
print('Dummy RPC server is running on http://localhost:8000...')
44+
httpd.serve_forever()
45+
46+
47+
if __name__ == '__main__':
48+
run_server()

src/main.rs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ lazy_static! {
3333
#[tokio::main]
3434
async fn main() -> Result<(), Box<dyn std::error::Error>> {
3535
let matches = Command::new("sothis")
36-
.version("0.3.1")
36+
.version("0.3.2")
3737
.author("makemake <vukasin@gostovic.me>")
3838
.about("Tool for replaying historical transactions. Designed to be used with anvil or hardhat.")
3939
.arg(Arg::new("source_rpc")
@@ -131,13 +131,13 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
131131
"historic" => {
132132
println!("Replaying in historic mode...");
133133

134-
let block: String = matches.get_one::<String>("terminal_block").expect("required").to_string();
135-
let block = format_number_input(&block);
134+
let terminal_block: String = matches.get_one::<String>("terminal_block").expect("required").to_string();
135+
let terminal_block = format_number_input(&terminal_block);
136136

137137
let replay_rpc: String = matches.get_one::<String>("replay_rpc").expect("required").to_string();
138138
let replay_rpc = RpcConnection::new(replay_rpc);
139139

140-
replay_historic_blocks(source_rpc, replay_rpc, hex_to_decimal(&block)?).await?;
140+
replay_historic_blocks(source_rpc, replay_rpc, hex_to_decimal(&terminal_block)?).await?;
141141
},
142142
"live" => {
143143
println!("Replaying live blocks...");
@@ -149,16 +149,22 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
149149
}
150150
"track" => {
151151
println!("Tracking state variable...");
152-
println!("Send SIGTERM or SIGINT to serialize to JSON, write and stop.");
152+
println!("Send SIGTERM or SIGINT (ctrl-c) to serialize to JSON, write and stop.");
153153

154154
let contract_address: String = matches.get_one::<String>("contract_address").expect("required").to_string();
155155
let storage_slot: String = matches.get_one::<String>("storage_slot").expect("required").to_string();
156156
let storage_slot = U256::from_dec_str(&storage_slot)?;
157+
158+
// If terminal_block is set by the user use that, otherwise have it be none
159+
let terminal_block: Option<u64> = matches.get_one::<String>("terminal_block").map(|x| x.parse().expect("Invalid terminal block"));
160+
161+
if terminal_block == None {
162+
println!("No terminal block set, tracking indefinitely.");
163+
}
157164

158-
track_state(source_rpc, storage_slot, contract_address).await?;
165+
track_state(source_rpc, storage_slot, contract_address, terminal_block).await?;
159166
}
160167
&_ => {
161-
// handle this properly later
162168
panic!("Mode does not exist!");
163169
},
164170
}

src/replay/replay.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ pub async fn replay_historic_blocks(
4646
let app_config = APP_CONFIG.lock()?;
4747
replay_delay = app_config.replay_delay;
4848
}
49-
50-
while until > replay_block {
49+
50+
loop {
5151
// we write a bit of illegible code
5252
let hex_block = decimal_to_hex(replay_block + 1);
5353
// get block from historical node
@@ -71,8 +71,10 @@ pub async fn replay_historic_blocks(
7171

7272
replay_block = hex_to_decimal(&replay_rpc.block_number().await?)?;
7373

74-
// TODO: For some godforsaken reason i cannot do an infinite loop and break here or else it crashes.
75-
// I feel dirty doing 2 checks for the same thing so you have to wait a bit ig.
74+
if replay_block >= until {
75+
break;
76+
}
77+
7678
sleep(Duration::from_millis(replay_delay));
7779
}
7880
println!("Done replaying blocks");

src/replay/send_transaction.rs

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,52 @@
1+
use crate::rpc::error::RequestError;
12
use crate::RpcConnection;
23
use crate::rpc::types::Transaction;
34
use crate::APP_CONFIG;
45

6+
// Abstract over the return types of send functions
7+
impl RpcConnection {
8+
async fn send(&self, tx: Transaction, chain_id: u64) -> Result<String, RequestError> {
9+
if APP_CONFIG.lock().unwrap().send_as_raw {
10+
self.send_raw_transaction(tx, chain_id).await
11+
} else {
12+
self.send_unsigned_transaction(tx, chain_id).await
13+
}
14+
}
15+
}
16+
517
// Generic function we use to replay all tx in a block.
618
pub async fn send_transactions(
719
replay_rpc: RpcConnection,
820
historical_txs: Vec<Transaction>,
921
chain_id: u64,
1022
) -> Result<(), Box<dyn std::error::Error>> {
11-
let app_config = APP_CONFIG.lock()?;
23+
let exit_on_tx_fail;
24+
let entropy_threshold;
25+
{
26+
let app_config = APP_CONFIG.lock()?;
27+
exit_on_tx_fail = app_config.exit_on_tx_fail;
28+
entropy_threshold = app_config.entropy_threshold;
29+
}
1230

1331
let tx_amount = historical_txs.len() as f32;
1432
let mut fail_tx_amount: f32 = 0.0;
1533

16-
// TODO: This is really bad, please reimplement this
17-
18-
if app_config.send_as_raw {
19-
for tx in historical_txs {
20-
// Gracefully handle errors so execution doesn't halt on error
21-
match replay_rpc.send_raw_transaction(tx, chain_id).await {
22-
Ok(_) => (),
23-
Err(e) => if app_config.exit_on_tx_fail {
24-
return Err(e.into());
25-
} else {
26-
fail_tx_amount += 1.0;
27-
println!("!!! \x1b[93mError sending transaction:\x1b[0m {} !!!", e)
28-
}
29-
}
30-
}
31-
} else {
32-
for tx in historical_txs {
33-
// Gracefully handle errors so execution doesn't halt on error
34-
match replay_rpc.send_unsigned_transaction(tx, chain_id).await {
35-
Ok(_) => (),
36-
Err(e) => if app_config.exit_on_tx_fail {
37-
return Err(e.into());
38-
} else {
39-
fail_tx_amount += 1.0;
40-
println!("!!! \x1b[93mError sending transaction:\x1b[0m {} !!!", e)
41-
}
34+
for tx in historical_txs {
35+
// Gracefully handle errors so execution doesn't halt on error
36+
match replay_rpc.send(tx, chain_id).await {
37+
Ok(_) => (),
38+
Err(e) => if exit_on_tx_fail {
39+
return Err(e.into());
40+
} else {
41+
fail_tx_amount += 1.0;
42+
println!("!!! \x1b[93mError sending transaction:\x1b[0m {} !!!", e)
4243
}
4344
}
4445
}
4546

4647
// Calculate the percentage of failed transactions
4748
let fail_percent = fail_tx_amount / tx_amount;
48-
if fail_percent > app_config.entropy_threshold {
49+
if fail_percent > entropy_threshold {
4950
println!("!!! \x1b[91mHigh entropy detected!\x1b[0m Fail ratio: {}. Consider restarting the fork\x1b[0m !!!", format!("{:.2}%", fail_percent * 100.0));
5051
}
5152

src/rpc/rpc.rs

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
use url::Url;
12
use std::thread::sleep;
3+
use std::time::Instant;
24

35
use tokio::time::Duration;
46
use serde::{Deserialize, Serialize};
@@ -27,27 +29,19 @@ struct JsonRpcResponse {
2729
id: u32,
2830
}
2931

32+
#[derive(Clone)]
3033
pub struct RpcConnection {
3134
client: Client,
3235
url: String,
3336
}
3437

35-
impl Clone for RpcConnection {
36-
fn clone(&self) -> Self {
37-
RpcConnection {
38-
client: self.client.clone(),
39-
url: self.url.clone(),
40-
}
41-
}
42-
}
43-
4438
#[allow(dead_code)]
4539
impl RpcConnection {
4640
// Create client and set url
4741
pub fn new(url: String) -> Self {
4842
Self {
4943
client: Client::new(),
50-
url,
44+
url: Url::parse(&url).expect("REASON").into(),
5145
}
5246
}
5347

@@ -240,9 +234,23 @@ impl RpcConnection {
240234
let mut new_blocknumber = blocknumber.clone();
241235
println!("Listening for new blocks from block {}...", hex_to_decimal(&blocknumber).unwrap());
242236

237+
// Start timer for the *heartbeat*
238+
let mut start_time = Instant::now();
239+
243240
while blocknumber == new_blocknumber {
244-
// sleep for 1 second
241+
// sleep for set duration
245242
sleep(Duration::from_millis(time));
243+
244+
// Add this as a *heartbeat* so users are less confused if nothing is happening
245+
let elapsed_time = start_time.elapsed();
246+
247+
if elapsed_time >= Duration::from_secs(60) {
248+
println!("!!! \x1b[93mNo new blocks have been detected in 60 seconds! Check your node(s)\x1b[0m !!!");
249+
println!("Still listening...");
250+
start_time = Instant::now();
251+
}
252+
253+
246254
new_blocknumber = self.block_number().await?
247255
}
248256

src/rpc/types.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,6 @@ impl Transaction {
7676
// features set to legacy, this is a legacy tx
7777
let mut typed_tx: TypedTransaction = Default::default();
7878

79-
//todo: fix this
80-
// If to doesnt contain a value, set it
8179
match self.to {
8280
Some(_) => {
8381
let address = H160::from_str(&self.to.clone().expect("Can't read `to` field"));

0 commit comments

Comments
 (0)