Skip to content

Commit

Permalink
Add a forc-submit plugin command for submitting txs to a given node (
Browse files Browse the repository at this point in the history
  • Loading branch information
mitchmindtree authored Feb 10, 2023
1 parent 9a00b5a commit 220325d
Show file tree
Hide file tree
Showing 18 changed files with 192 additions and 20 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,8 @@ jobs:
cargo install --locked --debug --path ./forc-plugins/forc-fmt
cargo install --locked --debug --path ./forc-plugins/forc-lsp
cargo install --locked --debug --path ./forc-plugins/forc-client
cargo install --locked --debug --path ./forc-plugins/forc-tx
cargo install --locked --debug --path ./forc-plugins/forc-doc
cargo install --locked --debug --path ./forc-plugins/forc-tx
cargo install --locked --debug forc-explore
- name: Install mdbook-forc-documenter
run: cargo install --locked --debug --path ./scripts/mdbook-forc-documenter
Expand Down
3 changes: 3 additions & 0 deletions Cargo.lock

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

5 changes: 3 additions & 2 deletions docs/book/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ cargo install --path ./scripts/mdbook-forc-documenter
You must also install forc plugins that are already documented within the book. You can skip plugins that are going to be removed and install plugins that are going to be added to the book:

```sh
cargo install --path ./forc-plugins/forc-client
cargo install --path ./forc-plugins/forc-doc
cargo install forc-explore
cargo install --path ./forc-plugins/forc-fmt
cargo install --path ./forc-plugins/forc-lsp
cargo install forc-explore
cargo install --path ./forc-plugins/forc-doc
```

To build book:
Expand Down
1 change: 1 addition & 0 deletions docs/book/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
- [forc client](./forc/plugins/forc_client/index.md)
- [forc deploy](./forc/plugins/forc_client/forc_deploy.md)
- [forc run](./forc/plugins/forc_client/forc_run.md)
- [forc submit](./forc/plugins/forc_client/forc_submit.md)
- [forc doc](./forc/plugins/forc_doc.md)
- [forc explore](./forc/plugins/forc_explore.md)
- [forc fmt](./forc/plugins/forc_fmt.md)
Expand Down
1 change: 1 addition & 0 deletions docs/book/src/forc/plugins/forc_client/forc_submit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# forc submit
11 changes: 9 additions & 2 deletions forc-plugins/forc-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ description = "A `forc` plugin for interacting with a Fuel node."
[dependencies]
anyhow = "1"
async-trait = "0.1.58"
chrono = { version = "0.4", default-features = false, features = ["std"] }
clap = { version = "3", features = ["derive", "env"] }
devault = "0.1"
forc = { version = "0.35.0", path = "../../forc" }
forc-pkg = { version = "0.35.0", path = "../../forc-pkg" }
forc-tracing = { version = "0.35.0", path = "../../forc-tracing" }
Expand All @@ -27,6 +29,7 @@ fuels-types = { workspace = true }
futures = "0.3"
hex = "0.4.3"
serde = "1.0"
serde_json = "1"
sway-core = { version = "0.35.0", path = "../../sway-core" }
sway-types = { version = "0.35.0", path = "../../sway-types" }
sway-utils = { version = "0.35.0", path = "../../sway-utils" }
Expand All @@ -35,11 +38,15 @@ tracing = "0.1"

[[bin]]
name = "forc-deploy"
path = "src/bin/deploy/main.rs"
path = "src/bin/deploy.rs"

[[bin]]
name = "forc-run"
path = "src/bin/run/main.rs"
path = "src/bin/run.rs"

[[bin]]
name = "forc-submit"
path = "src/bin/submit.rs"

[lib]
path = "src/lib.rs"
File renamed without changes.
File renamed without changes.
12 changes: 12 additions & 0 deletions forc-plugins/forc-client/src/bin/submit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use clap::Parser;
use forc_tracing::init_tracing_subscriber;

#[tokio::main]
async fn main() {
init_tracing_subscriber(Default::default());
let command = forc_client::cmd::Submit::parse();
if let Err(err) = forc_client::op::submit(command).await {
tracing::error!("Error: {:?}", err);
std::process::exit(1);
}
}
5 changes: 3 additions & 2 deletions forc-plugins/forc-client/src/cmd/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ pub struct Command {
pub build_output: BuildOutput,
#[clap(flatten)]
pub build_profile: BuildProfile,
/// The node url to deploy, if not specified uses DEFAULT_NODE_URL.
/// If url is specified overrides network url in manifest file (if there is one).
/// The URL of the Fuel node to which we're submitting the transaction.
/// If unspecified, checks the manifest's `network` table, then falls back
/// to [`crate::default::NODE_URL`].
#[clap(long, env = "FUEL_NODE_URL")]
pub node_url: Option<String>,
/// Do not sign the transaction
Expand Down
2 changes: 2 additions & 0 deletions forc-plugins/forc-client/src/cmd/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
pub mod deploy;
pub mod run;
pub mod submit;

pub use deploy::Command as Deploy;
pub use run::Command as Run;
pub use submit::Command as Submit;
9 changes: 6 additions & 3 deletions forc-plugins/forc-client/src/cmd/run.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use clap::Parser;
use fuel_crypto::SecretKey;

pub use super::submit::Network;
pub use forc::cli::shared::{BuildOutput, BuildProfile, Minify, Pkg, Print};
pub use forc_tx::Gas;

Expand All @@ -21,15 +22,17 @@ pub struct Command {
pub build_output: BuildOutput,
#[clap(flatten)]
pub build_profile: BuildProfile,
/// The URL of the Fuel node to which we're submitting the transaction.
/// If unspecified, checks the manifest's `network` table, then falls back
/// to [`crate::default::NODE_URL`].
#[clap(long, env = "FUEL_NODE_URL")]
pub node_url: Option<String>,
/// Hex string of data to input to script.
#[clap(short, long)]
pub data: Option<String>,
/// Only craft transaction and print it out.
#[clap(long)]
pub dry_run: bool,
/// URL of the Fuel Client Node
#[clap(long, env = "FUEL_NODE_URL")]
pub node_url: Option<String>,
/// Pretty-print the outputs from the node.
#[clap(long = "pretty-print", short = 'r')]
pub pretty_print: bool,
Expand Down
42 changes: 42 additions & 0 deletions forc-plugins/forc-client/src/cmd/submit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use devault::Devault;
use std::path::PathBuf;

/// Submit a transaction to the specified fuel node.
#[derive(Debug, Default, clap::Parser)]
#[clap(about, version)]
pub struct Command {
#[clap(flatten)]
pub network: Network,
#[clap(flatten)]
pub tx_status: TxStatus,
/// Path to the Transaction that is to be submitted to the Fuel node.
///
/// Paths to files ending with `.json` will be deserialized from JSON.
/// Paths to files ending with `.bin` will be deserialized from bytes
/// using the `fuel_tx::Transaction::try_from_bytes` constructor.
pub tx_path: PathBuf,
}

/// Options related to networking.
#[derive(Debug, Devault, clap::Args)]
pub struct Network {
/// The URL of the Fuel node to which we're submitting the transaction.
#[clap(long, env = "FUEL_NODE_URL", default_value_t = String::from(crate::default::NODE_URL))]
#[devault("String::from(crate::default::NODE_URL)")]
pub node_url: String,
/// Whether or not to await confirmation that the transaction has been committed.
///
/// When `true`, await commitment and output the transaction status.
/// When `false`, do not await confirmation and simply output the transaction ID.
#[clap(long = "await", default_value_t = true)]
#[devault("true")]
pub await_: bool,
}

/// Options related to the transaction status.
#[derive(Debug, Default, clap::Args)]
pub struct TxStatus {
/// Output the resulting transaction status as JSON rather than the default output.
#[clap(long = "tx-status-json", default_value_t = false)]
pub json: bool,
}
5 changes: 5 additions & 0 deletions forc-plugins/forc-client/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
pub mod cmd;
pub mod op;
mod util;

pub mod default {
/// Default to localhost to favour the common case of testing.
pub const NODE_URL: &str = sway_utils::constants::DEFAULT_NODE_URL;
}
12 changes: 5 additions & 7 deletions forc-plugins/forc-client/src/op/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ use std::path::PathBuf;
use std::time::Duration;
use sway_core::language::parsed::TreeType;
use sway_core::BuildTarget;
use sway_utils::constants::DEFAULT_NODE_URL;
use tracing::info;

pub struct DeployedContract {
Expand Down Expand Up @@ -57,12 +56,11 @@ pub async fn deploy_pkg(
manifest: &PackageManifestFile,
compiled: &BuiltPackage,
) -> Result<DeployedContract> {
let node_url = match &manifest.network {
Some(network) => &network.url,
_ => DEFAULT_NODE_URL,
};

let node_url = command.node_url.as_deref().unwrap_or(node_url);
let node_url = command
.node_url
.as_deref()
.or_else(|| manifest.network.as_ref().map(|nw| &nw.url[..]))
.unwrap_or(crate::default::NODE_URL);
let client = FuelClient::new(node_url)?;

let bytecode = compiled.bytecode.clone().into();
Expand Down
2 changes: 2 additions & 0 deletions forc-plugins/forc-client/src/op/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
mod deploy;
mod run;
mod submit;

pub use deploy::deploy;
pub use run::run;
pub use submit::submit;
4 changes: 1 addition & 3 deletions forc-plugins/forc-client/src/op/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ use sway_core::BuildTarget;
use tokio::time::timeout;
use tracing::info;

pub const NODE_URL: &str = "http://127.0.0.1:4000";

pub struct RanScript {
pub receipts: Vec<fuel_tx::Receipt>,
}
Expand Down Expand Up @@ -66,7 +64,7 @@ pub async fn run_pkg(
.node_url
.as_deref()
.or_else(|| manifest.network.as_ref().map(|nw| &nw.url[..]))
.unwrap_or(NODE_URL);
.unwrap_or(crate::default::NODE_URL);
let client = FuelClient::new(node_url)?;
let contract_ids = command
.contract
Expand Down
96 changes: 96 additions & 0 deletions forc-plugins/forc-client/src/op/submit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
use crate::cmd;
use anyhow::Context;
use fuel_core_client::client::{types::TransactionStatus, FuelClient};

/// A command for submitting transactions to a Fuel network.
pub async fn submit(cmd: cmd::Submit) -> anyhow::Result<()> {
let tx = read_tx(&cmd.tx_path)?;
let client = FuelClient::new(&cmd.network.node_url)?;
if cmd.network.await_ {
let status = client
.submit_and_await_commit(&tx)
.await
.context("Submission of tx or awaiting commit failed")?;
if cmd.tx_status.json {
print_status_json(&status)?;
} else {
print_status(&status);
}
} else {
let id = client.submit(&tx).await.context("Failed to submit tx")?;
println!("{id}");
}
Ok(())
}

/// Deserialize a `Transaction` from the given file into memory.
pub fn read_tx(path: &std::path::Path) -> anyhow::Result<fuel_tx::Transaction> {
let file = std::fs::File::open(path)?;
let reader = std::io::BufReader::new(file);
fn has_extension(path: &std::path::Path, ext: &str) -> bool {
path.extension().and_then(|ex| ex.to_str()) == Some(ext)
}
let tx: fuel_tx::Transaction = if has_extension(path, "json") {
serde_json::from_reader(reader)?
} else if has_extension(path, "bin") {
let tx_bytes = std::fs::read(path)?;
let (_bytes, tx) = fuel_tx::Transaction::try_from_bytes(&tx_bytes)?;
tx
} else {
anyhow::bail!(r#"Unsupported transaction file extension, expected ".json" or ".bin""#);
};
Ok(tx)
}

/// Format the transaction status in a more human-friendly manner.
pub fn fmt_status(status: &TransactionStatus, s: &mut String) -> anyhow::Result<()> {
use chrono::TimeZone;
use std::fmt::Write;
match status {
TransactionStatus::Submitted { submitted_at } => {
writeln!(s, "Transaction Submitted at {:?}", submitted_at.0)?;
}
TransactionStatus::Success {
block_id,
time,
program_state,
} => {
let utc = chrono::Utc.timestamp_nanos(time.to_unix());
writeln!(s, "Transaction Succeeded")?;
writeln!(s, " Block ID: {block_id}")?;
writeln!(s, " Time: {utc}",)?;
writeln!(s, " Program State: {program_state:?}")?;
}
TransactionStatus::SqueezedOut { reason } => {
writeln!(s, "Transaction Squeezed Out: {reason}")?;
}
TransactionStatus::Failure {
block_id,
time,
reason,
program_state,
} => {
let utc = chrono::Utc.timestamp_nanos(time.to_unix());
writeln!(s, "Transaction Failed")?;
writeln!(s, " Reason: {reason}")?;
writeln!(s, " Block ID: {block_id}")?;
writeln!(s, " Time: {utc}")?;
writeln!(s, " Program State: {program_state:?}")?;
}
}
Ok(())
}

/// Print the status to stdout.
pub fn print_status(status: &TransactionStatus) {
let mut string = String::new();
fmt_status(status, &mut string).expect("formatting to `String` is infallible");
println!("{string}");
}

/// Print the status to stdout in its JSON representation.
pub fn print_status_json(status: &TransactionStatus) -> anyhow::Result<()> {
let json = serde_json::to_string_pretty(status)?;
println!("{json}");
Ok(())
}

0 comments on commit 220325d

Please sign in to comment.