-
Notifications
You must be signed in to change notification settings - Fork 170
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
checkpointing: add keeper and core state #27
Changes from 19 commits
0eb0d77
b943bfa
8f10629
a65243e
1bbcfcb
059e1ad
aa32378
1448f35
cd36a10
6d5a391
eb4d99a
d5c19e5
65fe7e8
121c227
70e2022
e1083f4
232dc96
f50e493
ec60435
16a6f1e
3e0805c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
package keeper | ||
|
||
import ( | ||
"github.com/babylonchain/babylon/x/checkpointing/types" | ||
"github.com/cosmos/cosmos-sdk/codec" | ||
"github.com/cosmos/cosmos-sdk/store/prefix" | ||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
) | ||
|
||
type BlsSigsState struct { | ||
cdc codec.BinaryCodec | ||
blsSigs sdk.KVStore | ||
hashToEpoch sdk.KVStore | ||
} | ||
|
||
func (k Keeper) BlsSigsState(ctx sdk.Context) BlsSigsState { | ||
// Build the BlsSigsState storage | ||
store := ctx.KVStore(k.storeKey) | ||
return BlsSigsState{ | ||
cdc: k.cdc, | ||
blsSigs: prefix.NewStore(store, types.BlsSigsPrefix), | ||
hashToEpoch: prefix.NewStore(store, types.BlsSigsHashToEpochPrefix), | ||
} | ||
} | ||
|
||
// CreateBlsSig inserts the bls sig into the hash->epoch and (epoch, hash)->bls sig storage | ||
func (bs BlsSigsState) CreateBlsSig(sig *types.BlsSig) { | ||
epoch := sig.GetEpochNum() | ||
sigHash := sig.Hash() | ||
blsSigsKey := types.BlsSigsObjectKey(epoch, sigHash) | ||
epochKey := types.BlsSigsEpochKey(sigHash) | ||
|
||
// save concrete bls sig object | ||
bs.blsSigs.Set(blsSigsKey, types.BlsSigToBytes(bs.cdc, sig)) | ||
// map bls sig to epoch | ||
bs.hashToEpoch.Set(epochKey, sdk.Uint64ToBigEndian(epoch)) | ||
} | ||
|
||
// GetBlsSigsByEpoch retrieves bls sigs by their epoch | ||
func (bs BlsSigsState) GetBlsSigsByEpoch(epoch uint64, f func(sig *types.BlsSig) bool) error { | ||
store := prefix.NewStore(bs.blsSigs, sdk.Uint64ToBigEndian(epoch)) | ||
iter := store.Iterator(nil, nil) | ||
defer iter.Close() | ||
|
||
for ; iter.Valid(); iter.Next() { | ||
rawBytes := iter.Value() | ||
blsSig, err := types.BytesToBlsSig(bs.cdc, rawBytes) | ||
if err != nil { | ||
return err | ||
} | ||
stop := f(blsSig) | ||
if stop { | ||
return nil | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// Exists Check whether a bls sig is maintained in storage | ||
func (bs BlsSigsState) Exists(hash types.BlsSigHash) bool { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same question: if this is for duplicate checking, don't we have the pre-image of the hash at hand? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, we can use this method to prevent adding duplicate bls sigs in the mempool, can't we? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, we could use it for that. What I meant was that you would have a full blown signature in that case, so you can just pass it as-is, and calculate its storage key. Nobody has just the hash, and not the full signature. Whether we should check this duplicate before the mempool: I'm not sure that's necessary, if the validators only send their signatures once, everyone their own, they would not be duplicated, because the mempool would reject them as identical transactions. If they actually kept sending their signatures multiple times in different transactions, then we could charge them for it to discourage this behaviour, but only if we let the transactions into the mempool. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It makes sense. Thanks! |
||
store := prefix.NewStore(bs.hashToEpoch, types.BlsSigsHashToEpochPrefix) | ||
return store.Has(hash.Bytes()) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
package keeper | ||
|
||
import ( | ||
"errors" | ||
"github.com/babylonchain/babylon/x/checkpointing/types" | ||
"github.com/cosmos/cosmos-sdk/codec" | ||
"github.com/cosmos/cosmos-sdk/store/prefix" | ||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
) | ||
|
||
type CheckpointsState struct { | ||
cdc codec.BinaryCodec | ||
checkpoints sdk.KVStore | ||
} | ||
|
||
func (k Keeper) CheckpointsState(ctx sdk.Context) CheckpointsState { | ||
// Build the CheckpointsState storage | ||
store := ctx.KVStore(k.storeKey) | ||
return CheckpointsState{ | ||
cdc: k.cdc, | ||
checkpoints: prefix.NewStore(store, types.CheckpointsPrefix), | ||
} | ||
} | ||
|
||
// CreateRawCkptWithMeta inserts the raw checkpoint with meta into the storage by its epoch number | ||
// a new checkpoint is created with the status of UNCEHCKPOINTED | ||
func (cs CheckpointsState) CreateRawCkptWithMeta(ckpt *types.RawCheckpoint) { | ||
// save concrete ckpt object | ||
ckptWithMeta := types.NewCheckpointWithMeta(ckpt, types.Uncheckpointed) | ||
cs.checkpoints.Set(types.CkptsObjectKey(ckpt.EpochNum), types.CkptWithMetaToBytes(cs.cdc, ckptWithMeta)) | ||
Comment on lines
+29
to
+30
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It might be a good idea to return an error if this record already exists, just in case. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or even to panic, as it would be a gross programming error in our case. |
||
} | ||
|
||
// GetRawCkptWithMeta retrieves a raw checkpoint with meta by its epoch number | ||
func (cs CheckpointsState) GetRawCkptWithMeta(epoch uint64) (*types.RawCheckpointWithMeta, error) { | ||
ckptsKey := types.CkptsObjectKey(epoch) | ||
rawBytes := cs.checkpoints.Get(ckptsKey) | ||
if rawBytes == nil { | ||
return nil, types.ErrCkptDoesNotExist.Wrap("no raw checkpoint with provided epoch") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This |
||
} | ||
|
||
return types.BytesToCkptWithMeta(cs.cdc, rawBytes) | ||
} | ||
|
||
// GetRawCkptsWithMetaByStatus retrieves raw checkpoints with meta by their status by the descending order of epoch | ||
func (cs CheckpointsState) GetRawCkptsWithMetaByStatus(status types.CheckpointStatus, f func(sig *types.RawCheckpointWithMeta) bool) error { | ||
store := prefix.NewStore(cs.checkpoints, types.CkptsObjectPrefix) | ||
iter := store.ReverseIterator(nil, nil) | ||
defer iter.Close() | ||
|
||
// the iterator starts from the highest epoch number | ||
// once it gets to an epoch where the status is CONFIRMED, | ||
// all the lower epochs will be CONFIRMED | ||
for ; iter.Valid(); iter.Next() { | ||
ckptBytes := iter.Value() | ||
ckptWithMeta, err := types.BytesToCkptWithMeta(cs.cdc, ckptBytes) | ||
if err != nil { | ||
return err | ||
} | ||
// the loop can end if the current status is CONFIRMED but the requested status is not CONFIRMED | ||
if status != types.Confirmed && ckptWithMeta.Status == types.Confirmed { | ||
return nil | ||
} | ||
if ckptWithMeta.Status != status { | ||
continue | ||
} | ||
stop := f(ckptWithMeta) | ||
if stop { | ||
return nil | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// UpdateCkptStatus updates the checkpoint's status | ||
func (cs CheckpointsState) UpdateCkptStatus(ckpt *types.RawCheckpoint, status types.CheckpointStatus) error { | ||
ckptWithMeta, err := cs.GetRawCkptWithMeta(ckpt.EpochNum) | ||
if err != nil { | ||
// the checkpoint should exist | ||
return err | ||
} | ||
if !ckptWithMeta.Ckpt.Hash().Equals(ckpt.Hash()) { | ||
return errors.New("hash not the same with existing checkpoint") | ||
} | ||
ckptWithMeta.Status = status | ||
cs.checkpoints.Set(sdk.Uint64ToBigEndian(ckpt.EpochNum), types.CkptWithMetaToBytes(cs.cdc, ckptWithMeta)) | ||
|
||
return nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,17 +13,21 @@ import ( | |
|
||
type ( | ||
Keeper struct { | ||
cdc codec.BinaryCodec | ||
storeKey sdk.StoreKey | ||
memKey sdk.StoreKey | ||
paramstore paramtypes.Subspace | ||
cdc codec.BinaryCodec | ||
storeKey sdk.StoreKey | ||
memKey sdk.StoreKey | ||
stakingKeeper types.StakingKeeper | ||
epochingKeeper types.EpochingKeeper | ||
paramstore paramtypes.Subspace | ||
} | ||
) | ||
|
||
func NewKeeper( | ||
cdc codec.BinaryCodec, | ||
storeKey, | ||
memKey sdk.StoreKey, | ||
stakingKeeper types.StakingKeeper, | ||
epochingKeeper types.EpochingKeeper, | ||
ps paramtypes.Subspace, | ||
) Keeper { | ||
// set KeyTable if it has not already been set | ||
|
@@ -32,13 +36,59 @@ func NewKeeper( | |
} | ||
|
||
return Keeper{ | ||
cdc: cdc, | ||
storeKey: storeKey, | ||
memKey: memKey, | ||
paramstore: ps, | ||
cdc: cdc, | ||
storeKey: storeKey, | ||
memKey: memKey, | ||
stakingKeeper: stakingKeeper, | ||
epochingKeeper: epochingKeeper, | ||
paramstore: ps, | ||
} | ||
} | ||
|
||
func (k Keeper) Logger(ctx sdk.Context) log.Logger { | ||
return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName)) | ||
} | ||
|
||
// AddBlsSig add bls signatures into storage and generates a raw checkpoint | ||
// if sufficient sigs are accumulated for a specific epoch | ||
func (k Keeper) AddBlsSig(ctx sdk.Context, sig *types.BlsSig) error { | ||
// TODO: some checks: 1. duplication check 2. epoch check 3. raw ckpt existence check | ||
// TODO: aggregate bls sigs and try to build raw checkpoints | ||
k.BlsSigsState(ctx).CreateBlsSig(sig) | ||
Comment on lines
+50
to
+51
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you want to keep this design of storing lists of BLS checkpoints? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. I don't have strong opinions on either way we should choose. I just didn't see any projects that aggregate sigs one by one. I would love to hear it if you have any argument against this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Against what you are doing? My only counter argument would be not to have to maintain and test code to store and retrieve this. The on-the-fly aggregation can be done all in memory by amending a checkpoint record. Perhaps it can even have more statuses, like "accumulation", starting with an empty signature. It's unique key is still the epoch, so it could be:
And we can put all logic into into There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Something like struct CheckpointMeta {
RawCheckpoint RawCheckpoint,
Status CheckpointStatus
TotalPower Power
}
func (ckpt *CheckpointMeta) Accumulate(validators []ValidatorWithBlsKeyAndPower, sig BlsSig) error {
if ckpt.Status != CKPT_ACCUMULATING {
return CheckpointNoLongerAccumulating
}
validator, index := findValidator(validators, sig.BlsPublicKey)
if validator == nil {
return UneligibleValidator
}
if !IsValidSignature(sig, ckpt.RawCheckpoint) {
return InvalidSignature
}
if ckpt.Bitmap[index] {
return AlreadyVoted
}
ckpt.TotalPower += validator.Power
ckpt.Signature = BlsAggregate(ckpt.Signature, bls.Signature)
ckpt.Bitmap[index] = true
if ckpt.TotalPower > totalPower(validators) / 3 {
ckpt.Status = CKPT_UNCHECKPOINTED
}
return nil
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks neat. Thanks, @aakoshh! I agree that avoiding storing bls sigs separately is a big gain of on-the-fly aggregation. Since it includes bls aggregation and verification, I'll do this in a future PR. |
||
return nil | ||
} | ||
|
||
// AddRawCheckpoint adds a raw checkpoint into the storage | ||
// this API may not needed since checkpoints are generated internally | ||
func (k Keeper) AddRawCheckpoint(ctx sdk.Context, ckpt *types.RawCheckpoint) { | ||
// NOTE: may remove this API | ||
k.CheckpointsState(ctx).CreateRawCkptWithMeta(ckpt) | ||
} | ||
|
||
// CheckpointEpoch verifies checkpoint from BTC and returns epoch number | ||
func (k Keeper) CheckpointEpoch(ctx sdk.Context, rawCkptBytes []byte) (uint64, error) { | ||
ckpt, err := types.BytesToRawCkpt(k.cdc, rawCkptBytes) | ||
if err != nil { | ||
return 0, err | ||
} | ||
err = k.verifyRawCheckpoint(ckpt) | ||
if err != nil { | ||
return 0, err | ||
} | ||
return ckpt.EpochNum, nil | ||
} | ||
|
||
func (k Keeper) verifyRawCheckpoint(ckpt *types.RawCheckpoint) error { | ||
// TODO: verify checkpoint basic and bls multi-sig | ||
return nil | ||
} | ||
|
||
// UpdateCkptStatus updates the status of a raw checkpoint | ||
func (k Keeper) UpdateCkptStatus(ctx sdk.Context, rawCkptBytes []byte, status types.CheckpointStatus) error { | ||
// TODO: some checks | ||
ckpt, err := types.BytesToRawCkpt(k.cdc, rawCkptBytes) | ||
if err != nil { | ||
return err | ||
} | ||
return k.CheckpointsState(ctx).UpdateCkptStatus(ckpt, status) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package types | ||
|
||
import ( | ||
"github.com/cosmos/cosmos-sdk/x/staking/types" | ||
"github.com/tendermint/tendermint/libs/bits" | ||
"github.com/tendermint/tendermint/libs/bytes" | ||
) | ||
|
||
type BlsSigSet struct { | ||
epoch uint16 | ||
lastCommitHash bytes.HexBytes | ||
validators types.Validators | ||
|
||
sum uint64 | ||
sigsBitArray *bits.BitArray | ||
} | ||
|
||
// NewBlsSigSet constructs a new BlsSigSet struct used to accumulate bls sigs for a given epoch | ||
func NewBlsSigSet(epoch uint16, lastCommitHash bytes.HexBytes, validators types.Validators) *BlsSigSet { | ||
return &BlsSigSet{ | ||
epoch: epoch, | ||
lastCommitHash: lastCommitHash, | ||
validators: validators, | ||
sum: 0, | ||
sigsBitArray: bits.NewBitArray(validators.Len()), | ||
} | ||
} | ||
|
||
func (bs *BlsSigSet) AddBlsSig(sig *BlsSig) (bool, error) { | ||
return bs.addBlsSig(sig) | ||
} | ||
|
||
func (bs *BlsSigSet) addBlsSig(sig *BlsSig) (bool, error) { | ||
panic("implement this!") | ||
} | ||
|
||
func (bs *BlsSigSet) MakeRawCheckpoint() *RawCheckpoint { | ||
panic("implement this!") | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.