Timing constraints in a Tendermint application

Originally posted on Riot by @zvezdin

Hello! I’m trying to implement timing constraints in a Tendermint application. The goal is to request nodes to send a specific transaction every, say, round hour and not before or later than that (with a few seconds tolerance). How can the consensus enforce that?

One option is to check the blocktime when receiving a block. However, this allows the block creator to censor transactions from specific nodes (not include them in a block and by the time they can get included in the next block, enough time will have passed to invalidate them). Also as I understand by the BFT-Time section in the documentation, the blocktime of a block is based on the median time of the commits for the previous block (essentially being the time of the previous block).

Another option is to include custom checks before block creation, when including a transaction in the mempool or distributing it to other peers. The node can check the system time when it received the transaction and reject the transaction if it is not received in the expected time. This is a source of non-determinism in the system, though. And as I understand, not every node in the network receives the transaction to include it in its mempool before block creation. A possible workaround is for validators to vote nil for blocks that include transactions that they have not received previously to validate their time for submission. Once a block is committed, no more timing checks will be done by regular nodes.

Is my understanding for all these concepts correct and how would you recommend to implement such functionality?

Are you sure it requires the information be sent by a particular user? The app already gets access to the block time through the header, and can run logic in BeginBlock or EndBlock, so you can already run time-based logic without requiring a transaction to trigger it!

Otherwise, if you want block validation to depend on the tx validity, you might need to wait until we support something like https://github.com/tendermint/tendermint/issues/2639.

As I mentioned about the blocktime (from the block header) above, that time is the mean commit time of the previous block. I could try to work around this by accepting the transaction and only making state changes when the next block arrives (so I know the block time of the transaction in question).

Yet an issue is that a malicious miner can intentionally not include a transaction in a block where it would be considered valid and instead leave it in the mempool. By the time it is included in the next block, it may already be considered invalid.

Yet an issue is that a malicious miner can intentionally not include a transaction in a block where it would be considered valid and instead leave it in the mempool. By the time it is included in the next block, it may already be considered invalid.

You’ll need to write a client that sends a transaction to N random nodes (not just one) to avoid censorship. It lowers the probability, but there’s still a chance that large enough cabal group will try to block specific transactions.

Another option is to include custom checks before block creation

This won’t solve the censorship problem.

This is about distributing the transaction in the network, but even if this is done, the block proposer still has a monopoly on which transactions it decides to include, right?

Actually this will allow validator nodes to mark the timestamp of when they initially saw the transaction, which means that when they vote on a block proposal, they will verify the validity of the transactions based on the time they saw them, not based on the block timestamp (meaning that no matter how late a transaction is included in a block, it will be valid if the validators saw it early enough).

While this approach looks nice on the surface, it creates a lot of potential weak spots so now I’m trying to look for ways to utilize the block timestamp (despite the potential block creator censorship). How can I receive the real timestamp of the transaction’s block (because what is in the header is the timestamp of the previous block)? The only way I can come up with is to wait for the next block (to receive the timestamp) and then verify and execute the transactions from the previous block. But there has to be a prettier approach.

Right. That’s why we rotate them. So the next proposer will likely to include the transaction.

What about the blocktime problem: How can I get the blocktime to validate transactions in the block if the time I receive is actually the time of the previous block. I don’t want to put these transactions “on hold” and wait for the next block before they are executed against the state machine. There has to be a prettier way, right?