diff --git a/Cargo.lock b/Cargo.lock index 5c2f24a29e..c67e93d3c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -154,7 +154,7 @@ dependencies = [ "num-traits", "rusticata-macros", "thiserror", - "time 0.3.23", + "time 0.3.24", ] [[package]] @@ -188,7 +188,7 @@ checksum = "cc6dde6e4ed435a4c1ee4e73592f5ba9da2151af10076cc04858746af9352d09" dependencies = [ "proc-macro2 1.0.66", "quote 1.0.32", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] @@ -320,7 +320,7 @@ dependencies = [ "regex", "rustc-hash", "shlex 1.1.0", - "syn 2.0.27", + "syn 2.0.28", "which 4.4.0", ] @@ -738,7 +738,7 @@ dependencies = [ "autocfg", "cfg-if 1.0.0", "crossbeam-utils 0.8.16", - "memoffset 0.9.0", + "memoffset", "scopeguard", ] @@ -959,6 +959,12 @@ dependencies = [ "rusticata-macros", ] +[[package]] +name = "deranged" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8810e7e2cf385b1e9b50d68264908ec367ba642c96d02edfe61c39e88e2a3c01" + [[package]] name = "derive_arbitrary" version = "1.3.1" @@ -967,7 +973,7 @@ checksum = "53e0efad4403bfc52dc201159c4b842a246a14b98c64b55dfd0f2d89729dfeb8" dependencies = [ "proc-macro2 1.0.66", "quote 1.0.32", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] @@ -1021,7 +1027,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2 1.0.66", "quote 1.0.32", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] @@ -1110,9 +1116,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" dependencies = [ "errno-dragonfly", "libc", @@ -1197,9 +1203,9 @@ dependencies = [ [[package]] name = "evm" -version = "0.37.0" +version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4448c65b71e8e2b9718232d84d09045eeaaccb2320494e6bd6dbf7e58fec8ff" +checksum = "a49a4e11987c51220aa89dbe1a5cc877f5079fa6864c0a5b4533331db44e9365" dependencies = [ "auto_impl", "environmental", @@ -1218,9 +1224,9 @@ dependencies = [ [[package]] name = "evm-core" -version = "0.37.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64c51bec0eb68a891c2575c758eaaa1d61373fc51f7caaf216b1fb5c3fea3b5d" +checksum = "4f1f13264b044cb66f0602180f0bc781c29accb41ff560669a3ec15858d5b606" dependencies = [ "parity-scale-codec", "primitive-types", @@ -1230,9 +1236,9 @@ dependencies = [ [[package]] name = "evm-gasometer" -version = "0.37.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8b93c59c54fc26522d842f0e0d3f8e8be331c776df18ff3e540b53c2f64d509" +checksum = "8d43eadc395bd1a52990787ca1495c26b0248165444912be075c28909a853b8c" dependencies = [ "environmental", "evm-core", @@ -1242,9 +1248,9 @@ dependencies = [ [[package]] name = "evm-runtime" -version = "0.37.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c79b9459ce64f1a28688397c4013764ce53cd57bb84efc16b5187fa9b05b13ad" +checksum = "2aa5b32f59ec582a5651978004e5c784920291263b7dcb6de418047438e37f4f" dependencies = [ "auto_impl", "environmental", @@ -1400,7 +1406,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2 1.0.66", "quote 1.0.32", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] @@ -1715,11 +1721,11 @@ dependencies = [ [[package]] name = "intrusive-collections" -version = "0.9.5" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f4f90afb01281fdeffb0f8e082d230cbe4f888f837cc90759696b858db1a700" +checksum = "b694dc9f70c3bda874626d2aed13b780f137aab435f4e9814121955cf706122e" dependencies = [ - "memoffset 0.8.0", + "memoffset", ] [[package]] @@ -1934,15 +1940,6 @@ dependencies = [ "libc", ] -[[package]] -name = "memoffset" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" -dependencies = [ - "autocfg", -] - [[package]] name = "memoffset" version = "0.9.0" @@ -2639,7 +2636,7 @@ checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" dependencies = [ "proc-macro2 1.0.66", "quote 1.0.32", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] @@ -2756,7 +2753,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c64d9ba0963cdcea2e1b2230fbae2bab30eb25a174be395c41e764bfb65dd62" dependencies = [ "proc-macro2 1.0.66", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] @@ -3007,9 +3004,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310" +checksum = "b7b6d6190b7594385f61bd3911cd1be99dfddcfc365a4160cc2ab5bff4aed294" dependencies = [ "aho-corasick", "memchr", @@ -3253,9 +3250,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.175" +version = "1.0.179" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d25439cd7397d044e2748a6fe2432b5e85db703d6d097bd014b3c0ad1ebff0b" +checksum = "0a5bf42b8d227d4abf38a1ddb08602e229108a517cd4e5bb28f9c7eaafdce5c0" dependencies = [ "serde_derive", ] @@ -3281,20 +3278,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.175" +version = "1.0.179" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b23f7ade6f110613c0d63858ddb8b94c1041f550eab58a16b371bdf2c9c80ab4" +checksum = "741e124f5485c7e60c03b043f79f320bff3527f4bbf12cf3831750dc46a0ec2c" dependencies = [ "proc-macro2 1.0.66", "quote 1.0.32", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] name = "serde_json" -version = "1.0.103" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d03b412469450d4404fe8499a268edd7f8b79fecb074b0d812ad64ca21f4031b" +checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" dependencies = [ "itoa", "ryu", @@ -3303,13 +3300,13 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e168eaaf71e8f9bd6037feb05190485708e019f4fd87d161b3c0a0d37daf85e5" +checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" dependencies = [ "proc-macro2 1.0.66", "quote 1.0.32", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] @@ -3422,7 +3419,7 @@ dependencies = [ "serde", "serde_json", "slog", - "time 0.3.23", + "time 0.3.24", ] [[package]] @@ -3587,9 +3584,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.27" +version = "2.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b60f673f44a8255b9c8c657daf66a596d435f2da81a555b06dc644d080ba45e0" +checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" dependencies = [ "proc-macro2 1.0.66", "quote 1.0.32", @@ -3639,7 +3636,7 @@ dependencies = [ "subtle", "subtle-encoding", "tendermint-proto", - "time 0.3.23", + "time 0.3.24", "zeroize", ] @@ -3675,7 +3672,7 @@ dependencies = [ "tendermint", "tendermint-light-client-verifier", "tendermint-rpc", - "time 0.3.23", + "time 0.3.24", ] [[package]] @@ -3688,7 +3685,7 @@ dependencies = [ "flex-error", "serde", "tendermint", - "time 0.3.23", + "time 0.3.24", ] [[package]] @@ -3706,7 +3703,7 @@ dependencies = [ "serde", "serde_bytes", "subtle-encoding", - "time 0.3.23", + "time 0.3.24", ] [[package]] @@ -3728,7 +3725,7 @@ dependencies = [ "tendermint-config", "tendermint-proto", "thiserror", - "time 0.3.23", + "time 0.3.24", "url", "uuid", "walkdir", @@ -3818,7 +3815,7 @@ checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" dependencies = [ "proc-macro2 1.0.66", "quote 1.0.32", - "syn 2.0.27", + "syn 2.0.28", ] [[package]] @@ -3834,10 +3831,11 @@ dependencies = [ [[package]] name = "time" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446" +checksum = "b79eabcd964882a646b3584543ccabeae7869e9ac32a46f6f22b7a5bd405308b" dependencies = [ + "deranged", "itoa", "serde", "time-core", @@ -3852,9 +3850,9 @@ checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "time-macros" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4" +checksum = "eb71511c991639bb078fd5bf97757e03914361c48100d52878b8e52b46fb92cd" dependencies = [ "time-core", ] @@ -4146,7 +4144,7 @@ dependencies = [ "once_cell", "proc-macro2 1.0.66", "quote 1.0.32", - "syn 2.0.27", + "syn 2.0.28", "wasm-bindgen-shared", ] @@ -4168,7 +4166,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2 1.0.66", "quote 1.0.32", - "syn 2.0.27", + "syn 2.0.28", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4181,9 +4179,9 @@ checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "wasm-encoder" -version = "0.31.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06a3d1b4a575ffb873679402b2aedb3117555eb65c27b1b86c8a91e574bc2a2a" +checksum = "41763f20eafed1399fff1afb466496d3a959f58241436cfdc17e3f5ca954de16" dependencies = [ "leb128", ] @@ -4219,9 +4217,9 @@ checksum = "5fe3d5405e9ea6c1317a656d6e0820912d8b7b3607823a7596117c8f666daf6f" [[package]] name = "wasmparser" -version = "0.109.0" +version = "0.110.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bf9564f29de2890ee34406af52d2a92dec6ef044c8ddfc5add5db8dcfd36e6c" +checksum = "1dfcdb72d96f01e6c85b6bf20102e7423bdbaad5c337301bab2bbf253d26413c" dependencies = [ "indexmap", "semver 1.0.18", @@ -4229,19 +4227,19 @@ dependencies = [ [[package]] name = "wasmprinter" -version = "0.2.61" +version = "0.2.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df06dc468a161167818d5333cc248f49b58218cc0b20eb036840ea4332cb1a4a" +checksum = "42cd12ed4d96a984e4b598a17457f1126d01640cc7461afbb319642111ff9e7f" dependencies = [ "anyhow", - "wasmparser 0.109.0", + "wasmparser 0.110.0", ] [[package]] name = "wast" -version = "62.0.0" +version = "62.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f7ee878019d69436895f019b65f62c33da63595d8e857cbdc87c13ecb29a32" +checksum = "b8ae06f09dbe377b889fbd620ff8fa21e1d49d1d9d364983c0cdbf9870cb9f1f" dependencies = [ "leb128", "memchr", @@ -4251,9 +4249,9 @@ dependencies = [ [[package]] name = "wat" -version = "1.0.68" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "295572bf24aa5b685a971a83ad3e8b6e684aaad8a9be24bc7bf59bed84cc1c08" +checksum = "842e15861d203fb4a96d314b0751cdeaf0f6f8b35e8d81d2953af2af5e44e637" dependencies = [ "wast", ] @@ -4465,9 +4463,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "winnow" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25b5872fa2e10bd067ae946f927e726d7d603eaeb6e02fa6a350e0722d2b8c11" +checksum = "8bd122eb777186e60c3fdf765a58ac76e41c582f1f535fbf3314434c6b58f3f7" dependencies = [ "memchr", ] @@ -4507,7 +4505,7 @@ dependencies = [ "oid-registry", "rusticata-macros", "thiserror", - "time 0.3.23", + "time 0.3.24", ] [[package]] @@ -4562,5 +4560,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2 1.0.66", "quote 1.0.32", - "syn 2.0.27", + "syn 2.0.28", ] diff --git a/client-sdk/go/callformat/callformat.go b/client-sdk/go/callformat/callformat.go index bec645338f..a4a25f0a43 100644 --- a/client-sdk/go/callformat/callformat.go +++ b/client-sdk/go/callformat/callformat.go @@ -16,6 +16,8 @@ import ( type EncodeConfig struct { // PublicKey is an optional runtime's call data public key to use for encrypted call formats. PublicKey *types.SignedPublicKey + // Epoch is the epoch of the ephemeral runtime key (when PublicKey is set). + Epoch uint64 } type metaEncryptedX25519DeoxysII struct { @@ -60,6 +62,7 @@ func EncodeCall(call *types.Call, cf types.CallFormat, cfg *EncodeConfig) (*type Body: cbor.Marshal(&types.CallEnvelopeX25519DeoxysII{ Pk: *pk, Nonce: nonce, + Epoch: cfg.Epoch, Data: sealedCall, }), ReadOnly: call.ReadOnly, diff --git a/client-sdk/go/client/callformat.go b/client-sdk/go/client/callformat.go index 219653cbc9..62bd441051 100644 --- a/client-sdk/go/client/callformat.go +++ b/client-sdk/go/client/callformat.go @@ -11,7 +11,10 @@ import ( const methodCallDataPublicKey = "core.CallDataPublicKey" type callDataPublicKeyQueryResponse struct { + // PublicKey is the ephemeral X25519 runtime public key. PublicKey types.SignedPublicKey `json:"public_key"` + // Epoch is the epoch of the ephemeral runtime key. + Epoch uint64 `json:"epoch,omitempty"` } // encodeCall performs call encoding based on the specified call format. @@ -30,6 +33,7 @@ func (tb *TransactionBuilder) encodeCall(ctx context.Context, call *types.Call, // TODO: In case the node we are connecting to is not trusted, validate the key manager signature. cfg.PublicKey = &rsp.PublicKey + cfg.Epoch = rsp.Epoch default: } diff --git a/client-sdk/go/modules/core/types.go b/client-sdk/go/modules/core/types.go index 2702ad5941..6373539169 100644 --- a/client-sdk/go/modules/core/types.go +++ b/client-sdk/go/modules/core/types.go @@ -100,6 +100,8 @@ const ( type CallDataPublicKeyResponse struct { // PublicKey is the signed runtime call data public key. PublicKey types.SignedPublicKey `json:"public_key"` + // Epoch is the epoch of the ephemeral runtime key. + Epoch uint64 `json:"epoch,omitempty"` } // ExecuteReadOnlyTxQuery is the body of the core.ExecuteReadOnlyTx query. diff --git a/client-sdk/go/types/callformat.go b/client-sdk/go/types/callformat.go index 331f78e15c..2eda8b1fdc 100644 --- a/client-sdk/go/types/callformat.go +++ b/client-sdk/go/types/callformat.go @@ -8,6 +8,8 @@ type CallEnvelopeX25519DeoxysII struct { Pk [32]byte `json:"pk"` // Nonce. Nonce [deoxysii.NonceSize]byte `json:"nonce"` + // Epoch is the epoch of the ephemeral runtime key. + Epoch uint64 `json:"epoch,omitempty"` // Data is the encrypted call data. Data []byte `json:"data"` } diff --git a/runtime-sdk/modules/contracts/src/abi/oasis/test.rs b/runtime-sdk/modules/contracts/src/abi/oasis/test.rs index b44a094706..9723d00ee7 100644 --- a/runtime-sdk/modules/contracts/src/abi/oasis/test.rs +++ b/runtime-sdk/modules/contracts/src/abi/oasis/test.rs @@ -303,7 +303,7 @@ fn test_validate_and_transform() { let mut mock = mock::Mock::default(); let mut ctx = mock.create_ctx(); let params = Parameters::default(); - ctx.with_tx(0, 0, mock::transaction(), |ctx, _| { + ctx.with_tx(mock::transaction().into(), |ctx, _| { test::(ctx, ¶ms); }); } @@ -331,7 +331,7 @@ fn run_contract_with_defaults( let mut tx = mock::transaction(); tx.auth_info.fee.gas = gas_limit; - ctx.with_tx(0, 0, tx, |mut ctx, _| -> Result { + ctx.with_tx(tx.into(), |mut ctx, _| -> Result { fn transform( _ctx: &mut C, code: &[u8], diff --git a/runtime-sdk/modules/contracts/src/test.rs b/runtime-sdk/modules/contracts/src/test.rs index d239c32769..d6476f74d9 100644 --- a/runtime-sdk/modules/contracts/src/test.rs +++ b/runtime-sdk/modules/contracts/src/test.rs @@ -65,7 +65,7 @@ fn upload_hello_contract(ctx: &mut C) -> types::CodeId { ..Default::default() }, }; - ctx.with_tx(0, 0, tx, |mut tx_ctx, call| { + ctx.with_tx(tx.into(), |mut tx_ctx, call| { let code_id = Contracts::tx_upload(&mut tx_ctx, cbor::from_value(call.body).unwrap()) .expect("upload should succeed") .id; @@ -115,7 +115,7 @@ fn deploy_hello_contract( ..Default::default() }, }; - ctx.with_tx(0, 0, tx, |mut tx_ctx, call| { + ctx.with_tx(tx.into(), |mut tx_ctx, call| { let instance_id = Contracts::tx_instantiate(&mut tx_ctx, cbor::from_value(call.body).unwrap()) .expect("instantiate should succeed") @@ -232,7 +232,7 @@ fn test_hello_contract_call() { ..Default::default() }, }; - ctx.with_tx(0, 0, tx, |mut tx_ctx, call| { + ctx.with_tx(tx.into(), |mut tx_ctx, call| { let result = Contracts::tx_call(&mut tx_ctx, cbor::from_value(call.body).unwrap()) .expect("call should succeed"); @@ -329,7 +329,7 @@ fn test_hello_contract_call() { ..Default::default() }, }; - ctx.with_tx(0, 0, tx, |mut tx_ctx, call| { + ctx.with_tx(tx.into(), |mut tx_ctx, call| { let result = Contracts::tx_call(&mut tx_ctx, cbor::from_value(call.body).unwrap()) .expect("call should succeed"); @@ -482,7 +482,7 @@ fn test_hello_contract_call() { ..Default::default() }, }; - ctx.with_tx(0, 0, tx, |mut tx_ctx, call| { + ctx.with_tx(tx.into(), |mut tx_ctx, call| { let _result = Contracts::tx_call(&mut tx_ctx, cbor::from_value(call.body).unwrap()) .expect("call should succeed"); tx_ctx.commit(); @@ -569,7 +569,7 @@ fn test_hello_contract_call() { ..Default::default() }, }; - ctx.with_tx(0, 0, tx, |mut tx_ctx, call| { + ctx.with_tx(tx.into(), |mut tx_ctx, call| { let _result = Contracts::tx_call(&mut tx_ctx, cbor::from_value(call.body).unwrap()) .expect("call should succeed"); tx_ctx.commit(); @@ -667,13 +667,13 @@ fn test_hello_contract_call() { ..Default::default() }, }; - ctx.with_tx(0, 0, invalid_tx.clone(), |mut tx_ctx, call| { + ctx.with_tx(invalid_tx.clone().into(), |mut tx_ctx, call| { Contracts::tx_call(&mut tx_ctx, cbor::from_value(call.body).unwrap()) .expect_err("invalid call should fail"); }); ctx.with_child(context::Mode::CheckTx, |mut check_ctx| { - check_ctx.with_tx(0, 0, invalid_tx, |mut tx_ctx, call| { + check_ctx.with_tx(invalid_tx.into(), |mut tx_ctx, call| { Contracts::tx_call(&mut tx_ctx, cbor::from_value(call.body).unwrap()) .expect("invalid call should succeed check-tx"); }); @@ -769,7 +769,7 @@ fn test_hello_contract_subcalls_overflow() { ..Default::default() }, }; - ctx.with_tx(0, 0, tx, |mut tx_ctx, call| { + ctx.with_tx(tx.into(), |mut tx_ctx, call| { let result = Contracts::tx_call(&mut tx_ctx, cbor::from_value(call.body).unwrap()) .expect_err("call should fail"); @@ -823,7 +823,7 @@ fn test_hello_contract_subcalls() { ..Default::default() }, }; - ctx.with_tx(0, 0, tx.clone(), |mut tx_ctx, call| { + ctx.with_tx(tx.clone().into(), |mut tx_ctx, call| { let result = Contracts::tx_call(&mut tx_ctx, cbor::from_value(call.body).unwrap()) .expect("call should succeed"); @@ -891,7 +891,7 @@ fn test_hello_contract_query() { ..Default::default() }, }; - ctx.with_tx(0, 0, tx, |mut tx_ctx, call| { + ctx.with_tx(tx.into(), |mut tx_ctx, call| { let result = Contracts::tx_call(&mut tx_ctx, cbor::from_value(call.body).unwrap()) .expect("call should succeed"); @@ -933,7 +933,7 @@ fn test_hello_contract_query() { ..Default::default() }, }; - ctx.with_tx(0, 0, tx, |mut tx_ctx, call| { + ctx.with_tx(tx.into(), |mut tx_ctx, call| { let result = Contracts::tx_call(&mut tx_ctx, cbor::from_value(call.body).unwrap()) .expect("call should succeed"); @@ -975,7 +975,7 @@ fn test_hello_contract_query() { ..Default::default() }, }; - ctx.with_tx(0, 0, tx, |mut tx_ctx, call| { + ctx.with_tx(tx.into(), |mut tx_ctx, call| { let result = Contracts::tx_call(&mut tx_ctx, cbor::from_value(call.body).unwrap()) .expect("call should succeed"); @@ -1029,7 +1029,7 @@ fn test_hello_contract_upgrade() { ..Default::default() }, }; - ctx.with_tx(0, 0, tx, |mut tx_ctx, call| { + ctx.with_tx(tx.into(), |mut tx_ctx, call| { Contracts::tx_upgrade(&mut tx_ctx, cbor::from_value(call.body).unwrap()) .expect("upgrade should succeed"); @@ -1074,7 +1074,7 @@ fn test_hello_contract_upgrade_fail_policy() { ..Default::default() }, }; - ctx.with_tx(0, 0, tx, |mut tx_ctx, call| { + ctx.with_tx(tx.into(), |mut tx_ctx, call| { let result = Contracts::tx_upgrade(&mut tx_ctx, cbor::from_value(call.body).unwrap()) .expect_err("upgrade should fail"); @@ -1121,7 +1121,7 @@ fn test_hello_contract_upgrade_fail_pre() { ..Default::default() }, }; - ctx.with_tx(0, 0, tx, |mut tx_ctx, call| { + ctx.with_tx(tx.into(), |mut tx_ctx, call| { let result = Contracts::tx_upgrade(&mut tx_ctx, cbor::from_value(call.body).unwrap()) .expect_err("upgrade should fail"); @@ -1171,7 +1171,7 @@ fn test_hello_contract_upgrade_fail_post() { ..Default::default() }, }; - ctx.with_tx(0, 0, tx, |mut tx_ctx, call| { + ctx.with_tx(tx.into(), |mut tx_ctx, call| { let result = Contracts::tx_upgrade(&mut tx_ctx, cbor::from_value(call.body).unwrap()) .expect_err("upgrade should fail"); @@ -1218,7 +1218,7 @@ fn test_hello_contract_change_upgrade_policy() { ..Default::default() }, }; - ctx.with_tx(0, 0, tx, |mut tx_ctx, call| { + ctx.with_tx(tx.into(), |mut tx_ctx, call| { Contracts::tx_change_upgrade_policy(&mut tx_ctx, cbor::from_value(call.body).unwrap()) .expect("upgrade should succeed"); @@ -1260,7 +1260,7 @@ fn test_hello_contract_change_upgrade_policy_fail() { ..Default::default() }, }; - ctx.with_tx(0, 0, tx, |mut tx_ctx, call| { + ctx.with_tx(tx.into(), |mut tx_ctx, call| { let result = Contracts::tx_change_upgrade_policy(&mut tx_ctx, cbor::from_value(call.body).unwrap()) .expect_err("change upgrade policy should fail"); diff --git a/runtime-sdk/modules/evm/Cargo.toml b/runtime-sdk/modules/evm/Cargo.toml index 736adf9da8..55b60505de 100644 --- a/runtime-sdk/modules/evm/Cargo.toml +++ b/runtime-sdk/modules/evm/Cargo.toml @@ -30,7 +30,7 @@ rand_core = { version = "0.6.4", default-features = false } # Ethereum. ethabi = { version = "18.0.0", default-features = false, features = ["std"] } ethereum = "0.14" -evm = "0.37.0" +evm = "0.39.1" fixed-hash = "0.8.0" primitive-types = { version = "0.12", default-features = false, features = ["rlp", "num-traits"] } rlp = "0.5.2" diff --git a/runtime-sdk/modules/evm/src/backend.rs b/runtime-sdk/modules/evm/src/backend.rs index 79d1c52834..3fa5088a5e 100644 --- a/runtime-sdk/modules/evm/src/backend.rs +++ b/runtime-sdk/modules/evm/src/backend.rs @@ -340,6 +340,10 @@ impl<'ctx, 'backend, 'config, C: TxContext, Cfg: Config> Backend U256::zero() } + fn block_randomness(&self) -> Option { + None + } + fn block_gas_limit(&self) -> U256 { ::Core::max_batch_gas(&mut self.backend.ctx.borrow_mut()).into() } @@ -466,14 +470,15 @@ impl<'ctx, 'backend, 'config, C: TxContext, Cfg: Config> StackState<'config> .recursive_is_cold(&|a: &Accessed| a.accessed_storage.contains(&(address, key))) } - fn inc_nonce(&mut self, address: H160) { + fn inc_nonce(&mut self, address: H160) -> Result<(), ExitError> { // Do not increment the origin nonce as that has already been handled by the SDK. if address == self.origin() { - return; + return Ok(()); } let address = Cfg::map_address(address); Cfg::Accounts::inc_nonce(address); + Ok(()) } fn set_storage(&mut self, address: H160, key: H256, value: H256) { diff --git a/runtime-sdk/modules/evm/src/lib.rs b/runtime-sdk/modules/evm/src/lib.rs index 4912c815bb..83d044a127 100644 --- a/runtime-sdk/modules/evm/src/lib.rs +++ b/runtime-sdk/modules/evm/src/lib.rs @@ -21,7 +21,7 @@ use thiserror::Error; use oasis_runtime_sdk::{ callformat, - context::{BatchContext, Context, TxContext}, + context::{BatchContext, Context, TransactionWithMeta, TxContext}, handler, module::{self, Module as _}, modules::{ @@ -107,7 +107,7 @@ pub trait Config: 'static { } }) } else { - EVM_CONFIG.get_or_init(EVMConfig::london) + EVM_CONFIG.get_or_init(EVMConfig::shanghai) } } } @@ -186,6 +186,7 @@ impl From for Error { PCUnderflow => "PC underflow", CreateEmpty => "create empty", + MaxNonce => "nonce overflow", Other(msg) => return Error::ExecutionFailed(msg.to_string()), }; @@ -494,24 +495,27 @@ impl API for Module { ..Default::default() }, }; - sctx.with_tx(0, 0, call_tx, |mut txctx, _call| { - Self::do_evm( - caller, - &mut txctx, - |exec, gas_limit| { - exec.transact_call( - caller.into(), - address.into(), - value.into(), - data, - gas_limit, - vec![], - ) - }, - // Simulate call is never called from EstimateGas. - false, - ) - }) + sctx.with_tx( + TransactionWithMeta::internal(call_tx), + |mut txctx, _call| { + Self::do_evm( + caller, + &mut txctx, + |exec, gas_limit| { + exec.transact_call( + caller.into(), + address.into(), + value.into(), + data, + gas_limit, + vec![], + ) + }, + // Simulate call is never called from EstimateGas. + false, + ) + }, + ) }); Self::encode_evm_result(ctx, evm_result, tx_metadata) } diff --git a/runtime-sdk/modules/evm/src/precompile/mod.rs b/runtime-sdk/modules/evm/src/precompile/mod.rs index 82ec847ae6..7a0e58573e 100644 --- a/runtime-sdk/modules/evm/src/precompile/mod.rs +++ b/runtime-sdk/modules/evm/src/precompile/mod.rs @@ -3,7 +3,9 @@ use std::{cmp::min, marker::PhantomData}; use evm::{ - executor::stack::{PrecompileFailure, PrecompileHandle, PrecompileOutput, PrecompileSet}, + executor::stack::{ + IsPrecompileResult, PrecompileFailure, PrecompileHandle, PrecompileOutput, PrecompileSet, + }, ExitError, }; use primitive_types::H160; @@ -88,8 +90,14 @@ impl<'a, Cfg: Config, B: EVMBackendExt> Precompiles<'a, Cfg, B> { impl PrecompileSet for Precompiles<'_, Cfg, B> { fn execute(&self, handle: &mut impl PrecompileHandle) -> Option { let address = handle.code_address(); - if !self.is_precompile(address) { - return None; + match self.is_precompile(address, handle.remaining_gas()) { + IsPrecompileResult::Answer { + is_precompile: true, + .. + } => { /* Ok. */ } + _ => { + return None; + } } Some(match (address[0], address[18], address[19]) { // Ethereum-compatible. @@ -117,11 +125,11 @@ impl PrecompileSet for Precompiles<'_, Cfg, B> { }) } - fn is_precompile(&self, address: H160) -> bool { + fn is_precompile(&self, address: H160, remaining_gas: u64) -> IsPrecompileResult { // See above table in `execute` for matching on what is a valid precompile address. let addr_bytes = address.as_bytes(); let (a0, a18, a19) = (address[0], addr_bytes[18], addr_bytes[19]); - (address[1..18].iter().all(|b| *b == 0) + if address[1..18].iter().all(|b| *b == 0) && matches!( (a0, a18, a19, Cfg::CONFIDENTIAL), // Ethereum-compatible. @@ -130,9 +138,19 @@ impl PrecompileSet for Precompiles<'_, Cfg, B> { (1, 0, 1..=8, true) | // Oasis-specific, general. (1, 1, 1..=2, _) - )) - || Cfg::additional_precompiles() - .map(|pc| pc.is_precompile(address)) - .unwrap_or_default() + ) + { + IsPrecompileResult::Answer { + is_precompile: true, + extra_cost: 0, + } + } else { + Cfg::additional_precompiles() + .map(|pc| pc.is_precompile(address, remaining_gas)) + .unwrap_or(IsPrecompileResult::Answer { + is_precompile: false, + extra_cost: 0, + }) + } } } diff --git a/runtime-sdk/modules/evm/src/precompile/subcall.rs b/runtime-sdk/modules/evm/src/precompile/subcall.rs index c82a4a8356..71b4b82e33 100644 --- a/runtime-sdk/modules/evm/src/precompile/subcall.rs +++ b/runtime-sdk/modules/evm/src/precompile/subcall.rs @@ -198,10 +198,10 @@ mod test { /// Test contract code. static TEST_CONTRACT_CODE_HEX: &str = - include_str!("../../../../../tests/e2e/contracts/subcall/evm_subcall_compiled.hex"); + include_str!("../../../../../tests/e2e/contracts/subcall/evm_subcall.hex"); /// Test contract ABI. static TEST_CONTRACT_ABI_JSON: &str = - include_str!("../../../../../tests/e2e/contracts/subcall/evm_subcall_abi.json"); + include_str!("../../../../../tests/e2e/contracts/subcall/evm_subcall.abi"); fn init_and_deploy_contract(ctx: &mut C, signer: &mut EvmSigner) -> H160 { TestRuntime::migrate(ctx); diff --git a/runtime-sdk/modules/evm/src/test.rs b/runtime-sdk/modules/evm/src/test.rs index d08f77faac..95e169ac0e 100644 --- a/runtime-sdk/modules/evm/src/test.rs +++ b/runtime-sdk/modules/evm/src/test.rs @@ -219,7 +219,7 @@ fn do_test_evm_calls(force_plain: bool) { // Run authentication handler to simulate nonce increments. Accounts::authenticate_tx(&mut ctx, &create_tx).unwrap(); - let erc20_addr = ctx.with_tx(0, 0, create_tx, |mut tx_ctx, call| { + let erc20_addr = ctx.with_tx(create_tx.into(), |mut tx_ctx, call| { let addr = H160::from_slice( &EVMModule::::tx_create(&mut tx_ctx, cbor::from_value(call.body).unwrap()).unwrap(), ); @@ -258,7 +258,7 @@ fn do_test_evm_calls(force_plain: bool) { // Run authentication handler to simulate nonce increments. Accounts::authenticate_tx(&mut ctx, &call_name_tx).unwrap(); - let erc20_name = ctx.with_tx(0, 0, call_name_tx, |mut tx_ctx, call| { + let erc20_name = ctx.with_tx(call_name_tx.into(), |mut tx_ctx, call| { let name: Vec = cbor::from_value( decode_result!( tx_ctx, @@ -360,7 +360,7 @@ fn test_c10l_evm_balance_transfer() { // Run authentication handler to simulate nonce increments. Accounts::authenticate_tx(&mut ctx, &transfer_tx).unwrap(); - ctx.with_tx(0, 0, transfer_tx, |mut tx_ctx, call| { + ctx.with_tx(transfer_tx.into(), |mut tx_ctx, call| { EVMModule::::tx_call( &mut tx_ctx, cbor::from_value(call.body).unwrap(), @@ -535,7 +535,7 @@ fn do_test_evm_runtime() { // Run authentication handler to simulate nonce increments. as Runtime>::Modules::authenticate_tx(&mut ctx, &create_tx).unwrap(); - let erc20_addr = ctx.with_tx(0, 0, create_tx, |mut tx_ctx, call| { + let erc20_addr = ctx.with_tx(create_tx.into(), |mut tx_ctx, call| { let addr = H160::from_slice( &EVMModule::::tx_create(&mut tx_ctx, cbor::from_value(call.body).unwrap()).unwrap(), ); @@ -572,7 +572,7 @@ fn do_test_evm_runtime() { // Run authentication handler to simulate nonce increments. as Runtime>::Modules::authenticate_tx(&mut ctx, &out_of_gas_create).unwrap(); - ctx.with_tx(0, 0, out_of_gas_create.clone(), |mut tx_ctx, call| { + ctx.with_tx(out_of_gas_create.clone().into(), |mut tx_ctx, call| { assert!(!decode_result!( tx_ctx, EVMModule::::tx_create(&mut tx_ctx, cbor::from_value(call.body).unwrap()) @@ -582,7 +582,7 @@ fn do_test_evm_runtime() { // CheckTx should not fail. ctx.with_child(context::Mode::CheckTx, |mut check_ctx| { - check_ctx.with_tx(0, 0, out_of_gas_create, |mut tx_ctx, call| { + check_ctx.with_tx(out_of_gas_create.into(), |mut tx_ctx, call| { let rsp = EVMModule::::tx_create(&mut tx_ctx, cbor::from_value(call.body).unwrap()) .expect("call should succeed with empty result"); @@ -627,7 +627,7 @@ fn do_test_evm_runtime() { // Test transaction call in simulate mode. CurrentStore::with_transaction(|| { ctx.with_simulation(|mut sim_ctx| { - let erc20_name = sim_ctx.with_tx(0, 0, call_name_tx.clone(), |mut tx_ctx, call| { + let erc20_name = sim_ctx.with_tx(call_name_tx.clone().into(), |mut tx_ctx, call| { let name: Vec = cbor::from_value( decode_result!( tx_ctx, @@ -651,7 +651,7 @@ fn do_test_evm_runtime() { TransactionResult::Rollback(()) // Ignore simulation results. }); - let erc20_name = ctx.with_tx(0, 0, call_name_tx.clone(), |mut tx_ctx, call| { + let erc20_name = ctx.with_tx(call_name_tx.clone().into(), |mut tx_ctx, call| { let name: Vec = cbor::from_value( decode_result!( tx_ctx, @@ -709,7 +709,7 @@ fn do_test_evm_runtime() { // Run authentication handler to simulate nonce increments. as Runtime>::Modules::authenticate_tx(&mut ctx, &call_transfer_tx).unwrap(); - let transfer_ret = ctx.with_tx(0, 0, call_transfer_tx.clone(), |mut tx_ctx, call| { + let transfer_ret = ctx.with_tx(call_transfer_tx.clone().into(), |mut tx_ctx, call| { let ret: Vec = cbor::from_value( decode_result!( tx_ctx, @@ -758,7 +758,7 @@ fn do_test_evm_runtime() { }; as Runtime>::Modules::authenticate_tx(&mut ctx, &out_of_gas_tx).unwrap(); - ctx.with_tx(0, 0, out_of_gas_tx.clone(), |mut tx_ctx, call| { + ctx.with_tx(out_of_gas_tx.clone().into(), |mut tx_ctx, call| { assert!(!decode_result!( tx_ctx, EVMModule::::tx_call(&mut tx_ctx, cbor::from_value(call.body).unwrap()) @@ -768,7 +768,7 @@ fn do_test_evm_runtime() { // CheckTx should not fail. ctx.with_child(context::Mode::CheckTx, |mut check_ctx| { - check_ctx.with_tx(0, 0, out_of_gas_tx, |mut tx_ctx, call| { + check_ctx.with_tx(out_of_gas_tx.into(), |mut tx_ctx, call| { let rsp = EVMModule::::tx_call(&mut tx_ctx, cbor::from_value(call.body).unwrap()) .expect("call should succeed with empty result"); diff --git a/runtime-sdk/src/callformat.rs b/runtime-sdk/src/callformat.rs index 7ce797d3f5..bf27bf2d60 100644 --- a/runtime-sdk/src/callformat.rs +++ b/runtime-sdk/src/callformat.rs @@ -18,6 +18,12 @@ use crate::{ }, }; +/// Maximum age of an ephemeral key in the number of epochs. +/// +/// This is half the current window as enforced by the key manager as negative results are not +/// cached and randomized queries could open the scheme to a potential DoS attack. +const MAX_EPHEMERAL_KEY_AGE: beacon::EpochTime = 5; + /// Additional metadata required by the result encoding function. pub enum Metadata { Empty, @@ -52,6 +58,18 @@ pub fn get_key_pair_id(epoch: beacon::EpochTime) -> keymanager::KeyPairId { ]) } +fn verify_epoch(ctx: &C, epoch: beacon::EpochTime) -> Result<(), Error> { + if epoch > ctx.epoch() { + return Err(Error::InvalidCallFormat(anyhow!("epoch in the future"))); + } + if epoch < ctx.epoch().saturating_sub(MAX_EPHEMERAL_KEY_AGE) { + return Err(Error::InvalidCallFormat(anyhow!( + "epoch too far in the past" + ))); + } + Ok(()) +} + /// Decode call arguments. /// /// Returns `Some((Call, Metadata))` when processing should proceed and `None` in case further @@ -124,11 +142,16 @@ pub fn decode_call_ex( }; // Get transaction key pair from the key manager. Note that only the `input_keypair` - // portion is used. In case of failure, also try with previous epoch key in case the epoch - // transition just occurred. - let (data, sk) = decrypt(ctx.epoch()) - .or_else(|_| decrypt(ctx.epoch() - 1)) - .map_err(Error::InvalidCallFormat)?; + // portion is used. + let (data, sk) = if envelope.epoch > 0 { + verify_epoch(ctx, envelope.epoch)?; + decrypt(envelope.epoch) + } else { + // In case of failure, also try with previous epoch key in case the epoch + // transition just occurred. + decrypt(ctx.epoch()).or_else(|_| decrypt(ctx.epoch() - 1)) + } + .map_err(Error::InvalidCallFormat)?; let read_only = call.read_only; let call: Call = cbor::from_slice(&data) @@ -164,8 +187,9 @@ pub fn encode_call( let key_manager = ctx.key_manager().ok_or_else(|| { Error::InvalidCallFormat(anyhow!("confidential transactions not available")) })?; + let epoch = ctx.epoch(); let runtime_keypair = key_manager - .get_or_create_ephemeral_keys(get_key_pair_id(ctx.epoch()), ctx.epoch()) + .get_or_create_ephemeral_keys(get_key_pair_id(epoch), epoch) .map_err(|err| Error::Abort(err.into()))?; let runtime_pk = runtime_keypair.input_keypair.pk; let nonce = [0u8; deoxysii::NONCE_SIZE]; @@ -176,6 +200,7 @@ pub fn encode_call( body: cbor::to_value(types::callformat::CallEnvelopeX25519DeoxysII { pk: client_keypair.0, nonce, + epoch, data: deoxysii::box_seal( &nonce, cbor::to_vec(call), diff --git a/runtime-sdk/src/context.rs b/runtime-sdk/src/context.rs index 73af095e61..001750d53d 100644 --- a/runtime-sdk/src/context.rs +++ b/runtime-sdk/src/context.rs @@ -12,14 +12,14 @@ use io_context::Context as IoContext; use slog::{self, o}; use oasis_core_runtime::{ - common::{logger::get_logger, namespace::Namespace}, + common::{crypto::hash::Hash, logger::get_logger, namespace::Namespace}, consensus, consensus::roothash, protocol::HostInfo, }; use crate::{ - crypto::random::Rng, + crypto::random::{LeafRng, RootRng}, event::{Event, EventTag, EventTags}, history, keymanager::KeyManager, @@ -316,7 +316,7 @@ pub trait Context { } /// Returns a random number generator, if it is available, with optional personalization. - fn rng(&mut self, pers: &[u8]) -> Result; + fn rng(&mut self, pers: &[u8]) -> Result; } impl<'a, 'b, C: Context> Context for std::cell::RefMut<'a, &'b mut C> { @@ -401,21 +401,49 @@ impl<'a, 'b, C: Context> Context for std::cell::RefMut<'a, &'b mut C> { self.deref_mut().with_child(mode, f) } - fn rng(&mut self, pers: &[u8]) -> Result { + fn rng(&mut self, pers: &[u8]) -> Result { self.deref_mut().rng(pers) } } +/// Decoded transaction with additional metadata. +#[derive(Clone)] +pub struct TransactionWithMeta { + /// Decoded transaction. + pub tx: transaction::Transaction, + /// Transaction size. + pub tx_size: u32, + /// Transaction index within the batch. + pub tx_index: usize, + /// Transaction hash. + pub tx_hash: Hash, +} + +impl TransactionWithMeta { + /// Create transaction with metadata for an internally generated transaction. + /// + /// Internally generated transactions have zero size, index and hash. + pub fn internal(tx: transaction::Transaction) -> Self { + Self { + tx, + tx_size: 0, + tx_index: 0, + tx_hash: Default::default(), + } + } +} + +#[cfg(any(test, feature = "test"))] +impl From for TransactionWithMeta { + fn from(tx: transaction::Transaction) -> Self { + Self::internal(tx) // For use in tests. + } +} + /// Runtime SDK batch-wide context. pub trait BatchContext: Context { /// Executes a function in a per-transaction context. - fn with_tx( - &mut self, - tx_index: usize, - tx_size: u32, - tx: transaction::Transaction, - f: F, - ) -> Rs + fn with_tx(&mut self, tx: TransactionWithMeta, f: F) -> Rs where F: FnOnce(RuntimeTxContext<'_, '_, ::Runtime>, transaction::Call) -> Rs; @@ -553,7 +581,8 @@ pub struct RuntimeBatchContext<'a, R: runtime::Runtime> { /// Per-context values. values: BTreeMap<&'static str, Box>, - rng: Option, + /// A reference to the root RNG. + rng: &'a RootRng, _runtime: PhantomData, } @@ -570,6 +599,7 @@ impl<'a, R: runtime::Runtime> RuntimeBatchContext<'a, R> { consensus_state: &'a consensus::state::ConsensusState, history: &'a dyn history::HistoryHost, epoch: consensus::beacon::EpochTime, + rng: &'a RootRng, io_ctx: Arc, max_messages: u32, ) -> Self { @@ -590,7 +620,7 @@ impl<'a, R: runtime::Runtime> RuntimeBatchContext<'a, R> { max_messages, messages: Vec::new(), values: BTreeMap::new(), - rng: Default::default(), + rng, _runtime: PhantomData, } } @@ -690,6 +720,10 @@ impl<'a, R: runtime::Runtime> Context for RuntimeBatchContext<'a, R> { F: FnOnce(RuntimeBatchContext<'_, Self::Runtime>) -> Rs, { let remaining_messages = self.remaining_messages(); + if !self.is_pre_schedule() && mode != Mode::PreScheduleTx { + // Update RNG state to include entering this subcontext. + self.rng.append_subcontext(); + } let child_ctx = RuntimeBatchContext { mode, @@ -712,32 +746,27 @@ impl<'a, R: runtime::Runtime> Context for RuntimeBatchContext<'a, R> { }, messages: Vec::new(), values: BTreeMap::new(), - rng: self.rng.as_mut().map(|rng| rng.fork(&[])), + rng: self.rng, _runtime: PhantomData, }; f(child_ctx) } - fn rng(&mut self, pers: &[u8]) -> Result { - if self.rng.is_none() { - self.rng = Some(Rng::new(self)?); - } - Ok(self.rng.as_mut().unwrap().fork(pers)) + fn rng(&mut self, pers: &[u8]) -> Result { + self.rng.fork(self, pers) } } impl<'a, R: runtime::Runtime> BatchContext for RuntimeBatchContext<'a, R> { - fn with_tx( - &mut self, - tx_index: usize, - tx_size: u32, - tx: transaction::Transaction, - f: F, - ) -> Rs + fn with_tx(&mut self, tm: TransactionWithMeta, f: F) -> Rs where F: FnOnce(RuntimeTxContext<'_, '_, ::Runtime>, transaction::Call) -> Rs, { let remaining_messages = self.remaining_messages(); + if !self.is_pre_schedule() { + // Update RNG state to include entering this transaction context. + self.rng.append_tx(tm.tx_hash); + } let tx_ctx = RuntimeTxContext { mode: self.mode, @@ -752,11 +781,11 @@ impl<'a, R: runtime::Runtime> BatchContext for RuntimeBatchContext<'a, R> { logger: self .logger .new(o!("ctx" => "transaction", "mode" => Into::<&'static str>::into(&self.mode))), - tx_index, - tx_size, - tx_auth_info: tx.auth_info, - tx_call_format: tx.call.format, - read_only: tx.call.read_only, + tx_index: tm.tx_index, + tx_size: tm.tx_size, + tx_auth_info: tm.tx.auth_info, + tx_call_format: tm.tx.call.format, + read_only: tm.tx.call.read_only, internal: self.internal, etags: BTreeMap::new(), etags_unconditional: BTreeMap::new(), @@ -764,10 +793,10 @@ impl<'a, R: runtime::Runtime> BatchContext for RuntimeBatchContext<'a, R> { messages: Vec::new(), values: &mut self.values, tx_values: BTreeMap::new(), - rng: self.rng.as_mut().map(|rng| rng.fork(&[])), + rng: self.rng, _runtime: PhantomData, }; - f(tx_ctx, tx.call) + f(tx_ctx, tm.tx.call) } fn emit_messages( @@ -830,7 +859,7 @@ pub struct RuntimeTxContext<'round, 'store, R: runtime::Runtime> { tx_values: BTreeMap<&'static str, Box>, /// The RNG associated with the context. - rng: Option, + rng: &'round RootRng, _runtime: PhantomData, } @@ -935,6 +964,10 @@ impl<'round, 'store, R: runtime::Runtime> Context for RuntimeTxContext<'round, ' F: FnOnce(RuntimeBatchContext<'_, Self::Runtime>) -> Rs, { let remaining_messages = self.remaining_messages(); + if !self.is_pre_schedule() && mode != Mode::PreScheduleTx { + // Update RNG state to include entering this subcontext. + self.rng.append_subcontext(); + } let child_ctx = RuntimeBatchContext { mode, @@ -957,17 +990,14 @@ impl<'round, 'store, R: runtime::Runtime> Context for RuntimeTxContext<'round, ' }, messages: Vec::new(), values: BTreeMap::new(), - rng: self.rng.as_mut().map(|rng| rng.fork(&[])), + rng: self.rng, _runtime: PhantomData, }; f(child_ctx) } - fn rng(&mut self, pers: &[u8]) -> Result { - if self.rng.is_none() { - self.rng = Some(Rng::new(self)?); - } - Ok(self.rng.as_mut().unwrap().fork(pers)) + fn rng(&mut self, pers: &[u8]) -> Result { + self.rng.fork(self, pers) } } @@ -1187,7 +1217,7 @@ mod test { ..Default::default() }, }; - ctx.with_tx(0, 0, tx.clone(), |mut tx_ctx, _call| { + ctx.with_tx(tx.clone().into(), |mut tx_ctx, _call| { let mut y = tx_ctx.value::("module.TestKey"); let y = y.get_mut().unwrap(); assert_eq!(*y, 42); @@ -1211,7 +1241,7 @@ mod test { let x = ctx.value::("module.TestKey").get(); assert_eq!(x, Some(&48)); - ctx.with_tx(0, 0, tx, |mut tx_ctx, _call| { + ctx.with_tx(tx.into(), |mut tx_ctx, _call| { let z = tx_ctx.value::("module.TestKey").take(); assert_eq!(z, Some(48)); @@ -1249,7 +1279,7 @@ mod test { ..Default::default() }, }; - ctx.with_tx(0, 0, tx, |mut tx_ctx, _call| { + ctx.with_tx(tx.into(), |mut tx_ctx, _call| { // Changing the type of a key should result in a panic. tx_ctx.value::>("module.TestKey").get(); }); @@ -1291,7 +1321,7 @@ mod test { let max_messages = mock.max_messages; let mut ctx = mock.create_ctx(); - ctx.with_tx(0, 0, mock::transaction(), |mut tx_ctx, _call| { + ctx.with_tx(mock::transaction().into(), |mut tx_ctx, _call| { for i in 0..max_messages { assert_eq!(tx_ctx.remaining_messages(), max_messages - i); @@ -1359,7 +1389,7 @@ mod test { assert_eq!(ctx.remaining_messages(), 0); // Also in transaction contexts. - ctx.with_tx(0, 0, mock::transaction(), |mut tx_ctx, _call| { + ctx.with_tx(mock::transaction().into(), |mut tx_ctx, _call| { tx_ctx .emit_message(messages[0].0.clone(), messages[0].1.clone()) .expect_err("emitting a message should fail"); @@ -1388,7 +1418,7 @@ mod test { MessageEventHookInvocation::new("test".to_string(), ""), )]; - ctx.with_tx(0, 0, mock::transaction(), |mut tx_ctx, _call| { + ctx.with_tx(mock::transaction().into(), |mut tx_ctx, _call| { tx_ctx.limit_max_messages(1).unwrap(); tx_ctx.with_child(tx_ctx.mode(), |mut child_ctx| { @@ -1407,9 +1437,17 @@ mod test { fn test_tx_ctx_metadata() { let mut mock = Mock::default(); let mut ctx = mock.create_ctx(); - ctx.with_tx(42, 888, mock::transaction(), |tx_ctx, _call| { - assert_eq!(tx_ctx.tx_index(), 42); - assert_eq!(tx_ctx.tx_size(), 888); - }); + ctx.with_tx( + TransactionWithMeta { + tx: mock::transaction(), + tx_size: 888, + tx_index: 42, + tx_hash: Default::default(), + }, + |tx_ctx, _call| { + assert_eq!(tx_ctx.tx_index(), 42); + assert_eq!(tx_ctx.tx_size(), 888); + }, + ); } } diff --git a/runtime-sdk/src/crypto/random.rs b/runtime-sdk/src/crypto/random.rs index db57781df0..e562b95301 100644 --- a/runtime-sdk/src/crypto/random.rs +++ b/runtime-sdk/src/crypto/random.rs @@ -1,18 +1,43 @@ +//! Random number generator based on root VRF key and Merlin transcripts. +use std::cell::RefCell; + use merlin::{Transcript, TranscriptRng}; -use rand_core::{CryptoRng, RngCore}; -use schnorrkel::keys::{ExpansionMode, MiniSecretKey}; +use rand_core::{CryptoRng, OsRng, RngCore}; +use schnorrkel::keys::{ExpansionMode, Keypair, MiniSecretKey}; + +use oasis_core_runtime::common::crypto::hash::Hash; use crate::{context::Context, dispatcher, keymanager::KeyManagerError, modules::core::Error}; -pub struct Rng { +/// RNG domain separation context. +const RNG_CONTEXT: &[u8] = b"oasis-runtime-sdk/crypto: rng v1"; +/// Per-block root VRF key domain separation context. +const VRF_KEY_CONTEXT: &[u8] = b"oasis-runtime-sdk/crypto: root vrf key v1"; + +/// A root RNG that can be used to derive domain-separated leaf RNGs. +pub struct RootRng { + inner: RefCell, +} + +struct Inner { + /// Merlin transcript for initializing the RNG. transcript: Transcript, - rng: TranscriptRng, + /// A transcript-based RNG (when initialized). + rng: Option, } -impl Rng { - /// Creates a new RNG, potentially seeded using the provided `ctx`. - /// This should only be called once per top-level context. - pub fn new(ctx: &C) -> Result { +impl RootRng { + /// Create a new root RNG. + pub fn new() -> Self { + Self { + inner: RefCell::new(Inner { + transcript: Transcript::new(RNG_CONTEXT), + rng: None, + }), + } + } + + fn derive_root_vrf_key(ctx: &C) -> Result { let km = ctx .key_manager() .ok_or(Error::Abort(dispatcher::Error::KeyManagerFailure( @@ -20,12 +45,13 @@ impl Rng { )))?; let round_header_hash = ctx.runtime_header().encoded_hash(); let key_id = crate::keymanager::get_key_pair_id([ - b"oasis-runtime-sdk/crypto: random_bytes".as_slice(), + VRF_KEY_CONTEXT, &[ctx.mode() as u8], round_header_hash.as_ref(), ]); + // Use previous epoch's ephemeral key to ensure it is available. let km_kp = km - .get_or_create_ephemeral_keys(key_id, ctx.epoch()) + .get_or_create_ephemeral_keys(key_id, ctx.epoch().saturating_sub(1)) .map_err(|err| Error::Abort(dispatcher::Error::KeyManagerFailure(err)))? .input_keypair; // The KM returns an ed25519 key, but it needs to be in "expanded" form to use with @@ -37,37 +63,269 @@ impl Rng { )) })? .expand_to_keypair(ExpansionMode::Uniform); - let mut transcript = Transcript::new(b"MakeRNG"); - let rng = kp.vrf_sign(&mut transcript).0.make_merlin_rng(&[]); - Ok(Self { transcript, rng }) + + Ok(kp) + } + + /// Append local entropy to the root RNG. + /// + /// # Non-determinism + /// + /// Using this method will result in the RNG being non-deterministic. + pub fn append_local_entropy(&self) { + let mut bytes = [0u8; 32]; + OsRng.fill_bytes(&mut bytes); + + let mut inner = self.inner.borrow_mut(); + inner.transcript.append_message(b"local-rng", &bytes); + } + + /// Append an observed transaction hash to RNG transcript. + pub fn append_tx(&self, tx_hash: Hash) { + let mut inner = self.inner.borrow_mut(); + inner.transcript.append_message(b"tx", tx_hash.as_ref()); + } + + /// Append an observed subcontext to RNG transcript. + pub fn append_subcontext(&self) { + let mut inner = self.inner.borrow_mut(); + inner.transcript.append_message(b"subctx", &[]); + } + + /// Create an independent leaf RNG using this RNG as its parent. + pub fn fork(&self, ctx: &C, pers: &[u8]) -> Result { + let mut inner = self.inner.borrow_mut(); + + // Ensure the RNG is initialized and initialize it if not. + if inner.rng.is_none() { + // Derive the root VRF key for the current block. + let root_vrf_key = Self::derive_root_vrf_key(ctx)?; + + // Initialize the root RNG. + let rng = root_vrf_key + .vrf_create_hash(&mut inner.transcript) + .make_merlin_rng(&[]); + inner.rng = Some(rng); + } + + // Generate the leaf RNG. + inner.transcript.append_message(b"fork", pers); + + let rng_builder = inner.transcript.build_rng(); + let parent_rng = inner.rng.as_mut().expect("rng must be initialized"); + let rng = rng_builder.finalize(parent_rng); + + Ok(LeafRng(rng)) } +} - /// Create an independent RNG using this RNG as its parent. - pub fn fork(&mut self, pers: &[u8]) -> Self { - let mut transcript = self.transcript.clone(); - transcript.append_message(b"fork", &[]); - transcript.append_message(b"pers", pers); - let rng = transcript.build_rng().finalize(&mut self.rng); - Self { transcript, rng } +impl Default for RootRng { + fn default() -> Self { + Self::new() } } -impl RngCore for Rng { +/// A leaf RNG. +pub struct LeafRng(TranscriptRng); + +impl RngCore for LeafRng { fn next_u32(&mut self) -> u32 { - self.rng.next_u32() + self.0.next_u32() } fn next_u64(&mut self) -> u64 { - self.rng.next_u64() + self.0.next_u64() } fn fill_bytes(&mut self, dest: &mut [u8]) { - self.rng.fill_bytes(dest) + self.0.fill_bytes(dest) } fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> { - self.rng.try_fill_bytes(dest) + self.0.try_fill_bytes(dest) } } -impl CryptoRng for Rng {} +impl CryptoRng for LeafRng {} + +#[cfg(test)] +mod test { + use super::*; + + use crate::{context::Mode, testing::mock}; + + #[test] + fn test_rng_basic() { + let mut mock = mock::Mock::default(); + let ctx = mock.create_ctx_for_runtime::(Mode::ExecuteTx, true); + + // Create first root RNG. + let root_rng = RootRng::new(); + + let mut leaf_rng = root_rng.fork(&ctx, &[]).expect("rng fork should work"); + let mut bytes1 = [0u8; 32]; + leaf_rng.fill_bytes(&mut bytes1); + + let mut leaf_rng = root_rng.fork(&ctx, &[]).expect("rng fork should work"); + let mut bytes1_1 = [0u8; 32]; + leaf_rng.fill_bytes(&mut bytes1_1); + + assert_ne!(bytes1, bytes1_1, "rng should apply domain separation"); + + // Create second root RNG using the same context so the ephemeral key is shared. + let root_rng = RootRng::new(); + + let mut leaf_rng = root_rng.fork(&ctx, &[]).expect("rng fork should work"); + let mut bytes2 = [0u8; 32]; + leaf_rng.fill_bytes(&mut bytes2); + + assert_eq!(bytes1, bytes2, "rng should be deterministic"); + + let mut leaf_rng = root_rng.fork(&ctx, &[]).expect("rng fork should work"); + let mut bytes2_1 = [0u8; 32]; + leaf_rng.fill_bytes(&mut bytes2_1); + + assert_ne!(bytes2, bytes2_1, "rng should apply domain separation"); + assert_eq!(bytes1_1, bytes2_1, "rng should be deterministic"); + + // Create third root RNG using the same context, but with different personalization. + let root_rng = RootRng::new(); + + let mut leaf_rng = root_rng + .fork(&ctx, b"domsep") + .expect("rng fork should work"); + let mut bytes3 = [0u8; 32]; + leaf_rng.fill_bytes(&mut bytes3); + + assert_ne!(bytes2, bytes3, "rng should apply domain separation"); + + // Create another root RNG using the same context, but with different history. + let root_rng = RootRng::new(); + root_rng + .append_tx("0000000000000000000000000000000000000000000000000000000000000001".into()); + + let mut leaf_rng = root_rng.fork(&ctx, &[]).expect("rng fork should work"); + let mut bytes4 = [0u8; 32]; + leaf_rng.fill_bytes(&mut bytes4); + + assert_ne!(bytes2, bytes4, "rng should apply domain separation"); + + // Create another root RNG using the same context, but with different history. + let root_rng = RootRng::new(); + root_rng + .append_tx("0000000000000000000000000000000000000000000000000000000000000002".into()); + + let mut leaf_rng = root_rng.fork(&ctx, &[]).expect("rng fork should work"); + let mut bytes5 = [0u8; 32]; + leaf_rng.fill_bytes(&mut bytes5); + + assert_ne!(bytes4, bytes5, "rng should apply domain separation"); + + // Create another root RNG using the same context, but with same history as four. + let root_rng = RootRng::new(); + root_rng + .append_tx("0000000000000000000000000000000000000000000000000000000000000001".into()); + + let mut leaf_rng = root_rng.fork(&ctx, &[]).expect("rng fork should work"); + let mut bytes6 = [0u8; 32]; + leaf_rng.fill_bytes(&mut bytes6); + + assert_eq!(bytes4, bytes6, "rng should be deterministic"); + + // Create another root RNG using the same context, but with different history. + let root_rng = RootRng::new(); + root_rng + .append_tx("0000000000000000000000000000000000000000000000000000000000000001".into()); + root_rng + .append_tx("0000000000000000000000000000000000000000000000000000000000000002".into()); + + let mut leaf_rng = root_rng.fork(&ctx, &[]).expect("rng fork should work"); + let mut bytes7 = [0u8; 32]; + leaf_rng.fill_bytes(&mut bytes7); + + assert_ne!(bytes4, bytes7, "rng should apply domain separation"); + + // Create another root RNG using the same context, but with different init point. + let root_rng = RootRng::new(); + root_rng + .append_tx("0000000000000000000000000000000000000000000000000000000000000001".into()); + let _ = root_rng.fork(&ctx, &[]).expect("rng fork should work"); // Force init. + root_rng + .append_tx("0000000000000000000000000000000000000000000000000000000000000002".into()); + + let mut leaf_rng = root_rng.fork(&ctx, &[]).expect("rng fork should work"); + let mut bytes8 = [0u8; 32]; + leaf_rng.fill_bytes(&mut bytes8); + + assert_ne!(bytes7, bytes8, "rng should apply domain separation"); + assert_ne!(bytes6, bytes8, "rng should apply domain separation"); + } + + #[test] + fn test_rng_fail_nonconfidential() { + let mut mock = mock::Mock::default(); + let ctx = mock.create_ctx_for_runtime::(Mode::ExecuteTx, false); + + let root_rng = RootRng::new(); + assert!( + root_rng.fork(&ctx, &[]).is_err(), + "rng fork should fail on non-confidential runtimes" + ); + } + + #[test] + fn test_rng_local_entropy() { + let mut mock = mock::Mock::default(); + let ctx = mock.create_ctx_for_runtime::(Mode::ExecuteTx, true); + + // Create first root RNG. + let root_rng = RootRng::new(); + + let mut leaf_rng = root_rng.fork(&ctx, &[]).expect("rng fork should work"); + let mut bytes1 = [0u8; 32]; + leaf_rng.fill_bytes(&mut bytes1); + + // Create second root RNG using the same context, but mix in local entropy. + let root_rng = RootRng::new(); + root_rng.append_local_entropy(); + + let mut leaf_rng = root_rng.fork(&ctx, &[]).expect("rng fork should work"); + let mut bytes2 = [0u8; 32]; + leaf_rng.fill_bytes(&mut bytes2); + + assert_ne!(bytes1, bytes2, "rng should apply domain separation"); + } + + #[test] + fn test_rng_parent_fork_propagation() { + let mut mock = mock::Mock::default(); + let ctx = mock.create_ctx_for_runtime::(Mode::ExecuteTx, true); + + // Create first root RNG. + let root_rng = RootRng::new(); + + let mut leaf_rng = root_rng.fork(&ctx, b"a").expect("rng fork should work"); + let mut bytes1 = [0u8; 32]; + leaf_rng.fill_bytes(&mut bytes1); + + let mut leaf_rng = root_rng.fork(&ctx, b"a").expect("rng fork should work"); + let mut bytes1_1 = [0u8; 32]; + leaf_rng.fill_bytes(&mut bytes1_1); + + // Create second root RNG. + let root_rng = RootRng::new(); + + let mut leaf_rng = root_rng.fork(&ctx, b"b").expect("rng fork should work"); + let mut bytes2 = [0u8; 32]; + leaf_rng.fill_bytes(&mut bytes2); + + let mut leaf_rng = root_rng.fork(&ctx, b"a").expect("rng fork should work"); + let mut bytes2_1 = [0u8; 32]; + leaf_rng.fill_bytes(&mut bytes2_1); + + assert_ne!( + bytes1_1, bytes2_1, + "forks should propagate domain separator to parent" + ); + } +} diff --git a/runtime-sdk/src/dispatcher.rs b/runtime-sdk/src/dispatcher.rs index 43c5c1cd12..a2ec503c51 100644 --- a/runtime-sdk/src/dispatcher.rs +++ b/runtime-sdk/src/dispatcher.rs @@ -26,7 +26,8 @@ use oasis_core_runtime::{ use crate::{ callformat, - context::{BatchContext, Context, Mode, RuntimeBatchContext, TxContext}, + context::{BatchContext, Context, Mode, RuntimeBatchContext, TransactionWithMeta, TxContext}, + crypto::random::RootRng, error::{Error as _, RuntimeError}, event::IntoTags, keymanager::{KeyManagerClient, KeyManagerError}, @@ -114,6 +115,8 @@ pub struct DispatchOptions<'a> { pub tx_size: u32, /// Transaction index within the batch. pub tx_index: usize, + /// Transaction hash. + pub tx_hash: Hash, /// Optionally only allow methods for which the provided authorizer closure returns true. pub method_authorizer: Option<&'a dyn Fn(&str) -> bool>, /// Optionally skip authentication. @@ -270,52 +273,61 @@ impl Dispatcher { let is_read_only = tx.call.read_only; let (result, messages) = CurrentStore::with_transaction(|| { - ctx.with_tx(opts.tx_index, opts.tx_size, tx, |mut ctx, call| { - let (result, call_format_metadata) = Self::dispatch_tx_call(&mut ctx, call, opts); - if !result.is_success() || is_read_only { - // Retrieve unconditional events by doing an explicit rollback. - let etags = ctx.rollback(); - - return TransactionResult::Rollback(( - DispatchResult::new(result, etags.into_tags(), call_format_metadata), - Vec::new(), - )); - } + ctx.with_tx( + TransactionWithMeta { + tx, + tx_size: opts.tx_size, + tx_index: opts.tx_index, + tx_hash: opts.tx_hash, + }, + |mut ctx, call| { + let (result, call_format_metadata) = + Self::dispatch_tx_call(&mut ctx, call, opts); + if !result.is_success() || is_read_only { + // Retrieve unconditional events by doing an explicit rollback. + let etags = ctx.rollback(); + + return TransactionResult::Rollback(( + DispatchResult::new(result, etags.into_tags(), call_format_metadata), + Vec::new(), + )); + } - // Load priority. - let priority = R::Core::take_priority(&mut ctx); - // Load sender metadata. - let sender_metadata = R::Core::take_sender_meta(&mut ctx); - - if ctx.is_check_only() { - // Rollback state during checks. - ctx.rollback(); - - TransactionResult::Rollback(( - DispatchResult { - result, - tags: Vec::new(), - priority, - sender_metadata, - call_format_metadata, - }, - Vec::new(), - )) - } else { - // Commit store and return emitted tags and messages. - let state = ctx.commit(); - TransactionResult::Commit(( - DispatchResult { - result, - tags: state.events.into_tags(), - priority, - sender_metadata, - call_format_metadata, - }, - state.messages, - )) - } - }) + // Load priority. + let priority = R::Core::take_priority(&mut ctx); + // Load sender metadata. + let sender_metadata = R::Core::take_sender_meta(&mut ctx); + + if ctx.is_check_only() { + // Rollback state during checks. + ctx.rollback(); + + TransactionResult::Rollback(( + DispatchResult { + result, + tags: Vec::new(), + priority, + sender_metadata, + call_format_metadata, + }, + Vec::new(), + )) + } else { + // Commit store and return emitted tags and messages. + let state = ctx.commit(); + TransactionResult::Commit(( + DispatchResult { + result, + tags: state.events.into_tags(), + priority, + sender_metadata, + call_format_metadata, + }, + state.messages, + )) + } + }, + ) }); // Run after dispatch hooks. @@ -410,6 +422,7 @@ impl Dispatcher { pub fn execute_tx( ctx: &mut C, tx_size: u32, + tx_hash: Hash, tx: Transaction, tx_index: usize, ) -> Result { @@ -419,6 +432,7 @@ impl Dispatcher { &DispatchOptions { tx_size, tx_index, + tx_hash, ..Default::default() }, )?; @@ -536,14 +550,11 @@ impl Dispatcher { // NOTE: We are explicitly allowing private key operations during execution. .map(|mgr| mgr.with_private_context(rt_ctx.io_ctx.clone())); let history = self.consensus_verifier.clone(); + let rng = RootRng::new(); let root = storage::MKVSStore::new(rt_ctx.io_ctx.clone(), &mut rt_ctx.runtime_state); let mut ctx = RuntimeBatchContext::<'_, R>::new( - if rt_ctx.check_only { - Mode::CheckTx - } else { - Mode::ExecuteTx - }, + Mode::ExecuteTx, &self.host_info, key_manager, rt_ctx.header, @@ -551,6 +562,7 @@ impl Dispatcher { &rt_ctx.consensus_state, &history, rt_ctx.epoch, + &rng, rt_ctx.io_ctx.clone(), rt_ctx.max_messages, ); @@ -605,6 +617,7 @@ impl transaction::dispatcher::Dispatcher for Dispatche let tx_size = tx.len().try_into().map_err(|_| { Error::MalformedTransactionInBatch(anyhow!("transaction too large")) })?; + let tx_hash = Hash::digest_bytes(tx); // It is an error to include a malformed transaction in a batch. So instead of only // reporting a failed execution result, we fail the whole batch. This will make the compute // node vote for failure and the round will fail. @@ -612,7 +625,7 @@ impl transaction::dispatcher::Dispatcher for Dispatche // Correct proposers should only include transactions which have passed check_tx. let tx = Self::decode_tx(ctx, tx) .map_err(|err| Error::MalformedTransactionInBatch(err.into()))?; - txs.push((tx_size, tx.clone())); + txs.push((tx_size, tx_hash, tx.clone())); if prefetch_enabled { Self::prefetch_tx(&mut prefixes, tx)?; @@ -626,8 +639,8 @@ impl transaction::dispatcher::Dispatcher for Dispatche // Execute the batch. let mut results = Vec::with_capacity(batch.len()); - for (index, (tx_size, tx)) in txs.into_iter().enumerate() { - results.push(Self::execute_tx(ctx, tx_size, tx, index)?); + for (index, (tx_size, tx_hash, tx)) in txs.into_iter().enumerate() { + results.push(Self::execute_tx(ctx, tx_size, tx_hash, tx, index)?); } Ok(results) @@ -670,12 +683,13 @@ impl transaction::dispatcher::Dispatcher for Dispatche } // Decode transaction. + let tx_hash = Hash::digest_bytes(&raw_tx); let tx = match Self::decode_tx(ctx, &raw_tx) { Ok(tx) => tx, Err(_) => { // Transaction is malformed, make sure it gets removed from the // queue and don't include it in a block. - tx_reject_hashes.push(Hash::digest_bytes(&raw_tx)); + tx_reject_hashes.push(tx_hash); continue; } }; @@ -717,6 +731,7 @@ impl transaction::dispatcher::Dispatcher for Dispatche &DispatchOptions { tx_size, tx_index, + tx_hash, skip_authentication: true, // Already done. ..Default::default() }, @@ -729,7 +744,7 @@ impl transaction::dispatcher::Dispatcher for Dispatche } // Skip and reject the transaction. - tx_reject_hashes.push(Hash::digest_bytes(&raw_tx)); + tx_reject_hashes.push(tx_hash); Ok(true) }, ); @@ -741,7 +756,7 @@ impl transaction::dispatcher::Dispatcher for Dispatche } new_batch.push(raw_tx); - results.push(Self::execute_tx(ctx, tx_size, tx, tx_index)?); + results.push(Self::execute_tx(ctx, tx_size, tx_hash, tx, tx_index)?); } // If there's more room in the block and we got the maximum number of @@ -789,14 +804,11 @@ impl transaction::dispatcher::Dispatcher for Dispatche .as_ref() .map(|mgr| mgr.with_context(rt_ctx.io_ctx.clone())); let history = self.consensus_verifier.clone(); + let rng = RootRng::new(); let root = storage::MKVSStore::new(rt_ctx.io_ctx.clone(), &mut rt_ctx.runtime_state); let mut ctx = RuntimeBatchContext::<'_, R>::new( - if rt_ctx.check_only { - Mode::CheckTx - } else { - Mode::ExecuteTx - }, + Mode::CheckTx, &self.host_info, key_manager, rt_ctx.header, @@ -804,6 +816,7 @@ impl transaction::dispatcher::Dispatcher for Dispatche &rt_ctx.consensus_state, &history, rt_ctx.epoch, + &rng, rt_ctx.io_ctx.clone(), rt_ctx.max_messages, ); @@ -886,16 +899,17 @@ impl transaction::dispatcher::Dispatcher for Dispatche } }); + // Initialize the root RNG. For queries which don't need to be deterministic as they are + // node-local we mix in local (private) entropy. + let rng = RootRng::new(); + rng.append_local_entropy(); + // Prepare dispatch context. let history = self.consensus_verifier.clone(); let root = storage::MKVSStore::new(rt_ctx.io_ctx.clone(), &mut rt_ctx.runtime_state); let mut ctx = RuntimeBatchContext::<'_, R>::new( - if rt_ctx.check_only { - Mode::CheckTx - } else { - Mode::ExecuteTx - }, + Mode::CheckTx, &self.host_info, key_manager, rt_ctx.header, @@ -903,6 +917,7 @@ impl transaction::dispatcher::Dispatcher for Dispatche &rt_ctx.consensus_state, &history, rt_ctx.epoch, + &rng, rt_ctx.io_ctx.clone(), rt_ctx.max_messages, ); diff --git a/runtime-sdk/src/modules/accounts/test.rs b/runtime-sdk/src/modules/accounts/test.rs index 74a64f2f1e..c128f0ed12 100644 --- a/runtime-sdk/src/modules/accounts/test.rs +++ b/runtime-sdk/src/modules/accounts/test.rs @@ -238,7 +238,7 @@ fn test_api_tx_transfer_disabled() { }; // Try to transfer. - ctx.with_tx(0, 0, tx, |mut tx_ctx, call| { + ctx.with_tx(tx.into(), |mut tx_ctx, call| { assert!( matches!( Accounts::tx_transfer(&mut tx_ctx, cbor::from_value(call.body).unwrap()), @@ -281,7 +281,7 @@ fn test_prefetch() { auth_info: auth_info.clone(), }; // Transfer tokens from one account to the other and check balances. - ctx.with_tx(0, 0, tx, |mut _tx_ctx, call| { + ctx.with_tx(tx.into(), |mut _tx_ctx, call| { let mut prefixes = BTreeSet::new(); let result = Accounts::prefetch(&mut prefixes, &call.method, call.body, &auth_info) .ok_or(anyhow!("dispatch failure")) @@ -337,7 +337,7 @@ fn test_api_transfer() { init_accounts(&mut ctx); // Transfer tokens from one account to the other and check balances. - ctx.with_tx(0, 0, mock::transaction(), |mut tx_ctx, _call| { + ctx.with_tx(mock::transaction().into(), |mut tx_ctx, _call| { Accounts::transfer( &mut tx_ctx, keys::alice::address(), @@ -489,7 +489,7 @@ fn test_tx_transfer() { }; // Transfer tokens from one account to the other and check balances. - ctx.with_tx(0, 0, tx, |mut tx_ctx, call| { + ctx.with_tx(tx.into(), |mut tx_ctx, call| { Accounts::tx_transfer(&mut tx_ctx, cbor::from_value(call.body).unwrap()) .expect("transfer should succeed"); @@ -565,7 +565,7 @@ fn test_fee_disbursement() { // Authenticate transaction, fees should be moved to accumulator. Accounts::authenticate_tx(&mut ctx, &tx).expect("transaction authentication should succeed"); - ctx.with_tx(0, 0, tx, |mut tx_ctx, _call| { + ctx.with_tx(tx.into(), |mut tx_ctx, _call| { // Run after call tx handler. Accounts::after_handle_call( &mut tx_ctx, @@ -685,7 +685,7 @@ fn test_query_addresses() { Accounts::init(&mut ctx, gen); - ctx.with_tx(0, 0, mock::transaction(), |mut tx_ctx, _call| { + ctx.with_tx(mock::transaction().into(), |mut tx_ctx, _call| { let accs = Accounts::query_addresses(&mut tx_ctx, AddressesQuery { denomination: d1 }) .expect("query accounts should succeed"); assert_eq!(accs.len(), 2, "there should be two addresses"); @@ -1006,7 +1006,7 @@ fn test_fee_acc() { init_accounts(&mut ctx); // Check that Accounts::{charge,return}_tx_fee work. - ctx.with_tx(0, 0, mock::transaction(), |mut tx_ctx, _call| { + ctx.with_tx(mock::transaction().into(), |mut tx_ctx, _call| { Accounts::charge_tx_fee( &mut tx_ctx, keys::alice::address(), @@ -1041,7 +1041,7 @@ fn test_fee_acc_sim() { // Check that Accounts::{charge,return}_tx_fee don't do // anything in simulation mode. ctx.with_simulation(|mut sctx| { - sctx.with_tx(0, 0, mock::transaction(), |mut tx_ctx, _call| { + sctx.with_tx(mock::transaction().into(), |mut tx_ctx, _call| { Accounts::charge_tx_fee( &mut tx_ctx, keys::alice::address(), diff --git a/runtime-sdk/src/modules/consensus/test.rs b/runtime-sdk/src/modules/consensus/test.rs index 36deb75f5c..34cdf62614 100644 --- a/runtime-sdk/src/modules/consensus/test.rs +++ b/runtime-sdk/src/modules/consensus/test.rs @@ -26,7 +26,7 @@ fn test_api_transfer_invalid_denomination() { let mut mock = mock::Mock::default(); let mut ctx = mock.create_ctx(); - ctx.with_tx(0, 0, mock::transaction(), |mut tx_ctx, _call| { + ctx.with_tx(mock::transaction().into(), |mut tx_ctx, _call| { let hook_name = "test_event_handler"; let amount = BaseUnits::new(1_000, Denomination::NATIVE); @@ -45,7 +45,7 @@ fn test_api_transfer() { let mut mock = mock::Mock::default(); let mut ctx = mock.create_ctx(); - ctx.with_tx(0, 0, mock::transaction(), |mut tx_ctx, _call| { + ctx.with_tx(mock::transaction().into(), |mut tx_ctx, _call| { let hook_name = "test_event_handler"; let amount = BaseUnits::new(1_000, Denomination::from_str("TEST").unwrap()); Consensus::transfer( @@ -90,7 +90,7 @@ fn test_api_transfer_scaling_unrepresentable() { ..Default::default() }); - ctx.with_tx(0, 0, mock::transaction(), |mut tx_ctx, _call| { + ctx.with_tx(mock::transaction().into(), |mut tx_ctx, _call| { let hook_name = "test_event_handler"; // Amount is not representable as it must be in multiples of 1000. let amount = BaseUnits::new(500, Denomination::from_str("TEST").unwrap()); @@ -115,7 +115,7 @@ fn test_api_transfer_scaling() { ..Default::default() }); - ctx.with_tx(0, 0, mock::transaction(), |mut tx_ctx, _call| { + ctx.with_tx(mock::transaction().into(), |mut tx_ctx, _call| { let hook_name = "test_event_handler"; let amount = BaseUnits::new(1_000, Denomination::from_str("TEST").unwrap()); Consensus::transfer( @@ -156,7 +156,7 @@ fn test_api_withdraw() { let mut mock = mock::Mock::default(); let mut ctx = mock.create_ctx(); - ctx.with_tx(0, 0, mock::transaction(), |mut tx_ctx, _call| { + ctx.with_tx(mock::transaction().into(), |mut tx_ctx, _call| { let hook_name = "test_event_handler"; let amount = BaseUnits::new(1_000, Denomination::from_str("TEST").unwrap()); Consensus::withdraw( @@ -201,7 +201,7 @@ fn test_api_withdraw_scaling() { ..Default::default() }); - ctx.with_tx(0, 0, mock::transaction(), |mut tx_ctx, _call| { + ctx.with_tx(mock::transaction().into(), |mut tx_ctx, _call| { let hook_name = "test_event_handler"; let amount = BaseUnits::new(1_000, Denomination::from_str("TEST").unwrap()); Consensus::withdraw( @@ -241,7 +241,7 @@ fn test_api_escrow() { let mut mock = mock::Mock::default(); let mut ctx = mock.create_ctx(); - ctx.with_tx(0, 0, mock::transaction(), |mut tx_ctx, _call| { + ctx.with_tx(mock::transaction().into(), |mut tx_ctx, _call| { let hook_name = "test_event_handler"; let amount = BaseUnits::new(1_000, Denomination::from_str("TEST").unwrap()); Consensus::escrow( @@ -286,7 +286,7 @@ fn test_api_escrow_scaling() { ..Default::default() }); - ctx.with_tx(0, 0, mock::transaction(), |mut tx_ctx, _call| { + ctx.with_tx(mock::transaction().into(), |mut tx_ctx, _call| { let hook_name = "test_event_handler"; let amount = BaseUnits::new(1_000, Denomination::from_str("TEST").unwrap()); Consensus::escrow( @@ -331,7 +331,7 @@ fn test_api_reclaim_escrow() { ..Default::default() }); - ctx.with_tx(0, 0, mock::transaction(), |mut tx_ctx, _call| { + ctx.with_tx(mock::transaction().into(), |mut tx_ctx, _call| { let hook_name = "test_event_handler"; let amount = 1_000u128; Consensus::reclaim_escrow( diff --git a/runtime-sdk/src/modules/consensus_accounts/test.rs b/runtime-sdk/src/modules/consensus_accounts/test.rs index d46637ff99..e9d13a325d 100644 --- a/runtime-sdk/src/modules/consensus_accounts/test.rs +++ b/runtime-sdk/src/modules/consensus_accounts/test.rs @@ -115,7 +115,7 @@ fn test_api_deposit_invalid_denomination() { }, }; - ctx.with_tx(0, 0, tx, |mut tx_ctx, call| { + ctx.with_tx(tx.into(), |mut tx_ctx, call| { let result = Module::::tx_deposit( &mut tx_ctx, cbor::from_value(call.body).unwrap(), @@ -161,7 +161,7 @@ fn test_api_deposit_incompatible_signer() { }, }; - ctx.with_tx(0, 0, tx, |mut tx_ctx, call| { + ctx.with_tx(tx.into(), |mut tx_ctx, call| { let result = Module::::tx_deposit( &mut tx_ctx, cbor::from_value(call.body).unwrap(), @@ -209,7 +209,7 @@ fn test_api_deposit() { }, }; - let hook = ctx.with_tx(0, 0, tx, |mut tx_ctx, call| { + let hook = ctx.with_tx(tx.into(), |mut tx_ctx, call| { Module::::tx_deposit( &mut tx_ctx, cbor::from_value(call.body).unwrap(), @@ -321,7 +321,7 @@ fn test_api_withdraw_invalid_denomination() { }, }; - ctx.with_tx(0, 0, tx, |mut tx_ctx, call| { + ctx.with_tx(tx.into(), |mut tx_ctx, call| { let result = Module::::tx_withdraw( &mut tx_ctx, cbor::from_value(call.body).unwrap(), @@ -367,7 +367,7 @@ fn test_api_withdraw_insufficient_balance() { }, }; - ctx.with_tx(0, 0, tx, |mut tx_ctx, call| { + ctx.with_tx(tx.into(), |mut tx_ctx, call| { let result = Module::::tx_withdraw( &mut tx_ctx, cbor::from_value(call.body).unwrap(), @@ -408,7 +408,7 @@ fn test_api_withdraw_incompatible_signer() { }, }; - ctx.with_tx(0, 0, tx, |mut tx_ctx, call| { + ctx.with_tx(tx.into(), |mut tx_ctx, call| { let result = Module::::tx_withdraw( &mut tx_ctx, cbor::from_value(call.body).unwrap(), @@ -454,7 +454,7 @@ fn test_api_withdraw(signer_sigspec: SignatureAddressSpec) { }, }; - let hook = ctx.with_tx(0, 0, tx, |mut tx_ctx, call| { + let hook = ctx.with_tx(tx.into(), |mut tx_ctx, call| { Module::::tx_withdraw( &mut tx_ctx, cbor::from_value(call.body).unwrap(), @@ -586,7 +586,7 @@ fn test_api_withdraw_handler_failure() { }, }; - let hook = ctx.with_tx(0, 0, tx, |mut tx_ctx, call| { + let hook = ctx.with_tx(tx.into(), |mut tx_ctx, call| { Module::::tx_withdraw( &mut tx_ctx, cbor::from_value(call.body).unwrap(), @@ -738,7 +738,7 @@ fn perform_delegation(ctx: &mut C, success: bool) -> u64 { }, }; - let hook = ctx.with_tx(0, 0, tx, |mut tx_ctx, call| { + let hook = ctx.with_tx(tx.into(), |mut tx_ctx, call| { Module::::tx_delegate( &mut tx_ctx, cbor::from_value(call.body).unwrap(), @@ -910,7 +910,7 @@ fn test_api_delegate_insufficient_balance() { }, }; - ctx.with_tx(0, 0, tx, |mut tx_ctx, call| { + ctx.with_tx(tx.into(), |mut tx_ctx, call| { let result = Module::::tx_delegate( &mut tx_ctx, cbor::from_value(call.body).unwrap(), @@ -986,7 +986,7 @@ fn perform_undelegation( }, }; - let hook = ctx.with_tx(0, 0, tx, |mut tx_ctx, call| { + let hook = ctx.with_tx(tx.into(), |mut tx_ctx, call| { Module::::tx_undelegate( &mut tx_ctx, cbor::from_value(call.body).unwrap(), @@ -1265,7 +1265,7 @@ fn test_api_undelegate_insufficient_balance() { }, }; - ctx.with_tx(0, 0, tx, |mut tx_ctx, call| { + ctx.with_tx(tx.into(), |mut tx_ctx, call| { let result = Module::::tx_undelegate( &mut tx_ctx, cbor::from_value(call.body).unwrap(), @@ -1456,7 +1456,7 @@ fn test_prefetch() { auth_info: auth_info.clone(), }; // Withdraw should result in one prefix getting prefetched. - ctx.with_tx(0, 0, tx, |mut _tx_ctx, call| { + ctx.with_tx(tx.into(), |mut _tx_ctx, call| { let mut prefixes = BTreeSet::new(); let result = Module::::prefetch( &mut prefixes, @@ -1488,7 +1488,7 @@ fn test_prefetch() { auth_info: auth_info.clone(), }; // Deposit should result in zero prefixes. - ctx.with_tx(0, 0, tx, |mut _tx_ctx, call| { + ctx.with_tx(tx.into(), |mut _tx_ctx, call| { let mut prefixes = BTreeSet::new(); let result = Module::::prefetch( &mut prefixes, diff --git a/runtime-sdk/src/modules/core/mod.rs b/runtime-sdk/src/modules/core/mod.rs index 0c13ffbe02..cd5e3d8905 100644 --- a/runtime-sdk/src/modules/core/mod.rs +++ b/runtime-sdk/src/modules/core/mod.rs @@ -11,7 +11,7 @@ use thiserror::Error; use crate::{ callformat, - context::{BatchContext, Context, TxContext}, + context::{BatchContext, Context, TransactionWithMeta, TxContext}, core::consensus::beacon::EpochTime, dispatcher, error::Error as SDKError, @@ -603,26 +603,35 @@ impl Module { CurrentStore::with_transaction(|| { let result = ctx.with_simulation(|mut sim_ctx| { - sim_ctx.with_tx(0 /* index */, tx_size, tx, |mut tx_ctx, call| { - let (result, _) = dispatcher::Dispatcher::::dispatch_tx_call( - &mut tx_ctx, - call, - &Default::default(), - ); - if !result.is_success() && report_failure { - // Report failure. - let err: TxSimulationFailure = result.try_into().unwrap(); // Guaranteed to be a Failed CallResult. - return Err(Error::TxSimulationFailed(err)); - } - // Don't report success or failure. If the call fails, we still report - // how much gas it uses while it fails. - let gas_used = *tx_ctx.value::(CONTEXT_KEY_GAS_USED).or_default(); - if result.is_success() { - Ok(gas_used) - } else { - Ok(gas_used.saturating_add(extra_gas_fail).clamp(0, gas)) - } - }) + sim_ctx.with_tx( + TransactionWithMeta { + tx, + tx_size, + tx_index: 0, + tx_hash: Default::default(), + }, + |mut tx_ctx, call| { + let (result, _) = + dispatcher::Dispatcher::::dispatch_tx_call( + &mut tx_ctx, + call, + &Default::default(), + ); + if !result.is_success() && report_failure { + // Report failure. + let err: TxSimulationFailure = result.try_into().unwrap(); // Guaranteed to be a Failed CallResult. + return Err(Error::TxSimulationFailed(err)); + } + // Don't report success or failure. If the call fails, we still report + // how much gas it uses while it fails. + let gas_used = *tx_ctx.value::(CONTEXT_KEY_GAS_USED).or_default(); + if result.is_success() { + Ok(gas_used) + } else { + Ok(gas_used.saturating_add(extra_gas_fail).clamp(0, gas)) + } + }, + ) }); TransactionResult::Rollback(result) // Always rollback storage changes. @@ -747,8 +756,9 @@ impl Module { let key_manager = ctx .key_manager() .ok_or_else(|| Error::InvalidArgument(anyhow!("key manager not available")))?; + let epoch = ctx.epoch(); let public_key = key_manager - .get_public_ephemeral_key(callformat::get_key_pair_id(ctx.epoch()), ctx.epoch()) + .get_public_ephemeral_key(callformat::get_key_pair_id(epoch), epoch) .map_err(|err| match err { keymanager::KeyManagerError::InvalidEpoch => { Error::InvalidCallFormat(anyhow!("invalid epoch")) @@ -757,7 +767,7 @@ impl Module { })? .ok_or_else(|| Error::InvalidArgument(anyhow!("key not available")))?; - Ok(types::CallDataPublicKeyQueryResponse { public_key }) + Ok(types::CallDataPublicKeyQueryResponse { public_key, epoch }) } /// Query the minimum gas price. diff --git a/runtime-sdk/src/modules/core/test.rs b/runtime-sdk/src/modules/core/test.rs index f2cdb632fe..66f0459eee 100644 --- a/runtime-sdk/src/modules/core/test.rs +++ b/runtime-sdk/src/modules/core/test.rs @@ -48,7 +48,7 @@ fn test_use_gas() { let mut tx = mock::transaction(); tx.auth_info.fee.gas = MAX_GAS; - ctx.with_tx(0, 0, tx.clone(), |mut tx_ctx, _call| { + ctx.with_tx(tx.clone().into(), |mut tx_ctx, _call| { Core::use_tx_gas(&mut tx_ctx, MAX_GAS).expect("using gas under limit should succeed"); assert_eq!( Core::remaining_batch_gas(&mut tx_ctx), @@ -58,7 +58,7 @@ fn test_use_gas() { assert_eq!(Core::used_tx_gas(&mut tx_ctx), MAX_GAS); }); - ctx.with_tx(0, 0, tx.clone(), |mut tx_ctx, _call| { + ctx.with_tx(tx.clone().into(), |mut tx_ctx, _call| { Core::use_tx_gas(&mut tx_ctx, MAX_GAS) .expect("gas across separate transactions shouldn't accumulate"); assert_eq!( @@ -69,7 +69,7 @@ fn test_use_gas() { assert_eq!(Core::used_tx_gas(&mut tx_ctx), MAX_GAS); }); - ctx.with_tx(0, 0, tx.clone(), |mut tx_ctx, _call| { + ctx.with_tx(tx.clone().into(), |mut tx_ctx, _call| { Core::use_tx_gas(&mut tx_ctx, MAX_GAS).unwrap(); Core::use_tx_gas(&mut tx_ctx, 1).expect_err("gas in same transaction should accumulate"); assert_eq!(Core::remaining_tx_gas(&mut tx_ctx), 0); @@ -82,7 +82,7 @@ fn test_use_gas() { ); assert_eq!(Core::max_batch_gas(&mut ctx), BLOCK_MAX_GAS); - ctx.with_tx(0, 0, tx.clone(), |mut tx_ctx, _call| { + ctx.with_tx(tx.clone().into(), |mut tx_ctx, _call| { Core::use_tx_gas(&mut tx_ctx, 1).unwrap(); assert_eq!( Core::remaining_batch_gas(&mut tx_ctx), @@ -100,11 +100,11 @@ fn test_use_gas() { let mut big_tx = tx.clone(); big_tx.auth_info.fee.gas = u64::MAX; - ctx.with_tx(0, 0, big_tx, |mut tx_ctx, _call| { + ctx.with_tx(big_tx.into(), |mut tx_ctx, _call| { Core::use_tx_gas(&mut tx_ctx, u64::MAX).expect_err("batch overflow should cause error"); }); - ctx.with_tx(0, 0, tx.clone(), |mut tx_ctx, _call| { + ctx.with_tx(tx.clone().into(), |mut tx_ctx, _call| { Core::use_tx_gas(&mut tx_ctx, 1).expect_err("batch gas should accumulate"); }); @@ -113,7 +113,7 @@ fn test_use_gas() { let mut ctx = mock.create_check_ctx(); let mut big_tx = tx; big_tx.auth_info.fee.gas = u64::MAX; - ctx.with_tx(0, 0, big_tx, |mut tx_ctx, _call| { + ctx.with_tx(big_tx.into(), |mut tx_ctx, _call| { Core::use_tx_gas(&mut tx_ctx, u64::MAX) .expect("batch overflow should not happen in check-tx"); }); @@ -839,7 +839,7 @@ fn test_set_priority() { Core::set_priority(&mut ctx, 11); let tx = mock::transaction(); - ctx.with_tx(0, 0, tx, |mut tx_ctx, _call| { + ctx.with_tx(tx.into(), |mut tx_ctx, _call| { Core::set_priority(&mut tx_ctx, 10); }); @@ -923,13 +923,13 @@ fn test_min_gas_price() { }, }; - ctx.with_tx(0, 0, tx.clone(), |mut tx_ctx, call| { + ctx.with_tx(tx.clone().into(), |mut tx_ctx, call| { Core::before_handle_call(&mut tx_ctx, &call).expect_err("gas price should be too low"); }); tx.auth_info.fee.amount = token::BaseUnits::new(100000, token::Denomination::NATIVE); - ctx.with_tx(0, 0, tx.clone(), |mut tx_ctx, call| { + ctx.with_tx(tx.clone().into(), |mut tx_ctx, call| { Core::before_handle_call(&mut tx_ctx, &call).expect("gas price should be ok"); }); @@ -950,28 +950,28 @@ fn test_min_gas_price() { once_cell::unsync::Lazy::new(|| BTreeSet::from(["exempt.Method"])); } - ctx.with_tx(0, 0, tx.clone(), |mut tx_ctx, call| { + ctx.with_tx(tx.clone().into(), |mut tx_ctx, call| { super::Module::::before_handle_call(&mut tx_ctx, &call) .expect_err("gas price should be too low"); }); tx.auth_info.fee.amount = token::BaseUnits::new(1_000_000, token::Denomination::NATIVE); - ctx.with_tx(0, 0, tx.clone(), |mut tx_ctx, call| { + ctx.with_tx(tx.clone().into(), |mut tx_ctx, call| { super::Module::::before_handle_call(&mut tx_ctx, &call) .expect("gas price should be ok"); }); tx.auth_info.fee.amount = token::BaseUnits::new(1_000, "SMALLER".parse().unwrap()); - ctx.with_tx(0, 0, tx.clone(), |mut tx_ctx, call| { + ctx.with_tx(tx.clone().into(), |mut tx_ctx, call| { super::Module::::before_handle_call(&mut tx_ctx, &call) .expect_err("gas price should be too low"); }); tx.auth_info.fee.amount = token::BaseUnits::new(10_000, "SMALLER".parse().unwrap()); - ctx.with_tx(0, 0, tx.clone(), |mut tx_ctx, call| { + ctx.with_tx(tx.clone().into(), |mut tx_ctx, call| { super::Module::::before_handle_call(&mut tx_ctx, &call) .expect("gas price should be ok"); }); @@ -980,14 +980,14 @@ fn test_min_gas_price() { tx.call.method = "exempt.Method".into(); tx.auth_info.fee.amount = token::BaseUnits::new(100_000, token::Denomination::NATIVE); - ctx.with_tx(0, 0, tx.clone(), |mut tx_ctx, call| { + ctx.with_tx(tx.clone().into(), |mut tx_ctx, call| { super::Module::::before_handle_call(&mut tx_ctx, &call) .expect("method should be gas price exempt"); }); tx.auth_info.fee.amount = token::BaseUnits::new(0, token::Denomination::NATIVE); - ctx.with_tx(0, 0, tx, |mut tx_ctx, call| { + ctx.with_tx(tx.into(), |mut tx_ctx, call| { super::Module::::before_handle_call(&mut tx_ctx, &call) .expect("method should be gas price exempt"); }); @@ -1013,7 +1013,7 @@ fn test_emit_events() { } ctx.emit_event(TestEvent { i: 42 }); - let etags = ctx.with_tx(0, 0, mock::transaction(), |mut ctx, _| { + let etags = ctx.with_tx(mock::transaction().into(), |mut ctx, _| { ctx.emit_event(TestEvent { i: 2 }); ctx.emit_event(TestEvent { i: 3 }); ctx.emit_event(TestEvent { i: 1 }); @@ -1068,7 +1068,7 @@ fn test_gas_used_events() { let mut tx = mock::transaction(); tx.auth_info.fee.gas = 100_000; - let etags = ctx.with_tx(0, 0, tx, |mut tx_ctx, _call| { + let etags = ctx.with_tx(tx.into(), |mut tx_ctx, _call| { Core::use_tx_gas(&mut tx_ctx, 10).expect("using gas under limit should succeed"); assert_eq!(Core::used_tx_gas(&mut tx_ctx), 10); Core::after_handle_call( diff --git a/runtime-sdk/src/modules/core/types.rs b/runtime-sdk/src/modules/core/types.rs index 4a296b5c2b..13d5f10263 100644 --- a/runtime-sdk/src/modules/core/types.rs +++ b/runtime-sdk/src/modules/core/types.rs @@ -37,6 +37,8 @@ pub struct EstimateGasQuery { pub struct CallDataPublicKeyQueryResponse { /// Public key used for deriving the shared secret for encrypting call data. pub public_key: SignedPublicKey, + /// Epoch of the ephemeral runtime key. + pub epoch: u64, } #[derive(Debug, Copy, Clone, cbor::Encode, cbor::Decode)] diff --git a/runtime-sdk/src/subcall.rs b/runtime-sdk/src/subcall.rs index f4d2263d80..3ed6f752ae 100644 --- a/runtime-sdk/src/subcall.rs +++ b/runtime-sdk/src/subcall.rs @@ -2,7 +2,7 @@ use std::cell::RefCell; use crate::{ - context::{BatchContext, Context, State, TxContext}, + context::{BatchContext, Context, State, TransactionWithMeta, TxContext}, dispatcher, module::CallResult, modules::core::{Error, API as _}, @@ -166,7 +166,7 @@ pub fn call( }; let result = CurrentStore::with_transaction(|| { - ctx.with_tx(0, 0, tx, |ctx, call| { + ctx.with_tx(TransactionWithMeta::internal(tx), |ctx, call| { // Mark this sub-context as internal as it belongs to an existing transaction. let mut ctx = ctx.internal(); diff --git a/runtime-sdk/src/testing/mock.rs b/runtime-sdk/src/testing/mock.rs index db9f33c535..7d39e7f164 100644 --- a/runtime-sdk/src/testing/mock.rs +++ b/runtime-sdk/src/testing/mock.rs @@ -13,6 +13,7 @@ use oasis_core_runtime::{ use crate::{ context::{BatchContext, Mode, RuntimeBatchContext}, + crypto::random::RootRng, dispatcher, history, keymanager::KeyManager, module::MigrationHandler, @@ -66,6 +67,7 @@ pub struct Mock { pub consensus_state: ConsensusState, pub history: Box, pub epoch: beacon::EpochTime, + pub rng: RootRng, pub max_messages: u32, } @@ -99,6 +101,7 @@ impl Mock { &self.consensus_state, &self.history, self.epoch, + &self.rng, IoContext::background().freeze(), self.max_messages, ) @@ -127,6 +130,7 @@ impl Mock { consensus_state: ConsensusState::new(1, consensus_tree), history: Box::new(EmptyHistory), epoch: 1, + rng: RootRng::new(), max_messages: 32, } } diff --git a/runtime-sdk/src/types/callformat.rs b/runtime-sdk/src/types/callformat.rs index 07905955ca..976543674f 100644 --- a/runtime-sdk/src/types/callformat.rs +++ b/runtime-sdk/src/types/callformat.rs @@ -11,6 +11,9 @@ pub struct CallEnvelopeX25519DeoxysII { pub pk: [u8; 32], /// Nonce. pub nonce: [u8; deoxysii::NONCE_SIZE], + /// Epoch of the ephemeral runtime key. + #[cbor(optional)] + pub epoch: u64, /// Encrypted call data. pub data: Vec, } diff --git a/tests/e2e/contracts/contracts.mk b/tests/e2e/contracts/contracts.mk new file mode 100644 index 0000000000..a5e0296099 --- /dev/null +++ b/tests/e2e/contracts/contracts.mk @@ -0,0 +1,10 @@ +all: $(hex) + +$(hex): $(contract) + @solc $(contract) --bin --abi --optimize -o .build + @mv .build/*.abi $(abi) + @mv .build/*.bin $(hex) + @rmdir .build + +.PHONY: all + diff --git a/tests/e2e/contracts/evm_rng_compiled.hex b/tests/e2e/contracts/evm_rng_compiled.hex deleted file mode 100644 index ea9be26487..0000000000 --- a/tests/e2e/contracts/evm_rng_compiled.hex +++ /dev/null @@ -1 +0,0 @@ -60808060405234610016576104ad908161001c8239f35b600080fdfe608060409080825260048036101561001657600080fd5b600091823560e01c63f8a8fd6d1461002d57600080fd5b3461033f578260031936011261033f5761004681610343565b600d815261006a6020916c706572736f6e616c697a65642160981b838201526103cd565b901561030c57600a8151036102db57845161008481610343565b600a8152828101908336833782519184840192832091519020146102b2576100b886516100b081610374565b8681526103cd565b9190926100d188516100c981610374565b8881526103cd565b9490816102aa575b508061029f575b1561027657519020908051918482019283201461024d5751902090828151910120146102255783519361011285610374565b8385528051828101906212d687825282808201528651806060830152865b81811061021157509181610161608082868b8381998299010152601f8019910116810103606081018452018261038f565b51906001600160981b015afa3d15610209573d9061017e826103b2565b9161018b8451938461038f565b82523d868584013e5b156101d757516103ff19016101a7578380f35b5162461bcd60e51b815291820152600c60248201526b189859081b195b99dd1a080d60a21b604482015260649150fd5b505162461bcd60e51b815291820152600d60248201526c1d5b9cdd58d8d95cdcd99d5b0d609a1b604482015260649150fd5b606090610194565b888101860151838201608001528501610130565b835162461bcd60e51b8152918201526003602482015262323d3360e81b604482015260649150fd5b865162461bcd60e51b81528086018590526003602482015262189e9960e91b6044820152606490fd5b875162461bcd60e51b8152808701869052600360248201526232263360e81b6044820152606490fd5b5082518451146100e0565b9050386100d9565b855162461bcd60e51b815280850184905260036024820152620313d360ec1b6044820152606490fd5b845162461bcd60e51b8152808401839052600b60248201526a626164206c656e6774683160a81b6044820152606490fd5b845162461bcd60e51b8152808401839052600d60248201526c756e7375636365737366756c3160981b6044820152606490fd5b8280fd5b604081019081106001600160401b0382111761035e57604052565b634e487b7160e01b600052604160045260246000fd5b602081019081106001600160401b0382111761035e57604052565b601f909101601f19168101906001600160401b0382119082101761035e57604052565b6001600160401b03811161035e57601f01601f191660200190565b6040519160209283810190600a825260408082015283518060608301528560005b82811061046357505060009394509061042460808284878381989784010152601f8019910116810103606081018452018261038f565b51906001600160981b015afa3d1561045c573d610440816103b2565b9061044e604051928361038f565b8152600081943d92013e9190565b9150606090565b8681018201518482016080015281016103ee56fea264697066735822122045d40064eacb45524c22af79e5ec9678dee47dfe6409cc0a3d225db9d6679bde64736f6c63430008110033 diff --git a/tests/e2e/contracts/rng/Makefile b/tests/e2e/contracts/rng/Makefile new file mode 100644 index 0000000000..b5a603baef --- /dev/null +++ b/tests/e2e/contracts/rng/Makefile @@ -0,0 +1,6 @@ +contract = evm_rng.sol +abi = evm_rng.abi +hex = evm_rng.hex + +include ../contracts.mk + diff --git a/tests/e2e/contracts/rng/evm_rng.abi b/tests/e2e/contracts/rng/evm_rng.abi new file mode 100644 index 0000000000..a363b1fb8e --- /dev/null +++ b/tests/e2e/contracts/rng/evm_rng.abi @@ -0,0 +1 @@ +[{"inputs":[],"name":"test","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"count","type":"uint256"},{"internalType":"bytes","name":"pers","type":"bytes"}],"name":"testGenerate","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/tests/e2e/contracts/rng/evm_rng.hex b/tests/e2e/contracts/rng/evm_rng.hex new file mode 100644 index 0000000000..8bc7e981ed --- /dev/null +++ b/tests/e2e/contracts/rng/evm_rng.hex @@ -0,0 +1 @@ +608060405234801561000f575f80fd5b506105988061001d5f395ff3fe608060405234801561000f575f80fd5b5060043610610034575f3560e01c8063c5d7725b14610038578063f8a8fd6d14610061575b5f80fd5b61004b61004636600461044c565b61006b565b604051610058919061050e565b60405180910390f35b6100696100f6565b005b60605f806100ae8686868080601f0160208091040260200160405190810160405280939291908181526020018383808284375f920191909152506103c292505050565b91509150816100ed5760405162461bcd60e51b815260206004820152600660248201526519985a5b195960d21b60448201526064015b60405180910390fd5b95945050505050565b5f80610128600a6040518060400160405280600d81526020016c706572736f6e616c697a65642160981b8152506103c2565b91509150816101695760405162461bcd60e51b815260206004820152600d60248201526c756e7375636365737366756c3160981b60448201526064016100e4565b8051600a146101a85760405162461bcd60e51b815260206004820152600b60248201526a626164206c656e6774683160a81b60448201526064016100e4565b60408051600a8082528183019092525f91602082018180368337019050509050808051906020012082805190602001200361020b5760405162461bcd60e51b81526020600482015260036024820152620313d360ec1b60448201526064016100e4565b5f80610226600a60405180602001604052805f8152506103c2565b915091505f80610245600a60405180602001604052805f8152506103c2565b915091508380156102535750815b8015610260575080518351145b6102925760405162461bcd60e51b815260206004820152600360248201526232263360e81b60448201526064016100e4565b82805190602001208680519060200120036102d55760405162461bcd60e51b8152602060048201526003602482015262189e9960e91b60448201526064016100e4565b80805190602001208380519060200120036103185760405162461bcd60e51b8152602060048201526003602482015262323d3360e81b60448201526064016100e4565b5f806103356212d68760405180602001604052805f8152506103c2565b91509150816103765760405162461bcd60e51b815260206004820152600d60248201526c1d5b9cdd58d8d95cdcd99d5b0d609a1b60448201526064016100e4565b8051610400146103b75760405162461bcd60e51b815260206004820152600c60248201526b189859081b195b99dd1a080d60a21b60448201526064016100e4565b505050505050505050565b5f60606001600160981b016001600160a01b031684846040516020016103e9929190610527565b60408051601f198184030181529082905261040391610547565b5f60405180830381855afa9150503d805f811461043b576040519150601f19603f3d011682016040523d82523d5f602084013e610440565b606091505b50915091509250929050565b5f805f6040848603121561045e575f80fd5b83359250602084013567ffffffffffffffff8082111561047c575f80fd5b818601915086601f83011261048f575f80fd5b81358181111561049d575f80fd5b8760208285010111156104ae575f80fd5b6020830194508093505050509250925092565b5f5b838110156104db5781810151838201526020016104c3565b50505f910152565b5f81518084526104fa8160208601602086016104c1565b601f01601f19169290920160200192915050565b602081525f61052060208301846104e3565b9392505050565b828152604060208201525f61053f60408301846104e3565b949350505050565b5f82516105588184602087016104c1565b919091019291505056fea2646970667358221220b53371f01c0abb1590bfd6919191da9de96498580b746ff9e8a107f1c34e8dcc64736f6c63430008150033 \ No newline at end of file diff --git a/tests/e2e/contracts/evm_rng.sol b/tests/e2e/contracts/rng/evm_rng.sol similarity index 84% rename from tests/e2e/contracts/evm_rng.sol rename to tests/e2e/contracts/rng/evm_rng.sol index 200c3bebbc..f6006501fa 100644 --- a/tests/e2e/contracts/evm_rng.sol +++ b/tests/e2e/contracts/rng/evm_rng.sol @@ -27,4 +27,10 @@ contract Test { require(success4, "unsuccessful4"); require(out4.length == 1024, "bad length 4"); } + + function testGenerate(uint256 count, bytes calldata pers) external view returns (bytes memory) { + (bool success, bytes memory data) = randomBytes(count, pers); + require(success, "failed"); + return data; + } } diff --git a/tests/e2e/contracts/rng/rng.go b/tests/e2e/contracts/rng/rng.go new file mode 100644 index 0000000000..99a34e1488 --- /dev/null +++ b/tests/e2e/contracts/rng/rng.go @@ -0,0 +1,36 @@ +package rng + +import ( + _ "embed" + "encoding/hex" + "fmt" + "strings" + + ethABI "github.com/ethereum/go-ethereum/accounts/abi" +) + +// CompiledHex is the compiled subcall contract in hex encoding. +// +//go:embed evm_rng.hex +var CompiledHex string + +// Compiled is the compiled subcall contract. +var Compiled = func() []byte { + contract, err := hex.DecodeString(strings.TrimSpace(CompiledHex)) + if err != nil { + panic(fmt.Errorf("failed to decode contract: %w", err)) + } + return contract +}() + +//go:embed evm_rng.abi +var evmSubcallABIJson string + +// ABI is the ABI of the subcall contract. +var ABI = func() ethABI.ABI { + abi, err := ethABI.JSON(strings.NewReader(evmSubcallABIJson)) + if err != nil { + panic(err) + } + return abi +}() diff --git a/tests/e2e/contracts/subcall/Makefile b/tests/e2e/contracts/subcall/Makefile new file mode 100644 index 0000000000..97cc6f3fca --- /dev/null +++ b/tests/e2e/contracts/subcall/Makefile @@ -0,0 +1,6 @@ +contract = evm_subcall.sol +abi = evm_subcall.abi +hex = evm_subcall.hex + +include ../contracts.mk + diff --git a/tests/e2e/contracts/subcall/evm_subcall.abi b/tests/e2e/contracts/subcall/evm_subcall.abi new file mode 100644 index 0000000000..e22c8749cf --- /dev/null +++ b/tests/e2e/contracts/subcall/evm_subcall.abi @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"uint64","name":"code","type":"uint64"},{"internalType":"bytes","name":"module","type":"bytes"}],"name":"SubcallFailed","type":"error"},{"inputs":[{"internalType":"bytes","name":"method","type":"bytes"},{"internalType":"bytes","name":"body","type":"bytes"}],"name":"test","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes","name":"method","type":"bytes"},{"internalType":"bytes","name":"body","type":"bytes"}],"name":"test_delegatecall","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"method","type":"bytes"},{"internalType":"bytes","name":"body","type":"bytes"}],"name":"test_spin","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/tests/e2e/contracts/subcall/evm_subcall.hex b/tests/e2e/contracts/subcall/evm_subcall.hex new file mode 100644 index 0000000000..da57df7cda --- /dev/null +++ b/tests/e2e/contracts/subcall/evm_subcall.hex @@ -0,0 +1 @@ +608060405234801561000f575f80fd5b5061062e8061001d5f395ff3fe608060405260043610610033575f3560e01c80630c5561a61461003757806323bfb16a1461006057806350d82f381461007f575b5f80fd5b61004a610045366004610377565b61009e565b604051610057919061042b565b60405180910390f35b34801561006b575f80fd5b5061004a61007a366004610377565b610162565b34801561008a575f80fd5b5061004a610099366004610377565b610212565b60605f80610102600160981b016001600160a01b0316878787876040516020016100cb9493929190610465565b60408051601f19818403018152908290526100e59161048b565b5f604051808303815f865af19150503d805f811461011e576040519150601f19603f3d011682016040523d82523d5f602084013e610123565b606091505b50915091508161014e5760405162461bcd60e51b8152600401610145906104a6565b60405180910390fd5b610157816102e1565b979650505050505050565b60605f80610102600160981b016001600160a01b03168787878760405160200161018f9493929190610465565b60408051601f19818403018152908290526101a99161048b565b5f60405180830381855af49150503d805f81146101e1576040519150601f19603f3d011682016040523d82523d5f602084013e6101e6565b606091505b5091509150816102085760405162461bcd60e51b8152600401610145906104a6565b9695505050505050565b60605f80610102600160981b016001600160a01b03168787878760405160200161023f9493929190610465565b60408051601f19818403018152908290526102599161048b565b5f604051808303815f865af19150503d805f8114610292576040519150601f19603f3d011682016040523d82523d5f602084013e610297565b606091505b5091509150816102b95760405162461bcd60e51b8152600401610145906104a6565b5f5b60648112156102d657806102ce816104ce565b9150506102bb565b509695505050505050565b60605f80838060200190518101906102f9919061050c565b915091508167ffffffffffffffff165f1461032b57818160405163575a7c4d60e01b81526004016101459291906105ce565b9392505050565b5f8083601f840112610342575f80fd5b50813567ffffffffffffffff811115610359575f80fd5b602083019150836020828501011115610370575f80fd5b9250929050565b5f805f806040858703121561038a575f80fd5b843567ffffffffffffffff808211156103a1575f80fd5b6103ad88838901610332565b909650945060208701359150808211156103c5575f80fd5b506103d287828801610332565b95989497509550505050565b5f5b838110156103f85781810151838201526020016103e0565b50505f910152565b5f81518084526104178160208601602086016103de565b601f01601f19169290920160200192915050565b602081525f61032b6020830184610400565b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b604081525f61047860408301868861043d565b828103602084015261015781858761043d565b5f825161049c8184602087016103de565b9190910192915050565b6020808252600e908201526d1cdd5898d85b1b0819985a5b195960921b604082015260600190565b5f6001600160ff1b0182016104f157634e487b7160e01b5f52601160045260245ffd5b5060010190565b634e487b7160e01b5f52604160045260245ffd5b5f806040838503121561051d575f80fd5b825167ffffffffffffffff8082168214610535575f80fd5b602085015191935080821115610549575f80fd5b818501915085601f83011261055c575f80fd5b81518181111561056e5761056e6104f8565b604051601f8201601f19908116603f01168101908382118183101715610596576105966104f8565b816040528281528860208487010111156105ae575f80fd5b6105bf8360208301602088016103de565b80955050505050509250929050565b67ffffffffffffffff83168152604060208201525f6105f06040830184610400565b94935050505056fea26469706673582212200d1600b1d5aadfe166b8e12683fa33eb93b7d9b0f2c53c95a01ecb651f082a5b64736f6c63430008150033 \ No newline at end of file diff --git a/tests/e2e/contracts/subcall/evm_subcall_abi.json b/tests/e2e/contracts/subcall/evm_subcall_abi.json deleted file mode 100644 index 73e2df3caa..0000000000 --- a/tests/e2e/contracts/subcall/evm_subcall_abi.json +++ /dev/null @@ -1,90 +0,0 @@ -[ - { - "inputs": [ - { - "internalType": "uint64", - "name": "code", - "type": "uint64" - }, - { - "internalType": "bytes", - "name": "module", - "type": "bytes" - } - ], - "name": "SubcallFailed", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "bytes", - "name": "method", - "type": "bytes" - }, - { - "internalType": "bytes", - "name": "body", - "type": "bytes" - } - ], - "name": "test", - "outputs": [ - { - "internalType": "bytes", - "name": "", - "type": "bytes" - } - ], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes", - "name": "method", - "type": "bytes" - }, - { - "internalType": "bytes", - "name": "body", - "type": "bytes" - } - ], - "name": "test_delegatecall", - "outputs": [ - { - "internalType": "bytes", - "name": "", - "type": "bytes" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes", - "name": "method", - "type": "bytes" - }, - { - "internalType": "bytes", - "name": "body", - "type": "bytes" - } - ], - "name": "test_spin", - "outputs": [ - { - "internalType": "bytes", - "name": "", - "type": "bytes" - } - ], - "stateMutability": "nonpayable", - "type": "function" - } -] \ No newline at end of file diff --git a/tests/e2e/contracts/subcall/evm_subcall_compiled.hex b/tests/e2e/contracts/subcall/evm_subcall_compiled.hex deleted file mode 100644 index 98387feb67..0000000000 --- a/tests/e2e/contracts/subcall/evm_subcall_compiled.hex +++ /dev/null @@ -1 +0,0 @@ -608060405234801561001057600080fd5b50610a0c806100206000396000f3fe6080604052600436106100345760003560e01c80630c5561a61461003957806323bfb16a1461006957806350d82f38146100a6575b600080fd5b610053600480360381019061004e91906104df565b6100e3565b60405161006091906105f0565b60405180910390f35b34801561007557600080fd5b50610090600480360381019061008b91906104df565b6101e2565b60405161009d91906105f0565b60405180910390f35b3480156100b257600080fd5b506100cd60048036038101906100c891906104df565b6102d7565b6040516100da91906105f0565b60405180910390f35b606060008073010000000000000000000000000000000000010273ffffffffffffffffffffffffffffffffffffffff168787878760405160200161012a949392919061064e565b60405160208183030381529060405260405161014691906106c5565b6000604051808303816000865af19150503d8060008114610183576040519150601f19603f3d011682016040523d82523d6000602084013e610188565b606091505b5091509150816101cd576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101c490610739565b60405180910390fd5b6101d6816103ee565b92505050949350505050565b606060008073010000000000000000000000000000000000010273ffffffffffffffffffffffffffffffffffffffff1687878787604051602001610229949392919061064e565b60405160208183030381529060405260405161024591906106c5565b600060405180830381855af49150503d8060008114610280576040519150601f19603f3d011682016040523d82523d6000602084013e610285565b606091505b5091509150816102ca576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016102c190610739565b60405180910390fd5b8092505050949350505050565b606060008073010000000000000000000000000000000000010273ffffffffffffffffffffffffffffffffffffffff168787878760405160200161031e949392919061064e565b60405160208183030381529060405260405161033a91906106c5565b6000604051808303816000865af19150503d8060008114610377576040519150601f19603f3d011682016040523d82523d6000602084013e61037c565b606091505b5091509150816103c1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103b890610739565b60405180910390fd5b60005b60648112156103e05780806103d890610792565b9150506103c4565b508092505050949350505050565b606060008083806020019051810190610407919061093b565b9150915060008267ffffffffffffffff161461045c5781816040517f575a7c4d0000000000000000000000000000000000000000000000000000000081526004016104539291906109a6565b60405180910390fd5b8092505050919050565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b600080fd5b60008083601f84011261049f5761049e61047a565b5b8235905067ffffffffffffffff8111156104bc576104bb61047f565b5b6020830191508360018202830111156104d8576104d7610484565b5b9250929050565b600080600080604085870312156104f9576104f8610470565b5b600085013567ffffffffffffffff81111561051757610516610475565b5b61052387828801610489565b9450945050602085013567ffffffffffffffff81111561054657610545610475565b5b61055287828801610489565b925092505092959194509250565b600081519050919050565b600082825260208201905092915050565b60005b8381101561059a57808201518184015260208101905061057f565b60008484015250505050565b6000601f19601f8301169050919050565b60006105c282610560565b6105cc818561056b565b93506105dc81856020860161057c565b6105e5816105a6565b840191505092915050565b6000602082019050818103600083015261060a81846105b7565b905092915050565b82818337600083830152505050565b600061062d838561056b565b935061063a838584610612565b610643836105a6565b840190509392505050565b60006040820190508181036000830152610669818688610621565b9050818103602083015261067e818486610621565b905095945050505050565b600081905092915050565b600061069f82610560565b6106a98185610689565b93506106b981856020860161057c565b80840191505092915050565b60006106d18284610694565b915081905092915050565b600082825260208201905092915050565b7f73756263616c6c206661696c6564000000000000000000000000000000000000600082015250565b6000610723600e836106dc565b915061072e826106ed565b602082019050919050565b6000602082019050818103600083015261075281610716565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000819050919050565b600061079d82610788565b91507f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036107cf576107ce610759565b5b600182019050919050565b600067ffffffffffffffff82169050919050565b6107f7816107da565b811461080257600080fd5b50565b600081519050610814816107ee565b92915050565b600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b610857826105a6565b810181811067ffffffffffffffff821117156108765761087561081f565b5b80604052505050565b6000610889610466565b9050610895828261084e565b919050565b600067ffffffffffffffff8211156108b5576108b461081f565b5b6108be826105a6565b9050602081019050919050565b60006108de6108d98461089a565b61087f565b9050828152602081018484840111156108fa576108f961081a565b5b61090584828561057c565b509392505050565b600082601f8301126109225761092161047a565b5b81516109328482602086016108cb565b91505092915050565b6000806040838503121561095257610951610470565b5b600061096085828601610805565b925050602083015167ffffffffffffffff81111561098157610980610475565b5b61098d8582860161090d565b9150509250929050565b6109a0816107da565b82525050565b60006040820190506109bb6000830185610997565b81810360208301526109cd81846105b7565b9050939250505056fea2646970667358221220bb4ee71d5a949c56534abbc225739b6bc549487627a3c5eaf9d7528688bff2e564736f6c63430008120033 \ No newline at end of file diff --git a/tests/e2e/contracts/subcall/subcall.go b/tests/e2e/contracts/subcall/subcall.go index 4d8be9d110..05da6e7d16 100644 --- a/tests/e2e/contracts/subcall/subcall.go +++ b/tests/e2e/contracts/subcall/subcall.go @@ -11,7 +11,7 @@ import ( // CompiledHex is the compiled subcall contract in hex encoding. // -//go:embed evm_subcall_compiled.hex +//go:embed evm_subcall.hex var CompiledHex string // Compiled is the compiled subcall contract. @@ -23,7 +23,7 @@ var Compiled = func() []byte { return contract }() -//go:embed evm_subcall_abi.json +//go:embed evm_subcall.abi var evmSubcallABIJson string // ABI is the ABI of the subcall contract. diff --git a/tests/e2e/evmtest.go b/tests/e2e/evmtest.go index 83706a5a66..6e9adb60e7 100644 --- a/tests/e2e/evmtest.go +++ b/tests/e2e/evmtest.go @@ -9,6 +9,7 @@ import ( "fmt" "math/big" "strings" + "sync" ethMath "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/crypto" @@ -31,6 +32,7 @@ import ( "github.com/oasisprotocol/oasis-sdk/client-sdk/go/testing" "github.com/oasisprotocol/oasis-sdk/client-sdk/go/types" + contractRng "github.com/oasisprotocol/oasis-sdk/tests/e2e/contracts/rng" contractSubcall "github.com/oasisprotocol/oasis-sdk/tests/e2e/contracts/subcall" "github.com/oasisprotocol/oasis-sdk/tests/e2e/txgen" ) @@ -77,12 +79,6 @@ var evmEncryptionCompiledHex string //go:embed contracts/evm_key_derivation_compiled.hex var evmKeyDerivationCompiledHex string -// We store the compiled EVM bytecode for the SimpleEVMRngTest in a separate -// file (in hex) to preserve readability of this file. -// -//go:embed contracts/evm_rng_compiled.hex -var evmRngHex string - // We store the compiled EVM bytecode for the C10lEVMMessageSigningTest in a separate // file (in hex) to preserve readability of this file. // @@ -201,7 +197,10 @@ func evmSimulateCall(ctx context.Context, rtc client.RuntimeClient, e evm.V1, ca if err != nil { return nil, fmt.Errorf("failed to get call data public key: %w", err) } - encData, encMeta, err := callformat.EncodeCall(&signedCallDataPack.Data, types.CallFormatEncryptedX25519DeoxysII, &callformat.EncodeConfig{PublicKey: &callDataPublicKey.PublicKey}) + encData, encMeta, err := callformat.EncodeCall(&signedCallDataPack.Data, types.CallFormatEncryptedX25519DeoxysII, &callformat.EncodeConfig{ + PublicKey: &callDataPublicKey.PublicKey, + Epoch: callDataPublicKey.Epoch, + }) if err != nil { return nil, fmt.Errorf("failed to encode signed call data: %w", err) } @@ -1123,17 +1122,109 @@ func keyDerivationEVMTest(log *logging.Logger, rtc client.RuntimeClient, c10l c1 // Note that this test will only work with a confidential runtime because // it needs the confidential precompiles. func rngEVMTest(log *logging.Logger, rtc client.RuntimeClient, c10l c10lity) error { - // To generate the contract bytecode, use solc or https://remix.ethereum.org/ - // with the following settings: - // Compiler: 0.8.17+commit.8df45f5f.Darwin.appleclang - // EVM version: london - // Enable optimization: yes, 1, via-ir - // on the source in evm_rng.sol next to the hex file. - // (i.e. `solc evm_rng.sol --bin --optimize`) - _, err := simpleEVMCallTest(log, rtc, c10l, evmRngHex, "test", "f8a8fd6d", "") + ctx := context.Background() + ev := evm.NewV1(rtc) + gasPrice := uint64(2) + value := big.NewInt(0).Bytes() // Don't send any tokens with the calls. + + // Deploy the contract. + contractAddr, err := evmCreate(ctx, rtc, ev, testing.Dave.Signer, value, contractRng.Compiled, gasPrice, c10l) if err != nil { - return err + return fmt.Errorf("failed to deploy contract: %w", err) + } + + // Call the basic test method. + data, err := contractRng.ABI.Pack("test") + if err != nil { + return fmt.Errorf("failed to pack arguments: %w", err) + } + _, err = evmCall(ctx, rtc, ev, testing.Dave.Signer, contractAddr, value, data, gasPrice, c10l) + if err != nil { + return fmt.Errorf("failed to call contract: %w", err) + } + + // Create some accounts so we will be able to run test in parallel. + numAccounts := 5 + log.Info("creating secp256k1 accounts", "num_accounts", numAccounts) + + var signers []signature.Signer + for i := 0; i < numAccounts; i++ { + var signer signature.Signer + signer, err = txgen.CreateAndFundAccount(ctx, rtc, testing.Dave.Signer, i, txgen.AccountSecp256k1, 10_000_000) + if err != nil { + return err + } + + signers = append(signers, signer) } + + // Repeatedly invoke the RNG from multiple signers in parallel. + reqLen := 32 + pers := []byte("") + iterations := 10 + + data, err = contractRng.ABI.Pack("testGenerate", big.NewInt(int64(reqLen)), pers) + if err != nil { + return fmt.Errorf("failed to pack arguments: %w", err) + } + + var wg sync.WaitGroup + resultCh := make(chan interface{}, len(signers)*iterations) + callFn := func(startCh chan struct{}, signer signature.Signer) { + defer wg.Done() + + // Synchronize calls among all goroutines as we want to increase the chances of transactions + // landing in the same block. + <-startCh + + rawResult, err := evmCall(ctx, rtc, ev, signer, contractAddr, value, data, gasPrice, c10l) + if err != nil { + resultCh <- fmt.Errorf("failed to call contract: %w", err) + return + } + result, err := contractRng.ABI.Unpack("testGenerate", rawResult) + if err != nil { + resultCh <- fmt.Errorf("failed to unpack result: %w", err) + return + } + resultCh <- result[0].([]byte) + } + + log.Info("executing EVM calls to RNG") + + for i := 0; i < iterations; i++ { + startCh := make(chan struct{}) + for _, signer := range signers { + wg.Add(1) + go callFn(startCh, signer) + } + close(startCh) + wg.Wait() + } + + close(resultCh) + + // Do basic checks on all received outputs from the RNG. + seen := make(map[string]struct{}) + for result := range resultCh { + var randomBytes []byte + switch r := result.(type) { + case error: + return r + case []byte: + randomBytes = r + } + + if resLen := len(randomBytes); resLen != reqLen { + return fmt.Errorf("result has incorrect length (expected: %d got: %d)", reqLen, resLen) + } + + if _, ok := seen[string(randomBytes)]; ok { + return fmt.Errorf("got duplicate value: %X", randomBytes) + } + seen[string(randomBytes)] = struct{}{} + } + return nil } diff --git a/tests/runtimes/simple-evm/src/lib.rs b/tests/runtimes/simple-evm/src/lib.rs index 2a47a9a3e3..4288edf80e 100644 --- a/tests/runtimes/simple-evm/src/lib.rs +++ b/tests/runtimes/simple-evm/src/lib.rs @@ -95,7 +95,7 @@ impl sdk::Runtime for Runtime { }, modules::core::Genesis { parameters: modules::core::Parameters { - max_batch_gas: 2_000_000, + max_batch_gas: 30_000_000, max_tx_size: 32 * 1024, max_tx_signers: 8, max_multisig_signers: 8,