Repository: stellar/rs-soroban-env
Pull request #496 (2022-09-29 01:27:43)

Add get_ledger_network_id host fn
### What
Add get_ledger_network_id host fn

### Why
Early on we were exposing a field named network id to contracts and, if I'm remembering correctly, I asked for it to be named network passphrase. What I failed to remember in that moment that network id would actually be a hash of the passphrase, and therefore would be a fixed length value that we'd be able to use in things like as domain separators for signing in the same way that the `TransactionSignaturePayload` does.

The network passphrase is the string:
```
Public Global Stellar Network ; September 2015
```

The network ID is the 32byte SHA256 hash of the network passphrase, in this case:
```
7ac33997544e3175d266bd022439b22cdb16508c01163f26e5cb2a3e1045a979
```

A fixed length value can have an advantage in that it always takes up the same amount of space in a message, and therefore doesn't rely on prefixes or separators to ensure that the value disambiguates two values where one value is a prefix of another.

This doesn't impact anywhere we are using the network passphrase because the only places we use a network passphrase is inside structured values that contain length prefixes that would distinguish two values like described above. However if we expose network id we would probably use it instead of network passphrase.

In hindsight we probably could have exposed on network id and it was probably the wrong call on my part to expose network passphrase instead. There's no harm in having exposed network passphrase, however network id will be more useful.

Close https://github.com/stellar/rs-soroban-env/issues/494

graydon: You need to bump the env interface version. Also do you want to _remove_ the network passphrase? I don't really care, it and it seems like it might be a footgun?
leighmcculloch: > Also do you want to _remove_ the network passphrase? I don't really care, it and it seems like it might be a footgun?

Maybe, unclear at this point, probably, but I'd rather defer that decision until some later point, and then just sunset the host function.

Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 69b37397..598eb0ab 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -97,6 +97,18 @@ version = "1.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
 
+[[package]]
+name = "bytes-lit"
+version = "0.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c181bca161a49348b90fa75f9a54fe11b7138098eed90d841a1055d574b4250"
+dependencies = [
+ "num-bigint",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "cc"
 version = "1.0.73"
@@ -643,6 +655,7 @@ name = "soroban-env-host"
 version = "0.0.5"
 dependencies = [
  "backtrace",
+ "bytes-lit",
  "dyn-fmt",
  "ed25519-dalek",
  "env_logger",
diff --git a/soroban-env-common/src/env.rs b/soroban-env-common/src/env.rs
index 32a9b2b2..cc25534c 100644
--- a/soroban-env-common/src/env.rs
+++ b/soroban-env-common/src/env.rs
@@ -193,6 +193,10 @@ macro_rules! call_macro_with_all_host_functions {
                 /// the running contract. Traps if the running contract was not
                 /// invoked by an account.
                 {"c", fn get_invoking_account() -> Object }
+                /// Return the network id (sha256 hash of network passphrase) of
+                /// the current ledger as `Bytes`. The value is always 32 bytes
+                /// in length.
+                {"d", fn get_ledger_network_id() -> Object }
             }
 
             mod u64 "u" {
diff --git a/soroban-env-common/src/meta.rs b/soroban-env-common/src/meta.rs
index 5349c6f2..0b48ba0e 100644
--- a/soroban-env-common/src/meta.rs
+++ b/soroban-env-common/src/meta.rs
@@ -56,5 +56,5 @@
 // implementations over a long period of time.
 
 soroban_env_macros::generate_env_meta_consts!(
-    interface_version: 21,
+    interface_version: 22,
 );
diff --git a/soroban-env-host/Cargo.toml b/soroban-env-host/Cargo.toml
index 2eaa4f46..47cc535e 100644
--- a/soroban-env-host/Cargo.toml
+++ b/soroban-env-host/Cargo.toml
@@ -38,6 +38,7 @@ thousands = "0.2.0"
 # NB: this must match the same rand version used by ed25519-dalek above
 rand = "0.7.3"
 soroban-test-wasms = { package = "soroban-test-wasms", path = "../soroban-test-wasms" }
+bytes-lit = "0.0.4"
 
 [features]
 vm = ["wasmi", "parity-wasm", "soroban-env-common/vm"]
diff --git a/soroban-env-host/src/host.rs b/soroban-env-host/src/host.rs
index 8d1fb2fe..a48ff322 100644
--- a/soroban-env-host/src/host.rs
+++ b/soroban-env-host/src/host.rs
@@ -7,6 +7,7 @@ use core::fmt::Debug;
 
 use im_rc::{OrdMap, Vector};
 use num_bigint::Sign;
+use sha2::{Digest, Sha256};
 use soroban_env_common::{
     EnvVal, InvokerType, Status, TryConvert, TryFromVal, TryIntoVal, VmCaller, VmCallerCheckedEnv,
 };
@@ -427,7 +428,11 @@ impl Host {
 
     // Notes on metering: object visiting part is covered by unchecked_visit_val_obj. Closure function
     /// needs to be metered separately.
-    fn visit_obj<HOT: HostObjectType, F, U>(&self, obj: Object, f: F) -> Result<U, HostError>
+    pub(crate) fn visit_obj<HOT: HostObjectType, F, U>(
+        &self,
+        obj: Object,
+        f: F,
+    ) -> Result<U, HostError>
     where
         F: FnOnce(&HOT) -> Result<U, HostError>,
     {
@@ -2811,6 +2816,20 @@ impl VmCallerCheckedEnv for Host {
             .into())
     }
 
+    fn get_ledger_network_id(&self, _vmcaller: &mut VmCaller<Host>) -> Result<Object, Self::Error> {
+        Ok(self
+            .with_ledger_info(|li| {
+                let hash = Sha256::digest(li.network_passphrase.clone())
+                    .as_slice()
+                    .to_vec();
+                if hash.len() != 32 {
+                    return Err(self.err_general("incorrect hash size"));
+                }
+                self.add_host_object(hash)
+            })?
+            .into())
+    }
+
     fn get_current_call_stack(&self, _vmcaller: &mut VmCaller<Host>) -> Result<Object, HostError> {
         let frames = self.0.context.borrow();
 


Test patch:
diff --git a/soroban-env-host/src/test.rs b/soroban-env-host/src/test.rs
index b6ee8164..8b650fb7 100644
--- a/soroban-env-host/src/test.rs
+++ b/soroban-env-host/src/test.rs
@@ -5,6 +5,7 @@ mod basic;
 mod bigint;
 mod bytes;
 mod crypto;
+mod ledger;
 mod map;
 mod str;
 mod vec;
diff --git a/soroban-env-host/src/test/ledger.rs b/soroban-env-host/src/test/ledger.rs
new file mode 100644
index 00000000..54aabb55
--- /dev/null
+++ b/soroban-env-host/src/test/ledger.rs
@@ -0,0 +1,71 @@
+use soroban_env_common::CheckedEnv;
+
+use crate::{
+    budget::Budget,
+    host::metered_map::MeteredOrdMap,
+    storage::{Footprint, Storage},
+    Host, HostError, LedgerInfo,
+};
+
+#[test]
+fn ledger_network_passphrase() -> Result<(), HostError> {
+    let budget = Budget::default();
+    let storage = Storage::with_enforcing_footprint_and_map(
+        Footprint::default(),
+        MeteredOrdMap {
+            budget: budget.clone(),
+            map: im_rc::OrdMap::default(),
+        },
+    );
+
+    let host = Host::with_storage_and_budget(storage, budget);
+    host.set_ledger_info(LedgerInfo {
+        protocol_version: 0,
+        sequence_number: 0,
+        timestamp: 0,
+        network_passphrase: "Public Global Stellar Network ; September 2015"
+            .as_bytes()
+            .to_vec(),
+        base_reserve: 0,
+    });
+    let obj = host.get_ledger_network_passphrase()?;
+    let np = host.visit_obj(obj, |np: &Vec<u8>| Ok(np.clone()))?;
+    assert_eq!(
+        np,
+        "Public Global Stellar Network ; September 2015"
+            .as_bytes()
+            .to_vec(),
+    );
+    Ok(())
+}
+
+#[test]
+fn ledger_network_id() -> Result<(), HostError> {
+    let budget = Budget::default();
+    let storage = Storage::with_enforcing_footprint_and_map(
+        Footprint::default(),
+        MeteredOrdMap {
+            budget: budget.clone(),
+            map: im_rc::OrdMap::default(),
+        },
+    );
+
+    let host = Host::with_storage_and_budget(storage, budget);
+    host.set_ledger_info(LedgerInfo {
+        protocol_version: 0,
+        sequence_number: 0,
+        timestamp: 0,
+        network_passphrase: "Public Global Stellar Network ; September 2015"
+            .as_bytes()
+            .to_vec(),
+        base_reserve: 0,
+    });
+    let obj = host.get_ledger_network_id()?;
+    let np = host.visit_obj(obj, |np: &Vec<u8>| Ok(np.clone()))?;
+    assert_eq!(
+        np,
+        bytes_lit::bytes!(0x7ac33997544e3175d266bd022439b22cdb16508c01163f26e5cb2a3e1045a979)
+            .to_vec(),
+    );
+    Ok(())
+}