diff --git a/Cargo.lock b/Cargo.lock index 989de49a8b8..101c68d1ecb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17192,6 +17192,7 @@ dependencies = [ "ibc-union-spec", "jsonrpsee 0.26.0", "macros", + "proof-lens-light-client-types", "serde", "serde_json", "tokio", @@ -17284,6 +17285,7 @@ dependencies = [ "tokio", "tracing", "unionlabs", + "voyager-core", "voyager-sdk", ] diff --git a/cosmwasm/lightclient/gno/Cargo.toml b/cosmwasm/lightclient/gno/Cargo.toml index c18265ceb5d..d893a5403db 100644 --- a/cosmwasm/lightclient/gno/Cargo.toml +++ b/cosmwasm/lightclient/gno/Cargo.toml @@ -12,7 +12,7 @@ repository = { workspace = true } workspace = true [package.metadata.crane] -# test-include = ["cosmwasm/lightclient/gno/src/test"] +test-include = ["cosmwasm/lightclient/gno/testdata"] [lib] crate-type = ["cdylib", "rlib"] diff --git a/cosmwasm/lightclient/gno/src/client.rs b/cosmwasm/lightclient/gno/src/client.rs index 6f854ec22e8..6afdc844f53 100644 --- a/cosmwasm/lightclient/gno/src/client.rs +++ b/cosmwasm/lightclient/gno/src/client.rs @@ -516,9 +516,8 @@ mod tests { testing::{mock_dependencies, mock_env}, }; use gno_light_client_types::Fraction; - use hex_literal::hex; use ibc_union_spec::ClientId; - use ics23::ibc_api::SDK_SPECS; + use ics23::ibc_api::{self as ics23_api, GNO_SPECS, SDK_SPECS}; use unionlabs::{ encoding::{EncodeAs, EthAbi}, google::protobuf, @@ -624,24 +623,33 @@ mod tests { assert_status!(Frozen); } + /// Real proof captured from the `topaz-1` testnet + /// (`https://rpc.topaz.testnets.gno.land/`) after its main store + /// migrated from IAVL to bptree (`gnolang/gno` PR #5937). [`GNO_SPECS`] + /// must verify it; the old [`SDK_SPECS`] (IAVL) must reject it. #[test] - fn verify_proof() { - // TODO: This is from a dev deployment, update this test with values from an actual testnet or mainnet deployment once there's one live + fn verify_proof_bptree_topaz() { + use unionlabs::cosmos::ics23::commitment_proof::CommitmentProof; - let proof = r#"{"proofs":[{"@type":"exist","@value":{"key":"0x2f70762f766d3a676e6f2e6c616e642f722f636f72652f6962632f76312f636f72653a61366565663765333561626537303236373239363431313437663739313535373363376539376234376566613534366635663665333233303236336263623439","value":"0xfef470406bf3ca4daf4865ed047f1a4b9a49307e5724c21e508a8782f415889d","leaf":{"hash":"sha256","prehash_key":"no_hash","prehash_value":"sha256","length":"var_proto","prefix":"0x000206"},"path":[{"hash":"sha256","prefix":"0x020406206d81723c787f48cdb0fe48017bfeb8c7777c18d2ce768ff3bef8be989d732af920","suffix":"0x0"},{"hash":"sha256","prefix":"0x04060620424eb03f08942ba873f0ad61a3e532dd49f9ebe341c15820b91b2d6f59adb16520","suffix":"0x0"},{"hash":"sha256","prefix":"0x060a06207a6060551a75bb698b17d3eb5b11ef4b6c583d4dd65f074109c5d8ff352d874820","suffix":"0x0"},{"hash":"sha256","prefix":"0x08120620","suffix":"0x20aafb8c883b219159457c342081a41717e117622362092df6aecd69b2e823049b"},{"hash":"sha256","prefix":"0x0a2206207e05a6db10f558103a216d64fb77b010c74e9134995a502d303d1205cbe5ec2220","suffix":"0x0"},{"hash":"sha256","prefix":"0x0c420620f318b74164e6e7317df2eb259a7d3e6d0a81e9b7fc2b2b83d5407b37427a702e20","suffix":"0x0"},{"hash":"sha256","prefix":"0x0e86010620","suffix":"0x20508c3a794d8e22e86e7fa9ea4b1cf59fb581e726c4bec1a7bda9a238c830bb10"},{"hash":"sha256","prefix":"0x1086020620","suffix":"0x2028da987f98245d9cf15ae22917f42e5a2100d86cd7de7243a6a3a02519f2788a"},{"hash":"sha256","prefix":"0x1286040620","suffix":"0x2072afbfb3032a69b75dd4630d95f5916f34d5dd8ed017121f6f96a1e81d680011"},{"hash":"sha256","prefix":"0x14ca090620","suffix":"0x2042fe18a32b874de8f5c4cb122d9539e6b81b1453d991bf6013053975fe0bb975"}]}},{"@type":"exist","@value":{"key":"0x6d61696e","value":"0xf7e8b054c2e090fe8fb5ea96e815e62eb7b6ab710d211b9c9c1769f56d83a888","leaf":{"hash":"sha256","prehash_key":"no_hash","prehash_value":"sha256","length":"var_proto","prefix":"0x00"},"path":[{"hash":"sha256","prefix":"0x01ccb581a002a493db462c72bc97aac085192a8ffb6a45fa5ee3cf22ee89eb1574","suffix":"0x0"}]}}]}"#; + let proof: MerkleProof = + serde_json::from_str(include_str!("../testdata/topaz_bptree_proof.json")).unwrap(); + let CommitmentProof::Exist(existence_proof) = &proof.proofs[0] else { + panic!("expected the first proof to be an existence proof"); + }; + let value = existence_proof.value.to_vec(); + let root = MerkleRoot { + hash: "NACWxFYEYbJdstiUpo11iVtSm2ej46I+Jq1AvBVAHhM=" + .parse() + .unwrap(), + }; + let path = [b"main".to_vec(), b"pkg:gno.land/r/aib/ibc/core".to_vec()]; - verify_membership( - "gno.land/r/core/ibc/v1/core", - &SDK_SPECS, - &MerkleRoot { - hash: "UwihADwTPJO2lMK0aRr41qXcRJO6itbLR/zAKEN4bBo=" - .parse() - .unwrap(), - }, - hex!("a6eef7e35abe7026729641147f7915573c7e97b47efa546f5f6e3230263bcb49").to_vec(), - serde_json::from_str(proof).unwrap(), - hex!("fef470406bf3ca4daf4865ed047f1a4b9a49307e5724c21e508a8782f415889d").into(), - ) - .unwrap(); + ics23_api::verify_membership(&proof, &GNO_SPECS, &root, &path, value.clone()) + .expect("real bptree proof must verify under GNO_SPECS"); + + assert!( + ics23_api::verify_membership(&proof, &SDK_SPECS, &root, &path, value).is_err(), + "a bptree proof must NOT verify under the legacy IAVL-only SDK_SPECS" + ); } } diff --git a/cosmwasm/lightclient/gno/testdata/topaz_bptree_proof.json b/cosmwasm/lightclient/gno/testdata/topaz_bptree_proof.json new file mode 100644 index 00000000000..b11f34b91fe --- /dev/null +++ b/cosmwasm/lightclient/gno/testdata/topaz_bptree_proof.json @@ -0,0 +1 @@ +{"proofs":[{"@type":"exist","@value":{"key":"0x706b673a676e6f2e6c616e642f722f6169622f6962632f636f7265","value":"0x0a04636f72651217676e6f2e6c616e642f722f6169622f6962632f636f72651aae2e0a09524541444d452e6d6412a02e2320722f6169622f6962630a0a42656361757365206d6f7374206f66207468652066756e6374696f6e7320696e2074686973207265616c6d2074616b6520636f6d706c657820617267732c2069742069732072657175697265640a746f2063616c6c207468656d207573696e6720604d736752756e602028606d616b6574782072756e6020776974682074686520434c492920696e7374656164206f6620746865206d6f72650a636f6d6d6f6e6c79207573656420604d736743616c6c602e0a0a4865726520697320616e206578656d706c65206f662074686520636f6d6d616e643a0a0a6060600a2420676e6f6b6579206d616b6574782072756e202d6761732d666565203130303030303075676e6f74202d6761732d77616e746564203930303030303030205c0a202020202d62726f616463617374202d636861696e6964202264657622202d72656d6f746520227463703a2f2f3132372e302e302e313a323636353722205c0a20202020414444524553532072756e2e676e6f0a6060600a0a6072756e2e676e6f6020636f6e74656e7420646570656e6473206f6e207468652063616c6c65642066756e6374696f6e2c207365652074686520666f6c6c6f77696e672073656374696f6e730a666f72206578616d706c65732e0a0a232320437265617465436c69656e740a0a536565205b607a7a5f6372656174655f636c69656e745f6578616d706c655f66696c65746573742e676e6f605d282e2f7a7a5f6372656174655f636c69656e745f6578616d706c655f66696c65746573742e676e6f290a0a456d6974746564206576656e743a0a6060606a736f6e0a7b0a20202274797065223a20226372656174655f636c69656e74222c0a2020226174747273223a205b0a202020207b0a202020202020226b6579223a2022636c69656e745f6964222c0a2020202020202276616c7565223a202230372d74656e6465726d696e742d31220a202020207d2c0a202020207b0a202020202020226b6579223a2022636c69656e745f74797065222c0a2020202020202276616c7565223a202230372d74656e6465726d696e74220a202020207d2c0a202020207b0a202020202020226b6579223a2022636f6e73656e7375735f68656967687473222c0a2020202020202276616c7565223a2022322f32220a202020207d0a20205d2c0a202022706b675f70617468223a2022676e6f2e6c616e642f722f6169622f6962632f636f7265220a7d0a6060600a0a2323205265676973746572436f756e74657270617274790a0a536565205b607a7a5f72656769737465725f636f756e74657270617274795f6578616d706c655f66696c65746573742e676e6f605d282e2f7a7a5f72656769737465725f636f756e74657270617274795f6578616d706c655f66696c65746573742e676e6f290a0a232320557064617465436c69656e740a0a536565205b607a7a5f7570646174655f636c69656e745f6578616d706c655f66696c65746573742e676e6f605d282e2f7a7a5f7570646174655f636c69656e745f6578616d706c655f66696c65746573742e676e6f290a0a456d6974746564206576656e743a0a6060606a736f6e0a7b0a20202274797065223a20227570646174655f636c69656e74222c0a2020226174747273223a205b0a202020207b0a202020202020226b6579223a2022636c69656e745f6964222c0a2020202020202276616c7565223a202230372d74656e6465726d696e742d31220a202020207d2c0a202020207b0a202020202020226b6579223a2022636c69656e745f74797065222c0a2020202020202276616c7565223a202230372d74656e6465726d696e74220a202020207d2c0a202020207b0a202020202020226b6579223a2022636f6e73656e7375735f68656967687473222c0a2020202020202276616c7565223a2022322f35220a202020207d0a20205d2c0a202022706b675f70617468223a2022676e6f2e6c616e642f722f6169622f6962632f636f7265220a7d0a6060600a0a23232053656e645061636b65740a0a536565205b607a7a5f73656e645f7061636b65745f6578616d706c655f66696c65746573742e676e6f605d282e2f7a7a5f73656e645f7061636b65745f6578616d706c655f66696c65746573742e676e6f290a0a456d6974746564206576656e743a0a6060606a736f6e0a5b0a20207b0a202020202274797065223a202273656e645f7061636b6574222c0a20202020226174747273223a205b0a2020202020207b0a2020202020202020226b6579223a20227061636b65745f736f757263655f636c69656e74222c0a20202020202020202276616c7565223a202230372d74656e6465726d696e742d31220a2020202020207d2c0a2020202020207b0a2020202020202020226b6579223a20227061636b65745f646573745f636c69656e74222c0a20202020202020202276616c7565223a2022636f756e7465722d70617274792d6964220a2020202020207d2c0a2020202020207b0a2020202020202020226b6579223a20227061636b65745f73657175656e6365222c0a20202020202020202276616c7565223a202231220a2020202020207d2c0a2020202020207b0a2020202020202020226b6579223a20227061636b65745f74696d656f75745f74696d657374616d70222c0a20202020202020202276616c7565223a202231323334353731343930220a2020202020207d2c0a2020202020207b0a2020202020202020226b6579223a2022656e636f6465645f7061636b65745f686578222c0a20202020202020202276616c7565223a202230383031313230663330333732643734363536653634363537323664363936653734326433313161313036333666373536653734363537323264373036313732373437393264363936343230653261316438636330343261336630613132363736653666326536633631366536343566373235663639363236333631373037303331313230663634363537333734363936653631373436393666366535303666373237343161303237363331323231303631373037303663363936333631373436393666366532663661373336663665326130323762376432613366306131323637366536663265366336313665363435663732356636393632363336313730373033323132306636343635373337343639366536313734363936663665353036663732373431613032373633313232313036313730373036633639363336313734363936663665326636613733366636653261303237623764220a2020202020207d0a202020205d2c0a2020202022706b675f70617468223a2022676e6f2e6c616e642f722f6169622f6962632f636f7265220a20207d2c0a5d0a6060600a0a232320577269746541636b6e6f776c656467656d656e740a0a466f7220746865207374616e646172642073796e6368726f6e6f757320666c6f772c2060526563765061636b6574602077726974657320746865206170706c69636174696f6e0a61636b6e6f776c656467656d656e7420697473656c662e20416e206170706c69636174696f6e206d617920696e73746561642072657475726e0a605061636b65745374617475735f4173796e63602066726f6d20604f6e526563765061636b6574602c20696e2077686963682063617365207468652061636b2069732064656665727265640a756e74696c20746865206170706c69636174696f6e2063616c6c732060577269746541636b6e6f776c656467656d656e7460206c6174657220287479706963616c6c792066726f6d20610a73756273657175656e7420604f6e41636b6e6f776c656467656d656e745061636b657460206f7220604f6e54696d656f75745061636b6574602063616c6c6261636b207768696c650a666f7277617264696e672074686520706172656e74207061636b6574292e204173796e632061636b73206f6e6c7920737570706f72742073696e676c652d7061796c6f61640a7061636b6574732c20616e64206f6e6c7920746865207265616c6d2077686f736520604f6e526563765061636b6574602072657475726e6564206173796e632069730a617574686f72697a656420746f207772697465207468652061636b2e0a0a536565205b607a3130615f6173796e635f61636b5f66696c65746573742e676e6f605d282e2f7a3130615f6173796e635f61636b5f66696c65746573742e676e6f2920666f72207468650a66756c6c204120e28692204220e28692204320666f72776172642d616e642d61636b2063616c6c2067726170682e0a0a456d6974746564206576656e743a0a6060606a736f6e0a7b0a20202274797065223a202277726974655f61636b6e6f776c656467656d656e74222c0a2020226174747273223a205b0a202020207b0a202020202020226b6579223a20227061636b65745f736f757263655f636c69656e74222c0a2020202020202276616c7565223a202230372d74656e6465726d696e742d3432220a202020207d2c0a202020207b0a202020202020226b6579223a20227061636b65745f646573745f636c69656e74222c0a2020202020202276616c7565223a202230372d74656e6465726d696e742d31220a202020207d2c0a202020207b0a202020202020226b6579223a20227061636b65745f73657175656e6365222c0a2020202020202276616c7565223a202231220a202020207d2c0a202020207b0a202020202020226b6579223a20227061636b65745f74696d656f75745f74696d657374616d70222c0a2020202020202276616c7565223a202231323334353731343930220a202020207d2c0a202020207b0a202020202020226b6579223a2022656e636f6465645f7061636b65745f686578222c0a2020202020202276616c7565223a20223038303131323130333033373264373436353665363436353732366436393665373432643334333231613066333033373264373436353665363436353732366436393665373432643331323065326131643863633034326132383061303536313730373034393434313230353631373037303439343431613032373633313232313036313730373036633639363336313734363936663665326636613733366636653261303237623764220a202020207d2c0a202020207b0a202020202020226b6579223a2022656e636f6465645f61636b6e6f776c656467656d656e745f686578222c0a2020202020202276616c7565223a2022306130313031220a202020207d0a20205d2c0a202022706b675f70617468223a2022676e6f2e6c616e642f722f6169622f6962632f636f7265220a7d0a6060600a0a23232041636b6e6f776c656467656d656e740a0a536565205b607a7a5f61636b6e6f776c656467656d656e745f6578616d706c655f66696c65746573742e676e6f605d282e2f7a7a5f61636b6e6f776c656467656d656e745f6578616d706c655f66696c65746573742e676e6f290a0a456d6974746564206576656e743a0a6060606a736f6e0a5b0a20207b0a202020202274797065223a202261636b6e6f776c656467655f7061636b6574222c0a20202020226174747273223a205b0a2020202020207b0a2020202020202020226b6579223a20227061636b65745f736f757263655f636c69656e74222c0a20202020202020202276616c7565223a202230372d74656e6465726d696e742d31220a2020202020207d2c0a2020202020207b0a2020202020202020226b6579223a20227061636b65745f646573745f636c69656e74222c0a20202020202020202276616c7565223a202230372d74656e6465726d696e742d3432220a2020202020207d2c0a2020202020207b0a2020202020202020226b6579223a20227061636b65745f73657175656e6365222c0a20202020202020202276616c7565223a202231220a2020202020207d2c0a2020202020207b0a2020202020202020226b6579223a20227061636b65745f74696d656f75745f74696d657374616d70222c0a20202020202020202276616c7565223a202231323334353731343930220a2020202020207d2c0a2020202020207b0a2020202020202020226b6579223a2022656e636f6465645f7061636b65745f686578222c0a20202020202020202276616c7565223a2022303830313132306633303337326437343635366536343635373236643639366537343264333131613130333033373264373436353665363436353732366436393665373432643334333232306532613164386363303432613330306130333631373037303132306636343635373337343639366536313734363936663665353036663732373431613032373633313232313036313730373036633639363336313734363936663665326636613733366636653261303237623764220a2020202020207d0a202020205d2c0a2020202022706b675f70617468223a2022676e6f2e6c616e642f722f6169622f6962632f636f7265220a20207d0a5d0a6060600a0a2323205265636f766572436c69656e740a0a536565205b607a7a5f7265636f7665725f636c69656e745f6578616d706c655f66696c65746573742e676e6f605d282e2f7a7a5f7265636f7665725f636c69656e745f6578616d706c655f66696c65746573742e676e6f290a0a536565205b607265636f7665722d636c69656e742e6d64605d282e2f7265636f7665722d636c69656e742e6d642920666f722074686520656e642d746f2d656e6420666c6f7720616e640a776869636820706172616d65746572732063616e2062652061646a757374656420647572696e67207265636f766572792e0a0a456d6974746564206576656e743a0a6060606a736f6e0a7b0a20202274797065223a20227265636f7665725f636c69656e74222c0a2020226174747273223a205b0a202020207b0a202020202020226b6579223a20227375626a6563745f636c69656e745f6964222c0a2020202020202276616c7565223a202230372d74656e6465726d696e742d31220a202020207d2c0a202020207b0a202020202020226b6579223a2022737562737469747574655f636c69656e745f6964222c0a2020202020202276616c7565223a202230372d74656e6465726d696e742d32220a202020207d2c0a202020207b0a202020202020226b6579223a2022636c69656e745f74797065222c0a2020202020202276616c7565223a202230372d74656e6465726d696e74220a202020207d0a20205d2c0a202022706b675f70617468223a2022676e6f2e6c616e642f722f6169622f6962632f636f7265220a7d0a6060600a0a23232055706772616465436c69656e740a0a536565205b607a7a5f757067726164655f636c69656e745f6578616d706c655f66696c65746573742e676e6f605d282e2f7a7a5f757067726164655f636c69656e745f6578616d706c655f66696c65746573742e676e6f290a0a536565205b60757067726164652d636c69656e742e6d64605d282e2f757067726164652d636c69656e742e6d642920666f7220746865206c6966656379636c652c207468650a70726f6f662073686170652c20616e6420746865206669656c64206d617070696e67206265747765656e207468652063757272656e7420636c69656e7420616e64207468650a757067726164656420636c69656e742e0a0a456d6974746564206576656e743a0a6060606a736f6e0a7b0a20202274797065223a2022757067726164655f636c69656e74222c0a2020226174747273223a205b0a202020207b0a202020202020226b6579223a2022636c69656e745f6964222c0a2020202020202276616c7565223a202230372d74656e6465726d696e742d31220a202020207d2c0a202020207b0a202020202020226b6579223a2022636c69656e745f74797065222c0a2020202020202276616c7565223a202230372d74656e6465726d696e74220a202020207d2c0a202020207b0a202020202020226b6579223a2022636f6e73656e7375735f686569676874222c0a2020202020202276616c7565223a2022312d313030220a202020207d0a20205d2c0a202022706b675f70617468223a2022676e6f2e6c616e642f722f6169622f6962632f636f7265220a7d0a6060600a1ac0130a0961646d696e2e676e6f12b2137061636b61676520636f72650a0a696d706f727420280a0922636861696e2f72756e74696d652f756e73616665220a0a0922676e6f2e6c616e642f702f6e742f6270747265652f7630220a0922676e6f2e6c616e642f702f6e742f75666d742f7630220a290a0a76617220280a0961646d696e20202020616464726573730a0972656c6179657273203d206270747265652e4e6577425054726565333228290a290a0a66756e6320696e69742829207b0a092f2f205365742061646d696e20746f20746865206465706c6f796572277320454f412e20496e2070726f64756374696f6e2074686973206973206e6f6e2d656d7074792e0a092f2f20496e2074686520676e6f2074657374206672616d65776f726b2074686520626173652073746f7265206973207072652d6c6f6164656420776974680a092f2f204f726967696e43616c6c65723d222220736f2061646d696e20737461797320656d7074793b2053657441646d696e207468656e206163747320617320626f6f7473747261702e0a0961646d696e203d20756e736166652e4f726967696e43616c6c657228290a7d0a0a2f2f2053657441646d696e2073657473206f72207472616e7366657273207468652061646d696e20726f6c652e0a2f2f204966206e6f2061646d696e2069732063757272656e746c7920736574202861646d696e20776173206e6f7420696e697469616c697a656420696e20696e69742c20652e672e20696e0a2f2f207465737473292c207468652066697273742063616c6c6572206265636f6d65732061646d696e2e204f6e6365207365742c206f6e6c79207468652063757272656e742061646d696e0a2f2f2063616e206368616e67652069742e0a66756e632053657441646d696e28637572207265616c6d2c206e657741646d696e206164647265737329207b0a0969662061646d696e20213d202222207b0a0909656e7375726541646d696e43616c6c657228290a097d0a0961646d696e203d206e657741646d696e0a7d0a0a2f2f2041646452656c61796572206164647320616e206164647265737320746f2074686520617574686f72697a65642072656c617965722077686974656c6973742e0a2f2f2043616e206f6e6c792062652063616c6c6564206279207468652061646d696e2e0a66756e632041646452656c6179657228637572207265616c6d2c2061646472206164647265737329207b0a09656e7375726541646d696e43616c6c657228290a0972656c61796572732e53657428737472696e672861646472292c2074727565290a7d0a0a2f2f2052656d6f766552656c617965722072656d6f76657320616e20616464726573732066726f6d2074686520617574686f72697a65642072656c617965722077686974656c6973742e0a2f2f2043616e206f6e6c792062652063616c6c6564206279207468652061646d696e2e0a66756e632052656d6f766552656c6179657228637572207265616c6d2c2061646472206164647265737329207b0a09656e7375726541646d696e43616c6c657228290a0972656c61796572732e52656d6f766528737472696e67286164647229290a7d0a0a2f2f20656e7375726541646d696e43616c6c65722070616e6963732069662074686520454f412074686174207369676e656420746865207478206973206e6f74207468652061646d696e2e0a2f2f205573657320756e736166652e4f726967696e43616c6c657220666f722074782d6c6576656c20454f41206964656e7469747920e280942061646d696e20617574686f726974792069730a2f2f20696e74656e74696f6e616c6c7920626f756e6420746f2074686520454f412074686174207375626d6974746564207468652074782c20776869636820686173206e6f206375720a2f2f206571756976616c656e7420286375722e50726576696f75732829206964656e7469666965732074686520696d6d656469617465206672616d652c206e6f74207468650a2f2f207369676e6572292e204d6972726f7273207468652076322061646d696e207061747465726e20696e20722f676e6f6c616e642f626c6f672f61646d696e2e676e6f2e0a2f2f0a2f2f2054726164652d6f66663a207468697320616c6c6f7773207468652061646d696e20454f4120746f20617574686f72697a652061646d696e206f7073207468726f75676820616e790a2f2f20696e7465726d656469617465207265616c6d2028652e672e20766961206d616b6574782072756e2c206f7220612077726170706572207265616c6d292e20546861742069730a2f2f207468652073616d652074782e6f726967696e207269736b20636c617373206173206072756e74696d652e4f726967696e43616c6c65726020696e2076313b2063616c6c6572730a2f2f2077686f207365742061646d696e20746f20616e20454f41206d75737420656e73757265207468617420454f4120646f6573206e6f742063616c6c20696e746f0a2f2f20756e7472757374656420636f646520696e207468652073616d652074782e0a66756e6320656e7375726541646d696e43616c6c65722829207b0a0963616c6c6572203a3d20756e736166652e4f726967696e43616c6c657228290a0969662063616c6c657220213d2061646d696e207b0a090970616e69632875666d742e537072696e74662822756e617574686f72697a65643a2063616c6c6572202573206973206e6f74207468652061646d696e222c2063616c6c657229290a097d0a7d0a0a2f2f20656e73757265417574686f72697a656452656c617965722070616e6963732069662074686520454f412074686174207369676e656420746865207478206973206e6f7420696e207468650a2f2f2072656c617965722077686974656c6973742e204966207468652077686974656c69737420697320656d70747920616e7920454f4120697320616c6c6f7765642e2052657475726e730a2f2f207468652072656c61796572277320454f4120616464726573732e2053616d6520454f412d62696e64696e6720726174696f6e616c6520617320656e7375726541646d696e43616c6c65722e0a66756e6320656e73757265417574686f72697a656452656c6179657228292061646472657373207b0a0963616c6c6572203a3d20756e736166652e4f726967696e43616c6c657228290a0969662072656c61796572732e53697a652829203d3d2030207b0a090972657475726e2063616c6c65720a097d0a096966202172656c61796572732e48617328737472696e672863616c6c65722929207b0a090970616e69632875666d742e537072696e74662822756e617574686f72697a65642072656c61796572202573222c2063616c6c657229290a097d0a0972657475726e2063616c6c65720a7d0a1af7050a076170702e676e6f12eb057061636b61676520636f72650a0a696d706f727420280a0922676e6f2e6c616e642f702f6169622f6962632f617070220a0922676e6f2e6c616e642f702f6169622f6962632f686f7374220a0922676e6f2e6c616e642f702f6e742f75666d742f7630220a290a0a2f2f2052656769737465724170702072656769737465727320616e204942434170702c2074686520706f727449442069732064657465726d696e6564207573696e6720746865207061636b6167650a2f2f2070617468206f66207468652063616c6c65722c2068656e6365206974206d75737420616e206f74686572207265616c6d2e0a66756e6320526567697374657241707028637572207265616c6d2c20706f7274494420737472696e672c20617070206170702e49424341707029207b0a0963616c6c6572203a3d206375722e50726576696f757328290a0969662063616c6c65722e4973557365722829207b0a090970616e6963282252656769737465724170702063616e206f6e6c792062652063616c6c65642066726f6d20616e206f74686572207265616c6d22290a097d0a09696620657272203a3d20686f73742e506f72744964656e74696669657256616c696461746f7228706f72744944293b2065727220213d206e696c207b0a090970616e69632875666d742e4572726f72662822696e76616c696420706f727449443a202576222c2065727229290a097d0a096966205f2c206f6b203a3d2073746f72652e726f757465735b706f727449445d3b206f6b207b0a090970616e69632875666d742e4572726f72662822706f727449442025732068617320616c7265616479206265656e2072656769737465726564222c20706f7274494429290a097d0a0973746f72652e726f757465735b706f727449445d203d206962634170707b0a09094942434170703a20206170702c0a0909706b67506174683a2063616c6c65722e506b675061746828292c0a0909616464726573733a2063616c6c65722e4164647265737328292c0a097d0a7d0a1ae33c0a0a636c69656e742e676e6f12d43c7061636b61676520636f72650a0a696d706f727420280a0922636861696e220a0922636861696e2f72756e74696d652f756e73616665220a0a0922676e6f2e6c616e642f702f6169622f6962632f686f7374220a0922676e6f2e6c616e642f702f6169622f6962632f6c69676874636c69656e74220a0922676e6f2e6c616e642f702f6169622f6962632f7479706573220a0922676e6f2e6c616e642f702f6e742f75666d742f7630220a290a0a2f2f20437265617465436c69656e742067656e6572617465732061206e657720636c69656e74206964656e74696669657220616e6420696e766f6b657320746865206173736f6369617465640a2f2f206c6967687420636c69656e74206d6f64756c6520696e206f7264657220746f20696e697469616c697a652074686520636c69656e742e0a66756e6320437265617465436c69656e7428637572207265616c6d2c20636c69656e745374617465206c69676874636c69656e742e436c69656e7453746174652c20636f6e73656e7375735374617465206c69676874636c69656e742e436f6e73656e73757353746174652920737472696e67207b0a09696620636c69656e7453746174652e436c69656e7454797065282920213d20636f6e73656e73757353746174652e436c69656e74547970652829207b0a090970616e69632822636c69656e74207479706520666f7220636c69656e7420737461746520616e6420636f6e73656e73757320737461746520646f206e6f74206d6174636822290a097d0a09696620657272203a3d2074797065732e56616c6964617465436c69656e745479706528636c69656e7453746174652e436c69656e74547970652829293b2065727220213d206e696c207b0a090970616e69632875666d742e537072696e7466280a09090922636c69656e74207479706520646f6573206e6f74206d656574206e616d696e6720636f6e73747261696e74733a202576222c206572722c0a090929290a097d0a0a09696620657272203a3d20636c69656e7453746174652e56616c6964617465426173696328293b2065727220213d206e696c207b0a090970616e696328657272290a097d0a09696620657272203a3d20636f6e73656e73757353746174652e56616c6964617465426173696328293b2065727220213d206e696c207b0a090970616e696328657272290a097d0a0972656c61796572203a3d20656e73757265417574686f72697a656452656c6179657228290a0963203a3d2073746f72652e616464436c69656e7428636c69656e7453746174652e436c69656e745479706528292c2072656c61796572290a09657272203a3d20632e6c69676874436c69656e742e496e697469616c697a6528636c69656e7453746174652c20636f6e73656e7375735374617465290a0969662065727220213d206e696c207b0a090970616e696328657272290a097d0a09696620737461747573203a3d20632e6c69676874436c69656e742e53746174757328293b2073746174757320213d206c69676874636c69656e742e416374697665207b0a090970616e69632875666d742e537072696e7466282263616e6e6f742063726561746520636c69656e742028257329207769746820737461747573202573222c20632e69642c2073746174757329290a097d0a092f2f20456d69742063726561746520636c69656e74206576656e740a09636861696e2e456d69742874797065732e4576656e7454797065437265617465436c69656e742c0a090974797065732e4174747269627574654b6579436c69656e7449442c20632e69642c0a090974797065732e4174747269627574654b6579436c69656e74547970652c20632e7479702c0a090974797065732e4174747269627574654b6579436f6e73656e737573486569676874732c20632e6c69676874436c69656e742e4c617465737448656967687428292e537472696e6728292c0a09290a0972657475726e20632e69640a7d0a0a2f2f20436c69656e744944732072657475726e7320746865206c697374206f66206b6e6f776e20636c69656e74206964656e746966696572732c20736f72746564206c65786963616c6c792062790a2f2f206964202874686520756e6465726c79696e6720622b74726565206f72646572292e20496e74656e64656420666f7220726561642d6f6e6c792063616c6c65727320285549732c0a2f2f206f74686572207265616c6d73206275696c64696e672074786c696e6b73292e0a66756e6320436c69656e744944732829205b5d737472696e67207b0a09696473203a3d206d616b65285b5d737472696e672c20302c2073746f72652e636c69656e74427949442e53697a652829290a0973746f72652e636c69656e74427949442e4974657261746542794f666673657428302c2073746f72652e636c69656e74427949442e53697a6528292c2066756e63286b20737472696e672c205f20616e792920626f6f6c207b0a0909696473203d20617070656e64286964732c206b290a090972657475726e2066616c73650a097d290a0972657475726e206964730a7d0a0a2f2f20436c69656e744c61746573744865696768742072657475726e7320746865206c61746573742028686967686573742920636f6e73656e737573206865696768742074686520676976656e0a2f2f20636c69656e7420686173206265656e207570646174656420746f2c20666f726d617474656420617320223c7265766973696f6e3e2f3c6865696768743e222e2054686973206973207468650a2f2f20636c69656e742773206c61746573742074727573746564206865696768742e2052657475726e732022222069662074686520636c69656e7420697320756e6b6e6f776e2e0a2f2f20496e74656e64656420666f7220726561642d6f6e6c792063616c6c65727320285549732920746861742077616e7420746f207375726661636520686f772075702d746f2d6461746520610a2f2f20636c69656e742069732e0a66756e6320436c69656e744c617465737448656967687428636c69656e74494420737472696e672920737472696e67207b0a0963203a3d2073746f72652e676574436c69656e7428636c69656e744944290a0969662063203d3d206e696c207b0a090972657475726e2022220a097d0a0972657475726e20632e6c69676874436c69656e742e4c617465737448656967687428292e537472696e6728290a7d0a0a2f2f20436c69656e745374617475732072657475726e732074686520737461747573206f662074686520676976656e20636c69656e7420284163746976652c20457870697265642c0a2f2f2046726f7a656e2c202e2e2e292e2052657475726e732022222069662074686520636c69656e7420697320756e6b6e6f776e2e20496e74656e64656420666f7220726561642d6f6e6c790a2f2f2063616c6c65727320285549732920746861742077616e7420746f20737572666163652077686574686572206120636c69656e7420697320757361626c652e0a66756e6320436c69656e7453746174757328636c69656e74494420737472696e672920737472696e67207b0a0963203a3d2073746f72652e676574436c69656e7428636c69656e744944290a0969662063203d3d206e696c207b0a090972657475726e2022220a097d0a0972657475726e20632e6c69676874436c69656e742e53746174757328290a7d0a0a2f2f205265676973746572436f756e74657270617274792077696c6c207265676973746572207468652049424320763220636f756e746572706172747920696e666f20666f72207468650a2f2f20676976656e20636c69656e7449442e204974206d7573742062652063616c6c6564206279207468652073616d652072656c6179657220746861742063616c6c65640a2f2f20437265617465436c69656e742e0a66756e63205265676973746572436f756e746572706172747928637572207265616c6d2c20636c69656e74494420737472696e672c20636f756e74657270617274794d65726b6c65507265666978205b5d5b5d627974652c20636f756e7465727061727479436c69656e74494420737472696e6729207b0a096966202174797065732e497356616c6964436c69656e74494428636f756e7465727061727479436c69656e74494429207b0a090970616e69632822696e76616c696420636f756e746572706172747920636c69656e7420696422290a097d0a0963203a3d2073746f72652e676574436c69656e7428636c69656e744944290a0969662063203d3d206e696c207b0a090970616e69632875666d742e537072696e74662822636c69656e74202573206e6f7420666f756e64222c20636c69656e74494429290a097d0a0963616c6c6572203a3d20756e736166652e4f726967696e43616c6c657228290a09696620632e63726561746f7220213d2063616c6c6572207b0a090970616e69632875666d742e537072696e7466282265787065637465642073616d65207369676e657220617320437265617465436c69656e74207375626d69747465722025732c20676f74202573222c20632e63726561746f722c2063616c6c657229290a097d0a09696620632e636f756e7465727061727479436c69656e74494420213d202222207b0a090970616e6963282263616e6e6f7420726567697374657220636f756e7465727061727479206f6e636520697420697320616c72656164792073657422290a097d0a09632e636f756e7465727061727479436c69656e744944203d20636f756e7465727061727479436c69656e7449440a09632e636f756e74657270617274794d65726b6c65507265666978203d20636f756e74657270617274794d65726b6c655072656669780a7d0a0a2f2f20557064617465436c69656e742077696c6c207570646174652074686520676976656e20494243207632206c6967687420636c69656e7420776974682061206e6577206865616465722e0a2f2f2043616e20616c736f206265207573656420746f207375626d69742061206d69736265686176696f722028636c69656e744d6573736167652063616e206265206120686561646572206f720a2f2f2061206d69736265686176696f722c206d617962652073706c697420696e746f20322066756e6374696f6e7320776f756c64206d616b65206d6f72652073656e73652068657265292e0a66756e6320557064617465436c69656e7428637572207265616c6d2c20636c69656e74494420737472696e672c20636c69656e744d657373616765206c69676874636c69656e742e436c69656e744d65737361676529207b0a09696620657272203a3d20636c69656e744d6573736167652e56616c6964617465426173696328293b2065727220213d206e696c207b0a090970616e696328657272290a097d0a0963203a3d2073746f72652e676574436c69656e7428636c69656e744944290a0969662063203d3d206e696c207b0a090970616e69632875666d742e537072696e74662822636c69656e74202573206e6f7420666f756e64222c20636c69656e74494429290a097d0a09656e73757265417574686f72697a656452656c6179657228290a09696620632e74797020213d20636c69656e744d6573736167652e436c69656e74547970652829207b0a090970616e69632822636c69656e74207479706520666f7220636c69656e7420737461746520616e6420636c69656e74206d65737361676520646f206e6f74206d6174636822290a097d0a09696620737461747573203a3d20632e6c69676874436c69656e742e53746174757328293b2073746174757320213d206c69676874636c69656e742e416374697665207b0a090970616e69632875666d742e537072696e7466282263616e6e6f742075706461746520636c69656e742028257329207769746820737461747573202573222c20632e69642c2073746174757329290a097d0a0a09657272203a3d20632e6c69676874436c69656e742e566572696679436c69656e744d65737361676528636c69656e744d657373616765290a0969662065727220213d206e696c207b0a090970616e696328657272290a097d0a0a09666f756e644d69736265686176696f72203a3d20632e6c69676874436c69656e742e436865636b466f724d69736265686176696f757228636c69656e744d657373616765290a09696620666f756e644d69736265686176696f72207b0a0909632e6c69676874436c69656e742e55706461746553746174654f6e4d69736265686176696f757228636c69656e744d657373616765290a0a0909636861696e2e456d69742874797065732e4576656e74547970655375626d69744d69736265686176696f75722c0a09090974797065732e4174747269627574654b6579436c69656e7449442c20632e69642c0a09090974797065732e4174747269627574654b6579436c69656e74547970652c20632e7479702c0a0909290a090972657475726e0a097d0a0a09636f6e73656e73757348656967687473203a3d20632e6c69676874436c69656e742e557064617465537461746528636c69656e744d657373616765290a0a092f2f20456d69742075706461746520636c69656e74206576656e740a0976617220636f6e73656e7375734865696768747353747220737472696e670a09666f7220692c2068203a3d2072616e676520636f6e73656e73757348656967687473207b0a0909636f6e73656e73757348656967687473537472202b3d20682e537472696e6728290a090969662069203c206c656e28636f6e73656e73757348656967687473292d31207b0a090909636f6e73656e73757348656967687473537472202b3d20222c20220a09097d0a097d0a09636861696e2e456d69742874797065732e4576656e7454797065557064617465436c69656e742c0a090974797065732e4174747269627574654b6579436c69656e7449442c20632e69642c0a090974797065732e4174747269627574654b6579436c69656e74547970652c20632e7479702c0a090974797065732e4174747269627574654b6579436f6e73656e737573486569676874732c20636f6e73656e737573486569676874735374722c0a09290a7d0a0a2f2f2055706772616465436c69656e742075706772616465732074686520636c69656e7420746f2061206e657720636c69656e7420616e6420636f6e73656e7375732073746174652c0a2f2f2076657269666965642062792070726f6f667320746861742074686520636f756e746572706172747920636861696e20636f6d6d697474656420746f2074686f7365207374617465730a2f2f2061742069747320557067726164655061746820756e646572207468652063757272656e7420636c69656e742773206c617465737420636f6e73656e73757320726f6f742e0a66756e632055706772616465436c69656e7428637572207265616c6d2c20636c69656e74494420737472696e672c20636c69656e7453746174652c20636f6e73656e73757353746174652c2070726f6f6655706772616465436c69656e742c2070726f6f6655706772616465436f6e73656e737573537461746520616e7929207b0a09696620657272203a3d20686f73742e436c69656e744964656e74696669657256616c696461746f7228636c69656e744944293b2065727220213d206e696c207b0a090970616e696328657272290a097d0a09656e73757265417574686f72697a656452656c6179657228290a0963203a3d2073746f72652e676574436c69656e7428636c69656e744944290a0969662063203d3d206e696c207b0a090970616e69632875666d742e537072696e74662822636c69656e74202573206e6f7420666f756e64222c20636c69656e74494429290a097d0a09696620737461747573203a3d20632e6c69676874436c69656e742e53746174757328293b2073746174757320213d206c69676874636c69656e742e416374697665207b0a090970616e69632875666d742e537072696e7466282263616e6e6f74207570677261646520636c69656e742028257329207769746820737461747573202573222c20632e69642c2073746174757329290a097d0a09696620657272203a3d20632e6c69676874436c69656e742e56657269667955706772616465416e64557064617465537461746528636c69656e7453746174652c20636f6e73656e73757353746174652c2070726f6f6655706772616465436c69656e742c2070726f6f6655706772616465436f6e73656e7375735374617465293b2065727220213d206e696c207b0a090970616e696328657272290a097d0a09636861696e2e456d69742874797065732e4576656e745479706555706772616465436c69656e742c0a090974797065732e4174747269627574654b6579436c69656e7449442c20632e69642c0a090974797065732e4174747269627574654b6579436c69656e74547970652c20632e7479702c0a090974797065732e4174747269627574654b6579436f6e73656e7375734865696768742c20632e6c69676874436c69656e742e4c617465737448656967687428292e537472696e6728292c0a09290a7d0a0a2f2f205265636f766572436c69656e74207265636f7665727320612066726f7a656e206f722065787069726564207375626a65637420636c69656e74207573696e672061206865616c7468790a2f2f207375627374697475746520636c69656e74207468617420747261636b73207468652073616d6520636f756e746572706172747920636861696e2e0a66756e63205265636f766572436c69656e7428637572207265616c6d2c207375626a656374436c69656e7449442c2073756273746974757465436c69656e74494420737472696e6729207b0a09656e7375726541646d696e43616c6c657228290a096966207375626a656374436c69656e744944203d3d2073756273746974757465436c69656e744944207b0a090970616e696328227375626a65637420616e64207375627374697475746520636c69656e7420494473206d7573742064696666657222290a097d0a097375626a656374203a3d2073746f72652e676574436c69656e74287375626a656374436c69656e744944290a096966207375626a656374203d3d206e696c207b0a090970616e69632875666d742e537072696e746628227375626a65637420636c69656e74202573206e6f7420666f756e64222c207375626a656374436c69656e74494429290a097d0a0973756273746974757465203a3d2073746f72652e676574436c69656e742873756273746974757465436c69656e744944290a0969662073756273746974757465203d3d206e696c207b0a090970616e69632875666d742e537072696e746628227375627374697475746520636c69656e74202573206e6f7420666f756e64222c2073756273746974757465436c69656e74494429290a097d0a096966207375626a6563742e74797020213d20737562737469747574652e747970207b0a090970616e69632875666d742e537072696e7466280a090909227375626a65637420636c69656e74207479706520257320646f6573206e6f74206d61746368207375627374697475746520636c69656e742074797065202573222c0a0909097375626a6563742e7479702c20737562737469747574652e7479702c0a090929290a097d0a09696620737461747573203a3d207375626a6563742e6c69676874436c69656e742e53746174757328293b2073746174757320213d206c69676874636c69656e742e46726f7a656e2026262073746174757320213d206c69676874636c69656e742e45787069726564207b0a090970616e69632875666d742e537072696e7466280a0909092263616e6e6f74207265636f766572207375626a65637420636c69656e74202573207769746820737461747573202573222c0a0909097375626a6563742e69642c207374617475732c0a090929290a097d0a09696620737461747573203a3d20737562737469747574652e6c69676874436c69656e742e53746174757328293b2073746174757320213d206c69676874636c69656e742e416374697665207b0a090970616e69632875666d742e537072696e7466280a090909227375627374697475746520636c69656e74202573206d757374206265204163746976652c20676f74202573222c0a090909737562737469747574652e69642c207374617475732c0a090929290a097d0a09696620657272203a3d207375626a6563742e6c69676874436c69656e742e5265636f766572436c69656e7428737562737469747574652e6c69676874436c69656e74293b2065727220213d206e696c207b0a090970616e696328657272290a097d0a09636861696e2e456d69742874797065732e4576656e74547970655265636f766572436c69656e742c0a090974797065732e4174747269627574654b65795375626a656374436c69656e7449442c207375626a6563742e69642c0a090974797065732e4174747269627574654b657953756273746974757465436c69656e7449442c20737562737469747574652e69642c0a090974797065732e4174747269627574654b6579436c69656e74547970652c207375626a6563742e7479702c0a09290a7d0a1a9d7d0a08636f72652e676e6f12907d7061636b61676520636f72650a0a696d706f727420280a09226279746573220a0922636861696e220a0922656e636f64696e672f686578220a092274696d65220a0a0922676e6f2e6c616e642f702f6169622f6962632f686f7374220a0922676e6f2e6c616e642f702f6169622f6962632f6c69676874636c69656e74220a0922676e6f2e6c616e642f702f6169622f6962632f7479706573220a0922676e6f2e6c616e642f702f6e742f75666d742f7630220a290a0a636f6e7374206d617854696d656f757444656c74612074696d652e4475726174696f6e203d203234202a2074696d652e486f75720a0a66756e632053656e645061636b657428637572207265616c6d2c206d73672074797065732e4d736753656e645061636b657429202873657175656e63652075696e74363429207b0a09696620657272203a3d206d73672e56616c6964617465426173696328293b2065727220213d206e696c207b0a090970616e696328657272290a097d0a09636c69656e744944203a3d206d73672e536f75726365436c69656e740a0963203a3d2073746f72652e676574436c69656e7428636c69656e744944290a0969662063203d3d206e696c207b0a090970616e69632875666d742e537072696e74662822636c69656e74202573206e6f7420666f756e64222c20636c69656e74494429290a097d0a09696620737461747573203a3d20632e6c69676874436c69656e742e53746174757328293b2073746174757320213d206c69676874636c69656e742e416374697665207b0a090970616e69632875666d742e537072696e74662822636c69656e74202825732920737461747573206973202573222c20636c69656e7449442c2073746174757329290a097d0a0a092f2f20456e7375726520636f756e746572706172747920686173206265656e20726567697374657265640a09696620632e636f756e7465727061727479436c69656e744944203d3d202222207b0a090970616e69632875666d742e537072696e74662822636f756e7465727061727479206e6f7420666f756e6420666f7220636c69656e74202573222c20636c69656e74494429290a097d0a0a092f2f2074696d656f757454696d657374616d70206d7573742062652067726561746572207468616e2063757272656e7420626c6f636b2074696d6520616e64206c657373206f7220657175616c0a092f2f20746f2063757272656e7420626c6f636b2074696d65202b206d617854696d656f757444656c74612e0a0976617220280a090974696d656f7574202020203d2074696d652e556e697828696e743634286d73672e54696d656f757454696d657374616d70292c2030290a09096d696e54696d656f7574203d2074696d652e4e6f7728290a09096d617854696d656f7574203d206d696e54696d656f75742e416464286d617854696d656f757444656c7461290a09290a096966202174696d656f75742e4166746572286d696e54696d656f757429207b0a090970616e69632875666d742e537072696e7466280a0909092274696d656f7574206973206c657373207468616e206f7220657175616c207468652063757272656e7420626c6f636b2074696d657374616d702c202564203c3d202564222c0a0909096d73672e54696d656f757454696d657374616d702c206d696e54696d656f75742e556e697828292c0a090929290a097d0a0969662074696d656f75742e4166746572286d617854696d656f757429207b0a090970616e69632875666d742e537072696e7466280a0909092274696d656f757420697320616674657220746865206d617820616c6c6f7765642074696d656f75742c202564203e202564222c0a0909096d73672e54696d656f757454696d657374616d702c206d617854696d656f75742e556e697828292c0a090929290a097d0a0a092f2f20636865636b20696620746865206c617465737420636f6e73656e7375732074696d657374616d70206973206c6f776572207468616e2074696d656f757454696d657374616d700a096c617465737454696d657374616d702c20657272203a3d20632e6c69676874436c69656e742e54696d657374616d70417448656967687428632e6c69676874436c69656e742e4c61746573744865696768742829290a0969662065727220213d206e696c207b0a090970616e696328657272290a097d0a096966206c617465737454696d657374616d70203e3d206d73672e54696d656f757454696d657374616d70207b0a090970616e69632875666d742e4572726f726628226c61746573742074696d657374616d703a2025642c2074696d656f75742074696d657374616d703a202564222c0a0909096c617465737454696d657374616d702c206d73672e54696d656f757454696d657374616d7029290a097d0a0a092f2f20636f6e737472756374207061636b65742066726f6d20676976656e206669656c64730a0973657175656e6365203d2075696e74363428632e73656e645365712e4e6578742829290a097061636b6574203a3d2074797065732e4e65775061636b65742873657175656e63652c206d73672e536f75726365436c69656e742c20632e636f756e7465727061727479436c69656e7449442c206d73672e54696d656f757454696d657374616d702c206d73672e5061796c6f6164732e2e2e290a09696620657272203a3d207061636b65742e56616c6964617465426173696328293b2065727220213d206e696c207b0a090970616e69632875666d742e4572726f72662822636f6e7374727563746564207061636b6574206661696c65642062617369632076616c69646174696f6e3a202576222c2065727229290a097d0a0a092f2f2073657420746865207061636b657420636f6d6d69746d656e740a09632e7365745061636b6574436f6d6d69746d656e742873657175656e63652c207061636b6574290a0a092f2f20656d6974206576656e74730a09636861696e2e456d69742874797065732e4576656e745479706553656e645061636b65742c0a090974797065732e4174747269627574654b6579537263436c69656e742c207061636b65742e536f75726365436c69656e742c0a090974797065732e4174747269627574654b6579447374436c69656e742c207061636b65742e44657374696e6174696f6e436c69656e742c0a090974797065732e4174747269627574654b657953657175656e63652c2075666d742e537072696e746628222564222c207061636b65742e53657175656e6365292c0a090974797065732e4174747269627574654b657954696d656f757454696d657374616d702c2075666d742e537072696e746628222564222c207061636b65742e54696d656f757454696d657374616d70292c0a090974797065732e4174747269627574654b6579456e636f6465645061636b65744865782c206865782e456e636f6465546f537472696e67287061636b65742e50726f746f4d61727368616c2829292c0a09290a0a092f2f20496e766f6b6520726567697374656420617070204f6e53656e645061636b6574282920666f722065616368207061796c6f61642e536f75726365506f72742e0a09666f7220692c207061796c6f6164203a3d2072616e6765206d73672e5061796c6f616473207b0a0909617070203a3d2073746f72652e726f757465287061796c6f61642e536f75726365506f7274290a0909657272203a3d206170702e4f6e53656e645061636b6574280a09090963726f737328637572292c206d73672e536f75726365436c69656e742c20632e636f756e7465727061727479436c69656e7449442c2073657175656e63652c207061796c6f61642c0a0909290a090969662065727220213d206e696c207b0a09090970616e69632875666d742e537072696e7466280a090909092273656e64207061636b6574206661696c656420666f72207061796c6f616420232564206170702025713a202576222c20692c207061796c6f61642e536f75726365506f72742c206572722c0a09090929290a09097d0a097d0a0a0972657475726e2073657175656e63650a7d0a0a66756e6320526563765061636b657428637572207265616c6d2c206d73672074797065732e4d7367526563765061636b6574292074797065732e526573706f6e7365526573756c7454797065207b0a09696620657272203a3d206d73672e56616c6964617465426173696328293b2065727220213d206e696c207b0a090970616e696328657272290a097d0a0a09636c69656e744944203a3d206d73672e5061636b65742e44657374696e6174696f6e436c69656e740a0963203a3d2073746f72652e676574436c69656e7428636c69656e744944290a0969662063203d3d206e696c207b0a090970616e69632875666d742e537072696e74662822636c69656e74202573206e6f7420666f756e64222c20636c69656e74494429290a097d0a09656e73757265417574686f72697a656452656c6179657228290a092f2f20636865636b20636c69656e74206973206163746976650a09696620737461747573203a3d20632e6c69676874436c69656e742e53746174757328293b2073746174757320213d206c69676874636c69656e742e416374697665207b0a090970616e69632875666d742e537072696e74662822636c69656e74202825732920737461747573206973202573222c20636c69656e7449442c2073746174757329290a097d0a092f2f20636865636b20636f756e74657270617274790a09696620632e636f756e7465727061727479436c69656e74494420213d206d73672e5061636b65742e536f75726365436c69656e74207b0a090970616e69632875666d742e537072696e7466280a09090922636f756e7465727061727479206964202825732920646f6573206e6f74206d61746368207061636b657420736f757263652069642028257329222c0a090909632e636f756e7465727061727479436c69656e7449442c206d73672e5061636b65742e536f75726365436c69656e742c0a090929290a097d0a0963757272656e7454696d657374616d70203a3d2075696e7436342874696d652e4e6f7728292e556e69782829290a0969662063757272656e7454696d657374616d70203e3d206d73672e5061636b65742e54696d656f757454696d657374616d70207b0a090970616e69632875666d742e537072696e7466280a0909092263757272656e742074696d657374616d703a2025642c2074696d656f75742074696d657374616d703a202564222c0a09090963757272656e7454696d657374616d702c206d73672e5061636b65742e54696d656f757454696d657374616d702c0a090929290a097d0a0a092f2f205245504c41592050524f54454354494f4e3a205061636b65742072656365697074732077696c6c20696e64696361746520746861742061207061636b65742068617320616c72656164790a092f2f206265656e2072656365697665642e0a092f2f205061636b6574207265636569707473206d757374206e6f74206265207072756e65642c20756e6c65737320697420686173206265656e206d61726b6564207374616c65206279207468650a092f2f20696e637265617365206f66207468652072656376537461727453657175656e63652e20544f444f20636865636b2072656c6576616e6379206f6620636f6d6d656e740a09696620632e6861735061636b657452656365697074286d73672e5061636b65742e53657175656e636529207b0a09092f2f2054686973206572726f7220696e64696361746573207468617420746865207061636b65742068617320616c7265616479206265656e2072656c617965642e20436f7265204942430a09092f2f2077696c6c2074726561742074686973206572726f722061732061206e6f2d6f7020696e206f7264657220746f2070726576656e7420616e20656e746972652072656c61790a09092f2f207472616e73616374696f6e2066726f6d206661696c696e6720616e6420636f6e73756d696e6720756e6e656365737361727920666565732e0a090972657475726e2074797065732e524553504f4e53455f4e4f4f500a097d0a0a092f2f20566572696679206578697374656e6365206f662074686520636f6d6d69746d656e7420627974657320696e207468652070726f6f66732e0a0976617220280a09096b657920202020202020203d20686f73742e5061636b6574436f6d6d69746d656e744b6579286d73672e5061636b65742e536f75726365436c69656e742c206d73672e5061636b65742e53657175656e6365290a09096d65726b6c6550617468203d2074797065732e4275696c644d65726b6c655061746828632e636f756e74657270617274794d65726b6c655072656669782c206b6579290a090976616c75652020202020203d2074797065732e436f6d6d69745061636b6574286d73672e5061636b6574290a09290a09696620657272203a3d20632e6c69676874436c69656e742e5665726966794d656d62657273686970280a09096d73672e50726f6f664865696768742c206d73672e50726f6f66436f6d6d69746d656e742c206d65726b6c65506174682c2076616c75652c0a09293b2065727220213d206e696c207b0a090970616e69632875666d742e537072696e7466280a090909226661696c6564207061636b657420636f6d6d69746d656e7420766572696669636174696f6e20666f7220636c69656e7420282573293a202576222c0a090909636c69656e7449442c206572722c0a090929290a097d0a0a092f2f20536574205061636b6574205265636569707420746f2070726576656e742074696d656f75742066726f6d206f6363757272696e67206f6e20636f756e74657270617274790a09632e7365745061636b657452656365697074286d73672e5061636b65742e53657175656e6365290a0a092f2f20456d6974206576656e74730a09636861696e2e456d69742874797065732e4576656e7454797065526563765061636b65742c0a090974797065732e4174747269627574654b6579537263436c69656e742c206d73672e5061636b65742e536f75726365436c69656e742c0a090974797065732e4174747269627574654b6579447374436c69656e742c206d73672e5061636b65742e44657374696e6174696f6e436c69656e742c0a090974797065732e4174747269627574654b657953657175656e63652c2075666d742e537072696e746628222564222c206d73672e5061636b65742e53657175656e6365292c0a090974797065732e4174747269627574654b657954696d656f757454696d657374616d702c2075666d742e537072696e746628222564222c206d73672e5061636b65742e54696d656f757454696d657374616d70292c0a090974797065732e4174747269627574654b6579456e636f6465645061636b65744865782c206865782e456e636f6465546f537472696e67286d73672e5061636b65742e50726f746f4d61727368616c2829292c0a09290a0a09632e7772697465526563765061636b657441636b6e6f776c656467656d656e7428302c206375722c206d73672e5061636b6574290a0a0972657475726e2074797065732e524553504f4e53455f535543434553530a7d0a0a2f2f207772697465526563765061636b657441636b6e6f776c656467656d656e742069732061206e6f6e2d63726f7373696e672068656c7065723a20726c6d206973207468652063616c6c657227730a2f2f206c697665206375722c2074687265616465642061732061206e6f6e2d666972737420706172616d207669612074686520605f20696e742c20726c6d207265616c6d6020736861706520736f0a2f2f207468652063726f737320746f2065616368206170702773204f6e526563765061636b65742063616e206265206973737565642066726f6d20526563765061636b65742773206672616d652e0a66756e63202863202a636c69656e7429207772697465526563765061636b657441636b6e6f776c656467656d656e74285f20696e742c20726c6d207265616c6d2c207061636b65742074797065732e5061636b657429207b0a0976617220280a09092f2f206275696c6420757020746865207265637620726573756c747320666f722065616368206170706c69636174696f6e2063616c6c6261636b2e0a090961636b203d2074797065732e41636b6e6f776c656467656d656e747b0a09090941707041636b6e6f776c656467656d656e74733a205b5d5b5d627974657b7d2c0a09097d0a0909697353756363657373203d20747275650a09290a0a092f2f20496e766f6b6520726567697374656420617070204f6e526563765061636b6574282920666f722065616368207061796c6f61642e44657374696e6174696f6e506f72742e0a09666f7220692c207061796c6f6164203a3d2072616e6765207061636b65742e5061796c6f616473207b0a0909617070203a3d2073746f72652e726f757465287061796c6f61642e44657374696e6174696f6e506f7274290a0909726573203a3d206170702e4f6e526563765061636b6574280a09090963726f737328726c6d292c207061636b65742e536f75726365436c69656e742c207061636b65742e44657374696e6174696f6e436c69656e742c0a0909097061636b65742e53657175656e63652c207061796c6f61642c0a0909290a09096966207265732e537461747573203d3d2074797065732e5061636b65745374617475735f4661696c757265207b0a090909697353756363657373203d2066616c73650a0909092f2f20636f6e7374727563742061636b6e6f776c656467656d656e7420776974682073696e676c65206170702061636b6e6f776c656467656d656e742074686174206973207468650a0909092f2f2073656e74696e656c206572726f722061636b6e6f776c656467656d656e740a09090961636b203d2074797065732e41636b6e6f776c656467656d656e747b0a0909090941707041636b6e6f776c656467656d656e74733a205b5d5b5d627974657b74797065732e556e6976657273616c4572726f7241636b6e6f776c656467656d656e7428297d2c0a0909097d0a090909627265616b0a09097d0a09096966207265732e537461747573203d3d2074797065732e5061636b65745374617475735f4173796e63207b0a0909096966206c656e287061636b65742e5061796c6f61647329203e2031207b0a0909090970616e696328226173796e632061636b206e6f7420737570706f7274656420666f72206d756c74692d7061796c6f6164207061636b65747322290a0909097d0a090909632e7361766550656e64696e674173796e6341636b287061636b65742c206170702e706b6750617468290a09090972657475726e0a09097d0a0a09092f2f207375636365737366756c206170702061636b6e6f776c656467656d656e742063616e6e6f7420657175616c2073656e74696e656c206572726f720a09092f2f2061636b6e6f776c656467656d656e740a090969662062797465732e457175616c287265732e41636b6e6f776c656467656d656e742c2074797065732e556e6976657273616c4572726f7241636b6e6f776c656467656d656e74282929207b0a09090970616e69632875666d742e537072696e7466280a090909092263616c6c6261636b206572726f7220666f72207061796c6f616420232564206170702025713a206170706c69636174696f6e2061636b6e6f776c656467656d656e742063616e6e6f742062652073656e74696e656c206572726f722061636b6e6f776c656467656d656e74222c0a09090909692c207061796c6f61642e44657374696e6174696f6e506f72742c0a09090929290a09097d0a0a09092f2f20617070656e64206170702061636b6e6f776c656467656d656e7420746f20746865206f766572616c6c2061636b6e6f776c656467656d656e740a090961636b2e41707041636b6e6f776c656467656d656e7473203d20617070656e642861636b2e41707041636b6e6f776c656467656d656e74732c207265732e41636b6e6f776c656467656d656e74290a097d0a0a092f2f2053616e69747920636865636b20746f20656e737572652072657475726e65642061636b6e6f776c656467656d656e7420616e642063616c63756c61746564206973537563636573730a092f2f20626f6f6c65616e206d6174636865730a0969662061636b2e53756363657373282920213d20697353756363657373207b0a090970616e6963282261636b6e6f776c656467656d656e74207375636365737320666c6167206d69736d6174636822290a097d0a09696620657272203a3d2061636b2e56616c696461746528293b2065727220213d206e696c207b0a090970616e696328657272290a097d0a092f2f20736574207468652061636b6e6f776c656467656d656e7420736f20746861742069742063616e206265207665726966696564206f6e20746865206f7468657220736964650a09632e7365745061636b657441636b6e6f776c656467656d656e74287061636b65742e53657175656e63652c2074797065732e436f6d6d697441636b6e6f776c656467656d656e742861636b29290a09656d6974577269746541636b6e6f776c656467656d656e74287061636b65742c2061636b290a7d0a0a66756e6320577269746541636b6e6f776c656467656d656e7428637572207265616c6d2c20636c69656e74494420737472696e672c2073657175656e63652075696e7436342c2061636b2074797065732e41636b6e6f776c656467656d656e7429207b0a0963203a3d2073746f72652e676574436c69656e7428636c69656e744944290a0969662063203d3d206e696c207b0a090970616e69632875666d742e537072696e74662822636c69656e74202573206e6f7420666f756e64222c20636c69656e74494429290a097d0a0a0970656e64696e672c20666f756e64203a3d20632e67657450656e64696e674173796e6341636b2873657175656e6365290a0969662021666f756e64207b0a090970616e69632875666d742e537072696e746628226e6f2070656e64696e67206173796e632061636b20666f7220636c69656e743d25732073657175656e63653d2564222c20636c69656e7449442c2073657175656e636529290a097d0a0a0963616c6c657250617468203a3d206375722e50726576696f757328292e506b675061746828290a0969662063616c6c65725061746820213d2070656e64696e672e617070506b6750617468207b0a090970616e69632875666d742e537072696e7466280a0909092263616c6c6572202573206973206e6f7420617574686f72697a656420746f2077726974652061636b20666f722073657175656e63652025642028657870656374656420257329222c0a09090963616c6c6572506174682c2073657175656e63652c2070656e64696e672e617070506b67506174682c0a090929290a097d0a0a09696620632e6861735061636b657441636b6e6f776c656467656d656e742873657175656e636529207b0a090970616e69632875666d742e537072696e7466282261636b6e6f776c656467656d656e7420616c7265616479207772697474656e20666f722073657175656e6365202564222c2073657175656e636529290a097d0a09696620657272203a3d2061636b2e56616c696461746528293b2065727220213d206e696c207b0a090970616e696328657272290a097d0a0a09632e7365745061636b657441636b6e6f776c656467656d656e742873657175656e63652c2074797065732e436f6d6d697441636b6e6f776c656467656d656e742861636b29290a09632e64656c65746550656e64696e674173796e6341636b2873657175656e6365290a09656d6974577269746541636b6e6f776c656467656d656e742870656e64696e672e7061636b65742c2061636b290a7d0a0a66756e6320656d6974577269746541636b6e6f776c656467656d656e74287061636b65742074797065732e5061636b65742c2061636b2074797065732e41636b6e6f776c656467656d656e7429207b0a09636861696e2e456d69742874797065732e4576656e7454797065577269746541636b2c0a090974797065732e4174747269627574654b6579537263436c69656e742c207061636b65742e536f75726365436c69656e742c0a090974797065732e4174747269627574654b6579447374436c69656e742c207061636b65742e44657374696e6174696f6e436c69656e742c0a090974797065732e4174747269627574654b657953657175656e63652c2075666d742e537072696e746628222564222c207061636b65742e53657175656e6365292c0a090974797065732e4174747269627574654b657954696d656f757454696d657374616d702c2075666d742e537072696e746628222564222c207061636b65742e54696d656f757454696d657374616d70292c0a090974797065732e4174747269627574654b6579456e636f6465645061636b65744865782c206865782e456e636f6465546f537472696e67287061636b65742e50726f746f4d61727368616c2829292c0a090974797065732e4174747269627574654b6579456e636f64656441636b4865782c206865782e456e636f6465546f537472696e672861636b2e50726f746f4d61727368616c2829292c0a09290a7d0a0a66756e632041636b6e6f776c656467656d656e7428637572207265616c6d2c206d73672074797065732e4d736741636b6e6f776c656467656d656e74292074797065732e526573706f6e7365526573756c7454797065207b0a09696620657272203a3d206d73672e56616c6964617465426173696328293b2065727220213d206e696c207b0a090970616e696328657272290a097d0a0a09636c69656e744944203a3d206d73672e5061636b65742e536f75726365436c69656e740a0963203a3d2073746f72652e676574436c69656e7428636c69656e744944290a0969662063203d3d206e696c207b0a090970616e69632875666d742e537072696e74662822636c69656e74202573206e6f7420666f756e64222c20636c69656e74494429290a097d0a09656e73757265417574686f72697a656452656c6179657228290a092f2f20636865636b20636c69656e74206973206163746976650a09696620737461747573203a3d20632e6c69676874436c69656e742e53746174757328293b2073746174757320213d206c69676874636c69656e742e416374697665207b0a090970616e69632875666d742e537072696e74662822636c69656e74202825732920737461747573206973202573222c20636c69656e7449442c2073746174757329290a097d0a092f2f20636865636b20636f756e74657270617274790a09696620632e636f756e7465727061727479436c69656e74494420213d206d73672e5061636b65742e44657374696e6174696f6e436c69656e74207b0a090970616e69632875666d742e537072696e7466280a09090922636f756e7465727061727479206964202825732920646f6573206e6f74206d61746368207061636b65742064657374696e6174696f6e2069642028257329222c0a090909632e636f756e7465727061727479436c69656e7449442c206d73672e5061636b65742e44657374696e6174696f6e436c69656e742c0a090929290a097d0a0a09636f6d6d69746d656e74203a3d20632e6765745061636b6574436f6d6d69746d656e74286d73672e5061636b65742e53657175656e6365290a096966206c656e28636f6d6d69746d656e7429203d3d2030207b0a09092f2f2054686973206572726f7220696e646963617465732074686174207468652061636b6e6f776c656467656d656e742068617320616c7265616479206265656e2072656c617965640a09092f2f206f722074686572652069732061206d6973636f6e666967757265642072656c6179657220617474656d7074696e6720746f2070726f766520616e0a09092f2f2061636b6e6f776c656467656d656e7420666f722061207061636b6574206e657665722073656e742e20436f7265204942432077696c6c2074726561742074686973206572726f720a09092f2f2061732061206e6f2d6f7020696e206f7264657220746f2070726576656e7420616e20656e746972652072656c6179207472616e73616374696f6e2066726f6d206661696c696e670a09092f2f20616e6420636f6e73756d696e6720756e6e656365737361727920666565732e0a090972657475726e2074797065732e524553504f4e53455f4e4f4f500a097d0a097061636b6574436f6d6d69746d656e74203a3d2074797065732e436f6d6d69745061636b6574286d73672e5061636b6574290a092f2f20656e7375726520696e74656772697479206f6620636f6d6d69746d656e740a096966202162797465732e457175616c28636f6d6d69746d656e742c207061636b6574436f6d6d69746d656e7429207b0a090968312c206832203a3d206865782e456e636f6465546f537472696e67287061636b6574436f6d6d69746d656e74292c206865782e456e636f6465546f537472696e6728636f6d6d69746d656e74290a090970616e69632875666d742e537072696e7466280a09090922636f6d6d69746d656e7420627974657320617265206e6f7420657175616c3a20676f7420282576292c2065787065637465642028257629222c2068312c2068322c0a090929290a097d0a0a092f2f20566572696679206578697374656e6365206f66207468652061636b6e6f776c656467656d656e7420636f6d6d69746d656e7420627974657320696e207468652070726f6f66732e0a0976617220280a09096b657920202020202020203d20686f73742e5061636b657441636b6e6f776c656467656d656e744b6579286d73672e5061636b65742e44657374696e6174696f6e436c69656e742c206d73672e5061636b65742e53657175656e6365290a09096d65726b6c6550617468203d2074797065732e4275696c644d65726b6c655061746828632e636f756e74657270617274794d65726b6c655072656669782c206b6579290a090976616c75652020202020203d2074797065732e436f6d6d697441636b6e6f776c656467656d656e74286d73672e41636b6e6f776c656467656d656e74290a09290a09696620657272203a3d20632e6c69676874436c69656e742e5665726966794d656d62657273686970280a09096d73672e50726f6f664865696768742c206d73672e50726f6f6641636b65642c206d65726b6c65506174682c2076616c75652c0a09293b2065727220213d206e696c207b0a090970616e69632875666d742e537072696e7466280a090909226661696c6564207061636b65742061636b6e6f776c656467656d656e7420766572696669636174696f6e20666f7220636c69656e7420282573293a202576222c0a090909636c69656e7449442c206572722c0a090929290a097d0a0a09632e64656c6574655061636b6574436f6d6d69746d656e74286d73672e5061636b65742e53657175656e6365290a0a09636861696e2e456d69742874797065732e4576656e745479706541636b6e6f776c656467655061636b65742c0a090974797065732e4174747269627574654b6579537263436c69656e742c206d73672e5061636b65742e536f75726365436c69656e742c0a090974797065732e4174747269627574654b6579447374436c69656e742c206d73672e5061636b65742e44657374696e6174696f6e436c69656e742c0a090974797065732e4174747269627574654b657953657175656e63652c2075666d742e537072696e746628222564222c206d73672e5061636b65742e53657175656e6365292c0a090974797065732e4174747269627574654b657954696d656f757454696d657374616d702c2075666d742e537072696e746628222564222c206d73672e5061636b65742e54696d656f757454696d657374616d70292c0a090974797065732e4174747269627574654b6579456e636f6465645061636b65744865782c206865782e456e636f6465546f537472696e67286d73672e5061636b65742e50726f746f4d61727368616c2829292c0a09290a0a092f2f20496e766f6b6520726567697374656420617070204f6e41636b6e6f776c656467656d656e745061636b6574282920666f722065616368207061796c6f61642e536f75726365506f72742e0a097265637653756363657373203a3d202162797465732e457175616c286d73672e41636b6e6f776c656467656d656e742e41707041636b6e6f776c656467656d656e74735b305d2c0a090974797065732e556e6976657273616c4572726f7241636b6e6f776c656467656d656e742829290a09666f7220692c207061796c6f6164203a3d2072616e6765206d73672e5061636b65742e5061796c6f616473207b0a0909617070203a3d2073746f72652e726f757465287061796c6f61642e536f75726365506f7274290a09092f2f206966207265637620776173207375636365737366756c2c2065616368207061796c6f61642073686f756c64206861766520697473206f776e2061636b6e6f776c656467656d656e740a09092f2f20736f2077652073656e64206561636820696e646976696475616c2061636b6e6f776c6564676d656e7420746f20746865206170706c69636174696f6e206f74686572776973652c0a09092f2f207468652061636b6e6f776c656467656d656e74206f6e6c7920636f6e7461696e73207468652073656e74696e656c206572726f722061636b6e6f776c656467656d656e740a09092f2f2077686963682077652073656e6420746f20746865206170706c69636174696f6e2e20546865206170706c69636174696f6e20697320726573706f6e7369626c6520666f720a09092f2f206b6e6f77696e672074686174207468697320697320616e206572726f722061636b6e6f776c656467656d656e7420616e6420657865637574696e67207468650a09092f2f20617070726f707269617465206c6f6769632e0a09097661722061636b205b5d627974650a09096966207265637653756363657373207b0a09090961636b203d206d73672e41636b6e6f776c656467656d656e742e41707041636b6e6f776c656467656d656e74735b695d0a09097d20656c7365207b0a09090961636b203d2074797065732e556e6976657273616c4572726f7241636b6e6f776c656467656d656e7428290a09097d0a0909657272203a3d206170702e4f6e41636b6e6f776c656467656d656e745061636b6574280a09090963726f737328637572292c206d73672e5061636b65742e536f75726365436c69656e742c206d73672e5061636b65742e44657374696e6174696f6e436c69656e742c0a0909096d73672e5061636b65742e53657175656e63652c2061636b2c207061796c6f61642c0a0909290a090969662065727220213d206e696c207b0a09090970616e69632875666d742e537072696e7466280a090909092261636b6e6f776c656467656d656e74207061636b6574206661696c656420666f72207061796c6f616420232564206170702025713a202576222c0a09090909692c207061796c6f61642e536f75726365506f72742c206572722c0a09090929290a09097d0a097d0a0a0972657475726e2074797065732e524553504f4e53455f535543434553530a7d0a0a66756e632054696d656f757428637572207265616c6d2c206d73672074797065732e4d736754696d656f7574292074797065732e526573706f6e7365526573756c7454797065207b0a09696620657272203a3d206d73672e56616c6964617465426173696328293b2065727220213d206e696c207b0a090970616e696328657272290a097d0a0a09636c69656e744944203a3d206d73672e5061636b65742e536f75726365436c69656e740a0963203a3d2073746f72652e676574436c69656e7428636c69656e744944290a0969662063203d3d206e696c207b0a090970616e69632875666d742e537072696e74662822636c69656e74202573206e6f7420666f756e64222c20636c69656e74494429290a097d0a09656e73757265417574686f72697a656452656c6179657228290a092f2f20636865636b20636c69656e74206973206163746976650a09696620737461747573203a3d20632e6c69676874436c69656e742e53746174757328293b2073746174757320213d206c69676874636c69656e742e416374697665207b0a090970616e69632875666d742e537072696e74662822636c69656e74202825732920737461747573206973202573222c20636c69656e7449442c2073746174757329290a097d0a092f2f20636865636b20636f756e74657270617274790a09696620632e636f756e7465727061727479436c69656e74494420213d206d73672e5061636b65742e44657374696e6174696f6e436c69656e74207b0a090970616e69632875666d742e537072696e7466280a09090922636f756e7465727061727479206964202825732920646f6573206e6f74206d61746368207061636b65742064657374696e6174696f6e2069642028257329222c0a090909632e636f756e7465727061727479436c69656e7449442c206d73672e5061636b65742e44657374696e6174696f6e436c69656e742c0a090929290a097d0a092f2f20636865636b20746861742074696d656f75742074696d657374616d702068617320706173736564206f6e20746865206f7468657220656e640a0970726f6f6654696d657374616d702c20657272203a3d20632e6c69676874436c69656e742e54696d657374616d704174486569676874286d73672e50726f6f66486569676874290a0969662065727220213d206e696c207b0a090970616e696328657272290a097d0a0969662070726f6f6654696d657374616d70203c206d73672e5061636b65742e54696d656f757454696d657374616d70207b0a090970616e69632875666d742e4572726f7266282270726f6f662074696d657374616d703a2025642c2074696d656f75742074696d657374616d703a202564222c0a09090970726f6f6654696d657374616d702c206d73672e5061636b65742e54696d656f757454696d657374616d7029290a097d0a0a09636f6d6d69746d656e74203a3d20632e6765745061636b6574436f6d6d69746d656e74286d73672e5061636b65742e53657175656e6365290a096966206c656e28636f6d6d69746d656e7429203d3d2030207b0a09092f2f2054686973206572726f7220696e646963617465732074686174207468652074696d656f75742068617320616c7265616479206265656e2072656c61796564206f722074686572650a09092f2f2069732061206d6973636f6e666967757265642072656c6179657220617474656d7074696e6720746f2070726f766520612074696d656f757420666f722061207061636b65740a09092f2f206e657665722073656e742e20436f7265204942432077696c6c2074726561742074686973206572726f722061732061206e6f2d6f7020696e206f7264657220746f0a09092f2f2070726576656e7420616e20656e746972652072656c6179207472616e73616374696f6e2066726f6d206661696c696e6720616e6420636f6e73756d696e670a09092f2f20756e6e656365737361727920666565732e0a090972657475726e2074797065732e524553504f4e53455f4e4f4f500a097d0a097061636b6574436f6d6d69746d656e74203a3d2074797065732e436f6d6d69745061636b6574286d73672e5061636b6574290a092f2f20656e7375726520696e74656772697479206f6620636f6d6d69746d656e740a096966202162797465732e457175616c28636f6d6d69746d656e742c207061636b6574436f6d6d69746d656e7429207b0a090968312c206832203a3d206865782e456e636f6465546f537472696e67287061636b6574436f6d6d69746d656e74292c206865782e456e636f6465546f537472696e6728636f6d6d69746d656e74290a090970616e69632875666d742e537072696e7466280a09090922636f6d6d69746d656e7420627974657320617265206e6f7420657175616c3a20676f7420282576292c2065787065637465642028257629222c2068312c2068322c0a090929290a097d0a0a092f2f20566572696679207061636b6574207265636569707420616273656e63650a0976617220280a09096b657920202020202020203d20686f73742e5061636b6574526563656970744b6579286d73672e5061636b65742e44657374696e6174696f6e436c69656e742c206d73672e5061636b65742e53657175656e6365290a09096d65726b6c6550617468203d2074797065732e4275696c644d65726b6c655061746828632e636f756e74657270617274794d65726b6c655072656669782c206b6579290a09290a09696620657272203a3d20632e6c69676874436c69656e742e5665726966794e6f6e4d656d62657273686970280a09096d73672e50726f6f664865696768742c206d73672e50726f6f66556e72656365697665642c206d65726b6c65506174682c0a09293b2065727220213d206e696c207b0a090970616e69632875666d742e537072696e7466280a090909226661696c6564207061636b6574207265636569707420616273656e636520766572696669636174696f6e20666f7220636c69656e7420282573293a202576222c0a090909636c69656e7449442c206572722c0a090929290a097d0a0a09632e64656c6574655061636b6574436f6d6d69746d656e74286d73672e5061636b65742e53657175656e6365290a0a09636861696e2e456d69742874797065732e4576656e745479706554696d656f75745061636b65742c0a090974797065732e4174747269627574654b6579537263436c69656e742c206d73672e5061636b65742e536f75726365436c69656e742c0a090974797065732e4174747269627574654b6579447374436c69656e742c206d73672e5061636b65742e44657374696e6174696f6e436c69656e742c0a090974797065732e4174747269627574654b657953657175656e63652c2075666d742e537072696e746628222564222c206d73672e5061636b65742e53657175656e6365292c0a090974797065732e4174747269627574654b657954696d656f757454696d657374616d702c2075666d742e537072696e746628222564222c206d73672e5061636b65742e54696d656f757454696d657374616d70292c0a090974797065732e4174747269627574654b6579456e636f6465645061636b65744865782c206865782e456e636f6465546f537472696e67286d73672e5061636b65742e50726f746f4d61727368616c2829292c0a09290a0a09666f7220692c207061796c6f6164203a3d2072616e6765206d73672e5061636b65742e5061796c6f616473207b0a0909617070203a3d2073746f72652e726f757465287061796c6f61642e536f75726365506f7274290a0909657272203a3d206170702e4f6e54696d656f75745061636b6574280a09090963726f737328637572292c206d73672e5061636b65742e536f75726365436c69656e742c206d73672e5061636b65742e44657374696e6174696f6e436c69656e742c0a0909096d73672e5061636b65742e53657175656e63652c207061796c6f61642c0a0909290a090969662065727220213d206e696c207b0a09090970616e69632875666d742e537072696e7466280a090909092274696d656f7574207061636b6574206661696c656420666f72207061796c6f616420232564206170702025713a202576222c0a09090909692c207061796c6f61642e536f75726365506f72742c206572722c0a09090929290a09097d0a097d0a0a0972657475726e2074797065732e524553504f4e53455f535543434553530a7d0a1a91010a0b676e6f6d6f642e746f6d6c1281016d6f64756c65203d2022676e6f2e6c616e642f722f6169622f6962632f636f7265220a676e6f203d2022302e39220a0a5b616464706b675d0a202063726561746f72203d20226731326a367832636e706b767a38336c3661356c68667732323730336b7777706b6e70666e743730220a2020686569676874203d2033303439320a1aea330a117265636f7665722d636c69656e742e6d6412d43323205265636f766572436c69656e740a0a60636f72652e5265636f766572436c69656e74602069732074686520676f7665726e616e63652065736361706520686174636820746861742072657669766573206120636c69656e7420746861740a686173206265636f6d6520756e757361626c6520e2809420656974686572202a2a46726f7a656e2a2a202876616c6964206d69736265686176696f757220776173207375626d6974746564207669610a60557064617465436c69656e746029206f72202a2a457870697265642a2a20286e6f2076616c69642068656164657220776173207375626d69747465642077697468696e207468650a605472757374696e67506572696f64602920e2809420627920636f7079696e672073746174652066726f6d2061206865616c746879202a2a737562737469747574652a2a20636c69656e7420746861740a747261636b73207468652073616d6520636f756e746572706172747920636861696e2e0a0a606060676e6f0a636f72652e5265636f766572436c69656e742863726f73732c207375626a656374436c69656e7449442c2073756273746974757465436c69656e74494420737472696e67290a6060600a0a4f6e6c79207468652061646d696e2063616e2063616c6c2069742028736565206061646d696e2e676e6f60292e20496e20746865206c6f6e672072756e20746869732069732065787065637465640a746f2062652064726976656e206279206120676f7644414f2070726f706f73616c2063616c6c6261636b2028747261636b656420696e20697373756520233336292e0a0a232320456e642d746f2d656e6420666c6f770a0a23232320312e204120636c69656e74206265636f6d657320756e757361626c650a0a2d202a2a46726f7a656e2a2a3a20612072656c61796572207375626d69747465642076616c6964206d69736265686176696f7572207669612060557064617465436c69656e7460202874776f0a2020636f6e666c696374696e67207369676e6564206865616465727320666f72207468652073616d6520636861696e292e2054686520636c69656e742773206046726f7a656e486569676874600a20206265636f6d6573206e6f6e2d7a65726f20616e6420605374617475732829602072657475726e73206046726f7a656e602e0a2d202a2a457870697265642a2a3a206e6f2076616c69642068656164657220776173207375626d69747465642077697468696e20605472757374696e67506572696f64602c20736f0a2020605374617475732829602072657475726e7320604578706972656460206265636175736520746865206c617465737420636f6e73656e73757320737461746527732074696d657374616d700a2020697320746f6f206f6c642e0a0a46726f6d207468697320706f696e74206053656e645061636b6574602c2060526563765061636b6574602c206041636b6e6f776c656467656d656e74602c206054696d656f75746020616e640a60557064617465436c69656e746020616c6c2070616e696320666f72207468697320636c69656e742e20416e7920696e2d666c696768742075736572207061636b657473206172650a737475636b2c20616e6420616e7920696e626f756e64207061636b6574732063616e6e6f742062652061636b6e6f776c65646765642e204368616e6e656c73207573696e6720746869730a636c69656e74206172652066726f7a656e206f6e2074686973207369646520756e74696c2074686520636c69656e74206973207265636f76657265642e0a0a23232320322e204f66662d636861696e20636f6f7264696e6174696f6e0a0a5374616b65686f6c6465727320616772656520746f207265636f76657220726174686572207468616e206d69677261746520746f2061206272616e642d6e657720636c69656e742e0a5265636f766572792069732070726566657261626c652062656361757365206974207072657365727665732074686520636c69656e742049442c207061636b65740a636f6d6d69746d656e7473202f207265636569707473202f2061636b6e6f776c656467656d656e74732c20636f756e746572706172747920726567697374726174696f6e20616e640a6368616e6e656c20737461746520e2809420757365727320646f6e2774206e65656420746f206d69677261746520616e797468696e672e0a0a23232320332e204372656174652061207375627374697475746520636c69656e740a0a412072656c617965722063616c6c732060636f72652e437265617465436c69656e7460207769746820612066726573682c202a2a4163746976652a2a20636c69656e7420746172676574696e670a746865202a73616d6520636f756e746572706172747920636861696e2a2e205468652073756273746974757465206d75737420736174697366790a6069734d61746368696e67436c69656e74537461746560207769746820746865207375626a6563742c20692e652e207468657365206669656c6473206d757374206d617463683a0a0a2d206054727573744c6576656c600a2d2060556e626f6e64696e67506572696f64600a2d20604d6178436c6f636b4472696674600a2d206050726f6f665370656373600a2d20605570677261646550617468600a0a54686520666f6c6c6f77696e672061726520616c6c6f77656420746f2064696666657220616e6420617265202a2a61646f707465642066726f6d2074686520737562737469747574652a2a2062790a746865207375626a65637420647572696e67207265636f766572793a0a0a2d2060436861696e49446020287479706963616c6c79207468652073616d652c206275742074686520636f646520737570706f7274732061206368616e676520e2809420666f72206578616d706c650a2020612067656e657369732d72657374617274206f6e2061206e657720636861696e20494420747261636b696e67207468652073616d65207374617465292e2060436861696e49446020616e640a2020604c6174657374486569676874602061726520616c776179732061646f7074656420746f6765746865722c20736f207468656972207265766973696f6e206e756d6265727320737461790a2020616c69676e65643a2060436c69656e7453746174652e56616c69646174654261736963602072657175697265730a2020604c61746573744865696768742e5265766973696f6e4e756d626572203d3d205061727365436861696e494428436861696e494429602c20616e642073696e636520626f74682073696465730a20206f66207468617420657175616c69747920636f6d652066726f6d2074686520737562737469747574652028776869636820706173736564206056616c69646174654261736963602061740a202060437265617465436c69656e7460292c2074686520696e76617269616e7420697320707265736572766564206f6e20746865207375626a65637420706f73742d7265636f766572792e0a2d20604c6174657374486569676874600a2d20605472757374696e67506572696f646020e2809420746869732069732074686520706172616d657465722d747765616b696e67206b6e6f623a20696620746865206f726967696e616c0a2020605472757374696e67506572696f6460207761732073657420746f6f20616767726573736976656c792028616e6420706172746c79206361757365642074686520657870697279292c0a2020676f7665726e616e63652063616e2063686f6f73652061206c61726765722076616c7565206f6e20746865207375627374697475746520616e642074686174206e65772076616c75652069730a2020636f7069656420696e746f20746865207375626a6563742e2053616d65206d656368616e69736d206173206962632d676f2e0a2d206046726f7a656e486569676874602028616c7761797320726573657420746f207a65726f290a0a23232320342e20284f7074696f6e616c2920466173742d666f72776172642074686520737562737469747574650a0a52656c61796572732063616c6c2060557064617465436c69656e74287375627374697475746549442c20686561646572296020756e74696c20746865207375627374697475746527730a604c617465737448656967687460206973206174207468652064657369726564207265636f76657279206865696768742e205468652073756273746974757465206d7573742062650a604163746976656020617420746865206d6f6d656e74207265636f766572792065786563757465732e0a0a23232320352e20476f7665726e616e63652070726f706f73616c0a0a412070726f706f73616c2061736b7320746f2072756e3a0a0a606060676e6f0a636f72652e5265636f766572436c69656e742863726f73732c207375626a65637449442c20737562737469747574654944290a6060600a0a43757272656e746c792067617465642062792060656e7375726541646d696e43616c6c65722829603b206f6e636520676f7644414f20696e746567726174696f6e206c616e6473207468650a70726f706f73616c206578656375746f72206265636f6d65732074686520617574686f72697a65642063616c6c65722e0a0a23232320362e20605265636f766572436c69656e74602065786563757465730a0a60722f6169622f6962632f636f72652f636c69656e742e676e6f603a0a0a312e2060656e7375726541646d696e43616c6c65722829602e0a322e205375626a65637420616e64207375627374697475746520494473206d757374206469666665723b20626f7468206d757374207265736f6c76653b206074797060206d757374206d617463682e0a332e205375626a6563742073746174757320e28888207b6046726f7a656e602c206045787069726564607d3b207375627374697475746520737461747573206d7573742062652060416374697665602e0a342e2044656c65676174657320746f20607375626a6563742e6c69676874436c69656e742e5265636f766572436c69656e7428737562737469747574652e6c69676874436c69656e7429602e0a0a60702f6169622f6962632f6c69676874636c69656e742f74656e6465726d696e742f74656e6465726d696e742e676e6f603a0a0a312e20547970652d617373657274207375627374697475746520746f20602a544d4c69676874436c69656e74602e0a322e206069734d61746368696e67436c69656e7453746174656020636865636b2e0a332e2046657463682060737562737469747574652e476574436f6e73656e737573537461746528737562737469747574652e4c617465737448656967687429602e0a342e20436f707920696e746f207375626a6563743a2060436861696e4944602c20604c6174657374486569676874602c20605472757374696e67506572696f64603b2072657365740a2020206046726f7a656e486569676874602e0a352e2053746f7265207468652073756273746974757465277320636f6e73656e7375732073746174652061742074686520737562737469747574652773206c61746573742068656967687420696e0a202020746865207375626a6563742e0a0a23232320372e20506f73742d7265636f766572792073746174650a0a2d205375626a656374277320605374617475732829203d3d20416374697665602e2060557064617465436c69656e74602c207061636b657420766572696669636174696f6e2c206574632e0a2020726573756d652e0a2d202a2a5061636b657420636f6d6d69746d656e74732c2072656365697074732c2061636b6e6f776c656467656d656e74732c206073656e64536571602c0a202060636f756e7465727061727479436c69656e744944602c2060636f756e74657270617274794d65726b6c65507265666978602061726520756e746f75636865642a2a20e2809420746861742069730a202074686520706f696e743a206368616e6e656c73206b65657020776f726b696e672077697468207468656972206578697374696e67206964656e7469666965727320616e640a2020696e2d666c696768742073746174652e0a2d205072652d7265636f7665727920636f6e73656e737573207374617465732072656d61696e20696e20746865207375626a6563742773207472656520627574206172652062656c6f77207468650a20206e657720604c61746573744865696768746020616e6420617265206e6f74207573656420746f20766572696679206e6577207061636b6574732e0a2d20546865207375627374697475746520636c69656e74206973202a2a6e6f742a2a2064656c6574656420616e642072656d61696e732060416374697665602e2049742063616e2062650a202072657573656420666f72206120667574757265207265636f76657279206f72206c6566742069646c652e0a2d20607265636f7665725f636c69656e7460206576656e7420697320656d69747465642e0a0a23232320382e20436f756e74657270617274792073696465202873796d6d6574726963290a0a49662074686520636f756e746572706172747920636861696e277320636c69656e7420747261636b696e67207468697320636861696e20697320616c736f2046726f7a656e2f457870697265640a28636f6d6d6f6e207768656e206d69736265686176696f7572206f722061206c6f6e672068616c74206166666563747320626f7468207369646573292c2074686520636f756e74657270617274790a72756e7320697473206f776e20676f7665726e616e63652d6c6576656c207265636f766572792e205061636b65742072656c6179696e672063616e6e6f7420726573756d65206f6e20746861740a7061746820756e74696c20626f7468207369646573206172652060416374697665602e0a0a23232320392e2052656c6179657220726573756d65730a0a4f6e636520626f7468207369646573206172652060416374697665602c2072656c6179657273207375626d69742068656164657273207669612060557064617465436c69656e746020616e640a746865206e6f726d616c207061636b6574206c6966656379636c6520726573756d65732e204e6f2072652d605265676973746572436f756e7465727061727479602c206e6f206e65770a6368616e6e656c2e0a0a2323204368616e67696e6720706172616d657465727320647572696e67207265636f766572790a0a42656361757365207468652073756273746974757465277320605472757374696e67506572696f646020616e642060436861696e494460206172652061646f70746564206279207468650a7375626a6563742c206372656174696e6720746865207375627374697475746520697320616c736f20746865206f70706f7274756e69747920746f2061646a7573742074686f73650a706172616d6574657273207468726f756768207468652073616d6520676f7665726e616e636520616374696f6e3a0a0a2d202a2a4c656e677468656e696e6720605472757374696e67506572696f64602a2a20746f2072656475636520746865207269736b206f66206675747572652065787069727920e2809420666f720a20206578616d706c65206166746572206c6561726e696e6720746861742074686520636f756e7465727061727479277320626c6f636b2070726f64756374696f6e20697320736c6f7765720a20207468616e206f726967696e616c6c7920617373756d65642e0a2d202a2a41646f7074696e672061206e65772060436861696e4944602a2a206166746572206120636f756e74657270617274792067656e6573697320726573746172742074686174206b6570740a20207468652073616d6520636f6e73656e737573207374617465207472656520e2809420746865207375626a6563742073746172747320766572696679696e672068656164657273207369676e65640a2020756e64657220746865206e657720636861696e20494420776974686f7574206265696e67206d6967726174656420746f2061206e657720636c69656e742049442e0a0a4f7468657220706172616d657465727320286054727573744c6576656c602c2060556e626f6e64696e67506572696f64602c20604d6178436c6f636b4472696674602c0a6050726f6f665370656373602c206055706772616465506174686029202a2a63616e6e6f742a2a206265206368616e676564206279207265636f7665727920e2809420746865206d617463680a636865636b2072656a656374732074686520737562737469747574652e204368616e67696e672074686f7365207265717569726573206055706772616465436c69656e746020286f7220610a667265736820636c69656e74206d6967726174696f6e292e0a0a232320436176656174730a0a2d205468652073756273746974757465277320604c617465737448656967687460206973206e6f7420726571756972656420746f2062652067726561746572207468616e207468650a20207375626a65637427732e2053616d65206173206962632d676f20e28094206e6f7468696e6720656e666f7263657320612022666f727761726422207265636f766572792c2074686f75676820696e0a2020707261637469636520746865207375627374697475746520697320616c776179732061686561642e0a2d204f6e6c79207468652073756273746974757465277320636f6e73656e737573207374617465202a2a61742069747320604c6174657374486569676874602a2a20697320636f706965640a2020696e746f20746865207375626a6563742e204561726c696572207375627374697475746520636f6e73656e7375732073746174657320617265206e6f74206d696772617465642e0a2d205265636f7665727920646f6573206e6f74207265736574207061636b65742073657175656e636573206f7220636c65617220636f6d6d69746d656e747320e280942074686f7365206172650a20207061636b65742d6c6179657220636f6e6365726e7320616e64207374617920696e746163742e0a1ac67c0a0a72656e6465722e676e6f12b77c7061636b61676520636f72650a0a696d706f727420280a0922656e636f64696e672f626173653634220a0922737472636f6e76220a0922737472696e6773220a092274696d65220a0a0922676e6f2e6c616e642f702f6169622f6962632f6c69676874636c69656e74220a0922676e6f2e6c616e642f702f6169622f6962632f6c69676874636c69656e742f74656e6465726d696e74220a0922676e6f2e6c616e642f702f6169622f6962632f7479706573220a0922676e6f2e6c616e642f702f6169622f6a736f6e70616765220a0922676e6f2e6c616e642f702f6e742f6270747265652f7630220a0922676e6f2e6c616e642f702f6e742f6d75782f7630220a0922676e6f2e6c616e642f702f6e742f73657169642f7630220a0922676e6f2e6c616e642f702f6e742f75666d742f7630220a0922676e6f2e6c616e642f702f6f6e626c6f632f6a736f6e220a290a0a66756e632052656e646572287061746820737472696e672920737472696e67207b0a09726f75746572203a3d206d75782e4e6577526f7574657228290a09726f757465722e48616e646c6546756e632822222c2072656e646572486f6d65290a09726f757465722e48616e646c6546756e63282261646d696e222c2072656e64657241646d696e290a09726f757465722e48616e646c6546756e63282261707073222c2072656e64657241707073290a09726f757465722e48616e646c6546756e632822636c69656e7473222c2072656e646572436c69656e7473290a09726f757465722e48616e646c6546756e632822636c69656e74732f7b69647d222c2072656e646572436c69656e74290a09726f757465722e48616e646c6546756e632822636c69656e74732f7b69647d2f737461747573222c2072656e646572436c69656e74537461747573290a09726f757465722e48616e646c6546756e632822636c69656e74732f7b69647d2f636f6e73656e7375735f737461746573222c2072656e646572436c69656e74436f6e73656e737573537461746573290a09726f757465722e48616e646c6546756e632822636c69656e74732f7b69647d2f636f6e73656e7375735f7374617465732f7b7265766973696f6e5f6e756d6265727d2f7b7265766973696f6e5f6865696768747d222c2072656e646572436c69656e74436f6e73656e7375735374617465290a09726f757465722e48616e646c6546756e632822636c69656e74732f7b69647d2f6e6578745f73657175656e63655f73656e64222c2072656e646572436c69656e744e65787453657175656e636553656e64290a09726f757465722e48616e646c6546756e632822636c69656e74732f7b69647d2f7061636b65745f636f6d6d69746d656e7473222c2072656e646572436c69656e745061636b6574436f6d6d69746d656e7473290a09726f757465722e48616e646c6546756e632822636c69656e74732f7b69647d2f7061636b65745f636f6d6d69746d656e74732f7b73657175656e63657d222c2072656e646572436c69656e745061636b6574436f6d6d69746d656e74290a09726f757465722e48616e646c6546756e632822636c69656e74732f7b69647d2f7061636b65745f7265636569707473222c2072656e646572436c69656e745061636b65745265636569707473290a09726f757465722e48616e646c6546756e632822636c69656e74732f7b69647d2f7061636b65745f72656365697074732f7b73657175656e63657d222c2072656e646572436c69656e745061636b657452656365697074290a09726f757465722e48616e646c6546756e632822636c69656e74732f7b69647d2f7061636b65745f61636b6e6f776c656467656d656e7473222c2072656e646572436c69656e745061636b657441636b6e6f776c656467656d656e7473290a09726f757465722e48616e646c6546756e632822636c69656e74732f7b69647d2f7061636b65745f61636b6e6f776c656467656d656e74732f7b73657175656e63657d222c2072656e646572436c69656e745061636b657441636b6e6f776c656467656d656e74290a09726f757465722e48616e646c6546756e632822636c69656e74732f7b69647d2f756e72656365697665645f7061636b657473222c2072656e646572436c69656e74556e72656365697665645061636b657473290a0972657475726e20726f757465722e52656e6465722870617468290a7d0a0a66756e632072656e646572486f6d652877202a6d75782e526573706f6e73655772697465722c2072202a6d75782e5265717565737429207b0a09766172206f757420737472696e67732e4275696c6465720a096f75742e5772697465537472696e672822232049424320636f72655c6e5c6e22290a096f75742e5772697465537472696e67282249424320763220636f726520737461746520616e6420717565727920656e64706f696e74732e5c6e5c6e22290a096f75742e5772697465537472696e672875666d742e537072696e74662822232320436c69656e747320282564295c6e5c6e222c2073746f72652e636c69656e74427949442e53697a65282929290a0969662073746f72652e636c69656e74427949442e53697a652829203e2030207b0a09096f75742e5772697465537472696e6728227c20436c69656e74207c2053742e207c2043726561746f72207c2043707479207c20486569676874207c2054696d65207c204353207c20536571207c20436d7473207c205263707473207c2041636b73207c5c6e22290a09096f75742e5772697465537472696e6728227c2d2d2d2d2d2d2d2d7c2d2d2d2d2d7c2d2d2d2d2d2d2d2d2d7c2d2d2d2d2d2d7c2d2d2d2d2d2d2d2d7c2d2d2d2d2d2d7c2d2d2d2d7c2d2d2d2d2d7c2d2d2d2d2d2d7c2d2d2d2d2d2d2d7c2d2d2d2d2d2d7c5c6e22290a090973746f72652e636c69656e74427949442e4974657261746542794f666673657428302c2073746f72652e636c69656e74427949442e53697a6528292c2066756e63285f20737472696e672c207620616e792920626f6f6c207b0a09090963203a3d20762e282a636c69656e74290a09090976617220280a090909096e436f6e7353746174657320696e740a0909090968656967687454696d652020737472696e670a090909290a09090973776974636820632e747970207b0a09090963617365206c69676874636c69656e742e54656e6465726d696e743a0a090909096c63203a3d20632e6c69676874436c69656e742e282a74656e6465726d696e742e544d4c69676874436c69656e74290a090909096e436f6e73537461746573203d206c632e436f6e73656e737573537461746542794865696768742e53697a6528290a0909090969662063732c20666f756e64203a3d206c632e476574436f6e73656e7375735374617465286c632e4c61746573744865696768742829293b20666f756e64207b0a090909090968656967687454696d65203d2063732e54696d657374616d702e55544328292e466f726d61742874696d652e52464333333339290a090909097d0a0909097d0a0909096f75742e5772697465537472696e672875666d742e537072696e7466280a09090909227c205b602573605d282f722f6169622f6962632f636f72653a636c69656e74732f257329207c205b25735d282f722f6169622f6962632f636f72653a636c69656e74732f25732f73746174757329207c202573207c202573207c205b25735d282f722f6169622f6962632f636f72653a636c69656e74732f25732f636f6e73656e7375735f7374617465732f25642f256429207c202573207c205b25645d282f722f6169622f6962632f636f72653a636c69656e74732f25732f636f6e73656e7375735f73746174657329207c205b25645d282f722f6169622f6962632f636f72653a636c69656e74732f25732f6e6578745f73657175656e63655f73656e6429207c205b25645d282f722f6169622f6962632f636f72653a636c69656e74732f25732f7061636b65745f636f6d6d69746d656e747329207c205b25645d282f722f6169622f6962632f636f72653a636c69656e74732f25732f7061636b65745f726563656970747329207c205b25645d282f722f6169622f6962632f636f72653a636c69656e74732f25732f7061636b65745f61636b6e6f776c656467656d656e747329207c5c6e222c0a09090909632e69642c20632e69642c0a09090909632e6c69676874436c69656e742e53746174757328292c20632e69642c0a0909090972656e6465724164647228632e63726561746f72292c0a09090909632e636f756e7465727061727479436c69656e7449442c0a09090909632e6c69676874436c69656e742e4c617465737448656967687428292e537472696e6728292c20632e69642c20632e6c69676874436c69656e742e4c617465737448656967687428292e5265766973696f6e4e756d6265722c20632e6c69676874436c69656e742e4c617465737448656967687428292e5265766973696f6e4865696768742c0a0909090968656967687454696d652c0a090909096e436f6e735374617465732c20632e69642c0a09090909696e74363428632e73656e64536571292b312c20632e69642c0a09090909632e7061636b6574436f6d6d69746d656e747342795365712e53697a6528292c20632e69642c0a09090909632e7061636b6574526563656970747342795365712e53697a6528292c20632e69642c0a09090909632e7061636b657441636b6e6f776c656467656d656e747342795365712e53697a6528292c20632e69642c0a09090929290a09090972657475726e2066616c73650a09097d290a09096f75742e5772697465537472696e6728225c6e22290a097d20656c7365207b0a09096f75742e5772697465537472696e6728224e6f20636c69656e7473207965742e5c6e5c6e22290a097d0a096f75742e5772697465537472696e672875666d742e537072696e746628222323204170707320282564295c6e5c6e222c206c656e2873746f72652e726f757465732929290a096966206c656e2873746f72652e726f7574657329203e2030207b0a09096f75742e5772697465537472696e6728227c20506f7274207c20506b67207c2041646472207c5c6e22290a09096f75742e5772697465537472696e6728227c2d2d2d2d2d2d7c2d2d2d2d2d7c2d2d2d2d2d2d7c5c6e22290a0909666f7220706f72742c20617070203a3d2072616e67652073746f72652e726f75746573207b0a0909096f75742e5772697465537472696e672875666d742e537072696e746628227c202573207c202573207c202573207c5c6e222c20706f72742c2072656e646572506b6750617468286170702e706b6750617468292c2072656e64657241646472286170702e616464726573732929290a09097d0a09096f75742e5772697465537472696e6728225c6e22290a097d20656c7365207b0a09096f75742e5772697465537472696e6728224e6f20617070732072656769737465726564207965742e5c6e5c6e22290a097d0a096f75742e5772697465537472696e6728222323204a534f4e20656e64706f696e74735c6e5c6e22290a096f75742e5772697465537472696e6728222d205b6061646d696e605d282f722f6169622f6962632f636f72653a61646d696e293a2061646d696e20616e642072656c61796572735c6e22290a096f75742e5772697465537472696e6728222d205b6061707073605d282f722f6169622f6962632f636f72653a61707073293a206c697374207265676973746572656420494243206170706c69636174696f6e735c6e22290a096f75742e5772697465537472696e6728222d205b60636c69656e7473605d282f722f6169622f6962632f636f72653a636c69656e7473293a206c69737420636c69656e74732028603f70616765602c20603f6c696d697460295c6e22290a096f75742e5772697465537472696e6728222d2060636c69656e74732f7b69647d603a2067657420636c69656e742064657461696c735c6e22290a096f75742e5772697465537472696e6728222d2060636c69656e74732f7b69647d2f737461747573603a2067657420636c69656e74207374617475735c6e22290a096f75742e5772697465537472696e6728222d2060636c69656e74732f7b69647d2f636f6e73656e7375735f737461746573603a206c69737420636c69656e7420636f6e73656e737573207374617465732028603f70616765602c20603f6c696d697460295c6e22290a096f75742e5772697465537472696e6728222d2060636c69656e74732f7b69647d2f636f6e73656e7375735f7374617465732f7b7265766973696f6e5f6e756d6265727d2f7b7265766973696f6e5f6865696768747d603a20676574206120636f6e73656e737573207374617465206279206865696768745c6e22290a096f75742e5772697465537472696e6728222d2060636c69656e74732f7b69647d2f6e6578745f73657175656e63655f73656e64603a2067657420746865206e657874207061636b65742073657175656e636520746f2073656e645c6e22290a096f75742e5772697465537472696e6728222d2060636c69656e74732f7b69647d2f7061636b65745f636f6d6d69746d656e7473603a206c697374207061636b657420636f6d6d69746d656e74732028603f70616765602c20603f6c696d697460295c6e22290a096f75742e5772697465537472696e6728222d2060636c69656e74732f7b69647d2f7061636b65745f636f6d6d69746d656e74732f7b73657175656e63657d603a206765742061207061636b657420636f6d6d69746d656e742062792073657175656e63655c6e22290a096f75742e5772697465537472696e6728222d2060636c69656e74732f7b69647d2f7061636b65745f7265636569707473603a206c697374207061636b65742072656365697074732028603f70616765602c20603f6c696d697460295c6e22290a096f75742e5772697465537472696e6728222d2060636c69656e74732f7b69647d2f7061636b65745f72656365697074732f7b73657175656e63657d603a206765742061207061636b657420726563656970742062792073657175656e63655c6e22290a096f75742e5772697465537472696e6728222d2060636c69656e74732f7b69647d2f7061636b65745f61636b6e6f776c656467656d656e7473603a206c697374207061636b65742061636b6e6f776c656467656d656e74732028603f70616765602c20603f6c696d697460295c6e22290a096f75742e5772697465537472696e6728222d2060636c69656e74732f7b69647d2f7061636b65745f61636b6e6f776c656467656d656e74732f7b73657175656e63657d603a206765742061207061636b65742061636b6e6f776c656467656d656e742062792073657175656e63655c6e22290a096f75742e5772697465537472696e6728222d2060636c69656e74732f7b69647d2f756e72656365697665645f7061636b6574733f73657175656e6365733d312c322c33603a2072657475726e2073657175656e63657320776974686f75742061206c6f63616c207061636b657420726563656970745c6e5c6e22290a09772e5772697465286f75742e537472696e672829290a7d0a0a66756e632072656e646572417070732877202a6d75782e526573706f6e73655772697465722c2072202a6d75782e5265717565737429207b0a09766172206e6f646573205b5d2a6a736f6e2e4e6f64650a09666f7220706f72742c20617070203a3d2072616e67652073746f72652e726f75746573207b0a09096e6f646573203d20617070656e64286e6f6465732c206a736f6e2e4f626a6563744e6f64652822222c206d61705b737472696e675d2a6a736f6e2e4e6f64657b0a09090922706f72745f6964223a20206a736f6e2e537472696e674e6f64652822222c20706f7274292c0a09090922706b675f70617468223a206a736f6e2e537472696e674e6f64652822222c206170702e706b6750617468292c0a0909092261646472657373223a20206a736f6e2e537472696e674e6f64652822222c206170702e616464726573732e537472696e672829292c0a09097d29290a097d0a0972656e6465724e6f646528772c206a736f6e2e41727261794e6f64652822222c206e6f64657329290a7d0a0a66756e632072656e646572436c69656e74732877202a6d75782e526573706f6e73655772697465722c2072202a6d75782e5265717565737429207b0a0972656e6465724e6f646528772c206a736f6e706167652e52656e6465722873746f72652e636c69656e74427949442c20722c206e696c29290a7d0a0a66756e632072656e646572436c69656e742877202a6d75782e526573706f6e73655772697465722c2072202a6d75782e5265717565737429207b0a096964203a3d20722e4765745661722822696422290a0963203a3d2073746f72652e676574436c69656e74286964290a0969662063203d3d206e696c207b0a090972656e6465724e6f646528772c206e6f64654572726f72436c69656e744e6f74466f756e6428696429290a090972657475726e0a097d0a0972656e6465724e6f646528772c20632e52656e6465724a534f4e2829290a7d0a0a66756e632072656e646572436c69656e745374617475732877202a6d75782e526573706f6e73655772697465722c2072202a6d75782e5265717565737429207b0a096964203a3d20722e4765745661722822696422290a0963203a3d2073746f72652e676574436c69656e74286964290a0969662063203d3d206e696c207b0a090972656e6465724e6f646528772c206e6f64654572726f72436c69656e744e6f74466f756e6428696429290a090972657475726e0a097d0a0972656e6465724e6f646528772c206a736f6e2e4f626a6563744e6f64652822222c206d61705b737472696e675d2a6a736f6e2e4e6f64657b0a090922737461747573223a206a736f6e2e537472696e674e6f64652822222c20632e6c69676874436c69656e742e5374617475732829292c0a097d29290a7d0a0a66756e632072656e646572436c69656e74436f6e73656e7375735374617465732877202a6d75782e526573706f6e73655772697465722c2072202a6d75782e5265717565737429207b0a096964203a3d20722e4765745661722822696422290a0963203a3d2073746f72652e676574436c69656e74286964290a0969662063203d3d206e696c207b0a090972656e6465724e6f646528772c206e6f64654572726f72436c69656e744e6f74466f756e6428696429290a090972657475726e0a097d0a0972656e6465724e6f646528772c20632e72656e646572436f6e73656e737573537461746573287229290a7d0a0a66756e632072656e646572436c69656e74436f6e73656e73757353746174652877202a6d75782e526573706f6e73655772697465722c2072202a6d75782e5265717565737429207b0a0976617220280a09096964202020202020202020202020203d20722e4765745661722822696422290a09097265766973696f6e4e756d626572203d20722e47657456617228227265766973696f6e5f6e756d62657222290a09097265766973696f6e486569676874203d20722e47657456617228227265766973696f6e5f68656967687422290a09096865696768745374722020202020203d207265766973696f6e4e756d626572202b20222f22202b207265766973696f6e4865696768740a09290a096865696768742c20657272203a3d2074797065732e506172736548656967687428686569676874537472290a0969662065727220213d206e696c207b0a090972656e6465724e6f646528772c206e6f64654572726f72282263616e74207061727365206865696768742025733a202576222c206865696768745374722c2065727229290a090972657475726e0a097d0a0963203a3d2073746f72652e676574436c69656e74286964290a0969662063203d3d206e696c207b0a090972656e6465724e6f646528772c206e6f64654572726f72436c69656e744e6f74466f756e6428696429290a090972657475726e0a097d0a0973776974636820632e747970207b0a0963617365206c69676874636c69656e742e54656e6465726d696e743a0a09096c63203a3d20632e6c69676874436c69656e742e282a74656e6465726d696e742e544d4c69676874436c69656e74290a090963732c20666f756e64203a3d206c632e476574436f6e73656e737573537461746528686569676874290a090969662021666f756e64207b0a09090972656e6465724e6f646528772c206e6f64654572726f722822636f6e73656e737573207374617465206e6f7420666f756e6420666f7220686569676874202573222c2068656967687429290a09090972657475726e0a09097d0a090972656e6465724e6f646528772c2072656e646572436f6e73656e7375735374617465286865696768742c20637329290a097d0a7d0a0a66756e632072656e646572436c69656e744e65787453657175656e636553656e642877202a6d75782e526573706f6e73655772697465722c2072202a6d75782e5265717565737429207b0a096964203a3d20722e4765745661722822696422290a0963203a3d2073746f72652e676574436c69656e74286964290a0969662063203d3d206e696c207b0a090972656e6465724e6f646528772c206e6f64654572726f72436c69656e744e6f74466f756e6428696429290a090972657475726e0a097d0a09772e57726974652875666d742e537072696e746628607b226e6578745f73657175656e63655f73656e64223a2025647d22602c20696e74363428632e73656e64536571292b3129290a7d0a0a66756e632072656e646572436c69656e745061636b6574436f6d6d69746d656e74732877202a6d75782e526573706f6e73655772697465722c2072202a6d75782e5265717565737429207b0a096964203a3d20722e4765745661722822696422290a0963203a3d2073746f72652e676574436c69656e74286964290a0969662063203d3d206e696c207b0a090972656e6465724e6f646528772c206e6f64654572726f72436c69656e744e6f74466f756e6428696429290a090972657475726e0a097d0a0972656e646572436f6d6d69746d656e747328772c20722c20632e7061636b6574436f6d6d69746d656e74734279536571290a7d0a0a66756e632072656e646572436c69656e745061636b6574436f6d6d69746d656e742877202a6d75782e526573706f6e73655772697465722c2072202a6d75782e5265717565737429207b0a096964203a3d20722e4765745661722822696422290a0963203a3d2073746f72652e676574436c69656e74286964290a0969662063203d3d206e696c207b0a090972656e6465724e6f646528772c206e6f64654572726f72436c69656e744e6f74466f756e6428696429290a090972657475726e0a097d0a0972656e646572436f6d6d69746d656e7428772c20722c20632e7061636b6574436f6d6d69746d656e74734279536571290a7d0a0a66756e632072656e646572436c69656e745061636b657452656365697074732877202a6d75782e526573706f6e73655772697465722c2072202a6d75782e5265717565737429207b0a096964203a3d20722e4765745661722822696422290a0963203a3d2073746f72652e676574436c69656e74286964290a0969662063203d3d206e696c207b0a090972656e6465724e6f646528772c206e6f64654572726f72436c69656e744e6f74466f756e6428696429290a090972657475726e0a097d0a0972656e646572436f6d6d69746d656e747328772c20722c20632e7061636b657452656365697074734279536571290a7d0a0a66756e632072656e646572436c69656e745061636b6574526563656970742877202a6d75782e526573706f6e73655772697465722c2072202a6d75782e5265717565737429207b0a096964203a3d20722e4765745661722822696422290a0963203a3d2073746f72652e676574436c69656e74286964290a0969662063203d3d206e696c207b0a090972656e6465724e6f646528772c206e6f64654572726f72436c69656e744e6f74466f756e6428696429290a090972657475726e0a097d0a0972656e646572436f6d6d69746d656e7428772c20722c20632e7061636b657452656365697074734279536571290a7d0a0a66756e632072656e646572436c69656e745061636b657441636b6e6f776c656467656d656e74732877202a6d75782e526573706f6e73655772697465722c2072202a6d75782e5265717565737429207b0a096964203a3d20722e4765745661722822696422290a0963203a3d2073746f72652e676574436c69656e74286964290a0969662063203d3d206e696c207b0a090972656e6465724e6f646528772c206e6f64654572726f72436c69656e744e6f74466f756e6428696429290a090972657475726e0a097d0a0972656e646572436f6d6d69746d656e747328772c20722c20632e7061636b657441636b6e6f776c656467656d656e74734279536571290a7d0a0a66756e632072656e646572436c69656e745061636b657441636b6e6f776c656467656d656e742877202a6d75782e526573706f6e73655772697465722c2072202a6d75782e5265717565737429207b0a096964203a3d20722e4765745661722822696422290a0963203a3d2073746f72652e676574436c69656e74286964290a0969662063203d3d206e696c207b0a090972656e6465724e6f646528772c206e6f64654572726f72436c69656e744e6f74466f756e6428696429290a090972657475726e0a097d0a0972656e646572436f6d6d69746d656e7428772c20722c20632e7061636b657441636b6e6f776c656467656d656e74734279536571290a7d0a0a66756e632072656e646572436f6d6d69746d656e742877202a6d75782e526573706f6e73655772697465722c2072202a6d75782e526571756573742c2074726565202a6270747265652e42505472656529207b0a09736571537472203a3d20722e476574566172282273657175656e636522290a097365712c20657272203a3d20737472636f6e762e506172736555696e74287365715374722c2031302c203634290a0969662065727220213d206e696c207b0a090972656e6465724e6f646528772c206e6f64654572726f722822696e76616c69642073657175656e63652025713a202576222c207365715374722c2065727229290a090972657475726e0a097d0a0976203a3d20747265652e4765742873657169642e494428736571292e42696e6172792829290a0969662076203d3d206e696c207b0a090972656e6465724e6f646528772c206e6f64654572726f72282273657175656e6365202573206e6f7420666f756e64222c2073657153747229290a090972657475726e0a097d0a0972656e6465724e6f646528772c20636f6d6d69746d656e744e6f6465287365712c20762e285b5d627974652929290a7d0a0a66756e632072656e646572436f6d6d69746d656e74732877202a6d75782e526573706f6e73655772697465722c2072202a6d75782e526571756573742c2074726565202a6270747265652e42505472656529207b0a0972656e6465724e6f646528772c206a736f6e706167652e52656e64657228747265652c20722c2066756e63286b20737472696e672c207620616e7929202a6a736f6e2e4e6f6465207b0a090969642c205f203a3d2073657169642e46726f6d42696e617279286b290a090972657475726e20636f6d6d69746d656e744e6f64652875696e743634286964292c20762e285b5d6279746529290a097d29290a7d0a0a66756e6320636f6d6d69746d656e744e6f64652873657175656e63652075696e7436342c2064617461205b5d6279746529202a6a736f6e2e4e6f6465207b0a0972657475726e206a736f6e2e4f626a6563744e6f64652822222c206d61705b737472696e675d2a6a736f6e2e4e6f64657b0a09092273657175656e6365223a206a736f6e2e537472696e674e6f64652822222c20737472636f6e762e466f726d617455696e742873657175656e63652c20313029292c0a09092264617461223a20202020206a736f6e2e537472696e674e6f64652822222c206261736536342e537464456e636f64696e672e456e636f6465546f537472696e67286461746129292c0a097d290a7d0a0a2f2f2072656e646572436c69656e74556e72656365697665645061636b6574732072657475726e732c20676976656e2061206c697374206f662073657175656e6365732c207468650a2f2f2073657175656e63657320666f72207768696368206e6f20636f756e7465727061727479207061636b657420636f6d6d69746d656e74732068617665206265656e2072656365697665642e0a2f2f205468697320697320646f6e6520627920636865636b696e672069662061207265636569707420657869737473206f6e207468697320636861696e20666f7220746865207061636b65740a2f2f2073657175656e63652e0a66756e632072656e646572436c69656e74556e72656365697665645061636b6574732877202a6d75782e526573706f6e73655772697465722c2072202a6d75782e5265717565737429207b0a096964203a3d20722e4765745661722822696422290a0963203a3d2073746f72652e676574436c69656e74286964290a0969662063203d3d206e696c207b0a090972656e6465724e6f646528772c206e6f64654572726f72436c69656e744e6f74466f756e6428696429290a090972657475726e0a097d0a0973657173203a3d20737472696e67732e53706c697428737472696e67732e5472696d537061636528722e51756572792e476574282273657175656e6365732229292c20222c22290a0976617220756e726563656976656453657173205b5d2a6a736f6e2e4e6f64650a09666f72205f2c20736571203a3d2072616e67652073657173207b0a0909736571692c20657272203a3d20737472636f6e762e506172736555696e74287365712c2031302c203634290a090969662065727220213d206e696c207b0a09090972656e6465724e6f646528772c206e6f64654572726f722875666d742e537072696e74662822696e76616c69642073657175656e63652025713a202576222c207365712c206572722929290a09090972657475726e0a09097d0a090969662021632e6861735061636b657452656365697074287365716929207b0a090909756e726563656976656453657173203d20617070656e6428756e7265636569766564536571732c206a736f6e2e537472696e674e6f64652822222c2073657129290a09097d0a097d0a0972656e6465724e6f646528772c206a736f6e2e4f626a6563744e6f64652822222c206d61705b737472696e675d2a6a736f6e2e4e6f64657b0a090922686569676874223a20202020202020202020202020202072656e6465724865696768742874797065732e47657453656c664865696768742829292c0a090922756e72656365697665645f73657175656e636573223a206a736f6e2e41727261794e6f64652822222c20756e726563656976656453657173292c0a097d29290a7d0a0a2f2f20496d706c656d656e7473206a736f6e706167652e4a534f4e52656e64657265720a66756e63202863202a636c69656e74292052656e6465724a534f4e2829202a6a736f6e2e4e6f6465207b0a096d203a3d206d61705b737472696e675d2a6a736f6e2e4e6f64657b0a0909226964223a2020202020202020202020202020202020202020206a736f6e2e537472696e674e6f64652822222c20632e6964292c0a09092274797065223a202020202020202020202020202020202020206a736f6e2e537472696e674e6f64652822222c20632e747970292c0a09092263726561746f72223a202020202020202020202020202020206a736f6e2e537472696e674e6f64652822222c20632e63726561746f722e537472696e672829292c0a090922737461747573223a20202020202020202020202020202020206a736f6e2e537472696e674e6f64652822222c20632e6c69676874436c69656e742e5374617475732829292c0a090922636f756e74657270617274795f636c69656e745f6964223a206a736f6e2e537472696e674e6f64652822222c20632e636f756e7465727061727479436c69656e744944292c0a097d0a09766172207072656669786573205b5d2a6a736f6e2e4e6f64650a09666f72205f2c206d203a3d2072616e676520632e636f756e74657270617274794d65726b6c65507265666978207b0a09097072656669786573203d20617070656e642870726566697865732c206a736f6e2e537472696e674e6f64652822222c20737472696e67286d2929290a097d0a096d5b22636f756e74657270617274795f6d65726b655f707265666978225d203d206a736f6e2e41727261794e6f64652822222c207072656669786573290a0973776974636820632e747970207b0a0963617365206c69676874636c69656e742e54656e6465726d696e743a0a09096c63203a3d20632e6c69676874436c69656e742e282a74656e6465726d696e742e544d4c69676874436c69656e74290a09096d5b22636c69656e745f7374617465225d203d206a736f6e2e4f626a6563744e6f64652822222c206d61705b737472696e675d2a6a736f6e2e4e6f64657b0a09090922636861696e5f6964223a2020202020202020206a736f6e2e537472696e674e6f64652822222c206c632e436c69656e7453746174652e436861696e4944292c0a090909226c61746573745f686569676874223a2020202072656e646572486569676874286c632e436c69656e7453746174652e4c6174657374486569676874292c0a0909092266726f7a656e5f686569676874223a2020202072656e646572486569676874286c632e436c69656e7453746174652e46726f7a656e486569676874292c0a0909092274727573745f6c6576656c223a20202020202072656e6465724672616374696f6e286c632e436c69656e7453746174652e54727573744c6576656c292c0a090909227472757374696e675f706572696f64223a202072656e6465724475726174696f6e286c632e436c69656e7453746174652e5472757374696e67506572696f64292c0a09090922756e626f6e64696e675f706572696f64223a2072656e6465724475726174696f6e286c632e436c69656e7453746174652e556e626f6e64696e67506572696f64292c0a090909226d61785f636c6f636b5f6472696674223a202072656e6465724475726174696f6e286c632e436c69656e7453746174652e4d6178436c6f636b4472696674292c0a09090922757067726164655f70617468223a202020202072656e646572537472696e6773286c632e436c69656e7453746174652e5570677261646550617468292c0a09097d290a09096c617374436f6e7353746174652c20666f756e64203a3d206c632e476574436f6e73656e7375735374617465286c632e4c61746573744865696768742829290a0909696620666f756e64207b0a0909096d5b226c6173745f636f6e73656e7375735f7374617465225d203d2072656e646572436f6e73656e7375735374617465286c632e4c617465737448656967687428292c206c617374436f6e735374617465290a09097d0a097d0a0972657475726e206a736f6e2e4f626a6563744e6f64652822222c206d290a7d0a0a66756e63202863202a636c69656e74292072656e646572436f6e73656e7375735374617465732872202a6d75782e5265717565737429202a6a736f6e2e4e6f6465207b0a0973776974636820632e747970207b0a0963617365206c69676874636c69656e742e54656e6465726d696e743a0a09096c63203a3d20632e6c69676874436c69656e742e282a74656e6465726d696e742e544d4c69676874436c69656e74290a090972657475726e206a736f6e706167652e52656e646572286c632e436f6e73656e737573537461746542794865696768742c20722c2066756e63286b20737472696e672c207620616e7929202a6a736f6e2e4e6f6465207b0a0909096373203a3d20762e282a74656e6465726d696e742e436f6e73656e7375735374617465290a090909686569676874203a3d2074797065732e50617273654865696768744e6174536f7274286b290a09090972657475726e2072656e646572436f6e73656e7375735374617465286865696768742c206373290a09097d290a097d0a0972657475726e206e6f64654572726f722822756e68616e646c656420636c69656e742074797065202573222c20632e747970290a7d0a0a66756e632072656e646572436f6e73656e7375735374617465286865696768742074797065732e4865696768742c206373202a74656e6465726d696e742e436f6e73656e737573537461746529202a6a736f6e2e4e6f6465207b0a0972657475726e206a736f6e2e4f626a6563744e6f64652822222c206d61705b737472696e675d2a6a736f6e2e4e6f64657b0a090922686569676874223a20202020202020202020202020202072656e64657248656967687428686569676874292c0a09092274696d657374616d70223a2020202020202020202020206a736f6e2e4e756d6265724e6f64652822222c20666c6f617436342863732e54696d657374616d702e556e6978282929292c0a090922726f6f74223a20202020202020202020202020202020206a736f6e2e537472696e674e6f64652822222c206261736536342e537464456e636f64696e672e456e636f6465546f537472696e672863732e526f6f742e4861736829292c0a0909226e6578745f76616c696461746f72735f68617368223a206a736f6e2e537472696e674e6f64652822222c206261736536342e537464456e636f64696e672e456e636f6465546f537472696e672863732e4e65787456616c696461746f72734861736829292c0a097d290a7d0a0a66756e632072656e646572506b6750617468287061746820737472696e672920737472696e67207b0a09636f6e737420707265666978203d2022676e6f2e6c616e64220a09696620737472696e67732e48617350726566697828706174682c2070726566697829207b0a090972657475726e2075666d742e537072696e746628225b602573605d28257329222c20706174682c20706174685b6c656e28707265666978293a5d290a097d0a0972657475726e20226022202b2070617468202b202260220a7d0a0a66756e632072656e64657241646472286164647220616464726573732920737472696e67207b0a0973203a3d20616464722e537472696e6728290a0973686f7274203a3d20730a096966206c656e287329203e203130207b0a090973686f7274203d20735b3a365d202b2022e280a622202b20735b6c656e2873292d343a5d0a097d0a0972657475726e2075666d742e537072696e746628225b25735d282f752f257329222c2073686f72742c2073290a7d0a0a66756e632072656e64657248656967687428682074797065732e48656967687429202a6a736f6e2e4e6f6465207b0a0972657475726e206a736f6e2e4f626a6563744e6f64652822222c206d61705b737472696e675d2a6a736f6e2e4e6f64657b0a0909227265766973696f6e5f6e756d626572223a206a736f6e2e4e756d6265724e6f64652822222c20666c6f6174363428682e5265766973696f6e4e756d62657229292c0a0909227265766973696f6e5f686569676874223a206a736f6e2e4e756d6265724e6f64652822222c20666c6f6174363428682e5265766973696f6e48656967687429292c0a097d290a7d0a0a66756e632072656e6465724672616374696f6e28662074656e6465726d696e742e4672616374696f6e29202a6a736f6e2e4e6f6465207b0a0972657475726e206a736f6e2e4f626a6563744e6f64652822222c206d61705b737472696e675d2a6a736f6e2e4e6f64657b0a0909226e756d657261746f72223a2020206a736f6e2e4e756d6265724e6f64652822222c20666c6f6174363428662e4e756d657261746f7229292c0a09092264656e6f6d696e61746f72223a206a736f6e2e4e756d6265724e6f64652822222c20666c6f6174363428662e44656e6f6d696e61746f7229292c0a097d290a7d0a0a66756e632072656e6465724475726174696f6e28642074696d652e4475726174696f6e29202a6a736f6e2e4e6f6465207b0a0972657475726e206a736f6e2e4e756d6265724e6f64652822222c20642e5365636f6e64732829290a7d0a0a66756e632072656e646572537472696e67732873205b5d737472696e6729202a6a736f6e2e4e6f6465207b0a09766172206e6f646573205b5d2a6a736f6e2e4e6f64650a09666f72205f2c2073203a3d2072616e67652073207b0a09096e6f646573203d20617070656e64286e6f6465732c206a736f6e2e537472696e674e6f64652822222c207329290a097d0a0972657475726e206a736f6e2e41727261794e6f64652822222c206e6f646573290a7d0a0a66756e632072656e6465724e6f64652877202a6d75782e526573706f6e73655772697465722c206e202a6a736f6e2e4e6f646529207b0a09627a2c20657272203a3d206a736f6e2e4d61727368616c286e290a0969662065727220213d206e696c207b0a090970616e696328657272290a097d0a09772e577269746528737472696e6728627a29290a7d0a0a66756e63206e6f64654572726f72436c69656e744e6f74466f756e6428696420737472696e6729202a6a736f6e2e4e6f6465207b0a0972657475726e206e6f64654572726f722822636c69656e74202573206e6f7420666f756e64222c206964290a7d0a0a66756e63206e6f64654572726f72286d736720737472696e672c2061726773202e2e2e616e7929202a6a736f6e2e4e6f6465207b0a0972657475726e206a736f6e2e4f626a6563744e6f64652822222c206d61705b737472696e675d2a6a736f6e2e4e6f64657b0a0909226572726f72223a206a736f6e2e537472696e674e6f64652822222c2075666d742e537072696e7466286d73672c20617267732e2e2e29292c0a097d290a7d0a0a66756e632072656e64657241646d696e2877202a6d75782e526573706f6e73655772697465722c2072202a6d75782e5265717565737429207b0a09766172206f757420737472696e67732e4275696c6465720a096f75742e5772697465537472696e67282223232041646d696e20262052656c61796572735c6e5c6e22290a096f75742e5772697465537472696e672875666d742e537072696e746628222d202a2a41646d696e2a2a3a2025735c6e222c2072656e646572416464722861646d696e2929290a0969662072656c61796572732e53697a652829203d3d2030207b0a09096f75742e5772697465537472696e6728222d202a2a52656c61796572732a2a3a20616e79202877686974656c69737420656d707479295c6e22290a097d20656c7365207b0a09096f75742e5772697465537472696e6728222d202a2a52656c61796572732a2a3a5c6e22290a090972656c61796572732e497465726174652822222c2022222c2066756e63286b20737472696e672c207620616e792920626f6f6c207b0a0909096f75742e5772697465537472696e672875666d742e537072696e7466282220202d2025735c6e222c2072656e646572416464722861646472657373286b292929290a09090972657475726e2066616c73650a09097d290a097d0a09772e5772697465286f75742e537472696e672829290a7d0a1ac0330a0973746f72652e676e6f12b2337061636b61676520636f72650a0a696d706f727420280a0922636861696e2f706172616d73220a0a0922676e6f2e6c616e642f702f6169622f6962632f617070220a0922676e6f2e6c616e642f702f6169622f6962632f686f7374220a0922676e6f2e6c616e642f702f6169622f6962632f6c69676874636c69656e74220a0922676e6f2e6c616e642f702f6169622f6962632f6c69676874636c69656e742f74656e6465726d696e74220a0922676e6f2e6c616e642f702f6169622f6962632f7479706573220a0922676e6f2e6c616e642f702f6e742f6270747265652f7630220a0922676e6f2e6c616e642f702f6e742f73657169642f7630220a0922676e6f2e6c616e642f702f6e742f75666d742f7630220a290a0a7661722073746f7265202a53746f72650a0a66756e6320696e69742829207b0a0973746f7265203d206e657753746f726528290a7d0a0a747970652053746f726520737472756374207b0a09636c69656e74536571202073657169642e49440a09636c69656e7442794944202a6270747265652e425054726565202f2f2069643a636c69656e740a0a09726f75746573206d61705b737472696e675d6962634170700a7d0a0a66756e63206e657753746f72652829202a53746f7265207b0a0972657475726e202653746f72657b0a0909636c69656e74427949443a206270747265652e4e6577425054726565333228292c0a0909726f757465733a20202020206d616b65286d61705b737472696e675d696263417070292c0a097d0a7d0a0a7479706520636c69656e7420737472756374207b0a0969642020202020202020202020202020202020202020202020737472696e670a0974797020202020202020202020202020202020202020202020737472696e670a0963726561746f72202020202020202020202020202020202020616464726573730a09636f756e7465727061727479436c69656e7449442020202020737472696e670a09636f756e74657270617274794d65726b6c65507265666978205b5d5b5d627974650a0a096c69676874436c69656e74206c69676874636c69656e742e496e746572666163650a0a0973656e6453657120202020202020202020202020202020202020202073657169642e49440a097061636b6574436f6d6d69746d656e747342795365712020202020202a6270747265652e425054726565202f2f2073657175656e63653a636f6d6d69746d656e740a097061636b6574526563656970747342795365712020202020202020202a6270747265652e425054726565202f2f2073657175656e63653a726563656970740a097061636b657441636b6e6f776c656467656d656e74734279536571202a6270747265652e425054726565202f2f2073657175656e63653a61636b0a0970656e64696e674173796e6341636b734279536571202020202020202a6270747265652e425054726565202f2f2073657175656e63653a70656e64696e674173796e6341636b0a7d0a0a747970652069626341707020737472756374207b0a096170702e4942434170700a09706b675061746820737472696e670a096164647265737320616464726573730a7d0a0a747970652070656e64696e674173796e6341636b20737472756374207b0a097061636b6574202020202074797065732e5061636b65740a09617070506b675061746820737472696e670a7d0a0a66756e63202863202a636c69656e7429206765745061636b6574436f6d6d69746d656e742873657175656e63652075696e74363429205b5d62797465207b0a0976203a3d20632e7061636b6574436f6d6d69746d656e747342795365712e4765742873657169642e49442873657175656e6365292e42696e6172792829290a0969662076203d3d206e696c207b0a090972657475726e206e696c0a097d0a0972657475726e20762e285b5d62797465290a7d0a0a2f2f207365745061636b6574436f6d6d69746d656e742073746f72657320746865207061636b657420636f6d6d69746d656e7420696e2074686520636c69656e74206461746120616e6420696e0a2f2f2074686520706172616d732e0a66756e63202863202a636c69656e7429207365745061636b6574436f6d6d69746d656e742873657175656e63652075696e7436342c207061636b65742074797065732e5061636b657429207b0a09636f6d6d69746d656e74203a3d2074797065732e436f6d6d69745061636b6574287061636b6574290a0a092f2f2073746f726520696e20636c69656e7420646174610a09632e7061636b6574436f6d6d69746d656e747342795365712e5365742873657169642e49442873657175656e6365292e42696e61727928292c20636f6d6d69746d656e74290a0a092f2f2073746f726520696e20706172616d732c20736f2069742069732070726f7661626c652062792074686520636f756e74657270617274790a092f2f20546865206b65792077696c6c2062653a20286162636920706174683d2e73746f72652f6d61696e2f6b6579290a092f2f20222f70762f766d3a676e6f2e6c616e642f722f6169622f6962632f636f72653a22202b20636c69656e744944202b20223122202b2073657175656e63650a096b6579203a3d20686f73742e5061636b6574436f6d6d69746d656e744b657928632e69642c2073657175656e6365290a09736574436861696e506172616d286b65792c20636f6d6d69746d656e74290a7d0a0a66756e63202863202a636c69656e74292064656c6574655061636b6574436f6d6d69746d656e742873657175656e63652075696e74363429207b0a09632e7061636b6574436f6d6d69746d656e747342795365712e52656d6f76652873657169642e49442873657175656e6365292e42696e6172792829290a0a096b6579203a3d20686f73742e5061636b6574436f6d6d69746d656e744b657928632e69642c2073657175656e6365290a09736574436861696e506172616d286b65792c206e696c290a7d0a0a2f2f206861735061636b6574526563656970742072657475726e73207472756520696620746865207061636b65742072656365697074206578697374732c206f74686572776973652066616c73652e0a66756e63202863202a636c69656e7429206861735061636b6574526563656970742873657175656e63652075696e7436342920626f6f6c207b0a0972657475726e20632e7061636b6574526563656970747342795365712e4861732873657169642e49442873657175656e6365292e42696e6172792829290a7d0a0a2f2f207365745061636b6574526563656970742077726974657320746865207061636b6574207265636569707420756e64657220746865207265636569707420706174680a2f2f20546869732069732061207075626c696320706174682074686174206973207374616e64617264697a656420627920746865204942432056322073706563696669636174696f6e2e0a66756e63202863202a636c69656e7429207365745061636b6574526563656970742873657175656e63652075696e74363429207b0a0976616c7565203a3d205b5d627974657b627974652832297d202f2f20636f6e7374616e742076616c75650a0a092f2f2073746f726520696e20636c69656e7420646174610a09632e7061636b6574526563656970747342795365712e5365742873657169642e49442873657175656e6365292e42696e61727928292c2076616c7565290a0a092f2f2073746f726520696e20706172616d732c20736f2069742069732070726f7661626c652062792074686520636f756e74657270617274790a092f2f20546865206b65792077696c6c2062653a20286162636920706174683d2e73746f72652f6d61696e2f6b6579290a092f2f20222f70762f766d3a676e6f2e6c616e642f722f6169622f6962632f636f72653a22202b20636c69656e744944202b20223222202b2073657175656e63650a096b6579203a3d20686f73742e5061636b6574526563656970744b657928632e69642c2073657175656e6365290a09736574436861696e506172616d286b65792c2076616c7565290a7d0a0a66756e63202863202a636c69656e74292064656c6574655061636b6574526563656970742873657175656e63652075696e74363429207b0a09632e7061636b6574526563656970747342795365712e52656d6f76652873657169642e49442873657175656e6365292e42696e6172792829290a0a096b6579203a3d20686f73742e5061636b6574526563656970744b657928632e69642c2073657175656e6365290a09736574436861696e506172616d286b65792c206e696c290a7d0a0a2f2f206861735061636b657441636b6e6f776c656467656d656e7420636865636b7320696620746865207061636b65742061636b206861736820697320616c7265616479206f6e207468650a2f2f2073746f72652e0a66756e63202863202a636c69656e7429206861735061636b657441636b6e6f776c656467656d656e742873657175656e63652075696e7436342920626f6f6c207b0a0972657475726e20632e7061636b657441636b6e6f776c656467656d656e747342795365712e4861732873657169642e49442873657175656e6365292e42696e6172792829290a7d0a0a2f2f204765745061636b657441636b6e6f776c656467656d656e74206665746368657320746865207061636b65742061636b6e6f776c656467656d656e742066726f6d207468652073746f72652e0a66756e63202863202a636c69656e7429206765745061636b657441636b6e6f776c656467656d656e742873657175656e63652075696e74363429205b5d62797465207b0a0976203a3d20632e7061636b657441636b6e6f776c656467656d656e747342795365712e4765742873657169642e49442873657175656e6365292e42696e6172792829290a0969662076203d3d206e696c207b0a090972657475726e206e696c0a097d0a0972657475726e20762e285b5d62797465290a7d0a0a66756e63202863202a636c69656e7429207361766550656e64696e674173796e6341636b287061636b65742074797065732e5061636b65742c20617070506b675061746820737472696e6729207b0a0973657175656e6365203a3d207061636b65742e53657175656e63650a09696620632e68617350656e64696e674173796e6341636b2873657175656e636529207b0a090970616e69632875666d742e537072696e7466282270656e64696e67206173796e632061636b20616c72656164792065786973747320666f722073657175656e6365202564222c2073657175656e636529290a097d0a09696620632e6861735061636b657441636b6e6f776c656467656d656e742873657175656e636529207b0a090970616e69632875666d742e537072696e7466282261636b6e6f776c656467656d656e7420616c7265616479207772697474656e20666f722073657175656e6365202564222c2073657175656e636529290a097d0a09632e70656e64696e674173796e6341636b7342795365712e5365742873657169642e49442873657175656e6365292e42696e61727928292c202670656e64696e674173796e6341636b7b0a09097061636b65743a20202020207061636b65742c0a0909617070506b67506174683a20617070506b67506174682c0a097d290a7d0a0a66756e63202863202a636c69656e74292067657450656e64696e674173796e6341636b2873657175656e63652075696e7436342920282a70656e64696e674173796e6341636b2c20626f6f6c29207b0a0976203a3d20632e70656e64696e674173796e6341636b7342795365712e4765742873657169642e49442873657175656e6365292e42696e6172792829290a0969662076203d3d206e696c207b0a090972657475726e206e696c2c2066616c73650a097d0a0972657475726e20762e282a70656e64696e674173796e6341636b292c20747275650a7d0a0a66756e63202863202a636c69656e74292064656c65746550656e64696e674173796e6341636b2873657175656e63652075696e74363429207b0a09632e70656e64696e674173796e6341636b7342795365712e52656d6f76652873657169642e49442873657175656e6365292e42696e6172792829290a7d0a0a66756e63202863202a636c69656e74292068617350656e64696e674173796e6341636b2873657175656e63652075696e7436342920626f6f6c207b0a0972657475726e20632e70656e64696e674173796e6341636b7342795365712e4861732873657169642e49442873657175656e6365292e42696e6172792829290a7d0a0a2f2f205365745061636b657441636b6e6f776c656467656d656e7420777269746573207468652061636b6e6f776c656467656d656e74206861736820756e646572207468650a2f2f2061636b6e6f776c656467656d656e7420706174680a2f2f20546869732069732061207075626c696320706174682074686174206973207374616e64617264697a656420627920746865204942432056322073706563696669636174696f6e2e0a66756e63202863202a636c69656e7429207365745061636b657441636b6e6f776c656467656d656e742873657175656e63652075696e7436342c2061636b48617368205b5d6279746529207b0a092f2f2073746f726520696e20636c69656e7420646174610a09632e7061636b657441636b6e6f776c656467656d656e747342795365712e5365742873657169642e49442873657175656e6365292e42696e61727928292c2061636b48617368290a0a092f2f2073746f726520696e20706172616d732c20736f2069742069732070726f7661626c652062792074686520636f756e74657270617274790a092f2f20546865206b65792077696c6c2062653a20286162636920706174683d2e73746f72652f6d61696e2f6b6579290a092f2f20222f70762f766d3a676e6f2e6c616e642f722f6169622f6962632f636f72653a22202b20636c69656e744944202b20223322202b2073657175656e63650a096b6579203a3d20686f73742e5061636b657441636b6e6f776c656467656d656e744b657928632e69642c2073657175656e6365290a09736574436861696e506172616d286b65792c2061636b48617368290a7d0a0a66756e63202863202a636c69656e74292064656c6574655061636b657441636b6e6f776c656467656d656e742873657175656e63652075696e74363429207b0a09632e7061636b657441636b6e6f776c656467656d656e747342795365712e52656d6f76652873657169642e49442873657175656e6365292e42696e6172792829290a0a096b6579203a3d20686f73742e5061636b657441636b6e6f776c656467656d656e744b657928632e69642c2073657175656e6365290a09736574436861696e506172616d286b65792c206e696c290a7d0a0a66756e6320736574436861696e506172616d286b65792c2076616c7565205b5d6279746529207b0a092f2f20544f444f2066696e6420612077617920746f20617373657274207468697320777269746520696e2074657374732028646966666963756c742073696e636520706172616d7320646f65730a092f2f206e6f74206578706f73652067657474657273290a09706172616d732e536574427974657328737472696e67286b6579292c2076616c7565290a7d0a0a66756e6320286373202a53746f72652920616464436c69656e742874797020737472696e672c2063726561746f72206164647265737329202a636c69656e74207b0a096964203a3d2075666d742e537072696e7466282225732d2564222c207479702c2075696e7436342863732e636c69656e745365712e4e657874282929290a0963203a3d2026636c69656e747b0a090969643a20202020202069642c0a09097479703a20202020207479702c0a090963726561746f723a2063726561746f722c0a0a09097061636b6574436f6d6d69746d656e747342795365713a2020202020206270747265652e4e6577425054726565333228292c0a09097061636b6574526563656970747342795365713a2020202020202020206270747265652e4e6577425054726565333228292c0a09097061636b657441636b6e6f776c656467656d656e747342795365713a206270747265652e4e6577425054726565333228292c0a090970656e64696e674173796e6341636b7342795365713a202020202020206270747265652e4e6577425054726565333228292c0a097d0a0973776974636820747970207b0a0963617365206c69676874636c69656e742e54656e6465726d696e743a0a0909632e6c69676874436c69656e74203d2074656e6465726d696e742e4e6577544d4c69676874436c69656e7428290a0964656661756c743a0a090970616e69632822756e68616e646c6564206c6967687420636c69656e7420747970652022202b20747970290a097d0a0963732e636c69656e74427949442e5365742869642c2063290a0972657475726e20630a7d0a0a66756e6320286373202a53746f72652920676574436c69656e7428696420737472696e6729202a636c69656e74207b0a09636c69203a3d2063732e636c69656e74427949442e476574286964290a09696620636c69203d3d206e696c207b0a090972657475726e206e696c0a097d0a0972657475726e20636c692e282a636c69656e74290a7d0a0a2f2f20726f7574652072657475726e7320616e2049424341707020666f722074686520676976656e20706f727449442e0a66756e6320286373202a53746f72652920726f75746528706f7274494420737472696e672920696263417070207b0a096170702c206f6b203a3d2063732e726f757465735b706f727449445d0a09696620216f6b207b0a090970616e69632875666d742e537072696e746628226e6f20726567697374657265642061707020666f7220706f7274202573222c20706f7274494429290a097d0a0972657475726e206170700a7d0a1aae240a11757067726164652d636c69656e742e6d64129824232055706772616465436c69656e740a0a60636f72652e55706772616465436c69656e746020666f6c6c6f7773206120636f756e746572706172747920636861696e207468726f7567682061202a2a627265616b696e672a2a0a7570677261646520e2809420616e207570677261646520746861742060557064617465436c69656e74602063616e6e6f74206162736f726220626563617573652074686520636861696e0a697473656c66206368616e6765732073686170652e205479706963616c2074726967676572733a0a0a2d2060436861696e494460206368616e676520287369676e656420696e20657665727920686561646572292e0a2d205265766973696f6e2d6e756d6265722062756d702028656e636f64656420696e2060486569676874602c2070726f7061676174657320696e746f206865616465720a20207369676e696e67292e0a2d2056616c696461746f722d73657420646973636f6e74696e75697479202861206861726420666f726b207468617420646f65736e2774207072657365727665207468650a20207472757374206772617068292e0a2d2053656375726974792d706172616d65746572206368616e6765732074686520636c69656e74206361727269657320e280942060556e626f6e64696e67506572696f64602c0a20206050726f6f665370656373602c20605570677261646550617468602e0a0a606060676e6f0a636f72652e55706772616465436c69656e742863726f73732c20636c69656e7449442c0a202020207570677261646564436c69656e742c207570677261646564436f6e7353746174652c0a2020202070726f6f6655706772616465436c69656e742c2070726f6f6655706772616465436f6e735374617465290a6060600a0a47617465642062792060656e73757265417574686f72697a656452656c617965722829602e0a0a2323204c6966656379636c650a0a23232320312e20436f756e7465727061727479207363686564756c65732074686520757067726164650a0a4265666f7265207468652075706772616465206865696768742074686520636f756e746572706172747920636861696e20636f6d6d697473207468652075706772616465640a60436c69656e7453746174656020616e642060436f6e73656e73757353746174656020746f2069747320757067726164652073746f72652061742077656c6c2d6b6e6f776e0a706174687320646572697665642066726f6d2074686520636c69656e742773206055706772616465506174686020287468652053444b2075706772616465206d6f64756c650a64656661756c7420697320605b2275706772616465222c202275706772616465644942435374617465225d60293a0a0a2d20607b55706772616465506174685b305d7d2f7b55706772616465506174685b315d7d2f7b487d2f7570677261646564436c69656e74600a2d20607b55706772616465506174685b305d7d2f7b55706772616465506174685b315d7d2f7b487d2f7570677261646564436f6e735374617465600a0a60486020697320746865207570677261646520706c616e2068656967687420286a7573742074686520686569676874206e756d6265722c206e6f742061207265766973696f6e0a70616972292e2054686520636f6d6d69747465642062797465732061726520606364632e4d61727368616c496e74657266616365282e2e2e29602d7374796c653a0a7772617070656420696e2060676f6f676c652e70726f746f6275662e416e796020776974682074686520636f72726573706f6e64696e6720747970652055524c2e0a0a54686520636f6d6d69747465642060436c69656e74537461746560206861732069747320636c69656e742d637573746f6d697a61626c65206669656c64730a286054727573744c6576656c602c20605472757374696e67506572696f64602c20604d6178436c6f636b4472696674602c206046726f7a656e4865696768746029207a65726f65640a6f757420e28094206f6e6c792074686520636861696e2d737065636966696564206669656c6473202860436861696e4944602c2060556e626f6e64696e67506572696f64602c0a604c6174657374486569676874602c206050726f6f665370656373602c2060557067726164655061746860292061726520636f6d6d69747465642e205468650a636f6d6d69747465642060436f6e73656e737573537461746560206361727269657320612073656e74696e656c2060526f6f74602062656361757365207468650a757067726164656420636861696e206861736e27742070726f647563656420616e7920626c6f636b732079657420617420636f6d6d69742074696d652e0a0a23232320322e2052656c6179657220726561647320616e64207375626d6974730a0a4f6e63652074686520636861696e206861732072656163686564207468652075706772616465206865696768742c20612072656c61796572207265616473207468650a636f6d6d69747465642073746174657320616e642074686520636f72726573706f6e64696e67204943532d3233206d656d626572736869702070726f6f667320616e640a63616c6c73206055706772616465436c69656e74602e20456163682070726f6f66206973206120636861696e656420605b5d69637332332e436f6d6d69746d656e7450726f6f66600a6d61746368696e672074686520636c69656e742773206050726f6f6653706563736020287479706963616c6c7920322070726f6f66733a20616e204941564c0a6578697374656e63652070726f6f6620666f722074686520696e6e6572206b657920616e642061206d756c746973746f7265206578697374656e63652070726f6f6620666f720a7468652073746f7265206e616d65292e0a0a23232320332e20566572696669636174696f6e0a0a546865207265616c6d20636865636b733a0a0a2d2060557067726164655061746860206f6e207468652063757272656e7420636c69656e74206973206e6f6e2d656d7074792e0a2d20604c617465737448656967687460206f662074686520757067726164656420636c69656e742069732067726561746572207468616e207468652063757272656e740a20206c6174657374206865696768742e0a2d205468652072656c617965722773207375626d697474656420636c69656e742f636f6e73656e73757320737461746573207265636f6e737472756374207468652062797465730a202074686520636861696e20636f6d6d69747465643a207468652060436c69656e74537461746560206973207772617070656420696e0a202060676f6f676c652e70726f746f6275662e416e796020776974682069747320637573746f6d697a61626c65206669656c6473207a65726f65640a202028605a65726f437573746f6d4669656c647360292c207468652060436f6e73656e737573537461746560206973207772617070656420696e2060416e79602061732d69732e0a2d20426f746820636861696e65642070726f6f667320766572696679206d656d6265727368697020756e646572207468652063757272656e7420636c69656e7427730a20206c617465737420636f6e73656e73757320726f6f742e0a0a23232320342e205374617465207472616e736974696f6e0a0a41206e65772060436c69656e74537461746560206973206275696c74206279206d6978696e67206669656c647320287365652062656c6f77292e205468652073746f7265640a636f6e73656e73757320737461746520686173206974732060526f6f7460206f7665727772697474656e2077697468207468652073656e74696e656c2076616c756520736f2069740a63616e6e6f74206265207573656420746f20766572696679207061636b65742070726f6f667320e2809420627920636f6e737472756374696f6e2e205265616c20726f6f74730a6172726976652061667465727761726473207669612060557064617465436c69656e7460206f6e636520746865206e657720636861696e2069732070726f647563696e670a686561646572732e0a0a23232320352e20526573756d650a0a53756273657175656e742060557064617465436c69656e74602063616c6c73206272696e67207265616c20686561646572732066726f6d20746865206e657720636861696e3b0a7061636b657420666c6f7720726573756d657320616761696e73742074686f7365207265616c20636f6e73656e737573207374617465732e0a0a232320507265636f6e646974696f6e730a0a2d2054686520636c69656e742065786973747320616e64207468652063616c6c657220697320696e207468652072656c617965722077686974656c6973742e0a2d2054686520636c69656e7427732060557067726164655061746860206973207365742e0a2d2054686520636c69656e742773207374617475732069732060416374697665602e0a2d2054686520757067726164656420636c69656e74277320604c6174657374486569676874602069732067726561746572207468616e207468652063757272656e740a20206c6174657374206865696768742e0a2d20426f74682070726f6f667320766572696679206d656d62657273686970206f662074686520757067726164656420636c69656e7420616e6420636f6e73656e7375730a20207374617465206174207468652075706772616465207061746820756e646572207468652063757272656e74206c617465737420636f6e73656e73757320726f6f742e0a0a2323204669656c64206d617070696e670a0a4f6e207375636365737320746865206e65772060436c69656e74537461746560206973206275696c7420627920636f6d62696e696e673a0a0a2d202a2a54616b656e2066726f6d2074686520757067726164656420636c69656e742a2a2028636861696e2d737065636966696564293a2060436861696e4944602c0a202060556e626f6e64696e67506572696f64602c20604c6174657374486569676874602c206050726f6f665370656373602c20605570677261646550617468602e0a2d202a2a5072657365727665642066726f6d207468652063757272656e7420636c69656e742a2a2028637573746f6d697a61626c65293a206054727573744c6576656c602c0a2020605472757374696e67506572696f64602c20604d6178436c6f636b4472696674602e0a2d202a2a52657365742a2a3a206046726f7a656e486569676874602e0a0a49662074686520756e626f6e64696e6720706572696f6420736872616e6b2c20605472757374696e67506572696f6460206973207363616c65640a70726f706f7274696f6e616c6c7920736f2074686520736563757269747920726174696f206973207072657365727665643a0a0a202020206e65775472757374696e67203d207472757374696e67202a206e6577556e626f6e64696e67202f2063757272656e74556e626f6e64696e6720287472756e6361746564290a0a546865206d61746820697320706572666f726d656420696e207365636f6e64733b207375622d7365636f6e6420707265636973696f6e20697320697272656c6576616e7420666f720a7472757374696e6720706572696f647320776869636820617265206d6561737572656420696e20646179732e0a0a23232047656e65726174696e672070726f6f667320666f722074657374696e670a0a60636d642f67656e2d70726f6f66602070726f766964657320616e2060757067726164656020737562636f6d6d616e642074686174206d6f756e747320616e0a607570677261646560204941564c2073746f72652c20636f6d6d6974732074686520757067726164656420636c69656e742f636f6e73656e7375732073746174652061740a7468652053444b2075706772616465206b657973202860757067726164656449424353746174652f7b487d2f7570677261646564436c69656e74602c0a60757067726164656449424353746174652f7b487d2f7570677261646564436f6e73537461746560292c20616e642064756d707320476f206c69746572616c7320666f72207468650a636861696e65642070726f6f66733a0a0a606060626173680a676f2072756e202d43202e2f636d642f67656e2d70726f6f66202e20757067726164650a6060600a0a546865206f7574707574206973207375697461626c6520666f7220656d62656464696e6720696e20612066696c65746573742e205365650a607a3130635f757067726164655f636c69656e745f66696c65746573742e676e6f6020666f72207468652066756c6c2068617070792d70617468207363656e6172696f2e0a22230a132f676e6f2e4d656d5061636b61676554797065120c0a0a4d505573657250726f64","leaf":{"hash":"sha256","prehash_key":"no_hash","prehash_value":"sha256","length":"var_proto","prefix":"0x00"},"path":[{"hash":"sha256","prefix":"0x01","suffix":"0xa635c33c0fddad3cd43d5bc83fac6857d40ec3f9d0a8d651f1958545c7db8592"},{"hash":"sha256","prefix":"0x01","suffix":"0xda02180767c0b37107d9c8c73cbc75b9f29d4bc6b1ffbe43fff80841334ebe0f"},{"hash":"sha256","prefix":"0x01fc2ef7d31ceed14a3d4283dec5795e5f22d041e9124016e0008034ab636aa7ed","suffix":"0x0"},{"hash":"sha256","prefix":"0x01","suffix":"0x2c755b4d9cf97e5fcfed72da7c6613eb941a3e7612a94bf85f79c616e1681a67"},{"hash":"sha256","prefix":"0x01","suffix":"0x71395ce4ee8aa51a202b87ca7dc7e660e7bd01a6f9d5531f837dae98911f6c34"},{"hash":"sha256","prefix":"0x01fea892c186bd0fb993151beaa6c917fce1430783f4e290858f6a02ffad81217c","suffix":"0x0"},{"hash":"sha256","prefix":"0x01","suffix":"0xdc5190d9e27040a78aa6141ce30bb72f3327aa64b3ab84eeda8e7d319be8633a"},{"hash":"sha256","prefix":"0x01522e908201acf762f9a637fae686ff1d76a8d97d6ebd93d24830d1d56dce9a71","suffix":"0x0"},{"hash":"sha256","prefix":"0x010774b6612375062ad32453d4debbd15a85807d5195f63bdaa505011ff1396414","suffix":"0x0"},{"hash":"sha256","prefix":"0x01617efb69eaa0c7e373aa3c42a171a61a79338f834aee5d09fdfea319aa118b16","suffix":"0x0"},{"hash":"sha256","prefix":"0x01","suffix":"0xdbc1b4c900ffe48d575b5da5c638040125f65db0fe3e24494b76ea986457d986"},{"hash":"sha256","prefix":"0x01310aa2cf58f41c59ec30183b4b792ecb415bf56cb53d076de979b3565fb9abae","suffix":"0x0"},{"hash":"sha256","prefix":"0x01","suffix":"0xdbc1b4c900ffe48d575b5da5c638040125f65db0fe3e24494b76ea986457d986"},{"hash":"sha256","prefix":"0x01","suffix":"0xdbc1b4c900ffe48d575b5da5c638040125f65db0fe3e24494b76ea986457d986"},{"hash":"sha256","prefix":"0x01","suffix":"0xdbc1b4c900ffe48d575b5da5c638040125f65db0fe3e24494b76ea986457d986"}]}},{"@type":"exist","@value":{"key":"0x6d61696e","value":"0xb036506d578084eb69cea7bf812b1fa4a1652d31d219e8c169e92bf7f405d678","leaf":{"hash":"sha256","prehash_key":"no_hash","prehash_value":"sha256","length":"var_proto","prefix":"0x00"},"path":[{"hash":"sha256","prefix":"0x01ccb581a002a493db462c72bc97aac085192a8ffb6a45fa5ee3cf22ee89eb1574","suffix":"0x0"}]}}]} \ No newline at end of file diff --git a/lib/ics23/src/existence_proof.rs b/lib/ics23/src/existence_proof.rs index 526df4224d7..139c9ebf1be 100644 --- a/lib/ics23/src/existence_proof.rs +++ b/lib/ics23/src/existence_proof.rs @@ -41,7 +41,7 @@ pub fn check_against_spec( } if let Some(max_depth) = spec.max_depth - && existence_proof.path.len() < max_depth.inner() + && existence_proof.path.len() > max_depth.inner() { return Err(SpecMismatchError::InnerDepthTooLong { path_len: existence_proof.path.len(), diff --git a/lib/ics23/src/ibc_api.rs b/lib/ics23/src/ibc_api.rs index cf52fd0ff41..eae3597f520 100644 --- a/lib/ics23/src/ibc_api.rs +++ b/lib/ics23/src/ibc_api.rs @@ -4,13 +4,14 @@ use unionlabs::{ primitives::Bytes, }; -pub use crate::proof_specs::{IAVL_PROOF_SPEC, TENDERMINT_PROOF_SPEC}; +pub use crate::proof_specs::{BPTREE_PROOF_SPEC, IAVL_PROOF_SPEC, TENDERMINT_PROOF_SPEC}; use crate::{ existence_proof, verify::{self}, }; pub const SDK_SPECS: [ProofSpec; 2] = [IAVL_PROOF_SPEC, TENDERMINT_PROOF_SPEC]; +pub const GNO_SPECS: [ProofSpec; 2] = [BPTREE_PROOF_SPEC, TENDERMINT_PROOF_SPEC]; #[derive(Debug, Clone, PartialEq, thiserror::Error)] pub enum VerifyMembershipError { diff --git a/lib/ics23/src/proof_specs.rs b/lib/ics23/src/proof_specs.rs index b0643578016..be0a58dbe11 100644 --- a/lib/ics23/src/proof_specs.rs +++ b/lib/ics23/src/proof_specs.rs @@ -1,6 +1,7 @@ use std::borrow::Cow; use unionlabs::{ + bounded::BoundedUsize, cosmos::ics23::{ hash_op::HashOp, inner_spec::{InnerSpec, PositiveI32AsUsize}, @@ -68,6 +69,42 @@ pub const TENDERMINT_PROOF_SPEC: ProofSpec = ProofSpec { prehash_key_before_comparison: false, }; +pub const BPTREE_PROOF_SPEC: ProofSpec = ProofSpec { + leaf_spec: LeafOp { + hash: HashOp::Sha256, + prehash_key: HashOp::NoHash, + prehash_value: HashOp::Sha256, + length: LengthOp::VarProto, + prefix: Bytes::new_static(&[0]), + }, + inner_spec: InnerSpec { + child_order: Cow::Borrowed( + const { + &[ + result_unwrap!(PositiveI32AsUsize::new_const(0)), + result_unwrap!(PositiveI32AsUsize::new_const(1)), + ] + }, + ), + child_size: result_unwrap!(PositiveI32AsUsize::new_const(32)), + min_prefix_length: result_unwrap!(PositiveI32AsUsize::new_const(1)), + max_prefix_length: result_unwrap!(PositiveI32AsUsize::new_const(1)), + empty_child: Bytes::new_static(&[ + 0xdb, 0xc1, 0xb4, 0xc9, 0x00, 0xff, 0xe4, 0x8d, 0x57, 0x5b, 0x5d, 0xa5, 0xc6, 0x38, + 0x04, 0x01, 0x25, 0xf6, 0x5d, 0xb0, 0xfe, 0x3e, 0x24, 0x49, 0x4b, 0x76, 0xea, 0x98, + 0x64, 0x57, 0xd9, 0x86, + ]), + hash: HashOp::Sha256, + }, + max_depth: Some(result_unwrap!( + BoundedUsize::<1, { i32::MAX as usize }>::new_const(60) + )), + min_depth: Some(result_unwrap!( + BoundedUsize::<1, { i32::MAX as usize }>::new_const(5) + )), + prehash_key_before_comparison: false, +}; + #[must_use] pub fn compatible(lhs: &ProofSpec, rhs: &ProofSpec) -> bool { lhs.leaf_spec.hash == rhs.leaf_spec.hash diff --git a/voyager/modules/client-bootstrap/gno/src/main.rs b/voyager/modules/client-bootstrap/gno/src/main.rs index ae97e5a9a45..4d2fc797bc9 100644 --- a/voyager/modules/client-bootstrap/gno/src/main.rs +++ b/voyager/modules/client-bootstrap/gno/src/main.rs @@ -1,7 +1,7 @@ use std::{fmt::Debug, num::NonZeroU64, time::Duration}; use gno_light_client_types::{ClientState, ConsensusState, Fraction}; -use ics23::ibc_api::SDK_SPECS; +use ics23::ibc_api::GNO_SPECS; use jsonrpsee::{Extensions, core::async_trait}; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -133,7 +133,7 @@ impl ClientBootstrapModuleServer for Module { 0, height.inner().try_into().expect("is within bounds; qed;"), ), - proof_specs: SDK_SPECS.into(), + proof_specs: GNO_SPECS.into(), upgrade_path: vec!["upgrade".into(), "upgradedIBCState".into()], realm: self.ibc_core_realm.clone(), }) diff --git a/voyager/modules/state/gno/Cargo.toml b/voyager/modules/state/gno/Cargo.toml index c6f5ae5a325..7c650fcbad6 100644 --- a/voyager/modules/state/gno/Cargo.toml +++ b/voyager/modules/state/gno/Cargo.toml @@ -20,7 +20,7 @@ gno-rpc = { workspace = true } ibc-union-spec = { workspace = true, features = ["serde", "ethabi"] } jsonrpsee = { workspace = true, features = ["macros", "server", "tracing"] } protos = { workspace = true } -reqwest = { workspace = true, features = ["json"] } +reqwest = { workspace = true, features = ["json", "rustls-tls"] } serde = { workspace = true, features = ["derive"] } serde-utils = { workspace = true } serde_json = { workspace = true } diff --git a/voyager/modules/state/gno/src/main.rs b/voyager/modules/state/gno/src/main.rs index 8f5fe4a19b5..84005678874 100644 --- a/voyager/modules/state/gno/src/main.rs +++ b/voyager/modules/state/gno/src/main.rs @@ -3,12 +3,13 @@ use std::fmt::Display; +use futures::{TryFutureExt, TryStreamExt, stream::FuturesUnordered}; use ibc_union_spec::{ Channel, ChannelId, ClientId, Connection, ConnectionId, IbcUnion, MustBeZero, Packet, Status, path::{BatchPacketsPath, BatchReceiptsPath, StorePath}, query::{ ClientStatus, PacketAckByHash, PacketAckByHashResponse, PacketByHash, PacketByHashResponse, - PacketsByBatchHash, Query, + PacketsByBatchHash, PacketsByBatchHashResponse, Query, }, }; use jsonrpsee::{Extensions, core::async_trait}; @@ -51,6 +52,8 @@ pub struct Module { pub tx_indexer_rpc_url: String, pub ibc_core_realm: String, + + pub max_query_window: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -59,12 +62,22 @@ pub struct Config { pub rpc_url: String, pub tx_indexer_rpc_url: String, pub ibc_core_realm: String, + + #[serde(default)] + pub max_query_window: Option, } impl StateModule for Module { type Config = Config; async fn new(config: Self::Config, info: StateModuleInfo) -> anyhow::Result { + if config.max_query_window == Some(0) { + return Err(anyhow::anyhow!( + "max_query_window must not be 0 (omit it or set it to `null` to search \ + unbounded instead)" + )); + } + let gno_client = gno_rpc::Client::new(config.rpc_url).await?; let chain_id = gno_client.status(None).await?.node_info.network; @@ -78,10 +91,35 @@ impl StateModule for Module { tx_indexer_rpc_url: config.tx_indexer_rpc_url, chain_id: ChainId::new(chain_id), ibc_core_realm: config.ibc_core_realm, + max_query_window: config.max_query_window, }) } } +fn mk_windows(latest_height: u64, window: u64) -> Vec<(u64, u64)> { + let mut windows = Vec::new(); + let mut hi = latest_height; + + loop { + let mut lo = hi.saturating_sub(window.saturating_sub(1)); + + // avoid a degenerate trailing (0, 0) window + if lo <= 1 { + lo = 0; + } + + windows.push((lo, hi)); + + if lo == 0 { + break; + } + + hi = lo - 1; + } + + windows +} + impl Module { #[instrument(skip_all, fields(chain_id = %self.chain_id, %channel_id, %packet_hash))] pub async fn query_packet_by_hash( @@ -90,11 +128,45 @@ impl Module { packet_hash: H256, ) -> RpcResult { let ibc_core_realm = &self.ibc_core_realm; - let query = format!( - r#"query getEvents {{ + + let windows: Vec> = match self.max_query_window { + Some(window) => { + let latest_height = self + .gno_client + .status(None) + .await + .map_err(RpcError::retryable(format!( + "error querying latest height while constructing query windows \ + for packet {packet_hash}", + )))? + .sync_info + .latest_block_height; + + mk_windows(latest_height, window) + .into_iter() + .map(Some) + .collect() + } + None => vec![None], + }; + + let mut res = None; + + for window in windows { + let block_height_filter = window.map_or_else(String::new, |(lo, hi)| { + format!( + "block_height: {{ gt: {}, lt: {} }},", + lo.saturating_sub(1), + hi.saturating_add(1) + ) + }); + + let query = format!( + r#"query getEvents {{ getTransactions( where: {{ success: {{ eq: true }}, + {block_height_filter} response: {{ events: {{ _and: [ @@ -139,9 +211,228 @@ impl Module { }} }} }}"# - ); + ); + + println!("{query}"); + + let window_res = self + .tx_indexer_client + .post(&self.tx_indexer_rpc_url) + .json(&json!({ + "operationName": "getEvents", + "query": query + })) + .send() + .await + .map_err(RpcError::retryable("error sending graphql query"))? + .json::() + .await + .map_err(RpcError::retryable( + "invalid json returned from graphql query", + ))?; + + // indexer returns null (not []) for getTransactions when there's no match + let empty = Vec::new(); + let transactions_in_window = match window_res.pointer("/data/getTransactions") { + Some(Value::Array(transactions)) => transactions, + Some(Value::Null) => &empty, + _ => { + return Err(RpcError::retryable_from_message( + "no getTransactions in graphql response", + )); + } + }; + + match transactions_in_window.len() { + 0 => { + debug!(?window, "packet not found in window"); + continue; + } + 1 => { + res = Some(window_res); + break; + } + found => { + return Err(RpcError::retryable_from_message(format!( + "expected exactly one transaction matching channel_id {channel_id} \ + and packet_hash {packet_hash} in window {window:?}, found {found}", + ))); + } + } + } + + let res = res.ok_or_else(|| { + RpcError::retryable_from_message(format!( + "no packet_send event matching channel_id {channel_id} / packet_hash \ + {packet_hash} found after searching all windows", + )) + })?; + + let hash = res + .pointer("/data/getTransactions/0/hash") + .ok_or_else(|| RpcError::fatal_from_message("no tx hash in graphql response"))? + .as_str() + .ok_or_else(|| { + RpcError::fatal_from_message("tx hash in graphql response is not a string") + })? + .parse::>() + .map_err(RpcError::fatal("invalid tx hash in graphql response"))?; + + let height = res + .pointer("/data/getTransactions/0/block_height") + .ok_or_else(|| RpcError::fatal_from_message("no block height in graphql response"))? + .as_number() + .ok_or_else(|| { + RpcError::fatal_from_message("block height in graphql response is not a number") + })? + .as_u64() + .ok_or_else(|| { + RpcError::fatal_from_message("block height in graphql response is not a u64") + })?; + + let events = res + .pointer("/data/getTransactions/0/response/events") + .and_then(Value::as_array) + .ok_or_else(|| { + RpcError::fatal_from_message("no response events in graphql response") + })?; + + let (source_channel_id, destination_channel_id, packet_data, timeout_timestamp) = events + .iter() + .filter(|event| { + event.pointer("/type").and_then(Value::as_str) == Some("PacketSend") + && event.pointer("/pkg_path").and_then(Value::as_str) + == Some(ibc_core_realm.as_str()) + }) + .map(|raw_event| { + let event = serde_json::from_value(raw_event.clone()) + .map_err(RpcError::fatal("invalid attrs in graphql response"))?; + + let IbcEvent::PacketSend { + packet_hash: found_packet_hash, + packet_data, + source_channel_id: found_channel_id, + destination_channel_id, + timeout_timestamp, + } = IbcEvent::from_gno_event(event)?.ok_or_else(|| { + RpcError::fatal_from_message( + "invalid graphql response: invalid event returned from query", + ) + })? + else { + return Err(RpcError::fatal_from_message( + "invalid graphql response: unexpected event returned from query", + )); + }; + + Ok(( + found_packet_hash, + found_channel_id, + packet_data, + destination_channel_id, + timeout_timestamp, + )) + }) + .collect::>>()? + .into_iter() + // the tx can bundle multiple gno msgs together, so the where clause only + // guarantees *a* matching event is somewhere in the tx, not that every + // packet_send event in it belongs to this packet + .find_map( + |( + found_packet_hash, + found_channel_id, + packet_data, + destination_channel_id, + timeout_timestamp, + )| { + (found_packet_hash == packet_hash && found_channel_id == channel_id).then_some( + ( + found_channel_id, + destination_channel_id, + packet_data, + timeout_timestamp, + ), + ) + }, + ) + .ok_or_else(|| { + RpcError::fatal_from_message(format!( + "no packet_send event matching channel_id {channel_id} / packet_hash \ + {packet_hash} found in graphql response", + )) + })?; + + Ok(PacketByHashResponse { + packet: Packet { + source_channel_id, + destination_channel_id, + data: packet_data, + timeout_height: MustBeZero, + timeout_timestamp, + }, + tx_hash: Some(hash.into_encoding()), + provable_height: height + 1, + }) + } - println!("{query}"); + #[instrument(skip_all, fields(chain_id = %self.chain_id, %channel_id, %batch_hash))] + pub async fn query_packets_by_batch_hash( + &self, + channel_id: ChannelId, + batch_hash: H256, + ) -> RpcResult { + let ibc_core_realm = &self.ibc_core_realm; + let query = format!( + r#"query getEvents {{ + getTransactions( + where: {{ + success: {{ eq: true }}, + response: {{ + events: {{ + _and: [ + {{ + GnoEvent: {{ + type: {{ eq: "BatchSend" }} + pkg_path: {{ eq: "{ibc_core_realm}" }}, + attrs: {{ + key: {{ eq: "channel_id" }} + value: {{ eq: "{channel_id}" }} + }} + }} + }} + {{ + GnoEvent: {{ + type: {{ eq: "BatchSend" }} + pkg_path: {{ eq: "{ibc_core_realm}" }}, + attrs: {{ + key: {{ eq:"batch_hash" }} + value: {{ eq:"{batch_hash}" }} + }} + }} + }} + ] + }} + }} + }} + ) {{ + block_height + hash + response {{ + events {{ + ... on GnoEvent {{ + type + pkg_path + attrs {{ + key + value + }} + }} + }} + }} + }} +}}"# + ); let res = self .tx_indexer_client @@ -159,6 +450,26 @@ impl Module { "invalid json returned from graphql query", ))?; + // indexer returns null (not []) for getTransactions when there's no match + let empty = Vec::new(); + let transactions = match res.pointer("/data/getTransactions") { + Some(Value::Array(transactions)) => transactions, + Some(Value::Null) => &empty, + _ => { + return Err(RpcError::retryable_from_message( + "no getTransactions in graphql response", + )); + } + }; + + if transactions.len() != 1 { + return Err(RpcError::retryable_from_message(format!( + "expected exactly one transaction matching channel_id {channel_id} and \ + batch_hash {batch_hash}, found {}", + transactions.len() + ))); + } + let hash = res .pointer("/data/getTransactions/0/hash") .ok_or_else(|| RpcError::fatal_from_message("no tx hash in graphql response"))? @@ -181,42 +492,76 @@ impl Module { RpcError::fatal_from_message("block height in graphql response is not a u64") })?; - let raw_event = res - .pointer("/data/getTransactions/0/response/events/0") + let events = res + .pointer("/data/getTransactions/0/response/events") + .and_then(Value::as_array) .ok_or_else(|| { RpcError::fatal_from_message("no response events in graphql response") })?; - let event = serde_json::from_value(raw_event.clone()) - .map_err(RpcError::fatal("invalid attrs in graphql response"))?; - - let IbcEvent::PacketSend { - packet_data, - source_channel_id, - destination_channel_id, - timeout_timestamp, - .. - } = IbcEvent::from_gno_event(event)?.ok_or_else(|| { - RpcError::fatal_from_message( - "invalid graphql response: invalid event returned from query", - ) - })? - else { - return Err(RpcError::fatal_from_message( - "invalid graphql response: unexpected event returned from query", - )); - }; + let packet_hashes = events + .iter() + .filter(|event| { + event.pointer("/type").and_then(Value::as_str) == Some("BatchSend") + && event.pointer("/pkg_path").and_then(Value::as_str) + == Some(ibc_core_realm.as_str()) + }) + .map(|raw_event| { + let event = serde_json::from_value(raw_event.clone()) + .map_err(RpcError::fatal("invalid attrs in graphql response"))?; + + let IbcEvent::BatchSend { + packet_hash, + batch_hash: found_batch_hash, + channel_id: found_channel_id, + } = IbcEvent::from_gno_event(event)?.ok_or_else(|| { + RpcError::fatal_from_message( + "invalid graphql response: invalid event returned from query", + ) + })? + else { + return Err(RpcError::fatal_from_message( + "invalid graphql response: unexpected event returned from query", + )); + }; + + Ok((found_batch_hash, found_channel_id, packet_hash)) + }) + .collect::>>()? + .into_iter() + // the tx can bundle multiple gno msgs together (see + // `do_send_transaction`/`broadcast_tx_commit` in the gno transaction + // plugin), so the where clause only guarantees *a* matching event is + // somewhere in the tx, not that every BatchSend event in it belongs to + // this batch. events for other batches bundled into the same tx are + // filtered out here rather than treated as an error. + .filter_map(|(found_batch_hash, found_channel_id, packet_hash)| { + (found_batch_hash == batch_hash && found_channel_id == channel_id) + .then_some(packet_hash) + }) + .collect::>(); + + if packet_hashes.len() < 2 { + return Err(RpcError::fatal_from_message(format!( + "expected at least 2 packets in batch {batch_hash}, found {}", + packet_hashes.len() + ))); + } - Ok(PacketByHashResponse { - packet: Packet { - source_channel_id, - destination_channel_id, - data: packet_data, - timeout_height: MustBeZero, - timeout_timestamp, - }, - tx_hash: Some(hash.into_encoding()), - provable_height: height, + let packets = packet_hashes + .into_iter() + .map(|packet_hash| { + self.query_packet_by_hash(channel_id, packet_hash) + .map_ok(|res| res.packet) + }) + .collect::>() + .try_collect::>() + .await?; + + Ok(PacketsByBatchHashResponse { + packets, + tx_hash: hash.into_encoding(), + provable_height: height + 1, }) } @@ -226,129 +571,172 @@ impl Module { channel_id: ChannelId, packet_hash: H256, ) -> RpcResult { - // let query = format!( - // "wasm-write_ack.packet_hash='{packet_hash}' AND wasm-write_ack.channel_id={channel_id}" - // ); - - // let mut tx_result = self - // .gno_client - // .tx_search( - // &query, - // false, - // const { NonZeroU32::new(1).unwrap() }, - // const { NonZeroU8::new(1).unwrap() }, - // Order::Asc, - // ) - // .await - // .map_err(RpcError::retryable( - // "error querying packet ack by packet hash in tx", - // ))?; - - // if tx_result.total_count != 1 { - // return Err(RpcError::retryable_from_message(format!( - // "error querying for acknowledgement for packet \ - // {packet_hash}, expected 1 tx but found {}", - // tx_result.total_count, - // )) - // .with_data(json!({ "tx_result": tx_result }))); - // } + let ibc_core_realm = &self.ibc_core_realm; + let query = format!( + r#"query getEvents {{ + getTransactions( + where: {{ + success: {{ eq: true }}, + response: {{ + events: {{ + _and: [ + {{ + GnoEvent: {{ + type: {{ eq: "WriteAck" }} + pkg_path: {{ eq: "{ibc_core_realm}" }}, + attrs: {{ + key: {{ eq: "destination_channel_id" }} + value: {{ eq: "{channel_id}" }} + }} + }} + }} + {{ + GnoEvent: {{ + type: {{ eq: "WriteAck" }} + pkg_path: {{ eq: "{ibc_core_realm}" }}, + attrs: {{ + key: {{ eq:"packet_hash" }} + value: {{ eq:"{packet_hash}" }} + }} + }} + }} + ] + }} + }} + }} + ) {{ + block_height + hash + response {{ + events {{ + ... on GnoEvent {{ + type + pkg_path + attrs {{ + key + value + }} + }} + }} + }} + }} +}}"# + ); - // let tx = tx_result.txs.pop().expect("len is > 1; qed;"); - - // let extract_event = |event: &gno_rpc::types::abci::event::Event| { - // CosmosSdkEvent::::new(event.clone()) - // .ok() - // .and_then(|e| match e.event { - // IbcEvent::WasmWriteAck { - // channel_id: found_channel_id, - // packet_hash: found_packet_hash, - // acknowledgement, - // } => (channel_id == found_channel_id - // && packet_hash == found_packet_hash - // && e.contract_address - // .is_some_and(|a| a == self.ibc_host_contract_address)) - // .then_some(acknowledgement), - // _ => None, - // }) - // }; - - // let maybe_ack = tx.tx_result.events.iter().find_map(extract_event); - - // match maybe_ack { - // Some(ack) => { - // info!(%packet_hash, %channel_id, "queried packet"); - - // Ok(PacketAckByHashResponse { - // ack: ack.into_encoding(), - // tx_hash: Some(tx.hash.into_encoding()), - // provable_height: tx.height.expect("tx must have a height; qed;").get() + 1, - // }) - // } - // None => { - // info!("packet not found in a tx, checking for block events"); - - // let mut block_search_response = self - // .gno_client - // .block_search( - // query, - // const { NonZeroU32::new(1).unwrap() }, - // const { NonZeroU8::new(1).unwrap() }, - // Order::Asc, - // ) - // .await - // .map_err(RpcError::retryable( - // "error querying packet ack by packet hash in block", - // ))?; - - // if block_search_response.total_count != 1 { - // return Err(RpcError::retryable_from_message(format!( - // "error querying for packet {packet_hash}, \ - // expected 1 block but found {}", - // block_search_response.total_count, - // )) - // .with_data(json!({ "block_response": block_search_response }))); - // } - - // let block_response = block_search_response - // .blocks - // .pop() - // .expect("len is > 1; qed;"); - - // let block_results = self - // .gno_client - // .block_results(Some( - // (block_response.block.header.height.inner() as u64) - // .try_into() - // .expect("block number is valid"), - // )) - // .await - // .map_err(RpcError::retryable(format!( - // "error querying block {}", - // block_response.block.header.height - // )))?; - - // let ack = block_results - // .finalize_block_events - // .iter() - // .flatten() - // .find_map(extract_event) - // .ok_or_else(|| { - // RpcError::retryable_from_message(format!( - // "error querying for packet {packet_hash}, channel \ - // {channel_id}; the wasm-packet_ack event was not found", - // )) - // .with_data(json!({ "block_results": block_results })) - // })?; - - // Ok(PacketAckByHashResponse { - // ack: ack.into_encoding(), - // tx_hash: Some(tx.hash.into_encoding()), - // provable_height: block_response.block.header.height.inner() as u64 + 1, - // }) - // } - // } + let res = self + .tx_indexer_client + .post(&self.tx_indexer_rpc_url) + .json(&json!({ + "operationName": "getEvents", + "query": query + })) + .send() + .await + .map_err(RpcError::retryable("error sending graphql query"))? + .json::() + .await + .map_err(RpcError::retryable( + "invalid json returned from graphql query", + ))?; + + // indexer returns null (not []) for getTransactions when there's no match + let empty = Vec::new(); + let transactions = match res.pointer("/data/getTransactions") { + Some(Value::Array(transactions)) => transactions, + Some(Value::Null) => &empty, + _ => { + return Err(RpcError::retryable_from_message( + "no getTransactions in graphql response", + )); + } + }; - todo!() + if transactions.len() != 1 { + return Err(RpcError::retryable_from_message(format!( + "expected exactly one transaction matching channel_id {channel_id} and \ + packet_hash {packet_hash}, found {}", + transactions.len() + ))); + } + + let hash = res + .pointer("/data/getTransactions/0/hash") + .ok_or_else(|| RpcError::fatal_from_message("no tx hash in graphql response"))? + .as_str() + .ok_or_else(|| { + RpcError::fatal_from_message("tx hash in graphql response is not a string") + })? + .parse::>() + .map_err(RpcError::fatal("invalid tx hash in graphql response"))?; + + let height = res + .pointer("/data/getTransactions/0/block_height") + .ok_or_else(|| RpcError::fatal_from_message("no block height in graphql response"))? + .as_number() + .ok_or_else(|| { + RpcError::fatal_from_message("block height in graphql response is not a number") + })? + .as_u64() + .ok_or_else(|| { + RpcError::fatal_from_message("block height in graphql response is not a u64") + })?; + + let events = res + .pointer("/data/getTransactions/0/response/events") + .and_then(Value::as_array) + .ok_or_else(|| { + RpcError::fatal_from_message("no response events in graphql response") + })?; + + let acknowledgement = events + .iter() + .filter(|event| { + event.pointer("/type").and_then(Value::as_str) == Some("WriteAck") + && event.pointer("/pkg_path").and_then(Value::as_str) + == Some(ibc_core_realm.as_str()) + }) + .map(|raw_event| { + let event = serde_json::from_value(raw_event.clone()) + .map_err(RpcError::fatal("invalid attrs in graphql response"))?; + + let IbcEvent::WriteAck { + packet_hash: found_packet_hash, + destination_channel_id: found_channel_id, + acknowledgement, + } = IbcEvent::from_gno_event(event)?.ok_or_else(|| { + RpcError::fatal_from_message( + "invalid graphql response: invalid event returned from query", + ) + })? + else { + return Err(RpcError::fatal_from_message( + "invalid graphql response: unexpected event returned from query", + )); + }; + + Ok((found_packet_hash, found_channel_id, acknowledgement)) + }) + .collect::>>()? + .into_iter() + // the tx can bundle multiple gno msgs together, so the where clause only + // guarantees *a* matching event is somewhere in the tx, not that every + // write_ack event in it belongs to this packet + .find_map(|(found_packet_hash, found_channel_id, acknowledgement)| { + (found_packet_hash == packet_hash && found_channel_id == channel_id) + .then_some(acknowledgement) + }) + .ok_or_else(|| { + RpcError::fatal_from_message(format!( + "no write_ack event matching channel_id {channel_id} / packet_hash \ + {packet_hash} found in graphql response", + )) + })?; + + Ok(PacketAckByHashResponse { + ack: acknowledgement.into_encoding(), + tx_hash: Some(hash.into_encoding()), + provable_height: height + 1, + }) } #[instrument(skip_all, fields(?height))] @@ -681,13 +1069,13 @@ impl StateModuleServer for Module { .query_packet_by_hash(channel_id, packet_hash) .await .map(into_value), - // TODO: Also query block events here Query::PacketsByBatchHash(PacketsByBatchHash { - channel_id: _, - batch_hash: _, - }) => { - todo!() - } + channel_id, + batch_hash, + }) => self + .query_packets_by_batch_hash(channel_id, batch_hash) + .await + .map(into_value), Query::PacketAckByHash(PacketAckByHash { channel_id, packet_hash, @@ -798,11 +1186,71 @@ impl StateModuleServer for Module { } } -// REVIEW: Do I need to do unescaping here? fn parse_gno_string_object(s: impl AsRef) -> RpcResult { - s.as_ref() - .strip_prefix("(\"") - .and_then(|s| s.strip_suffix("\" string)")) - .map(ToOwned::to_owned) + let s = s.as_ref(); + // Handles both old format ("value" string) and new format + // ("value" gno.land/p/.../types.T, nil error) + s.strip_prefix("(\"") + .and_then(|s| s.find("\" ").map(|end| s[..end].to_owned())) .ok_or(RpcError::fatal_from_message("invalid string object")) } + +#[cfg(test)] +mod tests { + use super::mk_windows; + + fn assert_valid_windows(windows: &[(u64, u64)], latest_height: u64) { + assert!(!windows.is_empty(), "must produce at least one window"); + + assert_eq!(windows[0].1, latest_height, "first window must end at latest_height"); + assert_eq!( + windows.last().expect("checked above; qed").0, + 0, + "last window must start at 0" + ); + + for &(lo, hi) in windows { + assert!(lo <= hi, "window must not be inverted: {lo}..={hi}"); + } + + for window in windows.windows(2) { + assert_eq!(window[0].0, window[1].1 + 1, "windows must be contiguous: {window:?}"); + } + } + + #[test] + fn mk_windows_exact_multiple_of_window_size() { + // this used to produce a spurious trailing (0, 0) window + let windows = mk_windows(1000, 500); + assert_eq!(windows, vec![(501, 1000), (0, 500)]); + assert_valid_windows(&windows, 1000); + } + + #[test] + fn mk_windows_not_a_multiple_of_window_size() { + let windows = mk_windows(1001, 500); + assert_eq!(windows, vec![(502, 1001), (2, 501), (0, 1)]); + assert_valid_windows(&windows, 1001); + } + + #[test] + fn mk_windows_latest_height_smaller_than_window() { + let windows = mk_windows(10, 500); + assert_eq!(windows, vec![(0, 10)]); + assert_valid_windows(&windows, 10); + } + + #[test] + fn mk_windows_latest_height_zero() { + let windows = mk_windows(0, 500); + assert_eq!(windows, vec![(0, 0)]); + assert_valid_windows(&windows, 0); + } + + #[test] + fn mk_windows_window_size_one() { + let windows = mk_windows(3, 1); + assert_eq!(windows, vec![(3, 3), (2, 2), (0, 1)]); + assert_valid_windows(&windows, 3); + } +} diff --git a/voyager/plugins/event-source/gno/src/ibc_events.rs b/voyager/plugins/event-source/gno/src/ibc_events.rs index c6167d8a9f0..d6e84b33b7b 100644 --- a/voyager/plugins/event-source/gno/src/ibc_events.rs +++ b/voyager/plugins/event-source/gno/src/ibc_events.rs @@ -58,46 +58,31 @@ pub enum IbcEvent { packet_hash: H256, packet_data: Bytes, source_channel_id: ChannelId, - source_channel_version: String, - source_connection_id: ConnectionId, - source_connection_client_id: ClientId, destination_channel_id: ChannelId, - destination_connection_id: ConnectionId, - destination_connection_client_id: ClientId, timeout_timestamp: Timestamp, }, - // TODO - BatchSend {}, + BatchSend { + packet_hash: H256, + batch_hash: H256, + channel_id: ChannelId, + }, PacketRecv { packet_hash: H256, - packet_data: Bytes, - source_channel_id: ChannelId, - source_connection_id: ConnectionId, - source_connection_client_id: ClientId, destination_channel_id: ChannelId, - destination_channel_version: String, - destination_connection_id: ConnectionId, - destination_connection_client_id: ClientId, - timeout_timestamp: Timestamp, maker_msg: Bytes, }, - // TODO - PacketAck {}, + PacketAck { + packet_hash: H256, + source_channel_id: ChannelId, + acknowledgement: Bytes, + }, WriteAck { packet_hash: H256, - packet_data: Bytes, - source_channel_id: ChannelId, - source_connection_id: ConnectionId, - source_connection_client_id: ClientId, destination_channel_id: ChannelId, - destination_channel_version: String, - destination_connection_id: ConnectionId, - destination_connection_client_id: ClientId, - timeout_timestamp: Timestamp, acknowledgement: Bytes, }, } @@ -109,7 +94,6 @@ pub struct ChannelEvent { pub channel_id: ChannelId, pub counterparty_port_id: Bytes, pub counterparty_channel_id: Option, - pub connection_id: ConnectionId, pub connection_client_id: ClientId, pub connection_counterparty_client_id: ClientId, pub connection_counterparty_connection_id: ConnectionId, @@ -141,6 +125,35 @@ impl IbcEvent { .map_err(RpcError::fatal(format!("error parsing value for key {ty}"))) } + fn chunked_attr(attrs: &[gno_rpc::types::EventAttribute], ty: &str) -> RpcResult { + // If no _size field is present, the value was emitted as a single plain attribute. + let Some(hex_str_len) = attr::(attrs, &format!("{ty}_size")).ok() else { + return attr(attrs, ty); + }; + + // Collect raw chunk strings. gno splits the full "0x" string into + // 4096-char slices, so only chunk[0] carries the "0x" prefix; subsequent + // chunks are bare hex continuations. + let combined: String = (0..) + .map_while(|i| { + attrs + .iter() + .find_map(|a| (a.key == format!("{ty}[{i}]")).then(|| a.value.clone())) + }) + .collect(); + + if combined.len() != hex_str_len { + return Err(RpcError::fatal_from_message(format!( + "key {ty} incomplete: got {} hex chars, expected {hex_str_len}", + combined.len(), + ))); + } + + combined + .parse::() + .map_err(RpcError::fatal(format!("error parsing hex for key {ty}"))) + } + let attrs = gno_event .attrs .ok_or_else(|| RpcError::fatal_from_message("no attributes on event"))?; @@ -152,7 +165,6 @@ impl IbcEvent { counterparty_port_id: attr(&attrs, "counterparty_port_id")?, // TODO: Fix this once this is no longer emitted on init counterparty_channel_id: attr::(&attrs, "counterparty_channel_id").ok(), - connection_id: attr(&attrs, "connection_id")?, connection_client_id: attr(&attrs, "connection_client_id")?, connection_counterparty_client_id: attr( &attrs, @@ -204,44 +216,30 @@ impl IbcEvent { "ChannelOpenConfirm" => IbcEvent::ChannelOpenConfirm(parse_channel_event(attrs)?), "PacketRecv" => IbcEvent::PacketRecv { packet_hash: attr(&attrs, "packet_hash")?, - packet_data: attr(&attrs, "packet_data")?, - source_channel_id: attr(&attrs, "source_channel_id")?, - source_connection_id: attr(&attrs, "source_connection_id")?, - source_connection_client_id: attr(&attrs, "source_connection_client_id")?, destination_channel_id: attr(&attrs, "destination_channel_id")?, - destination_channel_version: attr(&attrs, "destination_channel_version")?, - destination_connection_id: attr(&attrs, "destination_connection_id")?, - destination_connection_client_id: attr(&attrs, "destination_connection_client_id")?, - timeout_timestamp: attr(&attrs, "timeout_timestamp")?, - maker_msg: attr(&attrs, "maker_msg")?, + maker_msg: chunked_attr(&attrs, "maker_msg")?, }, "PacketSend" => IbcEvent::PacketSend { packet_hash: attr(&attrs, "packet_hash")?, - packet_data: attr(&attrs, "packet_data")?, + packet_data: chunked_attr(&attrs, "packet_data")?, source_channel_id: attr(&attrs, "source_channel_id")?, - source_channel_version: attr(&attrs, "source_channel_version")?, - source_connection_id: attr(&attrs, "source_connection_id")?, - source_connection_client_id: attr(&attrs, "source_connection_client_id")?, destination_channel_id: attr(&attrs, "destination_channel_id")?, - destination_connection_id: attr(&attrs, "destination_connection_id")?, - destination_connection_client_id: attr(&attrs, "destination_connection_client_id")?, timeout_timestamp: attr(&attrs, "timeout_timestamp")?, }, - // TODO - "BatchSend" => IbcEvent::BatchSend {}, - "PacketAck" => IbcEvent::PacketAck {}, - "WriteAck" => IbcEvent::WriteAck { + "BatchSend" => IbcEvent::BatchSend { + packet_hash: attr(&attrs, "packet_hash")?, + batch_hash: attr(&attrs, "batch_hash")?, + channel_id: attr(&attrs, "channel_id")?, + }, + "PacketAck" => IbcEvent::PacketAck { packet_hash: attr(&attrs, "packet_hash")?, - packet_data: attr(&attrs, "packet_data")?, source_channel_id: attr(&attrs, "source_channel_id")?, - source_connection_id: attr(&attrs, "source_connection_id")?, - source_connection_client_id: attr(&attrs, "source_connection_client_id")?, + acknowledgement: chunked_attr(&attrs, "acknowledgement")?, + }, + "WriteAck" => IbcEvent::WriteAck { + packet_hash: attr(&attrs, "packet_hash")?, destination_channel_id: attr(&attrs, "destination_channel_id")?, - destination_channel_version: attr(&attrs, "destination_channel_version")?, - destination_connection_id: attr(&attrs, "destination_connection_id")?, - destination_connection_client_id: attr(&attrs, "destination_connection_client_id")?, - timeout_timestamp: attr(&attrs, "timeout_timestamp")?, - acknowledgement: attr(&attrs, "acknowledgement")?, + acknowledgement: chunked_attr(&attrs, "acknowledgement")?, }, event => { warn!("unknown event: {event}"); diff --git a/voyager/plugins/event-source/gno/src/lib.rs b/voyager/plugins/event-source/gno/src/lib.rs index 436584b67f8..926501de2fe 100644 --- a/voyager/plugins/event-source/gno/src/lib.rs +++ b/voyager/plugins/event-source/gno/src/lib.rs @@ -8,10 +8,10 @@ use std::{ use ibc_union_spec::{ Connection, ConnectionState, IbcUnion, MustBeZero, Packet, event::{ - ChannelMetadata, ChannelOpenAck, ChannelOpenConfirm, ChannelOpenInit, ChannelOpenTry, - ConnectionMetadata, ConnectionOpenAck, ConnectionOpenConfirm, ConnectionOpenInit, - ConnectionOpenTry, CounterpartyChannelMetadata, CreateClient, PacketMetadata, PacketRecv, - PacketSend, UpdateClient, WriteAck, + BatchSend, ChannelMetadata, ChannelOpenAck, ChannelOpenConfirm, ChannelOpenInit, + ChannelOpenTry, ConnectionMetadata, ConnectionOpenAck, ConnectionOpenConfirm, + ConnectionOpenInit, ConnectionOpenTry, CounterpartyChannelMetadata, CreateClient, + PacketAck, PacketMetadata, PacketRecv, PacketSend, UpdateClient, WriteAck, }, path::BatchPacketsPath, query::PacketByHash, @@ -19,7 +19,7 @@ use ibc_union_spec::{ use jsonrpsee::{Extensions, core::async_trait}; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; -use tracing::{debug, error, info, instrument, trace, warn}; +use tracing::{debug, info, instrument, trace, warn}; use unionlabs::{ibc::core::client::height::Height, never::Never, primitives::H256}; use voyager_sdk::{ ExtensionsExt, VoyagerClient, @@ -350,6 +350,10 @@ impl Module { // list of MakeChainEvent ops that will be queued in a conc let mut make_chain_event_ops: Vec> = vec![]; + // gno emits one BatchSend event per packet in the batch, all sharing the same + // (channel_id, batch_hash); only the first one seen should produce a ChainEvent. + let mut seen_batches = BTreeSet::new(); + let mut handle_event = |event: gno_rpc::types::Event, tx_hash| -> RpcResult<()> { trace!(?event, "observed event"); @@ -368,23 +372,18 @@ impl Module { }; let make_chain_event = || { - // if event.is_trivial() && !self.index_trivial_events { - // debug!("not indexing trivial event"); - // None - // } else { - // let event = match event.event { - // IbcEvent::BatchSend { - // } => { - // debug!(%packet_hash, %batch_hash, %channel_id, "found batch send event"); - // if seen_batches.insert((channel_id, batch_hash)) { - // info!(%batch_hash, %channel_id, "found batch send event"); - // event.clone() - // } else { - // return None; - // } - // } - // _ => event.clone(), - // }; + if let IbcEvent::BatchSend { + channel_id, + batch_hash, + packet_hash, + } = &event + { + debug!(%packet_hash, %batch_hash, %channel_id, "found batch send event"); + if !seen_batches.insert((*channel_id, *batch_hash)) { + return None; + } + } + Some(call(PluginMessage::new( self.plugin_name(), ModuleCall::from(MakeChainEvent { @@ -393,7 +392,6 @@ impl Module { event, }), ))) - // } }; if let Some(e) = make_chain_event() { @@ -650,7 +648,6 @@ impl Module { channel_id, counterparty_port_id, counterparty_channel_id: _, // THIS WILL BE ZERO - connection_id: _, connection_client_id, connection_counterparty_client_id, connection_counterparty_connection_id, @@ -698,7 +695,6 @@ impl Module { channel_id, counterparty_port_id, counterparty_channel_id, - connection_id: _, connection_client_id, connection_counterparty_client_id, connection_counterparty_connection_id, @@ -747,7 +743,6 @@ impl Module { channel_id, counterparty_port_id, counterparty_channel_id, - connection_id: _, connection_client_id, connection_counterparty_client_id, connection_counterparty_connection_id, @@ -797,7 +792,6 @@ impl Module { channel_id, counterparty_port_id, counterparty_channel_id, - connection_id: _, connection_client_id, connection_counterparty_client_id, connection_counterparty_connection_id, @@ -845,12 +839,7 @@ impl Module { packet_hash: _, packet_data, source_channel_id, - source_channel_version: _, - source_connection_id: _, - source_connection_client_id: _, destination_channel_id, - destination_connection_id: _, - destination_connection_client_id: _, timeout_timestamp, } => { let packet = Packet { @@ -943,170 +932,164 @@ impl Module { event, ))) } - // IbcEvent::BatchSend { - // channel_id, - // packet_hash: _, - // batch_hash, - // } => { - // let source_channel = voyager_client - // .query_ibc_state( - // self.chain_id.clone(), - // QueryHeight::Specific(height), - // ibc_union_spec::path::ChannelPath { channel_id }, - // ) - // .await?; - - // let source_connection = voyager_client - // .query_ibc_state( - // self.chain_id.clone(), - // QueryHeight::Specific(height), - // ibc_union_spec::path::ConnectionPath { - // connection_id: source_channel.connection_id, - // }, - // ) - // .await?; - - // let client_info = voyager_client - // .client_info::(self.chain_id.clone(), source_connection.client_id) - // .await?; - - // let client_state_meta = voyager_client - // .client_state_meta::( - // self.chain_id.clone(), - // height.into(), - // source_connection.client_id, - // ) - // .await?; - - // let event = BatchSend { - // batch_hash, - // source_channel: ChannelMetadata { - // channel_id, - // version: source_channel.version.clone(), - // connection: ConnectionMetadata { - // client_id: source_connection.client_id, - // connection_id: source_channel.connection_id, - // }, - // }, - // destination_channel: CounterpartyChannelMetadata { - // channel_id: source_channel - // .counterparty_channel_id - // .expect("channel is open"), - // connection: ConnectionMetadata { - // client_id: source_connection.counterparty_client_id, - // connection_id: source_connection.counterparty_connection_id.unwrap(), - // }, - // }, - // } - // .into(); - - // ibc_union_spec::log_event(&event, &self.chain_id); - - // Ok(data(ChainEvent::new::( - // self.chain_id.clone(), - // client_info, - // client_state_meta.counterparty_chain_id, - // tx_hash, - // provable_height, - // event, - // ))) - // } - // IbcEvent::PacketAck { - // acknowledgement, - // channel_id, - // packet_hash, - // } => { - // let packet = voyager_client - // .query( - // self.chain_id.clone(), - // PacketByHash { - // channel_id, - // packet_hash, - // }, - // ) - // .await? - // .packet; - - // let source_channel = voyager_client - // .query_ibc_state( - // self.chain_id.clone(), - // QueryHeight::Specific(height), - // ibc_union_spec::path::ChannelPath { - // channel_id: packet.source_channel_id, - // }, - // ) - // .await?; - - // let source_connection = voyager_client - // .query_ibc_state( - // self.chain_id.clone(), - // QueryHeight::Specific(height), - // ibc_union_spec::path::ConnectionPath { - // connection_id: source_channel.connection_id, - // }, - // ) - // .await?; - - // let client_info = voyager_client - // .client_info::(self.chain_id.clone(), source_connection.client_id) - // .await?; - - // let client_state_meta = voyager_client - // .client_state_meta::( - // self.chain_id.clone(), - // height.into(), - // source_connection.client_id, - // ) - // .await?; - - // let event = PacketAck { - // packet_data: packet.data, - // packet: PacketMetadata { - // source_channel: ChannelMetadata { - // channel_id: packet.source_channel_id, - // version: source_channel.version.clone(), - // connection: ConnectionMetadata { - // client_id: source_connection.client_id, - // connection_id: source_channel.connection_id, - // }, - // }, - // destination_channel: CounterpartyChannelMetadata { - // channel_id: packet.destination_channel_id, - // connection: ConnectionMetadata { - // client_id: source_connection.counterparty_client_id, - // connection_id: source_connection - // .counterparty_connection_id - // .unwrap(), - // }, - // }, - // timeout_timestamp: packet.timeout_timestamp, - // }, - // acknowledgement: acknowledgement.into_encoding(), - // } - // .into(); - - // ibc_union_spec::log_event(&event, &self.chain_id); - - // Ok(data(ChainEvent::new::( - // self.chain_id.clone(), - // client_info, - // client_state_meta.counterparty_chain_id, - // tx_hash, - // provable_height, - // event, - // ))) - // } + IbcEvent::BatchSend { + packet_hash: _, + batch_hash, + channel_id, + } => { + let source_channel = voyager_client + .query_ibc_state( + self.chain_id.clone(), + QueryHeight::Specific(height), + ibc_union_spec::path::ChannelPath { channel_id }, + ) + .await?; + + let source_connection = voyager_client + .query_ibc_state( + self.chain_id.clone(), + QueryHeight::Specific(height), + ibc_union_spec::path::ConnectionPath { + connection_id: source_channel.connection_id, + }, + ) + .await?; + + let client_info = voyager_client + .client_info::(self.chain_id.clone(), source_connection.client_id) + .await?; + + let client_state_meta = voyager_client + .client_state_meta::( + self.chain_id.clone(), + height.into(), + source_connection.client_id, + ) + .await?; + + let event = BatchSend { + batch_hash, + source_channel: ChannelMetadata { + channel_id, + version: source_channel.version.clone(), + connection: ConnectionMetadata { + client_id: source_connection.client_id, + connection_id: source_channel.connection_id, + }, + }, + destination_channel: CounterpartyChannelMetadata { + channel_id: source_channel + .counterparty_channel_id + .expect("channel is open"), + connection: ConnectionMetadata { + client_id: source_connection.counterparty_client_id, + connection_id: source_connection + .counterparty_connection_id + .expect("must be set"), + }, + }, + } + .into(); + + ibc_union_spec::log_event(&event, &self.chain_id); + + Ok(data(ChainEvent::new::( + self.chain_id.clone(), + client_info, + client_state_meta.counterparty_chain_id, + tx_hash, + provable_height, + event, + ))) + } + IbcEvent::PacketAck { + packet_hash, + source_channel_id, + acknowledgement, + } => { + let packet = voyager_client + .query( + self.chain_id.clone(), + PacketByHash { + channel_id: source_channel_id, + packet_hash, + }, + ) + .await? + .packet; + + let source_channel = voyager_client + .query_ibc_state( + self.chain_id.clone(), + QueryHeight::Specific(height), + ibc_union_spec::path::ChannelPath { + channel_id: packet.source_channel_id, + }, + ) + .await?; + + let source_connection = voyager_client + .query_ibc_state( + self.chain_id.clone(), + QueryHeight::Specific(height), + ibc_union_spec::path::ConnectionPath { + connection_id: source_channel.connection_id, + }, + ) + .await?; + + let client_info = voyager_client + .client_info::(self.chain_id.clone(), source_connection.client_id) + .await?; + + let client_state_meta = voyager_client + .client_state_meta::( + self.chain_id.clone(), + height.into(), + source_connection.client_id, + ) + .await?; + + let event = PacketAck { + packet_data: packet.data, + packet: PacketMetadata { + source_channel: ChannelMetadata { + channel_id: packet.source_channel_id, + version: source_channel.version.clone(), + connection: ConnectionMetadata { + client_id: source_connection.client_id, + connection_id: source_channel.connection_id, + }, + }, + destination_channel: CounterpartyChannelMetadata { + channel_id: packet.destination_channel_id, + connection: ConnectionMetadata { + client_id: source_connection.counterparty_client_id, + connection_id: source_connection + .counterparty_connection_id + .expect("must be set"), + }, + }, + timeout_timestamp: packet.timeout_timestamp, + }, + acknowledgement: acknowledgement.into_encoding(), + } + .into(); + + ibc_union_spec::log_event(&event, &self.chain_id); + + Ok(data(ChainEvent::new::( + self.chain_id.clone(), + client_info, + client_state_meta.counterparty_chain_id, + tx_hash, + provable_height, + event, + ))) + } IbcEvent::PacketRecv { packet_hash, - packet_data: _, - source_channel_id: _, - source_connection_id: _, - source_connection_client_id: _, destination_channel_id, - destination_channel_version: _, - destination_connection_id: _, - destination_connection_client_id: _, - timeout_timestamp: _, maker_msg, } => { let destination_channel = voyager_client @@ -1196,15 +1179,7 @@ impl Module { } IbcEvent::WriteAck { packet_hash, - packet_data: _, - source_channel_id: _, - source_connection_id: _, - source_connection_client_id: _, destination_channel_id, - destination_channel_version: _, - destination_connection_id: _, - destination_connection_client_id: _, - timeout_timestamp: _, acknowledgement, } => { let destination_channel = voyager_client @@ -1292,10 +1267,6 @@ impl Module { event, ))) } - _ => { - error!("unimplemented: {event:?}"); - Ok(noop()) - } } } } diff --git a/voyager/plugins/packet-timeout/Cargo.toml b/voyager/plugins/packet-timeout/Cargo.toml index 25c6495a737..d1770a3555f 100644 --- a/voyager/plugins/packet-timeout/Cargo.toml +++ b/voyager/plugins/packet-timeout/Cargo.toml @@ -12,14 +12,15 @@ repository = { workspace = true } workspace = true [dependencies] -embed-commit = { workspace = true } -enumorph = { workspace = true } -ibc-union-spec = { workspace = true, features = ["serde", "ethabi"] } -jsonrpsee = { workspace = true, features = ["macros", "server", "tracing"] } -macros = { workspace = true } -serde = { workspace = true, features = ["derive"] } -serde_json = { workspace = true } -tokio = { workspace = true } -tracing = { workspace = true } -unionlabs = { workspace = true } -voyager-sdk = { workspace = true } +embed-commit = { workspace = true } +enumorph = { workspace = true } +ibc-union-spec = { workspace = true, features = ["serde", "ethabi"] } +jsonrpsee = { workspace = true, features = ["macros", "server", "tracing"] } +macros = { workspace = true } +proof-lens-light-client-types = { workspace = true, features = ["serde"] } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +tokio = { workspace = true } +tracing = { workspace = true } +unionlabs = { workspace = true } +voyager-sdk = { workspace = true } diff --git a/voyager/plugins/packet-timeout/src/call.rs b/voyager/plugins/packet-timeout/src/call.rs index 385eac0df56..2d4122c429b 100644 --- a/voyager/plugins/packet-timeout/src/call.rs +++ b/voyager/plugins/packet-timeout/src/call.rs @@ -1,6 +1,7 @@ use enumorph::Enumorph; use ibc_union_spec::{ClientId, Timestamp, event::PacketSend}; use macros::model; +use unionlabs::ibc::core::client::height::Height; use voyager_sdk::primitives::ChainId; #[model] @@ -10,6 +11,7 @@ pub enum ModuleCall { MakeMsgTimeout(MakeMsgTimeout), UpdateClientToHeightTimestamp(UpdateClientToHeightTimestamp), MakeMsgTimeoutFromTrustedHeight(MakeMsgTimeoutFromTrustedHeight), + CommitProofLensNonMembershipProof(CommitProofLensNonMembershipProof), } #[model] @@ -40,3 +42,18 @@ pub struct MakeMsgTimeoutFromTrustedHeight { pub chain_id: ChainId, pub counterparty_chain_id: ChainId, } + +/// Commit a non-membership proof (proving that `event.packet` has not been received on +/// `counterparty_chain_id`) onto the L1 that the proof lens client on `chain_id` is anchored to, +/// so that the lens client can subsequently verify it by proxy. +/// +/// `counterparty_height` is pinned (rather than re-derived as "latest" on retry) - the +/// commitment is keyed by this exact height, so the client must be refreshed *at this height* +/// (not advanced to a new one) before the timeout message can be built from it. +#[model] +pub struct CommitProofLensNonMembershipProof { + pub event: PacketSend, + pub chain_id: ChainId, + pub counterparty_chain_id: ChainId, + pub counterparty_height: Height, +} diff --git a/voyager/plugins/packet-timeout/src/main.rs b/voyager/plugins/packet-timeout/src/main.rs index 7e84907e197..ab6edac6eb5 100644 --- a/voyager/plugins/packet-timeout/src/main.rs +++ b/voyager/plugins/packet-timeout/src/main.rs @@ -1,34 +1,40 @@ use std::collections::VecDeque; use ibc_union_spec::{ - IbcUnion, - datagram::{Datagram, MsgPacketTimeout}, - event::FullEvent, - path::{BatchPacketsPath, BatchReceiptsPath, COMMITMENT_MAGIC_ACK}, + ClientId, IbcUnion, + datagram::{Datagram, MsgCommitNonMembershipProof, MsgPacketTimeout}, + event::{FullEvent, PacketSend}, + path::{ + BatchPacketsPath, BatchReceiptsPath, COMMITMENT_MAGIC_ACK, ClientStatePath, + ConsensusStatePath, NonMembershipProofPath, + }, }; use jsonrpsee::{Extensions, core::async_trait}; +use proof_lens_light_client_types::{ + ClientState as ProofLensClientState, ConsensusState as ProofLensConsensusState, +}; use serde::{Deserialize, Serialize}; use serde_json::json; use tracing::{debug, info, instrument}; -use unionlabs::{self, never::Never}; +use unionlabs::{self, ibc::core::client::height::Height, never::Never}; use voyager_sdk::{ DefaultCmd, ExtensionsExt, VoyagerClient, anyhow, message::{ PluginMessage, VoyagerMessage, - call::{FetchUpdateHeaders, SubmitTx}, + call::{FetchUpdateHeaders, SubmitTx, WaitForHeightRelative}, callback::AggregateSubmitTxFromOrderedHeaders, data::{Data, IbcDatagram}, }, plugin::Plugin, - primitives::{IbcSpec, QueryHeight}, + primitives::{ChainId, ClientType, IbcSpec, QueryHeight}, rpc::{PluginServer, RpcError, RpcErrorExt, RpcResult, types::PluginInfo}, types::{ProofType, RawClientId}, vm::{Op, call, defer, defer_relative, noop, pass::PassResult, promise, seq}, }; use crate::call::{ - MakeMsgTimeout, MakeMsgTimeoutFromTrustedHeight, ModuleCall, UpdateClientToHeightTimestamp, - WaitForTimeoutOrReceipt, + CommitProofLensNonMembershipProof, MakeMsgTimeout, MakeMsgTimeoutFromTrustedHeight, ModuleCall, + UpdateClientToHeightTimestamp, WaitForTimeoutOrReceipt, }; pub mod call; @@ -43,6 +49,15 @@ pub struct Module {} #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Config {} +/// Info needed to verify/commit proofs by proxy through a proof lens client's L1, resolved from +/// the lens client's `ClientState`. See `proof_lens_light_client_types::ClientState` for the +/// A->B->C (self->L1->L2) terminology. +struct ProofLensResolution { + l1_chain_id: ChainId, + l1_client_id: ClientId, + l2_client_id: ClientId, +} + impl Plugin for Module { type Call = ModuleCall; type Callback = Never; @@ -333,7 +348,7 @@ impl PluginServer for Module { let proof_unreceived = voyager_client .query_ibc_proof( - counterparty_chain_id, + counterparty_chain_id.clone(), QueryHeight::Specific(client_meta.counterparty_height), BatchReceiptsPath::from_packets(&[event.packet().clone()]), ) @@ -342,31 +357,60 @@ impl PluginServer for Module { match proof_unreceived.proof_type { ProofType::NonMembership => { - let client_info = voyager_client - .client_info::( - chain_id.clone(), + match self + .resolve_proof_lens( + voyager_client, + &chain_id, event.packet.source_channel.connection.client_id, ) - .await?; - - let encoded_proof_commitment = voyager_client - .encode_proof::( - client_info.client_type, - client_info.ibc_interface, - proof_unreceived.proof, - ) - .await?; - - Ok(call(SubmitTx { - chain_id, - datagrams: vec![IbcDatagram::new::(Datagram::from( - MsgPacketTimeout { - packet: event.packet(), - proof: encoded_proof_commitment, - proof_height: client_meta.counterparty_height.height(), - }, - ))], - })) + .await? + { + // proof lens clients can't verify a raw counterparty proof + // directly - the fact that the packet hasn't been received must + // instead be committed onto the L1 first (see + // `commit_proof_lens_non_membership_proof`), which the lens client + // then verifies a proof of by proxy. + Some(lens) => { + self.make_msg_timeout_via_proof_lens( + voyager_client, + event, + chain_id, + counterparty_chain_id, + client_meta.counterparty_height, + lens, + ) + .await + } + None => { + let client_info = voyager_client + .client_info::( + chain_id.clone(), + event.packet.source_channel.connection.client_id, + ) + .await?; + + let encoded_proof_commitment = voyager_client + .encode_proof::( + client_info.client_type, + client_info.ibc_interface, + proof_unreceived.proof, + ) + .await?; + + Ok(call(SubmitTx { + chain_id, + datagrams: vec![IbcDatagram::new::( + Datagram::from(MsgPacketTimeout { + packet: event.packet(), + proof: encoded_proof_commitment, + proof_height: client_meta + .counterparty_height + .height(), + }), + )], + })) + } + } } ProofType::Membership => { info!( @@ -406,6 +450,21 @@ impl PluginServer for Module { ])) } } + ModuleCall::CommitProofLensNonMembershipProof(CommitProofLensNonMembershipProof { + event, + chain_id, + counterparty_chain_id, + counterparty_height, + }) => { + self.commit_proof_lens_non_membership_proof( + voyager_client, + event, + chain_id, + counterparty_chain_id, + counterparty_height, + ) + .await + } } } @@ -492,4 +551,255 @@ impl Module { } } } + + /// Determine whether `client_id` on `chain_id` is a proof lens client, resolving its L1/L2 + /// client ids if so. + async fn resolve_proof_lens( + &self, + voyager_client: &VoyagerClient, + chain_id: &ChainId, + client_id: ClientId, + ) -> RpcResult> { + let client_info = voyager_client + .client_info::(chain_id.clone(), client_id) + .await?; + + if client_info.client_type.as_str() != ClientType::PROOF_LENS { + return Ok(None); + } + + let proof_lens_client_state = voyager_client + .decode_client_state::( + client_info.client_type.clone(), + client_info.ibc_interface.clone(), + voyager_client + .query_ibc_state( + chain_id.clone(), + QueryHeight::Latest, + ClientStatePath { client_id }, + ) + .await?, + ) + .await?; + + let l1_chain_id = voyager_client + .client_state_meta::( + chain_id.clone(), + QueryHeight::Latest, + proof_lens_client_state.l1_client_id, + ) + .await? + .counterparty_chain_id; + + Ok(Some(ProofLensResolution { + l1_chain_id, + l1_client_id: proof_lens_client_state.l1_client_id, + l2_client_id: proof_lens_client_state.l2_client_id, + })) + } + + /// Build the timeout message for a packet whose source client is a proof lens client. + /// + /// Proof lens clients don't have their own proof format (`client/proof-lens`'s + /// `encode_proof`/`decode_proof` always error) - instead, the non-membership of the packet + /// must first be committed onto the client's L1 (see + /// `commit_proof_lens_non_membership_proof`), which the lens client then verifies a proof of + /// by proxy. + async fn make_msg_timeout_via_proof_lens( + &self, + voyager_client: &VoyagerClient, + event: PacketSend, + chain_id: ChainId, + counterparty_chain_id: ChainId, + counterparty_height: Height, + lens: ProofLensResolution, + ) -> RpcResult> { + let client_id = event.packet.source_channel.connection.client_id; + + // `verifyNonMembership` on the lens client reads the L1 height to check out of the lens + // client's own consensus state at `counterparty_height` (not `counterparty_height` + // itself, which is the L2/counterparty height) - so the proof we submit must be queried + // against that same L1 height, not the L2 height. + let lens_client_info = voyager_client + .client_info::(chain_id.clone(), client_id) + .await?; + + let proof_lens_consensus_state = voyager_client + .decode_consensus_state::( + lens_client_info.client_type, + lens_client_info.ibc_interface, + voyager_client + .query_ibc_state( + chain_id.clone(), + QueryHeight::Latest, + ConsensusStatePath { + client_id, + height: counterparty_height.height(), + }, + ) + .await?, + ) + .await?; + + let l1_proof_height = Height::new(proof_lens_consensus_state.l1_height); + + let receipt_path_key = BatchReceiptsPath::from_packets(&[event.packet()]).key(); + + let commitment_proof = voyager_client + .query_ibc_proof( + lens.l1_chain_id.clone(), + QueryHeight::Specific(l1_proof_height), + NonMembershipProofPath { + client_id: lens.l2_client_id, + proof_height: counterparty_height.height(), + path: receipt_path_key.into(), + }, + ) + .await? + .into_result()?; + + match commitment_proof.proof_type { + ProofType::Membership => { + let l1_client_info = voyager_client + .client_info::(chain_id.clone(), lens.l1_client_id) + .await?; + + let encoded_proof_commitment = voyager_client + .encode_proof::( + l1_client_info.client_type, + l1_client_info.ibc_interface, + commitment_proof.proof, + ) + .await?; + + Ok(call(SubmitTx { + chain_id, + datagrams: vec![IbcDatagram::new::(Datagram::from( + MsgPacketTimeout { + packet: event.packet(), + proof: encoded_proof_commitment, + proof_height: counterparty_height.height(), + }, + ))], + })) + } + ProofType::NonMembership => { + info!("proof lens non-membership commitment not yet posted on L1, committing"); + + Ok(call(PluginMessage::new( + self.plugin_name(), + ModuleCall::from(CommitProofLensNonMembershipProof { + event, + chain_id, + counterparty_chain_id, + counterparty_height, + }), + ))) + } + } + } + + /// Commit a non-membership proof (proving that `event.packet` has not been received on + /// `counterparty_chain_id` as of `counterparty_height`) onto the L1 anchoring + /// `event.packet`'s source client, then refresh the source client's consensus state *at + /// `counterparty_height`* (not a newer height - the commitment is keyed by this exact + /// height) before retrying the timeout message. + async fn commit_proof_lens_non_membership_proof( + &self, + voyager_client: &VoyagerClient, + event: PacketSend, + chain_id: ChainId, + counterparty_chain_id: ChainId, + counterparty_height: Height, + ) -> RpcResult> { + let client_id = event.packet.source_channel.connection.client_id; + + let raw_proof = voyager_client + .query_ibc_proof( + counterparty_chain_id.clone(), + QueryHeight::Specific(counterparty_height), + BatchReceiptsPath::from_packets(&[event.packet()]), + ) + .await? + .into_result()?; + + if raw_proof.proof_type != ProofType::NonMembership { + info!("packet was received in the meantime, no longer need to commit a timeout proof"); + + return Ok(noop()); + } + + let lens = self + .resolve_proof_lens(voyager_client, &chain_id, client_id) + .await? + .ok_or_else(|| RpcError::fatal_from_message("client is not a proof lens client"))?; + + let l2_client_info = voyager_client + .client_info::(lens.l1_chain_id.clone(), lens.l2_client_id) + .await?; + + let encoded_l2_proof = voyager_client + .encode_proof::( + l2_client_info.client_type, + l2_client_info.ibc_interface, + raw_proof.proof, + ) + .await?; + + let commit_msg = MsgCommitNonMembershipProof { + client_id: lens.l2_client_id, + proof_height: counterparty_height.height(), + proof: encoded_l2_proof, + path: BatchReceiptsPath::from_packets(&[event.packet()]) + .key() + .into(), + }; + + let lens_client_info = voyager_client + .client_info::(chain_id.clone(), client_id) + .await?; + + Ok(seq([ + call(SubmitTx { + chain_id: lens.l1_chain_id.clone(), + datagrams: vec![IbcDatagram::new::(Datagram::from(commit_msg))], + }), + call(WaitForHeightRelative { + chain_id: lens.l1_chain_id, + height_diff: 1, + finalized: false, + }), + // refresh the lens client's consensus state *at counterparty_height* (not a newer + // height, e.g. via `UpdateClientToHeightTimestamp`) - `FetchUpdateAfterL1Update` + // always builds a header off the L1's current latest height regardless of whether a + // consensus state already exists for this L2 height, and `updateClient` overwrites + // the L1 height for a given L2 height, so this refreshes it to (the now-post-commit) + // latest without changing the L2 height the client trusts, which the just-submitted + // commitment's key is bound to. + promise( + [call(FetchUpdateHeaders { + client_type: lens_client_info.client_type, + chain_id: counterparty_chain_id.clone(), + counterparty_chain_id: chain_id.clone(), + client_id: RawClientId::new(client_id), + update_from: counterparty_height, + update_to: counterparty_height, + })], + [], + AggregateSubmitTxFromOrderedHeaders { + ibc_spec_id: IbcUnion::ID, + chain_id: chain_id.clone(), + client_id: RawClientId::new(client_id), + }, + ), + call(PluginMessage::new( + self.plugin_name(), + ModuleCall::from(MakeMsgTimeoutFromTrustedHeight { + event, + chain_id, + counterparty_chain_id, + }), + )), + ])) + } } diff --git a/voyager/plugins/transaction-batch-proof-lens/Cargo.toml b/voyager/plugins/transaction-batch-proof-lens/Cargo.toml index 8cd852729ed..e76a4401415 100644 --- a/voyager/plugins/transaction-batch-proof-lens/Cargo.toml +++ b/voyager/plugins/transaction-batch-proof-lens/Cargo.toml @@ -30,3 +30,6 @@ tokio = { workspace = true } tracing = { workspace = true } unionlabs = { workspace = true } voyager-sdk = { workspace = true } + +[dev-dependencies] +voyager-core = { workspace = true } diff --git a/voyager/plugins/transaction-batch-proof-lens/src/lib.rs b/voyager/plugins/transaction-batch-proof-lens/src/lib.rs index 5cd2bd23aa0..e47d3edf102 100644 --- a/voyager/plugins/transaction-batch-proof-lens/src/lib.rs +++ b/voyager/plugins/transaction-batch-proof-lens/src/lib.rs @@ -190,7 +190,7 @@ impl ClientConfigs { serde_json::to_string(&many.keys().map(|k| (k, ())).collect::>()) .unwrap(); - format!("{clients_json} | has($client_id)") + format!("{clients_json} | has($client_id | tostring)") } } } @@ -1937,10 +1937,65 @@ fn commit(bytes: impl AsRef<[u8]>) -> H256 { #[cfg(test)] mod tests { + use ibc_union_spec::{ConnectionId, event::FullEvent}; use serde_json::json; + use voyager_core::filter::InterestFilters; + use voyager_sdk::{ + primitives::{ClientInfo, ClientType, IbcInterface}, + vm::filter::{FilterResult, InterestFilter}, + }; use super::*; + fn connection_open_init_op(counterparty_client_id: u32) -> Op { + Op::Data(Data::IbcEvent(ChainEvent::new::( + ChainId::new("source-chain"), + ClientInfo { + client_type: ClientType::new(ClientType::COMETBLS), + ibc_interface: IbcInterface::new(IbcInterface::IBC_COSMWASM), + metadata: serde_json::Value::Null, + }, + ChainId::new("union-devnet-1"), + None, + EventProvableHeight::Min(Height::new(1)), + FullEvent::ConnectionOpenInit(ConnectionOpenInit { + connection_id: ConnectionId::from_raw(1).unwrap(), + client_id: ClientId::from_raw(1).unwrap(), + counterparty_client_id: ClientId::from_raw(counterparty_client_id).unwrap(), + }), + ))) + } + + #[test] + fn interest_filter_matches_numeric_client_id() { + let info = Module::info(Config { + chain_id: ChainId::new("union-devnet-1"), + client_configs: ClientConfigsSerde::Many( + [5, 7] + .into_iter() + .map(|id| SpecificClientConfig { + client_id: ClientId::from_raw(id).unwrap(), + min_batch_size: 1, + max_batch_size: 1, + max_wait_time: Duration::from_secs(1), + }) + .collect(), + ), + }); + let filters = InterestFilters::new(vec![info]).unwrap(); + let takes_interest = |counterparty_client_id| { + let op = connection_open_init_op(counterparty_client_id); + + matches!( + filters.check_interest(&op), + FilterResult::Interest(interest) if interest.remove + ) + }; + + assert!(takes_interest(5)); + assert!(!takes_interest(99)); + } + #[test] fn config_serde() { let config_json = json!({ diff --git a/voyager/plugins/transaction-batch/src/lib.rs b/voyager/plugins/transaction-batch/src/lib.rs index ebbdc8d8eaf..8606cc2cf8c 100644 --- a/voyager/plugins/transaction-batch/src/lib.rs +++ b/voyager/plugins/transaction-batch/src/lib.rs @@ -183,7 +183,7 @@ impl ClientConfigs { serde_json::to_string(&many.keys().map(|k| (k, ())).collect::>()) .unwrap(); - format!("{clients_json} | has($client_id)") + format!("{clients_json} | has($client_id | tostring)") } } } diff --git a/voyager/plugins/transaction/gno/src/main.rs b/voyager/plugins/transaction/gno/src/main.rs index fd5af7a676c..859dcbbe6a9 100644 --- a/voyager/plugins/transaction/gno/src/main.rs +++ b/voyager/plugins/transaction/gno/src/main.rs @@ -24,7 +24,7 @@ use tracing::{debug, error, info, info_span, instrument, trace, warn}; use unionlabs::{ self, ErrorReporter, never::Never, - primitives::{Bech32, Bytes, H160, encoding::HexPrefixed}, + primitives::{Bech32, H160, encoding::HexPrefixed}, }; use voyager_sdk::{ DefaultCmd, @@ -35,7 +35,7 @@ use voyager_sdk::{ plugin::Plugin, primitives::ChainId, rpc::{PluginServer, RpcError, RpcResult, types::PluginInfo}, - vm::{Op, Visit, noop, pass::PassResult}, + vm::{Op, Visit, call, noop, pass::PassResult, seq}, }; use crate::call::{IbcMessage, ModuleCall}; @@ -212,13 +212,7 @@ impl Module { let memo = format!("Voyager {}", env!("CARGO_PKG_VERSION")); let ibc_core_realm = self.ibc_core_realm.clone(); - let msgs = process_msgs( - msgs, - signer, - ibc_core_realm, - // self.fee_recipient.as_ref(), - None, - ); + let msgs = process_msgs(msgs, signer, ibc_core_realm, self.fee_recipient.as_ref()); let msgs = msgs .into_iter() @@ -323,6 +317,55 @@ impl Module { }) .await } + + fn handle_gno_tx_failure( + &self, + log: String, + mut msgs: Vec, + retryable_context: &str, + ) -> RpcResult> { + let _span: tracing::span::EnteredSpan = info_span!("gno msg failed").entered(); + info!(%log, "tx log"); + + let Some(kind) = gno_permanent_tx_failure(&log) else { + warn!("error submitting transaction: {log}"); + + return Err(RpcError::retryable_from_message(format!( + "{retryable_context}: {log}" + ))); + }; + + let reason = match kind { + GnoTxFailureKind::AlreadyProcessed(reason) => reason, + GnoTxFailureKind::MalformedMessage(reason) => { + error!(%reason, "gno msg failed due to a malformed message or misconfiguration"); + reason + } + }; + + if msgs.len() == 1 { + warn!( + msg = %into_value(msgs.pop().expect("msgs.len() == 1; qed")), + %reason, + "gno msg failed permanently, dropping" + ); + + Ok(noop()) + } else { + warn!( + %reason, + batch.size = %msgs.len(), + "splitting batch to isolate permanently failing message" + ); + + Ok(seq(msgs.into_iter().map(|msg| { + call(PluginMessage::new( + self.plugin_name(), + ModuleCall::SubmitTransaction(vec![msg]), + )) + }))) + } + } } #[async_trait] @@ -392,45 +435,7 @@ impl PluginServer for Module { BroadcastTxCommitError::TxFailed { error, log } => { info!(%log, "error submitting gno tx: {}", ErrorReporter(error)); - let _span = info_span!("gno msg failed").entered(); - info!(%log, "tx log"); - - warn!("error submitting transaction: {log}"); - - // TODO: Add back more sophisticated checks - Err(RpcError::retryable_from_message(format!( - "error submitting tx, tx failed: {log}" - ))) - - // if msgs.len() == 1 { - // warn!(msg = %into_value(msgs.pop().unwrap()), "gno msg failed"); - - // Ok(noop()) - // } else { - // let failed_msg = msgs.remove(msg_idx); - - // if matches!( - // failed_msg, - // IbcMessage::IbcUnion(Datagram::UpdateClient(_)) - // ) { - // warn!( - // "update client failed, this may cause other messages to fail as well" - // ); - // } - - // warn!(msg = %into_value(failed_msg), "dropping failed msg"); - - // if msgs.is_empty() { - // info!("no messages to submit after dropping failed messages"); - - // Ok(noop()) - // } else { - // Ok(call(PluginMessage::new( - // self.plugin_name(), - // ModuleCall::SubmitTransaction(msgs), - // ))) - // } - // } + self.handle_gno_tx_failure(log, msgs, "error submitting tx, tx failed") } err => Err(RpcError::retryable("error submitting tx")(err)), }, @@ -466,11 +471,67 @@ impl PluginServer for Module { } } +// panic substrings from gno-ibc's errors.gno meaning the packet/ack/batch was already finalized elsewhere +const GNO_ALREADY_PROCESSED_FAILURES: &[&str] = &[ + "packet already acknowledged", + "acknowledgement already written", + "packet commitment not found", + "packet already received", + "packet timeout expired", + "packet commitment already exists", + "batch packets not found", + "batch receipts not found", + "port already registered", +]; + +// malformed/unauthorized msgs, or references to ids that don't exist on gno (ids are never torn down, so always a relayer bug) +const GNO_MALFORMED_MSG_FAILURES: &[&str] = &[ + "caller not authorized to write ack", + "caller not authorized to send packet", + "rlm does not match the current crossing frame", + "rlm is not proxy realm", + "not enough packets", + "batch must use the same channel", + "invalid counterparty connection id", + "invalid counterparty channel id", + "timeout must be set", + "acknowledgement mismatch", + "acknowledgement count mismatch", + "acknowledgement cannot be empty", + "client not found", + "connection not found", + "channel not found", + "port not found", +]; + +#[derive(Debug, Clone, Copy)] +enum GnoTxFailureKind { + AlreadyProcessed(&'static str), + MalformedMessage(&'static str), +} + +// unmatched (e.g. invalid connection/channel state, client not active, packet receipt not found) stays retryable pending gno maintainer confirmation +fn gno_permanent_tx_failure(log: &str) -> Option { + if let Some(reason) = GNO_ALREADY_PROCESSED_FAILURES + .iter() + .copied() + .find(|needle| log.contains(needle)) + { + return Some(GnoTxFailureKind::AlreadyProcessed(reason)); + } + + GNO_MALFORMED_MSG_FAILURES + .iter() + .copied() + .find(|needle| log.contains(needle)) + .map(GnoTxFailureKind::MalformedMessage) +} + fn process_msgs( msgs: Vec, signer: &LocalSigner, ibc_core_realm: String, - fee_recipient: Option<&Bech32>, + fee_recipient: Option<&Bech32>, ) -> Vec> { msgs.into_iter() .map(|IbcMessage::IbcUnion(msg)| { @@ -486,22 +547,20 @@ package main import ( core "{ibc_core_realm}" + types "gno.land/p/onbloc/ibc/union/types" ) -func main() {{ - id := core.CreateClient(cross, core.MsgCreateClient{{ - ClientType: "{}", - ClientStateBytes: {}, - ConsensusStateBytes: {}, - // Relayer: = {}, - }}) - println(id) +func main(cur realm) {{ + core.CreateClient(cross(cur), types.NewMsgCreateClient( + "{}", + {}, + {}, + )) }} "#, msg.client_type, gno_bytes(&msg.client_state_bytes), gno_bytes(&msg.consensus_state_bytes), - relayer, ) } Datagram::UpdateClient(msg) => { @@ -511,20 +570,18 @@ package main import ( core "{ibc_core_realm}" + types "gno.land/p/onbloc/ibc/union/types" ) -func main() {{ - height := core.UpdateClient(cross, core.MsgUpdateClient{{ - ClientId: core.ClientId({}), - ClientMessage: {}, - // Relayer: {}, - }}) - println(height) +func main(cur realm) {{ + core.UpdateClient(cross(cur), types.NewMsgUpdateClient( + types.ClientId({}), + {}, + )) }} "#, msg.client_id, gno_bytes(&msg.client_message), - relayer, ) } Datagram::ConnectionOpenInit(msg) => { @@ -534,14 +591,14 @@ package main import ( core "{ibc_core_realm}" + types "gno.land/p/onbloc/ibc/union/types" ) -func main() {{ - height := core.ConnectionOpenInit(cross, core.MsgConnectionOpenInit{{ - ClientId: core.ClientId({}), - CounterpartyClientId: core.ClientId({}), - }}) - println(height) +func main(cur realm) {{ + core.ConnectionOpenInit(cross(cur), types.NewMsgConnectionOpenInit( + types.ClientId({}), + types.ClientId({}), + )) }} "#, msg.client_id, msg.counterparty_client_id, @@ -554,16 +611,17 @@ package main import ( core "{ibc_core_realm}" + types "gno.land/p/onbloc/ibc/union/types" ) -func main() {{ - core.ConnectionOpenTry(cross, core.MsgConnectionOpenTry{{ - ClientId: core.ClientId({}), - CounterpartyClientId: core.ClientId({}), - CounterpartyConnectionId: core.ConnectionId({}), - ProofInit: {}, - ProofHeight: core.Height({}), - }}) +func main(cur realm) {{ + core.ConnectionOpenTry(cross(cur), types.NewMsgConnectionOpenTry( + types.ClientId({}), + types.ClientId({}), + types.ConnectionId({}), + {}, + {}, + )) }} "#, msg.client_id, @@ -580,15 +638,16 @@ package main import ( core "{ibc_core_realm}" + types "gno.land/p/onbloc/ibc/union/types" ) -func main() {{ - core.ConnectionOpenAck(cross, core.MsgConnectionOpenAck{{ - ConnectionId: core.ConnectionId({}), - CounterpartyConnectionId: core.ConnectionId({}), - ProofTry: {}, - ProofHeight: core.Height({}), - }}) +func main(cur realm) {{ + core.ConnectionOpenAck(cross(cur), types.NewMsgConnectionOpenAck( + types.ConnectionId({}), + types.ConnectionId({}), + {}, + {}, + )) }} "#, msg.connection_id, @@ -604,14 +663,15 @@ package main import ( core "{ibc_core_realm}" + types "gno.land/p/onbloc/ibc/union/types" ) -func main() {{ - core.ConnectionOpenConfirm(cross, core.MsgConnectionOpenConfirm{{ - ConnectionId: core.ConnectionId({}), - ProofAck: {}, - ProofHeight: core.Height({}), - }}) +func main(cur realm) {{ + core.ConnectionOpenConfirm(cross(cur), types.NewMsgConnectionOpenConfirm( + types.ConnectionId({}), + {}, + {}, + )) }} "#, msg.connection_id, @@ -626,21 +686,24 @@ package main import ( core "{ibc_core_realm}" + types "gno.land/p/onbloc/ibc/union/types" ) -func main() {{ - core.ChannelOpenInit(cross, core.MsgChannelOpenInit{{ - PortId: {}, - CounterpartyPortId: {}, - ConnectionId: {}, - Version: "{}", - }}) +func main(cur realm) {{ + core.ChannelOpenInit(cross(cur), types.NewMsgChannelOpenInit( + {}, + {}, + types.ConnectionId({}), + "{}", + "{}", + )) }} "#, gno_bytes(&msg.port_id), gno_bytes(&msg.counterparty_port_id), msg.connection_id, msg.version, + relayer, ) } Datagram::ChannelOpenTry(msg) => { @@ -650,22 +713,24 @@ package main import ( core "{ibc_core_realm}" + types "gno.land/p/onbloc/ibc/union/types" ) -func main() {{ - core.ChannelOpenTry(cross, core.MsgChannelOpenTry{{ - PortId: {}, - Channel: core.Channel{{ - State: {}, - ConnectionId: core.ConnectionId({}), - CounterpartyChannelId: core.ChannelId({}), - CounterpartyPortId: {}, - Version: "{}", - }}, - CounterpartyVersion: "{}", - ProofInit: {}, - ProofHeight: {}, - }}) +func main(cur realm) {{ + core.ChannelOpenTry(cross(cur), types.NewMsgChannelOpenTry( + {}, + types.NewChannel( + types.ChannelState({}), + types.ConnectionId({}), + types.ChannelId({}), + {}, + "{}", + ), + "{}", + {}, + {}, + "{}", + )) }} "#, gno_bytes(&msg.port_id), @@ -679,6 +744,7 @@ func main() {{ msg.counterparty_version, gno_bytes(&msg.proof_init), msg.proof_height, + relayer, ) } Datagram::ChannelOpenAck(msg) => { @@ -688,16 +754,18 @@ package main import ( core "{ibc_core_realm}" + types "gno.land/p/onbloc/ibc/union/types" ) -func main() {{ - core.ChannelOpenAck(cross, core.MsgChannelOpenAck{{ - ChannelId: core.ChannelId({}), - CounterpartyVersion: "{}", - CounterpartyChannelId: core.ChannelId({}), - ProofTry: {}, - ProofHeight: {}, - }}) +func main(cur realm) {{ + core.ChannelOpenAck(cross(cur), types.NewMsgChannelOpenAck( + types.ChannelId({}), + "{}", + types.ChannelId({}), + {}, + {}, + "{}", + )) }} "#, msg.channel_id, @@ -705,6 +773,7 @@ func main() {{ msg.counterparty_channel_id, gno_bytes(&msg.proof_try), msg.proof_height, + relayer, ) } Datagram::ChannelOpenConfirm(msg) => { @@ -714,19 +783,22 @@ package main import ( core "{ibc_core_realm}" + types "gno.land/p/onbloc/ibc/union/types" ) -func main() {{ - core.ChannelOpenConfirm(cross, core.MsgChannelOpenConfirm{{ - ChannelId: core.ChannelId({}), - ProofAck: {}, - ProofHeight: {}, - }}) +func main(cur realm) {{ + core.ChannelOpenConfirm(cross(cur), types.NewMsgChannelOpenConfirm( + types.ChannelId({}), + {}, + {}, + "{}", + )) }} "#, msg.channel_id, gno_bytes(&msg.proof_ack), msg.proof_height, + relayer, ) } Datagram::ChannelCloseInit(_msg) => todo!(), @@ -738,15 +810,16 @@ package main import ( core "{ibc_core_realm}" + types "gno.land/p/onbloc/ibc/union/types" ) -func main() {{ - core.PacketRecv(cross, core.MsgPacketRecv{{ - Packets: []core.Packet{{ {} }}, - RelayerMsgs: [][]byte{{ {} }}, - Proof: {}, - ProofHeight: {}, - }}) +func main(cur realm) {{ + core.PacketRecv(cross(cur), types.NewMsgPacketRecv( + []types.Packet{{ {} }}, + [][]byte{{ {} }}, + {}, + {}, + )) }} "#, msg.packets @@ -770,15 +843,16 @@ package main import ( core "{ibc_core_realm}" + types "gno.land/p/onbloc/ibc/union/types" ) -func main() {{ - core.PacketAcknowledgement(cross, core.MsgPacketAcknowledgement{{ - Packets: []core.Packet{{ {} }}, - Acknowledgements: [][]byte{{ {} }}, - Proof: {}, - ProofHeight: {}, - }}) +func main(cur realm) {{ + core.PacketAcknowledgement(cross(cur), types.NewMsgPacketAcknowledgement( + []types.Packet{{ {} }}, + [][]byte{{ {} }}, + {}, + {}, + )) }} "#, msg.packets @@ -802,20 +876,20 @@ package main import ( core "{ibc_core_realm}" + types "gno.land/p/onbloc/ibc/union/types" ) -func main() {{ - core.PacketTimeout(cross, core.MsgPacketTimeout{{ - Packet: core.Packet{{ - SourceChannelId: core.ChannelId({}), - DestinationChannelId: core.ChannelId({}), - Data: {}, - TimeoutHeight: 0, - TimeoutTimestamp: core.Timestamp({}), - }}, - Proof: {}, - ProofHeight: {}, - }}) +func main(cur realm) {{ + core.PacketTimeout(cross(cur), types.NewMsgPacketTimeout( + types.NewPacket( + types.ChannelId({}), + types.ChannelId({}), + {}, + types.Timestamp({}), + ), + {}, + {}, + )) }} "#, msg.packet.source_channel_id, @@ -827,7 +901,29 @@ func main() {{ ) } Datagram::IntentPacketRecv(_msg) => todo!(), - Datagram::BatchSend(_msg) => todo!(), + Datagram::BatchSend(msg) => { + format!( + r#" +package main + +import ( + core "{ibc_core_realm}" + types "gno.land/p/onbloc/ibc/union/types" +) + +func main(cur realm) {{ + core.BatchSend(cross(cur), types.NewMsgBatchSend( + []types.Packet{{ {} }}, + )) +}} + "#, + msg.packets + .iter() + .map(gno_packet) + .collect::>() + .join(","), + ) + } Datagram::BatchAcks(_msg) => todo!(), Datagram::CommitMembershipProof(_msg) => { return Err(RpcError::fatal_from_message( @@ -883,7 +979,7 @@ fn gno_bytes(bz: impl AsRef<[u8]>) -> String { fn gno_packet(packet: &Packet) -> String { format!( - "core.Packet{{ SourceChannelId: core.ChannelId({}), DestinationChannelId: core.ChannelId({}), Data: {}, TimeoutHeight: core.Height(0), TimeoutTimestamp: core.Timestamp({}) }}", + "types.NewPacket(types.ChannelId({}), types.ChannelId({}), {}, types.Timestamp({}))", packet.source_channel_id, packet.destination_channel_id, gno_bytes(&packet.data),