You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

4.4 KiB

ADR 058: Event hashing

Changelog

  • 2020-07-17: initial version
  • 2020-07-27: fixes after Ismail and Ethan's comments
  • 2020-07-27: declined

Context

Before PR#4845, Header#LastResultsHash was a root of the Merkle tree built from DeliverTx results. Only Code, Data fields were included because Info and Log fields are non-deterministic.

At some point, we've added events to ResponseBeginBlock, ResponseEndBlock, and ResponseDeliverTx to give applications a way to attach some additional information to blocks / transactions.

Many applications seem to have started using them since.

However, before PR#4845 there was no way to prove that certain events were a part of the result (unless the application developer includes them into the state tree).

Hence, PR#4845 was opened. In it, GasWanted along with GasUsed are included when hashing DeliverTx results. Also, events from BeginBlock, EndBlock and DeliverTx results are hashed into the LastResultsHash as follows:

  • Since we do not expect BeginBlock and EndBlock to contain many events, these will be Protobuf encoded and included in the Merkle tree as leaves.
  • LastResultsHash therefore is the root hash of a Merkle tree w/ 3 leafs: proto-encoded ResponseBeginBlock#Events, root hash of a Merkle tree build from ResponseDeliverTx responses (Log, Info and Codespace fields are ignored), and proto-encoded ResponseEndBlock#Events.
  • Order of events is unchanged - same as received from the ABCI application.

Spec PR

While it's certainly good to be able to prove something, introducing new events or removing such becomes difficult because it breaks the LastResultsHash. It means that every time you add, remove or update an event, you'll need a hard-fork. And that is undoubtedly bad for applications, which are evolving and don't have a stable events set.

Decision

As a middle ground approach, the proposal is to add the Block#LastResultsEvents consensus parameter that is a list of all events that are to be hashed in the header.

@ proto/tendermint/abci/types.proto:295 @ message BlockParams {
  int64 max_bytes = 1;
  // Note: must be greater or equal to -1
  int64 max_gas = 2;
  // List of events, which will be hashed into the LastResultsHash
  repeated string last_results_events = 3;
}

Initially the list is empty. The ABCI application can change it via InitChain or EndBlock.

Example:

func (app *MyApp) DeliverTx(req types.RequestDeliverTx) types.ResponseDeliverTx {
    //...
    events := []abci.Event{
        {
            Type: "transfer",
            Attributes: []abci.EventAttribute{
                {Key: []byte("sender"), Value: []byte("Bob"), Index: true},
            },
        },
    }
    return types.ResponseDeliverTx{Code: code.CodeTypeOK, Events: events}
}

For "transfer" event to be hashed, the LastResultsEvents must contain a string "transfer".

Status

Declined

Until there's more stability/motivation/use-cases/demand, the decision is to push this entirely application side and just have apps which want events to be provable to insert them into their application-side merkle trees. Of course this puts more pressure on their application state and makes event proving application specific, but it might help built up a better sense of use-cases and how this ought to ultimately be done by Tendermint.

Consequences

Positive

  1. networks can perform parameter change proposals to update this list as new events are added
  2. allows networks to avoid having to do hard-forks
  3. events can still be added at-will to the application w/o breaking anything

Negative

  1. yet another consensus parameter
  2. more things to track in the tendermint state

References

Appendix A. Alternative proposals

The other proposal was to add Hash bool flag to the Event, similarly to Index bool EventAttribute's field. When true, Tendermint would hash it into the LastResultsEvents. The downside is that the logic is implicit and depends largely on the node's operator, who decides what application code to run. The above proposal makes it (the logic) explicit and easy to upgrade via governance.