cosmos / interchain-security

Replicated security (aka interchain security V1) is an open sourced IBC application which allows cosmos blockchains to lease their proof-of-stake security to one another.
https://cosmos.github.io/interchain-security/
Other
153 stars 115 forks source link

Rethink byte prefixing scheme #939

Open jtremback opened 1 year ago

jtremback commented 1 year ago

Protocol Change Proposal

EDIT: Use the solution propose in https://github.com/cosmos/interchain-security/issues/939#issuecomment-1549162175.

Summary

We currently use Go's iota operator to choose byte prefixes for our database keys. This is clean looking, but it can be brittle. If someone changes the order of the keys in the file, or adds or removes a key, it changes the prefixes of all following keys. This means that simple refactors can require database migrations. There are many alternatives but all of them have tradeoffs. I will discuss them here and we can decide on whether to change what we do.

Proposal

Key prefixing techniques:


For Admin Use

shaspitz commented 1 year ago

Either of the last two options seem solid to me

mpoke commented 1 year ago
  • Use iota, but simply have a rule to only ever append to the list. Might help to have the keys in a separate file to prevent people from being tempted to refactor. Having a separate file might also facilitate an automated tool that would run on CI and error if keys had been rearranged.

I like this solution as it's the simplest to adopt. Basically, we need better testing around keys. Any idea of a test ensures that keys are only appended?

mpoke commented 1 year ago
  • Write out bytes, e.g. []byte{0x04}

Having a single keys.go file with all the keys can enable also this solution. We could check then that all the keys are unique. However, if somebody adds a key to keys.go, but not to keys_test.go, then we have a problem.

mpoke commented 1 year ago

What about using a constant map?

func getKeyPrefix() map[string]byte {
    return map[string]byte{
        "PortByteKey": 0,
        "MaturedUnbondingOpsByteKey": 1,
        "ValidatorSetUpdateIdByteKey": 2,
    }
}

func PortKey() []byte {
    return []byte{getKey("PortByteKey")}
}

Like this we can easily test that the map contains only unique values. Also, it's only a refactor.

jtremback commented 1 year ago

@mpoke this does make a possible catastrophic key rearrangement impossible. It doesn't solve the problem 100%, since there's still the potential for the numbers to become scrambled and have gaps as refactors happen. But that will just cause compile errors and the worst case scenario is someone having to try again with a different number as they're writing the code and get an error.

To me, either this approach, or the one I tried here would be satisfactory. I'll leave it to you to make the final call.

mpoke commented 2 weeks ago

Reopening issue to deal with consumer changes.