diff --git a/.gitignore b/.gitignore index 72a9bf5..b47f4e4 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,6 @@ docs/ /target seed.txt -.env \ No newline at end of file +.env + +.idea/ \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index dfc1aa0..52d477a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -112,7 +112,7 @@ checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.31", ] [[package]] @@ -221,20 +221,16 @@ dependencies = [ [[package]] name = "blockfrost-http-client" -version = "0.0.12" -source = "git+https://github.com/MitchTurner/blockfrost-http-client.git?rev=bbb29df268464a055b17b5ebfae20d5f02165dc0#bbb29df268464a055b17b5ebfae20d5f02165dc0" +version = "0.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c480c18a8cf3fb1f0fc3d596f15ff12972b2771e198bbe5b4a2b989bd6ae49f6" dependencies = [ "async-trait", - "cardano-multiplatform-lib", - "futures", "hex", "reqwest", "serde", "serde_json", - "serde_with", "thiserror", - "tiny-bip39", - "tokio", "toml", "url", ] @@ -253,9 +249,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "cardano-multiplatform-lib" @@ -322,16 +318,15 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.28" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ed24df0632f708f5f6d8082675bef2596f7084dee3dd55f632290bf35bfe0f" +checksum = "defd4e7873dbddba6c7c91e199c7fcb946abc4a6a4ac3195400bcfb01b5de877" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "serde", - "time 0.1.45", "wasm-bindgen", "windows-targets 0.48.5", ] @@ -377,7 +372,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.31", ] [[package]] @@ -476,6 +471,21 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cace84e55f07e7301bae1c519df89cdad8cc3cd868413d3fdbdeca9ff3db484" + [[package]] name = "crossbeam-utils" version = "0.8.16" @@ -487,9 +497,9 @@ dependencies = [ [[package]] name = "crypto-bigint" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4c2f4e1afd912bc40bfd6fed5d9dc1f288e0ba01bfcc835cc5bc3eb13efe15" +checksum = "740fe28e594155f10cfc383984cbefd529d7396050557148f79cb0f621204124" dependencies = [ "generic-array", "rand_core 0.6.4", @@ -534,7 +544,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.29", + "syn 2.0.31", ] [[package]] @@ -545,7 +555,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core", "quote", - "syn 2.0.29", + "syn 2.0.31", ] [[package]] @@ -751,7 +761,7 @@ dependencies = [ [[package]] name = "flat-rs" version = "1.0.16-alpha" -source = "git+https://github.com/aiken-lang/aiken.git#dfe433ea46a114c9ee2a066ec1edf247a2ce31f2" +source = "git+https://github.com/aiken-lang/aiken.git#9782c094b7d917c750dddb180197e8bb24514645" dependencies = [ "anyhow", "thiserror", @@ -804,6 +814,8 @@ dependencies = [ "sha2", "thiserror", "tokio", + "tracing", + "tracing-subscriber", "uplc 1.0.16-alpha (git+https://github.com/aiken-lang/aiken.git)", ] @@ -879,7 +891,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.31", ] [[package]] @@ -931,7 +943,7 @@ checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", ] [[package]] @@ -1242,9 +1254,9 @@ checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "memchr" -version = "2.6.2" +version = "2.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5486aed0026218e61b8a01d5fbd5a0a134649abb71a0e53b7bc088529dced86e" +checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" [[package]] name = "miette" @@ -1275,7 +1287,7 @@ checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.31", ] [[package]] @@ -1321,7 +1333,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "windows-sys 0.48.0", ] @@ -1346,7 +1358,6 @@ dependencies = [ [[package]] name = "naumachia" version = "0.2.0" -source = "git+https://github.com/free-honey/naumachia.git#98e5eaeddd2a98481a4c500d06f4d429c7137952" dependencies = [ "async-trait", "bech32 0.9.1", @@ -1359,7 +1370,7 @@ dependencies = [ "hex", "minicbor", "ogmios-client", - "pallas-addresses 0.19.0-alpha.2", + "pallas-addresses 0.19.0", "rand", "reqwest", "rust-argon2", @@ -1384,6 +1395,16 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num" version = "0.2.1" @@ -1484,9 +1505,9 @@ dependencies = [ [[package]] name = "object" -version = "0.32.0" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ac5bbd07aea88c60a577a1ce218075ffd59208b2d7ca97adf9bfc5aeb21ebe" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" dependencies = [ "memchr", ] @@ -1535,7 +1556,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.31", ] [[package]] @@ -1546,9 +1567,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.92" +version = "0.9.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db7e971c2c2bba161b2d2fdf37080177eff520b3bc044787c7f1f5f9e78d869b" +checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d" dependencies = [ "cc", "libc", @@ -1556,6 +1577,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "owo-colors" version = "3.5.0" @@ -1578,15 +1605,16 @@ dependencies = [ [[package]] name = "pallas-addresses" -version = "0.19.0-alpha.2" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04f247a1085a2fcadddc2a8ff089d39c8e6bd2a20093bb96ec3e4a42f56ee7ce" +checksum = "e653098758663a0b5e911f3a3d07ccdb5ab93cf1a6f911dff9a5ee0e420e13bb" dependencies = [ "base58", "bech32 0.9.1", + "crc", "hex", - "pallas-codec 0.19.0-alpha.2", - "pallas-crypto 0.19.0-alpha.2", + "pallas-codec 0.19.0", + "pallas-crypto 0.19.0", "thiserror", ] @@ -1603,9 +1631,9 @@ dependencies = [ [[package]] name = "pallas-codec" -version = "0.19.0-alpha.2" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e61ff35ab362b1b8ff7575f795808ed158363d531c728111814813e8d26ed9b7" +checksum = "3c87f8960a3abc72a9f8b4c4951fcab82a38afda1afe79333dc53f43ecac71ab" dependencies = [ "hex", "minicbor", @@ -1628,13 +1656,13 @@ dependencies = [ [[package]] name = "pallas-crypto" -version = "0.19.0-alpha.2" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cafad955c9ab4a5a9223c67f7126486b992b7dfc71d25e1ff754152e9c07f73" +checksum = "b87c23d9119e99367adf54858cabfa2feb671ca9e0af70bdd11beb54b5d341d6" dependencies = [ "cryptoxide", "hex", - "pallas-codec 0.19.0-alpha.2", + "pallas-codec 0.19.0", "rand_core 0.6.4", "serde", "thiserror", @@ -1995,9 +2023,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.38.11" +version = "0.38.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0c3dde1fc030af041adc40e79c0e7fbcf431dd24870053d187d7c66e4b87453" +checksum = "bdf14a7a466ce88b5eac3da815b53aefc208ce7e74d1c263aabb04d88c4abeb1" dependencies = [ "bitflags 2.4.0", "errno", @@ -2060,10 +2088,11 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "scrolls-client" version = "0.1.0" -source = "git+https://github.com/free-honey/scrolls-client.git#c47ffce807f8cfd7d176ecb9694e8e8ee48d462f" +source = "git+https://github.com/free-honey/scrolls-client.git#c058a031802027c9bf089ddcb9852ed2871c24b8" dependencies = [ "async-trait", - "pallas-addresses 0.19.0-alpha.2", + "hex", + "pallas-addresses 0.19.0", "redis", "serde", "serde-aux", @@ -2164,7 +2193,7 @@ checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.31", ] [[package]] @@ -2180,9 +2209,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.105" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" +checksum = "2cc66a619ed80bf7a0f6b17dd063a84b88f6dea1813737cf469aef1d081142c2" dependencies = [ "itoa", "ryu", @@ -2214,7 +2243,7 @@ dependencies = [ "serde", "serde_json", "serde_with_macros", - "time 0.3.28", + "time", ] [[package]] @@ -2226,7 +2255,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.31", ] [[package]] @@ -2267,6 +2296,15 @@ dependencies = [ "cc", ] +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + [[package]] name = "shell-words" version = "1.1.0" @@ -2415,9 +2453,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.29" +version = "2.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" +checksum = "718fa2415bcb8d8bd775917a1bf12a7931b6dfa890753378538118181e0cb398" dependencies = [ "proc-macro2", "quote", @@ -2460,33 +2498,32 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.47" +version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f" +checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.47" +version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" +checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.31", ] [[package]] -name = "time" -version = "0.1.45" +name = "thread_local" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", + "cfg-if", + "once_cell", ] [[package]] @@ -2578,7 +2615,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.31", ] [[package]] @@ -2628,9 +2665,21 @@ checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if", "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.31", +] + [[package]] name = "tracing-core" version = "0.1.31" @@ -2638,6 +2687,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", ] [[package]] @@ -2752,7 +2827,7 @@ dependencies = [ [[package]] name = "uplc" version = "1.0.16-alpha" -source = "git+https://github.com/aiken-lang/aiken.git#dfe433ea46a114c9ee2a066ec1edf247a2ce31f2" +source = "git+https://github.com/aiken-lang/aiken.git#9782c094b7d917c750dddb180197e8bb24514645" dependencies = [ "anyhow", "cryptoxide", @@ -2823,9 +2898,15 @@ checksum = "f7e1ba1f333bd65ce3c9f27de592fcbc256dafe3af2717f56d7c87761fbaccf4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.31", ] +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "vcpkg" version = "0.2.15" @@ -2847,12 +2928,6 @@ dependencies = [ "try-lock", ] -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -3133,5 +3208,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.31", ] diff --git a/Cargo.toml b/Cargo.toml index 7abb7dc..dba9aca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,3 +20,5 @@ sha2 = { version = "0.10.6", features = ["asm"] } thiserror = "1.0.40" tokio = { version = "1.28.0", features = ["full"] } num_cpus = "1.16.0" +tracing = "0.1.37" +tracing-subscriber = "0.3.17" diff --git a/src/contract.rs b/src/contract.rs index bde6083..9ed8bc6 100644 --- a/src/contract.rs +++ b/src/contract.rs @@ -1,14 +1,14 @@ use async_trait::async_trait; use chrono::{DateTime, Utc}; +use naumachia::scripts::raw_validator_script::plutus_data::{Constr, PlutusData}; use naumachia::{ address::PolicyId, backend::Backend, ledger_client::LedgerClient, logic::{SCLogic, SCLogicError, SCLogicResult}, scripts::{ - raw_policy_script::RawPolicy, raw_script::BlueprintFile, - raw_validator_script::RawPlutusValidator, MintingPolicy, ScriptError, ScriptResult, - ValidatorCode, + raw_policy_script::RawPolicy, raw_validator_script::RawPlutusValidator, MintingPolicy, + ScriptError, ScriptResult, ValidatorCode, }, smart_contract::{SmartContract, SmartContractTrait}, transaction::TxActions, @@ -16,21 +16,30 @@ use naumachia::{ values::Values, }; use sha2::{Digest, Sha256}; +use thiserror::Error; +use crate::queries::FortunaQuery; use crate::{ datums::State, error, mutations, queries, redeemers::{FortunaRedeemer, InputNonce, MintingState}, + Puzzle, }; -const BLUEPRINT: &str = include_str!("../genesis/mainnet.json"); -const SPEND_VALIDATOR_NAME: &str = "tuna.spend"; -const MINT_VALIDATOR_NAME: &str = "tuna.mint"; +const SCRIPT_VALUES: &str = include_str!("../genesis/mainnet.json"); pub const MASTER_TOKEN_NAME: &str = "lord tuna"; pub const TOKEN_NAME: &str = "TUNA"; pub const CONVERSION_TIME_SEC: u64 = 1691996128; pub const SLOT_OFFSET: u64 = 25325728; +#[derive(Error, Debug)] +pub enum FortunaError { + #[error("No datum found")] + DatumNotFound, + #[error("Output containing NFT not found")] + MasterTokenNotFound, +} + #[derive(Debug, PartialEq, Eq)] pub struct Fortuna; @@ -92,7 +101,7 @@ impl SCLogic for Fortuna { FortunaRedeemer::Mint(MintingState::Genesis), Box::new(mint), ) - .with_valid_range( + .with_valid_range_secs( Some(current_slot_time.try_into().unwrap()), Some((current_slot_time + 90).try_into().unwrap()), ); @@ -144,28 +153,66 @@ impl SCLogic for Fortuna { FortunaRedeemer::Mint(MintingState::Mine), Box::new(mint), ) - .with_valid_range( + .with_valid_range_secs( Some(current_slot_time as i64), Some(current_slot_time as i64 + 90), ); Ok(actions) } + Answer(_) => { + todo!() + } } } async fn lookup>( - _query: Self::Lookups, - _ledger_client: &Record, + query: Self::Lookups, + ledger_client: &Record, ) -> SCLogicResult { - todo!() + match query { + FortunaQuery::CurrentBlock => { + todo!() + } + FortunaQuery::LatestPuzzle => { + let (script, policy) = tuna_validators()?; + let network = ledger_client.network().await?; + let address = script.address(network)?; + let master_token = PolicyId::NativeToken( + policy.id().unwrap(), + Some(MASTER_TOKEN_NAME.to_string()), + ); + + let outputs = ledger_client.all_outputs_at_address(&address).await?; + let input = outputs + .into_iter() + .find(|output| output.values().get(&master_token).is_some()) + .ok_or(FortunaError::MasterTokenNotFound) + .map_err(|e| SCLogicError::Lookup(Box::new(e)))?; + let state = input + .typed_datum() + .ok_or(FortunaError::DatumNotFound) + .map_err(|e| SCLogicError::Lookup(Box::new(e)))?; + let current_difficulty_hash = state.current_hash.clone(); + let untyped_datum: PlutusData = state.into(); + let PlutusData::Constr(Constr { fields, .. }) = untyped_datum else { + unreachable!() + }; + + let puzzle = Puzzle { + current_difficulty_hash, + fields, + }; + Ok(queries::FortunaQueryResponse::Puzzle(puzzle)) + } + } } } pub async fn mutate(mutation: mutations::FortunaMutation) -> error::Result<()> { let ledger_client = get_trireme_ledger_client_from_file().await?; let backend = Backend::new(ledger_client); - let contract = SmartContract::new(&Fortuna, &backend); + let contract = SmartContract::new(Fortuna, backend); contract.hit_endpoint(mutation).await?; @@ -193,29 +240,21 @@ pub fn tuna_validators() -> ScriptResult<( RawPlutusValidator, RawPolicy, )> { - let blueprint: BlueprintFile = serde_json::from_str(BLUEPRINT) + let values: serde_json::Value = serde_json::from_str(SCRIPT_VALUES) .map_err(|e| ScriptError::FailedToConstruct(e.to_string()))?; - let spend_validator_blueprint = - blueprint - .get_validator(SPEND_VALIDATOR_NAME) - .ok_or(ScriptError::FailedToConstruct(format!( - "Validator not listed in Blueprint: {:?}", - SPEND_VALIDATOR_NAME - )))?; - - let mint_validator_blueprint = - blueprint - .get_validator(MINT_VALIDATOR_NAME) - .ok_or(ScriptError::FailedToConstruct(format!( - "Validator not listed in Blueprint: {:?}", - MINT_VALIDATOR_NAME - )))?; - - let raw_spend_script_validator = RawPlutusValidator::from_blueprint(spend_validator_blueprint) + let bytes = + &values + .get("validator") + .and_then(|v| v.as_str()) + .ok_or(ScriptError::FailedToConstruct( + "validator not found".to_string(), + ))?; + + let raw_spend_script_validator = RawPlutusValidator::v2_from_cbor(bytes.to_string()) .map_err(|e| ScriptError::FailedToConstruct(e.to_string()))?; - let raw_mint_script_validator = RawPolicy::from_blueprint(mint_validator_blueprint) + let raw_mint_script_validator = RawPolicy::v2_from_cbor(bytes.to_string()) .map_err(|e| ScriptError::FailedToConstruct(e.to_string()))?; Ok((raw_spend_script_validator, raw_mint_script_validator)) diff --git a/src/lib.rs b/src/lib.rs index a0dede5..d722421 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,3 +6,84 @@ pub mod mutations; pub mod queries; pub mod redeemers; pub mod util; + +pub mod sc_logic; + +use crate::submitter::Submitter; +use crate::updater::Updater; +use crate::worker_manager::WorkerManager; +use naumachia::scripts::raw_validator_script::plutus_data::PlutusData; +use tokio::sync::oneshot; + +pub use crate::error::*; +pub mod submitter; +pub mod updater; +pub mod worker_manager; + +#[cfg(test)] +mod tests; + +pub struct Miner { + updater: U, + submitter: S, + worker_manager: W, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Puzzle { + current_difficulty_hash: Vec, + fields: Vec, +} +#[derive(Clone, Debug)] +pub struct Answer { + #[allow(unused)] + nonce: [u8; 32], + new_hash: Vec, +} + +impl Miner { + pub fn new(updater: U, submitter: S, worker_manager: W) -> Self { + Self { + updater, + submitter, + worker_manager, + } + } + + pub async fn run(&self, mut shutdown: oneshot::Receiver<()>) -> Result<()> { + let (update_sender, update_receiver) = tokio::sync::watch::channel(None); + let (answer_sender, mut answer_receiver) = tokio::sync::mpsc::unbounded_channel(); + self.worker_manager + .start_workers(update_receiver, answer_sender) + .await?; + self.updater.start(update_sender).await?; + + loop { + tokio::select! { + _ = &mut shutdown => { + self.worker_manager.stop_workers().await?; + self.updater.stop().await?; + break; + } + maybe_answer = answer_receiver.recv() => { + if let Some(answer) = maybe_answer { + // TODO: can we start working on the next puzzle while we submit the answer? + match self.submitter.submit(answer).await { + Ok(()) => { + tracing::debug!("answer submitted"); + } + Err(e) => { + tracing::error!("failed to submit answer: {:?}", e); + } + } + } else { + tracing::debug!("no answer received"); + tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; + } + } + } + } + + Ok(()) + } +} diff --git a/src/mutations.rs b/src/mutations.rs index 5dcaa07..79def04 100644 --- a/src/mutations.rs +++ b/src/mutations.rs @@ -1,4 +1,4 @@ -use crate::{datums::State, redeemers::InputNonce}; +use crate::{datums::State, redeemers::InputNonce, Answer}; pub enum FortunaMutation { Genesis { @@ -9,4 +9,5 @@ pub enum FortunaMutation { redeemer: InputNonce, current_slot_time: u64, }, + Answer(Answer), } diff --git a/src/queries.rs b/src/queries.rs index 13fa973..d5602ae 100644 --- a/src/queries.rs +++ b/src/queries.rs @@ -1,7 +1,11 @@ +use crate::Puzzle; + pub enum FortunaQuery { CurrentBlock, + LatestPuzzle, } pub enum FortunaQueryResponse { Block, + Puzzle(Puzzle), } diff --git a/src/sc_logic.rs b/src/sc_logic.rs new file mode 100644 index 0000000..1ab5057 --- /dev/null +++ b/src/sc_logic.rs @@ -0,0 +1,132 @@ +use crate::mutations::FortunaMutation; +use crate::queries::{FortunaQuery, FortunaQueryResponse}; +use crate::submitter::Submitter; +use crate::{ + contract::Fortuna, datums::State, redeemers::FortunaRedeemer, updater::Updater, Puzzle, +}; +use crate::{Answer, Result}; +use naumachia::smart_contract::SmartContractTrait; +use naumachia::{ledger_client::LedgerClient, smart_contract::SmartContract}; +use std::sync::Arc; +use tokio::sync::watch::Sender; + +#[derive(Clone, Debug)] +pub struct FortunaOffChain> { + contract: Arc>, + update_frequency_millis: u64, +} + +impl> FortunaOffChain { + pub fn new(contract: SmartContract, update_frequency_millis: u64) -> Self { + Self { + contract: Arc::new(contract), + update_frequency_millis, + } + } +} + +#[async_trait::async_trait] +impl + 'static> Updater for FortunaOffChain { + async fn start(&self, update_sender: Sender>) -> Result<()> { + let contract = self.contract.clone(); + let update_frequency_millis = self.update_frequency_millis; + tokio::task::spawn(async move { + let mut last_known_puzzle = None; + loop { + match contract.lookup(FortunaQuery::LatestPuzzle).await { + Ok(FortunaQueryResponse::Puzzle(latest_puzzle)) => { + if Some(&latest_puzzle) != last_known_puzzle.as_ref() { + tracing::debug!("sending puzzle: {:?}", &latest_puzzle); + update_sender.send(Some(latest_puzzle.clone())).unwrap(); + last_known_puzzle = Some(latest_puzzle); + } + } + Err(e) => { + tracing::error!("failed to get latest puzzle: {:?}", e); + } + _ => { + tracing::error!("unexpected response from contract"); + } + } + tokio::time::sleep(std::time::Duration::from_millis(update_frequency_millis)).await; + } + }); + Ok(()) + } + + async fn stop(&self) -> Result<()> { + Ok(()) + } +} + +#[async_trait::async_trait] +impl<'a, LC: LedgerClient> Submitter for FortunaOffChain { + async fn submit(&self, answer: Answer) -> Result<()> { + let res = self + .contract + .hit_endpoint(FortunaMutation::Answer(answer.clone())) + .await?; + Ok(res) + } +} + +#[cfg(test)] +mod tests { + #![allow(non_snake_case)] + + use super::*; + use crate::contract::{tuna_validators, MASTER_TOKEN_NAME}; + use naumachia::address::PolicyId; + use naumachia::ledger_client::test_ledger_client::TestBackendsBuilder; + use naumachia::scripts::raw_validator_script::plutus_data::{Constr, PlutusData}; + use naumachia::scripts::{MintingPolicy, ValidatorCode}; + use naumachia::{Address, Network}; + + #[tokio::test] + async fn update__can_get_most_recent_puzzle() { + // given + let (spend, mint) = tuna_validators().unwrap(); + let script_address = spend.address(Network::Testnet).unwrap(); + let master_token = + PolicyId::NativeToken(mint.id().unwrap(), Some(MASTER_TOKEN_NAME.to_string())); + let alice = Address::from_bech32("addr_test1qpuy2q9xel76qxdw8r29skldzc876cdgg9cugfg7mwh0zvpg3292mxuf3kq7nysjumlxjrlsfn9tp85r0l54l29x3qcs7nvyfm").unwrap(); + let current_hash = [1; 32].to_vec(); + + let state = State { + block_number: 0, + difficulty_number: 0, + leading_zeros: 0, + epoch_time: 0, + current_time: 0, + extra: 0, + interlink: vec![], + current_hash: current_hash.clone(), + }; + + let backend = TestBackendsBuilder::new(&alice) + .start_output(&script_address) + .with_value(master_token, 1) + .with_datum(state.clone()) + .finish_output() + .build_in_memory(); + let contract = SmartContract::new(Fortuna, backend); + let updater = FortunaOffChain::new(contract, 10); + let (update_sender, update_receiver) = tokio::sync::watch::channel(None); + + // when + updater.start(update_sender).await.unwrap(); + tokio::time::sleep(std::time::Duration::from_millis(10)).await; + + // then + let PlutusData::Constr(Constr { fields, .. }) = PlutusData::from(state) else { + unreachable!() + }; + + let expected_puzzle = Puzzle { + current_difficulty_hash: current_hash, + fields, + }; + let actual_puzzle = update_receiver.borrow().to_owned().unwrap(); + assert_eq!(actual_puzzle, expected_puzzle); + } +} diff --git a/src/submitter.rs b/src/submitter.rs new file mode 100644 index 0000000..2ac0af0 --- /dev/null +++ b/src/submitter.rs @@ -0,0 +1,7 @@ +use crate::error::Result; +use crate::Answer; + +#[async_trait::async_trait] +pub trait Submitter { + async fn submit(&self, answer: Answer) -> Result<()>; +} diff --git a/src/tests.rs b/src/tests.rs new file mode 100644 index 0000000..563b631 --- /dev/null +++ b/src/tests.rs @@ -0,0 +1,101 @@ +#![allow(non_snake_case)] + +use super::*; +use std::ops::Deref; +use tokio::sync::mpsc::UnboundedSender; +use tokio::sync::watch::Receiver; +use tokio::sync::watch::Sender; + +struct MockUpdater { + puzzle: Puzzle, +} + +#[async_trait::async_trait] +impl Updater for MockUpdater { + async fn start(&self, update_sender: Sender>) -> Result<()> { + tracing::debug!("sending puzzle: {:?}", self.puzzle); + update_sender.send(Some(self.puzzle.clone())).unwrap(); + Ok(()) + } + + async fn stop(&self) -> Result<()> { + tracing::debug!("stopping updater"); + Ok(()) + } +} + +struct MockSubmitter { + answer_channel: UnboundedSender, +} + +#[async_trait::async_trait] +impl Submitter for MockSubmitter { + async fn submit(&self, answer: Answer) -> Result<()> { + tracing::debug!("submitting answer: {:?}", answer); + self.answer_channel.send(answer).unwrap(); + Ok(()) + } +} + +struct MockWorkerManager; + +#[async_trait::async_trait] +impl WorkerManager for MockWorkerManager { + async fn start_workers( + &self, + mut update_receiver: Receiver>, + answer_sender: UnboundedSender, + ) -> Result<()> { + tokio::task::spawn(async move { + while update_receiver.changed().await.is_ok() { + let maybe_puzzle = update_receiver.borrow(); + if let Some(puzzle) = maybe_puzzle.deref() { + tracing::debug!("received puzzle: {:?}", puzzle); + + // Obviously this is nothing like real mining, + // but it's a way to check the values are handled by worker! + // This test doesn't care about the accuracy of the values + let nonce = puzzle.current_difficulty_hash[0..32].try_into().unwrap(); + let answer = Answer { + nonce, + new_hash: puzzle.current_difficulty_hash.clone(), + }; + answer_sender.send(answer).unwrap(); + } + } + }); + Ok(()) + } + + async fn stop_workers(&self) -> Result<()> { + Ok(()) + } +} + +#[tokio::test] +async fn run__updater_puzzle_is_given_to_workers_and_appropriate_answer_given_to_submitter() { + tracing_subscriber::fmt() + .with_max_level(tracing::Level::DEBUG) + .try_init() + .unwrap(); + let puzzle = Puzzle { + current_difficulty_hash: [1; 32].to_vec(), + fields: vec![], + }; + let updater = MockUpdater { + puzzle: puzzle.clone(), + }; + let (answer_sender, mut answer_receiver) = tokio::sync::mpsc::unbounded_channel(); + let submitter = MockSubmitter { + answer_channel: answer_sender, + }; + let worker_manager = MockWorkerManager; + let miner = Miner::new(updater, submitter, worker_manager); + let (shutdown_sender, shutdown_receiver) = tokio::sync::oneshot::channel(); + tokio::task::spawn(async move { + miner.run(shutdown_receiver).await.unwrap(); + }); + let answer = answer_receiver.recv().await.unwrap(); + assert_eq!(answer.new_hash, puzzle.current_difficulty_hash); + shutdown_sender.send(()).unwrap(); +} diff --git a/src/updater.rs b/src/updater.rs new file mode 100644 index 0000000..e1e253b --- /dev/null +++ b/src/updater.rs @@ -0,0 +1,8 @@ +use crate::error::Result; +use crate::Puzzle; + +#[async_trait::async_trait] +pub trait Updater { + async fn start(&self, update_sender: tokio::sync::watch::Sender>) -> Result<()>; + async fn stop(&self) -> Result<()>; +} diff --git a/src/worker_manager.rs b/src/worker_manager.rs new file mode 100644 index 0000000..10ea55f --- /dev/null +++ b/src/worker_manager.rs @@ -0,0 +1,14 @@ +use crate::error::Result; +use crate::{Answer, Puzzle}; +use tokio::sync::mpsc::UnboundedSender; +use tokio::sync::watch::Receiver; + +#[async_trait::async_trait] +pub trait WorkerManager { + async fn start_workers( + &self, + update_receiver: Receiver>, + answer_sender: UnboundedSender, + ) -> Result<()>; + async fn stop_workers(&self) -> Result<()>; +}