diff --git a/app/modules.go b/app/modules.go index b4b061af..541f6011 100644 --- a/app/modules.go +++ b/app/modules.go @@ -95,6 +95,7 @@ var moduleAccountPermissions = map[string][]string{ wasmTypes.ModuleName: {authtypes.Burner}, gmmmoduletypes.ModuleName: {authtypes.Minter, authtypes.Burner, authtypes.Staking}, yieldmoduletypes.ModuleName: {authtypes.Minter, authtypes.Burner, authtypes.Staking}, + btclightclienttypes.ModuleName: {authtypes.Minter, authtypes.Burner}, } // appModules return modules to initialize module manager. diff --git a/x/btclightclient/client/cli/query.go b/x/btclightclient/client/cli/query.go index e831815c..849a8c9a 100644 --- a/x/btclightclient/client/cli/query.go +++ b/x/btclightclient/client/cli/query.go @@ -2,6 +2,8 @@ package cli import ( "fmt" + "strconv" + // "strings" "github.com/sideprotocol/side/x/btclightclient/types" @@ -24,6 +26,7 @@ func GetQueryCmd(_ string) *cobra.Command { cmd.AddCommand(CmdQueryParams()) cmd.AddCommand(CmdBestBlock()) + cmd.AddCommand(CmdQueryBlock()) // this line is used by starport scaffolding # 1 return cmd @@ -82,3 +85,42 @@ func CmdBestBlock() *cobra.Command { return cmd } + +// CmdQueryBlock returns the command to query the heights of the light client +func CmdQueryBlock() *cobra.Command { + cmd := &cobra.Command{ + Use: "block [hash or height]", + Short: "Query block by hash or height", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + queryClient := types.NewQueryClient(clientCtx) + + height, err := strconv.ParseUint(args[0], 10, 64) + + if err != nil { + res, err := queryClient.QueryBlockHeaderByHash(cmd.Context(), &types.QueryBlockHeaderByHashRequest{Hash: args[0]}) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + } + + res, err := queryClient.QueryBlockHeaderByHeight(cmd.Context(), &types.QueryBlockHeaderByHeightRequest{Height: height}) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} diff --git a/x/btclightclient/genesis_test.go b/x/btclightclient/genesis_test.go index 9df8b423..9f5e3e96 100644 --- a/x/btclightclient/genesis_test.go +++ b/x/btclightclient/genesis_test.go @@ -1,8 +1,14 @@ package btclightclient_test +// Path: x/btclightclient/genesis_test.go + import ( + "bytes" + "encoding/hex" "testing" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/wire" keepertest "github.com/sideprotocol/side/testutil/keeper" "github.com/sideprotocol/side/testutil/nullify" "github.com/sideprotocol/side/x/btclightclient" @@ -31,3 +37,47 @@ func TestGenesis(t *testing.T) { // this line is used by starport scaffolding # genesis/test/assert } + +// TestSubmitTx tests the SubmitTx function +// func TestSubmitTx(t *testing.T) { + +// // test tx: https://blockchain.info/tx/b657e22827039461a9493ede7bdf55b01579254c1630b0bfc9185ec564fc05ab?format=json + +// k, ctx := keepertest.BtcLightClientKeeper(t) + +// txHex := "02000000000101e5df234dbeff74d6da14754ebeea8ab0e2f60b1884566846cf6b36e8ceb5f5350100000000fdffffff02f0490200000000001600142ac13073e8d491428790481321a636696d00107681d7e205000000001600142bf3aa186cbdcbe88b70a67edcd5a32ce5e8e6d8024730440220081ee61d749ce8cedcf6eedde885579af2eb65ca67d29e6ae2c37109d81cbbb202203a1891ce45f91f159ccf04348ef37a3d1a12d89e5e01426e061326057e6c128d012103036bbdd77c9a932f37bd66175967c7fb7eb75ece06b87c1ad1716770cb3ca4ee79fc2a00" +// prevTxHex := "0200000000010183372652f2af9ab34b3a003efada6b054c75583185ac130de72599dfdf4e462b0100000000fdffffff02f0490200000000001600142ac13073e8d491428790481321a636696d001076a64ee50500000000160014a03614eef338681373de94a2dc2574de55da1980024730440220274250f6036bea0947daf4455ab4976f81721257d163fd952fb5b0c70470edc602202fba816be260219bbc40a8983c459cf05cf2209bf1e62e7ccbf78aec54db607f0121031cee21ef69fe68b240c3032616fa310c6a60a856c0a7e0c1298815c92fb2c61788fb2a00" + +// msg := &types.MsgSubmitTransactionRequest{ +// Sender: "", +// Blockhash: "000000000d73ecf25d3bf8e6ae65c35aa2a90e3271edff8bab90d87ed875f13b", +// TxBytes: "0100000001b3f7", +// PrevTxBytes: "0100000001b3f7", +// Proof: []string{"0100000001b3f7"}, +// } +// // this line is used by starport scaffolding # handler/test/submit +// err := k.ProcessBitcoinDepositTransaction(ctx, msg) +// require.NoError(t, err) +// } + +// Decode transaction +func TestDecodeTransaction(t *testing.T) { + hexStr := "02000000000101e5df234dbeff74d6da14754ebeea8ab0e2f60b1884566846cf6b36e8ceb5f5350100000000fdffffff02f0490200000000001600142ac13073e8d491428790481321a636696d00107681d7e205000000001600142bf3aa186cbdcbe88b70a67edcd5a32ce5e8e6d8024730440220081ee61d749ce8cedcf6eedde885579af2eb65ca67d29e6ae2c37109d81cbbb202203a1891ce45f91f159ccf04348ef37a3d1a12d89e5e01426e061326057e6c128d012103036bbdd77c9a932f37bd66175967c7fb7eb75ece06b87c1ad1716770cb3ca4ee79fc2a00" + + // Decode the hex string to transaction + txBytes, err := hex.DecodeString(hexStr) + require.NoError(t, err) + + // Create a new transaction + var tx wire.MsgTx + err = tx.Deserialize(bytes.NewReader(txBytes)) + require.NoError(t, err) + + uTx := btcutil.NewTx(&tx) + + for _, input := range uTx.MsgTx().TxIn { + t.Log(input.PreviousOutPoint.String()) + } + + require.GreaterOrEqual(t, len(uTx.MsgTx().TxIn), 1) +} diff --git a/x/btclightclient/keeper/keeper.go b/x/btclightclient/keeper/keeper.go index cb9d9dbb..2581c3c2 100644 --- a/x/btclightclient/keeper/keeper.go +++ b/x/btclightclient/keeper/keeper.go @@ -113,6 +113,7 @@ func (k Keeper) SetBlockHeaders(ctx sdk.Context, blockHeader []*types.BlockHeade // remove the block headers after the forked block header // and consider the forked block header as the best block header for i := header.Height; i <= best.Height; i++ { + ctx.Logger().Info("Removing block header: ", i) thash := k.GetBlockHashByHeight(ctx, i) store.Delete(types.BtcBlockHeaderHashKey(thash)) store.Delete(types.BtcBlockHeaderHeightKey(i)) @@ -139,7 +140,7 @@ func (k Keeper) SetBlockHeaders(ctx sdk.Context, blockHeader []*types.BlockHeade // Process Bitcoin Deposit Transaction func (k Keeper) ProcessBitcoinDepositTransaction(ctx sdk.Context, msg *types.MsgSubmitTransactionRequest) error { - ctx.Logger().Debug("Processing Transaction in block: ", msg.Blockhash) + ctx.Logger().Info("accept bitcoin deposit tx", "blockhash", msg.Blockhash) param := k.GetParams(ctx) header := k.GetBlockHeader(ctx, msg.Blockhash) @@ -217,11 +218,17 @@ func (k Keeper) ProcessBitcoinDepositTransaction(ctx sdk.Context, msg *types.Msg if err != nil { return err } + sender, err := pk.Address(types.ChainCfg) if err != nil { return err } + // if pk.Class() != txscript.WitnessV1TaprootTy || pk.Class() != txscript.WitnessV0PubKeyHashTy || pk.Class() != txscript.WitnessV0ScriptHashTy { + // ctx.Logger().Error("Unsupported script type", "script", pk.Class(), "address", sender.EncodeAddress()) + // return types.ErrUnsupportedScriptType + // } + // check if the proof is valid root, err := chainhash.NewHashFromStr(header.MerkleRoot) if err != nil { @@ -233,24 +240,28 @@ func (k Keeper) ProcessBitcoinDepositTransaction(ctx sdk.Context, msg *types.Msg for _, out := range uTx.MsgTx().TxOut { // check if the output is a valid address - pk, err := txscript.ParsePkScript(out.PkScript) + pks, err := txscript.ParsePkScript(out.PkScript) if err != nil { return err } - addr, err := pk.Address(types.ChainCfg) + addr, err := pks.Address(types.ChainCfg) if err != nil { return err } - if slices.Contains(param.BtcVoucherAddress, addr.EncodeAddress()) { + // TODO remove the true + // check if the receiver is one of the voucher addresses + if true || slices.Contains(param.BtcVoucherAddress, addr.EncodeAddress()) { // mint the voucher token - coins := sdk.NewCoins(sdk.NewCoin(param.BtcVoucherDenom, sdk.NewInt(int64(out.Value)))) + coins := sdk.NewCoins(sdk.NewCoin(param.BtcVoucherDenom, sdk.NewInt(out.Value))) senderAddr, err := sdk.AccAddressFromBech32(sender.EncodeAddress()) if err != nil { return err } k.bankKeeper.MintCoins(ctx, types.ModuleName, coins) k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, senderAddr, coins) + + ctx.Logger().Info("Voucher token minted", "address", senderAddr.String(), "amount", coins.String()) } } @@ -260,10 +271,10 @@ func (k Keeper) ProcessBitcoinDepositTransaction(ctx sdk.Context, msg *types.Msg func (k Keeper) GetBlockHeader(ctx sdk.Context, hash string) *types.BlockHeader { store := ctx.KVStore(k.storeKey) - var blockHeader *types.BlockHeader + var blockHeader types.BlockHeader bz := store.Get(types.BtcBlockHeaderHashKey(hash)) - k.cdc.MustUnmarshal(bz, blockHeader) - return blockHeader + k.cdc.MustUnmarshal(bz, &blockHeader) + return &blockHeader } func (k Keeper) GetBlockHashByHeight(ctx sdk.Context, height uint64) string { diff --git a/x/btclightclient/keeper/keeper_test.go b/x/btclightclient/keeper/keeper_test.go new file mode 100644 index 00000000..94292649 --- /dev/null +++ b/x/btclightclient/keeper/keeper_test.go @@ -0,0 +1 @@ +package keeper_test diff --git a/x/btclightclient/keeper/msg_server.go b/x/btclightclient/keeper/msg_server.go index e8801693..1d365d66 100644 --- a/x/btclightclient/keeper/msg_server.go +++ b/x/btclightclient/keeper/msg_server.go @@ -50,14 +50,20 @@ func (m msgServer) SubmitTransaction(goCtx context.Context, msg *types.MsgSubmit ctx := sdk.UnwrapSDKContext(goCtx) if err := msg.ValidateBasic(); err != nil { + ctx.Logger().Error("Error validating basic", "error", err) return nil, err } if err := m.ProcessBitcoinDepositTransaction(ctx, msg); err != nil { + ctx.Logger().Error("Error processing bitcoin deposit transaction", "error", err) return nil, err } // Emit Events + m.EmitEvent(ctx, msg.Sender, + sdk.NewAttribute("blockhash", msg.Blockhash), + sdk.NewAttribute("txBytes", msg.TxBytes), + ) return &types.MsgSubmitTransactionResponse{}, nil diff --git a/x/btclightclient/types/errors.go b/x/btclightclient/types/errors.go index 9f751bea..d09956b5 100644 --- a/x/btclightclient/types/errors.go +++ b/x/btclightclient/types/errors.go @@ -17,7 +17,8 @@ var ( ErrInvalidBtcTransaction = errorsmod.Register(ModuleName, 3100, "invalid bitcoin transaction") ErrBlockNotFound = errorsmod.Register(ModuleName, 3101, "block not found") - ErrTransactionNotIncluded = errorsmod.Register(ModuleName, 3101, "transaction not included in block") + ErrTransactionNotIncluded = errorsmod.Register(ModuleName, 3102, "transaction not included in block") ErrNotConfirmed = errorsmod.Register(ModuleName, 3200, "transaction not confirmed") ErrExceedMaxAcceptanceDepth = errorsmod.Register(ModuleName, 3201, "exceed max acceptance block depth") + ErrUnsupportedScriptType = errorsmod.Register(ModuleName, 3202, "unsupported script type") )