Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: add ADR for Partial Set Security #1584

Merged
merged 26 commits into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
3c90fc8
added Partial Set Security ADR
insumity Jan 22, 2024
a86272d
fix indentation
insumity Jan 22, 2024
4a418c8
add on AllocateTokens
insumity Jan 22, 2024
3bdb04b
fixed MsgOptOut to not have a delay and added different way to opt in
insumity Jan 23, 2024
bedb15a
added some clarifications
insumity Jan 23, 2024
0a354ae
added note on downtime
insumity Jan 23, 2024
f3f6d01
Update docs/docs/adrs/adr-015-partial-set-security.md
insumity Jan 25, 2024
66122ad
Update docs/docs/adrs/adr-015-partial-set-security.md
insumity Jan 25, 2024
84e2a8f
Update docs/docs/adrs/adr-015-partial-set-security.md
insumity Jan 26, 2024
9989e54
Update docs/docs/adrs/adr-015-partial-set-security.md
insumity Jan 29, 2024
99182df
Update docs/docs/adrs/adr-015-partial-set-security.md
insumity Jan 29, 2024
fcf8f6b
Update docs/docs/adrs/adr-015-partial-set-security.md
insumity Jan 29, 2024
53113c1
Update docs/docs/adrs/adr-015-partial-set-security.md
insumity Jan 29, 2024
3bdfa2c
Update docs/docs/adrs/adr-015-partial-set-security.md
insumity Jan 29, 2024
0f9bd8b
Update docs/docs/adrs/adr-015-partial-set-security.md
insumity Jan 29, 2024
a6c705b
updated based on comments
insumity Jan 29, 2024
9ffa1e2
remove fraud votes
insumity Jan 29, 2024
45c796d
smaller updates
insumity Jan 29, 2024
0b56b0f
add consumer key in the opt in message
insumity Jan 29, 2024
4ccb925
remove partial set computation and reward pseudocode
insumity Jan 30, 2024
5ac5b12
changed top_N_fraction to int
insumity Jan 30, 2024
537da61
nit changes
insumity Jan 30, 2024
01a7097
remove fraction
insumity Jan 30, 2024
3dea3ff
Update docs/docs/adrs/adr-015-partial-set-security.md
insumity Jan 31, 2024
c1bd9b8
Update docs/docs/adrs/adr-015-partial-set-security.md
insumity Jan 31, 2024
2e5dd4c
applied some changes based on comments
insumity Jan 31, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
308 changes: 308 additions & 0 deletions docs/docs/adrs/adr-015-partial-set-security.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,308 @@
---
sidebar_position: 16
title: Partial Set Security
---
# ADR 015: Partial Set Security

## Changelog
* 2024-01-22: Proposed, first draft of ADR.

## Status

Proposed

## Context
Currently, in _Replicated Security_, the **whole** validator set of the Cosmos Hub _provider chain_ is used to secure consumer chains. There are at least three concerns with this approach. First, a big chunk of validators might be forced to validate consumer chains they are not interested in securing. Second, it is costly for small validators to secure additional chains. This concern is only partially addressed through [soft opt-out](https://github.com/cosmos/interchain-security/blob/main/docs/docs/adrs/adr-009-soft-opt-out.md) where the bottom (based on voting power) 5% validator can opt out from validating consumer chains. Third and for the above reasons, it is challenging for a new consumer chain to join Replicated Security.
insumity marked this conversation as resolved.
Show resolved Hide resolved

As a solution, we present _Partial Set Security_ (PSS). As the name suggests, PSS allows for only a subset of the validator set of the Cosmos Hub to secure a consumer chain. In what follows we propose the exact steps we need to take to implement PSS. This is a first iteration of Partial Set Security, and therefore we present the most minimal solution that achieves PSS possible.
insumity marked this conversation as resolved.
Show resolved Hide resolved

## Decision
In Replicated Security, 95% (see soft opt-out) of the top Cosmos Hub validators have to secure a consumer chain. In Partial Set Security (PSS) we introduce a parameter `N` that is associated with a consumer chain and require that the top `N%` of the Cosmos Hub validators have to secure this consumer chain. Additionally, we allow validators that do not belong to the top `N%` of the Cosmos Hub validators to dynamically opt in if they want to validate on the consumer chain or not.
insumity marked this conversation as resolved.
Show resolved Hide resolved
For example, if a consumer chain has `N = 95%`, then it ultimately receives the same security it receives today by Replicated Security. On the other hand, if a consumer chain has `N = 0`%, then no validator is forced to validate the chain but validators can opt in to do so instead.
insumity marked this conversation as resolved.
Show resolved Hide resolved
In what follows, we call a consumer chain _Top N_ if it has joined as Top N chain with `N > 0` and _Opt In_ chain otherwise. An Opt In consumer chain is secured only by the validators that have opted in to secure that chain.
insumity marked this conversation as resolved.
Show resolved Hide resolved
insumity marked this conversation as resolved.
Show resolved Hide resolved

We intend to implement PSS using a feature branch off [v3.3.0 interchain security](https://github.com/cosmos/interchain-security/tree/v3.3.0).
insumity marked this conversation as resolved.
Show resolved Hide resolved

### How do consumer chains join?
As a [simplification](https://forum.cosmos.network/t/pss-permissionless-vs-premissioned-lite-opt-in-consumer-chains/12984/17), a consumer chain can only join Partial Set Security through a governance proposal and not in a permissionless way.
insumity marked this conversation as resolved.
Show resolved Hide resolved

Consumer chains join Partial Set Security the same way chains now join Replicated Security (e.g., see [Stride](https://www.mintscan.io/cosmos/proposals/799)). We extend [`ConsumerAdditionProposal`](https://github.com/cosmos/interchain-security/blob/v3.3.0/proto/interchain_security/ccv/provider/v1/provider.proto#L27) with 2 optional fields:
`bool is_top_N`: flag that captures whether the consumer chain joins under the Top N case.
`int8 top_N`: value that captures the percentage of the Top N% validators that should validate this consumer chain. This is only set if `is_top_N` is `true`.
insumity marked this conversation as resolved.
Show resolved Hide resolved

Note that `top_N` resides between `[50, 95]`.
Assuming that at most `1/3` of validators could be malicious, by having 50% as the minimum value for `top_N` we guarantee that we cannot have a successful incorrect-execution attack on a Top N consumer chain. This is because, a Top N consumer chain with `N >= 50%` would have at least `1/3` correct validators that would be able to censor any incorrect-execution attack.
insumity marked this conversation as resolved.
Show resolved Hide resolved
Additionally, by having a `N >= 50%` and hence `N > 33%` we provide the ability for validators to `Veto` a `ConsumerAdditionProposal` if they do not desire to validate a consumer chain.
insumity marked this conversation as resolved.
Show resolved Hide resolved

`top_N` can be up to 95% to capture the current case of Replicated Security where we allow the bottom 5% of validators to opt out. There is no reason for `top_N` to be higher than 95%. Smaller chains that belong in the bottom 5% validators can choose to opt in if they want to validate.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that 5% in the soft opt-out is a param.


So, the fields can take the following values:
Copy link
Contributor

@jtremback jtremback Jan 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might change, not a huge deal. We may want to change how much we limit the number of top n options that are available, but that can be a very surface level thing here in the prop

| `is_top_N` | `top_N`|
| :--- | :---|
| `true` | `[50, 95]` |
| `false` | not set |


If a proposal has those arguments wrongly set, it should get rejected in [ValidateBasic](https://github.com/cosmos/interchain-security/blob/v3.3.0/x/ccv/provider/types/proposal.go#L86).

Note that we could easily distinguish whether a chain is _Top N_ or _Opt In_ by only having the single field `top_N` and if `top_N == 0`, then we’re in the _Opt In_ case, although for clarity, we introduce both fields.

In a future version of PSS, we intend to introduce a way to modify the parameters of a consumer chain, e.g, a chain that is _Opt In_ to become _Top N_, etc.

#### State & Query
We augment the provider module’s state to keep track of the `is_top_N` and `top_N` values for each consumer chain. The key to store this information would be:
insumity marked this conversation as resolved.
Show resolved Hide resolved

```
ChainIdWithLenKey(IsTopNBytePrefix, chainID)
insumity marked this conversation as resolved.
Show resolved Hide resolved
```
reusing [`ChainIdWithLenKey`](https://github.com/cosmos/interchain-security/blob/v3.3.0/x/ccv/provider/types/keys.go#L418).

Then in the [keeper](https://github.com/cosmos/interchain-security/blob/v3.3.0/x/ccv/provider/keeper/keeper.go) we introduce methods as follows:
```
func (k Keeper) SetIsTopN(ctx sdk.Context, chainID string, isTopN bool) error
insumity marked this conversation as resolved.
Show resolved Hide resolved
func (k Keeper) IsTopN(ctx sdk.Context, chainID string) bool

// returns the N if Top N chain, otherwise an error
func (k Keeper) GetTopN(ctx sdk.Context, chainID string) (uint8, error)
```

In a future version of PSS, we intend to extend the `interchain-security-pd query provider list-consumer-chains` query to also return information on whether a consumer chain is an Opt In or a Top N chain and with what N. This way, block explorers like [Mintscan](https://www.mintscan.io/) would show for a chain “This chain is secured by N% of Cosmos Hub.”
insumity marked this conversation as resolved.
Show resolved Hide resolved

### How do validators opt in?
A validator can opt in by sending a new type of message that we introduce in [tx.proto](https://github.com/cosmos/interchain-security/blob/v3.3.0/proto/interchain_security/ccv/provider/v1/tx.proto#L1).
```
message MsgOptIn {
insumity marked this conversation as resolved.
Show resolved Hide resolved
// the chain id of the consumer chain to opt in to
string chain_id = 1;
// the provider address of the validator on Cosmos Hub
string provider_addr = 2;
}
```
Note that in a Top N consumer chain, the top N% Cosmos Hub validators are forced by default to validate the consumer chain. Nevertheless, validators in the bottom 100 - N% can opt in to validate as well.
insumity marked this conversation as resolved.
Show resolved Hide resolved
Additionally, note that due to undelegations, etc. a validator that is in the top N% validators might fall down to the 100 - N% in which case this validator is not forced to validate anymore. Therefore, a validator that might move out of the top N% might want to send a `MsgOptIn` to keep validating even if this happens.
insumity marked this conversation as resolved.
Show resolved Hide resolved

Note that a validator can send a `MsgOptIn` message even if the consumer chain is not yet running. To do this we reuse the [`IsConsumerProposedOrRegistered`](https://github.com/cosmos/interchain-security/blob/v3.3.0/x/ccv/provider/keeper/key_assignment.go#L644). If the `chainID` does not exist, the `MsgOptIn` should fail, as well as if the provider address does not exist.

Additionally, a validator that opts in can then use `MsgAssignConsumerKey` to change the consumer key on the consumer chain as is currently done in Replicated Security.

#### State & Query
We store the list of validators that have opted in and at the block height they opted it in under the key:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not clear. What exactly is the state here?

```
ChainIdWithLenKey(OptedInByteKey, chainID)
```

And for simplicity for each validator we store a boolean on whether this validator has opted in on this chain or not.
insumity marked this conversation as resolved.
Show resolved Hide resolved
```
ChainIdAndConsAddrKey(OptedInByteKey, chainID, addr)
```

We augment the state of the provider to keep track for each chain all the validators that have opted in and if a specific validator has opted in.
insumity marked this conversation as resolved.
Show resolved Hide resolved
```
// returns all the validators that have opted in on chain `chainID`
func (k Keeper) GetOptedInValidators(ctx sdk.Context, chainID string) []Validators

func (k Keeper) IsValidatorOptedIn(ctx sdk.Context, chainID string, val Validator) bool
```

We introduce a query to retrieve the validators that are opted in. The query would operate as follows.
insumity marked this conversation as resolved.
Show resolved Hide resolved
```
gaiad query provider optedInValidators $chainId
insumity marked this conversation as resolved.
Show resolved Hide resolved
```

#### When do validators opt in?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To implement top-n, we should just have an automatic process every block or epoch or whatever, which automatically opts in validators in the top-n, just as if they had sent the message themselves.

Validators can not only opt in by sending a `MsgOptIn` message, but can also opt in automatically if they voted `Yes`
during the `ConsumerAdditionProposal` that introduced a consumer chain. This simplifies validators operations because
they do not have to send an additional message to opt in.

Because the `Tally` method [deletes the votes](https://github.com/cosmos/cosmos-sdk/blob/v0.47.7/x/gov/keeper/tally.go#L71) after reading them, we cannot check the votes of the validators after the votes have been tallied. To circumvent this, we introduce
a hook for [`AfterProposalVote`](https://github.com/cosmos/cosmos-sdk/blob/v0.47.7/x/gov/keeper/vote.go#L35) and keep track
of all the votes cast by a validator. If a validator votes more than once, we only consider the latest vote.
Finally, we only consider a validator has opted in if it casts a 100% `Yes` vote in case of a [weighted vote](https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-037-gov-split-vote.md).


### How do validators opt out?
insumity marked this conversation as resolved.
Show resolved Hide resolved
Validators that have opted in on a chain can opt out as well by sending the following message:

```
message MsgOptOut {
// the chain id of the consumer chain to opt out from
string chain_id = 1;
// the provider address of the validator on Cosmos Hub
string provider_addr = 2;
}
```
Validators can only opt out after a consumer chain has started and hence the above message returns an error if the chain with `chain_id` is not running.

#### State & Query
We also update the state of the opted-in validators when a validator has opted out by removing it from there.
Nevertheless, we have to keep track of all the validators that have opted in during the last `X` (to be defined) blocks to be able to jail validators for downtime.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I undertand the reason behind this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We only want to jail validators that were opted in and avoid a scenario where we act on a SlashPacket that asks us to jail a validator that never validated (i.e., opted in) on a consumer chain. For this, we need to keep historical info on which validators were opted in.

I rephrased to:

Note that we only punish validators for downtime that were opted in in a consumer chain. For this, we keep historical info of all the validators that have opted in during the last X (to be defined) blocks. This way we can jail validators for downtime knowing that indeed the validators have opted in at some point in the past.
Otherwise, we can think of a scenario where a validator V is down for a period of time, but before V gets punished for downtime, validator V opts out and then we do not know whether V should be punished or not.


### When does a consumer chain start?
A Top N consumer chain starts at the specified date (`spawn_time`) if the [`ConsumerAdditionProposal`](https://github.com/cosmos/interchain-security/blob/v3.3.0/proto/interchain_security/ccv/provider/v1/provider.proto#L27) has passed. An Opt In consumer chain starts if at least one validator has opted in. We check this in [BeginBlockInit](https://github.com/cosmos/interchain-security/blob/v3.3.0/x/ccv/provider/keeper/proposal.go#L372):
```
insumity marked this conversation as resolved.
Show resolved Hide resolved
func (k Keeper) BeginBlockInit(ctx sdk.Context) {
propsToExecute := k.GetConsumerAdditionPropsToExecute(ctx)

for _, prop := range propsToExecute {
chainId := prop.ChainId
if !k.IsTopN(ctx, chainId) && len(k.GetOptedInValidators(ctx, chainId)) == 0 {
// drop the proposal
ctx.Logger().Info("could not start chain because noone has opted in")
insumity marked this conversation as resolved.
Show resolved Hide resolved
continue
}
...
```

### How do we send the partial validator sets to the consumer chains?
To send the validator set to a consumer chain we first have to generate the validator updates with a function similar to this one:

```
func (k Keeper) GetPartialSetValidators(ctx types.Context, chainId string, valUpdates []abci.ValidatorUpdate) (newUpdates []abci.ValidatorUpdate)

powerSum := sdk.ZeroDec()
totalPower := k.ComputeTotalPower(ctx)
insumity marked this conversation as resolved.
Show resolved Hide resolved
topNThreshold := k.GetTopN(ctx, chainId) / 100
for _, val := range valUpdates {
powerSum = powerSum.Add(sdk.NewDecFromInt(sdk.NewInt(val.Power)))
// if powerSum / totalPower > topNThreshold
if k.IsTopN(ctx, chainId) && powerSum.Quo(totalPower).GT(topNThreshold) {
insumity marked this conversation as resolved.
Show resolved Hide resolved
// is Top N validator
updates = append(updates, abci.ValidatorUpdate{
PubKey: val.PubKey,
Power: val.Power,
})
} else {
if k.IsValidatorOptedIn(ctx, chainId, val.PubKey) {
updates = append(updates, abci.ValidatorUpdate{
PubKey: val.PubKey,
Power: val.Power,
})
} else {
// remove this validator
updates = append(updates, abci.ValidatorUpdate{
insumity marked this conversation as resolved.
Show resolved Hide resolved
PubKey: val.PubKey,
Power: 0,
})
}
}

return updates
}
```
And then we change `QueueVSCPackets` to:

```
// QueueVSCPackets queues latest validator updates for every registered consumer chain
func (k Keeper) QueueVSCPackets(ctx sdk.Context) {
valUpdateID := k.GetValidatorSetUpdateId(ctx) // current valset update ID
// Get the validator updates from the staking module.
// Note: GetValidatorUpdates panics if the updates provided by the x/staking module
// of cosmos-sdk is invalid.
valUpdates := k.stakingKeeper.GetValidatorUpdates(ctx)

for _, chain := range k.GetAllConsumerChains(ctx) {
// Apply the key assignment to the validator updates.
valUpdates := k.GetPartialSetValidators(ctx, chain.ChainId, valUpdates)
valUpdates := k.MustApplyKeyAssignmentToValUpdates(ctx, chain.ChainId, valUpdates)
...
```

Note that we use `0` to remove validators that are not opted in as described [here](https://docs.cometbft.com/v0.37/spec/abci/abci++_methods#endblock) and as is done for the [assignment of consumer keys](https://github.com/cosmos/interchain-security/blob/v3.3.0/x/ccv/provider/keeper/key_assignment.go#L525).

### How do we distribute rewards?
Currently, rewards are distributed as follows: The consumer [periodically sends rewards](https://github.com/cosmos/interchain-security/blob/v3.3.0/x/ccv/consumer/keeper/distribution.go#L148) on the provider `ConsumerRewardsPool` address.
The provider then [transfers those rewards to the fee collector address](https://github.com/cosmos/interchain-security/blob/v3.3.0/x/ccv/provider/keeper/distribution.go#L77) and those transferred rewards are distributed to validators and delegators.

In PSS, we distribute rewards only to validators that actually validate the consumer chain. To do this, we have a pool associated with each consumer chain and consumers IBC transfer the rewards to this pool.
Then, because we do not use the canonical fee pool and hence rewards are not automatically distributed, we will create a new and modified version of [AllocateTokens](https://github.com/cosmos/cosmos-sdk/blob/v0.47.7/x/distribution/keeper/allocation.go#L14) as follows:

```
func (k Keeper) AllocateTokensFromPool(ctx sdk.Context, poolAddress string, totalPreviousPower int64, bondedVotes []abci.VoteInfo) {
rewardsCollectedInt := k.bankKeeper.GetAllBalances(ctx, poolAddress)
// rest in the same that `AllocateTokens` operates
...
```
insumity marked this conversation as resolved.
Show resolved Hide resolved

In [TransferRewardsToFeeCollector](https://github.com/cosmos/interchain-security/blob/v3.3.0/x/ccv/provider/keeper/distribution.go#L61) we then do:

```
for _, chain := range consumerChains {
insumity marked this conversation as resolved.
Show resolved Hide resolved
...
totalPower := 0
var []abci.VoteInfo votes
for _, val := range (validators that opted in or Top N) {
mpoke marked this conversation as resolved.
Show resolved Hide resolved
if val has opted in for more than X blocks {
insumity marked this conversation as resolved.
Show resolved Hide resolved
votes = append(votes, []abci.VoteInfo{{Validator: val, SignedLastBlock: true}})
}
}

k.AllocateTokens(ctx, pool associated with this chain, totalPower, votes)
insumity marked this conversation as resolved.
Show resolved Hide resolved
...
}
```

instead of calling:
```
// 4. Transfer the balance to the fee collector address
err := k.bankKeeper.SendCoinsFromModuleToModule(
ctx,
types.ConsumerRewardsPool,
k.feeCollectorName,
sdk.NewCoins(balance),
)
```

Note that we would only distribute rewards to validators that are opted in but are opted in for some time (e.g., 10000 blocks) to avoid cases where validators opt in and out in short intervals just in order to receive rewards.

### Misbehaviour

#### Fraud votes
For fraud votes, we add a new type `FraudVoteProposal` similar to [EquivocationProposal](https://github.com/cosmos/interchain-security/pull/703). The time to detect incorrect execution evidence is 7 days (the same time as that of detecting a light client attack on a client with a `trustingPeriod` of 14 days); We have 7 days to detect an incorrect execution and then 14 days for the proposal to be accepted.
insumity marked this conversation as resolved.
Show resolved Hide resolved

##### When are fraud votes eligible?
Because fraud votes allow the slashing of a validator through a governance proposal, we are cautious on when a fraud vote is eligible and try to restrict their eligibility as much as possible to avoid an adversary proposing a bogus fraud vote.
As described earlier, because in Top N chains we consider that `N > 50%` we only consider validators that have opted in an Opt In chain. Fraud votes are also **only** intended to be used for **malicious** behaviour, for example, in a scenario similar to what [happened to Neutron](https://blog.neutron.org/neutron-halt-post-mortem-927dbe4540c8), validators should vote against slashing those validators.
insumity marked this conversation as resolved.
Show resolved Hide resolved

#### Message
```
message FraudVoteProposal {
insumity marked this conversation as resolved.
Show resolved Hide resolved
string title = 1;
string description = 2;

// validator that performed incorrect execution (consensus address on consumer chain)
Validator validator = 3;

// validator's vote contains BlockId on what was signed
Vote vote = 4;

// header for which the hash led to the blockId in the vote
Header header = 5;
}
```

The message includes the `vote` that was signed by the validator. Validators that vote on a fraud vote can look at the `BlockID` contained in a `vote` and the `appHash`, `lastResultHash`, etc. in the `header` (that led to this `BlockID`) to conclude whether the to-be-voted validator has performed incorrect execution or not.
insumity marked this conversation as resolved.
Show resolved Hide resolved

#### Double signing
We do not change the way slashing for double signing and light client attacks functions. If a validator misbehaves on a consumer, then we slash that validator on the provider.

#### Downtime
insumity marked this conversation as resolved.
Show resolved Hide resolved
We do not change the way downtime jailing functions. If a validator is down on a consumer chain for an adequate amount of time, we jail this validator on Cosmos Hub only if the validator was opted in in the recent past.

## Consequences

### Positive
- Easier for new consumer chains to consume Cosmos Hub security because proposals are more likely to pass if not everyone is forced to validate.
- Small validators are not forced to validate chains anymore if they do not want to.

### Negative
- Fraud votes introduce one more way to slash validators.
- Depending on whether a consumer chain is Top N or Opt In, a consumer chain might not be as secure as with Replicated Security.


## References

- [PSS: Permissionless vs premissioned-lite opt-in consumer chains](https://forum.cosmos.network/t/pss-permissionless-vs-premissioned-lite-opt-in-consumer-chains/12984)
- [CHIPs discussion phase: Partial Set Security (updated)](https://forum.cosmos.network/t/chips-discussion-phase-partial-set-security-updated/11775)
- [PSS: Exclusive vs Inclusive Top-N](https://forum.cosmos.network/t/pss-exclusive-vs-inclusive-top-n/13058)
- [Initial PSS ADR and notes #1518](https://github.com/cosmos/interchain-security/pull/1518)
- [Replicated vs. Mesh Security](https://informal.systems/blog/replicated-vs-mesh-security)
1 change: 1 addition & 0 deletions docs/docs/adrs/intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ To suggest an ADR, please make use of the [ADR template](./adr-template.md) prov

- [ADR 011: Improving testing and increasing confidence](./adr-011-improving-test-confidence.md)
- [ADR 014: Epochs](./adr-014-epochs.md)
- [ADR 015: Partial Set Security](./adr-015-partial-set-security.md)

### Rejected

Expand Down
Loading