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

feat: support Bitcoin P2TR, P2WSH, P2SH, P2PKH address types #1982

Merged
merged 29 commits into from
Apr 4, 2024
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
18c7196
backport/add a AddressTaproot type that satisfies btcutil.Address int…
brewmaster012 Mar 6, 2024
f78eb47
support taproot in DecodeBtcAddress
brewmaster012 Mar 6, 2024
0c167cd
minor tweak
brewmaster012 Mar 6, 2024
8252ca3
Merge branch 'develop' into feat-support-taproot
ws4charlie Mar 13, 2024
b0b2d74
Merge branch 'develop' into feat-support-taproot
ws4charlie Mar 21, 2024
a60375c
Merge branch 'develop' into feat-support-taproot
ws4charlie Mar 22, 2024
c2dfb7a
initial commit to support different types of BTC addresses
ws4charlie Mar 22, 2024
738d265
added decoding for more intx types
ws4charlie Mar 22, 2024
291322b
added btc intx address decoding and tests
ws4charlie Mar 24, 2024
18fc62f
fix compile error
ws4charlie Mar 25, 2024
50db809
Merge branch 'v16.0.0' of https://github.com/zeta-chain/node into fea…
ws4charlie Mar 25, 2024
76b6817
adjusted btc outtx size and withdrawer fee
ws4charlie Mar 26, 2024
c338d41
added changelog entry
ws4charlie Mar 26, 2024
31a0dfe
fix unit tests
ws4charlie Mar 26, 2024
82527b5
added e2e tests for different types of bitcoin addresses
ws4charlie Mar 26, 2024
d2935b9
try retriggering CI
lumtis Mar 27, 2024
bc02fb7
e2e tests all PR
lumtis Mar 27, 2024
2bcdfbc
resolved some PR review feedback
ws4charlie Mar 28, 2024
edc9be6
removed panic from test method LoadObjectFromJSONFile
ws4charlie Mar 28, 2024
f02cc52
improved function and added comments
ws4charlie Mar 28, 2024
e7c4c60
feat: add metric for concurrent key signs (#1960)
kevinssgh Mar 28, 2024
a341da9
Merge branch 'v16.0.0' into feat-support-taproot
ws4charlie Mar 29, 2024
e0f0196
merge develop branch into feat-support-taproot and resolve conflicts
ws4charlie Apr 3, 2024
11bd7ea
Merge branch 'develop' into feat-support-taproot
ws4charlie Apr 3, 2024
d911f67
added live test for intx sender address parsing
ws4charlie Apr 3, 2024
cea2885
fix CI unit test failure
ws4charlie Apr 3, 2024
268803c
updated changelog
ws4charlie Apr 3, 2024
1815f13
format testdata and clean up empty lines
ws4charlie Apr 3, 2024
b15de98
merge develop branch and resolve conflict
ws4charlie Apr 4, 2024
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
3 changes: 3 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
* [1755](https://github.com/zeta-chain/node/issues/1755) - use evm JSON RPC for inbound tx (including blob tx) observation.
* [1815](https://github.com/zeta-chain/node/pull/1815) - add authority module for authorized actions
* [1884](https://github.com/zeta-chain/node/pull/1884) - added zetatool cmd, added subcommand to filter deposits
* [1942](https://github.com/zeta-chain/node/pull/1982) - support Bitcoin P2TR, P2WSH, P2SH, P2PKH addresses
* [1935](https://github.com/zeta-chain/node/pull/1935) - add an operational authority group
* [1954](https://github.com/zeta-chain/node/pull/1954) - add metric for concurrent keysigns

Expand Down Expand Up @@ -175,6 +176,7 @@
* [1675](https://github.com/zeta-chain/node/issues/1675) - use chain param ConfirmationCount for bitcoin confirmation

## Chores

* [1694](https://github.com/zeta-chain/node/pull/1694) - remove standalone network, use require testing package for the entire node folder

## Version: v12.1.0
Expand All @@ -187,6 +189,7 @@
* [1658](https://github.com/zeta-chain/node/pull/1658) - modify emission distribution to use fixed block rewards

### Fixes

* [1535](https://github.com/zeta-chain/node/issues/1535) - Avoid voting on wrong ballots due to false blockNumber in EVM tx receipt
* [1588](https://github.com/zeta-chain/node/pull/1588) - fix chain params comparison logic
* [1650](https://github.com/zeta-chain/node/pull/1605) - exempt (discounted) *system txs* from min gas price check and gas fee deduction
Expand Down
6 changes: 5 additions & 1 deletion cmd/zetae2e/local/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,11 @@ func localE2ETest(cmd *cobra.Command, _ []string) {
e2etests.TestZetaDepositRestrictedName,
}
bitcoinTests := []string{
e2etests.TestBitcoinWithdrawName,
e2etests.TestBitcoinWithdrawSegWitName,
e2etests.TestBitcoinWithdrawTaprootName,
e2etests.TestBitcoinWithdrawLegacyName,
e2etests.TestBitcoinWithdrawP2SHName,
e2etests.TestBitcoinWithdrawP2WSHName,
e2etests.TestBitcoinWithdrawInvalidAddressName,
e2etests.TestZetaWithdrawBTCRevertName,
e2etests.TestCrosschainSwapName,
Expand Down
53 changes: 47 additions & 6 deletions e2e/e2etests/e2etests.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ const (
TestZetaWithdrawBTCRevertName = "zeta_withdraw_btc_revert" // #nosec G101 - not a hardcoded password
TestMessagePassingName = "message_passing"
TestZRC20SwapName = "zrc20_swap"
TestBitcoinWithdrawName = "bitcoin_withdraw"
TestBitcoinWithdrawSegWitName = "bitcoin_withdraw_segwit"
TestBitcoinWithdrawTaprootName = "bitcoin_withdraw_taproot"
TestBitcoinWithdrawLegacyName = "bitcoin_withdraw_legacy"
TestBitcoinWithdrawP2WSHName = "bitcoin_withdraw_p2wsh"
TestBitcoinWithdrawP2SHName = "bitcoin_withdraw_p2sh"
TestBitcoinWithdrawInvalidAddressName = "bitcoin_withdraw_invalid"
TestBitcoinWithdrawRestrictedName = "bitcoin_withdraw_restricted"
TestCrosschainSwapName = "crosschain_swap"
Expand Down Expand Up @@ -130,12 +134,49 @@ var AllE2ETests = []runner.E2ETest{
TestZRC20Swap,
),
runner.NewE2ETest(
TestBitcoinWithdrawName,
"withdraw BTC from ZEVM",
TestBitcoinWithdrawSegWitName,
"withdraw BTC from ZEVM to a SegWit address",
[]runner.ArgDefinition{
runner.ArgDefinition{Description: "amount in btc", DefaultValue: "0.01"},
runner.ArgDefinition{Description: "receiver address", DefaultValue: ""},
runner.ArgDefinition{Description: "amount in btc", DefaultValue: "0.001"},
},
TestBitcoinWithdrawSegWit,
),
runner.NewE2ETest(
TestBitcoinWithdrawTaprootName,
"withdraw BTC from ZEVM to a Taproot address",
[]runner.ArgDefinition{
runner.ArgDefinition{Description: "receiver address", DefaultValue: ""},
runner.ArgDefinition{Description: "amount in btc", DefaultValue: "0.001"},
},
TestBitcoinWithdrawTaproot,
),
runner.NewE2ETest(
TestBitcoinWithdrawLegacyName,
"withdraw BTC from ZEVM to a legacy address",
[]runner.ArgDefinition{
runner.ArgDefinition{Description: "receiver address", DefaultValue: ""},
runner.ArgDefinition{Description: "amount in btc", DefaultValue: "0.001"},
},
TestBitcoinWithdraw,
TestBitcoinWithdrawLegacy,
),
runner.NewE2ETest(
TestBitcoinWithdrawP2WSHName,
"withdraw BTC from ZEVM to a P2WSH address",
[]runner.ArgDefinition{
runner.ArgDefinition{Description: "receiver address", DefaultValue: ""},
runner.ArgDefinition{Description: "amount in btc", DefaultValue: "0.001"},
},
TestBitcoinWithdrawP2WSH,
),
runner.NewE2ETest(
TestBitcoinWithdrawP2SHName,
"withdraw BTC from ZEVM to a P2SH address",
[]runner.ArgDefinition{
runner.ArgDefinition{Description: "receiver address", DefaultValue: ""},
runner.ArgDefinition{Description: "amount in btc", DefaultValue: "0.001"},
},
TestBitcoinWithdrawP2SH,
),
runner.NewE2ETest(
TestBitcoinWithdrawInvalidAddressName,
Expand Down Expand Up @@ -311,7 +352,7 @@ var AllE2ETests = []runner.E2ETest{
TestBitcoinWithdrawRestrictedName,
"withdraw Bitcoin from ZEVM to restricted address",
[]runner.ArgDefinition{
runner.ArgDefinition{Description: "amount in btc", DefaultValue: "0.01"},
runner.ArgDefinition{Description: "amount in btc", DefaultValue: "0.001"},
},
TestBitcoinWithdrawRestricted,
),
Expand Down
136 changes: 119 additions & 17 deletions e2e/e2etests/test_bitcoin_withdraw.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,98 @@ import (
"github.com/zeta-chain/zetacore/e2e/runner"
"github.com/zeta-chain/zetacore/e2e/utils"
"github.com/zeta-chain/zetacore/pkg/chains"
crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types"
"github.com/zeta-chain/zetacore/zetaclient/testutils"
)

func TestBitcoinWithdraw(r *runner.E2ERunner, args []string) {
if len(args) != 1 {
panic("TestBitcoinWithdraw requires exactly one argument for the amount.")
func TestBitcoinWithdrawSegWit(r *runner.E2ERunner, args []string) {
// check length of arguments
if len(args) != 2 {
panic("TestBitcoinWithdrawSegWit requires two arguments: [receiver, amount]")
}
r.SetBtcAddress(r.Name, false)

withdrawalAmount, err := strconv.ParseFloat(args[0], 64)
if err != nil {
panic("Invalid withdrawal amount specified for TestBitcoinWithdraw.")
// parse arguments
defaultReceiver := r.BTCDeployerAddress.EncodeAddress()
receiver, amount := parseBitcoinWithdrawArgs(args, defaultReceiver)
_, ok := receiver.(*btcutil.AddressWitnessPubKeyHash)
if !ok {
panic("Invalid receiver address specified for TestBitcoinWithdrawSegWit.")
}

withdrawalAmountSat, err := btcutil.NewAmount(withdrawalAmount)
if err != nil {
panic(err)
withdrawBTCZRC20(r, receiver, amount)
}

func TestBitcoinWithdrawTaproot(r *runner.E2ERunner, args []string) {
// check length of arguments
if len(args) != 2 {
panic("TestBitcoinWithdrawTaproot requires two arguments: [receiver, amount]")
}
amount := big.NewInt(int64(withdrawalAmountSat))
r.SetBtcAddress(r.Name, false)

// parse arguments and withdraw BTC
defaultReceiver := "bcrt1pqqqsyqcyq5rqwzqfpg9scrgwpugpzysnzs23v9ccrydpk8qarc0sj9hjuh"
receiver, amount := parseBitcoinWithdrawArgs(args, defaultReceiver)
_, ok := receiver.(*chains.AddressTaproot)
if !ok {
panic("Invalid receiver address specified for TestBitcoinWithdrawTaproot.")
}

withdrawBTCZRC20(r, receiver, amount)
}

func TestBitcoinWithdrawLegacy(r *runner.E2ERunner, args []string) {
// check length of arguments
if len(args) != 2 {
panic("TestBitcoinWithdrawLegacy requires two arguments: [receiver, amount]")
}
r.SetBtcAddress(r.Name, false)

WithdrawBitcoin(r, amount)
// parse arguments and withdraw BTC
defaultReceiver := "mxpYha3UJKUgSwsAz2qYRqaDSwAkKZ3YEY"
receiver, amount := parseBitcoinWithdrawArgs(args, defaultReceiver)
_, ok := receiver.(*btcutil.AddressPubKeyHash)
if !ok {
panic("Invalid receiver address specified for TestBitcoinWithdrawLegacy.")
}

withdrawBTCZRC20(r, receiver, amount)
}

func TestBitcoinWithdrawP2WSH(r *runner.E2ERunner, args []string) {
// check length of arguments
if len(args) != 2 {
panic("TestBitcoinWithdrawP2WSH requires two arguments: [receiver, amount]")
}
r.SetBtcAddress(r.Name, false)

// parse arguments and withdraw BTC
defaultReceiver := "bcrt1qm9mzhyky4w853ft2ms6dtqdyyu3z2tmrq8jg8xglhyuv0dsxzmgs2f0sqy"
receiver, amount := parseBitcoinWithdrawArgs(args, defaultReceiver)
_, ok := receiver.(*btcutil.AddressWitnessScriptHash)
if !ok {
panic("Invalid receiver address specified for TestBitcoinWithdrawP2WSH.")
}

withdrawBTCZRC20(r, receiver, amount)
}

func TestBitcoinWithdrawP2SH(r *runner.E2ERunner, args []string) {
// check length of arguments
if len(args) != 2 {
panic("TestBitcoinWithdrawP2SH requires two arguments: [receiver, amount]")
}
r.SetBtcAddress(r.Name, false)

// parse arguments and withdraw BTC
defaultReceiver := "2N6AoUj3KPS7wNGZXuCckh8YEWcSYNsGbqd"
receiver, amount := parseBitcoinWithdrawArgs(args, defaultReceiver)
_, ok := receiver.(*btcutil.AddressScriptHash)
if !ok {
panic("Invalid receiver address specified for TestBitcoinWithdrawP2SH.")
}

withdrawBTCZRC20(r, receiver, amount)
}

func TestBitcoinWithdrawRestricted(r *runner.E2ERunner, args []string) {
Expand All @@ -53,7 +123,37 @@ func TestBitcoinWithdrawRestricted(r *runner.E2ERunner, args []string) {

r.SetBtcAddress(r.Name, false)

WithdrawBitcoinRestricted(r, amount)
withdrawBitcoinRestricted(r, amount)
}

func parseBitcoinWithdrawArgs(args []string, defaultReceiver string) (btcutil.Address, *big.Int) {
// parse receiver address
var err error
var receiver btcutil.Address
if args[0] == "" {
// use the default receiver
receiver, err = chains.DecodeBtcAddress(defaultReceiver, chains.BtcRegtestChain().ChainId)
if err != nil {
panic("Invalid default receiver address specified for TestBitcoinWithdraw.")
}
} else {
receiver, err = chains.DecodeBtcAddress(args[0], chains.BtcRegtestChain().ChainId)
if err != nil {
panic("Invalid receiver address specified for TestBitcoinWithdraw.")
}
}
// parse the withdrawal amount
withdrawalAmount, err := strconv.ParseFloat(args[1], 64)
if err != nil {
panic("Invalid withdrawal amount specified for TestBitcoinWithdraw.")
}
withdrawalAmountSat, err := btcutil.NewAmount(withdrawalAmount)
if err != nil {
panic(err)
}
amount := big.NewInt(int64(withdrawalAmountSat))

return receiver, amount
}

func withdrawBTCZRC20(r *runner.E2ERunner, to btcutil.Address, amount *big.Int) *btcjson.TxRawResult {
Expand Down Expand Up @@ -85,7 +185,13 @@ func withdrawBTCZRC20(r *runner.E2ERunner, to btcutil.Address, amount *big.Int)
panic(err)
}

// get cctx and check status
cctx := utils.WaitCctxMinedByInTxHash(r.Ctx, receipt.TxHash.Hex(), r.CctxClient, r.Logger, r.CctxTimeout)
if cctx.CctxStatus.Status != crosschaintypes.CctxStatus_OutboundMined {
panic(fmt.Errorf("cctx status is not OutboundMined"))
}

// get bitcoin tx according to the outTxHash in cctx
outTxHash := cctx.GetCurrentOutTxParam().OutboundTxHash
hash, err := chainhash.NewHashFromStr(outTxHash)
if err != nil {
Expand Down Expand Up @@ -116,11 +222,7 @@ func withdrawBTCZRC20(r *runner.E2ERunner, to btcutil.Address, amount *big.Int)
return rawTx
}

func WithdrawBitcoin(r *runner.E2ERunner, amount *big.Int) {
withdrawBTCZRC20(r, r.BTCDeployerAddress, amount)
}

func WithdrawBitcoinRestricted(r *runner.E2ERunner, amount *big.Int) {
func withdrawBitcoinRestricted(r *runner.E2ERunner, amount *big.Int) {
// use restricted BTC P2WPKH address
addressRestricted, err := chains.DecodeBtcAddress(testutils.RestrictedBtcAddressTest, chains.BtcRegtestChain().ChainId)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions e2e/e2etests/test_bitcoin_withdraw_invalid.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ func WithdrawToInvalidAddress(r *runner.E2ERunner, amount *big.Int) {
stop := r.MineBlocks()

// withdraw amount provided as test arg BTC from ZRC20 to BTC legacy address
// the address "1EYVvXLusCxtVuEwoYvWRyN5EZTXwPVvo3" is for mainnet, not regtest
tx, err = r.BTCZRC20.Withdraw(r.ZEVMAuth, []byte("1EYVvXLusCxtVuEwoYvWRyN5EZTXwPVvo3"), amount)
if err != nil {
panic(err)
Expand Down
8 changes: 6 additions & 2 deletions e2e/runner/bitcoin.go
Original file line number Diff line number Diff line change
Expand Up @@ -267,14 +267,18 @@ func (runner *E2ERunner) SendToTSSFromDeployerWithMemo(
}

depositorFee := zetabitcoin.DefaultDepositorFee
events := zetabitcoin.FilterAndParseIncomingTx(
events, err := zetabitcoin.FilterAndParseIncomingTx(
btcRPC,
[]btcjson.TxRawResult{*rawtx},
0,
runner.BTCTSSAddress.EncodeAddress(),
&log.Logger,
log.Logger,
runner.BitcoinParams,
depositorFee,
)
if err != nil {
panic(err)
}
runner.Logger.Info("bitcoin intx events:")
for _, event := range events {
runner.Logger.Info(" TxHash: %s", event.TxHash)
Expand Down
35 changes: 33 additions & 2 deletions pkg/chains/address.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
}
}

// DecodeBtcAddress decodes a BTC address from a given string and chainID
func DecodeBtcAddress(inputAddress string, chainID int64) (address btcutil.Address, err error) {
defer func() {
if r := recover(); r != nil {
Expand All @@ -63,13 +64,43 @@
if err != nil {
return nil, err
}
if chainParams == nil {
return nil, fmt.Errorf("chain params not found")

Check warning on line 68 in pkg/chains/address.go

View check run for this annotation

Codecov / codecov/patch

pkg/chains/address.go#L68

Added line #L68 was not covered by tests
}
// test taproot address type
address, err = DecodeTaprootAddress(inputAddress)
if err == nil {
if address.IsForNet(chainParams) {
return address, nil
}
return nil, fmt.Errorf("address %s is not for network %s", inputAddress, chainParams.Name)
}
// test taproot address failed; continue testing other types: P2WSH, P2WPKH, P2SH, P2PKH
address, err = btcutil.DecodeAddress(inputAddress, chainParams)
if err != nil {
return nil, fmt.Errorf("decode address failed: %s , for input address %s", err.Error(), inputAddress)
return nil, fmt.Errorf("decode address failed: %s, for input address %s", err.Error(), inputAddress)
}
ok := address.IsForNet(chainParams)
if !ok {
return nil, fmt.Errorf("address is not for network %s", chainParams.Name)
return nil, fmt.Errorf("address %s is not for network %s", inputAddress, chainParams.Name)
}
return
}

// IsBtcAddressSupported returns true if the given BTC address is supported
func IsBtcAddressSupported(addr btcutil.Address) bool {
switch addr.(type) {
// P2TR address
case *AddressTaproot,
// P2WSH address
*btcutil.AddressWitnessScriptHash,
// P2WPKH address
*btcutil.AddressWitnessPubKeyHash,
// P2SH address
*btcutil.AddressScriptHash,
// P2PKH address
*btcutil.AddressPubKeyHash:
return true
}
return false

Check warning on line 105 in pkg/chains/address.go

View check run for this annotation

Codecov / codecov/patch

pkg/chains/address.go#L105

Added line #L105 was not covered by tests
}
Loading
Loading