Repository: CosmWasm/cosmwasm
Pull request #1077 (2021-09-07 13:21:17)

Add transaction index to Env
This came up in chat recently. With the transaction index we have a global tx identifier `(block height, transaction index)` that avoids exposing transaction hash.

Exposing block hash and transaction hash is not desired since people will use those for poor RNGs.

ethanfrey: The problem is that I think the tx index is not exposed in Cosmos SDK.
I do not see it listed here: https://github.com/cosmos/cosmos-sdk/blob/v0.42.9/types/context.go

Maybe @alpe knows more, but we need to make sure it is feasible to support without forking the SDK. *sigh*
assafmo: I love it!

A few notes:
1. I feel like `index` is part of the block, as it's the total gas that was used in the current block up until now.
2. Why is `TransactionInfo` an `Option`? When are `exec` or init `called` not inside a transaction?

IMO `index` can just be another field in `BlockInfo`.
webmaster128: > The problem is that I think the tx index is not exposed in Cosmos SDK.
> I do not see it listed here: https://github.com/cosmos/cosmos-sdk/blob/v0.42.9/types/context.go
> 
> Maybe @alpe knows more, but we need to make sure it is feasible to support without forking the SDK. _sigh_

Looking at https://github.com/CosmWasm/wasmd/blob/v0.18.0/proto/cosmwasm/wasm/v1/types.proto#L123-L131 and https://github.com/CosmWasm/wasmd/blob/v0.18.0/x/wasm/types/types.go#L206-L222 it seems you are right and we do not get a simple counter.
webmaster128: > Why is `TransactionInfo` an `Option`? When are `exec` or init `called` not inside a transaction?

Messages do not have to be executed as part of a transaction. They can for example be executed in [BeginBlocker or EndBlocker](https://docs.cosmos.network/v0.43/building-modules/beginblock-endblock.html).
ethanfrey: > > The problem is that I think the tx index is not exposed in Cosmos SDK.
> > I do not see it listed here: https://github.com/cosmos/cosmos-sdk/blob/v0.42.9/types/context.go
> > Maybe @alpe knows more, but we need to make sure it is feasible to support without forking the SDK. _sigh_
> 
> Looking at https://github.com/CosmWasm/wasmd/blob/v0.18.0/proto/cosmwasm/wasm/v1/types.proto#L123-L131 and https://github.com/CosmWasm/wasmd/blob/v0.18.0/x/wasm/types/types.go#L206-L222 it seems you are right and we do not get a simple counter.

Good eye. And the use of "GasConsumed" as a monotonic counter is actually quite useful. It also works when 3 messages are called in part of the same transaction. It is actually a quite clever way to order the various calls.

However, it may not provide @assafmo with what he wants to uniquely identify the transaction in the block.
webmaster128: Yeah, the gas meter is nice for ordering but not helpful when used together with external tooling. E.g. it would be nice to be able to find a transation (10985258, 0) or (10985258, 3) by simply looking at a tool like this: https://www.mintscan.io/cosmos/blocks/id/10985258
assafmo: > However, it may not provide @assafmo with what he wants to uniquely identify the transaction in the block.

@ethanfrey that's even better, we can distinguish between contract calls. I'm not really sure if that's necessary but it's pretty cool.
alpe: 🤔  I have not found any information about the use case this patch should handle. The "gas based position" would just be an incrementing counter with random steps. Can you not achieve the same in the contract itself with a counter per block?
A submessage sent to the same contract would have a higher gas index while it is in the same TX.
If a TX index is what we really need then I would investigate and try to make this happen.

webmaster128: > I have not found any information about the use case this patch should handle.

The original motivation is somewhere in a private chat, sorry. The contract should have the option of accurately describing when an event happened. A proposed solution was to expose the transaction hash. If we do that, we have to expose block hash as well for consistentcy. And we know once we expose those hashes, people use them for bad RNGs.

> Can you not achieve the same in the contract itself with a counter per block?

The contract can only know how often per block height it was called. This can solve some of the problem but makes it very hard to use in combination with other tooling. Something like _the 3rd contract call on height 6484654_ barely allows you to find the transaction in a explorer or other client. It also requires costy storage writes.

>  If a TX index is what we really need then I would investigate and try to make this happen.

It would be amazing if we could implement that.
webmaster128: The wasmd counter part is https://github.com/CosmWasm/wasmd/issues/601. Alex says he can make it happen 🧙
reuvenpo: Please note that a simple counter that says "this is the third time the contract was called in this block" is problematic for the following reasons:
- It doesn't let us easily find which tx the call was included in (main motivation)
- In complex callback chains a contract might be called multiple times in the same tx. This can happen either when contracts call back between each other, or when a contract is called multiple times with different arguments (e.g. sending CW-20 to multiple recipients). Do we want to distinguish those multiple calls? A contract counter would help here but it feels like an incomplete solution. The issue is that if a contract's counter says "5", that still might be in the first tx of the block. CC @assafmo 
- Do queries increment the counter? Should they?
assafmo: Also, I think you should not be concerned by misusing txhash for creating random numbers. Professional devs wouldn't do it, and if a dev is unaware of how problematic it is to get random numbers in blockchain then nothing will help. :shrug: 
webmaster128: > Also, I think you should not be concerned by misusing txhash for creating random numbers. Professional devs wouldn't do it, and if a dev is unaware of how problematic it is to get random numbers in blockchain then nothing will help them. 🤷

I was thinking in that direction as well alredy.

However, for the sake of consistency we should either expose both block hash and tx hash or none of the two.
assafmo: > For the sake of consistency we should either expose both block hash and tx hash or none of the two.

Then I vote for exposing both of them.

Call counter - I think it'll just be confusing and ugly in `x/wasmd`.
Block gas index - Might be interesting, but I don't see the use case for it.
reuvenpo: > Block gas index - Might be interesting, but I don't see the use case for it.

I was gonna say that it's useful for when the same contract gets called twice in the same TX - then you can imagine a scenario where a contract sends funds to multiple accounts in one tx from its own (or someone else's) balance. But then if all events are registered in an internal event log, even if they are all registered with the same txhash, then you can still see the order of events inside the tx just by the order in which events were registered.
assafmo: Okay, I don't think that's very interesting, but I get it. Also, you can have multiple messages in the same tx, so is the counter resets for every message? I'd argue that you have to add more context for that (E.g. `{"msg":0, "call":3}`), but again I don't think that's very interesting and the code for it in `x/wasmd` will be very ugly. 
reuvenpo: Yeah, I agree with that.
So... what interface do we want, then?
block time + block number + block hash + tx hash?
alpe: I am getting confused. The original discussion was about the TX index in Env. Is this still relevant or does this not work for you? A TX can have multiple messages though.
webmaster128: block hash + tx hash is a different story and I think we should keep it out of here. Maybe we do it, maybe not. Let's go with tx index for now.
webmaster128: Feel free to continue the block hash/tx hash discussion in https://github.com/CosmWasm/cosmwasm/issues/1086.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bf77f6173..c12b041c6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,8 @@ and this project adheres to
 
 - cosmwasm-std: Add new `WasmQuery::ContractInfo` variant to get metadata about
   the contract, like `code_id` and `admin`.
+- cosmwasm-std: New field `Env::transaction` containing info of the transaction
+  the contract call was executed in.
 
 ### Changed
 
diff --git a/packages/std/src/lib.rs b/packages/std/src/lib.rs
index 0a9e1a030..a6af5f57e 100644
--- a/packages/std/src/lib.rs
+++ b/packages/std/src/lib.rs
@@ -64,7 +64,7 @@ pub use crate::serde::{from_binary, from_slice, to_binary, to_vec};
 pub use crate::storage::MemoryStorage;
 pub use crate::timestamp::Timestamp;
 pub use crate::traits::{Api, Querier, QuerierResult, QuerierWrapper, Storage};
-pub use crate::types::{BlockInfo, ContractInfo, Env, MessageInfo};
+pub use crate::types::{BlockInfo, ContractInfo, Env, MessageInfo, TransactionInfo};
 
 // Exposed in wasm build only
 
diff --git a/packages/std/src/mock.rs b/packages/std/src/mock.rs
index ca1f6cfc3..e46f3e543 100644
--- a/packages/std/src/mock.rs
+++ b/packages/std/src/mock.rs
@@ -28,7 +28,7 @@ use crate::serde::{from_slice, to_binary};
 use crate::storage::MemoryStorage;
 use crate::timestamp::Timestamp;
 use crate::traits::{Api, Querier, QuerierResult};
-use crate::types::{BlockInfo, ContractInfo, Env, MessageInfo};
+use crate::types::{BlockInfo, ContractInfo, Env, MessageInfo, TransactionInfo};
 use crate::Attribute;
 
 pub const MOCK_CONTRACT_ADDR: &str = "cosmos2contract";
@@ -209,6 +209,7 @@ pub fn mock_env() -> Env {
             time: Timestamp::from_nanos(1_571_797_419_879_305_533),
             chain_id: "cosmos-testnet-14002".to_string(),
         },
+        transaction: Some(TransactionInfo { index: 3 }),
         contract: ContractInfo {
             address: Addr::unchecked(MOCK_CONTRACT_ADDR),
         },
diff --git a/packages/std/src/types.rs b/packages/std/src/types.rs
index 9b182ec80..790ed62c9 100644
--- a/packages/std/src/types.rs
+++ b/packages/std/src/types.rs
@@ -7,9 +7,24 @@ use crate::timestamp::Timestamp;
 #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
 pub struct Env {
     pub block: BlockInfo,
+    /// Information on the transaction this message was executed in.
+    /// The field is unset when the `MsgExecuteContract`/`MsgInstantiateContract`/`MsgMigrateContract`
+    /// is not executed as part of a transaction.
+    pub transaction: Option<TransactionInfo>,
     pub contract: ContractInfo,
 }
 
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
+pub struct TransactionInfo {
+    /// The position of this transaction in the block. The first
+    /// transaction has index 0.
+    ///
+    /// This allows you to get a unique transaction indentifier in this chain
+    /// using the pair (`env.block.height`, `env.transaction.index`).
+    ///
+    pub index: u32,
+}
+
 #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
 pub struct BlockInfo {
     /// The height of a block is the number of blocks preceding it in the blockchain.
@@ -24,13 +39,14 @@ pub struct BlockInfo {
     /// Using chrono:
     ///
     /// ```
-    /// # use cosmwasm_std::{Addr, BlockInfo, ContractInfo, Env, MessageInfo, Timestamp};
+    /// # use cosmwasm_std::{Addr, BlockInfo, ContractInfo, Env, MessageInfo, Timestamp, TransactionInfo};
     /// # let env = Env {
     /// #     block: BlockInfo {
     /// #         height: 12_345,
     /// #         time: Timestamp::from_nanos(1_571_797_419_879_305_533),
     /// #         chain_id: "cosmos-testnet-14002".to_string(),
     /// #     },
+    /// #     transaction: Some(TransactionInfo { index: 3 }),
     /// #     contract: ContractInfo {
     /// #         address: Addr::unchecked("contract"),
     /// #     },
@@ -45,13 +61,14 @@ pub struct BlockInfo {
     /// Creating a simple millisecond-precision timestamp (as used in JavaScript):
     ///
     /// ```
-    /// # use cosmwasm_std::{Addr, BlockInfo, ContractInfo, Env, MessageInfo, Timestamp};
+    /// # use cosmwasm_std::{Addr, BlockInfo, ContractInfo, Env, MessageInfo, Timestamp, TransactionInfo};
     /// # let env = Env {
     /// #     block: BlockInfo {
     /// #         height: 12_345,
     /// #         time: Timestamp::from_nanos(1_571_797_419_879_305_533),
     /// #         chain_id: "cosmos-testnet-14002".to_string(),
     /// #     },
+    /// #     transaction: Some(TransactionInfo { index: 3 }),
     /// #     contract: ContractInfo {
     /// #         address: Addr::unchecked("contract"),
     /// #     },
diff --git a/packages/vm/src/instance.rs b/packages/vm/src/instance.rs
index f0cb0253f..325bbbb49 100644
--- a/packages/vm/src/instance.rs
+++ b/packages/vm/src/instance.rs
@@ -691,7 +691,7 @@ mod tests {
 
         let report2 = instance.create_gas_report();
         assert_eq!(report2.used_externally, 73);
-        assert_eq!(report2.used_internally, 36837);
+        assert_eq!(report2.used_internally, 39202);
         assert_eq!(report2.limit, LIMIT);
         assert_eq!(
             report2.remaining,
@@ -890,7 +890,7 @@ mod singlepass_tests {
             .unwrap();
 
         let init_used = orig_gas - instance.get_gas_left();
-        assert_eq!(init_used, 36910);
+        assert_eq!(init_used, 39275);
     }
 
     #[test]
@@ -913,7 +913,7 @@ mod singlepass_tests {
             .unwrap();
 
         let execute_used = gas_before_execute - instance.get_gas_left();
-        assert_eq!(execute_used, 159891);
+        assert_eq!(execute_used, 162233);
     }
 
     #[test]
@@ -947,6 +947,6 @@ mod singlepass_tests {
         assert_eq!(answer.as_slice(), b"{\"verifier\":\"verifies\"}");
 
         let query_used = gas_before_query - instance.get_gas_left();
-        assert_eq!(query_used, 28274);
+        assert_eq!(query_used, 30646);
     }
 }


Test patch:
diff --git a/packages/vm/src/testing/mock.rs b/packages/vm/src/testing/mock.rs
index e71094df7..08ad0bc6a 100644
--- a/packages/vm/src/testing/mock.rs
+++ b/packages/vm/src/testing/mock.rs
@@ -1,5 +1,7 @@
 use cosmwasm_std::testing::{digit_sum, riffle_shuffle};
-use cosmwasm_std::{Addr, BlockInfo, Coin, ContractInfo, Env, MessageInfo, Timestamp};
+use cosmwasm_std::{
+    Addr, BlockInfo, Coin, ContractInfo, Env, MessageInfo, Timestamp, TransactionInfo,
+};
 
 use super::querier::MockQuerier;
 use super::storage::MockStorage;
@@ -160,6 +162,7 @@ pub fn mock_env() -> Env {
             time: Timestamp::from_nanos(1_571_797_419_879_305_533),
             chain_id: "cosmos-testnet-14002".to_string(),
         },
+        transaction: Some(TransactionInfo { index: 3 }),
         contract: ContractInfo {
             address: Addr::unchecked(MOCK_CONTRACT_ADDR),
         },