Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions proto/side/btcbridge/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ message MsgSubmitWithdrawTransaction {
// this is relayer address who submit the bitcoin transaction to the side chain
string sender = 1;
string blockhash = 2;
// the previous tx bytes in base64 format
string prev_tx_bytes = 3;
// the tx bytes in base64 format
string tx_bytes = 4;
repeated string proof = 5;
Expand Down
89 changes: 89 additions & 0 deletions x/btcbridge/keeper/keeper.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package keeper

import (
"bytes"
"encoding/base64"
"fmt"

"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/cometbft/cometbft/libs/log"
"github.com/cosmos/cosmos-sdk/codec"
storetypes "github.com/cosmos/cosmos-sdk/store/types"
Expand Down Expand Up @@ -175,3 +180,87 @@ func (k Keeper) IterateBlockHeaders(ctx sdk.Context, process func(header types.B
}
}
}

// ValidateTransaction validates the given transaction
func (k Keeper) ValidateTransaction(ctx sdk.Context, txBytes string, prevTxBytes string, blockHash string, proof []string) (*btcutil.Tx, *btcutil.Tx, error) {
params := k.GetParams(ctx)

header := k.GetBlockHeader(ctx, blockHash)
// Check if block confirmed
if header == nil || header.Height == 0 {
return nil, nil, types.ErrBlockNotFound
}

best := k.GetBestBlockHeader(ctx)
// Check if the block is confirmed
if best.Height-header.Height < uint64(params.Confirmations) {
return nil, nil, types.ErrNotConfirmed
}
// Check if the block is within the acceptable depth
// if best.Height-header.Height > param.MaxAcceptableBlockDepth {
// return types.ErrExceedMaxAcceptanceDepth
// }

// Decode the base64 transaction
rawTx, err := base64.StdEncoding.DecodeString(txBytes)
if err != nil {
fmt.Println("Error decoding transaction from base64:", err)
return nil, nil, err
}

// Create a new transaction
var tx wire.MsgTx
err = tx.Deserialize(bytes.NewReader(rawTx))
if err != nil {
fmt.Println("Error deserializing transaction:", err)
return nil, nil, err
}

uTx := btcutil.NewTx(&tx)

// Validate the transaction
if err := blockchain.CheckTransactionSanity(uTx); err != nil {
fmt.Println("Transaction is not valid:", err)
return nil, nil, err
}

// Decode the previous transaction
rawPrevTx, err := base64.StdEncoding.DecodeString(prevTxBytes)
if err != nil {
fmt.Println("Error decoding transaction from base64:", err)
return nil, nil, err
}

// Create a new transaction
var prevMsgTx wire.MsgTx
err = prevMsgTx.Deserialize(bytes.NewReader(rawPrevTx))
if err != nil {
fmt.Println("Error deserializing transaction:", err)
return nil, nil, err
}

prevTx := btcutil.NewTx(&prevMsgTx)

// Validate the transaction
if err := blockchain.CheckTransactionSanity(prevTx); err != nil {
fmt.Println("Transaction is not valid:", err)
return nil, nil, err
}

if uTx.MsgTx().TxIn[0].PreviousOutPoint.Hash.String() != prevTx.Hash().String() {
return nil, nil, types.ErrInvalidBtcTransaction
}

// check if the proof is valid
root, err := chainhash.NewHashFromStr(header.MerkleRoot)
if err != nil {
return nil, nil, err
}

if !types.VerifyMerkleProof(proof, uTx.Hash(), root) {
k.Logger(ctx).Error("Invalid merkle proof", "txhash", tx, "root", root, "proof", proof)
return nil, nil, types.ErrTransactionNotIncluded
}

return uTx, prevTx, nil
}
91 changes: 1 addition & 90 deletions x/btcbridge/keeper/keeper_deposit.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
package keeper

import (
"bytes"
"encoding/base64"
"fmt"

"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
Expand All @@ -20,7 +15,7 @@ import (
func (k Keeper) ProcessBitcoinDepositTransaction(ctx sdk.Context, msg *types.MsgSubmitDepositTransaction) (*chainhash.Hash, btcutil.Address, error) {
ctx.Logger().Info("accept bitcoin deposit tx", "blockhash", msg.Blockhash)

tx, prevTx, err := k.ValidateDepositTransaction(ctx, msg.TxBytes, msg.PrevTxBytes, msg.Blockhash, msg.Proof)
tx, prevTx, err := k.ValidateTransaction(ctx, msg.TxBytes, msg.PrevTxBytes, msg.Blockhash, msg.Proof)
if err != nil {
return nil, nil, err
}
Expand All @@ -33,90 +28,6 @@ func (k Keeper) ProcessBitcoinDepositTransaction(ctx sdk.Context, msg *types.Msg
return tx.Hash(), recipient, nil
}

// validateDepositTransaction validates the deposit transaction
func (k Keeper) ValidateDepositTransaction(ctx sdk.Context, txBytes string, prevTxBytes string, blockHash string, proof []string) (*btcutil.Tx, *btcutil.Tx, error) {
params := k.GetParams(ctx)

header := k.GetBlockHeader(ctx, blockHash)
// Check if block confirmed
if header == nil || header.Height == 0 {
return nil, nil, types.ErrBlockNotFound
}

best := k.GetBestBlockHeader(ctx)
// Check if the block is confirmed
if best.Height-header.Height < uint64(params.Confirmations) {
return nil, nil, types.ErrNotConfirmed
}
// Check if the block is within the acceptable depth
// if best.Height-header.Height > param.MaxAcceptableBlockDepth {
// return types.ErrExceedMaxAcceptanceDepth
// }

// Decode the base64 transaction
rawTx, err := base64.StdEncoding.DecodeString(txBytes)
if err != nil {
fmt.Println("Error decoding transaction from base64:", err)
return nil, nil, err
}

// Create a new transaction
var tx wire.MsgTx
err = tx.Deserialize(bytes.NewReader(rawTx))
if err != nil {
fmt.Println("Error deserializing transaction:", err)
return nil, nil, err
}

uTx := btcutil.NewTx(&tx)

// Validate the transaction
if err := blockchain.CheckTransactionSanity(uTx); err != nil {
fmt.Println("Transaction is not valid:", err)
return nil, nil, err
}

// Decode the previous transaction
rawPrevTx, err := base64.StdEncoding.DecodeString(prevTxBytes)
if err != nil {
fmt.Println("Error decoding transaction from base64:", err)
return nil, nil, err
}

// Create a new transaction
var prevMsgTx wire.MsgTx
err = prevMsgTx.Deserialize(bytes.NewReader(rawPrevTx))
if err != nil {
fmt.Println("Error deserializing transaction:", err)
return nil, nil, err
}

prevTx := btcutil.NewTx(&prevMsgTx)

// Validate the transaction
if err := blockchain.CheckTransactionSanity(prevTx); err != nil {
fmt.Println("Transaction is not valid:", err)
return nil, nil, err
}

if uTx.MsgTx().TxIn[0].PreviousOutPoint.Hash.String() != prevTx.Hash().String() {
return nil, nil, types.ErrInvalidBtcTransaction
}

// check if the proof is valid
root, err := chainhash.NewHashFromStr(header.MerkleRoot)
if err != nil {
return nil, nil, err
}

if !types.VerifyMerkleProof(proof, uTx.Hash(), root) {
k.Logger(ctx).Error("Invalid merkle proof", "txhash", tx, "root", root, "proof", proof)
return nil, nil, types.ErrTransactionNotIncluded
}

return uTx, prevTx, nil
}

// mint performs the minting operation of the voucher token
func (k Keeper) Mint(ctx sdk.Context, tx *btcutil.Tx, prevTx *btcutil.Tx, height uint64) (btcutil.Address, error) {
params := k.GetParams(ctx)
Expand Down
63 changes: 13 additions & 50 deletions x/btcbridge/keeper/keeper_withdraw.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
package keeper

import (
"bytes"
"encoding/base64"
"fmt"

"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"

sdk "github.com/cosmos/cosmos-sdk/types"

Expand Down Expand Up @@ -159,62 +152,32 @@ func (k Keeper) FilterWithdrawRequestsByAddr(ctx sdk.Context, req *types.QueryWi
func (k Keeper) ProcessBitcoinWithdrawTransaction(ctx sdk.Context, msg *types.MsgSubmitWithdrawTransaction) (*chainhash.Hash, error) {
ctx.Logger().Info("accept bitcoin withdraw tx", "blockhash", msg.Blockhash)

param := k.GetParams(ctx)

header := k.GetBlockHeader(ctx, msg.Blockhash)
// Check if block confirmed
if header == nil {
return nil, types.ErrBlockNotFound
}

best := k.GetBestBlockHeader(ctx)
// Check if the block is confirmed
if best.Height-header.Height < uint64(param.Confirmations) {
return nil, types.ErrNotConfirmed
}
// Check if the block is within the acceptable depth
if best.Height-header.Height > param.MaxAcceptableBlockDepth {
return nil, types.ErrExceedMaxAcceptanceDepth
}

// Decode the base64 transaction
txBytes, err := base64.StdEncoding.DecodeString(msg.TxBytes)
tx, prevTx, err := k.ValidateTransaction(ctx, msg.TxBytes, msg.PrevTxBytes, msg.Blockhash, msg.Proof)
if err != nil {
fmt.Println("Error decoding transaction from base64:", err)
return nil, err
}

// Create a new transaction
var tx wire.MsgTx
err = tx.Deserialize(bytes.NewReader(txBytes))
if types.SelectVaultByPkScript(k.GetParams(ctx).Vaults, prevTx.MsgTx().TxOut[tx.MsgTx().TxIn[0].PreviousOutPoint.Index].PkScript) == nil {
return nil, types.ErrInvalidWithdrawTransaction
}

sequence, err := types.ParseSequence(tx.MsgTx())
if err != nil {
fmt.Println("Error deserializing transaction:", err)
return nil, err
}

uTx := btcutil.NewTx(&tx)
if len(uTx.MsgTx().TxIn) < 1 {
return nil, types.ErrInvalidBtcTransaction
if !k.HasWithdrawRequest(ctx, sequence) {
return nil, types.ErrWithdrawRequestNotExist
}

txHash := uTx.MsgTx().TxHash()

if !k.HasWithdrawRequestByTxHash(ctx, txHash.String()) {
return nil, types.ErrWithdrawRequestNotExist
withdrawRequest := k.GetWithdrawRequest(ctx, sequence)
if withdrawRequest.Status == types.WithdrawStatus_WITHDRAW_STATUS_CONFIRMED {
return nil, types.ErrWithdrawRequestConfirmed
}

withdrawRequest := k.GetWithdrawRequestByTxHash(ctx, txHash.String())
// if withdrawRequest.Status != types.WithdrawStatus_WITHDRAW_STATUS_BROADCASTED {
// return types.ErrInvalidStatus
// }
withdrawRequest.Txid = tx.Hash().String()
withdrawRequest.Status = types.WithdrawStatus_WITHDRAW_STATUS_CONFIRMED
k.SetWithdrawRequest(ctx, withdrawRequest)

// Validate the transaction
if err := blockchain.CheckTransactionSanity(uTx); err != nil {
fmt.Println("Transaction is not valid:", err)
return nil, err
}

return &txHash, nil
return tx.Hash(), nil
}
27 changes: 15 additions & 12 deletions x/btcbridge/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,20 @@ var (

ErrInvalidSenders = errorsmod.Register(ModuleName, 2100, "invalid allowed senders")

ErrInvalidBtcTransaction = errorsmod.Register(ModuleName, 3100, "invalid bitcoin transaction")
ErrBlockNotFound = errorsmod.Register(ModuleName, 3101, "block not found")
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")
ErrTransactionAlreadyMinted = errorsmod.Register(ModuleName, 3203, "transaction already minted")
ErrInvalidDepositTransaction = errorsmod.Register(ModuleName, 3204, "invalid deposit transaction")

ErrInsufficientBalance = errorsmod.Register(ModuleName, 4201, "insufficient balance")
ErrWithdrawRequestNotExist = errorsmod.Register(ModuleName, 4202, "withdrawal request does not exist")
ErrInvalidStatus = errorsmod.Register(ModuleName, 4203, "invalid status")
ErrInvalidBtcTransaction = errorsmod.Register(ModuleName, 3100, "invalid bitcoin transaction")
ErrBlockNotFound = errorsmod.Register(ModuleName, 3101, "block not found")
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")
ErrTransactionAlreadyMinted = errorsmod.Register(ModuleName, 3203, "transaction already minted")
ErrInvalidDepositTransaction = errorsmod.Register(ModuleName, 3204, "invalid deposit transaction")
ErrInvalidWithdrawTransaction = errorsmod.Register(ModuleName, 3205, "invalid withdrawal transaction")

ErrInsufficientBalance = errorsmod.Register(ModuleName, 4201, "insufficient balance")
ErrWithdrawRequestNotExist = errorsmod.Register(ModuleName, 4202, "withdrawal request does not exist")
ErrWithdrawRequestConfirmed = errorsmod.Register(ModuleName, 4203, "withdrawal request has been confirmed")
ErrInvalidStatus = errorsmod.Register(ModuleName, 4204, "invalid status")

ErrInvalidAmount = errorsmod.Register(ModuleName, 5100, "invalid amount")
ErrAssetNotSupported = errorsmod.Register(ModuleName, 5101, "asset not supported")
Expand All @@ -39,4 +41,5 @@ var (

ErrInvalidDepositAmount = errorsmod.Register(ModuleName, 8100, "invalid deposit amount")
ErrInvalidWithdrawAmount = errorsmod.Register(ModuleName, 8101, "invalid withdrawal amount")
ErrInvalidSequence = errorsmod.Register(ModuleName, 8102, "invalid sequence")
)
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package types

import (
"bytes"
"math/big"

"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/txscript"
Expand All @@ -18,6 +21,11 @@ const (
RunesEdictNum = 1
)

var (
// withdrawal identifier which is used in the OP_RETURN script of the withdrawal tx
WithdrawIdentifier = []byte("side")
)

// ExtractRecipientAddr extracts the recipient address for minting voucher token by the type of the asset to be deposited
func ExtractRecipientAddr(tx *wire.MsgTx, prevTx *wire.MsgTx, vaults []*Vault, isRunes bool, chainCfg *chaincfg.Params) (btcutil.Address, error) {
if isRunes {
Expand Down Expand Up @@ -149,3 +157,19 @@ func CheckRunesDepositTransaction(tx *wire.MsgTx, vaults []*Vault) (*Edict, erro

return edicts[0], nil
}

// ParseSequence parses the sequence from the given tx
// make sure the tx is valid
func ParseSequence(tx *wire.MsgTx) (uint64, error) {
scriptBuilder := txscript.MakeScriptTokenizer(0, tx.TxOut[0].PkScript)

if scriptBuilder.Next() && scriptBuilder.Opcode() == txscript.OP_RETURN {
if scriptBuilder.Next() && bytes.Equal(scriptBuilder.Data(), WithdrawIdentifier) {
if scriptBuilder.Next() {
return new(big.Int).SetBytes(scriptBuilder.Data()).Uint64(), nil
}
}
}

return 0, ErrInvalidSequence
}
Loading