diff --git a/proto/babylon/btclightclient/btclightclient.proto b/proto/babylon/btclightclient/btclightclient.proto index e9487396a..16891fe49 100644 --- a/proto/babylon/btclightclient/btclightclient.proto +++ b/proto/babylon/btclightclient/btclightclient.proto @@ -5,15 +5,6 @@ import "gogoproto/gogo.proto"; option go_package = "github.com/babylonchain/babylon/x/btclightclient/types"; -// BaseBTCHeader corresponds to the oldest BTC header maintained in storage -// It is denoted by the header bytes and the height -message BaseBTCHeader { - bytes header = 1 [ - (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BTCHeaderBytes" - ]; - uint64 height = 2; -} - message BTCHeaderInfo { bytes header = 1 [ (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BTCHeaderBytes" @@ -22,5 +13,8 @@ message BTCHeaderInfo { (gogoproto.customtype) = "github.com/babylonchain/babylon/types.BTCHeaderHashBytes" ]; uint64 height = 3; + bytes work = 4 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Uint" + ]; } diff --git a/proto/babylon/btclightclient/genesis.proto b/proto/babylon/btclightclient/genesis.proto index 995f4dc5f..e159e7d2f 100644 --- a/proto/babylon/btclightclient/genesis.proto +++ b/proto/babylon/btclightclient/genesis.proto @@ -11,5 +11,5 @@ option go_package = "github.com/babylonchain/babylon/x/btclightclient/types"; message GenesisState { Params params = 1 [(gogoproto.nullable) = false]; - BaseBTCHeader base_btc_header = 2 [(gogoproto.nullable) = false]; + BTCHeaderInfo base_btc_header = 2 [(gogoproto.nullable) = false]; } diff --git a/types/btc_header_bytes.go b/types/btc_header_bytes.go index 99b677f16..be16c8748 100644 --- a/types/btc_header_bytes.go +++ b/types/btc_header_bytes.go @@ -134,6 +134,33 @@ func (m *BTCHeaderBytes) FromBlockHeader(header *wire.BlockHeader) { } } +func (m *BTCHeaderBytes) HasParent(header *BTCHeaderBytes) bool { + current := m.ToBlockHeader() + parent := header.ToBlockHeader() + + return current.PrevBlock.String() == parent.BlockHash().String() +} + +func (m *BTCHeaderBytes) Eq(other *BTCHeaderBytes) bool { + return m.Hash().Eq(other.Hash()) +} + +func (m *BTCHeaderBytes) Hash() *BTCHeaderHashBytes { + blockHash := m.ToBlockHeader().BlockHash() + hashBytes := NewBTCHeaderHashBytesFromChainhash(&blockHash) + return &hashBytes +} + +func (m *BTCHeaderBytes) ParentHash() *BTCHeaderHashBytes { + parentHash := m.ToBlockHeader().PrevBlock + hashBytes := NewBTCHeaderHashBytesFromChainhash(&parentHash) + return &hashBytes +} + +func (m *BTCHeaderBytes) Bits() uint32 { + return m.ToBlockHeader().Bits +} + func toBlockHeader(data []byte) (*wire.BlockHeader, error) { // Create an empty header header := &wire.BlockHeader{} diff --git a/types/btc_header_bytes_test.go b/types/btc_header_bytes_test.go index 0811cf59d..48bcf7eb3 100644 --- a/types/btc_header_bytes_test.go +++ b/types/btc_header_bytes_test.go @@ -202,3 +202,59 @@ func FuzzBTCHeaderBytesBtcdBlockOps(f *testing.F) { } }) } + +func FuzzBTCHeaderBytesOperators(f *testing.F) { + defaultHeader, _ := types.NewBTCHeaderBytesFromHex("00006020c6c5a20e29da938a252c945411eba594cbeba021a1e20000000000000000000039e4bd0cd0b5232bb380a9576fcfe7d8fb043523f7a158187d9473e44c1740e6b4fa7c62ba01091789c24c22") + defaultBtcdHeader := defaultHeader.ToBlockHeader() + + f.Add( + defaultBtcdHeader.Version, + defaultBtcdHeader.Bits, + defaultBtcdHeader.Nonce, + defaultBtcdHeader.Timestamp.Unix(), + defaultBtcdHeader.PrevBlock.String(), + defaultBtcdHeader.MerkleRoot.String(), + int64(17)) + + f.Fuzz(func(t *testing.T, version int32, bits uint32, nonce uint32, + timeInt int64, prevBlockStr string, merkleRootStr string, seed int64) { + + rand.Seed(seed) + btcdHeader := datagen.GenRandomBtcdHeader(version, bits, nonce, timeInt, prevBlockStr, merkleRootStr) + + btcdHeaderHash := btcdHeader.BlockHash() + childPrevBlock := types.NewBTCHeaderHashBytesFromChainhash(&btcdHeaderHash) + btcdHeaderChild := datagen.GenRandomBtcdHeader(version, bits, nonce, timeInt, childPrevBlock.MarshalHex(), merkleRootStr) + + var hb, hb2, hbChild types.BTCHeaderBytes + hb.FromBlockHeader(btcdHeader) + hb2.FromBlockHeader(btcdHeader) + hbChild.FromBlockHeader(btcdHeaderChild) + + if !hb.Eq(&hb) { + t.Errorf("BTCHeaderBytes object does not equal itself") + } + if !hb.Eq(&hb2) { + t.Errorf("BTCHeaderBytes object does not equal a different object with the same bytes") + } + if hb.Eq(&hbChild) { + t.Errorf("BTCHeaderBytes object equals a different object with different bytes") + } + + if !hbChild.HasParent(&hb) { + t.Errorf("HasParent method returns false with a correct parent") + } + if hbChild.HasParent(&hbChild) { + t.Errorf("HasParent method returns true for the same object") + } + + if !hbChild.ParentHash().Eq(&childPrevBlock) { + t.Errorf("ParentHash did not return the parent hash") + } + + if !hb.Hash().Eq(&childPrevBlock) { + t.Errorf("Hash method does not return the correct hash") + } + + }) +} diff --git a/types/btc_header_hash_bytes.go b/types/btc_header_hash_bytes.go index 77a8d718f..5efea9244 100644 --- a/types/btc_header_hash_bytes.go +++ b/types/btc_header_hash_bytes.go @@ -121,6 +121,15 @@ func (m *BTCHeaderHashBytes) FromChainhash(hash *chainhash.Hash) { } } +func (m *BTCHeaderHashBytes) String() string { + return m.ToChainhash().String() +} + +func (m *BTCHeaderHashBytes) Eq(hash *BTCHeaderHashBytes) bool { + // TODO: test + return m.String() == hash.String() +} + func toChainhash(data []byte) (*chainhash.Hash, error) { return chainhash.NewHash(data) } diff --git a/types/btc_header_hash_bytes_test.go b/types/btc_header_hash_bytes_test.go index 3ddaca21b..4475f9762 100644 --- a/types/btc_header_hash_bytes_test.go +++ b/types/btc_header_hash_bytes_test.go @@ -198,3 +198,29 @@ func FuzzHeaderHashBytesChainhashOps(f *testing.F) { } }) } + +func FuzzHeaderHashBytesOperators(f *testing.F) { + f.Add(int64(42)) + f.Fuzz(func(t *testing.T, seed int64) { + rand.Seed(seed) + + hexHash := datagen.GenRandomHexStr(types.BTCHeaderHashLen) + hexHash2 := datagen.GenRandomHexStr(types.BTCHeaderHashLen) + chHash, _ := chainhash.NewHashFromStr(hexHash) + + var hbb, hbb2 types.BTCHeaderHashBytes + hbb.FromChainhash(chHash) + hbb2, _ = types.NewBTCHeaderHashBytesFromHex(hexHash2) + + if hbb.String() != chHash.String() { + t.Errorf("String() method returned %s while %s was expected", hbb, chHash) + } + + if !hbb.Eq(&hbb) { + t.Errorf("Object does not equal itself") + } + if hbb.Eq(&hbb2) { + t.Errorf("Object %s equals %s", hbb, hbb2) + } + }) +} diff --git a/x/btclightclient/client/cli/query.go b/x/btclightclient/client/cli/query.go index 8e5fd427c..613abb083 100644 --- a/x/btclightclient/client/cli/query.go +++ b/x/btclightclient/client/cli/query.go @@ -63,7 +63,12 @@ func CmdHashes() *cobra.Command { queryClient := types.NewQueryClient(clientCtx) - params := types.NewQueryHashesRequest() + pageReq, err := client.ReadPageRequest(cmd.Flags()) + if err != nil { + return err + } + + params := types.NewQueryHashesRequest(pageReq) res, err := queryClient.Hashes(context.Background(), params) if err != nil { return err @@ -116,7 +121,12 @@ func CmdMainChain() *cobra.Command { queryClient := types.NewQueryClient(clientCtx) - params := types.NewQueryMainChainRequest() + pageReq, err := client.ReadPageRequest(cmd.Flags()) + if err != nil { + return err + } + + params := types.NewQueryMainChainRequest(pageReq) res, err := queryClient.MainChain(context.Background(), params) if err != nil { return err diff --git a/x/btclightclient/genesis_test.go b/x/btclightclient/genesis_test.go index ff8d9d09f..7ec6d292d 100644 --- a/x/btclightclient/genesis_test.go +++ b/x/btclightclient/genesis_test.go @@ -13,10 +13,13 @@ import ( func TestGenesis(t *testing.T) { headerBytes, _ := bbl.NewBTCHeaderBytesFromHex(types.DefaultBaseHeaderHex) + headerHash := headerBytes.Hash() + headerWork := types.CalcWork(&headerBytes) + baseHeaderInfo := types.NewBTCHeaderInfo(&headerBytes, headerHash, types.DefaultBaseHeaderHeight, &headerWork) genesisState := types.GenesisState{ Params: types.DefaultParams(), - BaseBtcHeader: types.DefaultBaseBTCHeader(headerBytes, types.DefaultBaseHeaderHeight), + BaseBtcHeader: *baseHeaderInfo, } k, ctx := keepertest.BTCLightClientKeeper(t) diff --git a/x/btclightclient/keeper/base_btc_header.go b/x/btclightclient/keeper/base_btc_header.go index 7862f6a18..a08e5fbb5 100644 --- a/x/btclightclient/keeper/base_btc_header.go +++ b/x/btclightclient/keeper/base_btc_header.go @@ -1,45 +1,21 @@ package keeper import ( - bbl "github.com/babylonchain/babylon/types" "github.com/babylonchain/babylon/x/btclightclient/types" sdk "github.com/cosmos/cosmos-sdk/types" ) -func (k Keeper) GetBaseBTCHeader(ctx sdk.Context) types.BaseBTCHeader { - baseBtcdHeader := k.HeadersState(ctx).GetBaseBTCHeader() - - if baseBtcdHeader == nil { - return types.BaseBTCHeader{} - } - - baseHash := baseBtcdHeader.BlockHash() - height, err := k.HeadersState(ctx).GetHeaderHeight(&baseHash) - - if err != nil { - return types.BaseBTCHeader{} - } - - headerBytes := bbl.NewBTCHeaderBytesFromBlockHeader(baseBtcdHeader) - return types.BaseBTCHeader{Header: &headerBytes, Height: height} +func (k Keeper) GetBaseBTCHeader(ctx sdk.Context) types.BTCHeaderInfo { + baseBtcHeader := k.HeadersState(ctx).GetBaseBTCHeader() + return *baseBtcHeader } // SetBaseBTCHeader checks whether a base BTC header exists and // if not inserts it into storage -func (k Keeper) SetBaseBTCHeader(ctx sdk.Context, baseBTCHeader types.BaseBTCHeader) { +func (k Keeper) SetBaseBTCHeader(ctx sdk.Context, baseBTCHeader types.BTCHeaderInfo) { existingHeader := k.HeadersState(ctx).GetBaseBTCHeader() if existingHeader != nil { panic("A base BTC Header has already been set") } - - btcdHeader := baseBTCHeader.Header.ToBlockHeader() - - // The cumulative work for the Base BTC header is only the work - // for that particular header. This means that it is very important - // that no forks will happen that discard the base header because we - // will not be able to detect those. Cumulative work will build based - // on the sum of the work of the chain starting from the base header. - blockWork := types.CalcWork(btcdHeader) - - k.HeadersState(ctx).CreateHeader(btcdHeader, baseBTCHeader.Height, blockWork) + k.HeadersState(ctx).CreateHeader(&baseBTCHeader) } diff --git a/x/btclightclient/keeper/grpc_query.go b/x/btclightclient/keeper/grpc_query.go index 17706794a..6cefda7aa 100644 --- a/x/btclightclient/keeper/grpc_query.go +++ b/x/btclightclient/keeper/grpc_query.go @@ -30,6 +30,14 @@ func (k Keeper) Hashes(ctx context.Context, req *types.QueryHashesRequest) (*typ sdkCtx := sdk.UnwrapSDKContext(ctx) + // Ensure that the pagination key corresponds to hash bytes + if len(req.Pagination.Key) != 0 { + _, err := bbl.NewBTCHeaderHashBytesFromBytes(req.Pagination.Key) + if err != nil { + return nil, err + } + } + store := prefix.NewStore(k.HeadersState(sdkCtx).hashToHeight, types.HashToHeightPrefix) pageRes, err := query.FilteredPaginate(store, req.Pagination, func(key []byte, _ []byte, accumulate bool) (bool, error) { if accumulate { @@ -50,8 +58,7 @@ func (k Keeper) Contains(ctx context.Context, req *types.QueryContainsRequest) ( return nil, status.Error(codes.InvalidArgument, "invalid request") } sdkCtx := sdk.UnwrapSDKContext(ctx) - chHash := req.Hash.ToChainhash() - contains := k.HeadersState(sdkCtx).HeaderExists(chHash) + contains := k.HeadersState(sdkCtx).HeaderExists(req.Hash) return &types.QueryContainsResponse{Contains: contains}, nil } @@ -67,19 +74,13 @@ func (k Keeper) MainChain(ctx context.Context, req *types.QueryMainChainRequest) } // If a starting key has not been set, then the first header is the tip prevHeader := k.HeadersState(sdkCtx).GetTip() - prevHeaderHash := prevHeader.BlockHash() - prevHeaderHeight, err := k.HeadersState(sdkCtx).GetHeaderHeight(&prevHeaderHash) - if err != nil { - panic("Maintained header does not have a height") - } // Otherwise, retrieve the header from the key if len(req.Pagination.Key) != 0 { headerHash, err := bbl.NewBTCHeaderHashBytesFromBytes(req.Pagination.Key) if err != nil { return nil, status.Error(codes.InvalidArgument, "key does not correspond to a header hash") } - chHash := headerHash.ToChainhash() - prevHeader, err = k.HeadersState(sdkCtx).GetHeaderByHash(chHash) + prevHeader, err = k.HeadersState(sdkCtx).GetHeaderByHash(&headerHash) } // If no tip exists or a key, then return an empty response @@ -88,21 +89,18 @@ func (k Keeper) MainChain(ctx context.Context, req *types.QueryMainChainRequest) } var headers []*types.BTCHeaderInfo - currentHeight := prevHeaderHeight - headerInfo := types.NewBTCHeaderInfo(prevHeader, prevHeaderHeight) - headers = append(headers, headerInfo) + headers = append(headers, prevHeader) store := prefix.NewStore(k.HeadersState(sdkCtx).headers, types.HeadersObjectPrefix) // Set this value to true to signal to FilteredPaginate to iterate the entries in reverse req.Pagination.Reverse = true pageRes, err := query.FilteredPaginate(store, req.Pagination, func(_ []byte, value []byte, accumulate bool) (bool, error) { if accumulate { - btcdHeader := blockHeaderFromStoredBytes(value) + headerInfo := headerInfoFromStoredBytes(k.cdc, value) // If the previous block extends this block, then this block is part of the main chain - if prevHeader.PrevBlock.String() == btcdHeader.BlockHash().String() { - currentHeight -= 1 - prevHeader = btcdHeader - headers = append(headers, types.NewBTCHeaderInfo(btcdHeader, currentHeight)) + if prevHeader.HasParent(headerInfo) { + prevHeader = headerInfo + headers = append(headers, headerInfo) } } return true, nil @@ -114,8 +112,7 @@ func (k Keeper) MainChain(ctx context.Context, req *types.QueryMainChainRequest) // Override the next key attribute to point to the parent of the last header // instead of the next element contained in the store - prevBlockCh := prevHeader.PrevBlock - pageRes.NextKey = bbl.NewBTCHeaderHashBytesFromChainhash(&prevBlockCh) + pageRes.NextKey = prevHeader.Header.ParentHash().MustMarshal() return &types.QueryMainChainResponse{Headers: headers, Pagination: pageRes}, nil } diff --git a/x/btclightclient/keeper/keeper.go b/x/btclightclient/keeper/keeper.go index 638032435..6eab46cb1 100644 --- a/x/btclightclient/keeper/keeper.go +++ b/x/btclightclient/keeper/keeper.go @@ -2,8 +2,7 @@ package keeper import ( "fmt" - "github.com/btcsuite/btcd/wire" - + bbl "github.com/babylonchain/babylon/types" "github.com/tendermint/tendermint/libs/log" "github.com/babylonchain/babylon/x/btclightclient/types" @@ -58,49 +57,60 @@ func (k *Keeper) SetHooks(bh types.BTCLightClientHooks) *Keeper { } // InsertHeader inserts a btcd header into the header state -func (k Keeper) InsertHeader(ctx sdk.Context, header *wire.BlockHeader) error { - headerHash := header.BlockHash() - headerExists := k.HeadersState(ctx).HeaderExists(&headerHash) +func (k Keeper) InsertHeader(ctx sdk.Context, header *bbl.BTCHeaderBytes) error { + headerHash := header.Hash() + parentHash := header.ParentHash() + + // Check whether the header already exists, if yes reject + headerExists := k.HeadersState(ctx).HeaderExists(headerHash) if headerExists { return types.ErrDuplicateHeader.Wrap("header with provided hash already exists") } - parentExists := k.HeadersState(ctx).HeaderExists(&header.PrevBlock) + // Check whether the parent exists, if not reject + parentExists := k.HeadersState(ctx).HeaderExists(parentHash) if !parentExists { return types.ErrHeaderParentDoesNotExist.Wrap("parent for provided hash is not maintained") } - parentHeight, err := k.HeadersState(ctx).GetHeaderHeight(&header.PrevBlock) + // Retrieve the height of the parent to calculate the current height + parentHeight, err := k.HeadersState(ctx).GetHeaderHeight(parentHash) if err != nil { // Height should always exist if the previous checks have passed panic("Height for parent is not maintained") } - parentWork, err := k.HeadersState(ctx).GetHeaderWork(&header.PrevBlock) + // Retrieve the work of the parent to calculate the cumulative work + parentWork, err := k.HeadersState(ctx).GetHeaderWork(parentHash) if err != nil { // Work should always exist if the previous checks have passed panic("Work for parent is not maintained") } + // Calculate the cumulative work headerWork := types.CalcWork(header) - cumulativeWork := types.CumulativeWork(headerWork, parentWork) + cumulativeWork := types.CumulativeWork(headerWork, *parentWork) + // Construct the BTCHeaderInfo object + headerInfo := types.NewBTCHeaderInfo(header, headerHash, parentHeight+1, &cumulativeWork) + + // Retrieve the previous tip for future usage previousTip := k.HeadersState(ctx).GetTip() + // Create the header - k.HeadersState(ctx).CreateHeader(header, parentHeight+1, cumulativeWork) + k.HeadersState(ctx).CreateHeader(headerInfo) // Get the new tip currentTip := k.HeadersState(ctx).GetTip() // Variable maintaining the headers that have been added to the main chain - var addedToMainChain []*wire.BlockHeader + var addedToMainChain []*types.BTCHeaderInfo // The tip has changed, we need to send events - if !sameBlock(currentTip, previousTip) { - if !sameBlock(currentTip, header) { + if currentTip.Eq(previousTip) { + if !currentTip.Eq(headerInfo) { panic("The tip was updated but with a different header than the one provided") } - tipHeight := parentHeight + 1 // Get the highest common ancestor between the new tip and the old tip // There are two cases: // 1. The new tip extends the old tip @@ -108,28 +118,20 @@ func (k Keeper) InsertHeader(ctx sdk.Context, header *wire.BlockHeader) error { // - No need to send a roll-back event // 2. There has been a chain re-org // - Need to send a roll-back event - var hca *wire.BlockHeader - var hcaHeight uint64 - if isParent(currentTip, previousTip) { + var hca *types.BTCHeaderInfo + if currentTip.HasParent(previousTip) { hca = previousTip - hcaHeight = parentHeight } else { - hca := k.HeadersState(ctx).GetHighestCommonAncestor(previousTip, currentTip) - hcaHash := hca.BlockHash() - hcaHeight, err = k.HeadersState(ctx).GetHeaderHeight(&hcaHash) - if err != nil { - panic("Height for maintained header not available in storage") - } + hca = k.HeadersState(ctx).GetHighestCommonAncestor(previousTip, currentTip) // chain re-org: trigger a roll-back event to the highest common ancestor - k.triggerRollBack(ctx, hca, hcaHeight) + k.triggerRollBack(ctx, hca) } // Find the newly added headers to the main chain addedToMainChain = k.HeadersState(ctx).GetInOrderAncestorsUntil(currentTip, hca) // Iterate through the added headers and trigger a roll-forward event - for idx, added := range addedToMainChain { + for _, added := range addedToMainChain { // tipHeight + 1 - len(addedToMainChain) -> height of the highest common ancestor - addedHeight := tipHeight - uint64(len(addedToMainChain)) + 1 + uint64(idx) - k.triggerRollForward(ctx, added, addedHeight) + k.triggerRollForward(ctx, added) } } @@ -137,25 +139,43 @@ func (k Keeper) InsertHeader(ctx sdk.Context, header *wire.BlockHeader) error { } // BlockHeight returns the height of the provided header -func (k Keeper) BlockHeight(ctx sdk.Context, header *wire.BlockHeader) (uint64, error) { - headerHash := header.BlockHash() - return k.HeadersState(ctx).GetHeaderHeight(&headerHash) +func (k Keeper) BlockHeight(ctx sdk.Context, header *bbl.BTCHeaderBytes) (uint64, error) { + headerHash := header.Hash() + return k.HeadersState(ctx).GetHeaderHeight(headerHash) } -// HeaderKDeep returns true if a header is at least k-deep on the main chain -func (k Keeper) HeaderKDeep(ctx sdk.Context, header *wire.BlockHeader, depth uint64) bool { - // TODO: optimize to not traverse the entire mainchain by storing the height along with the header - mainchain := k.HeadersState(ctx).GetMainChain() - if depth > uint64(len(mainchain)) { - return false +// MainChainDepth returns the depth of the header in the main chain or -1 if it does not exist in it +func (k Keeper) MainChainDepth(ctx sdk.Context, headerBytes *bbl.BTCHeaderBytes) (int64, error) { + // Retrieve the header. If it does not exist, return an error + headerInfo, err := k.HeadersState(ctx).GetHeaderByHash(headerBytes.Hash()) + if err != nil { + return -1, err } - // k-deep -> k headers built on top of the BTC header - // Discard the first `depth` headers - kDeepMainChain := mainchain[depth:] - for _, mainChainHeader := range kDeepMainChain { - if sameBlock(header, mainChainHeader) { - return true - } + + // Retrieve the tip + tipInfo := k.HeadersState(ctx).GetTip() + + // If the height of the requested header is larger than the tip, return an error + if tipInfo.Height < headerInfo.Height { + return -1, types.ErrHeaderHigherThanTip.Wrap("header higher than tip") + } + + headerDepth := tipInfo.Height - headerInfo.Height + 1 + mainchain := k.HeadersState(ctx).GetMainChainUpTo(headerDepth) + + // If we got an empty mainchain or the header does not equal the last element of the mainchain + // then the header is not maintained inside the mainchain. + if len(mainchain) == 0 || !headerInfo.Eq(mainchain[len(mainchain)-1]) { + return -1, nil + } + return int64(headerDepth), nil +} + +// IsHeaderKDeep returns true if a header is at least k-deep on the main chain +func (k Keeper) IsHeaderKDeep(ctx sdk.Context, headerBytes *bbl.BTCHeaderBytes, depth uint64) bool { + mainchainDepth, err := k.MainChainDepth(ctx, headerBytes) + if err != nil || mainchainDepth < 0 { + return false } - return false + return uint64(mainchainDepth) >= depth } diff --git a/x/btclightclient/keeper/msg_server.go b/x/btclightclient/keeper/msg_server.go index 99a429664..a628b6045 100644 --- a/x/btclightclient/keeper/msg_server.go +++ b/x/btclightclient/keeper/msg_server.go @@ -21,14 +21,12 @@ func (m msgServer) InsertHeader(ctx context.Context, msg *types.MsgInsertHeader) // so as to not pollute the mempool with transactions // that will get rejected. - // Get Btcd header from bytes - btcdHeader := msg.Header.ToBlockHeader() - // Get the SDK wrapped context sdkCtx := sdk.UnwrapSDKContext(ctx) + parentHash := msg.Header.ParentHash() // Retrieve parent - parent, err := m.k.HeadersState(sdkCtx).GetHeaderByHash(&btcdHeader.PrevBlock) + parent, err := m.k.HeadersState(sdkCtx).GetHeaderByHash(parentHash) // parent does not exist if err != nil { return nil, err @@ -40,8 +38,8 @@ func (m msgServer) InsertHeader(ctx context.Context, msg *types.MsgInsertHeader) // while in the second case it should have a maximum difference of a factor of 4 from it // See: https://github.com/bitcoinbook/bitcoinbook/blob/develop/ch10.asciidoc#retargeting-to-adjust-difficulty // We consolidate those into a single check. - oldDifficulty := blockchain.CompactToBig(parent.Bits) - currentDifficulty := blockchain.CompactToBig(btcdHeader.Bits) + oldDifficulty := blockchain.CompactToBig(parent.Header.Bits()) + currentDifficulty := blockchain.CompactToBig(msg.Header.Bits()) maxCurrentDifficulty := new(big.Int).Mul(oldDifficulty, big.NewInt(4)) minCurrentDifficulty := new(big.Int).Div(oldDifficulty, big.NewInt(4)) if currentDifficulty.Cmp(maxCurrentDifficulty) > 0 || currentDifficulty.Cmp(minCurrentDifficulty) < 0 { @@ -49,7 +47,7 @@ func (m msgServer) InsertHeader(ctx context.Context, msg *types.MsgInsertHeader) } // All good, insert the header - err = m.k.InsertHeader(sdkCtx, btcdHeader) + err = m.k.InsertHeader(sdkCtx, msg.Header) if err != nil { return nil, err } diff --git a/x/btclightclient/keeper/state.go b/x/btclightclient/keeper/state.go index 8910058f8..12ea97360 100644 --- a/x/btclightclient/keeper/state.go +++ b/x/btclightclient/keeper/state.go @@ -3,12 +3,9 @@ package keeper import ( bbl "github.com/babylonchain/babylon/types" "github.com/babylonchain/babylon/x/btclightclient/types" - "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/wire" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" - "math/big" ) type HeadersState struct { @@ -35,42 +32,39 @@ func (k Keeper) HeadersState(ctx sdk.Context) HeadersState { // - hash->height // - hash->work // - (height, hash)->header storage -func (s HeadersState) CreateHeader(header *wire.BlockHeader, height uint64, cumulativeWork *big.Int) { - headerHash := header.BlockHash() - // Get necessary keys according - headersKey := types.HeadersObjectKey(height, &headerHash) - heightKey := types.HeadersObjectHeightKey(&headerHash) - workKey := types.HeadersObjectWorkKey(&headerHash) +func (s HeadersState) CreateHeader(headerInfo *types.BTCHeaderInfo) { + headerHash := headerInfo.Hash + height := headerInfo.Height + cumulativeWork := headerInfo.Work - // Convert the block header into bytes - headerBytes := bbl.NewBTCHeaderBytesFromBlockHeader(header) + // Get necessary keys according + headersKey := types.HeadersObjectKey(height, headerHash) + heightKey := types.HeadersObjectHeightKey(headerHash) + workKey := types.HeadersObjectWorkKey(headerHash) // save concrete object - s.headers.Set(headersKey, headerBytes) + s.headers.Set(headersKey, s.cdc.MustMarshal(headerInfo)) // map header to height s.hashToHeight.Set(heightKey, sdk.Uint64ToBigEndian(height)) // map header to work - s.hashToWork.Set(workKey, cumulativeWork.Bytes()) + workBytes, err := cumulativeWork.Marshal() + if err != nil { + panic("Work cannot be marshalled") + } + s.hashToWork.Set(workKey, workBytes) - s.updateLongestChain(header, cumulativeWork) + s.updateLongestChain(headerInfo) } // CreateTip sets the provided header as the tip -func (s HeadersState) CreateTip(header *wire.BlockHeader) { +func (s HeadersState) CreateTip(headerInfo *types.BTCHeaderInfo) { // Retrieve the key for the tip storage tipKey := types.TipKey() - - // Convert the *wire.BlockHeader object into a BTCHeaderBytes object - headerBytes := bbl.NewBTCHeaderBytesFromBlockHeader(header) - - // Convert the BTCHeaderBytes object into a bytes array - rawBytes := headerBytes.MustMarshal() - - s.tip.Set(tipKey, rawBytes) + s.tip.Set(tipKey, s.cdc.MustMarshal(headerInfo)) } // GetHeader Retrieve a header by its height and hash -func (s HeadersState) GetHeader(height uint64, hash *chainhash.Hash) (*wire.BlockHeader, error) { +func (s HeadersState) GetHeader(height uint64, hash *bbl.BTCHeaderHashBytes) (*types.BTCHeaderInfo, error) { // Keyed by (height, hash) headersKey := types.HeadersObjectKey(height, hash) @@ -80,11 +74,11 @@ func (s HeadersState) GetHeader(height uint64, hash *chainhash.Hash) (*wire.Bloc return nil, types.ErrHeaderDoesNotExist.Wrap("no header with provided height and hash") } - return blockHeaderFromStoredBytes(rawBytes), nil + return headerInfoFromStoredBytes(s.cdc, rawBytes), nil } // GetHeaderHeight Retrieve the Height of a header -func (s HeadersState) GetHeaderHeight(hash *chainhash.Hash) (uint64, error) { +func (s HeadersState) GetHeaderHeight(hash *bbl.BTCHeaderHashBytes) (uint64, error) { // Keyed by hash hashKey := types.HeadersObjectHeightKey(hash) @@ -100,7 +94,7 @@ func (s HeadersState) GetHeaderHeight(hash *chainhash.Hash) (uint64, error) { } // GetHeaderWork Retrieve the work of a header -func (s HeadersState) GetHeaderWork(hash *chainhash.Hash) (*big.Int, error) { +func (s HeadersState) GetHeaderWork(hash *bbl.BTCHeaderHashBytes) (*sdk.Uint, error) { // Keyed by hash hashKey := types.HeadersObjectHeightKey(hash) // Retrieve the raw bytes for the work @@ -110,12 +104,16 @@ func (s HeadersState) GetHeaderWork(hash *chainhash.Hash) (*big.Int, error) { } // Convert to *big.Int form - work := new(big.Int).SetBytes(bz) + work := new(sdk.Uint) + err := work.Unmarshal(bz) + if err != nil { + panic("Stored header cannot be unmarshalled to sdk.Uint") + } return work, nil } // GetHeaderByHash Retrieve a header by its hash -func (s HeadersState) GetHeaderByHash(hash *chainhash.Hash) (*wire.BlockHeader, error) { +func (s HeadersState) GetHeaderByHash(hash *bbl.BTCHeaderHashBytes) (*types.BTCHeaderInfo, error) { // Get the height of the header in order to use it along with the hash // as a (height, hash) key for the object storage height, err := s.GetHeaderHeight(hash) @@ -126,7 +124,7 @@ func (s HeadersState) GetHeaderByHash(hash *chainhash.Hash) (*wire.BlockHeader, } // GetBaseBTCHeader retrieves the BTC header with the minimum height -func (s HeadersState) GetBaseBTCHeader() *wire.BlockHeader { +func (s HeadersState) GetBaseBTCHeader() *types.BTCHeaderInfo { // Retrieve the canonical chain canonicalChain := s.GetMainChain() // If the canonical chain is empty, then there is no base header @@ -138,18 +136,18 @@ func (s HeadersState) GetBaseBTCHeader() *wire.BlockHeader { } // GetTip returns the tip of the canonical chain -func (s HeadersState) GetTip() *wire.BlockHeader { +func (s HeadersState) GetTip() *types.BTCHeaderInfo { if !s.TipExists() { return nil } // Get the key to the tip storage tipKey := types.TipKey() - return blockHeaderFromStoredBytes(s.tip.Get(tipKey)) + return headerInfoFromStoredBytes(s.cdc, s.tip.Get(tipKey)) } // GetHeadersByHeight Retrieve headers by their height using an accumulator function -func (s HeadersState) GetHeadersByHeight(height uint64, f func(*wire.BlockHeader) bool) { +func (s HeadersState) GetHeadersByHeight(height uint64, f func(*types.BTCHeaderInfo) bool) { // The s.headers store is keyed by (height, hash) // By getting the prefix key using the height, // we are getting a store of `hash -> header` that contains all hashes @@ -162,7 +160,7 @@ func (s HeadersState) GetHeadersByHeight(height uint64, f func(*wire.BlockHeader // Iterate through the prefix store and retrieve each header object. // Using the header object invoke the accumulator function. for ; iter.Valid(); iter.Next() { - header := blockHeaderFromStoredBytes(iter.Value()) + header := headerInfoFromStoredBytes(s.cdc, iter.Value()) // The accumulator function notifies us whether the iteration should stop. stop := f(header) if stop { @@ -171,19 +169,22 @@ func (s HeadersState) GetHeadersByHeight(height uint64, f func(*wire.BlockHeader } } -// GetDescendingHeaders returns a collection of descending headers according to their height -func (s HeadersState) GetDescendingHeaders() []*wire.BlockHeader { - var headers []*wire.BlockHeader - s.iterateReverseHeaders(func(header *wire.BlockHeader) bool { +// GetDescendingHeadersUpTo returns a collection of descending headers according to their height +func (s HeadersState) GetDescendingHeadersUpTo(tipHeight uint64, depth uint64) []*types.BTCHeaderInfo { + var headers []*types.BTCHeaderInfo + s.iterateReverseHeaders(func(header *types.BTCHeaderInfo) bool { headers = append(headers, header) + if tipHeight-header.Height == depth { + return true + } return false }) return headers } -// GetMainChain returns the current canonical chain as a collection of block headers -// starting from the tip and ending on the base header -func (s HeadersState) GetMainChain() []*wire.BlockHeader { +// GetMainChainUpTo returns the current canonical chain as a collection of block headers +// starting from the tip and ending on the header that has a depth distance from it. +func (s HeadersState) GetMainChainUpTo(depth uint64) []*types.BTCHeaderInfo { // If there is no tip, there is no base header if !s.TipExists() { return nil @@ -191,9 +192,9 @@ func (s HeadersState) GetMainChain() []*wire.BlockHeader { currentHeader := s.GetTip() // Retrieve a collection of headers in descending height order - headers := s.GetDescendingHeaders() + headers := s.GetDescendingHeadersUpTo(currentHeader.Height, depth) - var chain []*wire.BlockHeader + var chain []*types.BTCHeaderInfo chain = append(chain, currentHeader) // Set the current header to be that of the tip // Iterate through the collection and: @@ -201,7 +202,7 @@ func (s HeadersState) GetMainChain() []*wire.BlockHeader { // - Find the parent of the header and set the current header to it // Return the current header for _, header := range headers { - if header.BlockHash().String() == currentHeader.PrevBlock.String() { + if currentHeader.HasParent(header) { currentHeader = header chain = append(chain, header) } @@ -210,9 +211,20 @@ func (s HeadersState) GetMainChain() []*wire.BlockHeader { return chain } +// GetMainChain retrieves the main chain as a collection of block headers starting from the tip +// and ending on the base BTC header. +func (s HeadersState) GetMainChain() []*types.BTCHeaderInfo { + if !s.TipExists() { + return nil + } + tip := s.GetTip() + // By providing the depth as the tip.Height, we ensure that we will go as deep as possible + return s.GetMainChainUpTo(tip.Height) +} + // GetHighestCommonAncestor traverses the ancestors of both headers // to identify the common ancestor with the highest height -func (s HeadersState) GetHighestCommonAncestor(header1 *wire.BlockHeader, header2 *wire.BlockHeader) *wire.BlockHeader { +func (s HeadersState) GetHighestCommonAncestor(header1 *types.BTCHeaderInfo, header2 *types.BTCHeaderInfo) *types.BTCHeaderInfo { // The algorithm works as follows: // 1. Initialize a hashmap hash -> bool denoting whether the hash // of an ancestor of either header1 or header2 has been encountered @@ -224,41 +236,42 @@ func (s HeadersState) GetHighestCommonAncestor(header1 *wire.BlockHeader, header // then that's the hash of the earliest ancestor // 5. Using the hash of the heighest ancestor wait until we get the header bytes // in order to avoid an extra access. - if isParent(header1, header2) { + if header1.HasParent(header2) { return header2 } - if isParent(header2, header1) { - return header1 + if header2.HasParent(header1) { + return header2 } - ancestor1 := header1.BlockHash() - ancestor2 := header2.BlockHash() + + ancestor1 := header1.Hash + ancestor2 := header2.Hash var encountered map[string]bool encountered[ancestor1.String()] = true encountered[ancestor2.String()] = true - var found *chainhash.Hash = nil + var found *bbl.BTCHeaderHashBytes = nil - var resHeader *wire.BlockHeader = nil + var resHeader *types.BTCHeaderInfo = nil - s.iterateReverseHeaders(func(btcdHeader *wire.BlockHeader) bool { + s.iterateReverseHeaders(func(header *types.BTCHeaderInfo) bool { // During iteration, we will encounter an ancestor for which its header hash // has been set on the hash map. // However, we do not have the entry yet, so we set the found flag to that hash // and when we encounter it during iteration we return it. - if found != nil && sameHash(*found, btcdHeader.BlockHash()) { - resHeader = btcdHeader + if found != nil && header.Hash.Eq(found) { + resHeader = header return true } else { - if ancestor1 == btcdHeader.BlockHash() { - ancestor1 = btcdHeader.PrevBlock + if ancestor1.Eq(header.Hash) { + ancestor1 = header.Hash if encountered[ancestor1.String()] { - found = &ancestor1 + found = ancestor1 } encountered[ancestor1.String()] = true } - if ancestor2 == btcdHeader.BlockHash() { - ancestor2 = btcdHeader.PrevBlock + if ancestor2.Eq(header.Hash) { + ancestor2 = header.Hash if encountered[ancestor2.String()] { - found = &ancestor2 + found = ancestor2 } encountered[ancestor2.String()] = true } @@ -268,39 +281,58 @@ func (s HeadersState) GetHighestCommonAncestor(header1 *wire.BlockHeader, header return resHeader } -// GetInOrderAncestorsUntil returns the list of nodes starting from the child and ending with the block *before* the `ancestor`. -func (s HeadersState) GetInOrderAncestorsUntil(child *wire.BlockHeader, ancestor *wire.BlockHeader) []*wire.BlockHeader { +// GetInOrderAncestorsUntil returns the list of nodes starting from the block *before* the `ancestor` and ending with the child. +func (s HeadersState) GetInOrderAncestorsUntil(child *types.BTCHeaderInfo, ancestor *types.BTCHeaderInfo) []*types.BTCHeaderInfo { + if ancestor.Height >= child.Height { + panic("Ancestor has a higher height than descendant") + } + currentHeader := child - var ancestors []*wire.BlockHeader + var ancestors []*types.BTCHeaderInfo ancestors = append(ancestors, child) - if isParent(child, ancestor) { + if child.HasParent(ancestor) { return ancestors } - s.iterateReverseHeaders(func(header *wire.BlockHeader) bool { - if header.BlockHash() == ancestor.BlockHash() { + + found := false + s.iterateReverseHeaders(func(header *types.BTCHeaderInfo) bool { + if header.Eq(ancestor) { + found = true return true } - if header.BlockHash().String() == currentHeader.PrevBlock.String() { + if currentHeader.HasParent(header) { currentHeader = header ancestors = append(ancestors, header) } + // Abandon the iteration if the height of the current header is lower + // than the height of the provided ancestor + if currentHeader.Height < ancestor.Height { + return true + } return false }) + // If the header was not found, discard the ancestors list + if !found { + ancestors = []*types.BTCHeaderInfo{} + } + + // Reverse the array + for i, j := 0, len(ancestors)-1; i < j; i, j = i+1, j-1 { + ancestors[i], ancestors[j] = ancestors[j], ancestors[i] + } + return ancestors } // HeaderExists Check whether a hash is maintained in storage -func (s HeadersState) HeaderExists(hash *chainhash.Hash) bool { +func (s HeadersState) HeaderExists(hash *bbl.BTCHeaderHashBytes) bool { // Get the prefix store for the hash->height collection store := prefix.NewStore(s.hashToHeight, types.HashToHeightPrefix) - // Convert the *chainhash.Hash object into a BTCHeaderHashBytesObject - hashBytes := bbl.NewBTCHeaderHashBytesFromChainhash(hash) - // Convert the BTCHeaderHashBytes object into raw bytes - rawBytes := hashBytes.MustMarshal() + rawBytes := hash.MustMarshal() return store.Has(rawBytes) } @@ -312,31 +344,24 @@ func (s HeadersState) TipExists() bool { } // updateLongestChain checks whether the tip should be updated and returns true if it does -func (s HeadersState) updateLongestChain(header *wire.BlockHeader, cumulativeWork *big.Int) { +func (s HeadersState) updateLongestChain(headerInfo *types.BTCHeaderInfo) { // If there is no existing tip, then the header is set as the tip if !s.TipExists() { - s.CreateTip(header) + s.CreateTip(headerInfo) return } // Get the current tip header hash tip := s.GetTip() - tipHash := tip.BlockHash() - // Retrieve the tip's work from storage - tipWork, err := s.GetHeaderWork(&tipHash) - if err != nil { - panic("Existing tip does not have a maintained work") - } - // If the work of the current tip is less than the work of the provided header, // the provided header is set as the tip. - if tipWork.Cmp(cumulativeWork) < 0 { - s.CreateTip(header) + if headerInfo.Work.GT(*tip.Work) { + s.CreateTip(headerInfo) } } -func (s HeadersState) iterateReverseHeaders(fn func(*wire.BlockHeader) bool) { +func (s HeadersState) iterateReverseHeaders(fn func(*types.BTCHeaderInfo) bool) { // Get the prefix store for the (height, hash) -> header collection store := prefix.NewStore(s.headers, types.HeadersObjectPrefix) // Iterate it in reverse in order to get highest heights first @@ -345,8 +370,8 @@ func (s HeadersState) iterateReverseHeaders(fn func(*wire.BlockHeader) bool) { defer iter.Close() for ; iter.Valid(); iter.Next() { - btcdHeader := blockHeaderFromStoredBytes(iter.Value()) - stop := fn(btcdHeader) + header := headerInfoFromStoredBytes(s.cdc, iter.Value()) + stop := fn(header) if stop { break } diff --git a/x/btclightclient/keeper/triggers.go b/x/btclightclient/keeper/triggers.go index de058c87d..3762fb962 100644 --- a/x/btclightclient/keeper/triggers.go +++ b/x/btclightclient/keeper/triggers.go @@ -2,20 +2,17 @@ package keeper import ( "github.com/babylonchain/babylon/x/btclightclient/types" - "github.com/btcsuite/btcd/wire" sdk "github.com/cosmos/cosmos-sdk/types" ) -func (k Keeper) triggerRollBack(ctx sdk.Context, header *wire.BlockHeader, height uint64) { - headerInfo := types.NewBTCHeaderInfo(header, height) +func (k Keeper) triggerRollBack(ctx sdk.Context, headerInfo *types.BTCHeaderInfo) { // Trigger AfterBTCRollBack hook k.AfterBTCRollBack(ctx, headerInfo) // Emit BTCRollBack event ctx.EventManager().EmitTypedEvent(&types.EventBTCRollBack{Header: headerInfo}) } -func (k Keeper) triggerRollForward(ctx sdk.Context, header *wire.BlockHeader, height uint64) { - headerInfo := types.NewBTCHeaderInfo(header, height) +func (k Keeper) triggerRollForward(ctx sdk.Context, headerInfo *types.BTCHeaderInfo) { // Trigger AfterBTCRollForward hook k.AfterBTCRollForward(ctx, headerInfo) // Emit BTCRollForward event diff --git a/x/btclightclient/keeper/utils.go b/x/btclightclient/keeper/utils.go index 37b5175a5..55d353cbf 100644 --- a/x/btclightclient/keeper/utils.go +++ b/x/btclightclient/keeper/utils.go @@ -1,29 +1,12 @@ package keeper import ( - bbl "github.com/babylonchain/babylon/types" - "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/wire" + "github.com/babylonchain/babylon/x/btclightclient/types" + "github.com/cosmos/cosmos-sdk/codec" ) -func blockHeaderFromStoredBytes(bz []byte) *wire.BlockHeader { - // Convert the bytes value into a BTCHeaderBytes object - headerBytes, err := bbl.NewBTCHeaderBytesFromBytes(bz) - if err != nil { - panic("Stored bytes cannot be converted to BTCHeaderBytes object") - } - // Convert the BTCHeaderBytes object into a *wire.BlockHeader object - return headerBytes.ToBlockHeader() -} - -func isParent(child *wire.BlockHeader, parent *wire.BlockHeader) bool { - return sameHash(child.PrevBlock, parent.BlockHash()) -} - -func sameBlock(header1 *wire.BlockHeader, header2 *wire.BlockHeader) bool { - return sameHash(header1.BlockHash(), header2.BlockHash()) -} - -func sameHash(hash1 chainhash.Hash, hash2 chainhash.Hash) bool { - return hash1.String() == hash2.String() +func headerInfoFromStoredBytes(cdc codec.BinaryCodec, bz []byte) *types.BTCHeaderInfo { + headerInfo := new(types.BTCHeaderInfo) + cdc.MustUnmarshal(bz, headerInfo) + return headerInfo } diff --git a/x/btclightclient/types/base_btc_header.go b/x/btclightclient/types/base_btc_header.go deleted file mode 100644 index 56fd4138e..000000000 --- a/x/btclightclient/types/base_btc_header.go +++ /dev/null @@ -1,20 +0,0 @@ -package types - -import ( - bbl "github.com/babylonchain/babylon/types" -) - -// NewBaseBTCHeader creates a new Params instance -func NewBaseBTCHeader(headerBytes bbl.BTCHeaderBytes, height uint64) BaseBTCHeader { - return BaseBTCHeader{Header: &headerBytes, Height: height} -} - -// DefaultBaseBTCHeader returns a default set of parameters -func DefaultBaseBTCHeader(headerBytes bbl.BTCHeaderBytes, height uint64) BaseBTCHeader { - return NewBaseBTCHeader(headerBytes, height) -} - -// Validate validates the base BTC header -func (p BaseBTCHeader) Validate() error { - return nil -} diff --git a/x/btclightclient/types/base_btc_header_test.go b/x/btclightclient/types/base_btc_header_test.go deleted file mode 100644 index 3c7488eb8..000000000 --- a/x/btclightclient/types/base_btc_header_test.go +++ /dev/null @@ -1,56 +0,0 @@ -package types_test - -import ( - "bytes" - "github.com/babylonchain/babylon/testutil/datagen" - bbl "github.com/babylonchain/babylon/types" - "github.com/babylonchain/babylon/x/btclightclient/types" - "math/rand" - "testing" -) - -func FuzzBaseBTCHeader(f *testing.F) { - defaultHeader, _ := bbl.NewBTCHeaderBytesFromHex(types.DefaultBaseHeaderHex) - defaultBtcdHeader := defaultHeader.ToBlockHeader() - - f.Add( - defaultBtcdHeader.Version, - defaultBtcdHeader.Bits, - defaultBtcdHeader.Nonce, - defaultBtcdHeader.Timestamp.Unix(), - defaultBtcdHeader.PrevBlock.String(), - defaultBtcdHeader.MerkleRoot.String(), - uint64(42), - int64(17)) - - f.Fuzz(func(t *testing.T, version int32, bits uint32, nonce uint32, - timeInt int64, prevBlockStr string, merkleRootStr string, height uint64, seed int64) { - - rand.Seed(seed) - // Get the btcd header based on the provided data - btcdHeader := datagen.GenRandomBtcdHeader(version, bits, nonce, timeInt, prevBlockStr, merkleRootStr) - // Convert it into bytes - headerBytesObj := bbl.NewBTCHeaderBytesFromBlockHeader(btcdHeader) - headerBytes, _ := headerBytesObj.Marshal() - - baseBTCHeader := types.NewBaseBTCHeader(headerBytesObj, height) - // Validate the various attributes - gotHeaderBytes, err := baseBTCHeader.Header.Marshal() - if err != nil { - t.Errorf("Header returned cannot be marshalled") - } - gotHeight := baseBTCHeader.Height - if bytes.Compare(headerBytes, gotHeaderBytes) != 0 { - t.Errorf("Header attribute is different") - } - if height != gotHeight { - t.Errorf("Height attribute is different") - } - - // Perform the header validation - err = baseBTCHeader.Validate() - if err != nil { - t.Errorf("Got error %s when validating", err) - } - }) -} diff --git a/x/btclightclient/types/btc_header_info.go b/x/btclightclient/types/btc_header_info.go index 10d926ff0..419c25b15 100644 --- a/x/btclightclient/types/btc_header_info.go +++ b/x/btclightclient/types/btc_header_info.go @@ -2,18 +2,26 @@ package types import ( bbl "github.com/babylonchain/babylon/types" - "github.com/btcsuite/btcd/wire" + sdk "github.com/cosmos/cosmos-sdk/types" ) -func NewBTCHeaderInfo(header *wire.BlockHeader, height uint64) *BTCHeaderInfo { - headerHashCh := header.BlockHash() - - currentHeaderBytes := bbl.NewBTCHeaderBytesFromBlockHeader(header) - - headerHash := bbl.NewBTCHeaderHashBytesFromChainhash(&headerHashCh) +func NewBTCHeaderInfo(header *bbl.BTCHeaderBytes, headerHash *bbl.BTCHeaderHashBytes, height uint64, work *sdk.Uint) *BTCHeaderInfo { return &BTCHeaderInfo{ - Header: ¤tHeaderBytes, - Hash: &headerHash, + Header: header, + Hash: headerHash, Height: height, + Work: work, } } + +func (hi *BTCHeaderInfo) HasParent(parent *BTCHeaderInfo) bool { + return hi.Header.HasParent(parent.Header) +} + +func (hi *BTCHeaderInfo) Eq(other *BTCHeaderInfo) bool { + return hi.Hash.Eq(other.Hash) +} + +func (hi BTCHeaderInfo) Validate() error { + return nil +} diff --git a/x/btclightclient/types/btc_header_info_test.go b/x/btclightclient/types/btc_header_info_test.go index 9400e15f2..90963019a 100644 --- a/x/btclightclient/types/btc_header_info_test.go +++ b/x/btclightclient/types/btc_header_info_test.go @@ -5,6 +5,7 @@ import ( "github.com/babylonchain/babylon/testutil/datagen" bbl "github.com/babylonchain/babylon/types" "github.com/babylonchain/babylon/x/btclightclient/types" + sdk "github.com/cosmos/cosmos-sdk/types" "math/rand" "testing" ) @@ -20,23 +21,34 @@ func FuzzNewHeaderInfo(f *testing.F) { btcdHeader.PrevBlock.String(), btcdHeader.MerkleRoot.String(), uint64(42), + uint64(24), int64(17)) f.Fuzz(func(t *testing.T, version int32, bits uint32, nonce uint32, - timeInt int64, prevBlockStr string, merkleRootStr string, height uint64, seed int64) { + timeInt int64, prevBlockStr string, merkleRootStr string, height uint64, work uint64, seed int64) { // If either of the hash strings is not of appropriate length // or not valid hex, generate a random hex randomly rand.Seed(seed) header := datagen.GenRandomBtcdHeader(version, bits, nonce, timeInt, prevBlockStr, merkleRootStr) + workSdk := sdk.NewUint(work) + // Get the expected header bytes expectedHeaderBytes := bbl.NewBTCHeaderBytesFromBlockHeader(header) + expectedHeaderHashBytes := expectedHeaderBytes.Hash() - headerInfo := types.NewBTCHeaderInfo(header, height) + headerInfo := types.NewBTCHeaderInfo(&expectedHeaderBytes, expectedHeaderHashBytes, height, &workSdk) + // Check that all attributes are properly set if headerInfo == nil { t.Errorf("returned object is nil") } + if headerInfo.Header == nil { + t.Errorf("Header inside header info is nil") + } + if headerInfo.Work == nil { + t.Errorf("Work inside header info is nil") + } gotHeaderBytes := *headerInfo.Header if bytes.Compare(expectedHeaderBytes, gotHeaderBytes) != 0 { @@ -49,5 +61,15 @@ func FuzzNewHeaderInfo(f *testing.F) { if bytes.Compare(expectedHashBytes, gotHashBytes) != 0 { t.Errorf("Expected header hash %s got %s", expectedHashBytes, gotHashBytes) } + + gotHeight := headerInfo.Height + if gotHeight != height { + t.Errorf("Expected height %d got height %d", height, gotHeight) + } + + gotWork := headerInfo.Work + if *gotWork != workSdk { + t.Errorf("Expected work %d got work %d", workSdk.Uint64(), (*gotWork).Uint64()) + } }) } diff --git a/x/btclightclient/types/btclightclient.pb.go b/x/btclightclient/types/btclightclient.pb.go index 48f3b0718..4cea909ea 100644 --- a/x/btclightclient/types/btclightclient.pb.go +++ b/x/btclightclient/types/btclightclient.pb.go @@ -6,6 +6,7 @@ package types import ( fmt "fmt" github_com_babylonchain_babylon_types "github.com/babylonchain/babylon/types" + github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types" _ "github.com/gogo/protobuf/gogoproto" proto "github.com/gogo/protobuf/proto" io "io" @@ -24,64 +25,18 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package -// BaseBTCHeader corresponds to the oldest BTC header maintained in storage -// It is denoted by the header bytes and the height -type BaseBTCHeader struct { - Header *github_com_babylonchain_babylon_types.BTCHeaderBytes `protobuf:"bytes,1,opt,name=header,proto3,customtype=github.com/babylonchain/babylon/types.BTCHeaderBytes" json:"header,omitempty"` - Height uint64 `protobuf:"varint,2,opt,name=height,proto3" json:"height,omitempty"` -} - -func (m *BaseBTCHeader) Reset() { *m = BaseBTCHeader{} } -func (m *BaseBTCHeader) String() string { return proto.CompactTextString(m) } -func (*BaseBTCHeader) ProtoMessage() {} -func (*BaseBTCHeader) Descriptor() ([]byte, []int) { - return fileDescriptor_3313d955a6cadef2, []int{0} -} -func (m *BaseBTCHeader) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *BaseBTCHeader) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_BaseBTCHeader.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *BaseBTCHeader) XXX_Merge(src proto.Message) { - xxx_messageInfo_BaseBTCHeader.Merge(m, src) -} -func (m *BaseBTCHeader) XXX_Size() int { - return m.Size() -} -func (m *BaseBTCHeader) XXX_DiscardUnknown() { - xxx_messageInfo_BaseBTCHeader.DiscardUnknown(m) -} - -var xxx_messageInfo_BaseBTCHeader proto.InternalMessageInfo - -func (m *BaseBTCHeader) GetHeight() uint64 { - if m != nil { - return m.Height - } - return 0 -} - type BTCHeaderInfo struct { Header *github_com_babylonchain_babylon_types.BTCHeaderBytes `protobuf:"bytes,1,opt,name=header,proto3,customtype=github.com/babylonchain/babylon/types.BTCHeaderBytes" json:"header,omitempty"` Hash *github_com_babylonchain_babylon_types.BTCHeaderHashBytes `protobuf:"bytes,2,opt,name=hash,proto3,customtype=github.com/babylonchain/babylon/types.BTCHeaderHashBytes" json:"hash,omitempty"` Height uint64 `protobuf:"varint,3,opt,name=height,proto3" json:"height,omitempty"` + Work *github_com_cosmos_cosmos_sdk_types.Uint `protobuf:"bytes,4,opt,name=work,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Uint" json:"work,omitempty"` } func (m *BTCHeaderInfo) Reset() { *m = BTCHeaderInfo{} } func (m *BTCHeaderInfo) String() string { return proto.CompactTextString(m) } func (*BTCHeaderInfo) ProtoMessage() {} func (*BTCHeaderInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_3313d955a6cadef2, []int{1} + return fileDescriptor_3313d955a6cadef2, []int{0} } func (m *BTCHeaderInfo) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -118,7 +73,6 @@ func (m *BTCHeaderInfo) GetHeight() uint64 { } func init() { - proto.RegisterType((*BaseBTCHeader)(nil), "babylon.btclightclient.v1.BaseBTCHeader") proto.RegisterType((*BTCHeaderInfo)(nil), "babylon.btclightclient.v1.BTCHeaderInfo") } @@ -127,26 +81,28 @@ func init() { } var fileDescriptor_3313d955a6cadef2 = []byte{ - // 253 bytes of a gzipped FileDescriptorProto + // 276 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0x4e, 0x4a, 0x4c, 0xaa, 0xcc, 0xc9, 0xcf, 0xd3, 0x4f, 0x2a, 0x49, 0xce, 0xc9, 0x4c, 0xcf, 0x00, 0x91, 0xa9, 0x79, 0x25, 0x68, 0x5c, 0xbd, 0x82, 0xa2, 0xfc, 0x92, 0x7c, 0x21, 0x49, 0xa8, 0x62, 0x3d, 0x34, 0xd9, 0x32, - 0x43, 0x29, 0x91, 0xf4, 0xfc, 0xf4, 0x7c, 0xb0, 0x2a, 0x7d, 0x10, 0x0b, 0xa2, 0x41, 0xa9, 0x92, - 0x8b, 0xd7, 0x29, 0xb1, 0x38, 0xd5, 0x29, 0xc4, 0xd9, 0x23, 0x35, 0x31, 0x25, 0xb5, 0x48, 0x28, - 0x80, 0x8b, 0x2d, 0x03, 0xcc, 0x92, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x71, 0xb2, 0xb8, 0x75, 0x4f, - 0xde, 0x24, 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, 0x2f, 0x39, 0x3f, 0x57, 0x1f, 0x6a, 0x41, 0x72, - 0x46, 0x62, 0x66, 0x1e, 0x8c, 0xa3, 0x5f, 0x52, 0x59, 0x90, 0x5a, 0xac, 0x07, 0x37, 0xc4, 0xa9, - 0xb2, 0x24, 0xb5, 0x38, 0x08, 0x6a, 0x8e, 0x90, 0x18, 0xc8, 0x44, 0x90, 0x5b, 0x24, 0x98, 0x14, - 0x18, 0x35, 0x58, 0x82, 0xa0, 0x3c, 0xa5, 0xd3, 0x8c, 0x5c, 0xbc, 0x70, 0x2d, 0x9e, 0x79, 0x69, - 0xf9, 0x34, 0xb0, 0x3b, 0x80, 0x8b, 0x25, 0x23, 0xb1, 0x38, 0x03, 0x6c, 0x33, 0x8f, 0x93, 0xcd, - 0xad, 0x7b, 0xf2, 0x16, 0x24, 0x9a, 0xe7, 0x91, 0x58, 0x9c, 0x01, 0x31, 0x13, 0x6c, 0x12, 0x92, - 0x6f, 0x98, 0x91, 0x7d, 0xe3, 0x14, 0x70, 0xe2, 0x91, 0x1c, 0xe3, 0x85, 0x47, 0x72, 0x8c, 0x0f, - 0x1e, 0xc9, 0x31, 0x4e, 0x78, 0x2c, 0xc7, 0x70, 0xe1, 0xb1, 0x1c, 0xc3, 0x8d, 0xc7, 0x72, 0x0c, - 0x51, 0x66, 0x84, 0x6c, 0xac, 0x40, 0x8f, 0x5a, 0xb0, 0x13, 0x92, 0xd8, 0xc0, 0x31, 0x64, 0x0c, - 0x08, 0x00, 0x00, 0xff, 0xff, 0x73, 0x01, 0xff, 0x5f, 0x01, 0x02, 0x00, 0x00, + 0x43, 0x29, 0x91, 0xf4, 0xfc, 0xf4, 0x7c, 0xb0, 0x2a, 0x7d, 0x10, 0x0b, 0xa2, 0x41, 0xa9, 0x87, + 0x89, 0x8b, 0xd7, 0x29, 0xc4, 0xd9, 0x23, 0x35, 0x31, 0x25, 0xb5, 0xc8, 0x33, 0x2f, 0x2d, 0x5f, + 0x28, 0x80, 0x8b, 0x2d, 0x03, 0xcc, 0x93, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x71, 0xb2, 0xb8, 0x75, + 0x4f, 0xde, 0x24, 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, 0x2f, 0x39, 0x3f, 0x57, 0x1f, 0x6a, 0x43, + 0x72, 0x46, 0x62, 0x66, 0x1e, 0x8c, 0xa3, 0x5f, 0x52, 0x59, 0x90, 0x5a, 0xac, 0x07, 0x37, 0xc8, + 0xa9, 0xb2, 0x24, 0xb5, 0x38, 0x08, 0x6a, 0x8e, 0x50, 0x00, 0x17, 0x4b, 0x46, 0x62, 0x71, 0x86, + 0x04, 0x13, 0xd8, 0x3c, 0x9b, 0x5b, 0xf7, 0xe4, 0x2d, 0x48, 0x34, 0xcf, 0x23, 0xb1, 0x38, 0x03, + 0x62, 0x26, 0xd8, 0x24, 0x21, 0x31, 0x90, 0x1b, 0x41, 0xde, 0x93, 0x60, 0x56, 0x60, 0xd4, 0x60, + 0x09, 0x82, 0xf2, 0x84, 0xec, 0xb9, 0x58, 0xca, 0xf3, 0x8b, 0xb2, 0x25, 0x58, 0xc0, 0x36, 0x69, + 0xdf, 0xba, 0x27, 0xaf, 0x8e, 0x64, 0x53, 0x72, 0x7e, 0x71, 0x6e, 0x7e, 0x31, 0x94, 0xd2, 0x2d, + 0x4e, 0xc9, 0x86, 0x5a, 0x13, 0x9a, 0x99, 0x57, 0x12, 0x04, 0xd6, 0xe8, 0x14, 0x70, 0xe2, 0x91, + 0x1c, 0xe3, 0x85, 0x47, 0x72, 0x8c, 0x0f, 0x1e, 0xc9, 0x31, 0x4e, 0x78, 0x2c, 0xc7, 0x70, 0xe1, + 0xb1, 0x1c, 0xc3, 0x8d, 0xc7, 0x72, 0x0c, 0x51, 0x66, 0x84, 0x9c, 0x5c, 0x81, 0x1e, 0x41, 0x60, + 0xc3, 0x93, 0xd8, 0xc0, 0xe1, 0x6c, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0x41, 0x6a, 0x8d, 0xae, + 0xc7, 0x01, 0x00, 0x00, } -func (m *BaseBTCHeader) Marshal() (dAtA []byte, err error) { +func (m *BTCHeaderInfo) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -156,56 +112,28 @@ func (m *BaseBTCHeader) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *BaseBTCHeader) MarshalTo(dAtA []byte) (int, error) { +func (m *BTCHeaderInfo) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *BaseBTCHeader) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *BTCHeaderInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int _ = l - if m.Height != 0 { - i = encodeVarintBtclightclient(dAtA, i, uint64(m.Height)) - i-- - dAtA[i] = 0x10 - } - if m.Header != nil { + if m.Work != nil { { - size := m.Header.Size() + size := m.Work.Size() i -= size - if _, err := m.Header.MarshalTo(dAtA[i:]); err != nil { + if _, err := m.Work.MarshalTo(dAtA[i:]); err != nil { return 0, err } i = encodeVarintBtclightclient(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *BTCHeaderInfo) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err + dAtA[i] = 0x22 } - return dAtA[:n], nil -} - -func (m *BTCHeaderInfo) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *BTCHeaderInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l if m.Height != 0 { i = encodeVarintBtclightclient(dAtA, i, uint64(m.Height)) i-- @@ -249,22 +177,6 @@ func encodeVarintBtclightclient(dAtA []byte, offset int, v uint64) int { dAtA[offset] = uint8(v) return base } -func (m *BaseBTCHeader) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.Header != nil { - l = m.Header.Size() - n += 1 + l + sovBtclightclient(uint64(l)) - } - if m.Height != 0 { - n += 1 + sovBtclightclient(uint64(m.Height)) - } - return n -} - func (m *BTCHeaderInfo) Size() (n int) { if m == nil { return 0 @@ -282,6 +194,10 @@ func (m *BTCHeaderInfo) Size() (n int) { if m.Height != 0 { n += 1 + sovBtclightclient(uint64(m.Height)) } + if m.Work != nil { + l = m.Work.Size() + n += 1 + l + sovBtclightclient(uint64(l)) + } return n } @@ -291,7 +207,7 @@ func sovBtclightclient(x uint64) (n int) { func sozBtclightclient(x uint64) (n int) { return sovBtclightclient(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } -func (m *BaseBTCHeader) Unmarshal(dAtA []byte) error { +func (m *BTCHeaderInfo) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -314,10 +230,10 @@ func (m *BaseBTCHeader) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: BaseBTCHeader: wiretype end group for non-group") + return fmt.Errorf("proto: BTCHeaderInfo: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: BaseBTCHeader: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: BTCHeaderInfo: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -356,10 +272,10 @@ func (m *BaseBTCHeader) Unmarshal(dAtA []byte) error { } iNdEx = postIndex case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Hash", wireType) } - m.Height = 0 + var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowBtclightclient @@ -369,66 +285,32 @@ func (m *BaseBTCHeader) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - m.Height |= uint64(b&0x7F) << shift + byteLen |= int(b&0x7F) << shift if b < 0x80 { break } } - default: - iNdEx = preIndex - skippy, err := skipBtclightclient(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { + if byteLen < 0 { return ErrInvalidLengthBtclightclient } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *BTCHeaderInfo) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBtclightclient + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthBtclightclient } - if iNdEx >= l { + if postIndex > l { return io.ErrUnexpectedEOF } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break + var v github_com_babylonchain_babylon_types.BTCHeaderHashBytes + m.Hash = &v + if err := m.Hash.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: BTCHeaderInfo: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: BTCHeaderInfo: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Header", wireType) + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) } - var byteLen int + m.Height = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowBtclightclient @@ -438,30 +320,14 @@ func (m *BTCHeaderInfo) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - byteLen |= int(b&0x7F) << shift + m.Height |= uint64(b&0x7F) << shift if b < 0x80 { break } } - if byteLen < 0 { - return ErrInvalidLengthBtclightclient - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthBtclightclient - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - var v github_com_babylonchain_babylon_types.BTCHeaderBytes - m.Header = &v - if err := m.Header.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: + case 4: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Hash", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Work", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -488,31 +354,12 @@ func (m *BTCHeaderInfo) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - var v github_com_babylonchain_babylon_types.BTCHeaderHashBytes - m.Hash = &v - if err := m.Hash.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + var v github_com_cosmos_cosmos_sdk_types.Uint + m.Work = &v + if err := m.Work.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - case 3: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) - } - m.Height = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBtclightclient - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.Height |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } default: iNdEx = preIndex skippy, err := skipBtclightclient(dAtA[iNdEx:]) diff --git a/x/btclightclient/types/errors.go b/x/btclightclient/types/errors.go index 3829a611a..b76689426 100644 --- a/x/btclightclient/types/errors.go +++ b/x/btclightclient/types/errors.go @@ -12,4 +12,5 @@ var ( ErrDuplicateHeader = sdkerrors.Register(ModuleName, 1101, "duplicate header") ErrHeaderParentDoesNotExist = sdkerrors.Register(ModuleName, 1102, "header parent does not exist") ErrInvalidDifficulty = sdkerrors.Register(ModuleName, 1103, "invalid difficulty bits") + ErrHeaderHigherThanTip = sdkerrors.Register(ModuleName, 1104, "header is higher than the tip") ) diff --git a/x/btclightclient/types/event.pb.go b/x/btclightclient/types/event.pb.go index 46f732761..9712be940 100644 --- a/x/btclightclient/types/event.pb.go +++ b/x/btclightclient/types/event.pb.go @@ -23,7 +23,10 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package -// EventBTCRollBack is emitted on Msg/InsertHeader +// The header included in the event is the block in the history +// of the current mainchain to which we are rolling back to. +// In other words, there is one rollback event emitted per re-org, to the +// greatest common ancestor of the old and the new fork. type EventBTCRollBack struct { Header *BTCHeaderInfo `protobuf:"bytes,1,opt,name=header,proto3" json:"header,omitempty"` } @@ -69,6 +72,9 @@ func (m *EventBTCRollBack) GetHeader() *BTCHeaderInfo { } // EventBTCRollForward is emitted on Msg/InsertHeader +// The header included in the event is the one the main chain is extended with. +// In the event of a reorg, each block on the new fork that comes after +// the greatest common ancestor will have a corresponding roll forward event. type EventBTCRollForward struct { Header *BTCHeaderInfo `protobuf:"bytes,1,opt,name=header,proto3" json:"header,omitempty"` } diff --git a/x/btclightclient/types/genesis.go b/x/btclightclient/types/genesis.go index 43c983dc8..333b1fe42 100644 --- a/x/btclightclient/types/genesis.go +++ b/x/btclightclient/types/genesis.go @@ -13,10 +13,18 @@ const ( // DefaultGenesis returns the default Capability genesis state func DefaultGenesis() *GenesisState { headerBytes, _ := bbl.NewBTCHeaderBytesFromHex(DefaultBaseHeaderHex) + headerHash := headerBytes.Hash() + // The cumulative work for the Base BTC header is only the work + // for that particular header. This means that it is very important + // that no forks will happen that discard the base header because we + // will not be able to detect those. Cumulative work will build based + // on the sum of the work of the chain starting from the base header. + headerWork := CalcWork(&headerBytes) + baseHeaderInfo := NewBTCHeaderInfo(&headerBytes, headerHash, DefaultBaseHeaderHeight, &headerWork) return &GenesisState{ Params: DefaultParams(), - BaseBtcHeader: DefaultBaseBTCHeader(headerBytes, DefaultBaseHeaderHeight), + BaseBtcHeader: *baseHeaderInfo, } } diff --git a/x/btclightclient/types/genesis.pb.go b/x/btclightclient/types/genesis.pb.go index ab2d0a734..09cc75a3f 100644 --- a/x/btclightclient/types/genesis.pb.go +++ b/x/btclightclient/types/genesis.pb.go @@ -26,7 +26,7 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // GenesisState defines the btclightclient module's genesis state. type GenesisState struct { Params Params `protobuf:"bytes,1,opt,name=params,proto3" json:"params"` - BaseBtcHeader BaseBTCHeader `protobuf:"bytes,2,opt,name=base_btc_header,json=baseBtcHeader,proto3" json:"base_btc_header"` + BaseBtcHeader BTCHeaderInfo `protobuf:"bytes,2,opt,name=base_btc_header,json=baseBtcHeader,proto3" json:"base_btc_header"` } func (m *GenesisState) Reset() { *m = GenesisState{} } @@ -69,11 +69,11 @@ func (m *GenesisState) GetParams() Params { return Params{} } -func (m *GenesisState) GetBaseBtcHeader() BaseBTCHeader { +func (m *GenesisState) GetBaseBtcHeader() BTCHeaderInfo { if m != nil { return m.BaseBtcHeader } - return BaseBTCHeader{} + return BTCHeaderInfo{} } func init() { @@ -85,7 +85,7 @@ func init() { } var fileDescriptor_723ed9409b965050 = []byte{ - // 257 bytes of a gzipped FileDescriptorProto + // 258 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x49, 0x4a, 0x4c, 0xaa, 0xcc, 0xc9, 0xcf, 0xd3, 0x4f, 0x2a, 0x49, 0xce, 0xc9, 0x4c, 0xcf, 0x00, 0x91, 0xa9, 0x79, 0x25, 0xfa, 0xe9, 0xa9, 0x79, 0xa9, 0xc5, 0x99, 0xc5, 0x7a, 0x05, 0x45, 0xf9, 0x25, 0xf9, 0x42, 0x92, @@ -96,13 +96,13 @@ var fileDescriptor_723ed9409b965050 = []byte{ 0xd4, 0xe0, 0x36, 0x52, 0xd4, 0xc3, 0xe9, 0x48, 0xbd, 0x00, 0xb0, 0x42, 0x27, 0x96, 0x13, 0xf7, 0xe4, 0x19, 0x82, 0xa0, 0xda, 0x84, 0xc2, 0xb8, 0xf8, 0x93, 0x12, 0x8b, 0x53, 0xe3, 0x93, 0x4a, 0x92, 0xe3, 0x33, 0x52, 0x13, 0x53, 0x52, 0x8b, 0x24, 0x98, 0xc0, 0x26, 0x69, 0xe0, 0x31, 0xc9, - 0x29, 0xb1, 0x38, 0xd5, 0x29, 0xc4, 0xd9, 0x03, 0xac, 0x1e, 0x6a, 0x20, 0x2f, 0xc8, 0x18, 0xa7, - 0x92, 0x64, 0xa8, 0x60, 0xc0, 0x89, 0x47, 0x72, 0x8c, 0x17, 0x1e, 0xc9, 0x31, 0x3e, 0x78, 0x24, - 0xc7, 0x38, 0xe1, 0xb1, 0x1c, 0xc3, 0x85, 0xc7, 0x72, 0x0c, 0x37, 0x1e, 0xcb, 0x31, 0x44, 0x99, - 0xa5, 0x67, 0x96, 0x64, 0x94, 0x26, 0xe9, 0x25, 0xe7, 0xe7, 0xea, 0x43, 0xad, 0x48, 0xce, 0x48, - 0xcc, 0xcc, 0x83, 0x71, 0xf4, 0x2b, 0xd0, 0x83, 0xa2, 0xa4, 0xb2, 0x20, 0xb5, 0x38, 0x89, 0x0d, - 0x1c, 0x04, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc6, 0x2e, 0x49, 0xd9, 0xad, 0x01, 0x00, - 0x00, + 0x29, 0xc4, 0xd9, 0x03, 0xac, 0xd6, 0x33, 0x2f, 0x2d, 0x1f, 0x6a, 0x20, 0x2f, 0xc8, 0x18, 0xa7, + 0x92, 0x64, 0x88, 0x84, 0x53, 0xc0, 0x89, 0x47, 0x72, 0x8c, 0x17, 0x1e, 0xc9, 0x31, 0x3e, 0x78, + 0x24, 0xc7, 0x38, 0xe1, 0xb1, 0x1c, 0xc3, 0x85, 0xc7, 0x72, 0x0c, 0x37, 0x1e, 0xcb, 0x31, 0x44, + 0x99, 0xa5, 0x67, 0x96, 0x64, 0x94, 0x26, 0xe9, 0x25, 0xe7, 0xe7, 0xea, 0x43, 0xad, 0x48, 0xce, + 0x48, 0xcc, 0xcc, 0x83, 0x71, 0xf4, 0x2b, 0xd0, 0x83, 0xa2, 0xa4, 0xb2, 0x20, 0xb5, 0x38, 0x89, + 0x0d, 0x1c, 0x04, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x20, 0xd6, 0xaf, 0x9a, 0xad, 0x01, + 0x00, 0x00, } func (m *GenesisState) Marshal() (dAtA []byte, err error) { diff --git a/x/btclightclient/types/keys.go b/x/btclightclient/types/keys.go index 47ff6e7ae..1e155af17 100644 --- a/x/btclightclient/types/keys.go +++ b/x/btclightclient/types/keys.go @@ -2,7 +2,6 @@ package types import ( bbl "github.com/babylonchain/babylon/types" - "github.com/btcsuite/btcd/chaincfg/chainhash" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -31,24 +30,20 @@ var ( TipPrefix = append(HeadersPrefix, 0x3) // where we store the tip ) -func HeadersObjectKey(height uint64, hash *chainhash.Hash) []byte { +func HeadersObjectKey(height uint64, hash *bbl.BTCHeaderHashBytes) []byte { he := sdk.Uint64ToBigEndian(height) - - hashBytes := bbl.NewBTCHeaderHashBytesFromChainhash(hash) + hashBytes := hash.MustMarshal() heightPrefix := append(HeadersObjectPrefix, he...) return append(heightPrefix, hashBytes...) } -func HeadersObjectHeightKey(hash *chainhash.Hash) []byte { - hashBytes := bbl.NewBTCHeaderHashBytesFromChainhash(hash) - - return append(HashToHeightPrefix, hashBytes...) +func HeadersObjectHeightKey(hash *bbl.BTCHeaderHashBytes) []byte { + return append(HashToHeightPrefix, hash.MustMarshal()...) } -func HeadersObjectWorkKey(hash *chainhash.Hash) []byte { - hashBytes := bbl.NewBTCHeaderHashBytesFromChainhash(hash) - return append(HashToWorkPrefix, hashBytes...) +func HeadersObjectWorkKey(hash *bbl.BTCHeaderHashBytes) []byte { + return append(HashToWorkPrefix, hash.MustMarshal()...) } func TipKey() []byte { diff --git a/x/btclightclient/types/keys_test.go b/x/btclightclient/types/keys_test.go index 60207aae4..c45dc8fd8 100644 --- a/x/btclightclient/types/keys_test.go +++ b/x/btclightclient/types/keys_test.go @@ -5,7 +5,6 @@ import ( "github.com/babylonchain/babylon/testutil/datagen" bbl "github.com/babylonchain/babylon/types" "github.com/babylonchain/babylon/x/btclightclient/types" - "github.com/btcsuite/btcd/chaincfg/chainhash" sdk "github.com/cosmos/cosmos-sdk/types" "math/rand" "testing" @@ -20,15 +19,15 @@ func FuzzHeadersObjectKey(f *testing.F) { hexHash = datagen.GenRandomHexStr(bbl.BTCHeaderHashLen) } // get chainhash and height - chHash, _ := chainhash.NewHashFromStr(hexHash) heightBytes := sdk.Uint64ToBigEndian(height) + headerHash, _ := bbl.NewBTCHeaderHashBytesFromHex(hexHash) // construct the expected key - chHashBytes := chHash[:] + headerHashBytes := headerHash.MustMarshal() expectedKey := append(types.HeadersObjectPrefix, heightBytes...) - expectedKey = append(expectedKey, chHashBytes...) + expectedKey = append(expectedKey, headerHashBytes...) - gotKey := types.HeadersObjectKey(height, chHash) + gotKey := types.HeadersObjectKey(height, &headerHash) if bytes.Compare(expectedKey, gotKey) != 0 { t.Errorf("Expected headers object key %s got %s", expectedKey, gotKey) } @@ -43,20 +42,17 @@ func FuzzHeadersObjectHeightAndWorkKey(f *testing.F) { if !datagen.ValidHex(hexHash, bbl.BTCHeaderHashLen) { hexHash = datagen.GenRandomHexStr(bbl.BTCHeaderHashLen) } - // Get the chainhash - chHash, _ := chainhash.NewHashFromStr(hexHash) + headerHash, _ := bbl.NewBTCHeaderHashBytesFromHex(hexHash) + headerHashBytes := headerHash.MustMarshal() - // Construct the expected key - chHashBytes := chHash[:] - - expectedHeightKey := append(types.HashToHeightPrefix, chHashBytes...) - gotHeightKey := types.HeadersObjectHeightKey(chHash) + expectedHeightKey := append(types.HashToHeightPrefix, headerHashBytes...) + gotHeightKey := types.HeadersObjectHeightKey(&headerHash) if bytes.Compare(expectedHeightKey, gotHeightKey) != 0 { t.Errorf("Expected headers object height key %s got %s", expectedHeightKey, gotHeightKey) } - expectedWorkKey := append(types.HashToWorkPrefix, chHashBytes...) - gotWorkKey := types.HeadersObjectWorkKey(chHash) + expectedWorkKey := append(types.HashToWorkPrefix, headerHashBytes...) + gotWorkKey := types.HeadersObjectWorkKey(&headerHash) if bytes.Compare(expectedWorkKey, gotWorkKey) != 0 { t.Errorf("Expected headers object work key %s got %s", expectedWorkKey, gotWorkKey) } diff --git a/x/btclightclient/types/querier.go b/x/btclightclient/types/querier.go index 2dce9283e..a1ddc83d3 100644 --- a/x/btclightclient/types/querier.go +++ b/x/btclightclient/types/querier.go @@ -1,6 +1,9 @@ package types -import "github.com/babylonchain/babylon/types" +import ( + "github.com/babylonchain/babylon/types" + "github.com/cosmos/cosmos-sdk/types/query" +) // NewQueryParamsRequest creates a new instance of QueryParamsRequest. func NewQueryParamsRequest() *QueryParamsRequest { @@ -8,8 +11,8 @@ func NewQueryParamsRequest() *QueryParamsRequest { } // NewQueryHashesRequest creates a new instance of QueryHashesRequest. -func NewQueryHashesRequest() *QueryHashesRequest { - return &QueryHashesRequest{} +func NewQueryHashesRequest(req *query.PageRequest) *QueryHashesRequest { + return &QueryHashesRequest{Pagination: req} } // NewQueryContainsRequest creates a new instance of QueryContainsRequest. @@ -22,6 +25,6 @@ func NewQueryContainsRequest(hash string) (*QueryContainsRequest, error) { return res, nil } -func NewQueryMainChainRequest() *QueryMainChainRequest { - return &QueryMainChainRequest{} +func NewQueryMainChainRequest(req *query.PageRequest) *QueryMainChainRequest { + return &QueryMainChainRequest{Pagination: req} } diff --git a/x/btclightclient/types/querier_test.go b/x/btclightclient/types/querier_test.go index ff222eb58..0d78efe1a 100644 --- a/x/btclightclient/types/querier_test.go +++ b/x/btclightclient/types/querier_test.go @@ -6,6 +6,7 @@ import ( bbl "github.com/babylonchain/babylon/types" "github.com/babylonchain/babylon/x/btclightclient/types" "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/cosmos/cosmos-sdk/types/query" "math/rand" "testing" ) @@ -23,14 +24,21 @@ func TestNewQueryParamsRequest(t *testing.T) { } func TestNewQueryHashesRequest(t *testing.T) { - newQueryHashes := types.NewQueryHashesRequest() + headerBytes, _ := bbl.NewBTCHeaderBytesFromHex(types.DefaultBaseHeaderHex) + headerHashBytes := headerBytes.Hash() + req := query.PageRequest{ + Key: headerHashBytes.MustMarshal(), + } + newQueryHashes := types.NewQueryHashesRequest(&req) if newQueryHashes == nil { t.Errorf("A nil object was returned") } - emptyQueryHashes := types.QueryHashesRequest{} - if *newQueryHashes != emptyQueryHashes { - t.Errorf("expected an empty QueryHashesRequest") + expectedQueryHashes := types.QueryHashesRequest{ + Pagination: &req, + } + if *newQueryHashes != expectedQueryHashes { + t.Errorf("expected a QueryHashesRequest %s", expectedQueryHashes) } } @@ -60,13 +68,19 @@ func FuzzNewQueryContainsRequest(f *testing.F) { } func TestNewQueryMainChainRequest(t *testing.T) { - newQueryMainChain := types.NewQueryMainChainRequest() + headerBytes, _ := bbl.NewBTCHeaderBytesFromHex(types.DefaultBaseHeaderHex) + req := query.PageRequest{ + Key: headerBytes.MustMarshal(), + } + newQueryMainChain := types.NewQueryMainChainRequest(&req) if newQueryMainChain == nil { t.Errorf("A nil object was returned") } - emptyQueryMainChain := types.QueryMainChainRequest{} - if *newQueryMainChain != emptyQueryMainChain { - t.Errorf("expected an empty QueryMainChainRequest") + expectedQueryMainChain := types.QueryMainChainRequest{ + Pagination: &req, + } + if *newQueryMainChain != expectedQueryMainChain { + t.Errorf("expected a QueryMainChainRequest %s", expectedQueryMainChain) } } diff --git a/x/btclightclient/types/work.go b/x/btclightclient/types/work.go index 101c8f1d7..4c86004cf 100644 --- a/x/btclightclient/types/work.go +++ b/x/btclightclient/types/work.go @@ -1,17 +1,18 @@ package types import ( + bbl "github.com/babylonchain/babylon/types" "github.com/btcsuite/btcd/blockchain" - "github.com/btcsuite/btcd/wire" - "math/big" + sdk "github.com/cosmos/cosmos-sdk/types" ) -func CalcWork(header *wire.BlockHeader) *big.Int { - return blockchain.CalcWork(header.Bits) +func CalcWork(header *bbl.BTCHeaderBytes) sdk.Uint { + return sdk.NewUintFromBigInt(blockchain.CalcWork(header.Bits())) } -func CumulativeWork(childWork *big.Int, parentWork *big.Int) *big.Int { - sum := new(big.Int) - sum.Add(childWork, parentWork) +func CumulativeWork(childWork sdk.Uint, parentWork sdk.Uint) sdk.Uint { + sum := sdk.NewUint(0) + sum.Add(childWork) + sum.Add(parentWork) return sum } diff --git a/x/btclightclient/types/work_test.go b/x/btclightclient/types/work_test.go index 7475e8547..8b07db58b 100644 --- a/x/btclightclient/types/work_test.go +++ b/x/btclightclient/types/work_test.go @@ -2,22 +2,23 @@ package types_test import ( "github.com/babylonchain/babylon/x/btclightclient/types" - "math/big" + sdk "github.com/cosmos/cosmos-sdk/types" "testing" ) func FuzzCumulativeWork(f *testing.F) { - f.Add(int64(17), int64(25)) - f.Fuzz(func(t *testing.T, numa int64, numb int64) { - biga := big.NewInt(numa) - bigb := big.NewInt(numb) + f.Add(uint64(17), uint64(25)) + f.Fuzz(func(t *testing.T, numa uint64, numb uint64) { + biga := sdk.NewUint(numa) + bigb := sdk.NewUint(numb) gotSum := types.CumulativeWork(biga, bigb) - expectedSum := new(big.Int) - expectedSum.Add(biga, bigb) + expectedSum := sdk.NewUint(0) + expectedSum.Add(biga) + expectedSum.Add(bigb) - if expectedSum.Cmp(gotSum) != 0 { + if !expectedSum.Equal(gotSum) { t.Errorf("Cumulative work does not correspond to actual one") } })