-
Notifications
You must be signed in to change notification settings - Fork 170
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
zoneconcierge/epoching: proof that a header is in an epoch (#248)
- Loading branch information
1 parent
d681148
commit 828d2da
Showing
21 changed files
with
2,908 additions
and
591 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
package keeper | ||
|
||
import ( | ||
"crypto/sha256" | ||
"fmt" | ||
|
||
"github.com/babylonchain/babylon/x/epoching/types" | ||
"github.com/cosmos/cosmos-sdk/store/prefix" | ||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" | ||
"github.com/tendermint/tendermint/crypto/merkle" | ||
tmcrypto "github.com/tendermint/tendermint/proto/tendermint/crypto" | ||
) | ||
|
||
func (k Keeper) setAppHash(ctx sdk.Context, height uint64, appHash []byte) { | ||
store := k.appHashStore(ctx) | ||
heightBytes := sdk.Uint64ToBigEndian(height) | ||
store.Set(heightBytes, appHash) | ||
} | ||
|
||
// GetAppHash gets the AppHash of the header at the given height | ||
func (k Keeper) GetAppHash(ctx sdk.Context, height uint64) ([]byte, error) { | ||
store := k.appHashStore(ctx) | ||
heightBytes := sdk.Uint64ToBigEndian(height) | ||
appHash := store.Get(heightBytes) | ||
if appHash == nil { | ||
return nil, sdkerrors.Wrapf(types.ErrInvalidHeight, "height %d is now known in DB yet", height) | ||
} | ||
return appHash, nil | ||
} | ||
|
||
// RecordAppHash stores the AppHash of the current header to KVStore | ||
func (k Keeper) RecordAppHash(ctx sdk.Context) { | ||
header := ctx.BlockHeader() | ||
height := uint64(header.Height) | ||
k.setAppHash(ctx, height, header.AppHash) | ||
} | ||
|
||
// GetAllAppHashsForEpoch fetches all AppHashs in the given epoch | ||
func (k Keeper) GetAllAppHashsForEpoch(ctx sdk.Context, epoch *types.Epoch) ([][]byte, error) { | ||
// if this epoch is the most recent AND has not ended, then we cannot get all AppHashs for this epoch | ||
if k.GetEpoch(ctx).EpochNumber == epoch.EpochNumber && !epoch.IsLastBlock(ctx) { | ||
return nil, sdkerrors.Wrapf(types.ErrInvalidHeight, "GetAllAppHashsForEpoch can only be invoked when this epoch has ended") | ||
} | ||
|
||
// fetch each AppHash in this epoch | ||
appHashs := [][]byte{} | ||
for i := epoch.FirstBlockHeight; i <= uint64(epoch.LastBlockHeader.Height); i++ { | ||
appHash, err := k.GetAppHash(ctx, i) | ||
if err != nil { | ||
return nil, err | ||
} | ||
appHashs = append(appHashs, appHash) | ||
} | ||
|
||
return appHashs, nil | ||
} | ||
|
||
// ProveAppHashInEpoch generates a proof that the given appHash is in a given epoch | ||
func (k Keeper) ProveAppHashInEpoch(ctx sdk.Context, height uint64, epochNumber uint64) (*tmcrypto.Proof, error) { | ||
// ensure height is inside this epoch | ||
epoch, err := k.GetHistoricalEpoch(ctx, epochNumber) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if !epoch.WithinBoundary(height) { | ||
return nil, sdkerrors.Wrapf(types.ErrInvalidHeight, "the given height %d is not in epoch %d (interval [%d, %d])", height, epoch.EpochNumber, epoch.FirstBlockHeight, uint64(epoch.LastBlockHeader.Height)) | ||
} | ||
|
||
// calculate index of this height in this epoch | ||
idx := height - epoch.FirstBlockHeight | ||
|
||
// fetch all AppHashs, calculate Merkle tree and proof | ||
appHashs, err := k.GetAllAppHashsForEpoch(ctx, epoch) | ||
if err != nil { | ||
return nil, err | ||
} | ||
_, proofs := merkle.ProofsFromByteSlices(appHashs) | ||
|
||
return proofs[idx].ToProto(), nil | ||
} | ||
|
||
// VerifyAppHashInclusion verifies whether the given appHash is in the Merkle tree w.r.t. the appHashRoot | ||
func VerifyAppHashInclusion(appHash []byte, appHashRoot []byte, proof *tmcrypto.Proof) error { | ||
if len(appHash) != sha256.Size { | ||
return fmt.Errorf("appHash with length %d is not a Sha256 hash", len(appHash)) | ||
} | ||
if len(appHashRoot) != sha256.Size { | ||
return fmt.Errorf("appHash with length %d is not a Sha256 hash", len(appHashRoot)) | ||
} | ||
if proof == nil { | ||
return fmt.Errorf("proof is nil") | ||
} | ||
|
||
unwrappedProof, err := merkle.ProofFromProto(proof) | ||
if err != nil { | ||
return fmt.Errorf("failed to unwrap proof: %w", err) | ||
} | ||
return unwrappedProof.Verify(appHashRoot, appHash) | ||
} | ||
|
||
// appHashStore returns the KVStore for the AppHash of each header | ||
// prefix: AppHashKey | ||
// key: height | ||
// value: AppHash in bytes | ||
func (k Keeper) appHashStore(ctx sdk.Context) prefix.Store { | ||
store := ctx.KVStore(k.storeKey) | ||
return prefix.NewStore(store, types.AppHashKey) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package keeper_test | ||
|
||
import ( | ||
"math/rand" | ||
"testing" | ||
|
||
"github.com/babylonchain/babylon/testutil/datagen" | ||
"github.com/babylonchain/babylon/x/epoching/keeper" | ||
"github.com/babylonchain/babylon/x/epoching/testepoching" | ||
"github.com/babylonchain/babylon/x/epoching/types" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func FuzzAppHashChain(f *testing.F) { | ||
datagen.AddRandomSeedsToFuzzer(f, 10) | ||
|
||
f.Fuzz(func(t *testing.T, seed int64) { | ||
rand.Seed(seed) | ||
|
||
helper := testepoching.NewHelper(t) | ||
ctx, k := helper.Ctx, helper.EpochingKeeper | ||
// ensure that the epoch info is correct at the genesis | ||
epoch := k.GetEpoch(ctx) | ||
require.Equal(t, epoch.EpochNumber, uint64(0)) | ||
require.Equal(t, epoch.FirstBlockHeight, uint64(0)) | ||
|
||
// set a random epoch interval | ||
epochInterval := rand.Uint64()%100 + 2 // the epoch interval should at at least 2 | ||
k.SetParams(ctx, types.Params{ | ||
EpochInterval: epochInterval, | ||
}) | ||
|
||
// reach the end of the 1st epoch | ||
expectedHeight := epochInterval | ||
expectedAppHashs := [][]byte{} | ||
for i := uint64(0); i < expectedHeight; i++ { | ||
ctx = helper.GenAndApplyEmptyBlock() | ||
expectedAppHashs = append(expectedAppHashs, ctx.BlockHeader().AppHash) | ||
} | ||
// ensure epoch number is 1 | ||
epoch = k.GetEpoch(ctx) | ||
require.Equal(t, uint64(1), epoch.EpochNumber) | ||
|
||
// ensure appHashs are same as expectedAppHashs | ||
appHashs, err := k.GetAllAppHashsForEpoch(ctx, epoch) | ||
require.NoError(t, err) | ||
require.Equal(t, expectedAppHashs, appHashs) | ||
|
||
// ensure prover and verifier are correct | ||
randomHeightInEpoch := uint64(rand.Intn(int(expectedHeight)) + 1) | ||
randomAppHash, err := k.GetAppHash(ctx, randomHeightInEpoch) | ||
require.NoError(t, err) | ||
proof, err := k.ProveAppHashInEpoch(ctx, randomHeightInEpoch, epoch.EpochNumber) | ||
require.NoError(t, err) | ||
err = keeper.VerifyAppHashInclusion(randomAppHash, epoch.AppHashRoot, proof) | ||
require.NoError(t, err) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.