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

Validator set pruning using relayed Validator sets #327

Merged
merged 7 commits into from
May 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions module/proto/gravity/v1/attestation.proto
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ enum ClaimType {
CLAIM_TYPE_WITHDRAW = 2;
CLAIM_TYPE_ERC20_DEPLOYED = 3;
CLAIM_TYPE_LOGIC_CALL_EXECUTED = 4;
CLAIM_TYPE_VALSET_UPDATED = 5;
}

// Attestation is an aggregate of `claims` that eventually becomes `observed` by
Expand Down
16 changes: 16 additions & 0 deletions module/proto/gravity/v1/msgs.proto
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package gravity.v1;
import "cosmos/base/v1beta1/coin.proto";
import "gogoproto/gogo.proto";
import "google/api/annotations.proto";
import "gravity/v1/types.proto";
option go_package = "github.com/cosmos/gravity-bridge/module/x/gravity/types";

// Msg defines the state transitions possible within gravity
Expand All @@ -28,6 +29,9 @@ service Msg {
rpc WithdrawClaim(MsgWithdrawClaim) returns (MsgWithdrawClaimResponse) {
option (google.api.http).post = "/gravity/v1/withdraw_claim";
}
rpc ValsetUpdateClaim(MsgValsetUpdatedClaim) returns (MsgValsetUpdatedClaimResponse) {
option (google.api.http).post = "/gravity/v1/valset_updated_claim";
}
rpc ERC20DeployedClaim(MsgERC20DeployedClaim)
returns (MsgERC20DeployedClaimResponse) {
option (google.api.http).post = "/gravity/v1/erc20_deployed_claim";
Expand Down Expand Up @@ -226,6 +230,18 @@ message MsgLogicCallExecutedClaim {

message MsgLogicCallExecutedClaimResponse {}

// This informs the Cosmos module that a validator
// set has been updated.
message MsgValsetUpdatedClaim {
uint64 event_nonce = 1;
uint64 valset_nonce = 2;
uint64 block_height = 3;
repeated BridgeValidator members = 4;
string orchestrator = 6;
}

message MsgValsetUpdatedClaimResponse {}

// This call allows the sender (and only the sender)
// to cancel a given MsgSendToEth and recieve a refund
// of the tokens
Expand Down
32 changes: 27 additions & 5 deletions module/x/gravity/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,20 @@ import (

// EndBlocker is called at the end of every block
func EndBlocker(ctx sdk.Context, k keeper.Keeper) {
params := k.GetParams(ctx)
// Question: what here can be epoched?
slashing(ctx, k)
attestationTally(ctx, k)
cleanupTimedOutBatches(ctx, k)
cleanupTimedOutLogicCalls(ctx, k)
createValsets(ctx, k)
pruneValsets(ctx, k, params)
// TODO: prune claims, attestations when they pass in the handler
Copy link
Member

Choose a reason for hiding this comment

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

please open a issue.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

}

func createValsets(ctx sdk.Context, k keeper.Keeper) {
// Auto ValsetRequest Creation.
// WARNING: do not use k.GetLastObservedValset in this function, it *will* result in losing control of the bridge
// 1. If there are no valset requests, create a new one.
// 2. If there is at least one validator who started unbonding in current block. (we persist last unbonded block height in hooks.go)
// This will make sure the unbonding validator has to provide an attestation to a new Valset
Expand All @@ -33,18 +37,36 @@ func createValsets(ctx sdk.Context, k keeper.Keeper) {
}
}

func pruneValsets(ctx sdk.Context, k keeper.Keeper, params types.Params) {
// Validator set pruning
// prune all validator sets with a nonce less than the
// last observed nonce, they can't be submitted any longer
//
// Only prune valsets after the signed valsets window has passed
// so that slashing can occur the block before we remove them
lastObserved := k.GetLastObservedValset(ctx)
currentBlock := uint64(ctx.BlockHeight())
tooEarly := currentBlock < params.SignedValsetsWindow
if lastObserved != nil && !tooEarly {
earliestToPrune := currentBlock - params.SignedValsetsWindow
sets := k.GetValsets(ctx)
for _, set := range sets {
if set.Nonce < lastObserved.Nonce && set.Height < earliestToPrune {
k.DeleteValset(ctx, set.Nonce)
}
}
}
}

func slashing(ctx sdk.Context, k keeper.Keeper) {

params := k.GetParams(ctx)

// Slash validator for not confirming valset requests, batch requests and not attesting claims rightfully
// Slash validator for not confirming valset requests, batch requests
ValsetSlashing(ctx, k, params)
BatchSlashing(ctx, k, params)
// TODO slashing for arbitrary logic is missing
// TODO slashing for arbitrary logic signatures is missing

// TODO: prune validator sets, older than 6 months, this time is chosen out of an abundance of caution
// TODO: prune outgoing tx batches while looping over them above, older than 15h and confirmed
// TODO: prune claims, attestations
}

// Iterate over all attestations currently being voted on in order of nonce and
Expand Down
3 changes: 3 additions & 0 deletions module/x/gravity/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ func NewHandler(k keeper.Keeper) sdk.Handler {
case *types.MsgLogicCallExecutedClaim:
res, err := msgServer.LogicCallExecutedClaim(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)
case *types.MsgValsetUpdatedClaim:
res, err := msgServer.ValsetUpdateClaim(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)

default:
return nil, sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, fmt.Sprintf("Unrecognized Gravity Msg type: %v", msg.Type()))
Expand Down
22 changes: 22 additions & 0 deletions module/x/gravity/keeper/attestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,28 @@ func (k Keeper) SetLastObservedEthereumBlockHeight(ctx sdk.Context, ethereumHeig
store.Set(types.LastObservedEthereumBlockHeightKey, k.cdc.MustMarshalBinaryBare(&height))
}

// GetLastObservedValset retrieves the last observed validator set from the store
// WARNING: This value is not an up to date validator set on Ethereum, it is a validator set
// that AT ONE POINT was the one in the Gravity bridge on Ethereum. If you assume that it's up
// to date you may break the bridge
func (k Keeper) GetLastObservedValset(ctx sdk.Context) *types.Valset {
store := ctx.KVStore(k.storeKey)
bytes := store.Get(types.LastObservedValsetKey)

if len(bytes) == 0 {
return nil
}
valset := types.Valset{}
k.cdc.MustUnmarshalBinaryBare(bytes, &valset)
return &valset
}

// SetLastObservedValset updates the last observed validator set in the store
func (k Keeper) SetLastObservedValset(ctx sdk.Context, valset types.Valset) {
store := ctx.KVStore(k.storeKey)
store.Set(types.LastObservedValsetKey, k.cdc.MustMarshalBinaryBare(&valset))
}

// setLastObservedEventNonce sets the latest observed event nonce
func (k Keeper) setLastObservedEventNonce(ctx sdk.Context, nonce uint64) {
store := ctx.KVStore(k.storeKey)
Expand Down
8 changes: 8 additions & 0 deletions module/x/gravity/keeper/attestation_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,14 @@ func (a AttestationHandler) Handle(ctx sdk.Context, att types.Attestation, claim

// Add to denom-erc20 mapping
a.keeper.setCosmosOriginatedDenomToERC20(ctx, claim.CosmosDenom, claim.TokenContract)
case *types.MsgValsetUpdatedClaim:
// TODO here we should check the contents of the validator set against
Copy link
Member

Choose a reason for hiding this comment

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

please open an issue.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

// the store, if they differ we should take some action to indicate to the
// user that bridge highjacking has occurred
a.keeper.SetLastObservedValset(ctx, types.Valset{
Nonce: claim.ValsetNonce,
Members: claim.Members,
})

default:
return sdkerrors.Wrapf(types.ErrInvalid, "event type: %s", claim.GetType())
Expand Down
40 changes: 40 additions & 0 deletions module/x/gravity/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,46 @@ func (k msgServer) LogicCallExecutedClaim(c context.Context, msg *types.MsgLogic
return &types.MsgLogicCallExecutedClaimResponse{}, nil
}

// ValsetUpdatedClaim handles claims for executing a validator set update on Ethereum
func (k msgServer) ValsetUpdateClaim(c context.Context, msg *types.MsgValsetUpdatedClaim) (*types.MsgValsetUpdatedClaimResponse, error) {
ctx := sdk.UnwrapSDKContext(c)

orchaddr, _ := sdk.AccAddressFromBech32(msg.Orchestrator)
validator := k.GetOrchestratorValidator(ctx, orchaddr)
if validator == nil {
return nil, sdkerrors.Wrap(types.ErrUnknown, "validator")
}

// return an error if the validator isn't in the active set
val := k.StakingKeeper.Validator(ctx, validator)
if val == nil || !val.IsBonded() {
return nil, sdkerrors.Wrap(sdkerrors.ErrorInvalidSigner, "validator not in acitve set")
}

any, err := codectypes.NewAnyWithValue(msg)
if err != nil {
return nil, err
}

// Add the claim to the store
_, err = k.Attest(ctx, msg, any)
if err != nil {
return nil, sdkerrors.Wrap(err, "create attestation")
}

// Emit the handle message event
ctx.EventManager().EmitEvent(
sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, msg.Type()),
Copy link
Member

Choose a reason for hiding this comment

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

The message type is not the module.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this is generally the pattern we use in the file. So if this is an issue we should open an issue and change it throughout the fie.

// TODO: maybe return something better here? is this the right string representation?
sdk.NewAttribute(types.AttributeKeyAttestationID, string(types.GetAttestationKey(msg.EventNonce, msg.ClaimHash()))),
),
)

return &types.MsgValsetUpdatedClaimResponse{}, nil
}

func (k msgServer) CancelSendToEth(c context.Context, msg *types.MsgCancelSendToEth) (*types.MsgCancelSendToEthResponse, error) {
ctx := sdk.UnwrapSDKContext(c)
sender, err := sdk.AccAddressFromBech32(msg.Sender)
Expand Down
62 changes: 33 additions & 29 deletions module/x/gravity/types/attestation.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions module/x/gravity/types/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ func RegisterInterfaces(registry types.InterfaceRegistry) {
&MsgERC20DeployedClaim{},
&MsgSetOrchestratorAddress{},
&MsgLogicCallExecutedClaim{},
&MsgValsetUpdatedClaim{},
&MsgCancelSendToEth{},
)

Expand All @@ -37,6 +38,7 @@ func RegisterInterfaces(registry types.InterfaceRegistry) {
&MsgWithdrawClaim{},
&MsgERC20DeployedClaim{},
&MsgLogicCallExecutedClaim{},
&MsgValsetUpdatedClaim{},
)

msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc)
Expand All @@ -56,6 +58,7 @@ func RegisterCodec(cdc *codec.LegacyAmino) {
cdc.RegisterConcrete(&MsgWithdrawClaim{}, "gravity/MsgWithdrawClaim", nil)
cdc.RegisterConcrete(&MsgERC20DeployedClaim{}, "gravity/MsgERC20DeployedClaim", nil)
cdc.RegisterConcrete(&MsgLogicCallExecutedClaim{}, "gravity/MsgLogicCallExecutedClaim", nil)
cdc.RegisterConcrete(&MsgValsetUpdatedClaim{}, "gravity/MsgValsetUpdatedClaim", nil)
cdc.RegisterConcrete(&OutgoingTxBatch{}, "gravity/OutgoingTxBatch", nil)
cdc.RegisterConcrete(&MsgCancelSendToEth{}, "gravity/MsgCancelSendToEth", nil)
cdc.RegisterConcrete(&OutgoingTransferTx{}, "gravity/OutgoingTransferTx", nil)
Expand Down
12 changes: 9 additions & 3 deletions module/x/gravity/types/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,6 @@ var (
// KeyOutgoingLogicConfirm indexes the outgoing logic confirms
KeyOutgoingLogicConfirm = []byte{0xae}

// LastObservedEthereumBlockHeightKey indexes the latest Ethereum block height
LastObservedEthereumBlockHeightKey = []byte{0xf9}

// DenomToERC20Key prefixes the index of Cosmos originated asset denoms to ERC20s
DenomToERC20Key = []byte{0xf3}

Expand All @@ -111,6 +108,15 @@ var (

// LastUnBondingBlockHeight indexes the last validator unbonding block height
LastUnBondingBlockHeight = []byte{0xf8}

// LastObservedEthereumBlockHeightKey indexes the latest Ethereum block height
LastObservedEthereumBlockHeightKey = []byte{0xf9}

// LastObservedValsetNonceKey indexes the latest observed valset nonce
// HERE THERE BE DRAGONS, do not use this value as an up to date validator set
// on Ethereum it will always lag significantly and may be totally wrong at some
// times.
LastObservedValsetKey = []byte{0xfa}
)

// GetOrchestratorAddressKey returns the following key format
Expand Down
Loading