Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize #86

Merged
merged 6 commits into from
Jun 20, 2024
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
1 change: 0 additions & 1 deletion x/btcbridge/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ func GetQueryCmd(_ string) *cobra.Command {
cmd.AddCommand(CmdQueryBlock())
cmd.AddCommand(CmdQueryUTXOs())
cmd.AddCommand(CmdQuerySigningRequest())

// this line is used by starport scaffolding # 1

return cmd
Expand Down
22 changes: 6 additions & 16 deletions x/btcbridge/keeper/keeper_deposit.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func (k Keeper) ProcessBitcoinDepositTransaction(ctx sdk.Context, msg *types.Msg
return err
}

// extract senders from the previous transaction
// Decode the previous transaction
prevTxBytes, err := base64.StdEncoding.DecodeString(msg.PrevTxBytes)
if err != nil {
fmt.Println("Error decoding transaction from base64:", err)
Expand Down Expand Up @@ -90,19 +90,10 @@ func (k Keeper) ProcessBitcoinDepositTransaction(ctx sdk.Context, msg *types.Msg
return types.ErrInvalidBtcTransaction
}

// check if the output is a valid address
// if there are multiple inputs, then the first input is considered as the sender
// assumpe all inputs are from the same sender
out := prevTx.MsgTx().TxOut[tx.TxIn[0].PreviousOutPoint.Index]
// check if the output is a valid address
pk, err := txscript.ParsePkScript(out.PkScript)
if err != nil {
return err
}

chainCfg := sdk.GetConfig().GetBtcChainCfg()

sender, err := pk.Address(chainCfg)
// Extract the recipient address
recipient, err := types.ExtractRecipientAddr(&tx, &prevMsgTx, param.Vaults, chainCfg)
if err != nil {
return err
}
Expand Down Expand Up @@ -145,12 +136,12 @@ func (k Keeper) ProcessBitcoinDepositTransaction(ctx sdk.Context, msg *types.Msg
// skip if the asset type of the sender address is unspecified
switch vault.AssetType {
case types.AssetType_ASSET_TYPE_BTC:
err := k.mintBTC(ctx, uTx, header.Height, sender.EncodeAddress(), vault, out, i, param.BtcVoucherDenom)
err := k.mintBTC(ctx, uTx, header.Height, recipient.EncodeAddress(), vault, out, i, param.BtcVoucherDenom)
if err != nil {
return err
}
case types.AssetType_ASSET_TYPE_RUNE:
k.mintRUNE(ctx, uTx, header.Height, sender.EncodeAddress(), vault, out, i, "rune")
k.mintRUNE(ctx, uTx, header.Height, recipient.EncodeAddress(), vault, out, i, "rune")
}
}

Expand Down Expand Up @@ -196,8 +187,7 @@ func (k Keeper) mintBTC(ctx sdk.Context, uTx *btcutil.Tx, height uint64, sender
IsLocked: false,
}

k.SetUTXO(ctx, &utxo)
k.SetOwnerUTXO(ctx, &utxo)
k.saveUTXO(ctx, &utxo)

return nil
}
Expand Down
10 changes: 8 additions & 2 deletions x/btcbridge/keeper/keeper_withdraw.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import (
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/wire"

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

"github.com/sideprotocol/side/x/btcbridge/types"
)

Expand Down Expand Up @@ -54,9 +56,9 @@ func (k Keeper) NewSigningRequest(ctx sdk.Context, sender string, coin sdk.Coin,
return nil, types.ErrInsufficientUTXOs
}

psbt, selectedUTXOs, err := types.BuildPsbt(utxos, sender, coin.Amount.Int64(), feeRate, vault)
psbt, selectedUTXOs, changeUTXO, err := types.BuildPsbt(utxos, sender, coin.Amount.Int64(), feeRate, vault)
if err != nil {
return nil, types.ErrFailToBuildTransaction
return nil, err
}

psbtB64, err := psbt.B64Encode()
Expand All @@ -67,6 +69,10 @@ func (k Keeper) NewSigningRequest(ctx sdk.Context, sender string, coin sdk.Coin,
// lock the selected utxos
k.LockUTXOs(ctx, selectedUTXOs)

// save the change utxo and mark minted
k.saveUTXO(ctx, changeUTXO)
k.addToMintHistory(ctx, psbt.UnsignedTx.TxHash().String())

signingRequest := &types.BitcoinSigningRequest{
Address: sender,
Txid: psbt.UnsignedTx.TxHash().String(),
Expand Down
6 changes: 6 additions & 0 deletions x/btcbridge/keeper/utxo.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,12 @@ func (bk *BaseUTXOKeeper) SpendUTXOs(ctx sdk.Context, utxos []*types.UTXO) error
return nil
}

// saveUTXO saves the given utxo
func (bk *BaseUTXOKeeper) saveUTXO(ctx sdk.Context, utxo *types.UTXO) {
bk.SetUTXO(ctx, utxo)
bk.SetOwnerUTXO(ctx, utxo)
}

// removeUTXO deletes the given utxo which is assumed to exist.
func (bk *BaseUTXOKeeper) removeUTXO(ctx sdk.Context, hash string, vout uint64) {
store := ctx.KVStore(bk.storeKey)
Expand Down
60 changes: 46 additions & 14 deletions x/btcbridge/types/bitcoin_transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/btcsuite/btcd/mempool"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"

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

Expand All @@ -19,53 +20,53 @@ const (
)

// BuildPsbt builds a bitcoin psbt from the given params.
// Assume that the utxo script type is witness.
func BuildPsbt(utxos []*UTXO, recipient string, amount int64, feeRate int64, change string) (*psbt.Packet, []*UTXO, error) {
// Assume that the utxo script type is native segwit.
func BuildPsbt(utxos []*UTXO, recipient string, amount int64, feeRate int64, change string) (*psbt.Packet, []*UTXO, *UTXO, error) {
chaincfg := sdk.GetConfig().GetBtcChainCfg()
recipientAddr, err := btcutil.DecodeAddress(recipient, chaincfg)
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}

recipientPkScript, err := txscript.PayToAddrScript(recipientAddr)
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}

changeAddr, err := btcutil.DecodeAddress(change, chaincfg)
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}

txOuts := make([]*wire.TxOut, 0)
txOuts = append(txOuts, wire.NewTxOut(amount, recipientPkScript))

unsignedTx, selectedUTXOs, err := BuildUnsignedTransaction(utxos, txOuts, feeRate, changeAddr)
unsignedTx, selectedUTXOs, changeUTXO, err := BuildUnsignedTransaction(utxos, txOuts, feeRate, changeAddr)
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}

p, err := psbt.NewFromUnsignedTx(unsignedTx)
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}

for i, utxo := range selectedUTXOs {
p.Inputs[i].SighashType = txscript.SigHashAll
p.Inputs[i].WitnessUtxo = wire.NewTxOut(int64(utxo.Amount), utxo.PubKeyScript)
}

return p, selectedUTXOs, nil
return p, selectedUTXOs, changeUTXO, nil
}

// BuildUnsignedTransaction builds an unsigned tx from the given params.
func BuildUnsignedTransaction(utxos []*UTXO, txOuts []*wire.TxOut, feeRate int64, change btcutil.Address) (*wire.MsgTx, []*UTXO, error) {
func BuildUnsignedTransaction(utxos []*UTXO, txOuts []*wire.TxOut, feeRate int64, change btcutil.Address) (*wire.MsgTx, []*UTXO, *UTXO, error) {
tx := wire.NewMsgTx(TxVersion)

outAmount := int64(0)
for _, txOut := range txOuts {
if mempool.IsDust(txOut, MinRelayFee) {
return nil, nil, ErrDustOutput
return nil, nil, nil, ErrDustOutput
}

tx.AddTxOut(txOut)
Expand All @@ -74,17 +75,29 @@ func BuildUnsignedTransaction(utxos []*UTXO, txOuts []*wire.TxOut, feeRate int64

changePkScript, err := txscript.PayToAddrScript(change)
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}

changeOut := wire.NewTxOut(0, changePkScript)

selectedUTXOs, err := AddUTXOsToTx(tx, utxos, outAmount, changeOut, feeRate)
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}

var changeUTXO *UTXO
if len(tx.TxOut) > len(txOuts) {
changeOut := tx.TxOut[len(tx.TxOut)-1]
changeUTXO = &UTXO{
Txid: tx.TxHash().String(),
Vout: uint64(len(tx.TxOut) - 1),
Address: change.EncodeAddress(),
Amount: uint64(changeOut.Value),
PubKeyScript: changeOut.PkScript,
}
}

return tx, selectedUTXOs, nil
return tx, selectedUTXOs, changeUTXO, nil
}

// AddUTXOsToTx adds the given utxos to the tx.
Expand Down Expand Up @@ -169,3 +182,22 @@ func GetTxVirtualSize(tx *wire.MsgTx, utxos []*UTXO) int64 {

return mempool.GetTxVirtualSize(btcutil.NewTx(newTx))
}

// CheckOutput checks the given output
func CheckOutput(address string, amount int64) error {
addr, err := btcutil.DecodeAddress(address, sdk.GetConfig().GetBtcChainCfg())
if err != nil {
return err
}

pkScript, err := txscript.PayToAddrScript(addr)
if err != nil {
return err
}

if mempool.IsDust(&wire.TxOut{Value: amount, PkScript: pkScript}, MinRelayFee) {
return ErrDustOutput
}

return nil
}
58 changes: 58 additions & 0 deletions x/btcbridge/types/deposit_policy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package types

import (
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
)

const (
// maximum allowed number of the non-vault outputs for the deposit transaction
MaxNonVaultOutNum = 1
)

// ExtractRecipientAddr extracts the recipient address for minting voucher token.
// First, extract the recipient from the tx out which is a non-vault address;
// Then fallback to the first input
func ExtractRecipientAddr(tx *wire.MsgTx, prevTx *wire.MsgTx, vaults []*Vault, chainCfg *chaincfg.Params) (btcutil.Address, error) {
var recipient btcutil.Address

nonVaultOutCount := 0

// extract from the tx out which is a non-vault address
for _, out := range tx.TxOut {
pkScript, err := txscript.ParsePkScript(out.PkScript)
if err != nil {
return nil, err
}

addr, err := pkScript.Address(chainCfg)
if err != nil {
return nil, err
}

vault := SelectVaultByBitcoinAddress(vaults, addr.EncodeAddress())
if vault == nil {
recipient = addr
nonVaultOutCount++
}
}

// exceed allowed non vault out number
if nonVaultOutCount > MaxNonVaultOutNum {
return nil, ErrInvalidDepositTransaction
}

if recipient != nil {
return recipient, nil
}

// fallback to extract from the first input
pkScript, err := txscript.ParsePkScript(prevTx.TxOut[tx.TxIn[0].PreviousOutPoint.Index].PkScript)
if err != nil {
return nil, err
}

return pkScript.Address(chainCfg)
}
25 changes: 13 additions & 12 deletions x/btcbridge/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@ 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")
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")

ErrInvalidSignatures = errorsmod.Register(ModuleName, 4200, "invalid signatures")
ErrInsufficientBalance = errorsmod.Register(ModuleName, 4201, "insufficient balance")
Expand All @@ -31,9 +32,9 @@ var (
ErrUTXOLocked = errorsmod.Register(ModuleName, 5101, "utxo locked")
ErrUTXOUnlocked = errorsmod.Register(ModuleName, 5102, "utxo unlocked")

ErrInvalidFeeRate = errorsmod.Register(ModuleName, 6100, "invalid fee rate")
ErrDustOutput = errorsmod.Register(ModuleName, 6101, "dust output value")
ErrInsufficientUTXOs = errorsmod.Register(ModuleName, 6102, "insufficient utxos")
ErrFailToBuildTransaction = errorsmod.Register(ModuleName, 6103, "failed to build transaction")
ErrFailToSerializePsbt = errorsmod.Register(ModuleName, 6104, "failed to serialize psbt")
ErrInvalidAmount = errorsmod.Register(ModuleName, 6100, "invalid amount")
ErrInvalidFeeRate = errorsmod.Register(ModuleName, 6101, "invalid fee rate")
ErrDustOutput = errorsmod.Register(ModuleName, 6102, "dust output value")
ErrInsufficientUTXOs = errorsmod.Register(ModuleName, 6103, "insufficient utxos")
ErrFailToSerializePsbt = errorsmod.Register(ModuleName, 6104, "failed to serialize psbt")
)
10 changes: 8 additions & 2 deletions x/btcbridge/types/message_withdraw_bitcoin.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,14 @@ func (msg *MsgWithdrawBitcoinRequest) ValidateBasic() error {
return sdkerrors.Wrapf(err, "invalid Sender address (%s)", err)
}

if len(msg.Amount) == 0 {
return sdkerrors.Wrap(sdk.ErrInvalidLengthCoin, "amount cannot be empty")
coin, err := sdk.ParseCoinNormalized(msg.Amount)
if err != nil {
return sdkerrors.Wrapf(ErrInvalidAmount, "invalid amount %s", msg.Amount)
}

err = CheckOutput(msg.Sender, coin.Amount.Int64())
if err != nil {
return err
}

if msg.FeeRate <= 0 {
Expand Down
Loading