diff --git a/.github/workflows/build_relay.yml b/.github/workflows/build_relay.yml new file mode 100644 index 000000000..2b43646c9 --- /dev/null +++ b/.github/workflows/build_relay.yml @@ -0,0 +1,42 @@ +name: ci_relay + +# relay 集成测试:btcd(simnet) + relayd + 6 个 chain33 节点。 +# 依赖 plugin/dapp/relay/cmd/build.sh 在 make build_ci 时构建 relayd 并生成 +# build/ci/relay(relayd、relayd.toml、Dockerfile-relayd、docker-compose-relay.yml、 +# testcase.sh),否则 make docker-compose dapp=relay 会打印 +# "dapp=relay not exist or is system dir" 后直接返回 0(假绿)。 +on: + push: + branches: [ master ] + pull_request: + workflow_dispatch: + +jobs: + ci_relay: + name: ci_relay + runs-on: ubuntu-22.04 + timeout-minutes: 90 + steps: + - name: checkout + uses: actions/checkout@v4 + with: + # build 脚本用 git describe --tags 注入版本号 + fetch-depth: 0 + + - name: Set up Golang + uses: actions/setup-go@v5 + with: + go-version-file: 'go.mod' + id: go + + - name: set go env + run: export PATH=${PATH}:`go env GOPATH`/bin + + - name: deploy + run: | + make build_ci + make docker-compose dapp=relay + + - name: cleanup + if: always() + run: make docker-compose-down dapp=relay diff --git a/plugin/dapp/relay/cmd/build.sh b/plugin/dapp/relay/cmd/build.sh index 61ad9259f..996736c88 100755 --- a/plugin/dapp/relay/cmd/build.sh +++ b/plugin/dapp/relay/cmd/build.sh @@ -4,14 +4,15 @@ strpwd=$(pwd) strcmd=${strpwd##*dapp/} strapp=${strcmd%/cmd*} -#OUT_DIR="${1}/$strapp" -#SRC_RELAYD=github.com/33cn/plugin/plugin/dapp/relay/cmd/relayd -#FLAG=$2 -# -## shellcheck disable=SC2086,1072 -#go build ${FLAG} -v -o "${OUT_DIR}/relayd" "${SRC_RELAYD}" -#cp ./relayd/relayd.toml "${OUT_DIR}/relayd.toml" -#cp ./build/* "${OUT_DIR}" +OUT_DIR="${1}/$strapp" +SRC_RELAYD=github.com/33cn/plugin/plugin/dapp/relay/cmd/relayd +FLAG=$2 + +mkdir -p "${OUT_DIR}" +# shellcheck disable=SC2086,1072 +go build ${FLAG} -v -o "${OUT_DIR}/relayd" "${SRC_RELAYD}" +cp ./relayd/relayd.toml "${OUT_DIR}/relayd.toml" +cp ./build/* "${OUT_DIR}" OUT_TESTDIR="${1}/dapptest/$strapp" mkdir -p "${OUT_TESTDIR}" diff --git a/plugin/dapp/relay/cmd/build/testcase.sh b/plugin/dapp/relay/cmd/build/testcase.sh index e7a33b622..6fcd478ed 100755 --- a/plugin/dapp/relay/cmd/build/testcase.sh +++ b/plugin/dapp/relay/cmd/build/testcase.sh @@ -46,8 +46,10 @@ function relay_config() { function wait_btcd_up() { local count=20 while [ $count -gt 0 ]; do - status=$(docker-compose ps | grep btcd | awk '{print $5}') - if [ "${status}" == "Up" ]; then + # docker compose ps 列取错(取到 CREATED 列)会导致永远重启 btcd, + # 且 ubuntu-22.04 runner 只有 compose v2,这里直接用 docker inspect 判状态 + status=$(docker inspect -f '{{.State.Status}}' "${BTCD}" 2>/dev/null || true) + if [ "${status}" == "running" ]; then break fi docker compose logs btcd @@ -334,6 +336,10 @@ function relay_test() { count=$((count - 1)) if [ $count -le 0 ]; then echo "wrong relay status finish real buy order id" + echo "=========== # relayd logs ===========" + docker compose logs relayd 2>&1 | tail -80 + echo "=========== # chain33 relay logs ===========" + docker compose logs chain33 2>&1 | grep -iE "verify|relay" | tail -60 exit 1 fi done diff --git a/plugin/dapp/relay/executor/relay_test.go b/plugin/dapp/relay/executor/relay_test.go index 77cf02dcd..6f9f4cd82 100644 --- a/plugin/dapp/relay/executor/relay_test.go +++ b/plugin/dapp/relay/executor/relay_test.go @@ -5,14 +5,21 @@ package executor import ( + "bytes" "testing" apimock "github.com/33cn/chain33/client/mocks" + "github.com/33cn/chain33/common" "github.com/33cn/chain33/common/address" "github.com/33cn/chain33/common/db" "github.com/33cn/chain33/common/db/mocks" "github.com/33cn/chain33/types" ty "github.com/33cn/plugin/plugin/dapp/relay/types" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/suite" @@ -372,6 +379,47 @@ func TestRunSuiteRelay(t *testing.T) { suite.Run(t, log) } +// TestVerifyBtcTxContentSimnet 集成测试场景回归: btcd simnet 的订单收款地址 +// 必须按 simnet 网络参数解析输出地址,硬编码 MainNet 会因地址编码不一致误判 +func TestVerifyBtcTxContentSimnet(t *testing.T) { + hash160 := bytes.Repeat([]byte{0xab}, 20) + addr, err := btcutil.NewAddressPubKeyHash(hash160, &chaincfg.SimNetParams) + if err != nil { + t.Fatal(err) + } + pkScript, err := txscript.PayToAddrScript(addr) + if err != nil { + t.Fatal(err) + } + + msgTx := wire.NewMsgTx(2) + // Deserialize 会把 vin 数 0 当作 witness marker,因此至少带一个输入 + // (真实的 btc 交易也必然有输入) + msgTx.AddTxIn(wire.NewTxIn(wire.NewOutPoint(&chainhash.Hash{}, 0xffffffff), nil, nil)) + msgTx.AddTxOut(wire.NewTxOut(100000, pkScript)) + var buf bytes.Buffer + if err := msgTx.SerializeNoWitness(&buf); err != nil { + t.Fatal(err) + } + rawHash := txidFromMsgTx(msgTx) + // reverse 原地修改切片,先拷贝一份作为期望值 + want := append([]byte{}, rawHash...) + btcTx := &ty.BtcTransaction{ + Hash: common.ToHex(reverse(rawHash)), + RawTx: common.ToHex(buf.Bytes()), + } + spv := &ty.BtcSpv{Hash: btcTx.Hash} + order := &ty.RelayOrder{XAddr: addr.EncodeAddress(), XAmount: 100000} + + got, err := verifyBtcTxContent(btcTx, spv, order) + if err != nil { + t.Fatalf("verifyBtcTxContent simnet addr: %v", err) + } + if !bytes.Equal(got, want) { + t.Fatalf("verifyBtcTxContent rawHash mismatch, got %x want %x", got, want) + } +} + ////////////////////////////////////// type suiteBtcHeader struct { diff --git a/plugin/dapp/relay/executor/relaybtc.go b/plugin/dapp/relay/executor/relaybtc.go index 161be72b6..caf8aef39 100644 --- a/plugin/dapp/relay/executor/relaybtc.go +++ b/plugin/dapp/relay/executor/relaybtc.go @@ -16,6 +16,7 @@ import ( "github.com/33cn/chain33/common/merkle" "github.com/33cn/chain33/types" ty "github.com/33cn/plugin/plugin/dapp/relay/types" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" @@ -342,9 +343,13 @@ func verifyBtcTxContent(btcTx *ty.BtcTransaction, spv *ty.BtcSpv, order *ty.Rela return nil, ty.ErrRelayBtcTxHashErr } + // 订单收款地址可能属于任一 btc 网络(集成测试为 simnet), + // 用能成功解码订单地址的网络参数解析输出地址, + // 否则硬编码 MainNet 会把 simnet 输出地址重编码成 mainnet 地址,永远匹配不上 + params := addrNetParams(order.XAddr) var foundtx bool for _, out := range msgTx.TxOut { - _, addrs, _, err := txscript.ExtractPkScriptAddrs(out.PkScript, &chaincfg.MainNetParams) + _, addrs, _, err := txscript.ExtractPkScriptAddrs(out.PkScript, params) if err != nil { continue } @@ -390,6 +395,21 @@ func (b *btcStore) verifyCmdBtcTx(verify *ty.RelayVerifyCli) error { return nil } +// addrNetParams 返回能成功解码 addr 的 btc 网络参数,全部失败时回退 MainNet +func addrNetParams(addr string) *chaincfg.Params { + for _, p := range []*chaincfg.Params{ + &chaincfg.MainNetParams, + &chaincfg.TestNet3Params, + &chaincfg.RegressionNetParams, + &chaincfg.SimNetParams, + } { + if _, err := btcutil.DecodeAddress(addr, p); err == nil { + return p + } + } + return &chaincfg.MainNetParams +} + // decodeRawTx 将 hex 编码的原始 btc 交易反序列化为 MsgTx func decodeRawTx(rawTx string) (*wire.MsgTx, error) { data, err := common.FromHex(rawTx)