diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9be3e568d2..48d1180a62 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,22 +32,6 @@ jobs: # make linter make - build-with-latest-stable-go: - strategy: - matrix: - os: [ ubuntu-22.04, macos-14 ] - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - uses: actions/setup-go@v3 - with: - go-version: '1.20' - - run: | - go env - make build - unit-test: name: unit-test runs-on: ubuntu-22.04 diff --git a/.github/workflows/build_rgbx.yml b/.github/workflows/build_rgbx.yml new file mode 100644 index 0000000000..c90b6492cc --- /dev/null +++ b/.github/workflows/build_rgbx.yml @@ -0,0 +1,23 @@ +name: ci_rgbx +on: [push, pull_request] + +jobs: + ci_rgbx: + name: ci_rgbx + runs-on: ubuntu-22.04 + timeout-minutes: 90 + steps: + - name: checkout + uses: actions/checkout@v3 + - name: Set up Golang + uses: actions/setup-go@v3 + with: + go-version-file: 'go.mod' + id: go + - name: deploy + run: | + make docker-compose dapp=rgbx + - name: cleanup + if: always() + run: | + make docker-compose-down dapp=rgbx diff --git a/.gitignore b/.gitignore index 107a3d1451..2ae9acac53 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,9 @@ datadir* *.log.last .idea .vscode +.kiro +.opencode +build/temp build/chain33* chain33_raft-1 build/datadir @@ -51,3 +54,10 @@ build/CHANGELOG.md plugin/dapp/dex/boss/build/ plugin/dapp/bridgevmxgo/cmd/build/bridgevmxgo/ plugin/dapp/bridgevmxgo/cmd/build/dapptest/ +go.work* +*.test +*.cursor +*.kiro +build/temp/* +.gitignore_local +.claude* diff --git a/Jenkinsfile b/Jenkinsfile index 8a0b401284..9e4285bcf4 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -16,15 +16,26 @@ pipeline { gitlabBuilds(builds: ['check']) checkoutToSubdirectory "src/github.com/33cn/plugin" } - tools {go 'go1.19'} + tools {go 'go'} stages { stage('deploy') { steps { dir("${PROJ_DIR}"){ gitlabCommitStatus(name: 'deploy'){ - sh 'go version' - sh 'make build_ci' - sh "cd build && mkdir ${env.BUILD_NUMBER} && cp ci/* ${env.BUILD_NUMBER} -r && ./docker-compose-pre.sh modify && cp chain33* Dockerfile* docker* *.sh ${env.BUILD_NUMBER}/ && cd ${env.BUILD_NUMBER}/ && rm -rf cross2eth mix && ./docker-compose-pre.sh run ${env.BUILD_NUMBER} all " + sh ''' + set -e + go version + make build_ci + cd build + rm -rf "${BUILD_NUMBER}" + mkdir -p "${BUILD_NUMBER}" + cp -r ci/* "${BUILD_NUMBER}/" + ./docker-compose-pre.sh modify + cp chain33* Dockerfile* docker-compose* *.sh "${BUILD_NUMBER}/" + cd "${BUILD_NUMBER}" + rm -rf cross2eth mix rgbx rollup|| true + ./docker-compose-pre.sh run "${BUILD_NUMBER}" all + ''' } } } @@ -32,7 +43,18 @@ pipeline { post { always { dir("${PROJ_DIR}"){ - sh "cd build/${env.BUILD_NUMBER} && ./docker-compose-pre.sh down ${env.BUILD_NUMBER} all && cd .. && rm -rf ${env.BUILD_NUMBER} && cd .. && make clean " + sh ''' + set +e + cd build + if [ -d "${BUILD_NUMBER}" ]; then + cd "${BUILD_NUMBER}" + ./docker-compose-pre.sh down "${BUILD_NUMBER}" all || true + cd .. + rm -rf "${BUILD_NUMBER}" + fi + cd .. + make clean || true + ''' } } } diff --git a/Makefile b/Makefile index 910b980afd..5a0243916c 100644 --- a/Makefile +++ b/Makefile @@ -31,9 +31,14 @@ build: depends @cp chain33.para.toml build/ci/paracross/ -build_ci: depends ## Build the binary file for CI - @go build -v -o $(CLI) $(SRC_CLI) - @go build $(BUILD_FLAGS) -v -o $(APP) +build_ci: depends ## Build the binary file for CI (Linux/amd64|arm64 when not on Linux, for Docker) + @if [ "$$(uname)" = "Linux" ]; then \ + go build $(BUILD_FLAGS) -v -o $(CLI) $(SRC_CLI) && \ + go build $(BUILD_FLAGS) -v -o $(APP); \ + else \ + CGO_ENABLED=0 GOOS=linux GOARCH=$$(go env GOARCH) go build $(BUILD_FLAGS) -v -o $(CLI) $(SRC_CLI) && \ + CGO_ENABLED=0 GOOS=linux GOARCH=$$(go env GOARCH) go build $(BUILD_FLAGS) -v -o $(APP); \ + fi @cp chain33.toml build/ @cp chain33.para.toml build/ci/paracross/ @@ -162,6 +167,10 @@ docker-compose: ## build docker-compose for chain33 run @cd build && if ! [ -d ci ]; then \ make -C ../ ; \ fi; \ + if [ "$$(uname)" != "Linux" ]; then \ + CGO_ENABLED=0 GOOS=linux GOARCH=$$(go env GOARCH) go build $(BUILD_FLAGS) -v -o chain33 ../ && \ + CGO_ENABLED=0 GOOS=linux GOARCH=$$(go env GOARCH) go build $(BUILD_FLAGS) -v -o chain33-cli $(SRC_CLI); \ + fi; \ cp chain33* Dockerfile docker-compose.yml *.sh ci/ && cd ci/ && ./docker-compose-pre.sh run $(proj) $(dapp) $(extra) && cd ../.. docker-compose-down: ## build docker-compose for chain33 run diff --git a/appveyor.yml b/appveyor.yml index a2bc0fe9e1..73d3751c9c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -18,8 +18,8 @@ environment: # set go version before_test: - - set PATH=C:\go119\bin;%PATH% - - set GOROOT=C:\go119 + - set PATH=C:\go122\bin;%PATH% + - set GOROOT=C:\go122 - go version - go env diff --git a/build/autotest/run.sh b/build/autotest/run.sh index 57dc9d85e5..3eed120a1b 100755 --- a/build/autotest/run.sh +++ b/build/autotest/run.sh @@ -7,11 +7,6 @@ set -o pipefail # os: ubuntu18.04 x64 -sedfix="" -if [ "$(uname)" == "Darwin" ]; then - sedfix=".bak" -fi - ## get chain33 path CHAIN33_PATH=$(go list -f "{{.Dir}}" github.com/33cn/chain33) @@ -20,8 +15,16 @@ function build_auto_test() { trap "rm -f ../autotest/main.go" INT TERM EXIT local AutoTestMain="${CHAIN33_PATH}/cmd/autotest/main.go" cp "${AutoTestMain}" ./ - sed -i $sedfix '/^package/a import _ \"github.com\/33cn\/plugin\/plugin\"' main.go - go build -v -i -o autotest + # BSD sed (macOS) does not support GNU sed's `/^package/a text` one-liner; use awk for portability. + awk '/^package/ && !done { + print + print "import _ \"github.com/33cn/plugin/plugin\"" + done = 1 + next + } + { print } + ' main.go > main.go.tmp && mv main.go.tmp main.go + go build -v -o autotest } function copyAutoTestConfig() { diff --git a/build/docker-compose-down.sh b/build/docker-compose-down.sh index e1efb90304..1fc744cd99 100755 --- a/build/docker-compose-down.sh +++ b/build/docker-compose-down.sh @@ -39,7 +39,7 @@ function down() { if [ "$num" -gt 0 ]; then # remove exsit container echo "=========== # docker-compose down =============" - docker compose down --rmi local + docker compose down --remove-orphans --rmi local fi } diff --git a/build/docker-compose-pre.sh b/build/docker-compose-pre.sh index 407b957edb..7f467720e7 100755 --- a/build/docker-compose-pre.sh +++ b/build/docker-compose-pre.sh @@ -19,6 +19,16 @@ TESTCASEFILE="testcase.sh" FORKTESTFILE="fork-test.sh" DOCKER_COMPOSE_SH="docker-compose.sh" +function copy_root_files_no_overwrite() { + local target_dir="$1" + local entry + for entry in ./*; do + if [ -f "${entry}" ]; then + cp -n "${entry}" "${target_dir}/" + fi + done +} + function down_dapp() { app=$1 @@ -29,7 +39,7 @@ function down_dapp() { ./docker-compose-down.sh "${PROJ}" "${app}" echo "============ down dapp=$app end =================" - cd .. && rm -rf ./"${app}"-ci + cd .. && rm -rf ./"${app}"-ci || true fi } @@ -40,7 +50,7 @@ function run_dapp() { echo "============ run dapp=$app start =================" if [ "$app" == "metrics" ]; then cp ./ci/paracross/* ./metrics && echo $? - cp -n ./* ./metrics/ && echo $? + copy_root_files_no_overwrite "./metrics" && echo $? cp -r ci/dapptest/ metrics/ && echo $? cd metrics && pwd rm docker-compose-paracross.yml @@ -48,7 +58,7 @@ function run_dapp() { app="paracross" else rm -rf "${app}"-ci && mkdir -p "${app}"-ci && cp -r ./"${app}"/* ./"${app}"-ci && echo $? - cp -n ./* ./"${app}"-ci/ && echo $? + copy_root_files_no_overwrite "./${app}-ci" && echo $? if [ "$app" == "paracross" ]; then cp -r dapptest/ "${app}"-ci/ && echo $? fi diff --git a/chain33.para.toml b/chain33.para.toml index a2bd9e9365..6cca143647 100644 --- a/chain33.para.toml +++ b/chain33.para.toml @@ -445,6 +445,11 @@ Enable=0 [fork.sub.rollup] Enable=-1 +[fork.sub.lightclient] +Enable=-1 +[fork.sub.rgbx] +Enable=-1 + [fork.sub.accountmanager] Enable=0 diff --git a/go.mod b/go.mod index 3aef202c5a..a9ed59d0d4 100644 --- a/go.mod +++ b/go.mod @@ -1,34 +1,43 @@ module github.com/33cn/plugin -go 1.19 +go 1.22 + +toolchain go1.22.12 replace ( github.com/ava-labs/avalanchego => github.com/33cn/avalanchego v1.10.10-0.20240529041529-ada691598153 - github.com/btcsuite/btcd => github.com/btcsuite/btcd v0.22.3 + github.com/btcsuite/btcd/btcec/v2 => github.com/btcsuite/btcd/btcec/v2 v2.3.2 github.com/consensys/gnark-crypto => github.com/consensys/gnark-crypto v0.5.3 ) require ( - github.com/33cn/chain33 v1.69.1-0.20250421040145-1bf4187c5141 + github.com/33cn/chain33 v1.69.1-0.20260508025622-0fa35083839d github.com/BurntSushi/toml v1.2.1 github.com/NebulousLabs/Sia v1.3.7 github.com/bitly/go-simplejson v0.5.0 - github.com/btcsuite/btcd v0.23.4 - github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 + github.com/btcsuite/btcd v0.24.3-0.20250318170759-4f4ea81776d6 + github.com/btcsuite/btcd/btcec/v2 v2.3.4 + github.com/btcsuite/btcd/btcutil v1.1.5 + github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 + github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f + github.com/btcsuite/btcwallet v0.16.17 + github.com/btcsuite/btcwallet/walletdb v1.5.1 + github.com/btcsuite/btcwallet/wtxmgr v1.5.6 github.com/consensys/gnark v0.5.2 github.com/consensys/gnark-crypto v0.10.0 github.com/coreos/etcd v3.3.15+incompatible github.com/davecgh/go-spew v1.1.1 github.com/ethereum/go-ethereum v1.12.0 github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 - github.com/golang/glog v1.1.0 + github.com/golang/glog v1.1.2 github.com/golang/protobuf v1.5.3 github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c - github.com/huin/goupnp v1.1.0 + github.com/huin/goupnp v1.2.0 github.com/jackpal/go-nat-pmp v1.0.2 github.com/jinzhu/copier v0.3.6-0.20220210061904-7948fe2be217 + github.com/lightninglabs/neutrino v0.16.0 github.com/mr-tron/base58 v1.2.0 github.com/pborman/uuid v1.2.0 github.com/perlin-network/life v0.0.0-20191203030451-05c0e0f7eaea @@ -38,14 +47,14 @@ require ( github.com/rs/cors v1.7.0 github.com/shopspring/decimal v1.2.0 github.com/spf13/cobra v1.5.0 - github.com/stretchr/testify v1.8.4 + github.com/stretchr/testify v1.9.0 github.com/tjfoc/gmsm v1.3.2 github.com/valyala/fasthttp v1.40.0 - golang.org/x/crypto v0.18.0 - golang.org/x/net v0.17.0 - golang.org/x/sys v0.16.0 - google.golang.org/grpc v1.57.0 - google.golang.org/protobuf v1.31.0 + golang.org/x/crypto v0.22.0 + golang.org/x/net v0.24.0 + golang.org/x/sys v0.19.0 + google.golang.org/grpc v1.59.0 + google.golang.org/protobuf v1.33.0 gopkg.in/yaml.v2 v2.4.0 gotest.tools v2.2.0+incompatible ) @@ -59,19 +68,20 @@ require ( github.com/OneOfOne/xxhash v1.2.5 // indirect github.com/VictoriaMetrics/fastcache v1.10.0 // indirect github.com/XiaoMi/pegasus-go-client v0.0.0-20210825081735-b8a75c1eac2b // indirect + github.com/aead/siphash v1.0.1 // indirect + github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 // indirect github.com/andybalholm/brotli v1.0.4 // indirect github.com/apache/arrow/go/arrow v0.0.0-20200923215132-ac86123a3f01 // indirect github.com/apache/arrow/go/v12 v12.0.1 // indirect - github.com/ava-labs/avalanchego v1.10.9 // indirect - github.com/benbjohnson/clock v1.3.0 // indirect + github.com/benbjohnson/clock v1.3.5 // indirect github.com/benbjohnson/immutable v0.2.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect - github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect - github.com/btcsuite/btcd/btcutil v1.1.3 // indirect - github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect - github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce // indirect + github.com/btcsuite/btcd/btcutil/psbt v1.1.8 // indirect + github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5 // indirect + github.com/btcsuite/btcwallet/wallet/txrules v1.2.2 // indirect + github.com/btcsuite/btcwallet/wallet/txsizes v1.2.5 // indirect github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 // indirect github.com/cenkalti/backoff/v4 v4.2.0 // indirect @@ -90,8 +100,10 @@ require ( github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect github.com/deckarep/golang-set/v2 v2.1.0 // indirect github.com/decred/base58 v1.0.3 // indirect - github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect + github.com/decred/dcrd/crypto/blake256 v1.1.0 // indirect + github.com/decred/dcrd/dcrec/edwards v1.0.0 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect + github.com/decred/dcrd/lru v1.1.2 // indirect github.com/dgraph-io/badger v1.6.2 // indirect github.com/dgraph-io/ristretto v0.0.2 // indirect github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 // indirect @@ -104,9 +116,11 @@ require ( github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/fxamacker/cbor/v2 v2.5.0 // indirect github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 // indirect + github.com/getamis/alice v1.0.3 // indirect + github.com/getamis/sirius v1.1.7 // indirect github.com/getsentry/sentry-go v0.18.0 // indirect github.com/go-interpreter/wagon v0.6.0 // indirect - github.com/go-logr/logr v1.2.3 // indirect + github.com/go-logr/logr v1.2.4 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-stack/stack v1.8.1 // indirect @@ -118,19 +132,17 @@ require ( github.com/golang-jwt/jwt/v4 v4.3.0 // indirect github.com/golang/mock v1.6.0 // indirect github.com/google/flatbuffers v2.0.8+incompatible // indirect - github.com/google/go-cmp v0.5.9 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/google/gopacket v1.1.19 // indirect github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b // indirect github.com/google/s2a-go v0.1.7 // indirect - github.com/google/uuid v1.3.0 // indirect + github.com/google/uuid v1.3.1 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect - github.com/gorilla/rpc v1.2.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.12.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-bexpr v0.1.10 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/golang-lru/v2 v2.0.2 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.5 // indirect github.com/holiman/bloomfilter/v2 v2.0.3 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/influxdata/flux v0.131.0 // indirect @@ -146,51 +158,59 @@ require ( github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect github.com/jbenet/goprocess v0.1.4 // indirect github.com/kevinms/leakybucket-go v0.0.0-20200115003610-082473db97ca // indirect - github.com/klauspost/compress v1.16.4 // indirect - github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/kkdai/bstream v1.0.0 // indirect + github.com/klauspost/compress v1.16.7 // indirect + github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/koron/go-ssdp v0.0.4 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/libp2p/go-cidranger v1.1.0 // indirect github.com/libp2p/go-flow-metrics v0.1.0 // indirect - github.com/libp2p/go-libp2p v0.27.9 // indirect + github.com/libp2p/go-libp2p v0.30.0 // indirect github.com/libp2p/go-libp2p-asn-util v0.3.0 // indirect github.com/libp2p/go-libp2p-kad-dht v0.23.0 // indirect github.com/libp2p/go-libp2p-kbucket v0.5.0 // indirect github.com/libp2p/go-libp2p-pubsub v0.9.3 // indirect github.com/libp2p/go-libp2p-record v0.2.0 // indirect github.com/libp2p/go-msgio v0.3.0 // indirect - github.com/libp2p/go-nat v0.1.0 // indirect + github.com/libp2p/go-nat v0.2.0 // indirect github.com/libp2p/go-netroute v0.2.1 // indirect - github.com/libp2p/go-reuseport v0.2.0 // indirect - github.com/libp2p/go-yamux/v4 v4.0.0 // indirect + github.com/libp2p/go-reuseport v0.4.0 // indirect + github.com/libp2p/go-yamux/v4 v4.0.1 // indirect github.com/libp2p/zeroconf/v2 v2.2.0 // indirect + github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf // indirect + github.com/lightninglabs/neutrino/cache v1.1.2 // indirect + github.com/lightningnetwork/lnd/clock v1.0.1 // indirect + github.com/lightningnetwork/lnd/fn/v2 v2.0.2 // indirect + github.com/lightningnetwork/lnd/queue v1.0.1 // indirect + github.com/lightningnetwork/lnd/ticker v1.0.0 // indirect + github.com/lightningnetwork/lnd/tlv v1.0.2 // indirect github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect - github.com/miekg/dns v1.1.53 // indirect + github.com/miekg/dns v1.1.55 // indirect github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect - github.com/minio/sha256-simd v1.0.0 // indirect + github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 // indirect + github.com/minio/sha256-simd v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/pointerstructure v1.2.0 // indirect github.com/multiformats/go-base32 v0.1.0 // indirect github.com/multiformats/go-base36 v0.2.0 // indirect - github.com/multiformats/go-multiaddr v0.9.0 // indirect + github.com/multiformats/go-multiaddr v0.11.0 // indirect github.com/multiformats/go-multiaddr-dns v0.3.1 // indirect github.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect github.com/multiformats/go-multibase v0.2.0 // indirect - github.com/multiformats/go-multicodec v0.8.1 // indirect - github.com/multiformats/go-multihash v0.2.1 // indirect + github.com/multiformats/go-multicodec v0.9.0 // indirect + github.com/multiformats/go-multihash v0.2.3 // indirect github.com/multiformats/go-multistream v0.4.1 // indirect github.com/multiformats/go-varint v0.0.7 // indirect - github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect - github.com/onsi/ginkgo/v2 v2.9.2 // indirect - github.com/opencontainers/runtime-spec v1.0.2 // indirect + github.com/onsi/ginkgo/v2 v2.11.0 // indirect + github.com/opencontainers/runtime-spec v1.1.0 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect github.com/pegasus-kv/thrift v0.13.0 // indirect @@ -198,17 +218,17 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/polydawn/refmt v0.89.0 // indirect github.com/prometheus/client_golang v1.14.0 // indirect - github.com/prometheus/client_model v0.3.0 // indirect + github.com/prometheus/client_model v0.4.0 // indirect github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect github.com/quic-go/qpack v0.4.0 // indirect - github.com/quic-go/qtls-go1-19 v0.3.3 // indirect - github.com/quic-go/qtls-go1-20 v0.2.3 // indirect - github.com/quic-go/quic-go v0.33.1 // indirect - github.com/quic-go/webtransport-go v0.5.2 // indirect + github.com/quic-go/qtls-go1-20 v0.3.2 // indirect + github.com/quic-go/quic-go v0.37.6 // indirect + github.com/quic-go/webtransport-go v0.5.3 // indirect github.com/raulk/go-watchdog v1.3.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563 // indirect - github.com/rogpeppe/go-internal v1.11.0 // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect + github.com/rollbar/rollbar-go v1.2.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sergi/go-diff v1.0.0 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect @@ -216,7 +236,7 @@ require ( github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/status-im/keycard-go v0.2.0 // indirect - github.com/stretchr/objx v0.5.0 // indirect + github.com/stretchr/objx v0.5.2 // indirect github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a // indirect github.com/tklauser/go-sysconf v0.3.5 // indirect github.com/tklauser/numcpus v0.2.2 // indirect @@ -232,41 +252,35 @@ require ( github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect + go.etcd.io/bbolt v1.3.11 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/otel v1.14.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.2 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.11.2 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.11.2 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.11.2 // indirect - go.opentelemetry.io/otel/sdk v1.11.2 // indirect go.opentelemetry.io/otel/trace v1.14.0 // indirect - go.opentelemetry.io/proto/otlp v0.19.0 // indirect - go.uber.org/atomic v1.10.0 // indirect - go.uber.org/dig v1.16.1 // indirect - go.uber.org/fx v1.19.2 // indirect - go.uber.org/mock v0.2.0 // indirect + go.uber.org/atomic v1.11.0 // indirect + go.uber.org/dig v1.17.0 // indirect + go.uber.org/fx v1.20.0 // indirect + go.uber.org/goleak v1.2.1 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.24.0 // indirect - golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect - golang.org/x/mod v0.12.0 // indirect - golang.org/x/sync v0.3.0 // indirect - golang.org/x/term v0.16.0 // indirect + go.uber.org/zap v1.25.0 // indirect + golang.org/x/exp v0.0.0-20231226003508-02704c960a9b // indirect + golang.org/x/mod v0.14.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/term v0.19.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect - golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 // indirect + golang.org/x/tools v0.16.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect gonum.org/v1/gonum v0.11.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect + google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20231012201019-e917dd12ba7a // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/sourcemap.v1 v1.0.5 // indirect gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apimachinery v0.17.5 // indirect - lukechampine.com/blake3 v1.1.7 // indirect - nhooyr.io/websocket v1.8.7 // indirect + lukechampine.com/blake3 v1.2.1 // indirect ) diff --git a/go.sum b/go.sum index 768c2558ee..8dacf1a843 100644 --- a/go.sum +++ b/go.sum @@ -12,37 +12,33 @@ cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6T cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.110.6 h1:8uYAkj3YHTP/1iwReuHPxLSbdcyc+dSBbzFMrVwDR6Q= +cloud.google.com/go v0.110.8 h1:tyNdfIxjzaWctIiLYOTalaLKZ17SI44SKFW26QbOhME= +cloud.google.com/go v0.110.8/go.mod h1:Iz8AkXJf1qmxC3Oxoep8R1T36w8B92yU29PcBhHO5fk= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/bigquery v1.53.0 h1:K3wLbjbnSlxhuG5q4pntHv5AEbQM1QqHKGYgwFIqOTg= +cloud.google.com/go/bigquery v1.56.0 h1:LHIc9E7Kw+ftFpQFKzZYBB88IAFz7qONawXXx0F3QBo= +cloud.google.com/go/bigquery v1.56.0/go.mod h1:KDcsploXTEY7XT3fDQzMUZlpQLHzE4itubHrnmhUrZA= cloud.google.com/go/bigtable v1.2.0/go.mod h1:JcVAOl45lrTmQfLj7T6TxyMzIN/3FGGcFm+2xVAli2o= cloud.google.com/go/bigtable v1.3.0 h1:PAplkJLXheOLlK5PPyy4/HXtPzHn+1/LaYDWIeGxnio= cloud.google.com/go/bigtable v1.3.0/go.mod h1:z5EyKrPE8OQmeg4h5MNdKvuSnI9CCT49Ki3f23aBzio= -cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY= +cloud.google.com/go/compute v1.23.1 h1:V97tBoDaZHb6leicZ1G6DLK2BAaZLJ/7+9BB/En3hR0= +cloud.google.com/go/compute v1.23.1/go.mod h1:CqB3xpmPKKt3OJpW2ndFIXnA9A4xAy/F3Xp1ixncW78= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/iam v1.1.1 h1:lW7fzj15aVIXYHREOqjRBV9PsH0Z6u8Y46a1YGvQP4Y= -cloud.google.com/go/longrunning v0.5.1 h1:Fr7TXftcqTudoyRJa113hyaqlGdiBQkp0Gq7tErFDWI= +cloud.google.com/go/iam v1.1.3 h1:18tKG7DzydKWUnLjonWcJO6wjSCAtzh4GcRKlH/Hrzc= +cloud.google.com/go/iam v1.1.3/go.mod h1:3khUlaBXfPKKe7huYgEpDn6FtgRyMEqbkvBxrQyY5SE= +cloud.google.com/go/longrunning v0.5.2 h1:u+oFqfEwwU7F9dIELigxbe0XVnBAo9wqMuQLA50CZ5k= +cloud.google.com/go/longrunning v0.5.2/go.mod h1:nqo6DQbNV2pXhGDbDMoN2bWz68MjZUzqv2YttZiveCs= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE= dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= @@ -50,10 +46,8 @@ dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBr dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= -github.com/33cn/avalanchego v1.10.10-0.20240529041529-ada691598153 h1:fG9c8gQK312kjhHGZhiAp3QTpUU9etMnvIJQtkVcITk= -github.com/33cn/avalanchego v1.10.10-0.20240529041529-ada691598153/go.mod h1:SgoXVssKD3a3diOV4qaTkOEQ+2KEfErcdOASIhV6a1s= -github.com/33cn/chain33 v1.69.1-0.20250421040145-1bf4187c5141 h1:uAHCiv/wKCuQpgzwkgv6eer11y8wnLc/pBMVIUeFeQ4= -github.com/33cn/chain33 v1.69.1-0.20250421040145-1bf4187c5141/go.mod h1:Sr0PFcmd96zJcRxKYkgceZZRfFd5CfbdfbfAzghyhB4= +github.com/33cn/chain33 v1.69.1-0.20260508025622-0fa35083839d h1:4RcD7vxrCuO/1yJ9hg9Anh0cWseMOXGQCpOylGdAH5M= +github.com/33cn/chain33 v1.69.1-0.20260508025622-0fa35083839d/go.mod h1:odrlSSvyZE9Zrrzf4uHk/hLUfzHZlb/8YlhetQaSIps= github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= @@ -103,6 +97,7 @@ github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8= github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM= +github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= @@ -134,10 +129,13 @@ github.com/VictoriaMetrics/fastcache v1.10.0/go.mod h1:tjiYeEfYXCqacuvYw/7UoDIeJ github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/XiaoMi/pegasus-go-client v0.0.0-20210825081735-b8a75c1eac2b h1:KF7k0g1S53oeveZxGM2wfyT5PSpO82ZxBrYlA5mM0cw= github.com/XiaoMi/pegasus-go-client v0.0.0-20210825081735-b8a75c1eac2b/go.mod h1:VrfgKISflRhFm32m3e0SXLccvNJTyG8PRywWbUuGEfY= +github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/agiledragon/gomonkey v2.0.2+incompatible h1:eXKi9/piiC3cjJD1658mEE2o3NjkJ5vDLgYjCQu0Xlw= github.com/agiledragon/gomonkey v2.0.2+incompatible/go.mod h1:2NGfXu1a80LLr2cmWXGBDaHEjb1idR6+FVlX5T3D9hw= +github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 h1:w1UutsfOrms1J05zt7ISrnJIXKzwaspym5BTKGx93EI= +github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= @@ -154,7 +152,6 @@ github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ= github.com/apache/arrow/go/arrow v0.0.0-20191024131854-af6fa24be0db/go.mod h1:VTxUBvSJ3s3eHAg65PNgrsn5BtqCRPdmyXh6rAfdxN0= github.com/apache/arrow/go/arrow v0.0.0-20200601151325-b2287a20f230/go.mod h1:QNYViu/X0HXDHw7m3KXzWSVXIbfUvJqBFe6Gj8/pYA0= @@ -165,6 +162,7 @@ github.com/apache/arrow/go/v12 v12.0.1/go.mod h1:weuTY7JvTG/HDPtMQxEUp7pU73vkLWM github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.16.0 h1:qEy6UW60iVOlUy+b9ZR0d5WzUWYGOo4HfopoyBaNmoY= +github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -182,8 +180,9 @@ github.com/aws/aws-sdk-go v1.30.12/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZve github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= +github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/immutable v0.2.1 h1:EVv7H1ju7cDg/a8HUF4hAH4DBrMJh6RWWFwq9JfoO9I= github.com/benbjohnson/immutable v0.2.1/go.mod h1:uc6OHo6PN2++n98KHLxW8ef4W42ylHiQSENghE1ezxI= github.com/benbjohnson/tmpl v1.0.0/go.mod h1:igT620JFIi44B6awvU9IsDhR77IXWtFigTLil/RPdps= @@ -201,23 +200,43 @@ github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx2 github.com/bonitoo-io/go-sql-bigquery v0.3.4-1.4.0 h1:MaVh0h9+KaMnJcoDvvIGp+O3fefdWm+8MBUX6ELTJTM= github.com/bonitoo-io/go-sql-bigquery v0.3.4-1.4.0/go.mod h1:J4Y6YJm0qTWB9aFziB7cPeSyc6dOZFyJdteSeybVpXQ= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= -github.com/btcsuite/btcd v0.22.3 h1:kYNaWFvOw6xvqP0vR20RP1Zq1DVMBxEO8QN5d1/EfNg= -github.com/btcsuite/btcd v0.22.3/go.mod h1:wqgTSL29+50LRkmOVknEdmt8ZojIzhuWvgu/iptuN7Y= -github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= +github.com/btcsuite/btcd v0.22.0-beta.0.20220207191057-4dc4ff7963b4/go.mod h1:7alexyj/lHlOtr2PJK7L/+HDJZpcGDn/pAU98r7DY08= +github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A= +github.com/btcsuite/btcd v0.24.3-0.20250318170759-4f4ea81776d6 h1:8n9k3I7e8DkpdQ5YAP4j8ly/LSsbe6qX9vmVbrUGvVw= +github.com/btcsuite/btcd v0.24.3-0.20250318170759-4f4ea81776d6/go.mod h1:OmM4kFtB0klaG/ZqT86rQiyw/1iyXlJgc3UHClPhhbs= github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= -github.com/btcsuite/btcd/btcutil v1.1.3 h1:xfbtw8lwpp0G6NwSHb+UE67ryTFHJAiNuipusjXSohQ= -github.com/btcsuite/btcd/btcutil v1.1.3/go.mod h1:UR7dsSJzJUfMmFiiLlIrMq1lS9jh9EdCV7FStZSnpi0= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= +github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= +github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= +github.com/btcsuite/btcd/btcutil v1.1.5 h1:+wER79R5670vs/ZusMTF1yTcRYE5GUsFbdjdisflzM8= +github.com/btcsuite/btcd/btcutil v1.1.5/go.mod h1:PSZZ4UitpLBWzxGd5VGOrLnmOjtPP/a6HaFo12zMs00= +github.com/btcsuite/btcd/btcutil/psbt v1.1.8 h1:4voqtT8UppT7nmKQkXV+T9K8UyQjKOn2z/ycpmJK8wg= +github.com/btcsuite/btcd/btcutil/psbt v1.1.8/go.mod h1:kA6FLH/JfUx++j9pYU0pyu+Z8XGBQuuTmuKYUf6q7/U= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ= +github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= -github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce h1:YtWJF7RHm2pYCvA5t0RPmAaLUhREsKuKd+SLhxFbFeQ= -github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcwallet v0.16.17 h1:1N6lHznRdcjDopBvcofxaIHknArkJ/EcVKgLKfGL4Dg= +github.com/btcsuite/btcwallet v0.16.17/go.mod h1:YO+W745BAH8n/Rpgj68QsLR6eLlgM4W2do4RejT0buo= +github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5 h1:Rr0njWI3r341nhSPesKQ2JF+ugDSzdPoeckS75SeDZk= +github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5/go.mod h1:+tXJ3Ym0nlQc/iHSwW1qzjmPs3ev+UVWMbGgfV1OZqU= +github.com/btcsuite/btcwallet/wallet/txrules v1.2.2 h1:YEO+Lx1ZJJAtdRrjuhXjWrYsmAk26wLTlNzxt2q0lhk= +github.com/btcsuite/btcwallet/wallet/txrules v1.2.2/go.mod h1:4v+grppsDpVn91SJv+mZT7B8hEV4nSmpREM4I8Uohws= +github.com/btcsuite/btcwallet/wallet/txsizes v1.2.5 h1:93o5Xz9dYepBP4RMFUc9RGIFXwqP2volSWRkYJFrNtI= +github.com/btcsuite/btcwallet/wallet/txsizes v1.2.5/go.mod h1:lQ+e9HxZ85QP7r3kdxItkiMSloSLg1PEGis5o5CXUQw= +github.com/btcsuite/btcwallet/walletdb v1.5.1 h1:HgMhDNCrtEFPC+8q0ei5DQ5U9Tl4RCspA22DEKXlopI= +github.com/btcsuite/btcwallet/walletdb v1.5.1/go.mod h1:jk/hvpLFINF0C1kfTn0bfx2GbnFT+Nvnj6eblZALfjs= +github.com/btcsuite/btcwallet/wtxmgr v1.5.6 h1:Zwvr/rrJYdOLqdBCSr4eICEstnEA+NBUvjIWLkrXaYI= +github.com/btcsuite/btcwallet/wtxmgr v1.5.6/go.mod h1:lzVbDkk/jRao2ib5kge46aLZW1yFc8RFNycdYpnsmZA= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3SkEwmHoWBmX1DNXhXZqlTpq6s4tyJGc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= @@ -235,6 +254,7 @@ github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+M github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= +github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM= @@ -252,11 +272,6 @@ github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA= github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= @@ -306,6 +321,7 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -315,14 +331,20 @@ github.com/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6 github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= github.com/decred/base58 v1.0.3 h1:KGZuh8d1WEMIrK0leQRM47W85KqCAdl2N+uagbctdDI= github.com/decred/base58 v1.0.3/go.mod h1:pXP9cXCfM2sFLb2viz2FNIdeMWmZDBKG3ZBYbiSM78E= -github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8= +github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/dcrec/edwards v1.0.0 h1:UDcPNzclKiJlWqV3x1Fl8xMCJrolo4PB4X9t8LwKDWU= +github.com/decred/dcrd/dcrec/edwards v1.0.0/go.mod h1:HblVh1OfMt7xSxUL1ufjToaEvpbjpWvvTAUx4yem8BI= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= +github.com/decred/dcrd/lru v1.1.2 h1:KdCzlkxppuoIDGEvCGah1fZRicrDH36IipvlB1ROkFY= +github.com/decred/dcrd/lru v1.1.2/go.mod h1:gEdCVgXs1/YoBvFWt7Scgknbhwik3FgVSzlnCcXL2N8= github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M= github.com/deepmap/oapi-codegen v1.8.2 h1:SegyeYGcdi0jLLrpbCMoJxnUUn8GBXHsvr4rbzjuhfU= +github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw= github.com/denisenkom/go-mssqldb v0.10.0 h1:QykgLZBorFE95+gO3u9esLd0BmbvpWp0/waNNZfHBM8= github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= @@ -365,10 +387,7 @@ github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4s github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= -github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= github.com/ethereum/go-ethereum v1.12.0 h1:bdnhLPtqETd4m3mS8BGMNvBTf36bO5bx/hxE2zljOa0= @@ -395,6 +414,7 @@ github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiD github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= +github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= @@ -406,6 +426,10 @@ github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrt github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 h1:f6D9Hr8xV8uYKlyuj8XIruxlh9WjVjdh1gIicAS7ays= github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= +github.com/getamis/alice v1.0.3 h1:uwEP8N7DBM1IdrA2PD3apXM6Yc+COzsvfGtbLV6PpFk= +github.com/getamis/alice v1.0.3/go.mod h1:vufJnjHliInL+yaseqFsLukMmletcFMieWD9SgIpwRc= +github.com/getamis/sirius v1.1.7 h1:RosKxc+hg7Wx3+RZidODYfLVWN0TSRXJYtF6K3AeKU0= +github.com/getamis/sirius v1.1.7/go.mod h1:a3PAEkzOLYURXHgaMlDWFcT6zAezcgO1IMoMiAtfllg= github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= github.com/getsentry/sentry-go v0.12.0/go.mod h1:NSap0JBYWzHND8oMbyi0+XZhUalc1TBdRL1M71JZW2c= github.com/getsentry/sentry-go v0.18.0 h1:MtBW5H9QgdcJabtZcuJG80BMOwaBpkRDZkxRkNC1sN0= @@ -413,11 +437,7 @@ github.com/getsentry/sentry-go v0.18.0/go.mod h1:Kgon4Mby+FJ7ZWHFUAZgVaIa8sxHtnR github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= -github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= -github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= -github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= -github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= @@ -428,6 +448,7 @@ github.com/go-chi/chi v4.1.0+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxm github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -441,8 +462,8 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= -github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= @@ -507,13 +528,6 @@ github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+ github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= github.com/go-openapi/validate v0.19.3/go.mod h1:90Vh6jjkTn+OT1Eefm0ZixWNFjhtOH7vS9k0lo6zwJo= github.com/go-openapi/validate v0.19.8/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= -github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= -github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= -github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= @@ -550,12 +564,10 @@ github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGt github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= -github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= -github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= -github.com/gobwas/ws v1.2.1 h1:F2aeBZrm2NDsc7vbovKrWSogd4wvfAxg0FQ89/iqOTk= github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk= +github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= @@ -588,22 +600,20 @@ github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGw github.com/golang/geo v0.0.0-20190916061304-5b978397cfec h1:lJwO/92dFXWeXOZdoGXgptLmNLwynMSHUmU6besqtiw= github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= -github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE= -github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= +github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= +github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -611,7 +621,6 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= @@ -642,25 +651,20 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gopacket v1.1.17/go.mod h1:UdDNZ1OO62aGYVnPhxT1U6aI7ukYtA/kB8vaU0diBUM= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190309163659-77426154d546/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -668,8 +672,6 @@ github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200417002340-c6e0a841f49a/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b h1:h9U78+dx9a4BKdQkBBos92HalKpaGKHrp+3Uo6yTodo= github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= @@ -679,8 +681,8 @@ github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8 github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/googleapis/gax-go v2.0.0+incompatible h1:j0GKcs05QVmm7yesiZq2+9cxHkNK9YM6zKx4D2qucQU= @@ -689,18 +691,18 @@ github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE0 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= +github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.4.0/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU= github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/gophercloud/gophercloud v0.10.0/go.mod h1:gmC5oQqMDOMO1t1gq5DquX/yAU808e/4mzjjDA76+Ss= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4= +github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/rpc v1.2.0 h1:WvvdC2lNeT1SP32zrIce5l0ECBfbAlmrmSBsuc57wfk= -github.com/gorilla/rpc v1.2.0/go.mod h1:V4h9r+4sF5HnzqbwIez0fKSpANP0zlYd3qR7p36jkTQ= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= @@ -711,10 +713,6 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.14.4/go.mod h1:6CwZWGDSPRJidgKAtJVvND6soZe6fT7iteq8wDPdhb0= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.12.0 h1:kr3j8iIMR4ywO/O0rvksXaJvauGGCMg2zAZIiNZ9uIQ= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.12.0/go.mod h1:ummNFgdgLhhX7aIiy35vVmQNS0rWXknfPE0qe6fmFXg= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= github.com/hashicorp/consul/api v1.4.0/go.mod h1:xc8u05kyMa3Wjr9eEAsIAo3dg8+LywT5E/Cl7cNS5nU= github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= @@ -749,8 +747,8 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/hashicorp/golang-lru/v2 v2.0.2 h1:Dwmkdr5Nc/oBiXgJS3CDHNhJtIHkuZ3DZF5twqnfBdU= -github.com/hashicorp/golang-lru/v2 v2.0.2/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/golang-lru/v2 v2.0.5 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvHgwfx2Vm4= +github.com/hashicorp/golang-lru/v2 v2.0.5/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= @@ -767,10 +765,8 @@ github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c/go.mod h1:SC8Ryt github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= -github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= -github.com/huin/goupnp v1.1.0 h1:gEe0Dp/lZmPZiDFzJJaOfUpOvv2MKUkoBX8lDrn9vKU= -github.com/huin/goupnp v1.1.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= -github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= +github.com/huin/goupnp v1.2.0 h1:uOKW26NG1hsSSbXIZ1IR7XP9Gjd1U8pnLaCMgntmkmY= +github.com/huin/goupnp v1.2.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= github.com/hydrogen18/memlistener v0.0.0-20200120041712-dcc25e7acd91/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -788,6 +784,7 @@ github.com/influxdata/influxdb v1.9.5 h1:4O7AC5jOA9RoqtDuD2rysXbumcEwaqWlWXmwuyK github.com/influxdata/influxdb v1.9.5/go.mod h1:4uPVvcry9KWQVWLxyT9641qpkRXUBN+xa0MJFFNNLKo= github.com/influxdata/influxdb-client-go/v2 v2.3.1-0.20210518120617-5d1fff431040/go.mod h1:vLNHdxTJkIf2mSLvGrpj8TCcISApPoXkaxP8g9uRlW8= github.com/influxdata/influxdb-client-go/v2 v2.4.0 h1:HGBfZYStlx3Kqvsv1h2pJixbCl/jhnFtxpKFAv9Tu5k= +github.com/influxdata/influxdb-client-go/v2 v2.4.0/go.mod h1:vLNHdxTJkIf2mSLvGrpj8TCcISApPoXkaxP8g9uRlW8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/influxdata/influxql v1.1.0/go.mod h1:KpVI7okXjK6PRi3Z5B+mtKZli+R1DnZgb3N+tzevNgo= github.com/influxdata/influxql v1.1.1-0.20210223160523-b6ab99450c93 h1:4t/8PcmLnI2vrcaHcEKeeLsGxC0WMRaOQdPX9b7DF8Y= @@ -795,6 +792,7 @@ github.com/influxdata/influxql v1.1.1-0.20210223160523-b6ab99450c93/go.mod h1:gH github.com/influxdata/line-protocol v0.0.0-20180522152040-32c6aa80de5e/go.mod h1:4kt73NQhadE3daL3WhR5EJ/J2ocX0PZzwxQ0gXJ7oFE= github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097 h1:vilfsDSy7TDxedi9gyBkMvAirat/oRcL0lFdJBf6tdM= +github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= github.com/influxdata/pkg-config v0.2.8/go.mod h1:EMS7Ll0S4qkzDk53XS3Z72/egBsPInt+BeRxb0WeSwk= github.com/influxdata/promql/v2 v2.12.0/go.mod h1:fxOPu+DY0bqCTCECchSRtWfc+0X19ybifQhZoQNF5D8= github.com/influxdata/roaring v0.4.13-0.20180809181101-fc520f41fab6/go.mod h1:bSgUQ7q5ZLSO+bKBGqJiCBGAl+9DxyW63zLTujjUlOE= @@ -832,6 +830,7 @@ github.com/jbenet/go-temp-err-catcher v0.1.0/go.mod h1:0kJRvmDZXNMIiJirNPEYfhpPw github.com/jbenet/goprocess v0.1.4 h1:DRGOFReOMqqDNXwW70QkacFW0YN9QnwLV0Vqk+3oU0o= github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jinzhu/copier v0.3.6-0.20220210061904-7948fe2be217 h1:Uy9RZ3MW1z8PUZ4NE+bybs4teJbzt62EKZ3/j42jSFw= github.com/jinzhu/copier v0.3.6-0.20220210061904-7948fe2be217/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= @@ -849,7 +848,6 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jsternberg/zap-logfmt v1.0.0/go.mod h1:uvPs/4X51zdkcm5jXl5SYoN+4RK21K8mysFmDaM/h+o= @@ -875,26 +873,25 @@ github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQL github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/kkdai/bstream v1.0.0 h1:Se5gHwgp2VT2uHfDrkbbgbgEvV9cimLELwrPJctSjg8= +github.com/kkdai/bstream v1.0.0/go.mod h1:FDnDOHt5Yx4p3FaHcioFT0QjDOtgUpvjeZqAs+NVZZA= github.com/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK4= +github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.16.4 h1:91KN02FnsOYhuunwU4ssRe8lc2JosWmizWa91B5v1PU= -github.com/klauspost/compress v1.16.4/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= +github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= -github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= +github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk= github.com/koron/go-ssdp v0.0.4 h1:1IDwrghSKYM7yLf7XCzbByg2sJ/JcNOZRXS2jczTwz0= github.com/koron/go-ssdp v0.0.4/go.mod h1:oDXq+E5IL5q0U8uSBcoAXzTzInwy5lEgC91HoKtbmZk= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -912,13 +909,12 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v0.0.0-20160406211939-eadb3ce320cb/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= -github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= -github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= @@ -927,8 +923,8 @@ github.com/libp2p/go-cidranger v1.1.0 h1:ewPN8EZ0dd1LSnrtuwd4709PXVcITVeuwbag38y github.com/libp2p/go-cidranger v1.1.0/go.mod h1:KWZTfSr+r9qEo9OkI9/SIEeAtw+NNoU0dXIXt15Okic= github.com/libp2p/go-flow-metrics v0.1.0 h1:0iPhMI8PskQwzh57jB9WxIuIOQ0r+15PChFGkx3Q3WM= github.com/libp2p/go-flow-metrics v0.1.0/go.mod h1:4Xi8MX8wj5aWNDAZttg6UPmc0ZrnFNsMtpsYUClFtro= -github.com/libp2p/go-libp2p v0.27.9 h1:n5p5bQD469v7I/1qncaHDq0BeSx4iT2fHF3NyNuKOmY= -github.com/libp2p/go-libp2p v0.27.9/go.mod h1:Tdx7ZuJl9NE78PkB4FjPVbf6kaQNOh2ppU/OVvVB6Wc= +github.com/libp2p/go-libp2p v0.30.0 h1:9EZwFtJPFBcs/yJTnP90TpN1hgrT/EsFfM+OZuwV87U= +github.com/libp2p/go-libp2p v0.30.0/go.mod h1:nr2g5V7lfftwgiJ78/HrID+pwvayLyqKCEirT2Y3Byg= github.com/libp2p/go-libp2p-asn-util v0.3.0 h1:gMDcMyYiZKkocGXDQ5nsUQyquC9+H+iLEQHwOCZ7s8s= github.com/libp2p/go-libp2p-asn-util v0.3.0/go.mod h1:B1mcOrKUE35Xq/ASTmQ4tN3LNzVVaMNmq2NACuqyB9w= github.com/libp2p/go-libp2p-kad-dht v0.23.0 h1:sxE6LxLopp79eLeV695n7+c77V/Vn4AMF28AdM/XFqM= @@ -940,20 +936,35 @@ github.com/libp2p/go-libp2p-pubsub v0.9.3/go.mod h1:RYA7aM9jIic5VV47WXu4GkcRxRhr github.com/libp2p/go-libp2p-record v0.2.0 h1:oiNUOCWno2BFuxt3my4i1frNrt7PerzB3queqa1NkQ0= github.com/libp2p/go-libp2p-record v0.2.0/go.mod h1:I+3zMkvvg5m2OcSdoL0KPljyJyvNDFGKX7QdlpYUcwk= github.com/libp2p/go-libp2p-testing v0.12.0 h1:EPvBb4kKMWO29qP4mZGyhVzUyR25dvfUIK5WDu6iPUA= +github.com/libp2p/go-libp2p-testing v0.12.0/go.mod h1:KcGDRXyN7sQCllucn1cOOS+Dmm7ujhfEyXQL5lvkcPg= github.com/libp2p/go-msgio v0.3.0 h1:mf3Z8B1xcFN314sWX+2vOTShIE0Mmn2TXn3YCUQGNj0= github.com/libp2p/go-msgio v0.3.0/go.mod h1:nyRM819GmVaF9LX3l03RMh10QdOroF++NBbxAb0mmDM= -github.com/libp2p/go-nat v0.1.0 h1:MfVsH6DLcpa04Xr+p8hmVRG4juse0s3J8HyNWYHffXg= -github.com/libp2p/go-nat v0.1.0/go.mod h1:X7teVkwRHNInVNWQiO/tAiAVRwSr5zoRz4YSTC3uRBM= -github.com/libp2p/go-netroute v0.1.2/go.mod h1:jZLDV+1PE8y5XxBySEBgbuVAXbhtuHSdmLPL2n9MKbk= +github.com/libp2p/go-nat v0.2.0 h1:Tyz+bUFAYqGyJ/ppPPymMGbIgNRH+WqC5QrT5fKrrGk= +github.com/libp2p/go-nat v0.2.0/go.mod h1:3MJr+GRpRkyT65EpVPBstXLvOlAPzUVlG6Pwg9ohLJk= github.com/libp2p/go-netroute v0.2.1 h1:V8kVrpD8GK0Riv15/7VN6RbUQ3URNZVosw7H2v9tksU= github.com/libp2p/go-netroute v0.2.1/go.mod h1:hraioZr0fhBjG0ZRXJJ6Zj2IVEVNx6tDTFQfSmcq7mQ= -github.com/libp2p/go-reuseport v0.2.0 h1:18PRvIMlpY6ZK85nIAicSBuXXvrYoSw3dsBAR7zc560= -github.com/libp2p/go-reuseport v0.2.0/go.mod h1:bvVho6eLMm6Bz5hmU0LYN3ixd3nPPvtIlaURZZgOY4k= -github.com/libp2p/go-sockaddr v0.0.2/go.mod h1:syPvOmNs24S3dFVGJA1/mrqdeijPxLV2Le3BRLKd68k= -github.com/libp2p/go-yamux/v4 v4.0.0 h1:+Y80dV2Yx/kv7Y7JKu0LECyVdMXm1VUoko+VQ9rBfZQ= -github.com/libp2p/go-yamux/v4 v4.0.0/go.mod h1:NWjl8ZTLOGlozrXSOZ/HlfG++39iKNnM5wwmtQP1YB4= +github.com/libp2p/go-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQscQm2s= +github.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU= +github.com/libp2p/go-yamux/v4 v4.0.1 h1:FfDR4S1wj6Bw2Pqbc8Uz7pCxeRBPbwsBbEdfwiCypkQ= +github.com/libp2p/go-yamux/v4 v4.0.1/go.mod h1:NWjl8ZTLOGlozrXSOZ/HlfG++39iKNnM5wwmtQP1YB4= github.com/libp2p/zeroconf/v2 v2.2.0 h1:Cup06Jv6u81HLhIj1KasuNM/RHHrJ8T7wOTS4+Tv53Q= github.com/libp2p/zeroconf/v2 v2.2.0/go.mod h1:fuJqLnUwZTshS3U/bMRJ3+ow/v9oid1n0DmyYyNO1Xs= +github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf h1:HZKvJUHlcXI/f/O0Avg7t8sqkPo78HFzjmeYFl6DPnc= +github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf/go.mod h1:vxmQPeIQxPf6Jf9rM8R+B4rKBqLA2AjttNxkFBL2Plk= +github.com/lightninglabs/neutrino v0.16.0 h1:YNTQG32fPR/Zg0vvJVI65OBH8l3U18LSXXtX91hx0q0= +github.com/lightninglabs/neutrino v0.16.0/go.mod h1:x3OmY2wsA18+Kc3TSV2QpSUewOCiscw2mKpXgZv2kZk= +github.com/lightninglabs/neutrino/cache v1.1.2 h1:C9DY/DAPaPxbFC+xNNEI/z1SJY9GS3shmlu5hIQ798g= +github.com/lightninglabs/neutrino/cache v1.1.2/go.mod h1:XJNcgdOw1LQnanGjw8Vj44CvguYA25IMKjWFZczwZuo= +github.com/lightningnetwork/lnd/clock v1.0.1 h1:QQod8+m3KgqHdvVMV+2DRNNZS1GRFir8mHZYA+Z2hFo= +github.com/lightningnetwork/lnd/clock v1.0.1/go.mod h1:KnQudQ6w0IAMZi1SgvecLZQZ43ra2vpDNj7H/aasemg= +github.com/lightningnetwork/lnd/fn/v2 v2.0.2 h1:M7o2lYrh/zCp+lntPB3WP/rWTu5U+4ssyHW+kqNJ0fs= +github.com/lightningnetwork/lnd/fn/v2 v2.0.2/go.mod h1:TOzwrhjB/Azw1V7aa8t21ufcQmdsQOQMDtxVOQWNl8s= +github.com/lightningnetwork/lnd/queue v1.0.1 h1:jzJKcTy3Nj5lQrooJ3aaw9Lau3I0IwvQR5sqtjdv2R0= +github.com/lightningnetwork/lnd/queue v1.0.1/go.mod h1:vaQwexir73flPW43Mrm7JOgJHmcEFBWWSl9HlyASoms= +github.com/lightningnetwork/lnd/ticker v1.0.0 h1:S1b60TEGoTtCe2A0yeB+ecoj/kkS4qpwh6l+AkQEZwU= +github.com/lightningnetwork/lnd/ticker v1.0.0/go.mod h1:iaLXJiVgI1sPANIF2qYYUJXjoksPNvGNYowB8aRbpX0= +github.com/lightningnetwork/lnd/tlv v1.0.2 h1:LG7H3Uw/mHYGnEeHRPg+STavAH+UsFvuBflD0PzcYFQ= +github.com/lightningnetwork/lnd/tlv v1.0.2/go.mod h1:fICAfsqk1IOsC1J7G9IdsWX1EqWRMqEDCNxZJSKr9C4= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= @@ -1012,8 +1023,8 @@ github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKju github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= -github.com/miekg/dns v1.1.53 h1:ZBkuHr5dxHtB1caEOlZTLPo7D3L3TWckgUUs/RHfDxw= -github.com/miekg/dns v1.1.53/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= +github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo= +github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c h1:bzE/A84HN25pxAuk9Eej1Kz9OUelF97nAc82bDquQI8= github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c/go.mod h1:0SQS9kMwD2VsyFEB++InYyBJroV/FRmBgcydeSUcJms= github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b h1:z78hV3sbSMAUoyUMM0I83AUIT6Hu17AWfgjzIbtrYFc= @@ -1022,11 +1033,14 @@ github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc h1:PTfri+PuQmWDqERdn github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc/go.mod h1:cGKTAVKx4SxOuR/czcZ/E2RSJ3sfHs8FpHhQ5CWMf9s= github.com/mileusna/useragent v0.0.0-20190129205925-3e331f0949a5/go.mod h1:JWhYAp2EXqUtsxTKdeGlY8Wp44M7VxThC9FEoNGi2IE= github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 h1:AMFGa4R4MiIpspGNG7Z948v4n35fFGB3RR3G/ry4FWs= +github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= +github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 h1:lYpkrQH5ajf0OXOcUbGjvZxxijuBwbbmlSxLiuofa+g= github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 h1:+n/aFZefKZp7spd8DFdX7uMikMLXX4oubIzJF4kv/wI= +github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= -github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= -github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= +github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= +github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= @@ -1045,12 +1059,10 @@ github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjU github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= github.com/mmcloughlin/avo v0.0.0-20190318053554-7a0eb66183da/go.mod h1:lf5GMZxA5kz8dnCweJuER5Rmbx6dDu6qvw0fO3uYKK8= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= @@ -1064,20 +1076,20 @@ github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9 github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= github.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU55txyt0p4aiWVohjo= github.com/multiformats/go-multiaddr v0.2.0/go.mod h1:0nO36NvPpyV4QzvTLi/lafl2y95ncPj0vFwVF6k6wJ4= -github.com/multiformats/go-multiaddr v0.9.0 h1:3h4V1LHIk5w4hJHekMKWALPXErDfz/sggzwC/NcqbDQ= -github.com/multiformats/go-multiaddr v0.9.0/go.mod h1:mI67Lb1EeTOYb8GQfL/7wpIZwc46ElrvzhYnoJOmTT0= +github.com/multiformats/go-multiaddr v0.11.0 h1:XqGyJ8ufbCE0HmTDwx2kPdsrQ36AGPZNZX6s6xfJH10= +github.com/multiformats/go-multiaddr v0.11.0/go.mod h1:gWUm0QLR4thQ6+ZF6SXUw8YjtwQSPapICM+NmCkxHSM= github.com/multiformats/go-multiaddr-dns v0.3.1 h1:QgQgR+LQVt3NPTjbrLLpsaT2ufAA2y0Mkk+QRVJbW3A= github.com/multiformats/go-multiaddr-dns v0.3.1/go.mod h1:G/245BRQ6FJGmryJCrOuTdB37AMA5AMOVuO6NY3JwTk= github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E= github.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo= github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g= github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk= -github.com/multiformats/go-multicodec v0.8.1 h1:ycepHwavHafh3grIbR1jIXnKCsFm0fqsfEOsJ8NtKE8= -github.com/multiformats/go-multicodec v0.8.1/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k= +github.com/multiformats/go-multicodec v0.9.0 h1:pb/dlPnzee/Sxv/j4PmkDRxCOi3hXTz3IbPKOXWJkmg= +github.com/multiformats/go-multicodec v0.9.0/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k= github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= github.com/multiformats/go-multihash v0.0.13/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= -github.com/multiformats/go-multihash v0.2.1 h1:aem8ZT0VA2nCHHk7bPJ1BjUbHNciqZC/d16Vve9l108= -github.com/multiformats/go-multihash v0.2.1/go.mod h1:WxoMcYG85AZVQUyRyo9s4wULvW5qrI9vb2Lt6evduFc= +github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U= +github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= github.com/multiformats/go-multistream v0.4.1 h1:rFy0Iiyn3YT0asivDUIR05leAdwZq3de4741sbiSdfo= github.com/multiformats/go-multistream v0.4.1/go.mod h1:Mz5eykRVAjJWckE2U78c6xqdtyNUEhKSM0Lwar2p77Q= github.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= @@ -1095,8 +1107,6 @@ github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzE github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d h1:AREM5mwr4u1ORQBMvzfzBgpsctsbQikCVpvC+tX285E= -github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= @@ -1116,12 +1126,13 @@ github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU= -github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts= +github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= +github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= @@ -1130,10 +1141,12 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= -github.com/onsi/gomega v1.27.4 h1:Z2AnStgsdSayCMDiCU42qIz+HLqEPcgiOCXjAU/w+8E= +github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc= +github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJKFnNQ= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= -github.com/opencontainers/runtime-spec v1.0.2 h1:UfAcuLBJB9Coz72x1hgl8O5RVzTdNiaglX6v2DM6FI0= github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.1.0 h1:HHUyrt9mwHUjtasSbXSMvs4cyFxh+Bll4AjJ9odEGpg= +github.com/opencontainers/runtime-spec v1.1.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= github.com/opentracing-contrib/go-stdlib v0.0.0-20190519235532-cf7a6c988dc9/go.mod h1:PLldrQSroqzH70Xl+1DQcGnefIbqsKR7UDaiux3zV+w= github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= @@ -1159,8 +1172,6 @@ github.com/pegasus-kv/thrift v0.13.0 h1:4ESwaNoHImfbHa9RUGJiJZ4hrxorihZHk5aarYwY github.com/pegasus-kv/thrift v0.13.0/go.mod h1:Gl9NT/WHG6ABm6NsrbfE8LiJN0sAyneCrvB4qN4NPqQ= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= -github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= -github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/perlin-network/life v0.0.0-20191203030451-05c0e0f7eaea h1:okKoivlkNRRLqXraEtatHfEhW+D71QTwkaj+4n4M2Xc= github.com/perlin-network/life v0.0.0-20191203030451-05c0e0f7eaea/go.mod h1:3KEU5Dm8MAYWZqity880wOFJ9PhQjyKVZGwAEfc5Q4E= @@ -1208,8 +1219,8 @@ github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1: github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= -github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= +github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= @@ -1230,14 +1241,12 @@ github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB github.com/prometheus/prometheus v0.0.0-20200609090129-a6600f564e3c/go.mod h1:S5n0C6tSgdnwWshBUceRx5G1OsjLv/EeZ9t3wIfEtsY= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= -github.com/quic-go/qtls-go1-19 v0.3.3 h1:wznEHvJwd+2X3PqftRha0SUKmGsnb6dfArMhy9PeJVE= -github.com/quic-go/qtls-go1-19 v0.3.3/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI= -github.com/quic-go/qtls-go1-20 v0.2.3 h1:m575dovXn1y2ATOb1XrRFcrv0F+EQmlowTkoraNkDPI= -github.com/quic-go/qtls-go1-20 v0.2.3/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM= -github.com/quic-go/quic-go v0.33.1 h1:EVsG7O/7FVZI8Za71GzpHDoWpBTKdjDv1/x0KFcckho= -github.com/quic-go/quic-go v0.33.1/go.mod h1:YMuhaAV9/jIu0XclDXwZPAsP/2Kgr5yMYhe9oxhhOFA= -github.com/quic-go/webtransport-go v0.5.2 h1:GA6Bl6oZY+g/flt00Pnu0XtivSD8vukOu3lYhJjnGEk= -github.com/quic-go/webtransport-go v0.5.2/go.mod h1:OhmmgJIzTTqXK5xvtuX0oBpLV2GkLWNDA+UeTGJXErU= +github.com/quic-go/qtls-go1-20 v0.3.2 h1:rRgN3WfnKbyik4dBV8A6girlJVxGand/d+jVKbQq5GI= +github.com/quic-go/qtls-go1-20 v0.3.2/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= +github.com/quic-go/quic-go v0.37.6 h1:2IIUmQzT5YNxAiaPGjs++Z4hGOtIR0q79uS5qE9ccfY= +github.com/quic-go/quic-go v0.37.6/go.mod h1:YsbH1r4mSHPJcLF4k4zruUkLBqctEMBDR6VPvcYjIsU= +github.com/quic-go/webtransport-go v0.5.3 h1:5XMlzemqB4qmOlgIus5zB45AcZ2kCgCy2EptUrfOPWU= +github.com/quic-go/webtransport-go v0.5.3/go.mod h1:OhmmgJIzTTqXK5xvtuX0oBpLV2GkLWNDA+UeTGJXErU= github.com/raulk/go-watchdog v1.3.0 h1:oUmdlHxdkXRJlwfG0O9omj8ukerm8MEQavSiDTEtBsk= github.com/raulk/go-watchdog v1.3.0/go.mod h1:fIvOnLbF0b0ZwkB9YU4mOW9Did//4vPZtDqv66NfsMU= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= @@ -1254,8 +1263,10 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rollbar/rollbar-go v1.2.0 h1:CUanFtVu0sa3QZ/fBlgevdGQGLWaE3D4HxoVSQohDfo= +github.com/rollbar/rollbar-go v1.2.0/go.mod h1:czC86b8U4xdUH7W2C6gomi2jutLm8qK0OtrF5WMvpcc= github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= @@ -1266,7 +1277,6 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= -github.com/sanity-io/litter v1.5.1 h1:dwnrSypP6q56o3lFxTU+t2fwQ9A+U5qrXVO4Qg9KwVU= github.com/satori/go.uuid v0.0.0-20160603004225-b111a074d5ef/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= @@ -1350,8 +1360,9 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -1364,12 +1375,12 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a h1:1ur3QoCqvE5fl+nylMaIr9PVV1w343YRDtsy+Rwu7XI= github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= -github.com/thepudds/fzgen v0.4.2 h1:HlEHl5hk2/cqEomf2uK5SA/FeJc12s/vIHmOG+FbACw= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= @@ -1396,11 +1407,9 @@ github.com/uber/jaeger-lib v2.2.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6 github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg= github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= -github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= -github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= @@ -1454,7 +1463,10 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= +go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= @@ -1468,41 +1480,26 @@ go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/otel v1.14.0 h1:/79Huy8wbf5DnIPhemGB+zEPVwnN6fuQybr/SRXa6hM= go.opentelemetry.io/otel v1.14.0/go.mod h1:o4buv+dJzx8rohcUeRmWUZhqupFvzWis188WlggnNeU= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.2 h1:htgM8vZIF8oPSCxa341e3IZ4yr/sKxgu8KZYllByiVY= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.2/go.mod h1:rqbht/LlhVBgn5+k3M5QK96K5Xb0DvXpMJ5SFQpY6uw= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.11.2 h1:fqR1kli93643au1RKo0Uma3d2aPQKT+WBKfTSBaKbOc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.11.2/go.mod h1:5Qn6qvgkMsLDX+sYK64rHb1FPhpn0UtxF+ouX1uhyJE= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.11.2 h1:ERwKPn9Aer7Gxsc0+ZlutlH1bEEAUXAUhqm3Y45ABbk= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.11.2/go.mod h1:jWZUM2MWhWCJ9J9xVbRx7tzK1mXKpAlze4CeulycwVY= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.11.2 h1:Us8tbCmuN16zAnK5TC69AtODLycKbwnskQzaB6DfFhc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.11.2/go.mod h1:GZWSQQky8AgdJj50r1KJm8oiQiIPaAX7uZCFQX9GzC8= -go.opentelemetry.io/otel/sdk v1.11.2 h1:GF4JoaEx7iihdMFu30sOyRx52HDHOkl9xQ8SMqNXUiU= -go.opentelemetry.io/otel/sdk v1.11.2/go.mod h1:wZ1WxImwpq+lVRo4vsmSOxdd+xwoUJ6rqyLc3SyX9aU= go.opentelemetry.io/otel/trace v1.14.0 h1:wp2Mmvj41tDsyAJXiWDWpfNsOiIyd38fy85pyKcFq/M= go.opentelemetry.io/otel/trace v1.14.0/go.mod h1:8avnQLK+CG77yNLUae4ea2JDQ6iT+gozhnZjy/rw9G8= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw= -go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.5.1/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= -go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/dig v1.16.1 h1:+alNIBsl0qfY0j6epRubp/9obgtrObRAc5aD+6jbWY8= -go.uber.org/dig v1.16.1/go.mod h1:557JTAUZT5bUK0SvCwikmLPPtdQhfvLYtO5tJgQSbnk= -go.uber.org/fx v1.19.2 h1:SyFgYQFr1Wl0AYstE8vyYIzP4bFz2URrScjwC4cwUvY= -go.uber.org/fx v1.19.2/go.mod h1:43G1VcqSzbIv77y00p1DRAsyZS8WdzuYdhZXmEUkMyQ= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/dig v1.17.0 h1:5Chju+tUvcC+N7N6EV08BJz41UZuO3BmHcN4A287ZLI= +go.uber.org/dig v1.17.0/go.mod h1:rTxpf7l5I0eBTlE6/9RL+lDybC7WFwY2QH55ZSjy1mU= +go.uber.org/fx v1.20.0 h1:ZMC/pnRvhsthOZh9MZjMq5U8Or3mA9zBSPaLnzs3ihQ= +go.uber.org/fx v1.20.0/go.mod h1:qCUj0btiR3/JnanEr1TYEePfSw6o/4qYJscgvzQ5Ub0= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= -go.uber.org/mock v0.2.0 h1:TaP3xedm7JaAgScZO7tlvlKrqT0p7I6OsdGB5YNSMDU= -go.uber.org/mock v0.2.0/go.mod h1:J0y0rp9L3xiff1+ZBfKxlC1fz2+aO16tw0tsDOixfuM= +go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= @@ -1518,12 +1515,13 @@ go.uber.org/zap v1.14.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= -go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= -go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c= +go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= golang.org/x/arch v0.0.0-20181203225421-5a4828bb7045/go.mod h1:cYlCBUl1MsqxdiKgmc4uh7TxZfWSFLOGSRR090WDxt8= golang.org/x/arch v0.0.0-20190312162104-788fe5ffcd8c/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4= golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180505025534-4ec37c66abab/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -1547,7 +1545,6 @@ golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200422194213-44a606286825/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -1559,8 +1556,8 @@ golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWP golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= -golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1574,8 +1571,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= -golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp v0.0.0-20231226003508-02704c960a9b h1:kLiC65FbiHWFAOu+lxwNPujcsl8VYyTYYEZnsOO1WK4= +golang.org/x/exp v0.0.0-20231226003508-02704c960a9b/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -1600,15 +1597,14 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1629,7 +1625,6 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -1643,16 +1638,11 @@ golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -1665,8 +1655,8 @@ golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1674,8 +1664,8 @@ golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g= +golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU= +golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1688,8 +1678,8 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1705,14 +1695,12 @@ golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190306220234-b354f8bf4d9e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1749,16 +1737,12 @@ golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1783,18 +1767,18 @@ golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220405052023-b1e9470b6e64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= -golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= +golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1871,28 +1855,18 @@ golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304024140-c4206d458c3f/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200422205258-72e4a01eba43/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200721032237-77f530d86f9a/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 h1:Vve/L0v7CXXuxUmaMGIEK/dEeq7uiqb5qBgQrZzIE7E= -golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= +golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM= +golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1922,14 +1896,10 @@ google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsb google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/api v0.128.0 h1:RjPESny5CnQRn9V6siglged+DZCgfu9l6mO9dkX9VOg= +google.golang.org/api v0.128.0/go.mod h1:Y611qgqaE92On/7g65MQgxYul3c0rEB894kniWLY750= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1967,28 +1937,16 @@ google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvx google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200420144010-e5e8543f8aeb/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= -google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5 h1:L6iMMGrtzgHsWofoFcihmDEMYeDR9KN/ThbPWGrh++g= -google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5/go.mod h1:oH/ZOT02u4kWEp7oYBGYFFkCdKS/uYR9Z7+0/xuuFp8= -google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e h1:z3vDksarJxsAKM5dmEGv0GHwE2hKJ096wZra71Vs4sw= -google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= +google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b h1:+YaDE2r2OG8t/z5qmsh7Y+XXwCbvadxxZ0YY6mTdrVA= +google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:CgAqfJo+Xmu0GwA0411Ht3OU3OntXwsGmrmjI8ioGXI= +google.golang.org/genproto/googleapis/api v0.0.0-20231012201019-e917dd12ba7a h1:myvhA4is3vrit1a6NZCWBIwN0kNEnX21DJOJX/NvIfI= +google.golang.org/genproto/googleapis/api v0.0.0-20231012201019-e917dd12ba7a/go.mod h1:SUBoKXbI1Efip18FClrQVGjWcyd0QZd8KkvdP34t7ww= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 h1:AB/lmRny7e2pLhFEYIbl5qkDAUt2h0ZRO4wGPhZf+ik= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405/go.mod h1:67X1fPuzjcrkymZzZV1vvkFeTn2Rvc6lYF9MYFGCcwE= google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= @@ -2009,16 +1967,10 @@ google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8 google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.0/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw= -google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= +google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= +google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -2027,13 +1979,11 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -2089,7 +2039,6 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.17.5/go.mod h1:0zV5/ungglgy2Rlm3QK8fbxkXVs+BSJWpJP/+8gUVLY= k8s.io/apimachinery v0.0.0-20191123233150-4c4803ed55e3/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= k8s.io/apimachinery v0.17.5 h1:QAjfgeTtSGksdkgyaPrIb4lhU16FWMIzxKejYD5S0gc= @@ -2104,10 +2053,10 @@ k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKf k8s.io/kube-openapi v0.0.0-20200316234421-82d701f24f9d/go.mod h1:F+5wygcW0wmRTnM3cOgIqGivxkwSWIWT5YdsDbeAOaU= k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20200414100711-2df71ebbae66/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0= -lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA= -nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= -nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= +lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= +lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= +pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= +pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= diff --git a/plugin/consensus/dpos/types/priv_validator.go b/plugin/consensus/dpos/types/priv_validator.go index dfb274991e..133cab4635 100644 --- a/plugin/consensus/dpos/types/priv_validator.go +++ b/plugin/consensus/dpos/types/priv_validator.go @@ -6,7 +6,6 @@ package types import ( "bytes" - "crypto/ecdsa" "encoding/hex" "encoding/json" "errors" @@ -19,7 +18,7 @@ import ( "github.com/33cn/chain33/common/crypto" vrf "github.com/33cn/chain33/common/vrf/secp256k1" "github.com/33cn/chain33/types" - secp256k1 "github.com/btcsuite/btcd/btcec" + secp256k1 "github.com/btcsuite/btcd/btcec/v2" ) // KeyText ... @@ -334,8 +333,8 @@ func (pv *PrivValidatorImp) VrfEvaluate(input []byte) (hash [32]byte, proof []by pv.mtx.Lock() defer pv.mtx.Unlock() - privKey, _ := secp256k1.PrivKeyFromBytes(secp256k1.S256(), pv.PrivKey.Bytes()) - vrfPriv := &vrf.PrivateKey{PrivateKey: (*ecdsa.PrivateKey)(privKey)} + privKey, _ := secp256k1.PrivKeyFromBytes(pv.PrivKey.Bytes()) + vrfPriv := &vrf.PrivateKey{PrivateKey: privKey.ToECDSA()} hash, proof = vrfPriv.Evaluate(input) return hash, proof } @@ -345,11 +344,11 @@ func (pv *PrivValidatorImp) VrfProof(pubkey []byte, input []byte, hash [32]byte, pv.mtx.Lock() defer pv.mtx.Unlock() - pubKey, err := secp256k1.ParsePubKey(pubkey, secp256k1.S256()) + pubKey, err := secp256k1.ParsePubKey(pubkey) if err != nil { return false } - vrfPub := &vrf.PublicKey{PublicKey: (*ecdsa.PublicKey)(pubKey)} + vrfPub := &vrf.PublicKey{PublicKey: pubKey.ToECDSA()} vrfHash, err := vrfPub.ProofToHash(input, proof) if err != nil { return false diff --git a/plugin/consensus/ticket/ticket.go b/plugin/consensus/ticket/ticket.go index 10fbe3e331..6f73f72844 100644 --- a/plugin/consensus/ticket/ticket.go +++ b/plugin/consensus/ticket/ticket.go @@ -6,7 +6,6 @@ package ticket import ( "bytes" - "crypto/ecdsa" "encoding/hex" "errors" "fmt" @@ -30,7 +29,7 @@ import ( cty "github.com/33cn/chain33/system/dapp/coins/types" "github.com/33cn/chain33/types" ty "github.com/33cn/plugin/plugin/dapp/ticket/types" - secp256k1 "github.com/btcsuite/btcd/btcec" + secp256k1 "github.com/btcsuite/btcd/btcec/v2" "github.com/golang/protobuf/proto" ) @@ -423,12 +422,12 @@ func (client *Client) CheckBlock(parent *types.Block, current *types.BlockDetail } func vrfVerify(pub []byte, input []byte, proof []byte, hash []byte) error { - pubKey, err := secp256k1.ParsePubKey(pub, secp256k1.S256()) + pubKey, err := secp256k1.ParsePubKey(pub) if err != nil { tlog.Error("vrfVerify", "err", err) return ty.ErrVrfVerify } - vrfPub := &vrf.PublicKey{PublicKey: (*ecdsa.PublicKey)(pubKey)} + vrfPub := &vrf.PublicKey{PublicKey: pubKey.ToECDSA()} vrfHash, err := vrfPub.ProofToHash(input, proof) if err != nil { tlog.Error("vrfVerify", "err", err) @@ -701,8 +700,8 @@ func (client *Client) addMinerTx(parent, block *types.Block, diff *big.Int, priv if input == nil { input = miner.PrivHash } - privKey, _ := secp256k1.PrivKeyFromBytes(secp256k1.S256(), priv.Bytes()) - vrfPriv := &vrf.PrivateKey{PrivateKey: (*ecdsa.PrivateKey)(privKey)} + privKey, _ := secp256k1.PrivKeyFromBytes(priv.Bytes()) + vrfPriv := &vrf.PrivateKey{PrivateKey: privKey.ToECDSA()} vrfHash, vrfProof := vrfPriv.Evaluate(input) miner.VrfHash = vrfHash[:] miner.VrfProof = vrfProof diff --git a/plugin/consensus/ticket/ticket_test.go b/plugin/consensus/ticket/ticket_test.go index 61d11cff06..62b7220373 100755 --- a/plugin/consensus/ticket/ticket_test.go +++ b/plugin/consensus/ticket/ticket_test.go @@ -5,8 +5,8 @@ package ticket import ( - "crypto/ecdsa" "fmt" + "github.com/btcsuite/btcd/btcec/v2" "testing" "time" @@ -18,7 +18,6 @@ import ( "github.com/33cn/chain33/util" "github.com/33cn/chain33/util/testnode" ty "github.com/33cn/plugin/plugin/dapp/ticket/types" - secp256k1 "github.com/btcsuite/btcd/btcec" "github.com/stretchr/testify/assert" apimocks "github.com/33cn/chain33/client/mocks" @@ -204,8 +203,8 @@ func Test_vrfVerify(t *testing.T) { assert.NoError(t, err) pub := priv.PubKey().Bytes() - privKey, _ := secp256k1.PrivKeyFromBytes(secp256k1.S256(), priv.Bytes()) - vpriv := &vrf.PrivateKey{PrivateKey: (*ecdsa.PrivateKey)(privKey)} + privKey, _ := btcec.PrivKeyFromBytes(priv.Bytes()) + vpriv := &vrf.PrivateKey{PrivateKey: privKey.ToECDSA()} m1 := []byte("data1") m2 := []byte("data2") diff --git a/plugin/dapp/bridgevmxgo/boss4x/chain33/chain33NewOracleClaim.go b/plugin/dapp/bridgevmxgo/boss4x/chain33/chain33NewOracleClaim.go index b6dda4cd31..1aabcad8c5 100644 --- a/plugin/dapp/bridgevmxgo/boss4x/chain33/chain33NewOracleClaim.go +++ b/plugin/dapp/bridgevmxgo/boss4x/chain33/chain33NewOracleClaim.go @@ -20,7 +20,7 @@ import ( "github.com/33cn/plugin/plugin/dapp/cross2eth/ebrelayer/utils" evmAbi "github.com/33cn/plugin/plugin/dapp/evm/executor/abi" evmtypes "github.com/33cn/plugin/plugin/dapp/evm/types" - btcec_secp256k1 "github.com/btcsuite/btcd/btcec" + btcec_secp256k1 "github.com/btcsuite/btcd/btcec/v2" "github.com/ethereum/go-ethereum/crypto" "github.com/golang/protobuf/proto" "github.com/spf13/cobra" @@ -80,7 +80,7 @@ func NewOracleClaim(cmd *cobra.Command, args []string) { return } - temp, _ := btcec_secp256k1.PrivKeyFromBytes(btcec_secp256k1.S256(), privateKey.Bytes()) + temp, _ := btcec_secp256k1.PrivKeyFromBytes(privateKey.Bytes()) privatekey4chain33Ecdsa := temp.ToECDSA() nonceBytes := big.NewInt(nonce).Bytes() diff --git a/plugin/dapp/bridgevmxgo/boss4x/chain33/offline/multisignTransfer.go b/plugin/dapp/bridgevmxgo/boss4x/chain33/offline/multisignTransfer.go index e8bba73d6d..856447ca21 100644 --- a/plugin/dapp/bridgevmxgo/boss4x/chain33/offline/multisignTransfer.go +++ b/plugin/dapp/bridgevmxgo/boss4x/chain33/offline/multisignTransfer.go @@ -16,7 +16,7 @@ import ( relayerutils "github.com/33cn/plugin/plugin/dapp/cross2eth/ebrelayer/utils" evmAbi "github.com/33cn/plugin/plugin/dapp/evm/executor/abi" "github.com/33cn/plugin/plugin/dapp/evm/executor/vm/common/math" - btcecsecp256k1 "github.com/btcsuite/btcd/btcec" + btcecsecp256k1 "github.com/btcsuite/btcd/btcec/v2" ethSecp256k1 "github.com/ethereum/go-ethereum/crypto/secp256k1" "github.com/spf13/cobra" ) @@ -179,7 +179,7 @@ func MultisignTransfer(cmd *cobra.Command, _ []string) { fmt.Println("evmAbi.Pack(parameter, erc20.ERC20ABI, false)", "Failed", err.Error()) return } - temp, _ := btcecsecp256k1.PrivKeyFromBytes(btcecsecp256k1.S256(), ownerPrivateKey.Bytes()) + temp, _ := btcecsecp256k1.PrivKeyFromBytes(ownerPrivateKey.Bytes()) privateKey4chain33Ecdsa := temp.ToECDSA() sig, err := ethSecp256k1.Sign(contentHash, math.PaddedBigBytes(privateKey4chain33Ecdsa.D, 32)) diff --git a/plugin/dapp/cross2eth/boss4x/chain33/offline/multisignTransfer.go b/plugin/dapp/cross2eth/boss4x/chain33/offline/multisignTransfer.go index 67a01d329e..fc697527db 100644 --- a/plugin/dapp/cross2eth/boss4x/chain33/offline/multisignTransfer.go +++ b/plugin/dapp/cross2eth/boss4x/chain33/offline/multisignTransfer.go @@ -14,7 +14,7 @@ import ( relayerutils "github.com/33cn/plugin/plugin/dapp/cross2eth/ebrelayer/utils" evmAbi "github.com/33cn/plugin/plugin/dapp/evm/executor/abi" "github.com/33cn/plugin/plugin/dapp/evm/executor/vm/common/math" - btcecsecp256k1 "github.com/btcsuite/btcd/btcec" + btcecsecp256k1 "github.com/btcsuite/btcd/btcec/v2" ethSecp256k1 "github.com/ethereum/go-ethereum/crypto/secp256k1" "github.com/spf13/cobra" ) @@ -177,7 +177,7 @@ func MultisignTransfer(cmd *cobra.Command, _ []string) { fmt.Println("evmAbi.Pack(parameter, erc20.ERC20ABI, false)", "Failed", err.Error()) return } - temp, _ := btcecsecp256k1.PrivKeyFromBytes(btcecsecp256k1.S256(), ownerPrivateKey.Bytes()) + temp, _ := btcecsecp256k1.PrivKeyFromBytes(ownerPrivateKey.Bytes()) privateKey4chain33Ecdsa := temp.ToECDSA() sig, err := ethSecp256k1.Sign(contentHash, math.PaddedBigBytes(privateKey4chain33Ecdsa.D, 32)) diff --git a/plugin/dapp/cross2eth/boss4x/keyManager.go b/plugin/dapp/cross2eth/boss4x/keyManager.go index 77a0dcec2c..a3f876f76c 100644 --- a/plugin/dapp/cross2eth/boss4x/keyManager.go +++ b/plugin/dapp/cross2eth/boss4x/keyManager.go @@ -12,7 +12,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" chain33Common "github.com/33cn/chain33/common" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/ethereum/go-ethereum/common" "github.com/spf13/cobra" ) @@ -53,7 +53,7 @@ func generareChain33KeyCmd() *cobra.Command { func generareChain33Key(cmd *cobra.Command, _ []string) { - privateKey, err := btcec.NewPrivateKey(btcec.S256()) + privateKey, err := btcec.NewPrivateKey() if nil != err { fmt.Println("Failed to generate private key for chain33" + err.Error()) return @@ -84,7 +84,7 @@ func showChain33Key(cmd *cobra.Command, _ []string) { fmt.Println("invalid priv key length", len(privateKeySlice)) return } - _, pubKey := btcec.PrivKeyFromBytes(btcec.S256(), privateKeySlice) + _, pubKey := btcec.PrivKeyFromBytes(privateKeySlice) uncompressedKey := pubKey.SerializeUncompressed() uncompressedKey = uncompressedKey[1:] @@ -143,7 +143,7 @@ func showEtheremKey(cmd *cobra.Command, _ []string) { return } - _, pubKey := btcec.PrivKeyFromBytes(crypto.S256(), privateKeySlice) + _, pubKey := btcec.PrivKeyFromBytes(privateKeySlice) uncompressedKey := pubKey.SerializeUncompressed() uncompressedKey = uncompressedKey[1:] compressedKey := pubKey.SerializeCompressed() diff --git a/plugin/dapp/cross2eth/boss4x/sm2.go b/plugin/dapp/cross2eth/boss4x/sm2.go index d1a242e1e0..d3f849108d 100644 --- a/plugin/dapp/cross2eth/boss4x/sm2.go +++ b/plugin/dapp/cross2eth/boss4x/sm2.go @@ -4,8 +4,7 @@ import ( "fmt" "math/big" - "github.com/btcsuite/btcd/btcec" - "github.com/ethereum/go-ethereum/crypto" + "github.com/btcsuite/btcd/btcec/v2" chain33Common "github.com/33cn/chain33/common" "github.com/33cn/chain33/system/crypto/sm2" @@ -213,7 +212,7 @@ func encryptWithSm2(cmd *cobra.Command, args []string) { sm4Key = sm4Key[1:] //第三步,计算secp256k1对应的公钥,非压缩 - _, pubKey := btcec.PrivKeyFromBytes(crypto.S256(), privateKeySlice) + _, pubKey := btcec.PrivKeyFromBytes(privateKeySlice) uncompressedKey := pubKey.SerializeUncompressed() uncompressedKey = uncompressedKey[1:] diff --git a/plugin/dapp/cross2eth/ebrelayer/relayer/chain33/account.go b/plugin/dapp/cross2eth/ebrelayer/relayer/chain33/account.go index 4e6bf348b9..cfc9122eb2 100644 --- a/plugin/dapp/cross2eth/ebrelayer/relayer/chain33/account.go +++ b/plugin/dapp/cross2eth/ebrelayer/relayer/chain33/account.go @@ -10,7 +10,7 @@ import ( chain33Types "github.com/33cn/chain33/types" wcom "github.com/33cn/chain33/wallet/common" x2ethTypes "github.com/33cn/plugin/plugin/dapp/cross2eth/ebrelayer/types" - btcec_secp256k1 "github.com/btcsuite/btcd/btcec" + btcec_secp256k1 "github.com/btcsuite/btcd/btcec/v2" "github.com/ethereum/go-ethereum/crypto" ) @@ -64,7 +64,7 @@ func (chain33Relayer *Relayer4Chain33) ImportPrivateKey(passphrase, privateKeySt chain33Relayer.rwLock.Lock() chain33Relayer.privateKey4Chain33 = priKey - temp, _ := btcec_secp256k1.PrivKeyFromBytes(btcec_secp256k1.S256(), priKey.Bytes()) + temp, _ := btcec_secp256k1.PrivKeyFromBytes(priKey.Bytes()) chain33Relayer.privateKey4Chain33_ecdsa = temp.ToECDSA() chain33Relayer.rwLock.Unlock() chain33Relayer.unlockChan <- start diff --git a/plugin/dapp/cross2eth/ebrelayer/relayer/chain33/tx.go b/plugin/dapp/cross2eth/ebrelayer/relayer/chain33/tx.go index b71a16c327..9e7b9154f5 100644 --- a/plugin/dapp/cross2eth/ebrelayer/relayer/chain33/tx.go +++ b/plugin/dapp/cross2eth/ebrelayer/relayer/chain33/tx.go @@ -14,7 +14,7 @@ import ( "github.com/33cn/plugin/plugin/dapp/cross2eth/ebrelayer/relayer/events" erc20 "github.com/33cn/plugin/plugin/dapp/cross2eth/contracts/erc20/generated" - btcec_secp256k1 "github.com/btcsuite/btcd/btcec" + btcec_secp256k1 "github.com/btcsuite/btcd/btcec/v2" "github.com/33cn/plugin/plugin/dapp/cross2eth/ebrelayer/utils" @@ -520,7 +520,7 @@ func safeTransfer(ownerPrivateKeyStr, mulSign, chainName, rpcURL, receiver, toke if nil != err { return "", err } - temp, _ := btcec_secp256k1.PrivKeyFromBytes(btcec_secp256k1.S256(), ownerPrivateKey.Bytes()) + temp, _ := btcec_secp256k1.PrivKeyFromBytes(ownerPrivateKey.Bytes()) privateKey4Chain33_ecdsa := temp.ToECDSA() sig, err := ethSecp256k1.Sign(contentHash, math.PaddedBigBytes(privateKey4Chain33_ecdsa.D, 32)) diff --git a/plugin/dapp/dposvote/commands/vote.go b/plugin/dapp/dposvote/commands/vote.go index fe6a8c2be0..e898db672e 100644 --- a/plugin/dapp/dposvote/commands/vote.go +++ b/plugin/dapp/dposvote/commands/vote.go @@ -6,7 +6,7 @@ package commands import ( "bytes" - "crypto/ecdsa" + "encoding/hex" "encoding/json" "fmt" @@ -23,7 +23,7 @@ import ( "github.com/33cn/chain33/types" ttypes "github.com/33cn/plugin/plugin/consensus/dpos/types" dty "github.com/33cn/plugin/plugin/dapp/dposvote/types" - secp256k1 "github.com/btcsuite/btcd/btcec" + secp256k1 "github.com/btcsuite/btcd/btcec/v2" "github.com/spf13/cobra" ) @@ -720,12 +720,12 @@ func verify(cmd *cobra.Command, args []string) { return } - pubKey, err := secp256k1.ParsePubKey(bKey, secp256k1.S256()) + pubKey, err := secp256k1.ParsePubKey(bKey) if err != nil { fmt.Println("vrf Verify failed: ", err) return } - vrfPub := &vrf.PublicKey{PublicKey: (*ecdsa.PublicKey)(pubKey)} + vrfPub := &vrf.PublicKey{PublicKey: pubKey.ToECDSA()} vrfHash, err := vrfPub.ProofToHash(m, p) if err != nil { fmt.Println("vrf Verify failed: ", err) @@ -782,8 +782,8 @@ func evaluate(cmd *cobra.Command, args []string) { return } - privKey, _ := secp256k1.PrivKeyFromBytes(secp256k1.S256(), bKey) - vrfPriv := &vrf.PrivateKey{PrivateKey: (*ecdsa.PrivateKey)(privKey)} + privKey, _ := secp256k1.PrivKeyFromBytes(bKey) + vrfPriv := &vrf.PrivateKey{PrivateKey: privKey.ToECDSA()} vrfHash, vrfProof := vrfPriv.Evaluate(m) fmt.Println("vrf evaluate:") fmt.Println("input:", data) diff --git a/plugin/dapp/evm/cmd/ci2/chain33.proxyminer.toml b/plugin/dapp/evm/cmd/ci2/chain33.proxyminer.toml index 9e73434123..2486d18a42 100644 --- a/plugin/dapp/evm/cmd/ci2/chain33.proxyminer.toml +++ b/plugin/dapp/evm/cmd/ci2/chain33.proxyminer.toml @@ -732,6 +732,10 @@ Enable=0 [fork.sub.wasm] Enable=0 +[fork.sub.lightclient] +Enable=-1 +[fork.sub.rgbx] +Enable=-1 [fork.sub.store-kvmvccmavl] ForkKvmvccmavl=1870000 diff --git a/plugin/dapp/evm/executor/query.go b/plugin/dapp/evm/executor/query.go index 8846f5e417..7adda01670 100644 --- a/plugin/dapp/evm/executor/query.go +++ b/plugin/dapp/evm/executor/query.go @@ -101,25 +101,27 @@ func (evm *EVMExecutor) quick_estimateGas(req *evmtypes.EstimateEVMGasReq) (type } result := &evmtypes.EstimateEVMGasResp{} - conf := types.ConfSub(evm.GetAPI().GetConfig(), evmtypes.ExecutorName) - gasmultiple, err := conf.G("gasmultiple") - if err == nil { - multiple, ok := gasmultiple.(float64) - if ok { - if multiple < 1.3 { //quick gas 默认增加30% - multiple = 1.3 - } - result.Gas = uint64(float64(result.Gas) * multiple) - } - - } else { - result.Gas = callData.UsedGas + uint64(float64(callData.UsedGas)*0.3) - } + gasmultiple, _ := conf.G("gasmultiple") + result.Gas = quickEstimateGasValue(callData.UsedGas, gasmultiple) log.Info("quick_estimateGas", "from:", from, "gasused:", callData.UsedGas, "return Gas:", result.Gas, "gasmultiple:", gasmultiple) return result, nil } +func quickEstimateGasValue(usedGas uint64, gasmultiple interface{}) uint64 { + multiple := 1.3 // quick gas 默认增加30%,最低不低于1.0倍 + switch v := gasmultiple.(type) { + case float64: + multiple = v + case int64: + multiple = float64(v) + } + if multiple < 1.0 { + multiple = 1.0 + } + return uint64(float64(usedGas) * multiple) +} + // Query_EstimateGas 此方法用来估算合约消耗的Gas,不能修改原有执行器的状态数据 func (evm *EVMExecutor) Query_EstimateGas(req *evmtypes.EstimateEVMGasReq) (types.Message, error) { conf := types.ConfSub(evm.GetAPI().GetConfig(), evmtypes.ExecutorName) diff --git a/plugin/dapp/evm/executor/query_test.go b/plugin/dapp/evm/executor/query_test.go index 00fc4049c7..d4afaf2701 100644 --- a/plugin/dapp/evm/executor/query_test.go +++ b/plugin/dapp/evm/executor/query_test.go @@ -7,6 +7,7 @@ import ( "strings" "testing" + "github.com/BurntSushi/toml" apimock "github.com/33cn/chain33/client/mocks" "github.com/33cn/chain33/common/address" dbm "github.com/33cn/chain33/common/db" @@ -238,3 +239,61 @@ func TestEVMExecutor_Check(t *testing.T) { assert.Equal(t, nil, err) } + +func TestQuickEstimateGasValue(t *testing.T) { + usedGas := uint64(1000) + testCases := []struct { + name string + toml string + expect uint64 + }{ + { + name: "nil default", + toml: ``, + expect: 1300, + }, + { + name: "float from toml", + toml: `gasmultiple = 2.0`, + expect: 2000, + }, + { + name: "int from toml", + toml: `gasmultiple = 2`, + expect: 2000, + }, + { + name: "below 1.0 clamped", + toml: `gasmultiple = 0.5`, + expect: 1000, + }, + { + name: "exactly 1.0", + toml: `gasmultiple = 1.0`, + expect: 1000, + }, + { + name: "invalid string fallback", + toml: `gasmultiple = "invalid"`, + expect: 1300, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + got := quickEstimateGasValue(usedGas, gasMultipleFromTOML(t, tc.toml)) + assert.Equal(t, got, tc.expect) + }) + } +} + +func gasMultipleFromTOML(t *testing.T, cfg string) interface{} { + t.Helper() + var parsed struct { + Gasmultiple interface{} `toml:"gasmultiple"` + } + if _, err := toml.Decode(cfg, &parsed); err != nil { + t.Fatalf("decode toml failed: %v", err) + } + return parsed.Gasmultiple +} diff --git a/plugin/dapp/evm/executor/vm/common/crypto/crypto.go b/plugin/dapp/evm/executor/vm/common/crypto/crypto.go index 82646653a8..af24e11dca 100644 --- a/plugin/dapp/evm/executor/vm/common/crypto/crypto.go +++ b/plugin/dapp/evm/executor/vm/common/crypto/crypto.go @@ -6,12 +6,13 @@ package crypto import ( "crypto/ecdsa" + "math/big" ethCrypto "github.com/ethereum/go-ethereum/crypto" "github.com/33cn/plugin/plugin/dapp/evm/executor/vm/common" - "github.com/btcsuite/btcd/btcec" + ecdsa2 "github.com/btcsuite/btcd/btcec/v2/ecdsa" "golang.org/x/crypto/sha3" ) @@ -43,8 +44,8 @@ func SigToPub(hash, sig []byte) (*ecdsa.PublicKey, error) { btcsig[0] = sig[64] + 27 copy(btcsig[1:], sig) - pub, _, err := btcec.RecoverCompact(btcec.S256(), btcsig, hash) - return (*ecdsa.PublicKey)(pub), err + pub, _, err := ecdsa2.RecoverCompact(btcsig, hash) + return pub.ToECDSA(), err } // Keccak256 计算并返回 Keccak256 哈希 diff --git a/plugin/dapp/evm/executor/vm/runtime/log_address_integration_test.go b/plugin/dapp/evm/executor/vm/runtime/log_address_integration_test.go new file mode 100644 index 0000000000..63e6a50f34 --- /dev/null +++ b/plugin/dapp/evm/executor/vm/runtime/log_address_integration_test.go @@ -0,0 +1,101 @@ +package runtime + +import ( + "bytes" + "math/big" + "testing" + + apimock "github.com/33cn/chain33/client/mocks" + ctypes "github.com/33cn/chain33/types" + "github.com/33cn/chain33/util" + vmcommon "github.com/33cn/plugin/plugin/dapp/evm/executor/vm/common" + "github.com/33cn/plugin/plugin/dapp/evm/executor/vm/state" + evmtypes "github.com/33cn/plugin/plugin/dapp/evm/types" + "github.com/stretchr/testify/require" +) + +func TestCallContractEmitsLogWithCalleeAddress(t *testing.T) { + cfg := ctypes.NewChain33Config(ctypes.GetDefaultCfgstring()) + api := new(apimock.QueueProtocolAPI) + api.On("GetConfig").Return(cfg) + + dbDir, stateDB, localDB := util.CreateTestDB() + defer util.CloseTestDB(dbDir, stateDB) + + // Use a height above ForkEVMState so TyLogEVMEventData is exported from snapshots. + const blockHeight = int64(700000) + mdb := state.NewMemoryStateDB(stateDB, localDB, nil, blockHeight, api) + txHash := vmcommon.BytesToHash([]byte("tx-a-call-b-log")) + mdb.Prepare(txHash, 0) + + callerAddr := vmcommon.BytesToAddress([]byte{0x01}) + contractA := vmcommon.BytesToAddress([]byte{0xa1}) + contractB := vmcommon.BytesToAddress([]byte{0xb1}) + logTopic := vmcommon.BytesToHash([]byte("callee-b-topic")) + + mdb.CreateAccount(contractA.String(), callerAddr.String(), "evm.A", "A") + mdb.CreateAccount(contractB.String(), callerAddr.String(), "evm.B", "B") + mdb.SetCode(contractA.String(), buildCallCode(contractB)) + mdb.SetCode(contractB.String(), buildEmitLogCode(logTopic)) + + evm := NewEVM(Context{ + CanTransfer: func(state.EVMStateDB, vmcommon.Address, uint64) bool { return true }, + Transfer: func(state.EVMStateDB, vmcommon.Address, vmcommon.Address, uint64) bool { return true }, + GetHash: func(uint64) vmcommon.Hash { return vmcommon.Hash{} }, + BlockNumber: big.NewInt(blockHeight), + }, mdb, Config{}, cfg) + + var err error + _, _, _, err = evm.Call(AccountRef(callerAddr), contractA, nil, 5_000_000, 0) + require.NoError(t, err) + + ver := mdb.GetLastSnapshot() + require.NotNil(t, ver) + _, logs := mdb.GetChangedData(ver.GetID()) + + found := false + for _, lg := range logs { + if lg.Ty != evmtypes.TyLogEVMEventData { + continue + } + var evmLog ctypes.EVMLog + err = ctypes.Decode(lg.Log, &evmLog) + require.NoError(t, err) + + if len(evmLog.GetTopic()) == 1 && bytes.Equal(evmLog.GetTopic()[0], logTopic.Bytes()) { + found = true + require.Equal(t, contractB.String(), evmLog.GetAddress()) + } + } + require.True(t, found, "expected at least one event log emitted by contract B") +} + +func buildCallCode(callee vmcommon.Address) []byte { + code := []byte{ + 0x60, 0x00, // retSize + 0x60, 0x00, // retOffset + 0x60, 0x00, // inSize + 0x60, 0x00, // inOffset + 0x60, 0x00, // value + 0x73, // PUSH20 + } + code = append(code, callee.Bytes()...) + code = append(code, + 0x61, 0xff, 0xff, // gas + 0xf1, // CALL + 0x00, // STOP + ) + return code +} + +func buildEmitLogCode(topic vmcommon.Hash) []byte { + code := []byte{0x7f} // PUSH32 + code = append(code, topic.Bytes()...) + code = append(code, + 0x60, 0x00, // mSize + 0x60, 0x00, // mStart + 0xa1, // LOG1 + 0x00, // STOP + ) + return code +} diff --git a/plugin/dapp/evm/executor/vm/state/statedb.go b/plugin/dapp/evm/executor/vm/state/statedb.go index 34efd03476..d2907c5765 100644 --- a/plugin/dapp/evm/executor/vm/state/statedb.go +++ b/plugin/dapp/evm/executor/vm/state/statedb.go @@ -685,10 +685,13 @@ func (mdb *MemoryStateDB) mergeResult(one, two *types.Receipt) (ret *types.Recei // 生成对应的日志信息,目前这些生成的日志信息会在合约执行后打印到日志文件中 func (mdb *MemoryStateDB) AddLog(log *model.ContractLog) { newEvmLog := &types.EVMLog{ - Topic: [][]byte{log.Topics[0].Bytes()}, - Data: log.Data, + Data: log.Data, + Address: log.Address.String(), } + logTopic := "" if len(log.Topics) > 0 { + logTopic = log.Topics[0].Hex() + newEvmLog.Topic = append(newEvmLog.Topic, log.Topics[0].Bytes()) for i := 1; i < len(log.Topics); i++ { newEvmLog.Topic = append(newEvmLog.Topic, log.Topics[i].Bytes()) } @@ -707,7 +710,7 @@ func (mdb *MemoryStateDB) AddLog(log *model.ContractLog) { mdb.logs[mdb.txHash] = append(mdb.logs[mdb.txHash], log) mdb.logSize++ log15.Info("MemoryStateDB::AddLog", "txhash", mdb.txHash.Hex(), "blockHeight", mdb.blockHeight, "txIndex", mdb.txIndex, - "mdb.logSize", mdb.logSize, "topic", log.Topics[0].Hex()) + "mdb.logSize", mdb.logSize, "topic", logTopic) } // AddPreimage 存储sha3指令对应的数据 diff --git a/plugin/dapp/evm/executor/vm/state/statedb_test.go b/plugin/dapp/evm/executor/vm/state/statedb_test.go new file mode 100644 index 0000000000..b7b377d71e --- /dev/null +++ b/plugin/dapp/evm/executor/vm/state/statedb_test.go @@ -0,0 +1,96 @@ +package state + +import ( + "testing" + + ctypes "github.com/33cn/chain33/types" + "github.com/33cn/plugin/plugin/dapp/evm/executor/vm/common" + "github.com/33cn/plugin/plugin/dapp/evm/executor/vm/model" +) + +func TestMemoryStateDBAddLogStoresAddressAndDefaultsRemoved(t *testing.T) { + txHash := common.BytesToHash([]byte("tx-log-address")) + contractAddr := common.BytesToAddress([]byte{0x11, 0x22, 0x33}) + topic := common.BytesToHash([]byte{0xaa}) + + mdb := &MemoryStateDB{ + logs: make(map[common.Hash][]*model.ContractLog), + txHash: txHash, + } + mdb.currentVer = &Snapshot{id: 1, statedb: mdb} + + mdb.AddLog(&model.ContractLog{ + Address: contractAddr, + Topics: []common.Hash{topic}, + Data: []byte{0x01, 0x02}, + }) + + if got := len(mdb.logs[txHash]); got != 1 { + t.Fatalf("expected one in-memory contract log, got %d", got) + } + if mdb.logSize != 1 { + t.Fatalf("expected logSize to be 1, got %d", mdb.logSize) + } + if got := len(mdb.currentVer.entries); got != 1 { + t.Fatalf("expected one snapshot entry, got %d", got) + } + + change, ok := mdb.currentVer.entries[0].(addLogChange) + if !ok { + t.Fatalf("expected snapshot entry type addLogChange, got %T", mdb.currentVer.entries[0]) + } + if len(change.logs) != 1 { + t.Fatalf("expected one receipt log in addLogChange, got %d", len(change.logs)) + } + + var evmLog ctypes.EVMLog + if err := ctypes.Decode(change.logs[0].Log, &evmLog); err != nil { + t.Fatalf("decode evm log failed: %v", err) + } + + if evmLog.GetAddress() != contractAddr.String() { + t.Fatalf("expected address %s, got %s", contractAddr.String(), evmLog.GetAddress()) + } + if evmLog.GetRemoved() { + t.Fatalf("expected removed default false, got true") + } + if len(evmLog.GetTopic()) != 1 { + t.Fatalf("expected one topic, got %d", len(evmLog.GetTopic())) + } +} + +func TestMemoryStateDBAddLogWithNoTopics(t *testing.T) { + txHash := common.BytesToHash([]byte("tx-log-no-topic")) + contractAddr := common.BytesToAddress([]byte{0x44, 0x55, 0x66}) + + mdb := &MemoryStateDB{ + logs: make(map[common.Hash][]*model.ContractLog), + txHash: txHash, + } + mdb.currentVer = &Snapshot{id: 1, statedb: mdb} + + mdb.AddLog(&model.ContractLog{ + Address: contractAddr, + Data: []byte{0x09}, + }) + + if got := len(mdb.currentVer.entries); got != 1 { + t.Fatalf("expected one snapshot entry, got %d", got) + } + change, ok := mdb.currentVer.entries[0].(addLogChange) + if !ok { + t.Fatalf("expected snapshot entry type addLogChange, got %T", mdb.currentVer.entries[0]) + } + + var evmLog ctypes.EVMLog + if err := ctypes.Decode(change.logs[0].Log, &evmLog); err != nil { + t.Fatalf("decode evm log failed: %v", err) + } + + if len(evmLog.GetTopic()) != 0 { + t.Fatalf("expected zero topics for LOG0-style event, got %d", len(evmLog.GetTopic())) + } + if evmLog.GetAddress() != contractAddr.String() { + t.Fatalf("expected address %s, got %s", contractAddr.String(), evmLog.GetAddress()) + } +} diff --git a/plugin/dapp/init/init.go b/plugin/dapp/init/init.go index 211d3e79ba..b83e432cde 100644 --- a/plugin/dapp/init/init.go +++ b/plugin/dapp/init/init.go @@ -17,25 +17,27 @@ import ( _ "github.com/33cn/plugin/plugin/dapp/hashlock" //auto gen _ "github.com/33cn/plugin/plugin/dapp/issuance" //auto gen _ "github.com/33cn/plugin/plugin/dapp/js" //auto gen - _ "github.com/33cn/plugin/plugin/dapp/lottery" //auto gen - _ "github.com/33cn/plugin/plugin/dapp/mix" //auto gen - _ "github.com/33cn/plugin/plugin/dapp/multisig" //auto gen - _ "github.com/33cn/plugin/plugin/dapp/norm" //auto gen - _ "github.com/33cn/plugin/plugin/dapp/oracle" //auto gen - _ "github.com/33cn/plugin/plugin/dapp/paracross" //auto gen - _ "github.com/33cn/plugin/plugin/dapp/pokerbull" //auto gen - _ "github.com/33cn/plugin/plugin/dapp/privacy" //auto gen - _ "github.com/33cn/plugin/plugin/dapp/qbftNode" //auto gen - _ "github.com/33cn/plugin/plugin/dapp/relay" //auto gen - _ "github.com/33cn/plugin/plugin/dapp/retrieve" //auto gen - _ "github.com/33cn/plugin/plugin/dapp/rollup" //auto gen - _ "github.com/33cn/plugin/plugin/dapp/storage" //auto gen - _ "github.com/33cn/plugin/plugin/dapp/ticket" //auto gen - _ "github.com/33cn/plugin/plugin/dapp/token" //auto gen - _ "github.com/33cn/plugin/plugin/dapp/trade" //auto gen - _ "github.com/33cn/plugin/plugin/dapp/unfreeze" //auto gen - _ "github.com/33cn/plugin/plugin/dapp/valnode" //auto gen - _ "github.com/33cn/plugin/plugin/dapp/vote" //auto gen - _ "github.com/33cn/plugin/plugin/dapp/wasm" //auto gen - _ "github.com/33cn/plugin/plugin/dapp/zksync" //auto gen + _ "github.com/33cn/plugin/plugin/dapp/lightclient" + _ "github.com/33cn/plugin/plugin/dapp/lottery" //auto gen + _ "github.com/33cn/plugin/plugin/dapp/mix" //auto gen + _ "github.com/33cn/plugin/plugin/dapp/multisig" //auto gen + _ "github.com/33cn/plugin/plugin/dapp/norm" //auto gen + _ "github.com/33cn/plugin/plugin/dapp/oracle" //auto gen + _ "github.com/33cn/plugin/plugin/dapp/paracross" //auto gen + _ "github.com/33cn/plugin/plugin/dapp/pokerbull" //auto gen + _ "github.com/33cn/plugin/plugin/dapp/privacy" //auto gen + _ "github.com/33cn/plugin/plugin/dapp/qbftNode" //auto gen + _ "github.com/33cn/plugin/plugin/dapp/relay" //auto gen + _ "github.com/33cn/plugin/plugin/dapp/retrieve" //auto gen + _ "github.com/33cn/plugin/plugin/dapp/rgbx" + _ "github.com/33cn/plugin/plugin/dapp/rollup" //auto gen + _ "github.com/33cn/plugin/plugin/dapp/storage" //auto gen + _ "github.com/33cn/plugin/plugin/dapp/ticket" //auto gen + _ "github.com/33cn/plugin/plugin/dapp/token" //auto gen + _ "github.com/33cn/plugin/plugin/dapp/trade" //auto gen + _ "github.com/33cn/plugin/plugin/dapp/unfreeze" //auto gen + _ "github.com/33cn/plugin/plugin/dapp/valnode" //auto gen + _ "github.com/33cn/plugin/plugin/dapp/vote" //auto gen + _ "github.com/33cn/plugin/plugin/dapp/wasm" //auto gen + _ "github.com/33cn/plugin/plugin/dapp/zksync" //auto gen ) diff --git a/plugin/dapp/lightclient/cmd/Makefile b/plugin/dapp/lightclient/cmd/Makefile new file mode 100644 index 0000000000..8ea1cd1096 --- /dev/null +++ b/plugin/dapp/lightclient/cmd/Makefile @@ -0,0 +1,2 @@ +all: + bash build.sh $(OUT) $(FLAG) diff --git a/plugin/dapp/lightclient/cmd/build.sh b/plugin/dapp/lightclient/cmd/build.sh new file mode 100644 index 0000000000..cbcf75fe1f --- /dev/null +++ b/plugin/dapp/lightclient/cmd/build.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# 官方ci集成脚本 +strpwd=$(pwd) +strcmd=${strpwd##*dapp/} +strapp=${strcmd%/cmd*} + +OUT_DIR="${1}/$strapp" +#FLAG=$2 + +mkdir -p "${OUT_DIR}" +cp ./build/* "${OUT_DIR}" diff --git a/plugin/dapp/lightclient/commands/commands.go b/plugin/dapp/lightclient/commands/commands.go new file mode 100644 index 0000000000..0d5f32a5c7 --- /dev/null +++ b/plugin/dapp/lightclient/commands/commands.go @@ -0,0 +1,97 @@ +/*Package commands implement dapp client commands*/ +package commands + +import ( + jsonrpc "github.com/33cn/chain33/rpc/jsonclient" + rpctypes "github.com/33cn/chain33/rpc/types" + "github.com/33cn/chain33/types" + ltypes "github.com/33cn/plugin/plugin/dapp/lightclient/lighttypes" + "github.com/spf13/cobra" +) + +/* + * 实现合约对应客户端 + */ + +// Cmd lightclient client command +func Cmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "lightcli", + Short: "lightclient command", + Args: cobra.MinimumNArgs(1), + } + cmd.AddCommand( + btcCommand(), + ) + return cmd +} + +func btcCommand() *cobra.Command { + + cmd := &cobra.Command{ + Use: "btc", + Short: "btc lightclient command", + Args: cobra.MinimumNArgs(1), + } + cmd.AddCommand( + btcLastHeaderCMD(), + btcHeaderCMD(), + ) + return cmd +} + +func btcLastHeaderCMD() *cobra.Command { + cmd := &cobra.Command{ + Use: "last", + Short: "show btc last header", + Run: getBtcLast, + } + + return cmd +} + +func getBtcLast(cmd *cobra.Command, args []string) { + + params := &types.ReqNil{} + header := <ypes.BtcHeader{} + sendQueryRPC(cmd, "GetBtcLastHeader", params, header) +} + +func btcHeaderCMD() *cobra.Command { + cmd := &cobra.Command{ + Use: "header", + Short: "show btc header info", + Run: getBtcHeader, + } + cmd.Flags().Int64P("height", "t", 0, "btc height") + markRequired(cmd, "height") + + return cmd +} + +func getBtcHeader(cmd *cobra.Command, args []string) { + + height, _ := cmd.Flags().GetInt64("height") + params := &types.ReqInt{Height: height} + header := <ypes.BtcHeader{} + sendQueryRPC(cmd, "GetBtcHeader", params, header) +} + +func sendQueryRPC(cmd *cobra.Command, funcName string, req, reply types.Message) { + rpcAddr, _ := cmd.Flags().GetString("rpc_laddr") + payLoad := types.MustPBToJSON(req) + query := &rpctypes.Query4Jrpc{ + Execer: ltypes.LightclientX, + FuncName: funcName, + Payload: payLoad, + } + + ctx := jsonrpc.NewRPCCtx(rpcAddr, "Chain33.Query", query, reply) + ctx.Run() +} + +func markRequired(cmd *cobra.Command, params ...string) { + for _, param := range params { + _ = cmd.MarkFlagRequired(param) + } +} diff --git a/plugin/dapp/lightclient/executor/btcd_validate.go b/plugin/dapp/lightclient/executor/btcd_validate.go new file mode 100644 index 0000000000..f587d0eb87 --- /dev/null +++ b/plugin/dapp/lightclient/executor/btcd_validate.go @@ -0,0 +1,136 @@ +package executor + +import ( + "time" + + dbm "github.com/33cn/chain33/common/db" + ltypes "github.com/33cn/plugin/plugin/dapp/lightclient/lighttypes" + "github.com/btcsuite/btcd/blockchain" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" +) + +type btcChainContext struct { + params *chaincfg.Params +} + +func newBtcChainContext(params *chaincfg.Params) *btcChainContext { + return &btcChainContext{params: params} +} + +func (c *btcChainContext) ChainParams() *chaincfg.Params { + return c.params +} + +func (c *btcChainContext) BlocksPerRetarget() int32 { + return int32(c.params.TargetTimespan / c.params.TargetTimePerBlock) +} + +func (c *btcChainContext) MinRetargetTimespan() int64 { + target := int64(c.params.TargetTimespan / time.Second) + return target / c.params.RetargetAdjustmentFactor +} + +func (c *btcChainContext) MaxRetargetTimespan() int64 { + target := int64(c.params.TargetTimespan / time.Second) + return target * c.params.RetargetAdjustmentFactor +} + +func (c *btcChainContext) VerifyCheckpoint(_ int32, _ *chainhash.Hash) bool { + return true +} + +func (c *btcChainContext) FindPreviousCheckpoint() (blockchain.HeaderCtx, error) { + return nil, nil +} + +type btcHeaderContext struct { + header *ltypes.BtcHeader + parent blockchain.HeaderCtx + localDB dbm.KV + ancestor map[uint64]blockchain.HeaderCtx +} + +func newBtcHeaderContext(header *ltypes.BtcHeader, parent blockchain.HeaderCtx, localDB dbm.KV) *btcHeaderContext { + ctx := &btcHeaderContext{ + header: header, + parent: parent, + localDB: localDB, + ancestor: make(map[uint64]blockchain.HeaderCtx), + } + ctx.ancestor[header.GetHeight()] = ctx + return ctx +} + +func (h *btcHeaderContext) Height() int32 { + return int32(h.header.GetHeight()) +} + +func (h *btcHeaderContext) Bits() uint32 { + return uint32(h.header.GetBits()) +} + +func (h *btcHeaderContext) Timestamp() int64 { + return h.header.GetTime() +} + +func (h *btcHeaderContext) Parent() blockchain.HeaderCtx { + return h.parent +} + +func (h *btcHeaderContext) RelativeAncestorCtx(distance int32) blockchain.HeaderCtx { + if distance <= 0 { + return h + } + + curr := blockchain.HeaderCtx(h) + for i := int32(0); i < distance && curr != nil; i++ { + curr = curr.Parent() + } + if curr != nil { + if ancestor, ok := curr.(*btcHeaderContext); ok { + h.ancestor[ancestor.header.GetHeight()] = ancestor + } + return curr + } + + targetHeight := int64(h.header.GetHeight()) - int64(distance) + if targetHeight < 0 { + return nil + } + if ancestor, ok := h.ancestor[uint64(targetHeight)]; ok { + return ancestor + } + if h.localDB == nil { + return nil + } + targetHeader, err := getBtcHeader(h.localDB, uint64(targetHeight)) + if err != nil { + return nil + } + ancestor := newBtcHeaderContext(targetHeader, nil, h.localDB) + h.ancestor[targetHeader.GetHeight()] = ancestor + return ancestor +} + +func toWireHeader(head *ltypes.BtcHeader) (*wire.BlockHeader, error) { + preHash, err := chainhash.NewHashFromStr(head.GetPreviousHash()) + if err != nil { + return nil, err + } + merkleRoot, err := chainhash.NewHashFromStr(head.GetMerkleRoot()) + if err != nil { + return nil, err + } + + h := &wire.BlockHeader{} + h.Version = int32(head.Version) + h.PrevBlock = *preHash + h.MerkleRoot = *merkleRoot + h.Bits = uint32(head.Bits) + h.Nonce = uint32(head.Nonce) + h.Timestamp = time.Unix(head.Time, 0) + + return h, nil +} diff --git a/plugin/dapp/lightclient/executor/checktx.go b/plugin/dapp/lightclient/executor/checktx.go new file mode 100644 index 0000000000..3dfa373783 --- /dev/null +++ b/plugin/dapp/lightclient/executor/checktx.go @@ -0,0 +1,142 @@ +package executor + +import ( + "encoding/hex" + "errors" + + "github.com/33cn/chain33/types" + ltypes "github.com/33cn/plugin/plugin/dapp/lightclient/lighttypes" + "github.com/btcsuite/btcd/blockchain" +) + +// CheckTx 实现自定义检验交易接口,供框架调用 +func (l *lightclient) CheckTx(tx *types.Transaction, index int) error { + + action := <ypes.LightClientAction{} + + err := types.Decode(tx.GetPayload(), action) + if err != nil { + elog.Error("CheckTx", "txHash", hex.EncodeToString(tx.Hash()), "Decode payload error", err) + return ErrDecodeAction + } + + if action.Ty == ltypes.TyBtcHeadersAction { + + err = l.checkBtcHeaders(tx, action.GetBtcHeaders()) + } else { + err = types.ErrActionNotSupport + } + if err != nil { + elog.Error("CheckTx", "txHash", hex.EncodeToString(tx.Hash()), "actionName", tx.ActionName(), "err", err) + } + return err +} + +func (l *lightclient) checkBtcHeaders(tx *types.Transaction, headers *ltypes.BtcHeaders) error { + + if lightCfg.CommitAddress != "" && tx.From() != lightCfg.CommitAddress { + + elog.Error("checkBtcHeaders", "from", tx.From(), "configAddress", lightCfg.CommitAddress) + return ErrIllegalCommitAddress + } + + prevHeader, err := getBtcLastHeader(l.GetStateDB()) + if err != nil { + elog.Error("checkBtcHeaders", "getBtcLastHeader err", err) + return ErrBtcGetLastHeader + } + + if len(headers.GetHeaders()) < 1 { + elog.Error("checkBtcHeaders", "err", "commit empty headers") + return types.ErrInvalidParam + } + + params := ltypes.GetBtcChainParams(lightCfg.BtcNetName) + chainCtx := newBtcChainContext(params) + timeSource := blockchain.NewMedianTime() + isBootstrap := prevHeader == nil || prevHeader.GetHash() == "" + var prevCtx blockchain.HeaderCtx + if !isBootstrap { + prevCtx = newBtcHeaderContext(prevHeader, nil, l.GetLocalDB()) + } + + for _, h := range headers.GetHeaders() { + + if h.GetHash() == "" { + elog.Error("checkBtcHeaders nil header") + return types.ErrInvalidParam + } + // 首次提交也要保证本批 headers 内部严格连续;仅首个header允许无前置锚点。 + if prevHeader.GetHash() != "" && (prevHeader.Height+1 != h.GetHeight() || prevHeader.Hash != h.PreviousHash) { + elog.Error("checkBtcHeaders", "prevHeight", prevHeader.Height, "prevHash", prevHeader.Hash, + "commitHeight", h.GetHeight(), "commitPrevHash", h.GetPreviousHash()) + return ErrBtcHeaderDisorder + } + + btcHeader, err := toWireHeader(h) + if err != nil { + elog.Error("checkBtcHeaders", "height", h.GetHeight(), "hash", h.GetHash(), "toWireHeader err", err) + return ErrToBtcWireHeader + } + hash := btcHeader.BlockHash() + if hash.String() != h.Hash { + elog.Error("checkBtcHeaders", "expectHash", hash, "height", h.GetHeight(), "hash", h.GetHash(), "err", err) + return ErrInvalidBtcBlockHash + } + + if err = blockchain.CheckBlockHeaderSanity(btcHeader, params.PowLimit, timeSource, blockchain.BFNone); err != nil { + elog.Error("checkBtcHeaders CheckBlockHeaderSanity", "height", h.GetHeight(), "hash", h.GetHash(), "err", err) + return mapBtcHeaderVerifyErr(err) + } + // 首次提交时(prevCtx=nil)无法获取前置上下文,仅首个header跳过context校验。 + if prevCtx != nil { + // 首次导入且刚好在难度调整点,如果缺少历史祖先,则仅校验sanity与顺序。 + if isBootstrap && !canValidateHeaderContext(prevCtx, chainCtx) { + prevHeader = h + prevCtx = newBtcHeaderContext(h, prevCtx, l.GetLocalDB()) + continue + } + if err = blockchain.CheckBlockHeaderContext(btcHeader, prevCtx, blockchain.BFNone, chainCtx, true); err != nil { + // regtest 批量导入场景下,连续快速挖块可能出现同秒时间戳。 + err = mapBtcHeaderVerifyErr(err) + if !(lightCfg.AllowRegtestTimeWarp && lightCfg.BtcNetName == "regtest" && err == ErrBtcHeaderTimeTooOld) { + elog.Error("checkBtcHeaders CheckBlockHeaderContext", "height", h.GetHeight(), "hash", h.GetHash(), "err", err) + return err + } + } + } + prevHeader = h + prevCtx = newBtcHeaderContext(h, prevCtx, l.GetLocalDB()) + + } + + return nil + +} + +func mapBtcHeaderVerifyErr(err error) error { + var ruleErr blockchain.RuleError + if errors.As(err, &ruleErr) { + switch ruleErr.ErrorCode { + case blockchain.ErrUnexpectedDifficulty: + return ErrBtcTargetBits + case blockchain.ErrTimeTooOld: + return ErrBtcHeaderTimeTooOld + case blockchain.ErrTimeTooNew: + return ErrBtcHeaderTimeTooNew + case blockchain.ErrInvalidTime: + return ErrBtcHeaderInvalidTime + } + } + return ErrBtcHeaderVerify +} + +func canValidateHeaderContext(prevCtx blockchain.HeaderCtx, chainCtx *btcChainContext) bool { + if prevCtx == nil { + return false + } + if (prevCtx.Height()+1)%chainCtx.BlocksPerRetarget() != 0 { + return true + } + return prevCtx.RelativeAncestorCtx(chainCtx.BlocksPerRetarget()-1) != nil +} diff --git a/plugin/dapp/lightclient/executor/errors.go b/plugin/dapp/lightclient/executor/errors.go new file mode 100644 index 0000000000..64647df494 --- /dev/null +++ b/plugin/dapp/lightclient/executor/errors.go @@ -0,0 +1,19 @@ +package executor + +import "errors" + +var ( + ErrDecodeAction = errors.New("ErrDecodeAction") + ErrNilBtcHeader = errors.New("ErrNilBtcHeader") + ErrBtcGetLastHeader = errors.New("ErrBtcGetLastHeader") + ErrIllegalCommitAddress = errors.New("ErrIllegalCommitAddress") + ErrBtcHeaderDisorder = errors.New("ErrBtcHeaderDisorder") + + ErrBtcTargetBits = errors.New("ErrBtcTargetBits") + ErrBtcHeaderTimeTooOld = errors.New("ErrBtcHeaderTimeTooOld") + ErrBtcHeaderTimeTooNew = errors.New("ErrBtcHeaderTimeTooNew") + ErrBtcHeaderInvalidTime = errors.New("ErrBtcHeaderInvalidTime") + ErrToBtcWireHeader = errors.New("ErrToBtcWireHeader") + ErrInvalidBtcBlockHash = errors.New("ErrInvalidBtcBlockHash") + ErrBtcHeaderVerify = errors.New("ErrBtcHeaderVerify") +) diff --git a/plugin/dapp/lightclient/executor/exec.go b/plugin/dapp/lightclient/executor/exec.go new file mode 100644 index 0000000000..786d048425 --- /dev/null +++ b/plugin/dapp/lightclient/executor/exec.go @@ -0,0 +1,44 @@ +package executor + +import ( + "github.com/33cn/chain33/types" + ltypes "github.com/33cn/plugin/plugin/dapp/lightclient/lighttypes" +) + +/* + * 实现交易的链上执行接口 + * 关键数据上链(statedb)并生成交易回执(log) + */ + +func (l *lightclient) Exec_BtcHeaders(headers *ltypes.BtcHeaders, tx *types.Transaction, index int) (*types.Receipt, error) { + receipt := &types.Receipt{Ty: types.ExecOk} + + prevHeader, err := getBtcLastHeader(l.GetStateDB()) + if err != nil && err != types.ErrNotFound { + elog.Error("Exec_BtcHeaders", "getBtcLastHeader err", err) + return nil, ErrBtcGetLastHeader + } + + commitHeader := headers.GetHeaders()[len(headers.GetHeaders())-1] + elog.Debug("Exec_BtcHeaders", "lastHeight", prevHeader.GetHeight(), "lastHash", prevHeader.GetHash(), + "commitHeight", commitHeader.GetHeight(), "commitHash", commitHeader.GetHash()) + log := <ypes.BtcHeadersLog{} + log.LastHeight = prevHeader.GetHeight() + log.LastHash = prevHeader.GetHash() + + log.CommitHash = commitHeader.GetHash() + log.CommitHeight = commitHeader.GetHeight() + log.Confirmations = commitHeader.GetConfirmations() + + receipt.KV = append(receipt.KV, &types.KeyValue{ + Key: btcLastHeaderKey(), + Value: types.Encode(commitHeader), + }) + + receipt.Logs = append(receipt.Logs, &types.ReceiptLog{ + Ty: ltypes.TyBtcHeadersLog, + Log: types.Encode(log), + }) + + return receipt, nil +} diff --git a/plugin/dapp/lightclient/executor/exec_del_local.go b/plugin/dapp/lightclient/executor/exec_del_local.go new file mode 100644 index 0000000000..be4bd4a0e3 --- /dev/null +++ b/plugin/dapp/lightclient/executor/exec_del_local.go @@ -0,0 +1,20 @@ +package executor + +import ( + "github.com/33cn/chain33/types" +) + +/* + * 实现区块回退时本地执行的数据清除 + */ + +// ExecDelLocal localdb kv数据自动回滚接口 +func (l *lightclient) ExecDelLocal(tx *types.Transaction, receipt *types.ReceiptData, index int) (*types.LocalDBSet, error) { + kvs, err := l.DelRollbackKV(tx, tx.Execer) + if err != nil { + return nil, err + } + dbSet := &types.LocalDBSet{} + dbSet.KV = append(dbSet.KV, kvs...) + return dbSet, nil +} diff --git a/plugin/dapp/lightclient/executor/exec_local.go b/plugin/dapp/lightclient/executor/exec_local.go new file mode 100644 index 0000000000..265dea7011 --- /dev/null +++ b/plugin/dapp/lightclient/executor/exec_local.go @@ -0,0 +1,38 @@ +package executor + +import ( + "github.com/33cn/chain33/types" + ltypes "github.com/33cn/plugin/plugin/dapp/lightclient/lighttypes" +) + +/* + * 实现交易相关数据本地执行,数据不上链 + * 非关键数据,本地存储(localDB), 用于辅助查询,效率高 + */ + +func (l *lightclient) ExecLocal_BtcHeaders(headers *ltypes.BtcHeaders, tx *types.Transaction, receiptData *types.ReceiptData, index int) (*types.LocalDBSet, error) { + dbSet := &types.LocalDBSet{} + + for _, h := range headers.GetHeaders() { + + dbSet.KV = append(dbSet.KV, + &types.KeyValue{ + Key: btcHeaderKey(h.GetHeight()), + Value: types.Encode(h), + }, &types.KeyValue{ + Key: btcHeaderHashHeightKey(h.GetHash()), + Value: types.Encode(&types.Int64{Data: int64(h.GetHeight())}), + }) + } + + //auto gen for localdb auto rollback + return l.addAutoRollBack(tx, dbSet.KV), nil +} + +// 当区块回滚时,框架支持自动回滚localdb kv,需要对exec-local返回的kv进行封装 +func (l *lightclient) addAutoRollBack(tx *types.Transaction, kv []*types.KeyValue) *types.LocalDBSet { + + dbSet := &types.LocalDBSet{} + dbSet.KV = l.AddRollbackKV(tx, tx.Execer, kv) + return dbSet +} diff --git a/plugin/dapp/lightclient/executor/executor_test.go b/plugin/dapp/lightclient/executor/executor_test.go new file mode 100644 index 0000000000..c63e5d0e2b --- /dev/null +++ b/plugin/dapp/lightclient/executor/executor_test.go @@ -0,0 +1,325 @@ +package executor + +import ( + "bytes" + "errors" + "testing" + "time" + + "github.com/33cn/chain33/client/mocks" + "github.com/33cn/chain33/common/crypto" + "github.com/33cn/chain33/types" + "github.com/33cn/chain33/util" + ltypes "github.com/33cn/plugin/plugin/dapp/lightclient/lighttypes" + "github.com/btcsuite/btcd/blockchain" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + "github.com/stretchr/testify/require" +) + +func TestLightclientCheckTxBasic(t *testing.T) { + cli := newLightclient().(*lightclient) + addr, priv := util.Genaddress() + lightCfg.CommitAddress = addr + lightCfg.BtcNetName = "regtest" + + tx := &types.Transaction{Payload: []byte("invalid")} + require.Equal(t, ErrDecodeAction, cli.CheckTx(tx, 0)) + + action := <ypes.LightClientAction{Ty: 999} + tx.Payload = types.Encode(action) + tx.Sign(types.SECP256K1, priv) + require.Equal(t, types.ErrActionNotSupport, cli.CheckTx(tx, 0)) +} + +func TestMapBtcHeaderVerifyErr(t *testing.T) { + cases := []struct { + name string + err error + want error + }{ + { + name: "difficulty", + err: blockchain.RuleError{ErrorCode: blockchain.ErrUnexpectedDifficulty, Description: "bad bits"}, + want: ErrBtcTargetBits, + }, + { + name: "time too old", + err: blockchain.RuleError{ErrorCode: blockchain.ErrTimeTooOld, Description: "too old"}, + want: ErrBtcHeaderTimeTooOld, + }, + { + name: "time too new", + err: blockchain.RuleError{ErrorCode: blockchain.ErrTimeTooNew, Description: "too new"}, + want: ErrBtcHeaderTimeTooNew, + }, + { + name: "invalid time precision", + err: blockchain.RuleError{ErrorCode: blockchain.ErrInvalidTime, Description: "invalid time"}, + want: ErrBtcHeaderInvalidTime, + }, + { + name: "fallback", + err: errors.New("other"), + want: ErrBtcHeaderVerify, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + require.Equal(t, tc.want, mapBtcHeaderVerifyErr(tc.err)) + }) + } +} + +func TestLightclientCheckBtcHeaders(t *testing.T) { + dir, stateDB, localDB := util.CreateTestDB() + defer util.CloseTestDB(dir, stateDB) + + cli := newLightclient().(*lightclient) + setupTestDriver(t, cli) + cli.SetStateDB(stateDB) + cli.SetLocalDB(localDB) + + commitAddr, commitPriv := util.Genaddress() + _, illegalPriv := util.Genaddress() + lightCfg.CommitAddress = commitAddr + lightCfg.BtcNetName = "regtest" + + regtest := &chaincfg.RegressionNetParams + ts := types.Now().Add(-time.Hour) + prev := mineBtcHeader(t, nil, 100, regtest.PowLimitBits, ts) + next := mineBtcHeader(t, prev, 101, regtest.PowLimitBits, ts.Add(time.Minute)) + + require.NoError(t, stateDB.Set(btcLastHeaderKey(), types.Encode(prev))) + require.NoError(t, localDB.Set(btcHeaderKey(prev.Height), types.Encode(prev))) + + t.Run("illegal commit address", func(t *testing.T) { + tx := buildCheckTx(t, <ypes.BtcHeaders{Headers: []*ltypes.BtcHeader{next}}, illegalPriv) + require.Equal(t, ErrIllegalCommitAddress, cli.CheckTx(tx, 0)) + }) + + t.Run("empty headers", func(t *testing.T) { + tx := buildCheckTx(t, <ypes.BtcHeaders{}, commitPriv) + require.Equal(t, types.ErrInvalidParam, cli.CheckTx(tx, 0)) + }) + + t.Run("nil header", func(t *testing.T) { + tx := buildCheckTx(t, <ypes.BtcHeaders{Headers: []*ltypes.BtcHeader{nil}}, commitPriv) + require.Equal(t, types.ErrInvalidParam, cli.CheckTx(tx, 0)) + }) + + t.Run("header disorder", func(t *testing.T) { + bad := cloneHeader(next) + bad.PreviousHash = chainhash.Hash{}.String() + tx := buildCheckTx(t, <ypes.BtcHeaders{Headers: []*ltypes.BtcHeader{bad}}, commitPriv) + require.Equal(t, ErrBtcHeaderDisorder, cli.CheckTx(tx, 0)) + }) + + t.Run("invalid wire header", func(t *testing.T) { + bad := cloneHeader(next) + bad.MerkleRoot = "not-a-hash" + tx := buildCheckTx(t, <ypes.BtcHeaders{Headers: []*ltypes.BtcHeader{bad}}, commitPriv) + require.Equal(t, ErrToBtcWireHeader, cli.CheckTx(tx, 0)) + }) + + t.Run("invalid header hash", func(t *testing.T) { + bad := cloneHeader(next) + bad.Hash = prev.Hash + tx := buildCheckTx(t, <ypes.BtcHeaders{Headers: []*ltypes.BtcHeader{bad}}, commitPriv) + require.Equal(t, ErrInvalidBtcBlockHash, cli.CheckTx(tx, 0)) + }) + + t.Run("target bits mismatch", func(t *testing.T) { + badBitsHeader := mineBtcHeader(t, prev, 101, regtest.PowLimitBits-1, ts.Add(2*time.Minute)) + tx := buildCheckTx(t, <ypes.BtcHeaders{Headers: []*ltypes.BtcHeader{badBitsHeader}}, commitPriv) + require.Equal(t, ErrBtcTargetBits, cli.CheckTx(tx, 0)) + }) + + t.Run("success", func(t *testing.T) { + tx := buildCheckTx(t, <ypes.BtcHeaders{Headers: []*ltypes.BtcHeader{next}}, commitPriv) + require.NoError(t, cli.CheckTx(tx, 0)) + }) +} + +func TestLightclientCheckBtcHeadersBootstrap(t *testing.T) { + dir, stateDB, localDB := util.CreateTestDB() + defer util.CloseTestDB(dir, stateDB) + + cli := newLightclient().(*lightclient) + setupTestDriver(t, cli) + cli.SetStateDB(stateDB) + cli.SetLocalDB(localDB) + + commitAddr, commitPriv := util.Genaddress() + lightCfg.CommitAddress = commitAddr + lightCfg.BtcNetName = "regtest" + + regtest := &chaincfg.RegressionNetParams + ts := types.Now().Add(-time.Hour) + h1 := mineBtcHeader(t, nil, 1, regtest.PowLimitBits, ts) + h2 := mineBtcHeader(t, h1, 2, regtest.PowLimitBits, ts.Add(time.Minute)) + + tx := buildCheckTx(t, <ypes.BtcHeaders{Headers: []*ltypes.BtcHeader{h1, h2}}, commitPriv) + require.NoError(t, cli.CheckTx(tx, 0)) +} + +func TestLightclientExecBtcHeaders(t *testing.T) { + t.Run("get last header error", func(t *testing.T) { + dir, stateDB, _ := util.CreateTestDB() + defer util.CloseTestDB(dir, stateDB) + cli := newLightclient().(*lightclient) + setupTestDriver(t, cli) + cli.SetStateDB(stateDB) + // Corrupt data to force decode error. + require.NoError(t, stateDB.Set(btcLastHeaderKey(), []byte("bad-data"))) + + headers := <ypes.BtcHeaders{Headers: []*ltypes.BtcHeader{{Height: 1, Hash: chainhash.Hash{}.String()}}} + recp, err := cli.Exec_BtcHeaders(headers, &types.Transaction{}, 0) + require.Nil(t, recp) + require.Equal(t, ErrBtcGetLastHeader, err) + }) + + t.Run("success", func(t *testing.T) { + dir, stateDB, _ := util.CreateTestDB() + defer util.CloseTestDB(dir, stateDB) + cli := newLightclient().(*lightclient) + setupTestDriver(t, cli) + cli.SetStateDB(stateDB) + + prev := <ypes.BtcHeader{Height: 100, Hash: "prevhash"} + require.NoError(t, stateDB.Set(btcLastHeaderKey(), types.Encode(prev))) + commit := <ypes.BtcHeader{Height: 101, Hash: "commithash", Confirmations: 6} + + recp, err := cli.Exec_BtcHeaders(<ypes.BtcHeaders{Headers: []*ltypes.BtcHeader{commit}}, &types.Transaction{}, 0) + require.NoError(t, err) + require.EqualValues(t, types.ExecOk, recp.Ty) + require.Len(t, recp.KV, 1) + require.Equal(t, btcLastHeaderKey(), recp.KV[0].Key) + + saved := <ypes.BtcHeader{} + require.NoError(t, types.Decode(recp.KV[0].Value, saved)) + require.Equal(t, commit.Height, saved.Height) + require.Equal(t, commit.Hash, saved.Hash) + + require.Len(t, recp.Logs, 1) + log := <ypes.BtcHeadersLog{} + require.NoError(t, types.Decode(recp.Logs[0].Log, log)) + require.Equal(t, prev.Height, log.LastHeight) + require.Equal(t, prev.Hash, log.LastHash) + require.Equal(t, commit.Height, log.CommitHeight) + require.Equal(t, commit.Hash, log.CommitHash) + require.Equal(t, commit.Confirmations, log.Confirmations) + }) +} + +func TestLightclientExecLocalBtcHeaders(t *testing.T) { + dir, stateDB, localDB := util.CreateTestDB() + defer util.CloseTestDB(dir, stateDB) + + cli := newLightclient().(*lightclient) + setupTestDriver(t, cli) + cli.SetStateDB(stateDB) + cli.SetLocalDB(localDB) + + h1 := <ypes.BtcHeader{Height: 11, Hash: "h11"} + h2 := <ypes.BtcHeader{Height: 12, Hash: "h12"} + tx := &types.Transaction{Execer: []byte(ltypes.LightclientX)} + dbSet, err := cli.ExecLocal_BtcHeaders(<ypes.BtcHeaders{Headers: []*ltypes.BtcHeader{h1, h2}}, tx, nil, 0) + require.NoError(t, err) + require.NotNil(t, dbSet) + require.GreaterOrEqual(t, len(dbSet.KV), 4) + + require.True(t, hasKV(dbSet.KV, btcHeaderKey(h1.Height))) + require.True(t, hasKV(dbSet.KV, btcHeaderHashHeightKey(h1.Hash))) + require.True(t, hasKV(dbSet.KV, btcHeaderKey(h2.Height))) + require.True(t, hasKV(dbSet.KV, btcHeaderHashHeightKey(h2.Hash))) +} + +func buildCheckTx(t *testing.T, headers *ltypes.BtcHeaders, priv crypto.PrivKey) *types.Transaction { + t.Helper() + action := <ypes.LightClientAction{ + Ty: ltypes.TyBtcHeadersAction, + Value: <ypes.LightClientAction_BtcHeaders{ + BtcHeaders: headers, + }, + } + tx := &types.Transaction{Payload: types.Encode(action)} + tx.Sign(types.SECP256K1, priv) + return tx +} + +func mineBtcHeader(t *testing.T, prev *ltypes.BtcHeader, height uint64, bits uint32, ts time.Time) *ltypes.BtcHeader { + t.Helper() + + prevHash := chainhash.Hash{}.String() + if prev != nil { + prevHash = prev.Hash + } + pre, err := chainhash.NewHashFromStr(prevHash) + require.NoError(t, err) + merkle := chainhash.DoubleHashH([]byte{byte(height), byte(height >> 8)}) + + head := &wire.BlockHeader{ + Version: 1, + PrevBlock: *pre, + MerkleRoot: merkle, + Timestamp: ts, + Bits: bits, + } + target := blockchain.CompactToBig(bits) + for { + hash := head.BlockHash() + if blockchain.HashToBig(&hash).Cmp(target) <= 0 { + return <ypes.BtcHeader{ + Hash: hash.String(), + Height: height, + Version: uint32(head.Version), + MerkleRoot: head.MerkleRoot.String(), + Time: head.Timestamp.Unix(), + Nonce: uint64(head.Nonce), + Bits: int64(head.Bits), + PreviousHash: head.PrevBlock.String(), + Confirmations: 0, + } + } + head.Nonce++ + if head.Nonce == 0 { + head.Timestamp = head.Timestamp.Add(time.Second) + } + } +} + +func hasKV(kvs []*types.KeyValue, key []byte) bool { + for _, kv := range kvs { + if bytes.Equal(kv.Key, key) { + return true + } + } + return false +} + +func cloneHeader(h *ltypes.BtcHeader) *ltypes.BtcHeader { + return <ypes.BtcHeader{ + Hash: h.GetHash(), + Confirmations: h.GetConfirmations(), + Height: h.GetHeight(), + Version: h.GetVersion(), + MerkleRoot: h.GetMerkleRoot(), + Time: h.GetTime(), + Nonce: h.GetNonce(), + Bits: h.GetBits(), + Difficulty: h.GetDifficulty(), + PreviousHash: h.GetPreviousHash(), + NextHash: h.GetNextHash(), + } +} + +func setupTestDriver(t *testing.T, cli *lightclient) { + t.Helper() + api := &mocks.QueueProtocolAPI{} + cfg := types.NewChain33Config(types.GetDefaultCfgstring()) + api.On("GetConfig").Return(cfg) + cli.SetAPI(api) +} diff --git a/plugin/dapp/lightclient/executor/kv.go b/plugin/dapp/lightclient/executor/kv.go new file mode 100644 index 0000000000..19a879838f --- /dev/null +++ b/plugin/dapp/lightclient/executor/kv.go @@ -0,0 +1,32 @@ +package executor + +import "fmt" + +/* + * 用户合约存取kv数据时,key值前缀需要满足一定规范 + * 即key = keyPrefix + userKey + * 需要字段前缀查询时,使用’-‘作为分割符号 + */ + +var ( + //KeyPrefixStateDB state db key必须前缀 + KeyPrefixStateDB = "mavl-lightclient-" + //KeyPrefixLocalDB local db的key必须前缀 + KeyPrefixLocalDB = "LODB-lightclient-" +) + +// statedb + +func btcLastHeaderKey() []byte { + return []byte(KeyPrefixStateDB + "btc-lastheader") +} + +// localdb + +func btcHeaderKey(height uint64) []byte { + return []byte(KeyPrefixLocalDB + fmt.Sprintf("btc-header-%010d", height)) +} + +func btcHeaderHashHeightKey(hash string) []byte { + return []byte(KeyPrefixLocalDB + "btc-hash2height-" + hash) +} diff --git a/plugin/dapp/lightclient/executor/lightclient.go b/plugin/dapp/lightclient/executor/lightclient.go new file mode 100644 index 0000000000..fe7d2a0e58 --- /dev/null +++ b/plugin/dapp/lightclient/executor/lightclient.go @@ -0,0 +1,112 @@ +package executor + +import ( + "sync" + + "github.com/33cn/chain33/common/db" + log "github.com/33cn/chain33/common/log/log15" + drivers "github.com/33cn/chain33/system/dapp" + "github.com/33cn/chain33/types" + ltypes "github.com/33cn/plugin/plugin/dapp/lightclient/lighttypes" +) + +/* + * 执行器相关定义 + * 重载基类相关接口 + */ + +type config struct { + CommitAddress string `json:"commitAddress"` + BtcNetName string `json:"btcNetName"` + AllowRegtestTimeWarp bool `json:"allowRegtestTimeWarp"` +} + +var ( + //日志 + elog = log.New("module", "lightclient.exec") + lightCfg config + cfgInitOnce sync.Once +) + +var driverName = ltypes.LightclientX + +// Init register dapp +func Init(_ string, cfg *types.Chain33Config, sub []byte) { + initCfg(sub) + drivers.Register(cfg, GetName(), newLightclient, cfg.GetDappFork(driverName, "Enable")) + InitExecType() +} + +func initCfg(sub []byte) { + + cfgInitOnce.Do(func() { + types.MustDecode(sub, &lightCfg) + if lightCfg.BtcNetName == "" { + lightCfg.BtcNetName = "mainnet" + } + }) +} + +// InitExecType Init Exec Type +func InitExecType() { + ety := types.LoadExecutorType(driverName) + ety.InitFuncList(types.ListMethod(&lightclient{})) +} + +type lightclient struct { + drivers.DriverBase +} + +func newLightclient() drivers.Driver { + t := &lightclient{} + t.SetChild(t) + t.SetExecutorType(types.LoadExecutorType(driverName)) + return t +} + +// GetName get driver name +func GetName() string { + return newLightclient().GetName() +} + +func (l *lightclient) GetDriverName() string { + return driverName +} + +// ExecutorOrder 设置localdb的EnableRead +func (l *lightclient) ExecutorOrder() int64 { + return drivers.ExecLocalSameTime +} + +func readDB(kdb db.KV, key []byte, result types.Message) error { + + val, err := kdb.Get(key) + if err != nil { + return err + } + return types.Decode(val, result) +} + +func getBtcLastHeader(sdb db.KV) (*ltypes.BtcHeader, error) { + + header := <ypes.BtcHeader{} + err := readDB(sdb, btcLastHeaderKey(), header) + if err == types.ErrNotFound { + return <ypes.BtcHeader{}, nil + } + return header, err +} + +func getBtcHeader(ldb db.KV, height uint64) (*ltypes.BtcHeader, error) { + + header := <ypes.BtcHeader{} + err := readDB(ldb, btcHeaderKey(height), header) + return header, err +} + +func getBtcHeight(ldb db.KV, hash string) (*types.Int64, error) { + + height := &types.Int64{} + err := readDB(ldb, btcHeaderHashHeightKey(hash), height) + return height, err +} diff --git a/plugin/dapp/lightclient/executor/query.go b/plugin/dapp/lightclient/executor/query.go new file mode 100644 index 0000000000..7a698c72ed --- /dev/null +++ b/plugin/dapp/lightclient/executor/query.go @@ -0,0 +1,33 @@ +package executor + +import ( + "github.com/33cn/chain33/types" + ltypes "github.com/33cn/plugin/plugin/dapp/lightclient/lighttypes" +) + +func (l *lightclient) Query_GetBtcLastHeader(req *types.ReqNil) (types.Message, error) { + + header, err := getBtcLastHeader(l.GetStateDB()) + return header, err +} + +func (l *lightclient) Query_GetBtcHeader(req *ltypes.ReqGetBtcHeader) (types.Message, error) { + + header, err := getBtcHeader(l.GetLocalDB(), req.GetHeight()) + return header, err +} + +func (l *lightclient) Query_GetBtcHeaderByHash(req *types.ReqString) (types.Message, error) { + + height, err := getBtcHeight(l.GetLocalDB(), req.GetData()) + if err != nil { + elog.Error("Query_GetBtcHeaderByHash", "hash", req.GetData(), "err", err) + return nil, err + } + header, err := getBtcHeader(l.GetLocalDB(), uint64(height.GetData())) + return header, err +} + +func (l *lightclient) Query_GetBtcNetName(req *types.ReqNil) (types.Message, error) { + return &types.ReplyString{Data: lightCfg.BtcNetName}, nil +} diff --git a/plugin/dapp/lightclient/lighttypes/lightclient.go b/plugin/dapp/lightclient/lighttypes/lightclient.go new file mode 100644 index 0000000000..846e6c403f --- /dev/null +++ b/plugin/dapp/lightclient/lighttypes/lightclient.go @@ -0,0 +1,88 @@ +package lighttypes + +import ( + "reflect" + + log "github.com/33cn/chain33/common/log/log15" + "github.com/33cn/chain33/types" +) + +/* + * 交易相关类型定义 + * 交易action通常有对应的log结构,用于交易回执日志记录 + * 每一种action和log需要用id数值和name名称加以区分 + */ + +// action类型id和name,这些常量可以自定义修改 +const ( + TyUnknowAction = iota + 100 + TyBtcHeadersAction + + NameBtcHeadersAction = "BtcHeaders" +) + +// log类型id值 +const ( + TyUnknownLog = iota + 100 + TyBtcHeadersLog + + NameBtcHeadersLog = "BtcHeadersLog" +) + +var ( + //LightclientX 执行器名称定义 + LightclientX = "lightclient" + //定义actionMap + actionMap = map[string]int32{ + NameBtcHeadersAction: TyBtcHeadersAction, + } + //定义log的id和具体log类型及名称,填入具体自定义log类型 + logMap = map[int64]*types.LogInfo{ + TyBtcHeadersLog: {Ty: reflect.TypeOf(BtcHeadersLog{}), Name: NameBtcHeadersLog}, + } + tlog = log.New("module", "lightclient.types") +) + +// init defines a register function +func init() { + types.AllowUserExec = append(types.AllowUserExec, []byte(LightclientX)) + //注册合约启用高度 + types.RegFork(LightclientX, InitFork) + types.RegExec(LightclientX, InitExecutor) +} + +// InitFork defines register fork +func InitFork(cfg *types.Chain33Config) { + cfg.RegisterDappFork(LightclientX, "Enable", 0) +} + +// InitExecutor defines register executor +func InitExecutor(cfg *types.Chain33Config) { + types.RegistorExecutor(LightclientX, NewType(cfg)) +} + +type lightClientType struct { + types.ExecTypeBase +} + +func NewType(cfg *types.Chain33Config) *lightClientType { + c := &lightClientType{} + c.SetChild(c) + c.SetConfig(cfg) + return c +} + +// GetPayload 获取合约action结构 +func (l *lightClientType) GetPayload() types.Message { + return &LightClientAction{} +} + +// GetTypeMap 获取合约action的id和name信息 +func (l *lightClientType) GetTypeMap() map[string]int32 { + return actionMap +} + +// GetLogMap 获取合约log相关信息 +func (l *lightClientType) GetLogMap() map[int64]*types.LogInfo { + return logMap +} diff --git a/plugin/dapp/lightclient/lighttypes/lightclient.pb.go b/plugin/dapp/lightclient/lighttypes/lightclient.pb.go new file mode 100644 index 0000000000..b781ec0e98 --- /dev/null +++ b/plugin/dapp/lightclient/lighttypes/lightclient.pb.go @@ -0,0 +1,1090 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.26.0 +// protoc v3.17.0 +// source: lightclient.proto + +package lighttypes + +import ( + context "context" + grpc "google.golang.org/grpc" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type LightClientAction struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Ty int32 `protobuf:"varint,1,opt,name=ty,proto3" json:"ty,omitempty"` + // Types that are assignable to Value: + // *LightClientAction_BtcHeaders + Value isLightClientAction_Value `protobuf_oneof:"value"` +} + +func (x *LightClientAction) Reset() { + *x = LightClientAction{} + if protoimpl.UnsafeEnabled { + mi := &file_lightclient_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *LightClientAction) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LightClientAction) ProtoMessage() {} + +func (x *LightClientAction) ProtoReflect() protoreflect.Message { + mi := &file_lightclient_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LightClientAction.ProtoReflect.Descriptor instead. +func (*LightClientAction) Descriptor() ([]byte, []int) { + return file_lightclient_proto_rawDescGZIP(), []int{0} +} + +func (x *LightClientAction) GetTy() int32 { + if x != nil { + return x.Ty + } + return 0 +} + +func (m *LightClientAction) GetValue() isLightClientAction_Value { + if m != nil { + return m.Value + } + return nil +} + +func (x *LightClientAction) GetBtcHeaders() *BtcHeaders { + if x, ok := x.GetValue().(*LightClientAction_BtcHeaders); ok { + return x.BtcHeaders + } + return nil +} + +type isLightClientAction_Value interface { + isLightClientAction_Value() +} + +type LightClientAction_BtcHeaders struct { + BtcHeaders *BtcHeaders `protobuf:"bytes,2,opt,name=btcHeaders,proto3,oneof"` +} + +func (*LightClientAction_BtcHeaders) isLightClientAction_Value() {} + +type BtcHeader struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Hash string `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` + Confirmations uint64 `protobuf:"varint,2,opt,name=confirmations,proto3" json:"confirmations,omitempty"` + Height uint64 `protobuf:"varint,3,opt,name=height,proto3" json:"height,omitempty"` + Version uint32 `protobuf:"varint,4,opt,name=version,proto3" json:"version,omitempty"` + MerkleRoot string `protobuf:"bytes,5,opt,name=merkleRoot,proto3" json:"merkleRoot,omitempty"` + Time int64 `protobuf:"varint,6,opt,name=time,proto3" json:"time,omitempty"` + Nonce uint64 `protobuf:"varint,7,opt,name=nonce,proto3" json:"nonce,omitempty"` + Bits int64 `protobuf:"varint,8,opt,name=bits,proto3" json:"bits,omitempty"` + Difficulty int64 `protobuf:"varint,9,opt,name=difficulty,proto3" json:"difficulty,omitempty"` + PreviousHash string `protobuf:"bytes,10,opt,name=previousHash,proto3" json:"previousHash,omitempty"` + NextHash string `protobuf:"bytes,11,opt,name=nextHash,proto3" json:"nextHash,omitempty"` +} + +func (x *BtcHeader) Reset() { + *x = BtcHeader{} + if protoimpl.UnsafeEnabled { + mi := &file_lightclient_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BtcHeader) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BtcHeader) ProtoMessage() {} + +func (x *BtcHeader) ProtoReflect() protoreflect.Message { + mi := &file_lightclient_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BtcHeader.ProtoReflect.Descriptor instead. +func (*BtcHeader) Descriptor() ([]byte, []int) { + return file_lightclient_proto_rawDescGZIP(), []int{1} +} + +func (x *BtcHeader) GetHash() string { + if x != nil { + return x.Hash + } + return "" +} + +func (x *BtcHeader) GetConfirmations() uint64 { + if x != nil { + return x.Confirmations + } + return 0 +} + +func (x *BtcHeader) GetHeight() uint64 { + if x != nil { + return x.Height + } + return 0 +} + +func (x *BtcHeader) GetVersion() uint32 { + if x != nil { + return x.Version + } + return 0 +} + +func (x *BtcHeader) GetMerkleRoot() string { + if x != nil { + return x.MerkleRoot + } + return "" +} + +func (x *BtcHeader) GetTime() int64 { + if x != nil { + return x.Time + } + return 0 +} + +func (x *BtcHeader) GetNonce() uint64 { + if x != nil { + return x.Nonce + } + return 0 +} + +func (x *BtcHeader) GetBits() int64 { + if x != nil { + return x.Bits + } + return 0 +} + +func (x *BtcHeader) GetDifficulty() int64 { + if x != nil { + return x.Difficulty + } + return 0 +} + +func (x *BtcHeader) GetPreviousHash() string { + if x != nil { + return x.PreviousHash + } + return "" +} + +func (x *BtcHeader) GetNextHash() string { + if x != nil { + return x.NextHash + } + return "" +} + +type BtcHeadersLog struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Confirmations uint64 `protobuf:"varint,1,opt,name=confirmations,proto3" json:"confirmations,omitempty"` + LastHeight uint64 `protobuf:"varint,2,opt,name=lastHeight,proto3" json:"lastHeight,omitempty"` + CommitHeight uint64 `protobuf:"varint,3,opt,name=commitHeight,proto3" json:"commitHeight,omitempty"` + LastHash string `protobuf:"bytes,4,opt,name=lastHash,proto3" json:"lastHash,omitempty"` + CommitHash string `protobuf:"bytes,5,opt,name=commitHash,proto3" json:"commitHash,omitempty"` +} + +func (x *BtcHeadersLog) Reset() { + *x = BtcHeadersLog{} + if protoimpl.UnsafeEnabled { + mi := &file_lightclient_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BtcHeadersLog) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BtcHeadersLog) ProtoMessage() {} + +func (x *BtcHeadersLog) ProtoReflect() protoreflect.Message { + mi := &file_lightclient_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BtcHeadersLog.ProtoReflect.Descriptor instead. +func (*BtcHeadersLog) Descriptor() ([]byte, []int) { + return file_lightclient_proto_rawDescGZIP(), []int{2} +} + +func (x *BtcHeadersLog) GetConfirmations() uint64 { + if x != nil { + return x.Confirmations + } + return 0 +} + +func (x *BtcHeadersLog) GetLastHeight() uint64 { + if x != nil { + return x.LastHeight + } + return 0 +} + +func (x *BtcHeadersLog) GetCommitHeight() uint64 { + if x != nil { + return x.CommitHeight + } + return 0 +} + +func (x *BtcHeadersLog) GetLastHash() string { + if x != nil { + return x.LastHash + } + return "" +} + +func (x *BtcHeadersLog) GetCommitHash() string { + if x != nil { + return x.CommitHash + } + return "" +} + +type BtcHeaders struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Headers []*BtcHeader `protobuf:"bytes,1,rep,name=headers,proto3" json:"headers,omitempty"` +} + +func (x *BtcHeaders) Reset() { + *x = BtcHeaders{} + if protoimpl.UnsafeEnabled { + mi := &file_lightclient_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BtcHeaders) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BtcHeaders) ProtoMessage() {} + +func (x *BtcHeaders) ProtoReflect() protoreflect.Message { + mi := &file_lightclient_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BtcHeaders.ProtoReflect.Descriptor instead. +func (*BtcHeaders) Descriptor() ([]byte, []int) { + return file_lightclient_proto_rawDescGZIP(), []int{3} +} + +func (x *BtcHeaders) GetHeaders() []*BtcHeader { + if x != nil { + return x.Headers + } + return nil +} + +type BtcTransaction struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Hash string `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` + BlockHeight uint64 `protobuf:"varint,2,opt,name=blockHeight,proto3" json:"blockHeight,omitempty"` + Vin []*Vin `protobuf:"bytes,3,rep,name=vin,proto3" json:"vin,omitempty"` + Vout []*Vout `protobuf:"bytes,4,rep,name=vout,proto3" json:"vout,omitempty"` + Time int64 `protobuf:"varint,5,opt,name=time,proto3" json:"time,omitempty"` + Confirmations uint64 `protobuf:"varint,6,opt,name=confirmations,proto3" json:"confirmations,omitempty"` +} + +func (x *BtcTransaction) Reset() { + *x = BtcTransaction{} + if protoimpl.UnsafeEnabled { + mi := &file_lightclient_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BtcTransaction) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BtcTransaction) ProtoMessage() {} + +func (x *BtcTransaction) ProtoReflect() protoreflect.Message { + mi := &file_lightclient_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BtcTransaction.ProtoReflect.Descriptor instead. +func (*BtcTransaction) Descriptor() ([]byte, []int) { + return file_lightclient_proto_rawDescGZIP(), []int{4} +} + +func (x *BtcTransaction) GetHash() string { + if x != nil { + return x.Hash + } + return "" +} + +func (x *BtcTransaction) GetBlockHeight() uint64 { + if x != nil { + return x.BlockHeight + } + return 0 +} + +func (x *BtcTransaction) GetVin() []*Vin { + if x != nil { + return x.Vin + } + return nil +} + +func (x *BtcTransaction) GetVout() []*Vout { + if x != nil { + return x.Vout + } + return nil +} + +func (x *BtcTransaction) GetTime() int64 { + if x != nil { + return x.Time + } + return 0 +} + +func (x *BtcTransaction) GetConfirmations() uint64 { + if x != nil { + return x.Confirmations + } + return 0 +} + +type Vin struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + Value uint64 `protobuf:"varint,2,opt,name=Value,proto3" json:"Value,omitempty"` +} + +func (x *Vin) Reset() { + *x = Vin{} + if protoimpl.UnsafeEnabled { + mi := &file_lightclient_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Vin) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Vin) ProtoMessage() {} + +func (x *Vin) ProtoReflect() protoreflect.Message { + mi := &file_lightclient_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Vin.ProtoReflect.Descriptor instead. +func (*Vin) Descriptor() ([]byte, []int) { + return file_lightclient_proto_rawDescGZIP(), []int{5} +} + +func (x *Vin) GetAddress() string { + if x != nil { + return x.Address + } + return "" +} + +func (x *Vin) GetValue() uint64 { + if x != nil { + return x.Value + } + return 0 +} + +type Vout struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Coinbase bool `protobuf:"varint,1,opt,name=coinbase,proto3" json:"coinbase,omitempty"` + Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"` + Value uint64 `protobuf:"varint,3,opt,name=Value,proto3" json:"Value,omitempty"` +} + +func (x *Vout) Reset() { + *x = Vout{} + if protoimpl.UnsafeEnabled { + mi := &file_lightclient_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Vout) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Vout) ProtoMessage() {} + +func (x *Vout) ProtoReflect() protoreflect.Message { + mi := &file_lightclient_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Vout.ProtoReflect.Descriptor instead. +func (*Vout) Descriptor() ([]byte, []int) { + return file_lightclient_proto_rawDescGZIP(), []int{6} +} + +func (x *Vout) GetCoinbase() bool { + if x != nil { + return x.Coinbase + } + return false +} + +func (x *Vout) GetAddress() string { + if x != nil { + return x.Address + } + return "" +} + +func (x *Vout) GetValue() uint64 { + if x != nil { + return x.Value + } + return 0 +} + +type BtcSpv struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + TxHash string `protobuf:"bytes,1,opt,name=txHash,proto3" json:"txHash,omitempty"` + Time int64 `protobuf:"varint,2,opt,name=time,proto3" json:"time,omitempty"` + Height uint64 `protobuf:"varint,3,opt,name=height,proto3" json:"height,omitempty"` + BlockHash string `protobuf:"bytes,4,opt,name=blockHash,proto3" json:"blockHash,omitempty"` + TxIndex uint32 `protobuf:"varint,5,opt,name=txIndex,proto3" json:"txIndex,omitempty"` + BranchProof [][]byte `protobuf:"bytes,6,rep,name=branchProof,proto3" json:"branchProof,omitempty"` +} + +func (x *BtcSpv) Reset() { + *x = BtcSpv{} + if protoimpl.UnsafeEnabled { + mi := &file_lightclient_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BtcSpv) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BtcSpv) ProtoMessage() {} + +func (x *BtcSpv) ProtoReflect() protoreflect.Message { + mi := &file_lightclient_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BtcSpv.ProtoReflect.Descriptor instead. +func (*BtcSpv) Descriptor() ([]byte, []int) { + return file_lightclient_proto_rawDescGZIP(), []int{7} +} + +func (x *BtcSpv) GetTxHash() string { + if x != nil { + return x.TxHash + } + return "" +} + +func (x *BtcSpv) GetTime() int64 { + if x != nil { + return x.Time + } + return 0 +} + +func (x *BtcSpv) GetHeight() uint64 { + if x != nil { + return x.Height + } + return 0 +} + +func (x *BtcSpv) GetBlockHash() string { + if x != nil { + return x.BlockHash + } + return "" +} + +func (x *BtcSpv) GetTxIndex() uint32 { + if x != nil { + return x.TxIndex + } + return 0 +} + +func (x *BtcSpv) GetBranchProof() [][]byte { + if x != nil { + return x.BranchProof + } + return nil +} + +type TssSignNotify struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + InputAmounts []int64 `protobuf:"varint,1,rep,packed,name=inputAmounts,proto3" json:"inputAmounts,omitempty"` + Payload []byte `protobuf:"bytes,2,opt,name=payload,proto3" json:"payload,omitempty"` + TxType string `protobuf:"bytes,3,opt,name=txType,proto3" json:"txType,omitempty"` + BtcTxData []byte `protobuf:"bytes,4,opt,name=btcTxData,proto3" json:"btcTxData,omitempty"` + Signers []string `protobuf:"bytes,5,rep,name=signers,proto3" json:"signers,omitempty"` +} + +func (x *TssSignNotify) Reset() { + *x = TssSignNotify{} + if protoimpl.UnsafeEnabled { + mi := &file_lightclient_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TssSignNotify) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TssSignNotify) ProtoMessage() {} + +func (x *TssSignNotify) ProtoReflect() protoreflect.Message { + mi := &file_lightclient_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TssSignNotify.ProtoReflect.Descriptor instead. +func (*TssSignNotify) Descriptor() ([]byte, []int) { + return file_lightclient_proto_rawDescGZIP(), []int{8} +} + +func (x *TssSignNotify) GetInputAmounts() []int64 { + if x != nil { + return x.InputAmounts + } + return nil +} + +func (x *TssSignNotify) GetPayload() []byte { + if x != nil { + return x.Payload + } + return nil +} + +func (x *TssSignNotify) GetTxType() string { + if x != nil { + return x.TxType + } + return "" +} + +func (x *TssSignNotify) GetBtcTxData() []byte { + if x != nil { + return x.BtcTxData + } + return nil +} + +func (x *TssSignNotify) GetSigners() []string { + if x != nil { + return x.Signers + } + return nil +} + +type ReqGetBtcHeader struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Height uint64 `protobuf:"varint,1,opt,name=height,proto3" json:"height,omitempty"` +} + +func (x *ReqGetBtcHeader) Reset() { + *x = ReqGetBtcHeader{} + if protoimpl.UnsafeEnabled { + mi := &file_lightclient_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ReqGetBtcHeader) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReqGetBtcHeader) ProtoMessage() {} + +func (x *ReqGetBtcHeader) ProtoReflect() protoreflect.Message { + mi := &file_lightclient_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReqGetBtcHeader.ProtoReflect.Descriptor instead. +func (*ReqGetBtcHeader) Descriptor() ([]byte, []int) { + return file_lightclient_proto_rawDescGZIP(), []int{9} +} + +func (x *ReqGetBtcHeader) GetHeight() uint64 { + if x != nil { + return x.Height + } + return 0 +} + +var File_lightclient_proto protoreflect.FileDescriptor + +var file_lightclient_proto_rawDesc = []byte{ + 0x0a, 0x11, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x22, 0x5b, 0x0a, 0x11, 0x4c, 0x69, 0x67, 0x68, 0x74, 0x43, 0x6c, 0x69, 0x65, + 0x6e, 0x74, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x74, 0x79, 0x12, 0x2d, 0x0a, 0x0a, 0x62, 0x74, 0x63, 0x48, + 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x42, + 0x74, 0x63, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x48, 0x00, 0x52, 0x0a, 0x62, 0x74, 0x63, + 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x42, 0x07, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x22, 0xb5, 0x02, 0x0a, 0x09, 0x42, 0x74, 0x63, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x12, + 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, + 0x73, 0x68, 0x12, 0x24, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, 0x69, 0x67, + 0x68, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, + 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x6d, 0x65, + 0x72, 0x6b, 0x6c, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x6d, 0x65, 0x72, 0x6b, 0x6c, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x69, + 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x14, + 0x0a, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x6e, + 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x69, 0x74, 0x73, 0x18, 0x08, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x04, 0x62, 0x69, 0x74, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x69, 0x66, 0x66, + 0x69, 0x63, 0x75, 0x6c, 0x74, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x64, 0x69, + 0x66, 0x66, 0x69, 0x63, 0x75, 0x6c, 0x74, 0x79, 0x12, 0x22, 0x0a, 0x0c, 0x70, 0x72, 0x65, 0x76, + 0x69, 0x6f, 0x75, 0x73, 0x48, 0x61, 0x73, 0x68, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, + 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x48, 0x61, 0x73, 0x68, 0x12, 0x1a, 0x0a, 0x08, + 0x6e, 0x65, 0x78, 0x74, 0x48, 0x61, 0x73, 0x68, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x6e, 0x65, 0x78, 0x74, 0x48, 0x61, 0x73, 0x68, 0x22, 0xb5, 0x01, 0x0a, 0x0d, 0x42, 0x74, 0x63, + 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x4c, 0x6f, 0x67, 0x12, 0x24, 0x0a, 0x0d, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x12, 0x1e, 0x0a, 0x0a, 0x6c, 0x61, 0x73, 0x74, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x6c, 0x61, 0x73, 0x74, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, + 0x12, 0x22, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x48, 0x65, + 0x69, 0x67, 0x68, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x61, 0x73, 0x74, 0x48, 0x61, 0x73, 0x68, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x61, 0x73, 0x74, 0x48, 0x61, 0x73, 0x68, + 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x48, 0x61, 0x73, 0x68, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x48, 0x61, 0x73, 0x68, + 0x22, 0x32, 0x0a, 0x0a, 0x42, 0x74, 0x63, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x12, 0x24, + 0x0a, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x0a, 0x2e, 0x42, 0x74, 0x63, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x07, 0x68, 0x65, 0x61, + 0x64, 0x65, 0x72, 0x73, 0x22, 0xb3, 0x01, 0x0a, 0x0e, 0x42, 0x74, 0x63, 0x54, 0x72, 0x61, 0x6e, + 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x20, 0x0a, 0x0b, 0x62, + 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x16, 0x0a, + 0x03, 0x76, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x04, 0x2e, 0x56, 0x69, 0x6e, + 0x52, 0x03, 0x76, 0x69, 0x6e, 0x12, 0x19, 0x0a, 0x04, 0x76, 0x6f, 0x75, 0x74, 0x18, 0x04, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x05, 0x2e, 0x56, 0x6f, 0x75, 0x74, 0x52, 0x04, 0x76, 0x6f, 0x75, 0x74, + 0x12, 0x12, 0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, + 0x74, 0x69, 0x6d, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x35, 0x0a, 0x03, 0x56, 0x69, + 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x56, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x56, 0x61, 0x6c, 0x75, + 0x65, 0x22, 0x52, 0x0a, 0x04, 0x56, 0x6f, 0x75, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x6f, 0x69, + 0x6e, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x63, 0x6f, 0x69, + 0x6e, 0x62, 0x61, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, + 0x14, 0x0a, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, + 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0xa6, 0x01, 0x0a, 0x06, 0x42, 0x74, 0x63, 0x53, 0x70, 0x76, + 0x12, 0x16, 0x0a, 0x06, 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, + 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x68, 0x65, + 0x69, 0x67, 0x68, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, + 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, + 0x73, 0x68, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x78, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x07, 0x74, 0x78, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x20, 0x0a, 0x0b, + 0x62, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x06, 0x20, 0x03, 0x28, + 0x0c, 0x52, 0x0b, 0x62, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x22, 0x9d, + 0x01, 0x0a, 0x0d, 0x74, 0x73, 0x73, 0x53, 0x69, 0x67, 0x6e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x79, + 0x12, 0x22, 0x0a, 0x0c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x03, 0x52, 0x0c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x41, 0x6d, 0x6f, + 0x75, 0x6e, 0x74, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x16, + 0x0a, 0x06, 0x74, 0x78, 0x54, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x74, 0x78, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x62, 0x74, 0x63, 0x54, 0x78, 0x44, + 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x62, 0x74, 0x63, 0x54, 0x78, + 0x44, 0x61, 0x74, 0x61, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x73, 0x18, + 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x73, 0x22, 0x29, + 0x0a, 0x0f, 0x72, 0x65, 0x71, 0x47, 0x65, 0x74, 0x42, 0x74, 0x63, 0x48, 0x65, 0x61, 0x64, 0x65, + 0x72, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x32, 0x0d, 0x0a, 0x0b, 0x4c, 0x69, 0x67, + 0x68, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x42, 0x0f, 0x5a, 0x0d, 0x2e, 0x2e, 0x2f, 0x6c, + 0x69, 0x67, 0x68, 0x74, 0x74, 0x79, 0x70, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, +} + +var ( + file_lightclient_proto_rawDescOnce sync.Once + file_lightclient_proto_rawDescData = file_lightclient_proto_rawDesc +) + +func file_lightclient_proto_rawDescGZIP() []byte { + file_lightclient_proto_rawDescOnce.Do(func() { + file_lightclient_proto_rawDescData = protoimpl.X.CompressGZIP(file_lightclient_proto_rawDescData) + }) + return file_lightclient_proto_rawDescData +} + +var file_lightclient_proto_msgTypes = make([]protoimpl.MessageInfo, 10) +var file_lightclient_proto_goTypes = []interface{}{ + (*LightClientAction)(nil), // 0: LightClientAction + (*BtcHeader)(nil), // 1: BtcHeader + (*BtcHeadersLog)(nil), // 2: BtcHeadersLog + (*BtcHeaders)(nil), // 3: BtcHeaders + (*BtcTransaction)(nil), // 4: BtcTransaction + (*Vin)(nil), // 5: Vin + (*Vout)(nil), // 6: Vout + (*BtcSpv)(nil), // 7: BtcSpv + (*TssSignNotify)(nil), // 8: tssSignNotify + (*ReqGetBtcHeader)(nil), // 9: reqGetBtcHeader +} +var file_lightclient_proto_depIdxs = []int32{ + 3, // 0: LightClientAction.btcHeaders:type_name -> BtcHeaders + 1, // 1: BtcHeaders.headers:type_name -> BtcHeader + 5, // 2: BtcTransaction.vin:type_name -> Vin + 6, // 3: BtcTransaction.vout:type_name -> Vout + 4, // [4:4] is the sub-list for method output_type + 4, // [4:4] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name +} + +func init() { file_lightclient_proto_init() } +func file_lightclient_proto_init() { + if File_lightclient_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_lightclient_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*LightClientAction); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightclient_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BtcHeader); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightclient_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BtcHeadersLog); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightclient_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BtcHeaders); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightclient_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BtcTransaction); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightclient_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Vin); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightclient_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Vout); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightclient_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BtcSpv); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightclient_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TssSignNotify); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lightclient_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ReqGetBtcHeader); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_lightclient_proto_msgTypes[0].OneofWrappers = []interface{}{ + (*LightClientAction_BtcHeaders)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_lightclient_proto_rawDesc, + NumEnums: 0, + NumMessages: 10, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_lightclient_proto_goTypes, + DependencyIndexes: file_lightclient_proto_depIdxs, + MessageInfos: file_lightclient_proto_msgTypes, + }.Build() + File_lightclient_proto = out.File + file_lightclient_proto_rawDesc = nil + file_lightclient_proto_goTypes = nil + file_lightclient_proto_depIdxs = nil +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConnInterface + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion6 + +// LightclientClient is the client API for Lightclient service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type LightclientClient interface { +} + +type lightclientClient struct { + cc grpc.ClientConnInterface +} + +func NewLightclientClient(cc grpc.ClientConnInterface) LightclientClient { + return &lightclientClient{cc} +} + +// LightclientServer is the server API for Lightclient service. +type LightclientServer interface { +} + +// UnimplementedLightclientServer can be embedded to have forward compatible implementations. +type UnimplementedLightclientServer struct { +} + +func RegisterLightclientServer(s *grpc.Server, srv LightclientServer) { + s.RegisterService(&_Lightclient_serviceDesc, srv) +} + +var _Lightclient_serviceDesc = grpc.ServiceDesc{ + ServiceName: "Lightclient", + HandlerType: (*LightclientServer)(nil), + Methods: []grpc.MethodDesc{}, + Streams: []grpc.StreamDesc{}, + Metadata: "lightclient.proto", +} diff --git a/plugin/dapp/lightclient/lighttypes/utils.go b/plugin/dapp/lightclient/lighttypes/utils.go new file mode 100644 index 0000000000..0b4dbfe75c --- /dev/null +++ b/plugin/dapp/lightclient/lighttypes/utils.go @@ -0,0 +1,25 @@ +package lighttypes + +import ( + "github.com/btcsuite/btcd/chaincfg" +) + +// GetBtcChainParams 获取比特币链参数 +func GetBtcChainParams(netName string) *chaincfg.Params { + switch netName { + case "mainnet", "": + return &chaincfg.MainNetParams + case "testnet3", "testnet": + return &chaincfg.TestNet3Params + case "testnet4": + return &chaincfg.TestNet4Params + case "regtest": + return &chaincfg.RegressionNetParams + case "simnet": + return &chaincfg.SimNetParams + case "signet": + return &chaincfg.SigNetParams + default: + return &chaincfg.MainNetParams + } +} diff --git a/plugin/dapp/lightclient/plugin.go b/plugin/dapp/lightclient/plugin.go new file mode 100644 index 0000000000..a6fc1d32d4 --- /dev/null +++ b/plugin/dapp/lightclient/plugin.go @@ -0,0 +1,23 @@ +package types + +import ( + "github.com/33cn/chain33/pluginmgr" + "github.com/33cn/plugin/plugin/dapp/lightclient/commands" + "github.com/33cn/plugin/plugin/dapp/lightclient/executor" + lightclienttypes "github.com/33cn/plugin/plugin/dapp/lightclient/lighttypes" + "github.com/33cn/plugin/plugin/dapp/lightclient/rpc" +) + +/* + * 初始化dapp相关的组件 + */ + +func init() { + pluginmgr.Register(&pluginmgr.PluginBase{ + Name: lightclienttypes.LightclientX, + ExecName: executor.GetName(), + Exec: executor.Init, + Cmd: commands.Cmd, + RPC: rpc.Init, + }) +} diff --git a/plugin/dapp/lightclient/proto/Makefile b/plugin/dapp/lightclient/proto/Makefile new file mode 100644 index 0000000000..446c4d8473 --- /dev/null +++ b/plugin/dapp/lightclient/proto/Makefile @@ -0,0 +1,2 @@ +all: + bash ./create_protobuf.sh diff --git a/plugin/dapp/lightclient/proto/create_protobuf.sh b/plugin/dapp/lightclient/proto/create_protobuf.sh new file mode 100644 index 0000000000..c2a15dd687 --- /dev/null +++ b/plugin/dapp/lightclient/proto/create_protobuf.sh @@ -0,0 +1,4 @@ +#!/bin/bash +# proto生成命令,将pb.go文件生成到types/目录下, chain33_path支持引用chain33框架的proto文件 +chain33_path=$(go list -f '{{.Dir}}' "github.com/33cn/chain33") +protoc --go_out=plugins=grpc:../lighttypes ./*.proto --proto_path=. --proto_path="${chain33_path}/types/proto/" diff --git a/plugin/dapp/lightclient/proto/lightclient.proto b/plugin/dapp/lightclient/proto/lightclient.proto new file mode 100644 index 0000000000..c9bb6903ac --- /dev/null +++ b/plugin/dapp/lightclient/proto/lightclient.proto @@ -0,0 +1,79 @@ +syntax = "proto3"; + +option go_package = "../lighttypes"; + +message LightClientAction { + int32 ty = 1; + oneof value { + BtcHeaders btcHeaders = 2; + } +} + +message BtcHeader { + string hash = 1; + uint64 confirmations = 2; + uint64 height = 3; + uint32 version = 4; + string merkleRoot = 5; + int64 time = 6; + uint64 nonce = 7; + int64 bits = 8; + int64 difficulty = 9; + string previousHash = 10; + string nextHash = 11; +} + +message BtcHeadersLog { + uint64 confirmations = 1; + uint64 lastHeight = 2; + uint64 commitHeight = 3; + string lastHash = 4; + string commitHash = 5; +} + +message BtcHeaders { + repeated BtcHeader headers = 1; +} + +message BtcTransaction { + string hash = 1; + uint64 blockHeight = 2; + repeated Vin vin = 3; + repeated Vout vout = 4; + int64 time = 5; + uint64 confirmations = 6; +} + +message Vin { + string address = 1; + uint64 Value = 2; +} + +message Vout { + bool coinbase = 1; + string address = 2; + uint64 Value = 3; +} + +message BtcSpv { + string txHash = 1; + int64 time = 2; + uint64 height = 3; + string blockHash = 4; + uint32 txIndex = 5; + repeated bytes branchProof = 6; +} + +message tssSignNotify { + repeated int64 inputAmounts = 1; + bytes payload = 2; + string txType = 3; + bytes btcTxData = 4; + repeated string signers = 5; +} + +message reqGetBtcHeader { + uint64 height = 1; +} + +service Lightclient {} diff --git a/plugin/dapp/lightclient/rpc/lightclient/btc/btcd.go b/plugin/dapp/lightclient/rpc/lightclient/btc/btcd.go new file mode 100644 index 0000000000..734b57af64 --- /dev/null +++ b/plugin/dapp/lightclient/rpc/lightclient/btc/btcd.go @@ -0,0 +1,291 @@ +// Copyright Fuzamei Corp. 2018 All Rights Reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package btc + +import ( + "errors" + "strconv" + "sync" + "time" + + log "github.com/33cn/chain33/common/log/log15" + "github.com/33cn/chain33/common/merkle" + ty "github.com/33cn/plugin/plugin/dapp/lightclient/lighttypes" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/rpcclient" +) + +// BtcClient interface +type BtcClient interface { + Start() error + Stop() error + GetLatestBlock() (*chainhash.Hash, uint64, error) + GetBlockHeader(height uint64) (*ty.BtcHeader, error) + GetSPV(height uint64, txHash string) (*ty.BtcSpv, error) + GetTransaction(hash string) (*ty.BtcTransaction, error) + Ping() +} + +type ( + blockStamp struct { + Height int32 + Hash chainhash.Hash + } + + blockMeta struct { + blockStamp + Time time.Time + } + + clientConnected struct{} + blockConnected blockMeta + blockDisconnected blockMeta +) + +type params struct { + *chaincfg.Params + RPCClientPort string + RPCServerPort string +} + +var mainNetParams = params{ + Params: &chaincfg.MainNetParams, + RPCClientPort: "8334", + RPCServerPort: "8332", +} + +type btcdClient struct { + rpcClient *rpcclient.Client + connConfig *rpcclient.ConnConfig + chainParams *chaincfg.Params + reconnectAttempts int + enqueueNotification chan interface{} + dequeueNotification chan interface{} + currentBlock chan *blockStamp + quit chan struct{} + wg sync.WaitGroup + started bool + quitMtx sync.Mutex +} + +func newBtcd(config *rpcclient.ConnConfig, reconnectAttempts int) (BtcClient, error) { + if reconnectAttempts < 0 { + return nil, errors.New("ReconnectAttempts must be positive") + } + client := &btcdClient{ + connConfig: config, + chainParams: mainNetParams.Params, + reconnectAttempts: reconnectAttempts, + enqueueNotification: make(chan interface{}), + dequeueNotification: make(chan interface{}), + currentBlock: make(chan *blockStamp), + quit: make(chan struct{}), + } + + rpcClient, err := rpcclient.New(client.connConfig, nil) + if err != nil { + return nil, err + } + client.rpcClient = rpcClient + return client, nil +} + +func (b *btcdClient) Start() error { + err := b.rpcClient.Connect(b.reconnectAttempts) + if err != nil { + return err + } + + // Verify that the server is running on the expected network. + net, err := b.rpcClient.GetCurrentNet() + if err != nil { + b.rpcClient.Disconnect() + return err + } + if net != b.chainParams.Net { + b.rpcClient.Disconnect() + return errors.New("mismatched networks") + } + + b.quitMtx.Lock() + b.started = true + b.quitMtx.Unlock() + + return nil +} + +func (b *btcdClient) Stop() error { + b.quitMtx.Lock() + select { + case <-b.quit: + default: + close(b.quit) + b.rpcClient.Shutdown() + + if !b.started { + close(b.dequeueNotification) + } + } + b.quitMtx.Unlock() + return nil +} + +func (b *btcdClient) WaitForShutdown() { + b.rpcClient.WaitForShutdown() + b.wg.Wait() +} + +func (b *btcdClient) Notifications() <-chan interface{} { + return b.dequeueNotification +} + +func (b *btcdClient) BlockStamp() (*blockStamp, error) { + select { + case bs := <-b.currentBlock: + return bs, nil + case <-b.quit: + return nil, errors.New("disconnected") + } +} + +// POSTClient creates the equivalent HTTP POST rpcclient.Client. +func (b *btcdClient) POSTClient() (*rpcclient.Client, error) { + configCopy := *b.connConfig + configCopy.HTTPPostMode = true + return rpcclient.New(&configCopy, nil) +} + +func (b *btcdClient) GetSPV(height uint64, txHash string) (*ty.BtcSpv, error) { + hash, err := chainhash.NewHashFromStr(txHash) + if err != nil { + return nil, err + } + + ret, err := b.rpcClient.GetRawTransactionVerbose(hash) + if err != nil { + return nil, err + } + + blockHash, err := chainhash.NewHashFromStr(ret.BlockHash) + if err != nil { + return nil, err + } + + block, err := b.rpcClient.GetBlockVerbose(blockHash) + if err != nil { + return nil, err + } + var txIndex uint32 + txs := make([][]byte, 0, len(block.Tx)) + for index, tx := range block.Tx { + if txHash == tx { + txIndex = uint32(index) + } + hash, err := merkle.NewHashFromStr(tx) + if err != nil { + return nil, err + } + txs = append(txs, hash.CloneBytes()) + } + proof := merkle.GetMerkleBranch(txs, txIndex) + spv := &ty.BtcSpv{ + TxHash: txHash, + Time: block.Time, + Height: uint64(block.Height), + BlockHash: block.Hash, + TxIndex: txIndex, + BranchProof: proof, + } + return spv, nil +} + +func (b *btcdClient) GetTransaction(hash string) (*ty.BtcTransaction, error) { + txHash, err := chainhash.NewHashFromStr(hash) + if err != nil { + return nil, err + } + tx, err := b.rpcClient.GetRawTransactionVerbose(txHash) + if err != nil { + return nil, err + } + + blockHash, err := chainhash.NewHashFromStr(tx.BlockHash) + if err != nil { + return nil, err + } + + header, err := b.rpcClient.GetBlockHeaderVerbose(blockHash) + if err != nil { + return nil, err + } + + btxTx := &ty.BtcTransaction{} + btxTx.Hash = hash + btxTx.Time = tx.Time + btxTx.BlockHeight = uint64(header.Height) + vin := make([]*ty.Vin, len(tx.Vin)) + for index, in := range tx.Vin { + var v ty.Vin + // v.Address = in. + v.Value = uint64(float64(in.Vout) * coinsPrecision) + vin[index] = &v + } + btxTx.Vin = vin + vout := make([]*ty.Vout, len(tx.Vout)) + for index, in := range tx.Vout { + var out ty.Vout + out.Value = uint64(in.Value * coinsPrecision) + out.Address = in.ScriptPubKey.Addresses[0] + vout[index] = &out + } + btxTx.Vout = vout + return btxTx, nil +} + +func (b *btcdClient) GetBlockHeader(height uint64) (*ty.BtcHeader, error) { + hash, err := b.rpcClient.GetBlockHash(int64(height)) + if err != nil { + return nil, err + } + header, err := b.rpcClient.GetBlockHeaderVerbose(hash) + if err != nil { + return nil, err + } + + bits, err := strconv.ParseInt(header.Bits, 16, 32) + if err != nil { + return nil, err + } + + h := &ty.BtcHeader{ + Hash: header.Hash, + Confirmations: uint64(header.Confirmations), + Height: uint64(header.Height), + MerkleRoot: header.MerkleRoot, + Time: header.Time, + Nonce: header.Nonce, + Bits: bits, + PreviousHash: header.PreviousHash, + NextHash: header.NextHash, + Version: uint32(header.Version), + } + return h, nil + +} + +func (b *btcdClient) GetLatestBlock() (*chainhash.Hash, uint64, error) { + hash, height, err := b.rpcClient.GetBestBlock() + return hash, uint64(height), err +} + +func (b *btcdClient) Ping() { + hash, height, err := b.rpcClient.GetBestBlock() + if err != nil { + log.Error("btcdClient ping", "error", err) + } + + log.Info("btcdClient ping", "latest Hash: ", hash.String(), "latest height", height) +} diff --git a/plugin/dapp/lightclient/rpc/lightclient/btc/btcd_test.go b/plugin/dapp/lightclient/rpc/lightclient/btc/btcd_test.go new file mode 100644 index 0000000000..66b9fd6ef5 --- /dev/null +++ b/plugin/dapp/lightclient/rpc/lightclient/btc/btcd_test.go @@ -0,0 +1,80 @@ +// Copyright Fuzamei Corp. 2018 All Rights Reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package btc + +import ( + "io/ioutil" + "path/filepath" + "testing" + + "github.com/33cn/chain33/common/merkle" + "github.com/btcsuite/btcd/rpcclient" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +type suiteBctd struct { + // Include our basic suite logic. + suite.Suite + btc BtcClient +} + +func TestRunSuiteBtcd(t *testing.T) { + btcd := new(suiteBctd) + suite.Run(t, btcd) +} + +func (s *suiteBctd) SetupSuite() { + reconnectAttempts := 3 + certs, _ := ioutil.ReadFile(filepath.Join("./", "rpc.cert")) + connCfg := &rpcclient.ConnConfig{ + Host: "127.0.0.1:18556", + User: "root", + Pass: "1314", + HTTPPostMode: true, + Certificates: certs, + } + s.btc, _ = newBtcd(connCfg, reconnectAttempts) +} + +func (s *suiteBctd) TestGetBlockHeader() { + blockZeroHeader, err := s.btc.GetBlockHeader(0) + s.NotNil(err) + s.T().Log(blockZeroHeader) +} + +func (s *suiteBctd) TestGetLatestBlock() { + latestBLock, height, err := s.btc.GetLatestBlock() + s.NotNil(err) + s.Nil(latestBLock) + s.T().Log(latestBLock) + s.T().Log(height) +} + +func (s *suiteBctd) TestGetTransaction() { + tx, err := s.btc.GetTransaction("6359f0868171b1d194cbee1af2f16ea598ae8fad666d9b012c8ed2b79a236ec4") + s.NotNil(err) + s.Nil(tx) +} + +func (s *suiteBctd) TestGetSPV() { + spv, err := s.btc.GetSPV(22448, "aad85f52da28f808822aadfee72b8df23e2591a22ea5ef3cbc6592681a4baa2e") + s.NotNil(err) + s.Nil(spv) +} + +func TestOneTxMerkle(t *testing.T) { + tx0string := "b86f5ef1da8ddbdb29ec269b535810ee61289eeac7bf2b2523b494551f03897c" + tx0hash, err := merkle.NewHashFromStr(tx0string) + assert.Nil(t, err) + tx0byte := tx0hash.CloneBytes() + leaves := make([][]byte, 1) + leaves[0] = tx0byte + t.Log(leaves) + bitHash := merkle.GetMerkleRoot(leaves) + t.Log(bitHash) + hash := merkle.GetMerkleBranch(leaves, 0) + t.Log(hash) +} diff --git a/plugin/dapp/lightclient/rpc/lightclient/btc/btcweb.go b/plugin/dapp/lightclient/rpc/lightclient/btc/btcweb.go new file mode 100644 index 0000000000..9913a29be4 --- /dev/null +++ b/plugin/dapp/lightclient/rpc/lightclient/btc/btcweb.go @@ -0,0 +1,147 @@ +// Copyright Fuzamei Corp. 2018 All Rights Reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package btc + +import ( + "crypto/tls" + "encoding/json" + "errors" + "fmt" + + log "github.com/33cn/chain33/common/log/log15" + "github.com/33cn/chain33/common/merkle" + ty "github.com/33cn/plugin/plugin/dapp/lightclient/lighttypes" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/valyala/fasthttp" +) + +type btcWeb struct { + urlRoot string + httpClient *fasthttp.Client +} + +func newBtcWeb() (BtcClient, error) { + b := &btcWeb{ + urlRoot: "https://blockchain.info", + httpClient: &fasthttp.Client{TLSConfig: &tls.Config{InsecureSkipVerify: true}}, + } + return b, nil +} + +func (b *btcWeb) Start() error { + return nil +} + +func (b *btcWeb) Stop() error { + return nil +} + +func (b *btcWeb) GetBlockHeader(height uint64) (*ty.BtcHeader, error) { + block, err := b.getBlock(height) + if err != nil { + return nil, err + } + return block.BtcHeader(), nil +} + +func (b *btcWeb) getBlock(height uint64) (*block, error) { + if height < 0 { + return nil, errors.New("height < 0") + } + url := fmt.Sprintf("%s/block-height/%d?format=json", b.urlRoot, height) + data, err := b.requestURL(url) + if err != nil { + return nil, err + } + var blocks = blocks{} + err = json.Unmarshal(data, &blocks) + if err != nil { + return nil, err + } + block := blocks.Blocks[0] + return &block, nil +} + +func (b *btcWeb) GetLatestBlock() (*chainhash.Hash, uint64, error) { + url := b.urlRoot + "/latestblock" + data, err := b.requestURL(url) + if err != nil { + return nil, 0, err + } + var blocks = latestBlock{} + err = json.Unmarshal(data, &blocks) + if err != nil { + return nil, 0, err + } + + hash, err := chainhash.NewHashFromStr(blocks.Hash) + if err != nil { + return nil, 0, err + } + + return hash, blocks.Height, nil +} + +func (b *btcWeb) Ping() { + hash, height, err := b.GetLatestBlock() + if err != nil { + log.Error("btcWeb ping", "error", err) + } + log.Info("btcWeb ping", "latest Hash: ", hash.String(), "latest height", height) +} + +func (b *btcWeb) GetTransaction(hash string) (*ty.BtcTransaction, error) { + url := b.urlRoot + "/rawtx/" + hash + data, err := b.requestURL(url) + if err != nil { + return nil, err + } + var tx = transactionResult{} + err = json.Unmarshal(data, &tx) + if err != nil { + return nil, err + } + return tx.BtcTransaction(), nil +} + +func (b *btcWeb) GetSPV(height uint64, txHash string) (*ty.BtcSpv, error) { + block, err := b.getBlock(height) + if err != nil { + return nil, err + } + var txIndex uint32 + txs := make([][]byte, 0, len(block.Tx)) + for index, tx := range block.Tx { + if txHash == tx.Hash { + txIndex = uint32(index) + } + hash, err := merkle.NewHashFromStr(tx.Hash) + if err != nil { + return nil, err + } + txs = append(txs, hash.CloneBytes()) + } + proof := merkle.GetMerkleBranch(txs, txIndex) + spv := &ty.BtcSpv{ + TxHash: txHash, + Time: block.Time, + Height: block.Height, + BlockHash: block.Hash, + TxIndex: txIndex, + BranchProof: proof, + } + return spv, nil +} + +func (b *btcWeb) requestURL(url string) ([]byte, error) { + status, body, err := b.httpClient.Get(nil, url) + if err != nil { + return nil, err + } + if status != fasthttp.StatusOK { + return nil, fmt.Errorf("%d", status) + } + return body, nil +} diff --git a/plugin/dapp/lightclient/rpc/lightclient/btc/btcweb_test.go b/plugin/dapp/lightclient/rpc/lightclient/btc/btcweb_test.go new file mode 100644 index 0000000000..e1c7805af8 --- /dev/null +++ b/plugin/dapp/lightclient/rpc/lightclient/btc/btcweb_test.go @@ -0,0 +1,58 @@ +// Copyright Fuzamei Corp. 2018 All Rights Reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package btc + +import ( + "testing" + + "github.com/stretchr/testify/suite" +) + +type suiteWeb struct { + // Include our basic suite logic. + suite.Suite + btc BtcClient +} + +func TestRunSuiteWeb(t *testing.T) { + web := new(suiteWeb) + suite.Run(t, web) +} + +func (s *suiteWeb) SetupSuite() { + s.btc, _ = newBtcWeb() +} + +func (s *suiteWeb) TestGetBlockHeader() { + blockZeroHeader, err := s.btc.GetBlockHeader(0) + //s.Nil(err) + s.T().Log(err) + s.T().Log(blockZeroHeader) +} + +func (s *suiteWeb) TestGetLatestBlock() { + latestBLock, height, err := s.btc.GetLatestBlock() + //s.Nil(err) + //s.NotNil(latestBLock) + s.T().Log(err) + s.T().Log(latestBLock) + s.T().Log(height) +} + +func (s *suiteWeb) TestGetTransaction() { + tx, err := s.btc.GetTransaction("6359f0868171b1d194cbee1af2f16ea598ae8fad666d9b012c8ed2b79a236ec4") + //s.Nil(err) + //s.NotNil(tx) + s.T().Log(tx) + s.T().Log(err) +} + +func (s *suiteWeb) TestGetSPV() { + spv, err := s.btc.GetSPV(22448, "aad85f52da28f808822aadfee72b8df23e2591a22ea5ef3cbc6592681a4baa2e") + //s.Nil(err) + //s.NotNil(spv) + s.T().Log(err) + s.T().Log(spv) +} diff --git a/plugin/dapp/lightclient/rpc/lightclient/btc/client.go b/plugin/dapp/lightclient/rpc/lightclient/btc/client.go new file mode 100644 index 0000000000..648744536c --- /dev/null +++ b/plugin/dapp/lightclient/rpc/lightclient/btc/client.go @@ -0,0 +1,41 @@ +package btc + +import ( + "context" + "encoding/json" + + "github.com/33cn/chain33/queue" + "github.com/33cn/chain33/types" + + "github.com/33cn/chain33/client" + "github.com/33cn/plugin/plugin/dapp/lightclient/rpc/lightclient" +) + +func init() { + lightclient.Register("btc", newClient) +} + +type btcLight struct { + ctx context.Context + cli BtcClient + cfg config + api client.QueueProtocolAPI +} + +func newClient() lightclient.Lighter { + return &btcLight{} +} + +func (b *btcLight) Init(ctx context.Context, q queue.Queue, cfg *lightclient.Config) error { + + b.ctx = ctx + b.api, _ = client.New(q.Client(), nil) + subCfg, _ := json.Marshal(cfg.Btc) + types.MustDecode(subCfg, &b.cfg) + + return nil +} + +func (b *btcLight) Start() { + +} diff --git a/plugin/dapp/lightclient/rpc/lightclient/btc/config.go b/plugin/dapp/lightclient/rpc/lightclient/btc/config.go new file mode 100644 index 0000000000..e6a7a8e25c --- /dev/null +++ b/plugin/dapp/lightclient/rpc/lightclient/btc/config.go @@ -0,0 +1,58 @@ +// Copyright Fuzamei Corp. 2018 All Rights Reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package btc + +import ( + "os" + + "github.com/btcsuite/btcd/rpcclient" +) + +// btc client config +type config struct { + ID string + Host string + Endpoint string + User string + Pass string + DisableTLS bool + CertFile string + Proxy string + ProxyUser string + ProxyPass string + DisableAutoReconnect bool + DisableConnectOnNew bool + HTTPPostMode bool + EnableBCInfoHacks bool + ReconnectAttempts int +} + +// transfer to connection config +func (c *config) toConnConfig() *rpcclient.ConnConfig { + conn := &rpcclient.ConnConfig{} + conn.Host = c.Host + conn.Endpoint = c.Endpoint + conn.User = c.User + conn.Pass = c.Pass + conn.DisableTLS = c.DisableTLS + conn.Proxy = c.Proxy + conn.ProxyUser = c.ProxyUser + conn.ProxyPass = c.ProxyPass + conn.DisableAutoReconnect = c.DisableAutoReconnect + conn.DisableConnectOnNew = c.DisableConnectOnNew + conn.HTTPPostMode = c.HTTPPostMode + conn.EnableBCInfoHacks = c.EnableBCInfoHacks + + if c.CertFile == "" { + return conn + } + + certs, err := os.ReadFile(c.CertFile) + if err != nil { + panic(err) + } + conn.Certificates = certs + return conn +} diff --git a/plugin/dapp/lightclient/rpc/lightclient/btc/type.go b/plugin/dapp/lightclient/rpc/lightclient/btc/type.go new file mode 100644 index 0000000000..f064b0f496 --- /dev/null +++ b/plugin/dapp/lightclient/rpc/lightclient/btc/type.go @@ -0,0 +1,145 @@ +// Copyright Fuzamei Corp. 2018 All Rights Reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package btc + +import ( + _ "github.com/33cn/chain33/system/address" + ty "github.com/33cn/plugin/plugin/dapp/lightclient/lighttypes" +) + +var ( + currentBtcBlockheightKey = []byte("000007bc077cc540821f06280c4c2f04e036931f21ea3b1bf509b972cbbef5ca") + firstHeaderKey = []byte("1e1ffda1446d62a96b7ba9347a35ecdb1fad2379f35041d32e82a7b97646b9ff") + privateKey = []byte("privateKey-relayd") + coinsPrecision = 1e8 +) + +type latestBlock struct { + Hash string `json:"hash"` + Time int64 `json:"time"` + BlockIndex uint64 `json:"block_index"` + Height uint64 `json:"height"` + TxIndexes []uint64 `json:"txIndexes"` +} + +func (b *block) BtcHeader() *ty.BtcHeader { + return &ty.BtcHeader{ + Hash: b.Hash, + Height: b.Height, + MerkleRoot: b.MerkleRoot, + Time: b.Time, + Nonce: b.Nonce, + Bits: b.Bits, + PreviousHash: b.PrevBlock, + Version: uint32(b.Ver), + } +} + +type block struct { + // header + Hash string `json:"hash"` + Ver uint64 `json:"ver"` + PrevBlock string `json:"prev_block"` + MerkleRoot string `json:"mrkl_root"` + Time int64 `json:"time"` + Bits int64 `json:"bits"` + Nonce uint64 `json:"nonce"` + Fee float64 `json:"fee,omitempty"` + TxNum uint64 `json:"n_tx"` + Size uint64 `json:"size"` + BlockIndex uint64 `json:"block_index"` + MainChain bool `json:"main_chain"` + Height uint64 `json:"height"` + ReceivedTime int64 `json:"received_time"` + RelayedBy string `json:"relayed_by"` + Tx []transactionDetails `json:"tx"` +} + +type transactionDetails struct { + LockTime int64 `json:"lock_time"` + Ver uint64 `json:"ver"` + Size uint64 `json:"size"` + Inputs []txInput `json:"inputs"` + Weight int64 `json:"weight"` + Time int64 `json:"time"` + TxIndex uint64 `json:"tx_index"` + VinSz uint64 `json:"vin_sz"` + Hash string `json:"hash"` + VoutSz uint64 `json:"vout_sz"` + RelayedBy string `json:"relayed_by"` + Outs []txOut `json:"out"` +} + +type txInput struct { + Sequence int64 `json:"sequence"` + Witness string `json:"witness"` + Script string `json:"script"` +} + +type txOut struct { + Spent bool `json:"spent"` + TxIndex uint64 `json:"tx_index"` + Type int `json:"type"` + Address string `json:"addr"` + Value uint64 `json:"value"` + N uint64 `json:"n"` + Script string `json:"script"` +} + +type blocks struct { + Blocks []block `json:"blocks"` +} + +type transactionResult struct { + Ver uint `json:"ver"` + Inputs []inputs `json:"inputs"` + Weight int64 `json:"weight"` + BlockHeight uint64 `json:"block_height"` + RelayedBy string `json:"relayed_by"` + Out []txOut `json:"out"` + LockTime int64 `json:"lock_time"` + Size uint64 `json:"size"` + DoubleSpend bool `json:"double_spend"` + Time int64 `json:"time"` + TxIndex uint64 `json:"tx_index"` + VinSz uint64 `json:"vin_sz"` + Hash string `json:"hash"` + VoutSz uint64 `json:"vout_sz"` +} + +type inputs struct { + Sequence uint `json:"sequence"` + Witness string `json:"witness"` + PrevOut txOut `json:"prev_out"` + Script string `json:"script"` +} + +func (t *transactionResult) BtcTransaction() *ty.BtcTransaction { + btcTx := &ty.BtcTransaction{} + btcTx.Hash = t.Hash + btcTx.Time = t.Time + btcTx.BlockHeight = t.BlockHeight + + vin := make([]*ty.Vin, len(t.Inputs)) + for index, in := range t.Inputs { + var v ty.Vin + v.Value = in.PrevOut.Value + v.Address = in.PrevOut.Address + vin[index] = &v + } + btcTx.Vin = vin + + vout := make([]*ty.Vout, len(t.Out)) + for index, in := range t.Out { + var out ty.Vout + out.Value = in.Value + out.Address = in.Address + vout[index] = &out + // TODO + // vout[index].Coinbase + } + btcTx.Vout = vout + return btcTx +} diff --git a/plugin/dapp/lightclient/rpc/lightclient/neutrino/CONFIG.md b/plugin/dapp/lightclient/rpc/lightclient/neutrino/CONFIG.md new file mode 100644 index 0000000000..6d3247bd44 --- /dev/null +++ b/plugin/dapp/lightclient/rpc/lightclient/neutrino/CONFIG.md @@ -0,0 +1,295 @@ +# RGBX 配置说明手册(运维/测试) + +本文档仅说明配置项及含义,覆盖: + +- Chain33 主链配置 +- Chain33 平行链配置 +- Neutrino 子配置(`rpc.sub.light.neutrino`) +- Bitcoin 节点关键配置 + +## 1. 配置文件范围 + +建议按职责拆分为三类配置: + +- 主链配置(示例:`chain33.toml`) +- 平行链配置(示例:`chain33.para.toml`) +- BTC 节点配置(示例:`bitcoin.conf` 或等价启动参数) + +其中跨链相关核心段落在主链和平行链里分别是: + +- 主链:`[exec.sub.lightclient]`、`[exec.sub.rgbx]` +- 平行链:`[rpc.sub.light]`、`[rpc.sub.light.neutrino]`、`[rpc.sub.light.neutrino.btcRPC]`、`[rpc.sub.light.neutrino.tss]` + +## 2. Chain33 主链配置项 + +### 2.1 `[exec.sub.lightclient]` + +- `btcNetName` + - 含义:BTC 网络类型 + - 常用值:`regtest`、`testnet`、`mainnet` +- `commitAddress` + - 含义:提交 lightclient 相关交易的授权地址 + - 要求:应为受控地址,且与运维密钥管理策略一致 +- `allowRegtestTimeWarp` + - 含义:仅用于 regtest 测试场景的时间容错开关 + - 建议:仅在 regtest 打开,生产网络关闭 + +### 2.2 `[exec.sub.rgbx]` + +- `commitAddress` + - 含义:提交 RGBX 关键交易(如确认交易)的授权地址 + - 要求:应与对应签名私钥匹配 +- `crossChainAssetPrefix` + - 含义:跨链映射资产前缀 + - 示例:`X`,则 BTC 映射资产为 `XBTC` +- `guardianParachainTitle` + - 含义:受信平行链标题(para title) + - 要求:与平行链 `Title` 完全一致 + +## 3. Chain33 平行链配置项 + +### 3.1 平行链基础配置(按模块归属) + +- 根级:`Title` + - 含义:平行链标题 + - 要求:与主链 `guardianParachainTitle` 一致 +- `[rpc.parachain]`:`mainChainGrpcAddr` + - 含义:主链 gRPC 地址 + - 用途:平行链访问主链查询/提交接口 +- `[consensus.sub.para]`:`authAccount` + - 含义:平行链共识节点账户 + - 用途:节点身份与权限校验(跨链节点需与钱包私钥对应) +- `[crypto]`:`enableTSS` + - 含义:是否开启 TSS 功能 + - 建议:跨链节点必须开启 +- `[p2p]`:`types`、`enable`、`waitPid` + - 含义:P2P 网络与 DHT 开关配置 + - 建议:开启 DHT(`types=["dht"]`、`enable=true`),保证 TSS 节点发现与消息分发 +- `[p2p.sub.dht]`:`DHTDataPath` + - 含义:DHT 数据目录 + - 建议:配置稳定持久化路径,避免重启后频繁重建邻居关系 + +### 3.2 `[rpc.sub.light]` + +- `clients` + - 含义:light client 插件列表 + - RGBX 场景固定为:`["neutrino"]` +- `commitAddr` + - 含义:light RPC 使用的提交地址 + - 要求:与对应提交私钥匹配 + +## 4. Neutrino 配置项(平行链) + +以下字段定义来源于 `plugin/dapp/lightclient/rpc/lightclient/neutrino/config.go`。 + +### 4.1 `[rpc.sub.light.neutrino]` + +- `isOfficialNode` (bool) + - `true`:启动官方流程(BTC 同步、充值监听、提现处理、确认提交) + - `false`:不执行官方提交流程,通常用于验证/协作节点 +- `maxPeer` (int) + - 含义:Neutrino 最大对等节点数 + - 默认行为:小于 1 时使用默认值(实现内为 8) +- `blockCacheSize` (uint64) + - 含义:区块缓存大小(字节) + - 默认行为:小于 1MB 时使用默认值(约 20MB) +- `netName` (string) + - 含义:BTC 网络名 + - 要求:与主链 lightclient 的 `btcNetName` 一致 +- `addPeers` ([]string) + - 含义:启动后主动增加的 P2P 节点列表 +- `connectPeers` ([]string) + - 含义:固定连接节点列表 + - 说明:配置后通常只连该列表,不再自动找出站节点 +- `btcBlockInterval` (uint32) + - 含义:BTC 区块同步轮询间隔基准(秒) + - 默认行为:未设置时采用实现默认值 +- `blockConfirmations` (uint32) + - 含义:BTC 确认数门限 + - 影响:区块头提交、pending 拉取、充值/提现确认时机 +- `btcHeaderStartHeight` (uint64) + - 含义:首次提交 BTC header 的起始高度 + - 默认行为:未设置时从 1 开始 +- `maxUtxoRescanTime` (int64) + - 含义:UTXO 重扫超时(单位:小时) + - 特性:0 表示不超时;内部会转为秒 + +### 4.2 `[rpc.sub.light.neutrino.btcRPC]` + +- `host` + - 含义:BTC RPC 地址(host:port) +- `user` + - 含义:BTC RPC 用户名 +- `pass` + - 含义:BTC RPC 密码 +- `mode` + - 含义:RPC 连接模式,支持 `ws`(默认)或 `http` +- `disableTLS` (bool) + - 含义:是否禁用 TLS + - 建议:生产环境使用 TLS +- `certFile` + - 含义:TLS 证书路径(可选) + - 要求:启用 TLS 时路径可读且证书与 `host` 匹配 + +### 4.3 `[rpc.sub.light.neutrino.tss]` + +- `peers` ([]string) + - 含义:TSS 参与节点地址列表 + - 要求:所有参与节点配置一致 +- `threshold` (uint32) + - 含义:阈值签名门限(t-of-n 中的 t) + - 建议:按容错策略设置,且不大于节点总数 +- `rank` (uint32) + - 含义:节点角色标识(用于区分官方/验证角色) + - 要求:同一节点在全网配置必须稳定一致 + +## 5. Bitcoin 节点关键配置项 + +以下示例使用 `bitcoin.conf` 格式说明关键配置(btcd/bitcoin-core 参数名有差异时,以节点实现文档为准): + +```ini +# 网络(与 Chain33 的 btcNetName/netName 保持一致) +regtest=1 +# testnet=1 +# mainnet 默认不需要显式开启 + +# RPC 监听地址(需保证 Neutrino 的 btcRPC.host 可达) +rpcbind=0.0.0.0 +rpcport=18443 + +# RPC 认证(需与 rpc.sub.light.neutrino.btcRPC.user/pass 一致) +rpcuser=root +rpcpassword=1314 + +# TLS / 证书(若启用 TLS,证书路径需与 Neutrino certFile 对应) +# 常见做法是由节点自动生成证书;若禁用 TLS 则需与 disableTLS=true 匹配 +# rpcssl=1 +# rpcsslcertificatechainfile=/path/to/rpc.cert + +# 交易与地址索引(建议开启) +txindex=1 +addrindex=1 + +# 过滤能力(支持neutrino轻节点) +blockfilterindex=1 +peerblockfilters=1 +``` + +字段对照关系: + +- `rpc.sub.light.neutrino.btcRPC.host` <-> `rpcbind/rpcport` +- `rpc.sub.light.neutrino.btcRPC.user` <-> `rpcuser` +- `rpc.sub.light.neutrino.btcRPC.pass` <-> `rpcpassword` +- `rpc.sub.light.neutrino.btcRPC.disableTLS/certFile` <-> TLS/证书配置 +- `rpc.sub.light.neutrino.netName` <-> `regtest/testnet/mainnet` 网络选择 + +补充说明(参考 Bitcoin Core Neutrino 模式文档): + +- Bitcoin Core 需支持 BIP157/BIP158(常见要求为 0.21.0+) +- 首次开启 `blockfilterindex=1` 后,节点会重建过滤索引,过程可能较慢 +- 如果不希望节点对外自动发现,可在 `bitcoin.conf` 配置 `discover=0` + +## 6. TSS 组网说明(当前实现约束) + +当前实现建议采用:**1 个官方节点 + N 个第三方节点**(N >= 2,且满足阈值)。 + +- 官方节点: + - `rpc.sub.light.neutrino.isOfficialNode=true` + - 负责发起业务主流程(BTC 同步、充值监听、提现处理、确认提交) +- 第三方节点: + - `rpc.sub.light.neutrino.isOfficialNode=false` + - 参与 DKG 和签名协作,不发起官方提交流程 +- 角色约定(实践中): + - 官方节点 `rank=0` + - 第三方节点 `rank=1` +- 全体节点必须保持一致: + - `rpc.sub.light.neutrino.tss.peers` + - `rpc.sub.light.neutrino.tss.threshold` +- 组网依赖: + - 平行链需启用 DHT P2P(`[p2p] types=["dht"]` 且 `enable=true`) + +## 7. 配置一致性检查清单 + +上线/联调前建议逐项核对: + +- `btcNetName == netName == BTC 节点 network` +- 主链 `guardianParachainTitle == 平行链 Title` +- `commitAddress/commitAddr/authAccount` 与私钥管理匹配 +- `blockConfirmations` 符合环境安全要求(测试可低,生产应高) +- `btcRPC.host/user/pass/TLS` 与 BTC 节点一致 +- 全部 TSS 节点的 `peers/threshold` 一致,且 `rank` 分配无冲突 + +## 8. 最小示例(仅配置片段) + +```toml +# main +[exec.sub.lightclient] +btcNetName="regtest" +commitAddress="1xxxxxxxxxxxxxxxx" +allowRegtestTimeWarp=true + +[exec.sub.rgbx] +commitAddress="1xxxxxxxxxxxxxxxx" +crossChainAssetPrefix="X" +guardianParachainTitle="user.p.rgbx." + +# para root-level +Title="user.p.rgbx." + +[p2p] +types=["dht"] +enable=true +waitPid=false + +[p2p.sub.dht] +DHTDataPath="paradatadir/p2pstore" + +[rpc.parachain] +mainChainGrpcAddr="127.0.0.1:8802" + +[consensus.sub.para] +authAccount="1xxxxxxxxxxxxxxxx" + +[crypto] +enableTSS=true + +[rpc.sub.light] +clients=["neutrino"] +commitAddr="1xxxxxxxxxxxxxxxx" + +[rpc.sub.light.neutrino] +isOfficialNode=true +netName="regtest" +connectPeers=["127.0.0.1:18444"] +btcBlockInterval=2 +blockConfirmations=1 +maxUtxoRescanTime=60 + +[rpc.sub.light.neutrino.btcRPC] +host="127.0.0.1:18443" +user="root" +pass="1314" +disableTLS=false +certFile="/path/to/rpc.cert" + +[rpc.sub.light.neutrino.tss] +peers=["1addrA","1addrB","1addrC","1addrD"] +threshold=3 +rank=0 # 官方节点;第三方节点配置为 rank=1,且 isOfficialNode=false +``` + +第三方节点相对官方节点的最小差异: + +- `[rpc.sub.light.neutrino] isOfficialNode=false` +- `[rpc.sub.light.neutrino.tss] rank=1` +- 节点自身 `authAccount` / `commitAddr` 使用本节点受控地址 +- `peers` 与 `threshold` 必须与官方节点保持一致 + +## 9. 常见配置错误 + +- 网络不一致:`netName` 与 BTC 实际网络不一致导致校验失败 +- TLS 不匹配:启用 TLS 但 `certFile` 错误或证书主机名不匹配 +- TSS 不一致:节点间 `peers/threshold` 不一致导致 DKG 或签名异常 +- 多官方节点:多个 `isOfficialNode=true` 节点并行处理,导致重复提交/状态竞争 +- 确认数过低:测试通过但生产抗重组能力不足 +- 授权地址不匹配:`commitAddress/commitAddr/authAccount` 与私钥不对应 diff --git a/plugin/dapp/lightclient/rpc/lightclient/neutrino/FUNCTIONAL.md b/plugin/dapp/lightclient/rpc/lightclient/neutrino/FUNCTIONAL.md new file mode 100644 index 0000000000..fc6b14f335 --- /dev/null +++ b/plugin/dapp/lightclient/rpc/lightclient/neutrino/FUNCTIONAL.md @@ -0,0 +1,116 @@ +# 比特币跨链桥功能文档 + +**版本**: v1.1 +**适用对象**: 用户、产品经理、运营 +**最后更新**: 2026-04-14 + +--- + +## 1. 产品概述 + +比特币跨链桥用于在 **Bitcoin** 与 **Chain33** 之间转移 BTC 价值: + +- **充值(BTC -> Chain33)**: BTC 主链转入后,在 Chain33 侧记账映射资产 +- **提现(Chain33 -> BTC)**: Chain33 发起提现后,在 BTC 主链收到真实 BTC + +系统默认由官方节点执行链下监听、证明构建、签名协调和提交确认。 + +--- + +## 2. 快速使用 + +### 2.1 充值(BTC -> Chain33) + +1. 获取 TSS 地址和你的 Chain33 收款地址。 +2. 在 BTC 交易里: + - 向 TSS 地址转入 BTC; + - 带一条 OP_RETURN,内容为 `rgbx:deposit:`。 +3. 等待 BTC 确认(通常按 6 确认口径)。 +4. 官方节点提交证明后,Chain33 侧到账。 + +### 2.2 提现(Chain33 -> BTC) + +1. 在 Chain33 发起 `WithdrawAsset`,填写 BTC 目标地址、金额、费率。 +2. 系统进入 pending,官方节点构造 BTC 提现交易并发起 TSS 签名。 +3. BTC 交易广播并确认后,官方节点提交 `Confirm` 完成结算。 + +--- + +## 3. 用户可见规则 + +### 3.1 充值规则 + +- 充值交易需要可验证承诺: + - 普通地址场景:OP_RETURN 与 `rgbx:deposit:
` 脚本严格匹配; + - UTXO 地址场景:按输入 outpoint 绑定(不是 OP_RETURN 模式)。 +- 充值证明会校验: + - BTC 区块头; + - Merkle 证明; + - 交易内容与金额约束; + - 防重复处理状态。 + +### 3.2 提现规则 + +- 资产在提现发起时会先锁定到合约锁仓地址,确认后再 burn 结算。 +- 提现参数有协议约束(当前实现): + - 最小提现金额:`546` sat; + - 费率范围:`1 ~ 1000` sat/vB。 +- 地址合法性由链上脚本校验逻辑决定(以当前网络参数为准)。 + +### 3.3 防重复提现(Sticky UTXO) + +为约束重试交易,系统会绑定一个 sticky UTXO: + +- 构造提现交易成功后,绑定该交易使用的 sticky UTXO; +- 后续重试必须维持同一 sticky 绑定; +- 非官方签名节点会做一致性校验,不一致则拒签。 + +> 注:当前实现绑定的是交易输入列表中的“最后一个输入”(及对应 outpoint 绑定关系),而非“第一个输入”。 + +--- + +## 4. 常见问题(统一版) + +### Q1: 充值多久到账? + +- 取决于 BTC 确认速度与网络拥堵; +- 系统一般按 6 确认后进入提交流程。 + +### Q2: 充值没到账先查什么? + +1. BTC 交易是否已上链; +2. 确认数是否足够; +3. 承诺内容(OP_RETURN 或 UTXO 绑定)是否正确。 + +### Q3: 提现为什么不是立刻到账? + +提现包含两段时间: + +- Chain33 侧请求 -> TSS 签名与 BTC 广播; +- BTC 广播后等待确认。 + +### Q4: 提现失败会怎样? + +- 系统会重试(构造、签名、广播、确认提交流程); +- 若长期异常,需人工排障(节点可用性、余额、网络)。 + +### Q5: 提现金额为什么会变少? + +BTC 矿工费由提现交易承担,到账金额会扣除实际手续费。 + +--- + +## 5. 安全提示 + +- 充值前先小额测试,确认承诺格式无误。 +- 提现前务必核对 BTC 地址,错误地址不可逆。 +- 保护好私钥/助记词,防止本地资产泄露。 +- 通过官方渠道获取 TSS 地址和工具。 + +--- + +## 6. 范围说明 + +本文件面向“功能使用”。 +架构细节、实现边界、配置字段和状态机请看 `TECHNICAL.md`。 + diff --git a/plugin/dapp/lightclient/rpc/lightclient/neutrino/TECHNICAL.md b/plugin/dapp/lightclient/rpc/lightclient/neutrino/TECHNICAL.md new file mode 100644 index 0000000000..6f1c5c0e8b --- /dev/null +++ b/plugin/dapp/lightclient/rpc/lightclient/neutrino/TECHNICAL.md @@ -0,0 +1,634 @@ +# 比特币跨链桥技术架构文档 + +**版本**: v1.0 +**适用对象**: 开发者、架构师、安全审计人员 +**最后更新**: 2026-04-14 + +--- + +## 目录 + +1. [系统架构](#1-系统架构) +2. [核心概念](#2-核心概念) +3. [安全模型](#3-安全模型) +4. [配置与部署](#4-配置与部署) +5. [代码结构](#5-代码结构) +6. [技术附录](#6-技术附录) + +--- + +## 1. 系统架构 + +### 1.1 整体架构图 + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ Chain33 主链 │ +│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ +│ │ lightclient │ │ rgbx │ │ TSS网络 │ │ +│ │ 合约 │ │ 合约 │ │ (P2P) │ │ +│ │ • BTC区块头存储 │ │ • 充值(Deposit) │ │ • DKG密钥生成 │ │ +│ │ • SPV验证 │ │ • 提现(Withdraw)│ │ • 阈值签名 │ │ +│ │ • 难度校验 │ │ • 确认(Confirm) │ │ • 签名协调 │ │ +│ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ │ +│ │ │ │ │ +│ │◄───── SPV证明 ─────┘ │ │ +│ │ │◄───── 提现请求 ─────┘ │ +│ │◄─────────────────── 确认交易 ─────────────┘ │ +└───────────┼────────────────────┼────────────────────┼─────────────────────┘ + │ │ │ + │ │ │ +┌───────────┼────────────────────┼────────────────────┼─────────────────────┐ +│ │ Bitcoin 主链 │ │ +│ │ ┌─────────────────────────────────────┐ │ │ +│ └──│ BTC P2P 网络 │◄┘ │ +│ │ • 交易广播 │ │ +│ │ • 区块传播 │ │ +│ │ • 全网共识 │ │ +│ └─────────────────────────────────────┘ │ +│ ▲ │ +│ │ 监听新区块/交易 │ +│ ┌─────────┴──────────────────┐ │ +│ │ Neutrino 轻客户端 │ │ +│ │ • BIP157/158 过滤区块 │ │ +│ │ • 仅下载相关区块 │ │ +│ │ • 低带宽需求 │ │ +│ └──────────────────────────────┘ │ +│ 官方节点运行 (BTC全节点或轻节点) │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +### 1.2 模块职责 + +| 模块 | 类型 | 核心职责 | +|------|------|---------| +| **lightclient** | Chain33合约 | 1. 存储/验证BTC区块头
2. 提供SPV验证接口
3. 维护BTC链状态 | +| **rgbx** | Chain33合约 | 1. 充值(Deposit)验证与资产发行
2. 提现(Withdraw)创建Pending记录
3. 确认(Confirm)结算与资产销毁 | +| **neutrino** | 官方节点服务 | 1. 运行轻客户端同步BTC
2. 提交区块头到lightclient
3. 监听Deposit/Withdraw事件
4. 构造BTC交易并广播
5. 提交Confirm交易 | +| **TSS** | 节点网络服务 | 1. DKG分布式密钥生成
2. 阈值签名(提现交易)
3. 签名协调与验证 | + +### 1.3 数据流 + +#### 充值数据流 (BTC → Chain33) + +``` +用户BTC交易 + │ + ▼ +┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ +│ BTC网络广播 │───▶│ Neutrino监听 │───▶│ 官方节点构建 │───▶│ rgbx.Deposit │ +│ │ │ 交易确认 │ │ SPV证明 │ │ 合约验证 │ +└─────────────┘ └─────────────┘ └─────────────┘ └──────┬──────┘ + │ + ▼ + ┌─────────────┐ + │ 发行映射资产 │ + │ 到用户地址 │ + └─────────────┘ +``` + +#### 提现数据流 (Chain33 → BTC) + +``` +用户调用Withdraw + │ + ▼ +┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ +│ rgbx合约 │───▶│ 官方节点监听│───▶│ 构造BTC交易 │───▶│ TSS阈值签名 │ +│ 创建Pending │ │ Pending记录 │ │ (sticky保护)│ │ (多节点参与)│ +└─────────────┘ └─────────────┘ └──────┬──────┘ └──────┬──────┘ + │ │ + └─────────┬─────────┘ + ▼ + ┌─────────────┐ + │ BTC网络广播 │ + │ 提现交易 │ + └──────┬──────┘ + │ + ▼ + ┌─────────────┐ + │ 官方节点监听 │ + │ 交易确认 │ + └──────┬──────┘ + │ + ▼ + ┌─────────────┐ + │ rgbx.Confirm │ + │ 完成结算 │ + └─────────────┘ +``` + +--- + +## 2. 核心概念 + +### 2.1 TSS (阈值签名方案) + +#### 2.1.1 设计原理 + +TSS 实现了**分布式私钥管理**,解决了传统多签的两个问题: +- **单点故障**:没有节点掌握完整私钥 +- **链上成本高**:单个签名即可花费,节省交易大小 + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ 传统多签 (n-of-n) │ +│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ +│ │ 私钥A │ │ 私钥B │ │ 私钥C │ │ +│ └────┬────┘ └────┬────┘ └────┬────┘ │ +│ │ │ │ │ +│ └────────────┼────────────┘ │ +│ ▼ │ +│ ┌─────────┐ │ +│ │ 多签脚本 │ (n个公钥哈希+验签逻辑) │ +│ │ 上链存储 │ │ +│ └─────────┘ │ +│ │ +│ 问题: 交易大,脚本复杂,费用高 │ +└─────────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────────┐ +│ TSS 阈值签名 (t-of-n) │ +│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ +│ │ 私钥份额A│ │ 私钥份额B│ │ 私钥份额C│ │ +│ │ (shard) │ │ (shard) │ │ (shard) │ │ +│ └────┬────┘ └────┬────┘ └────┬────┘ │ +│ │ │ │ │ +│ └────────────┼────────────┘ │ +│ ▼ │ +│ DKG协议聚合 │ +│ │ │ +│ ┌─────▼─────┐ │ +│ │ 共享公钥 │ (单一公钥,链上无特殊脚本) │ +│ │ P2WPKH │ │ +│ └───────────┘ │ +│ │ +│ 优势: 交易小,标准P2WPKH地址,费用低 │ +└─────────────────────────────────────────────────────────────────┘ +``` + +#### 2.1.2 DKG 流程 + +DKG (分布式密钥生成) 协议步骤: + +1. **多项式生成**:各节点独立生成随机多项式,其系数作为该节点的私钥份额 +2. **份额交换**:节点间安全交换承诺值和加密后的私钥份额 +3. **一致性验证**:使用 Feldman VSS 或 Pedersen DKG 验证承诺一致性,确保无恶意节点 +4. **公钥聚合**:通过椭圆曲线点加法聚合所有节点的公钥贡献,生成共享公钥 +5. **地址派生**:将共享公钥哈希后生成标准的 P2WPKH 地址,作为 TSS 收款地址 + +**关键属性**: +- **阈值 t**: 需要至少 t 个节点参与才能生成有效签名 +- **节点数 n**: 总节点数量 (t ≤ n) +- **容错性**: 最多 n-t 个节点掉线仍可工作 + +#### 2.1.3 签名流程 + +TSS 阈值签名协议 (GG18/GG20) 执行流程: + +1. **节点选择**:从 n 个节点中随机选择不少于 t 个节点参与本次签名 +2. **份额计算**:各参与节点使用本地保存的私钥份额独立计算签名片段 +3. **签名聚合**:通过 Lagrange 插值算法将多个签名片段聚合成完整的有效签名 +4. **有效性验证**:验证聚合后的签名是否符合椭圆曲线签名标准 (ECDSA/Schnorr) +5. **交易填充**:将有效签名填入 BTC 交易的 ScriptWitness 字段,完成交易构造 + +### 2.2 SPV 证明 (简化支付验证) + +#### 2.2.1 验证原理 + +SPV 允许在不下载完整区块链的情况下验证交易存在性。 + +``` +交易哈希 (txHash) + │ + ▼ +┌──────────────────────────────────────────────────────────────┐ +│ Merkle树 │ +│ │ +│ [MerkleRoot] │ +│ / \ │ +│ [Hash01] [Hash23] │ +│ / \ / \ │ +│ [Hash0] [Hash1] [Hash2] [Hash3] │ +│ │ │ │ │ │ +│ [tx0] [tx1] [tx2] [tx3] │ +│ ↑ │ +│ 目标交易 │ +│ │ +│ Merkle分支 (Branch): [tx2, Hash3, Hash01] │ +│ 验证: hash(hash(tx2, Hash3), Hash01) == MerkleRoot │ +└──────────────────────────────────────────────────────────────┘ +``` + +#### 2.2.2 链上验证 + +Chain33 合约执行 SPV 验证的五个步骤: + +1. **区块头存在性检查**:验证目标区块头是否已存储在 lightclient 合约中(需官方节点提前提交) +2. **工作量证明验证**:校验区块头是否符合 BTC 网络的难度要求,防止伪造低难度区块 +3. **Merkle 路径计算**:利用提供的 Merkle 分支路径,从目标交易哈希逐层向上计算 +4. **根哈希比对**:将计算得到的 MerkleRoot 与区块头中存储的原始 MerkleRoot 进行比对 +5. **验证结果判定**:若两者一致,则数学证明该交易确实存在于该区块中,验证通过 + +**防攻击设计**: +- 需要等待区块确认数 (通常6个),防止重组攻击 +- 区块头需官方节点提交,依赖诚实假设 +- 轻客户端独立验证区块头连接性 + +### 2.3 Sticky 末输入机制 + +#### 2.3.1 问题背景 + +官方节点负责构造提现交易,存在**潜在作恶或错误风险**: +- 同一笔提现请求构造多个不同交易(重复提现) +- 广播失败后更换所有输入(可能构造冲突交易) + +#### 2.3.2 机制设计 + +Sticky 机制强制要求:**同一提现请求的所有重试必须复用最后一个 UTXO 作为末输入**。 + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Sticky 末输入机制 │ +│ │ +│ 首次构造成功 │ +│ │ │ +│ ▼ │ +│ ┌────────────────┐ │ +│ │ 记录末输入 UTXO │ (chain33Hash → stickyUTXO OutPoint) │ +│ │ 到本地DB │ 存储桶: rgbx-withdraw-sticky-utxo │ +│ └────────┬───────┘ │ +│ │ │ +│ ┌────────▼────────┐ ┌────────────────┐ ┌────────────────┐ │ +│ │ 广播失败 │───▶│ 重试构造 │───▶│ 必须使用末输入 │ │ +│ │ (网络/签名问题) │ │ (buildWithdrawTx)│ │ UTXO-A │ │ +│ └─────────────────┘ └────────────────┘ └───────┬────────┘ │ +│ │ │ +│ 其他输入可更换: UTXO-B/C/D... │ +│ │ │ +│ ┌────────────────────────▼────────┐ │ +│ │ 非官方节点验证 │ │ +│ │ • 检查末输入是否与首次签名一致 │ │ +│ │ • 如果不一致,拒绝签名 │ │ +│ └─────────────────────────────────┘ │ +│ │ +│ Confirm 成功后 │ +│ │ │ +│ ▼ │ +│ ┌────────────────┐ │ +│ │ 清理 sticky 记录│ (删除 stickyUTXO 映射) │ +│ └────────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +#### 2.3.3 实现流程 + +**官方节点 - 构造阶段流程** + +1. **读取 sticky 记录**:首先查询本地数据库,获取该提现请求是否已有 sticky UTXO 记录 +2. **构造交易**:调用交易构建函数,传入 sticky UTXO 约束(如有),函数会锁定选中的 UTXO 并返回锁定列表 +3. **记录末输入**:若为首次构造(sticky 记录为空),将锁定列表中的最后一个 UTXO 作为 sticky 记录持久化到数据库 +4. **失败处理**:若签名或广播失败,释放所有锁定的 UTXO,但保留 sticky 末输入的锁定状态供下次重试使用 + +**非官方节点 - 签名验证流程** + +1. **提取末输入**:从待签名交易的输入列表中取最后一个输入的 OutPoint 作为待验证的 sticky UTXO +2. **链上一致性检查**:验证该 OutPoint 与 chain33 提现哈希的绑定关系是否正确(通过本地映射表验证) +3. **落库状态校验**:查询官方节点是否已将此 sticky UTXO 记录到数据库,比对记录值与实际交易输入是否一致 +4. **拒绝条件**:若任一检查不通过,节点拒绝参与签名,防止官方节点更换输入构造双花交易 + +#### 2.3.4 安全属性 + +| 攻击场景 | Sticky 防护效果 | +|---------|----------------| +| 官方节点构造双花交易 | 非官方节点检测到末输入不一致,拒绝签名 | +| 广播失败后更换所有输入 | 末输入必须保持,其他输入变化不影响安全性 | +| 重放旧签名 | 本地状态记录已签名,重复请求被拒绝 | +| 官方节点删除 sticky 记录 | 其他节点本地状态仍然存在,可追溯 | + +--- + +## 3. 安全模型 + +### 3.1 威胁矩阵 + +| 威胁 | 攻击者 | 影响 | 防护措施 | 残余风险 | +|------|--------|------|---------|---------| +| **重复提现** | 官方节点 | 资金双重支出 | Sticky末输入+非官方验证 | 官方+多数TSS节点共谋 | +| **伪造充值** | 任何人 | 无成本获得资产 | SPV证明+OP_RETURN校验 | 51%攻击BTC链 | +| **拒绝服务** | 任何人 | 系统不可用 | 多节点冗余+超时重试 | 所有官方节点下线 | +| **TSS私钥泄露** | 节点入侵者 | 盗取资金 | 阈值签名,无私钥整体 | 同时入侵t个节点 | +| **链重组** | BTC矿工 | 充值/状态回滚 | 6确认+轻客户端跟踪 | >6块深度重组 | +| **确认劫持** | 中间人 | 无法完成结算 | 官方节点多实例+重试 | 所有官方节点被隔离 | + +### 3.2 信任假设 + +#### 最小信任假设 + +1. **TSS 诚实多数**: 至少 t 个 TSS 节点诚实(阈值签名安全基础) +2. **官方节点诚实**: 至少 1 个官方节点诚实运行(区块头/SPV数据来源) +3. **BTC 网络安全**: BTC 链未遭受 51% 攻击(外部依赖) + +#### 无需信任 + +- 无需信任任何单一机构或个人 +- 无需信任 Chain33 验证者(合约逻辑开源可验证) +- 无需信任第三方托管方 + +### 3.3 关键安全机制 + +#### 3.3.1 充值安全 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 充值验证流程 │ +│ │ +│ 1. SPV证明验证 │ +│ ├─ 区块头存在 (lightclient合约) │ +│ ├─ 工作量证明有效 (难度累积) │ +│ └─ Merkle证明正确 (交易在区块中) │ +│ │ +│ 2. 交易内容验证 │ +│ ├─ 有输出到 TSS地址 (金额匹配) │ +│ ├─ OP_RETURN格式正确 (rgbx:deposit:目标地址) │ +│ └─ 目标地址有效 (Chain33地址格式) │ +│ │ +│ 3. 防重放验证 │ +│ └─ 该BTC交易未处理过 (本地状态记录) │ +│ │ +│ 全部通过 ──► 发行映射资产 │ +└─────────────────────────────────────────────────────────────┘ +``` + +#### 3.3.2 提现安全 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 提现安全流程 │ +│ │ +│ 1. Pending记录创建 (rgbx合约) │ +│ ├─ 扣除用户映射资产 (原子操作) │ +│ ├─ 创建待处理记录 (状态机: pending) │ +│ └─ 记录提现参数 (目标地址/金额/费率) │ +│ │ +│ 2. BTC交易构造 (官方节点) │ +│ ├─ 选择UTXO (sticky末输入保护) │ +│ ├─ 构建OP_RETURN (rgbx:withdraw:chain33Hash) │ +│ └─ 计算找零与手续费 │ +│ │ +│ 3. TSS签名 (多节点参与) │ +│ ├─ 官方节点验证交易内容 │ +│ ├─ 非官方节点验证sticky末输入 │ +│ ├─ 各节点计算签名份额 │ +│ └─ 聚合签名 (达到阈值) │ +│ │ +│ 4. 广播与确认 │ +│ ├─ 广播到BTC网络 │ +│ ├─ 监听确认 (6个区块) │ +│ └─ 提交Confirm交易 (rgbx合约结算) │ +│ │ +│ 5. 超时处理 │ +│ └─ 超时后官方节点标记timeout,释放资源 │ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +## 4. 配置与部署 + +### 4.1 官方节点配置详解 + +```toml +[neutrino] +# 节点身份 +IsOfficialNode = true # 必须: 标记为官方节点,负责提交区块头和SPV + +# BTC区块头同步 +BtcHeaderStartHeight = 840000 # 首次同步的起始高度(建议当前高度-1000) +BlockConfirmations = 6 # 提交前需要的确认数 +BtcBlockInterval = 600 # BTC平均出块间隔(秒) + +# 提现处理 +WithdrawTimeoutHours = 24 # 提现超时时间(小时) +MaxConcurrentWithdraws = 100 # 最大并发处理提现数 + +# UTXO管理 +MinChangeAmount = 546 # 最小找零金额(粉尘限制) +UtxoLeaseDuration = "24h" # UTXO锁定时长 + +# TSS配置 +[neutrino.Tss] +Peers = ["node1", "node2", "node3", "node4", "node5"] # 所有TSS节点标识 +Threshold = 3 # 签名阈值 (3/5) +Rank = 0 # 本节点排名 + +# BTC RPC (可选,用于加速或冗余) +[neutrino.BtcRPC] +Host = "localhost:8332" # BTC全节点RPC地址 +User = "bitcoin_rpc_user" # RPC用户名 +Pass = "bitcoin_rpc_password" # RPC密码 +DisableTLS = false # 是否禁用TLS + +# Neutrino轻客户端配置 +[neutrino.Neutrino] +DataDir = "./neutrino_data" # 数据目录 +ChainParams = "mainnet" # 网络类型: mainnet/testnet/regtest +``` + +### 4.2 非官方节点配置 + +```toml +[neutrino] +IsOfficialNode = false # 仅参与TSS签名 + +[neutrino.Tss] +Peers = ["node1", "node2", "node3", "node4", "node5"] +Threshold = 3 +Rank = 1 # 不同节点不同rank +``` + +### 4.3 部署拓扑 + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ 生产部署拓扑 │ +│ │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ 官方节点A │ │ 官方节点B │ │ 官方节点C │ │ +│ │ (主+轻客户端)│ │ (主+轻客户端)│ │ (主+轻客户端)│ │ +│ │ - 提交区块头 │ │ - 提交区块头 │ │ - 提交区块头 │ │ +│ │ - 处理充值 │ │ - 处理充值 │ │ - 处理充值 │ │ +│ │ - 处理提现 │ │ - 处理提现 │ │ - 处理提现 │ │ +│ │ - TSS签名 │ │ - TSS签名 │ │ - TSS签名 │ │ +│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ +│ │ │ │ │ +│ └─────────────────┼─────────────────┘ │ +│ │ │ +│ ┌──────▼──────┐ │ +│ │ Chain33主链 │ │ +│ └─────────────┘ │ +│ │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ 非官方节点D │ │ 非官方节点E │ │ 非官方节点F │ │ +│ │ (仅TSS签名) │ │ (仅TSS签名) │ │ (仅TSS签名) │ │ +│ │ - 验证sticky│ │ - 验证sticky│ │ - 验证sticky│ │ +│ │ 末输入 │ │ 末输入 │ │ 末输入 │ │ +│ │ - 参与签名 │ │ - 参与签名 │ │ - 参与签名 │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +│ │ +│ 推荐配置: 3官方节点 + 4非官方节点 = 7节点, 阈值=5 │ +│ 容错: 最多2个节点掉线仍可签名 │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### 4.4 启动与监控 + +```bash +# 1. 初始化(首次启动) +# DKG将自动执行,生成TSS地址 +./chain33 -f chain33.toml + +# 2. 查看DKG状态 +./chain33-cli tss status + +# 3. 查看TSS地址 +./chain33-cli tss address + +# 4. 监控指标(官方节点) +# - 最新提交的BTC区块头高度 +# - 待处理的Deposit数量 +# - 待处理的Withdraw数量 +# - UTXO池余额 +# - TSS签名成功率 +``` + +--- + +## 5. 代码结构 + +### 5.1 模块组织 + +``` +plugin/dapp/lightclient/rpc/lightclient/neutrino/ +├── btcwallet.go # BTC钱包管理(UTXO/交易构建/广播) +├── bitcoin.go # 官方节点业务逻辑(充值/提现/确认) +├── tss.go # TSS服务(DKG/签名/验证) +├── rgbx.go # Chain33 Pending交易拉取与管理 +├── rpc.go # Chain33 RPC客户端封装 +├── cache.go # Pending交易内存缓存 +├── client.go # 客户端初始化和生命周期 +├── config.go # 配置定义 +└── wallet.go # 钱包数据库工具 + +plugin/dapp/lightclient/executor/ +├── checktx.go # BTC区块头交易校验 +└── ... + +plugin/dapp/rgbx/executor/ +├── checktx.go # 充值/提现交易校验 +├── validate_proof.go # SPV证明验证 +├── crosschain.go # 充值/提现执行逻辑 +└── exec_local.go # 本地状态更新 +``` + +### 5.2 关键数据结构 + +**提现请求 (内部使用)** + +| 字段 | 类型 | 说明 | +|------|------|------| +| chain33WithDrawHash | 字节数组 | chain33 提现交易的哈希,作为唯一标识 | +| amount | btcutil.Amount | 提现金额,以 satoshi 为单位 | +| feeRate | btcutil.Amount | 交易费率,单位 sat/vByte | +| toAddress | 字符串 | 目标 BTC 收款地址 | +| stickyUTXO | UTXO 指针 | sticky 末输入引用,首次为空,后续重试必须复用 | + +**UTXO 结构** + +| 字段 | 类型 | 说明 | +|------|------|------| +| OutPoint | wire.OutPoint | 交易输出点,包含交易哈希和输出索引 | +| Amount | btcutil.Amount | 该 UTXO 的金额,以 satoshi 为单位 | +| PkScript | 字节数组 | 锁定脚本,用于构建交易输入的签名逻辑 | + +**BTC 交易证明 (提交到 rgbx 合约)** + +| 字段 | 类型 | 说明 | +|------|------|------| +| TxData | 字节数组 | 序列化的 BTC 交易数据(不包含 witness 数据) | +| BlockHash | 字符串 | 包含该交易的 BTC 区块哈希 | +| BlockHeight | 无符号 64 位整数 | 区块高度,用于快速定位 | +| TxIndex | 无符号 32 位整数 | 交易在区块中的索引位置 | +| MerkleProof | 字节数组列表 | Merkle 分支路径,用于 SPV 验证 | + +### 5.3 状态存储 + +``` +数据库桶 (walletdb): +├── rgbx-withdraw-state # 提现状态 +│ └── key: chain33TxHash, value: {sent, confirmed} +├── rgbx-withdraw-sticky-utxo # sticky末输入 +│ └── key: chain33TxHash, value: UTXO +├── rgbx-deposit-state # 充值状态 +│ └── key: btcTxHash, value: {processed} +├── rgbx-btcwallet-monitor # BTC监控断点 +│ └── min-pending-height +└── rgbx-tss # TSS DKG结果 + └── dkg-result +``` + +--- + +## 6. 技术附录 + +### 6.1 术语表 + +| 术语 | 英文 | 说明 | +|------|------|------| +| TSS | Threshold Signature Scheme | 阈值签名方案,多方共同签名无私钥整体 | +| DKG | Distributed Key Generation | 分布式密钥生成,TSS的密钥初始化协议 | +| SPV | Simplified Payment Verification | 简化支付验证,轻客户端验证交易 | +| UTXO | Unspent Transaction Output | 未花费交易输出,BTC的账户模型 | +| P2WPKH | Pay-to-Witness-Public-Key-Hash | 隔离见证地址格式 | +| Merkle树 | Merkle Tree | 哈希树,用于高效验证数据成员 | +| OP_RETURN | OP_RETURN | BTC脚本操作码,用于嵌入不可花费数据 | +| Neutrino | Neutrino | BTC轻客户端协议,支持BIP157/158 | +| 粉尘限制 | Dust Limit | 过小额输出不被网络中继的阈值(546 sat) | +| 找零 | Change | BTC交易剩余金额返回给发送方 | + +### 6.2 相关规范 + +- **BIP157**: Bitcoin Client-Side Block Filtering +- **BIP158**: Compact Block Filters for Light Clients +- **GG18**: Securing Multi-Party Signatures (阈值签名论文) +- **SPV**: Bitcoin Whitepaper Section 8 (Simplified Payment Verification) + +### 6.3 外部依赖 + +| 依赖 | 版本 | 用途 | +|------|------|------| +| btcd | v0.24.x | BTC协议实现,区块/交易解析 | +| btcwallet | v0.16.x | BTC钱包,UTXO管理,Neutrino客户端 | +| chain33 | v1.x | 基础区块链框架 | + +### 6.4 调试与诊断 + +**开启详细日志** + +将日志级别设置为 debug,并指定 `dapp-lightclient-neutrino` 组件,可输出详细的运行状态信息。 + +**关键日志关键字** + +| 日志关键字 | 含义 | +|-----------|------| +| processWithdrawRequest | 提现请求处理主流程,记录收到提现请求后的处理步骤 | +| buildWithdrawTx | BTC 提现交易构造,包括输出分配和手续费计算 | +| selectAndLockUTXOs | UTXO 选择与锁定过程,记录选中哪些未花费输出 | +| stickyUTXO | Sticky 末输入机制相关日志,记录 sticky UTXO 的选取和验证 | +| handleSignNotify | TSS 签名通知处理,包括签名请求接收和响应 | +| commitDepositTx | 充值交易提交到 chain33 主链的过程 | +| submitBitcoinHeaders | BTC 区块头提交到 lightclient 合约的操作记录 | + +--- + +*文档结束* diff --git a/plugin/dapp/lightclient/rpc/lightclient/neutrino/bitcoin.go b/plugin/dapp/lightclient/rpc/lightclient/neutrino/bitcoin.go new file mode 100644 index 0000000000..ac9706a40a --- /dev/null +++ b/plugin/dapp/lightclient/rpc/lightclient/neutrino/bitcoin.go @@ -0,0 +1,581 @@ +// Copyright Fuzamei Corp. 2018 All Rights Reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package neutrino + +import ( + "bytes" + "encoding/hex" + "errors" + "fmt" + "strings" + "time" + + "github.com/33cn/chain33/types" + ltypes "github.com/33cn/plugin/plugin/dapp/lightclient/lighttypes" + rtypes "github.com/33cn/plugin/plugin/dapp/rgbx/types" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcwallet/walletdb" +) + +// headerPublisher 提交比特币区块头到链上 +func (n *neutrinoClient) submitBitcoinHeaders() { + + nextSubmitHeight := n.cfg.BtcHeaderStartHeight + n.waitUntilDone("wait getChain33BtcLastHeader", func() bool { + if lastHeader := n.getChain33BtcLastHeader(); lastHeader != nil { + if lastHeader.GetHeight() > 0 { + nextSubmitHeight = lastHeader.GetHeight() + 1 + } + return true + } + return false + }, 0) + + log.Info("submitBitcoinHeaders start", "nextSubmitHeight", nextSubmitHeight) + + interval := n.cfg.BtcBlockInterval/3 + 1 + ticker := time.NewTicker(time.Second * time.Duration(interval)) + defer ticker.Stop() + const batchSize = 16 + for { + select { + case <-n.ctx.Done(): + return + + case <-ticker.C: + + best := n.getBestBlock() + if best == nil { + continue + } + + confirmedHeight := uint64(best.Height) - uint64(n.cfg.BlockConfirmations) + headers := make([]*ltypes.BtcHeader, 0, batchSize) + for height := nextSubmitHeight; height <= confirmedHeight && len(headers) < batchSize; height++ { + header, err := n.neutrinoCS.BlockHeaders.FetchHeaderByHeight(uint32(height)) + if err != nil { + log.Error("submitBitcoinHeaders FetchHeaderByHeight", "height", height, "err", err) + break + } + headerHash := header.BlockHash().String() + headers = append(headers, <ypes.BtcHeader{ + Hash: headerHash, + Confirmations: uint64(best.Height) - height + 1, + Height: height, + Version: uint32(header.Version), + MerkleRoot: header.MerkleRoot.String(), + Time: header.Timestamp.Unix(), + Nonce: uint64(header.Nonce), + Bits: int64(header.Bits), + PreviousHash: header.PrevBlock.String(), + }) + } + if len(headers) > 0 { + payload := <ypes.BtcHeaders{Headers: headers} + n.submitMainchainTxUntilSuccess(ltypes.LightclientX, ltypes.NameBtcHeadersAction, payload) + lastHeader := headers[len(headers)-1] + nextSubmitHeight = lastHeader.GetHeight() + 1 + log.Debug("submitBitcoinHeaders", "commitHeight", lastHeader.GetHeight(), + "hash", lastHeader.GetHash(), "nextSubmitHeight", nextSubmitHeight) + } + + } + } +} + +func (n *neutrinoClient) getChain33BtcLastHeader() *ltypes.BtcHeader { + reply, err := n.mainChainGrpc.QueryChain(n.ctx, &types.ChainExecutor{ + Driver: ltypes.LightclientX, + FuncName: "GetBtcLastHeader", + }) + if err != nil { + log.Error("getChain33BtcLastHeader", "query err", err) + return nil + } + data := <ypes.BtcHeader{} + err = types.Decode(reply.GetMsg(), data) + if err != nil { + log.Error("getChain33BtcLastHeader", "decode err", err) + return nil + } + return data +} + +// depositWatcher 监听比特币充值交易, 并向chain33主链提交rgbx deposit交易 +func (n *neutrinoClient) depositWatcher() { + + depositChan := n.bw.GetDepositChannel() + retryTicker := time.NewTicker(time.Second * 30) + var retryList []*btcPendingTx + for { + select { + case <-n.ctx.Done(): + return + case <-retryTicker.C: + temp := retryList + retryList = retryList[:0] + for _, pendingTx := range temp { + if err := n.commitDepositTx(pendingTx); err != nil { + log.Error("depositWatcher commitDepositTx retry", "txHash", pendingTx.txHash.String(), "err", err) + retryList = append(retryList, pendingTx) + } + } + case pendingTx := <-depositChan: + + // 如果chain33DepositAddress为空,则使用第一个输入的utxo地址 + if pendingTx.chain33DepositAddress == "" { + firstInputUtxo := pendingTx.tx.TxIn[0].PreviousOutPoint + pendingTx.chain33DepositAddress = rtypes.FormatUtxo(firstInputUtxo.Hash.String(), firstInputUtxo.Index) + } + if pendingTx.depositAmount <= 0 { + log.Error("depositWatcher invalid deposit amount", "txHash", pendingTx.txHash.String(), + "amount", pendingTx.depositAmount) + continue + } + + if err := n.commitDepositTx(pendingTx); err != nil { + log.Error("depositWatcher commitDepositTx", "txHash", pendingTx.txHash.String(), "err", err) + retryList = append(retryList, pendingTx) + } + } + } +} +func (n *neutrinoClient) commitDepositTx(pendingTx *btcPendingTx) error { + if state := n.getDepositState(pendingTx.txHash[:]); bytes.Equal(state, depositStatusProcessed) { + log.Debug("commitDepositTx already processed", "txHash", pendingTx.txHash.String()) + n.bw.removePendingTx(pendingTx.txHash) + return nil + } + spv, err := n.bw.buildTxExistenceProof(pendingTx) + if err != nil { + log.Error("commitDepositTx buildTxExistenceProof", "txHash", pendingTx.txHash.String(), "err", err) + return err + } + buf := bytes.NewBuffer(make([]byte, 0, pendingTx.tx.SerializeSizeStripped())) + if err = pendingTx.tx.SerializeNoWitness(buf); err != nil { + log.Error("commitDepositTx SerializeNoWitness", "txHash", pendingTx.txHash.String(), "err", err) + return err + } + deposit := &rtypes.DepositAsset{ + Amount: int64(pendingTx.depositAmount), + DepositAddress: pendingTx.chain33DepositAddress, + AssetSymbol: rtypes.BTCSymbol, + TxProof: &rtypes.BtcTxProof{ + TxData: buf.Bytes(), + BlockHash: pendingTx.blockHash.String(), + BlockHeight: uint64(pendingTx.blockHeight), + TxIndex: spv.GetTxIndex(), + MerkleProof: spv.GetBranchProof(), + }, + } + n.submitMainchainTxUntilSuccess(rtypes.RgbxX, rtypes.NameDepositAssetAction, deposit) + if err = n.setDepositState(pendingTx.txHash[:], depositStatusProcessed); err != nil { + log.Error("commitDepositTx setDepositState processed", "txHash", pendingTx.txHash.String(), "err", err) + } + n.bw.removePendingTx(pendingTx.txHash) + log.Debug("commitDepositTx submit deposit success", "btxHash", pendingTx.txHash.String(), + "depositAddr", deposit.GetDepositAddress(), "amount", deposit.GetAmount()) + return nil +} + +func pending2WithdrawRequest(chain33Pending *rtypes.PendingTx) *withdrawRequest { + return &withdrawRequest{ + chain33WithDrawHash: chain33Pending.GetTxHash(), + amount: btcutil.Amount(chain33Pending.GetAmount()), + feeRate: btcutil.Amount(chain33Pending.GetFeeRate()), + toAddress: chain33Pending.GetTargetAddress(), + } +} + +func (n *neutrinoClient) processWithdrawRequest(req *withdrawRequest) (err error) { + + txHash := hex.EncodeToString(req.chain33WithDrawHash) + tx, inputAmounts, lockedUTXOs, err := n.bw.buildWithdrawTx(req) + if err != nil { + log.Error("processWithdrawRequest buildWithdrawTx", "txHash", txHash, "err", err) + return err + } + defer func() { + if r := recover(); r != nil { + log.Error("processWithdrawRequest panic", "err", err, "panic err", r) + err = fmt.Errorf("processWithdrawRequest panic: %v", r) + } + if err != nil { + n.bw.releaseUTXOsExcept(lockedUTXOs, req.stickyUTXO) + } + }() + + if len(lockedUTXOs) == 0 { + return fmt.Errorf("no locked UTXOs") + } + lastUTXO := lockedUTXOs[len(lockedUTXOs)-1] + expectedHash := n.getExpectedWithdrawHash(lastUTXO.OutPoint.String()) + if len(expectedHash) > 0 && !bytes.Equal(expectedHash, req.chain33WithDrawHash) { + log.Error("processWithdrawRequest sticky input mismatch", "expected", hex.EncodeToString(expectedHash), + "actual", txHash, "stickyOutPoint", lastUTXO.OutPoint.String()) + return fmt.Errorf("invalid sticky input") + } + + // 提现交易构建后,则和最后一个utxo强绑定,后续不能更改 + if req.stickyUTXO == nil { + if err = n.setWithdrawStickyUTXO(req.chain33WithDrawHash, lastUTXO); err != nil { + log.Error("processWithdrawRequest setWithdrawStickyUTXO", "txHash", txHash, "stickyUTXO", lastUTXO.OutPoint.String(), "err", err) + return err + } + req.stickyUTXO = lastUTXO + } + // 主节点也进行验证,保证各节点执行相同的逻辑 + err = n.tss.validateWithdrawTx(tx, inputAmounts, req) + if err != nil { + log.Error("processSignBtcTx validateWithdrawTx", "txHash", txHash, "err", err) + return err + } + btcTxHash := tx.TxHash().String() + if err = n.tss.processSignBtcTx(tx, transactionTypeWithdraw, inputAmounts, req.chain33WithDrawHash); err != nil { + log.Error("processWithdrawRequest processSignBtcTx", "txHash", txHash, "btcTxHash", btcTxHash, "err", err) + return err + } + + if err = n.bw.broadcastTransaction(tx, btcTxHash); err != nil { + log.Error("processWithdrawRequest broadcastTransaction", "txHash", txHash, "btcTxHash", btcTxHash, "err", err) + return err + } + n.bw.addPendingTx(&btcPendingTx{ + tx: tx, + submitTime: types.Now(), + confirmations: 0, + blockHeight: -1, + txHash: tx.TxHash(), + txType: transactionTypeWithdraw, + withdrawAddress: req.toAddress, + chain33WithdrawTxHash: req.chain33WithDrawHash, + }) + + if setStateErr := n.setWithdrawState(req.chain33WithDrawHash, withdrawStatusSent); setStateErr != nil { + log.Error("processWithdrawRequest setWithdrawState", "txHash", txHash, "btcTxHash", btcTxHash, "err", setStateErr) + return setStateErr + } + log.Debug("processWithdrawRequest success", "withdrawTxHash", txHash, "btcTxHash", btcTxHash) + return nil +} + +type confirmWithdraw struct { + btcPending *btcPendingTx + pendingTxBlockIndex *rtypes.TxBlockIndex + confirmTx *rtypes.ConfirmTx +} + +var ( + withdrawStateBucket = []byte("rgbx-withdraw-state") + withdrawStatusSent = []byte("broadcasted") + withdrawStatusConfirmed = []byte("confirmed") + withdrawStickyUTXOBucket = []byte("rgbx-withdraw-sticky-utxo") + + depositStateBucket = []byte("rgbx-deposit-state") + depositStatusProcessed = []byte("processed") +) + +func (n *neutrinoClient) getWithdrawState(chain33TxHash []byte) []byte { + var data []byte + err := walletdb.View(n.neutrinoCfg.Database, func(tx walletdb.ReadTx) error { + bucket := tx.ReadBucket(withdrawStateBucket) + if bucket == nil { + return walletdb.ErrBucketNotFound + } + data = bucket.Get(chain33TxHash) + return nil + }) + if err != nil && !errors.Is(err, walletdb.ErrBucketNotFound) { + log.Error("getWithdrawState", "txHash", hex.EncodeToString(chain33TxHash), "err", err) + } + return data +} + +func (n *neutrinoClient) setWithdrawState(txHash []byte, status []byte) error { + + return walletdb.Update(n.neutrinoCfg.Database, func(tx walletdb.ReadWriteTx) error { + bucket, err := tx.CreateTopLevelBucket(withdrawStateBucket) + if err != nil { + return err + } + return bucket.Put(txHash, status) + }) +} + +func encodeOutPoint(op *wire.OutPoint) []byte { + return []byte(op.String()) +} + +func decodeOutPoint(data []byte) (*wire.OutPoint, error) { + return wire.NewOutPointFromString(string(data)) +} + +func (n *neutrinoClient) getWithdrawStickyUTXO(chain33TxHash []byte) *UTXO { + var data []byte + err := walletdb.View(n.neutrinoCfg.Database, func(tx walletdb.ReadTx) error { + bucket := tx.ReadBucket(withdrawStickyUTXOBucket) + if bucket == nil { + return walletdb.ErrBucketNotFound + } + data = bucket.Get(chain33TxHash) + return nil + }) + if err != nil && !errors.Is(err, walletdb.ErrBucketNotFound) { + log.Error("getWithdrawStickyUTXO", "txHash", hex.EncodeToString(chain33TxHash), "err", err) + return nil + } + if len(data) == 0 { + return nil + } + u := &rtypes.Utxo{} + err = types.Decode(data, u) + if err != nil { + log.Error("getWithdrawStickyUTXO decode", "txHash", hex.EncodeToString(chain33TxHash), "err", err) + return nil + } + outPoint, err := wire.NewOutPointFromString(u.OutPoint) + if err != nil { + log.Error("getWithdrawStickyUTXO NewOutPointFromString", "txHash", hex.EncodeToString(chain33TxHash), "err", err) + return nil + } + return &UTXO{ + OutPoint: *outPoint, + Amount: btcutil.Amount(u.Amount), + PkScript: u.PkScript, + } +} + +func (n *neutrinoClient) getExpectedWithdrawHash(outPoint string) []byte { + var data []byte + err := walletdb.View(n.neutrinoCfg.Database, func(tx walletdb.ReadTx) error { + bucket := tx.ReadBucket(withdrawStickyUTXOBucket) + if bucket == nil { + return walletdb.ErrBucketNotFound + } + data = bucket.Get([]byte(outPoint)) + return nil + }) + if err != nil && !errors.Is(err, walletdb.ErrBucketNotFound) { + log.Error("getExpectedWithdrawHash", "outPoint", outPoint, "err", err) + return nil + } + return data +} + +func (n *neutrinoClient) setWithdrawStickyUTXO(chain33TxHash []byte, utxo *UTXO) error { + u := &rtypes.Utxo{ + OutPoint: utxo.OutPoint.String(), + Amount: int64(utxo.Amount), + PkScript: utxo.PkScript, + } + return walletdb.Update(n.neutrinoCfg.Database, func(tx walletdb.ReadWriteTx) error { + bucket, err := tx.CreateTopLevelBucket(withdrawStickyUTXOBucket) + if err != nil { + return err + } + err = bucket.Put([]byte(utxo.OutPoint.String()), chain33TxHash) + if err != nil { + return err + } + return bucket.Put(chain33TxHash, types.Encode(u)) + }) +} + +func (n *neutrinoClient) clearWithdrawStickyUTXO(chain33TxHash []byte) error { + return walletdb.Update(n.neutrinoCfg.Database, func(tx walletdb.ReadWriteTx) error { + bucket := tx.ReadWriteBucket(withdrawStickyUTXOBucket) + if bucket == nil { + return nil + } + return bucket.Delete(chain33TxHash) + }) +} + +func (n *neutrinoClient) getDepositState(txHash []byte) []byte { + var data []byte + err := walletdb.View(n.neutrinoCfg.Database, func(tx walletdb.ReadTx) error { + bucket := tx.ReadBucket(depositStateBucket) + if bucket == nil { + return walletdb.ErrBucketNotFound + } + data = bucket.Get(txHash) + return nil + }) + if err != nil && !errors.Is(err, walletdb.ErrBucketNotFound) { + log.Error("getDepositState", "txHash", hex.EncodeToString(txHash), "err", err) + } + return data +} + +func (n *neutrinoClient) setDepositState(txHash []byte, status []byte) error { + return walletdb.Update(n.neutrinoCfg.Database, func(tx walletdb.ReadWriteTx) error { + bucket, err := tx.CreateTopLevelBucket(depositStateBucket) + if err != nil { + return err + } + return bucket.Put(txHash, status) + }) +} + +func (n *neutrinoClient) getPendingTxBlockIndex(txHash []byte) *rtypes.TxBlockIndex { + hashStr := hex.EncodeToString(txHash) + pendingTx := n.rgbx.pendingCache.getTx(hashStr) + if pendingTx != nil { + return &rtypes.TxBlockIndex{ + BlockHeight: pendingTx.TxBlockHeight, + TxIndex: pendingTx.TxIndex, + } + } + txDetail, err := n.getTxDetail(txHash) + if err != nil { + log.Error("getPendingTxBlockIndex getTxDetail", "txHash", hashStr, "err", err) + return nil + } + txBlockIndex := &rtypes.TxBlockIndex{BlockHeight: txDetail.GetHeight(), TxIndex: txDetail.GetIndex()} + return txBlockIndex +} + +// withdrawalProcessor 监听chain33主链上比特币提现请求, 构造提现交易到比特币网络,并向chain33主链提交rgbx confirm交易 +func (n *neutrinoClient) withdrawalProcessor() { + withdrawalChan := n.bw.GetWithdrawChannel() + // 根据配置,同时兼容测试和正式环境 + retryTicker := time.NewTicker(time.Second * time.Duration(n.cfg.BtcBlockInterval/15+1)) + defer retryTicker.Stop() + withdrawReqChan := n.withdrawReqChan + withdrawReqList := make([]*withdrawRequest, 0, 16) + confirmRetryList := make([]*confirmWithdraw, 0, 16) + + for { + select { + case <-n.ctx.Done(): + return + case <-retryTicker.C: + tempWithdraws := withdrawReqList + withdrawReqList = withdrawReqList[:0] + for _, p := range tempWithdraws { + if err := n.processWithdrawRequest(p); err != nil { + withdrawReqList = append(withdrawReqList, p) + } + } + tempConfirms := confirmRetryList + confirmRetryList = confirmRetryList[:0] + for _, p := range tempConfirms { + if !n.processWithdrawConfirm(p) { + confirmRetryList = append(confirmRetryList, p) + } + } + case pendingBtc := <-withdrawalChan: // 向chain33主链提交确认提现交易 + confirm := &confirmWithdraw{ + btcPending: pendingBtc, + } + if !n.processWithdrawConfirm(confirm) { + confirmRetryList = append(confirmRetryList, confirm) + } + + case pending := <-withdrawReqChan: // 向btc链提交提现交易 + state := n.getWithdrawState(pending.GetTxHash()) + if len(state) > 0 && bytes.Equal(state, withdrawStatusConfirmed) { + log.Debug("withdrawalProcessor hasWithdrawState", "txHash", hex.EncodeToString(pending.GetTxHash()), "state", string(state)) + continue + } + req := pending2WithdrawRequest(pending) + req.stickyUTXO = n.getWithdrawStickyUTXO(pending.GetTxHash()) + // 状态非空时,存在stickyUTXO才允许重试 + if len(state) > 0 && req.stickyUTXO == nil { + log.Debug("withdrawalProcessor hasWithdrawState but no stickyUTXO", "txHash", hex.EncodeToString(pending.GetTxHash()), "state", string(state)) + continue + } + if err := n.processWithdrawRequest(req); err != nil { + withdrawReqList = append(withdrawReqList, req) + } + } + } +} + +func (n *neutrinoClient) commitWithdrawConfirm(confirm *rtypes.ConfirmTx, confirmHash string) (string, error) { + + txHash, err := n.submitMainchainTx(rtypes.RgbxX, rtypes.NameConfirmAction, confirm) + if err != nil && !strings.Contains(err.Error(), "already confirmed") { + return "", err + } + n.rgbx.pendingCache.removeTx(confirmHash) + if err := n.setWithdrawState(confirm.TxHash, withdrawStatusConfirmed); err != nil { + log.Error("commitWithdrawConfirm setWithdrawState", "txHash", txHash, + "confirmHash", confirmHash, "err", err) + } + return txHash, nil +} + +func (n *neutrinoClient) buildWithdrawConfirm(btcPending *btcPendingTx, pendingTxBlockIndex *rtypes.TxBlockIndex) *rtypes.ConfirmTx { + if pendingTxBlockIndex == nil { + return nil + } + spv, err := n.bw.buildTxExistenceProof(btcPending) + if err != nil { + log.Error("buildWithdrawConfirmPayload buildTxExistenceProof", "btcTxHash", btcPending.txHash.String(), + "chain33WithdrawTxHash", hex.EncodeToString(btcPending.chain33WithdrawTxHash), "err", err) + return nil + } + buf := bytes.NewBuffer(make([]byte, 0, btcPending.tx.SerializeSizeStripped())) + if err = btcPending.tx.SerializeNoWitness(buf); err != nil { + log.Error("buildWithdrawConfirmPayload SerializeNoWitness", "btcTxHash", btcPending.txHash.String(), + "chain33WithdrawTxHash", hex.EncodeToString(btcPending.chain33WithdrawTxHash), "err", err) + return nil + } + minPendingHeight := n.rgbx.pendingCache.getMinPendingHeight() + return &rtypes.ConfirmTx{ + ActionType: rtypes.TyWithDrawAsset, + ConfirmedBlockHeight: minPendingHeight - 1, + TxBlockHeight: pendingTxBlockIndex.GetBlockHeight(), + TxIndex: pendingTxBlockIndex.GetTxIndex(), + TxHash: btcPending.chain33WithdrawTxHash, + BtcTxProof: &rtypes.BtcTxProof{ + TxData: buf.Bytes(), + BlockHash: btcPending.blockHash.String(), + BlockHeight: uint64(btcPending.blockHeight), + TxIndex: spv.GetTxIndex(), + MerkleProof: spv.GetBranchProof(), + }, + } +} + +func (n *neutrinoClient) processWithdrawConfirm(confirm *confirmWithdraw) bool { + + if confirm.pendingTxBlockIndex == nil { + confirm.pendingTxBlockIndex = n.getPendingTxBlockIndex(confirm.btcPending.chain33WithdrawTxHash) + } + + if confirm.confirmTx == nil { + confirm.confirmTx = n.buildWithdrawConfirm(confirm.btcPending, confirm.pendingTxBlockIndex) + } + if confirm.confirmTx == nil { + return false + } + confirmHash := hex.EncodeToString(confirm.confirmTx.TxHash) + if state := n.getWithdrawState(confirm.confirmTx.GetTxHash()); bytes.Equal(state, withdrawStatusConfirmed) { + n.rgbx.pendingCache.removeTx(confirmHash) + n.bw.removePendingTx(confirm.btcPending.txHash) + if err := n.clearWithdrawStickyUTXO(confirm.confirmTx.GetTxHash()); err != nil { + log.Error("processWithdrawConfirm clearWithdrawFirstInput", "confirmHash", confirmHash, "err", err) + } + log.Debug("processWithdrawConfirm already confirmed local state", "confirmHash", confirmHash) + return true + } + + txHash, err := n.commitWithdrawConfirm(confirm.confirmTx, confirmHash) + if err != nil { + log.Error("processWithdrawConfirm commitWithdrawConfirm", "txHash", txHash, "confirmHash", confirmHash, "err", err) + return false + } + n.bw.removePendingTx(confirm.btcPending.txHash) + if err := n.clearWithdrawStickyUTXO(confirm.confirmTx.GetTxHash()); err != nil { + log.Error("processWithdrawConfirm clearWithdrawFirstInput", "confirmHash", confirmHash, "err", err) + } + log.Debug("processWithdrawConfirm success", "txHash", txHash, + "btcTxHash", confirm.btcPending.txHash.String(), "confirmHash", confirmHash) + return true + +} diff --git a/plugin/dapp/lightclient/rpc/lightclient/neutrino/bitcoin_test.go b/plugin/dapp/lightclient/rpc/lightclient/neutrino/bitcoin_test.go new file mode 100644 index 0000000000..644e99d7cf --- /dev/null +++ b/plugin/dapp/lightclient/rpc/lightclient/neutrino/bitcoin_test.go @@ -0,0 +1,100 @@ +// Copyright Fuzamei Corp. 2018 All Rights Reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package neutrino + +import ( + "testing" + + rtypes "github.com/33cn/plugin/plugin/dapp/rgbx/types" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/wire" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestEncodeDecodeOutPoint tests the OutPoint encoding/decoding functions +func TestEncodeDecodeOutPoint(t *testing.T) { + tests := []struct { + name string + op wire.OutPoint + }{ + { + name: "normal outpoint", + op: wire.OutPoint{ + Hash: [32]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}, + Index: 1, + }, + }, + { + name: "zero hash", + op: wire.OutPoint{ + Hash: [32]byte{}, + Index: 0, + }, + }, + { + name: "max index", + op: wire.OutPoint{ + Hash: [32]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + Index: 0xffffffff, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Encode + encoded := encodeOutPoint(&tt.op) + require.NotNil(t, encoded) + assert.Equal(t, tt.op.String(), string(encoded)) + + // Decode + decoded, err := decodeOutPoint(encoded) + require.NoError(t, err) + assert.Equal(t, tt.op.Hash, decoded.Hash) + assert.Equal(t, tt.op.Index, decoded.Index) + }) + } +} + +// TestDecodeOutPointError tests decodeOutPoint with invalid input +func TestDecodeOutPointError(t *testing.T) { + invalidInputs := []string{ + "invalid", + "", + "too:many:colons", + "badhash:999999999999999999999", + } + + for _, input := range invalidInputs { + _, err := decodeOutPoint([]byte(input)) + assert.Error(t, err, "should error for input: %s", input) + } +} + +// TestPending2WithdrawRequest tests the conversion function +func TestPending2WithdrawRequest(t *testing.T) { + chain33Hash := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32} + amount := int64(1000000) + feeRate := int64(10) + toAddress := "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa" + + // Create a mock pending tx (simplified) + pending := &rtypes.PendingTx{ + TxHash: chain33Hash, + Amount: amount, + FeeRate: feeRate, + TargetAddress: toAddress, + ActionType: rtypes.TyWithDrawAsset, + } + + req := pending2WithdrawRequest(pending) + + assert.Equal(t, chain33Hash, req.chain33WithDrawHash) + assert.Equal(t, btcutil.Amount(amount), req.amount) + assert.Equal(t, btcutil.Amount(feeRate), req.feeRate) + assert.Equal(t, toAddress, req.toAddress) + assert.Nil(t, req.stickyUTXO) +} diff --git a/plugin/dapp/lightclient/rpc/lightclient/neutrino/btcwallet.go b/plugin/dapp/lightclient/rpc/lightclient/neutrino/btcwallet.go new file mode 100644 index 0000000000..a4d8717fbe --- /dev/null +++ b/plugin/dapp/lightclient/rpc/lightclient/neutrino/btcwallet.go @@ -0,0 +1,1121 @@ +package neutrino + +import ( + "bytes" + "encoding/hex" + "errors" + "fmt" + "math" + "sort" + "strings" + "time" + + "github.com/33cn/chain33/common/merkle" + "github.com/33cn/chain33/types" + lighttypes "github.com/33cn/plugin/plugin/dapp/lightclient/lighttypes" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/rpcclient" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcwallet/chain" + "github.com/btcsuite/btcwallet/waddrmgr" + "github.com/btcsuite/btcwallet/wallet" + "github.com/btcsuite/btcwallet/walletdb" + "github.com/btcsuite/btcwallet/wtxmgr" +) + +const ( + transactionTypeDeposit string = "deposit" + transactionTypeWithdraw string = "withdraw" + _ string = "mergeBalance" +) + +const ( + // 默认确认数 + defaultRequiredConfs = 6 + // 最小找零金额(粉尘限制) + minChangeAmount = 546 + // UTXO锁定时长 + utxoLeaseDuration = 24 * time.Hour +) + +const ( + withdrawOpReturnPrefix = "rgbx:" + transactionTypeWithdraw + ":" + withdrawOpReturnDataLen = len(withdrawOpReturnPrefix) + 32 + withdrawOpReturnScriptLen = 1 + 1 + withdrawOpReturnDataLen +) + +const ( + btcwalletMonitorBucket = "rgbx-btcwallet-monitor" + minPendingHeightKey = "min-pending-height" +) + +// utxoWithdrawLockID UTXO锁定ID +var utxoLockID = wtxmgr.LockID{ + 'R', 'G', 'B', 'X', '-', 'L', 'O', 'C', 'K', + '-', 'I', 'D', '-', 'V', '1', '.', '0', '.', '0', + 0, 0, 0, 0, +} + +// DepositNotification 充值通知 +type DepositNotification struct { + TxHash chainhash.Hash + Amount btcutil.Amount + FromAddress string + Chain33Addr string // 绑定的Chain33地址 + OpReturnData string // 原始OP_RETURN数据 +} + +// WithdrawNotification 提现确认通知 +type WithdrawNotification struct { + TxHash chainhash.Hash + ToAddress string + Chain33TxHash string // Chain33提现交易哈希 + OpReturnData string // 原始OP_RETURN数据 +} + +// withdrawRequest 提现请求 +type withdrawRequest struct { + chain33WithDrawHash []byte + amount btcutil.Amount + feeRate btcutil.Amount // sat/byte,0表示使用默认 + toAddress string + stickyUTXO *UTXO +} + +type btcWallet struct { + *wallet.Wallet + client *neutrinoClient + chainParams chaincfg.Params + chainClient chain.Interface + rpcClient *rpcclient.Client + db walletdb.DB + + minPendingHeight int32 + processedHeight int32 + + // TSS相关 + tssAddress btcutil.Address + tssPubKey *btcec.PublicKey + tssPkScript []byte // 预计算的TSS地址脚本 + + // 通知channel + depositChan chan *btcPendingTx + withdrawChan chan *btcPendingTx + addPendingChan chan *btcPendingTx + removePendingChan chan chainhash.Hash + + // 配置 + requiredConfs int32 + + // 交易监控 + pendingTxs map[chainhash.Hash]*btcPendingTx + rescanDone bool + // txLock sync.RWMutex +} + +type btcPendingTx struct { + tx *wire.MsgTx + submitTime time.Time + notified bool + confirmations int32 + blockHeight int32 + blockHash chainhash.Hash + txHash chainhash.Hash + txType string // "deposit" or "withdraw" + depositAmount btcutil.Amount + withdrawAmount btcutil.Amount + chain33DepositAddress string // Chain33充值地址 + withdrawAddress string + chain33WithdrawTxHash []byte // Chain33提现交易哈希 + // OP_RETURN数据 + opReturnData opReturnData // 原始OP_RETURN解析数据 +} + +func newBtcWallet(n *neutrinoClient) (*btcWallet, error) { + bw := &btcWallet{ + client: n, + chainParams: n.neutrinoCfg.ChainParams, + depositChan: make(chan *btcPendingTx, 100), + withdrawChan: make(chan *btcPendingTx, 100), + addPendingChan: make(chan *btcPendingTx, 100), + removePendingChan: make(chan chainhash.Hash, 100), + requiredConfs: int32(n.cfg.BlockConfirmations), + pendingTxs: make(map[chainhash.Hash]*btcPendingTx), + } + + if n.cfg.BtcRPC.Host != "" { + connCfg, err := n.cfg.BtcRPC.toConnConfig() + if err != nil { + log.Error("newBtcWallet btc rpc conn config error", "err", err) + return nil, err + } + rpcCli, err := rpcclient.New(connCfg, nil) + if err != nil { + log.Error("newBtcWallet create btc rpc client error", "err", err) + return nil, err + } + bw.rpcClient = rpcCli + } + + exist, db, err := openWalletDB(n.neutrinoCfg.DataDir, "btcwallet.db") + if err != nil { + log.Error("newBtcWallet open db error", "err", err) + return nil, err + } + + pubPass := []byte("hello") + if !exist { + err = wallet.CreateWatchingOnly(db, pubPass, &bw.chainParams, types.Now()) + if err != nil { + log.Error("newBtcWallet create wallet error", "err", err) + _ = db.Close() + return nil, err + } + } + + w, err := wallet.Open(db, pubPass, nil, &bw.chainParams, 0) + if err != nil { + log.Error("newBtcWallet open wallet error", "err", err) + _ = db.Close() + return nil, err + } + log.Info("newBtcWallet open wallet success", "wallet birthtime", w.Manager.Birthday().Unix()) + bw.db = db + bw.Wallet = w + bw.chainClient = chain.NewNeutrinoClient(&bw.chainParams, n.neutrinoCS) + return bw, nil +} + +func (b *btcWallet) start() error { + if err := b.chainClient.Start(); err != nil { + log.Error("btcwallet chainclient start error", "err", err) + return err + } + + b.Wallet.Start() + + // 启动交易监听 + go b.monitorTransactions() + + return nil +} + +func (b *btcWallet) stop() { + b.Wallet.Stop() + b.chainClient.Stop() + if b.rpcClient != nil { + b.rpcClient.Shutdown() + } + _ = b.db.Close() +} + +// waitAndImportTSSAddress 等待TSS地址生成并导入 +func (b *btcWallet) waitAndImportTSSAddress() { + + b.tssPubKey = b.client.tss.tssPublicKey + b.tssPkScript = b.client.tss.pkScript + b.tssAddress = b.client.tss.tssAddress + log.Debug("waitAndImportTSSAddress", "address", b.tssAddress.String()) + b.client.waitUntilDone("waitImportTSSAddress", func() bool { + // 显式导入TSS公钥到钱包 + if _, err := b.Wallet.AddressInfo(b.tssAddress); err != nil { + if !waddrmgr.IsError(err, waddrmgr.ErrAddressNotFound) { + log.Error("waitAndImportTSSAddress AddressInfo failed", "err", err) + return false + } + err = b.importTSSPublicKey() + if err != nil { + log.Error("waitAndImportTSSAddress ImportPublicKey failed", "err", err) + return false + } + } + return true + }, time.Second*3) + log.Info("waitAndImportTSSAddress success", "address", b.tssAddress.String()) +} + +func (b *btcWallet) importTSSPublicKey() error { + err := b.Wallet.ImportPublicKey(b.tssPubKey, waddrmgr.WitnessPubKey) + if err == nil { + return nil + } + // Old/external wallet DBs might miss BIP84 scope (m/84'/coin'). Create it + // on-demand and retry importing the TSS key. + if !waddrmgr.IsError(err, waddrmgr.ErrScopeNotFound) { + return err + } + + scope := waddrmgr.KeyScopeBIP0084 + schema, ok := waddrmgr.ScopeAddrMap[scope] + if !ok { + return err + } + if _, addErr := b.Wallet.AddScopeManager(scope, schema); addErr != nil { + log.Warn("importTSSPublicKey AddScopeManager failed, retry import anyway", + "scope", scope, "err", addErr) + } + return b.Wallet.ImportPublicKey(b.tssPubKey, waddrmgr.WitnessPubKey) +} + +func (b *btcWallet) loadMinPendingHeight() int32 { + var height int32 + err := walletdb.View(b.client.neutrinoCfg.Database, func(tx walletdb.ReadTx) error { + bucket := tx.ReadBucket([]byte(btcwalletMonitorBucket)) + if bucket == nil { + return walletdb.ErrBucketNotFound + } + val := bucket.Get([]byte(minPendingHeightKey)) + if len(val) == 0 { + return nil + } + reply := &types.Int64{} + if err := types.Decode(val, reply); err != nil { + return err + } + height = int32(reply.GetData()) + return nil + }) + log.Debug("loadMinPendingHeight", "height", height, "err", err) + if err != nil && !errors.Is(err, walletdb.ErrBucketNotFound) { + log.Error("loadMinPendingHeight", "err", err) + } + return height +} + +func (b *btcWallet) saveMinPendingHeight(height int32) { + err := walletdb.Update(b.client.neutrinoCfg.Database, func(tx walletdb.ReadWriteTx) error { + bucket, err := tx.CreateTopLevelBucket([]byte(btcwalletMonitorBucket)) + if err != nil { + return err + } + if height <= 0 { + return bucket.Delete([]byte(minPendingHeightKey)) + } + data := types.Encode(&types.Int64{Data: int64(height)}) + return bucket.Put([]byte(minPendingHeightKey), data) + }) + log.Debug("saveMinPendingHeight", "height", height, "err", err) + if err != nil { + log.Error("saveMinPendingHeight", "err", err, "height", height) + } +} + +func (b *btcWallet) updateMinPendingHeight() { + minHeight := int32(0) + for _, pending := range b.pendingTxs { + if pending.blockHeight <= 0 { + continue + } + if minHeight == 0 || pending.blockHeight < minHeight { + minHeight = pending.blockHeight + } + } + log.Debug("updateMinPendingHeight", "minHeight", minHeight, + "processedHeight", b.processedHeight, "bestHeight", b.client.getBestBlockHeight(), + "b.minPendingHeight", b.minPendingHeight, "pendingTxs", len(b.pendingTxs)) + if minHeight > 0 && minHeight != b.minPendingHeight { + b.minPendingHeight = minHeight + b.saveMinPendingHeight(minHeight) + } +} + +// rescanFromHeight 从指定高度开始重新扫描, 需要注意和wallet.SynchronizeRPC的同步关系,可能造成死锁 +func (b *btcWallet) rescanFromHeight(height int32) error { + + log.Debug("rescanFromHeight", "height", height) + hash, err := b.chainClient.GetBlockHash(int64(height)) + if err != nil { + return err + } + stamp := waddrmgr.BlockStamp{ + Hash: *hash, + Height: height, + } + if header, err := b.chainClient.GetBlockHeader(hash); err == nil { + stamp.Timestamp = header.Timestamp + } + job := &wallet.RescanJob{ + InitialSync: true, + Addrs: []btcutil.Address{b.tssAddress}, + BlockStamp: stamp, + } + select { + case err := <-b.Wallet.SubmitRescan(job): + log.Debug("rescanFromHeight submitRescan done") + return err + case <-b.client.ctx.Done(): + return types.ErrChannelClosed + } + +} + +func (b *btcWallet) rescanWalletTxs(start, end int32, rescanChan chan *wallet.GetTransactionsResult) error { + b.client.waitUntilDone("walletTxsRescan", func() bool { + return b.Wallet.ChainSynced() + }, time.Second*2) + log.Debug("rescanWalletTxs", "start", start, "end", end, "bestHeight", b.client.getBestBlockHeight()) + startBlock := wallet.NewBlockIdentifierFromHeight(start) + endBlock := wallet.NewBlockIdentifierFromHeight(end) + res, err := b.Wallet.GetTransactions(startBlock, endBlock, "", b.client.ctx.Done()) + if err != nil { + log.Error("rescanWalletTxs GetTransactions error", "err", err) + return err + } + rescanChan <- res + return nil + +} + +func (b *btcWallet) handleNotify(attachedBlocks []wallet.Block, unminedTxs []wallet.TransactionSummary) { + + log.Debug("handleNotify", "attachedBlocks", len(attachedBlocks), "unminedTxs", len(unminedTxs)) + // 处理已确认交易, attachedBlocks是btc链上新添加的区块 + for _, block := range attachedBlocks { + log.Debug("handleNotify block", "height", block.Height, "hash", block.Hash.String(), "transactions", len(block.Transactions)) + for _, tx := range block.Transactions { + b.handleTransaction(&tx, block.Height, *block.Hash) + } + b.processedHeight = block.Height + } + + // 处理未确认交易(重置确认数) + for _, tx := range unminedTxs { + b.handleUnminedTransaction(*tx.Hash) + } +} + +// monitorTransactions 监听交易通知 +func (b *btcWallet) monitorTransactions() { + + client := b.Wallet.NtfnServer.TransactionNotifications() + b.Wallet.SynchronizeRPC(b.chainClient) + b.waitAndImportTSSAddress() + rescanHeight := b.loadMinPendingHeight() + bestHeight := b.client.getBestBlockHeight() + if rescanHeight < int32(b.client.cfg.BtcHeaderStartHeight) { + rescanHeight = int32(b.client.cfg.BtcHeaderStartHeight) + log.Info("monitorTransactions initial rescan", "height", rescanHeight, "bestHeight", bestHeight) + } else { + log.Debug("monitorTransactions resume from height", "height", rescanHeight, "bestHeight", bestHeight) + } + b.minPendingHeight = rescanHeight + interval := b.client.cfg.BtcBlockInterval/2 + 1 + ticker := time.NewTicker(time.Second * time.Duration(interval)) + rescanChan := make(chan *wallet.GetTransactionsResult, 1) + firstProcessFlag := true + for { + select { + case <-b.client.ctx.Done(): + client.Done() + return + case res := <-rescanChan: + b.handleNotify(res.MinedTransactions, res.UnminedTransactions) + b.rescanDone = true + + case ntfn := <-client.C: + if ntfn == nil { + continue + } + // 首次处理检测是否需要重新扫描 + if firstProcessFlag && len(ntfn.AttachedBlocks) > 0 { + log.Debug("monitorTransactions first process", "height", ntfn.AttachedBlocks[0].Height, "rescanHeight", rescanHeight) + firstProcessFlag = false + if ntfn.AttachedBlocks[0].Height > rescanHeight { + go b.rescanWalletTxs(rescanHeight, ntfn.AttachedBlocks[0].Height-1, rescanChan) + } else { + b.rescanDone = true + } + } + b.handleNotify(ntfn.AttachedBlocks, ntfn.UnminedTransactions) + + case pending := <-b.addPendingChan: + b.pendingTxs[pending.txHash] = pending + log.Debug("addPendingTx", "txHash", pending.txHash.String(), "txType", pending.txType) + case txHash := <-b.removePendingChan: + delete(b.pendingTxs, txHash) + log.Debug("removePendingTx", "txHash", txHash.String(), "txLen", len(b.pendingTxs), + "processedHeight", b.processedHeight, "minPendingHeight", b.minPendingHeight) + if len(b.pendingTxs) > 0 { + b.updateMinPendingHeight() + } + case <-ticker.C: + if b.rescanDone && len(b.pendingTxs) == 0 && b.processedHeight > b.minPendingHeight { + b.minPendingHeight = b.processedHeight + b.saveMinPendingHeight(b.minPendingHeight) + log.Debug("monitorTransactions update minPendingHeight", + "minPendingHeight", b.minPendingHeight, "bestHeight", b.client.getBestBlockHeight()) + } + b.updateTransactionConfirmations() + } + } +} + +// handleTransaction 处理单个交易 +func (b *btcWallet) handleTransaction(tx *wallet.TransactionSummary, blockHeight int32, blockHash chainhash.Hash) { + txHash := *tx.Hash + // 检查提现交易是否已在pending缓存中(避免重复解析),如果存在,则更新区块高度和区块哈希 + pending, exists := b.pendingTxs[txHash] + if exists { + pending.blockHeight = blockHeight + pending.blockHash = blockHash + log.Debug("handleTransaction already in pending", "txHash", txHash.String()) + return + } + + log.Debug("handleTransaction processing", "txHash", txHash.String(), "blockHeight", blockHeight, + "inputs", len(tx.Tx.TxIn), "outputs", len(tx.Tx.TxOut)) + + // 分析交易类型和相关信息 + pending = b.analyzeTransaction(tx.Hash, tx.Tx) + if pending == nil { + log.Debug("handleTransaction not deposit/withdraw", "txHash", txHash.String()) + return + } + pending.tx = tx.Tx + pending.blockHeight = blockHeight + pending.blockHash = blockHash + pending.txHash = txHash + // 记录关键交易信息 + log.Info("handleTransaction detected "+pending.txType, "blockHeight", blockHeight, "txHash", txHash.String(), + "depositAmount", pending.depositAmount, "withdrawAmount", pending.withdrawAmount) + + // 添加到待确认列表 + b.pendingTxs[txHash] = pending +} + +// handleUnminedTransaction 处理未确认交易(重置确认数) +func (b *btcWallet) handleUnminedTransaction(txHash chainhash.Hash) { + if pending, exists := b.pendingTxs[txHash]; exists && pending.blockHeight > 0 { + // 重置确认数和区块高度 + oldConfirmations := pending.confirmations + oldBlockHeight := pending.blockHeight + + pending.confirmations = 0 + pending.blockHeight = -1 + pending.notified = false + + log.Debug("handleUnminedTransaction reset confirmations", "txHash", txHash.String(), + "oldConfirmations", oldConfirmations, "oldBlockHeight", oldBlockHeight, "type", pending.txType) + } +} + +// updateTransactionConfirmations 更新已存在交易的确认数 +func (b *btcWallet) updateTransactionConfirmations() { + bestHeight := b.client.getBestBlockHeight() + log.Debug("updateTransactionConfirmations", "bestBlock", bestHeight, "pendingTxs", len(b.pendingTxs)) + for txHash, pending := range b.pendingTxs { + if pending.blockHeight > 0 && bestHeight > 0 { + pending.confirmations = bestHeight - pending.blockHeight + 1 + } + // 如果达到要求的确认数,发送通知 + if !pending.notified && pending.confirmations >= b.requiredConfs { + log.Debug("updateTransactionConfirmations ready for notification", "txHash", txHash.String(), "type", pending.txType, + "confirmations", pending.confirmations, "required", b.requiredConfs) + b.sendTransactionNotification(txHash, pending) + pending.notified = true + } + + } + +} + +func (b *btcWallet) addPendingTx(pending *btcPendingTx) { + b.addPendingChan <- pending +} + +func (b *btcWallet) removePendingTx(txHash chainhash.Hash) { + b.removePendingChan <- txHash +} + +// OpReturnData OP_RETURN数据结构 +type opReturnData struct { + protocol string // "rgbx" + action string // "deposit" | "withdraw" + payload string // chain33地址 或 交易哈希 +} + +// parseOpReturn 解析OP_RETURN数据 +func (b *btcWallet) parseOpReturn(pkScript []byte) (*opReturnData, error) { + // 提取数据(跳过OP_RETURN和长度字节, 解析格式: protocol:action:payload + dataStr := string(pkScript[2:]) + parts := strings.Split(dataStr, ":") + if len(parts) < 3 { + log.Error("parseOpReturn invalid data", "data", dataStr) + return nil, fmt.Errorf("invalid OP_RETURN format: expected 3 parts, got %d", len(parts)) + } + + opData := &opReturnData{ + protocol: parts[0], + action: parts[1], + payload: parts[2], + } + + return opData, nil +} + +// analyzeTransaction 分析交易类型(优化版本) +// 返回: ("deposit"|"withdraw"|"", pendingTx) +func (b *btcWallet) analyzeTransaction(hash *chainhash.Hash, tx *wire.MsgTx) *btcPendingTx { + info := &btcPendingTx{} + + // 检查输出:查找TSS地址、非TSS地址的输出和OP_RETURN + var hasTssOutput bool + var depositAmount, withdrawAmount btcutil.Amount + var firstNonTssOutputAddress string + var parsed *opReturnData + var err error + + for i, output := range tx.TxOut { + // 检查并解析 OP_RETURN 输出 + if len(output.PkScript) > 2 && output.PkScript[0] == txscript.OP_RETURN && parsed == nil { + parsed, err = b.parseOpReturn(output.PkScript) + if err != nil { + log.Error("analyzeTransaction parseOpReturn failed", "txHash", hash.String(), + "outputIndex", i, "err", err, "pkScript", hex.EncodeToString(output.PkScript)) + } else { + + info.opReturnData = *parsed + log.Debug("analyzeTransaction parseOpReturn success", "txHash", hash.String(), + "protocol", parsed.protocol, "action", parsed.action, "payloadLen", len(parsed.payload)) + } + continue + } + + // 直接比较脚本(预计算的TSS地址脚本) + if bytes.Equal(output.PkScript, b.tssPkScript) { + hasTssOutput = true + depositAmount += btcutil.Amount(output.Value) + log.Debug("analyzeTransaction TSS output found", "txHash", hash.String(), "outputIndex", i, "amount", btcutil.Amount(output.Value)) + continue + } + + withdrawAmount += btcutil.Amount(output.Value) + if firstNonTssOutputAddress == "" { + // 提取第一个非TSS地址输出(提现地址) + _, addrs, _, err := txscript.ExtractPkScriptAddrs(output.PkScript, &b.chainParams) + if err == nil && len(addrs) > 0 { + firstNonTssOutputAddress = addrs[0].String() + log.Debug("analyzeTransaction non-TSS output found", "txHash", hash.String(), + "outputIndex", i, "address", firstNonTssOutputAddress, "amount", btcutil.Amount(output.Value)) + } + } + } + + hasTssInput := false + // 检查输入:直接从witness解析公钥验证是否来自TSS地址(仅支持 P2WPKH,不支持 Taproot/嵌套 SegWit) + if len(tx.TxIn) > 0 { + if witness := tx.TxIn[0].Witness; len(witness) == 2 && + bytes.Equal(witness[1], b.tssPubKey.SerializeCompressed()) { + hasTssInput = true + } + } + + log.Debug("analyzeTransaction analysis result", "txHash", hash.String(), + "hasTssInput", hasTssInput, "hasTssOutput", hasTssOutput, + "depositAmount", depositAmount, "firstNonTssAddress", firstNonTssOutputAddress) + + // 根据规则判断交易类型 + // 提现交易特征:有TSS输入,有非TSS输出 + if hasTssInput && firstNonTssOutputAddress != "" { + info.withdrawAddress = firstNonTssOutputAddress + info.txType = transactionTypeWithdraw + info.withdrawAmount = withdrawAmount + info.chain33WithdrawTxHash = []byte(info.opReturnData.payload) + return info + } else if hasTssOutput && !hasTssInput { // 充值交易特征:有TSS输出,无TSS输入 + info.depositAmount = depositAmount + info.txType = transactionTypeDeposit + info.chain33DepositAddress = info.opReturnData.payload + return info + } + + // 不符合充值或提现特征,可能是小额合并交易 + log.Debug("analyzeTransaction not deposit/withdraw", "txHash", hash.String(), + "hasTssInput", hasTssInput, "hasTssOutput", hasTssOutput) + return nil +} + +// sendTransactionNotification 发送交易确认通知 +func (b *btcWallet) sendTransactionNotification(_ chainhash.Hash, pending *btcPendingTx) { + if pending.txType == "deposit" { + b.depositChan <- pending + } else { + b.withdrawChan <- pending + } +} + +// buildWithdrawTx 构建提现交易 +// 返回: (交易, 输入金额列表, 已锁定的UTXO列表, 错误) +// 注意: 如果返回错误,UTXO锁定会自动释放;如果成功,调用方需要在广播失败时调用releaseUTXOs +func (b *btcWallet) buildWithdrawTx(req *withdrawRequest) (*wire.MsgTx, []int64, []*UTXO, error) { + + // 解析目标地址 + toAddr, err := btcutil.DecodeAddress(req.toAddress, &b.chainParams) + if err != nil { + return nil, nil, nil, fmt.Errorf("invalid to address: %w", err) + } + + // 构建输出 + pkScript, err := txscript.PayToAddrScript(toAddr) + if err != nil { + return nil, nil, nil, fmt.Errorf("create pk script failed: %w", err) + } + + outputs := []*wire.TxOut{ + { + Value: int64(req.amount), + PkScript: pkScript, + }, + } + + // 手动选择UTXO并构建交易 + tx, inputAmounts, lockedUTXOs, err := b.buildTransaction(outputs, req.feeRate, req.chain33WithDrawHash, true, req.stickyUTXO) + if err != nil { + return nil, nil, nil, err + } + + log.Debug("buildWithdrawTx", "to", req.toAddress, "amount", req.amount, "feeRate", req.feeRate, + "inputs", len(tx.TxIn), "outputs", len(tx.TxOut), "lockedUTXOs", len(lockedUTXOs)) + + return tx, inputAmounts, lockedUTXOs, nil +} + +// buildTransaction 手动构建交易 +// 返回: (交易, 输入金额列表, 选中的UTXO列表, 错误) +func (b *btcWallet) buildTransaction(outputs []*wire.TxOut, feeRate btcutil.Amount, chain33Hash []byte, + receiverPaysFee bool, stickyUTXO *UTXO) (*wire.MsgTx, []int64, []*UTXO, error) { + // 计算输出总额 + var outputTotal btcutil.Amount + for _, output := range outputs { + outputTotal += btcutil.Amount(output.Value) + } + + hashStr := hex.EncodeToString(chain33Hash) + // 获取可用UTXO + utxos, err := b.listUnspent() + if err != nil { + log.Error("buildTransaction listUnspent failed", "hash", hashStr, "targetAmount", outputTotal, + "utxos", len(utxos), "err", err) + return nil, nil, nil, fmt.Errorf("list unspent failed: %w", err) + } + + // 选择并锁定UTXO(防止并发双花) + selectionFeeRate := feeRate + if receiverPaysFee { + selectionFeeRate = 0 + } + selectedUTXOs, inputTotal, err := b.selectAndLockUTXOs(utxos, outputTotal, selectionFeeRate, stickyUTXO) + if err != nil { + log.Error("buildTransaction selectAndLockUTXOs failed", "hash", hashStr, "targetAmount", outputTotal, + "err", err) + return nil, nil, nil, err + } + + // 创建交易 + tx := wire.NewMsgTx(wire.TxVersion) + + // 添加输入 + inputAmounts := make([]int64, 0, len(selectedUTXOs)) + for _, utxo := range selectedUTXOs { + tx.AddTxIn(wire.NewTxIn(&utxo.OutPoint, nil, nil)) + inputAmounts = append(inputAmounts, int64(utxo.Amount)) + } + + buf := make([]byte, 0, withdrawOpReturnDataLen) + buf = append(buf, []byte(withdrawOpReturnPrefix)...) + buf = append(buf, chain33Hash...) + opScript, err := txscript.NullDataScript(buf) + if err != nil { + log.Error("build tx op script failed", "hash", hashStr, "err", err) + return nil, nil, nil, err + } + tx.AddTxOut(wire.NewTxOut(0, opScript)) + // 添加输出 + for _, output := range outputs { + tx.AddTxOut(output) + } + + // 计算实际手续费 + fee := estimateBtcFee(tx, feeRate) + + // 计算找零 + change := inputTotal - outputTotal + if receiverPaysFee { + // 提现输出金额过小,则不构建交易 + if fee+minChangeAmount >= outputTotal { + b.releaseUTXOsExcept(selectedUTXOs, stickyUTXO) + log.Error("buildTransaction withdraw amount too small for fee", "hash", hashStr, "targetAmount", outputTotal, + "fee", fee, "change", change) + return nil, nil, nil, fmt.Errorf("withdraw amount too small for fee: amount %d, fee %d", outputTotal, fee) + } + outputs[0].Value = int64(outputTotal - fee) + } else { + change = inputTotal - outputTotal - fee + } + + // 注意:如果找零过小,会被用于交易费,由平台承担 + // 还有种方案是将这部分补贴给提现地址,减少用户提现成本 + if change > minChangeAmount { + // 添加找零输出(使用预计算的脚本) + tx.AddTxOut(wire.NewTxOut(int64(change), b.tssPkScript)) + } else if change < 0 { + // 资金不足,释放已锁定的UTXO + b.releaseUTXOsExcept(selectedUTXOs, stickyUTXO) + log.Error("buildTransaction insufficient funds", "hash", hashStr, "targetAmount", outputTotal, + "fee", fee, "change", change) + return nil, nil, nil, fmt.Errorf("insufficient funds: need %d, have %d", outputTotal+fee, inputTotal) + } + + log.Debug("buildTransaction success", "inputs", len(selectedUTXOs), "inputTotal", inputTotal, + "outputTotal", outputTotal, "fee", fee, "change", change) + + return tx, inputAmounts, selectedUTXOs, nil +} + +// listUnspent 获取可用UTXO +func (b *btcWallet) listUnspent() ([]*UTXO, error) { + + // 获取钱包中的未花费输出 + unspentOutputs, err := b.Wallet.ListUnspent(b.requiredConfs, math.MaxInt32, "") + if err != nil { + return nil, fmt.Errorf("list unspent from wallet failed: %w", err) + } + + var utxos []*UTXO + totalAmount := btcutil.Amount(0) + for _, output := range unspentOutputs { + // 解析OutPoint + txHash, err := chainhash.NewHashFromStr(output.TxID) + if err != nil { + log.Error("listUnspent invalid txid", "txid", output.TxID, "err", err) + continue + } + + // 转换金额 + amount, err := btcutil.NewAmount(output.Amount) + if err != nil { + log.Error("listUnspent invalid amount", "amount", output.Amount, "err", err) + continue + } + + // 解析脚本 + pkScript, err := hex.DecodeString(output.ScriptPubKey) + if err != nil { + log.Error("listUnspent invalid script", "script", output.ScriptPubKey, "err", err) + continue + } + if !bytes.Equal(pkScript, b.tssPkScript) { + log.Debug("listUnspent skip non-tss utxo", "txid", output.TxID, "vout", output.Vout, "amount", amount) + continue + } + + utxo := &UTXO{ + OutPoint: wire.OutPoint{ + Hash: *txHash, + Index: output.Vout, + }, + Amount: amount, + PkScript: pkScript, + } + utxos = append(utxos, utxo) + totalAmount += amount + } + + log.Debug("listUnspent", "count", len(utxos), "totalAmount", totalAmount.ToBTC()) + return utxos, nil +} + +// selectUTXOs 找到最少数量的UTXO组合 +// 策略:按金额从大到小排序,依次选择直到满足需求 +func (b *btcWallet) selectUTXOs(utxos []*UTXO, targetAmount, feeRate btcutil.Amount, stickyCount int) ([]*UTXO, btcutil.Amount, error) { + // 按金额从小到大排序 + sorted := make([]*UTXO, len(utxos)) + copy(sorted, utxos) + sort.Slice(sorted, func(i, j int) bool { + return sorted[i].Amount < sorted[j].Amount + }) + + //从小到大,找第一个大于等于amount的UTXO索引,如果没有则返回最大的 + findFirstGreaterOrEqual := func(sorted []*UTXO, amount btcutil.Amount) int { + + for i := 0; i < len(sorted); i++ { + if sorted[i].Amount >= amount { + return i + } + } + return len(sorted) - 1 + } + + var selected []*UTXO + var total btcutil.Amount + for len(sorted) > 0 { + // 估算当前手续费 + txSize := b.estimateTxSize(len(selected)+stickyCount+1, 2, withdrawOpReturnScriptLen) + fee := btcutil.Amount(txSize) * feeRate + needed := targetAmount + fee - total + idx := findFirstGreaterOrEqual(sorted, needed) + selected = append(selected, sorted[idx]) + total += sorted[idx].Amount + sorted = sorted[:idx] + // 检查是否满足需求 + if targetAmount+fee <= total { + log.Debug("selectUTXOs success", "selected", len(selected), "total", total, + "target", targetAmount, "fee", fee, "waste", total-targetAmount-fee) + return selected, total, nil + } + } + + log.Debug("selectUTXOs insufficient funds", "target", targetAmount, "total", total) + return nil, 0, fmt.Errorf("insufficient funds: need %d, have %d", targetAmount, total) +} + +// estimateTxSize 估算交易大小 +// inputCount: 输入数量 +// p2wpkhOutputCount: P2WPKH输出数量 +// opReturnScriptSize: OP_RETURN脚本长度(字节) +// 返回: 交易大小(字节) +func (b *btcWallet) estimateTxSize(inputCount, p2wpkhOutputCount, opReturnScriptSize int) int { + // 基本交易大小 + baseSize := 10 // version(4) + locktime(4) + input_count(1) + output_count(1) + + // P2WPKH输入大小 + // - OutPoint: 36字节 (txid:32 + index:4) + // - ScriptSig: 1字节 (空) + // - Sequence: 4字节 + // - Witness: ~108字节 (signature:72 + pubkey:33 + witness_count:1 + lengths:2) + inputSize := inputCount * (36 + 1 + 4 + 108) + + // P2WPKH输出大小 + // - Value: 8字节 + // - ScriptPubKey: 23字节 (OP_0 + 20字节pubkey hash) + outputSize := p2wpkhOutputCount * (8 + 23) + if opReturnScriptSize > 0 { + outputSize += 8 + 1 + opReturnScriptSize // value + script len + script + } + + return baseSize + inputSize + outputSize +} + +// selectAndLockUTXOs 选择并锁定UTXO(用于提现交易) +// 使用wallet.LeaseOutput实现持久化锁定,防止UTXO被重复使用 +func (b *btcWallet) selectAndLockUTXOs(utxos []*UTXO, targetAmount, feeRate btcutil.Amount, stickyUTXO *UTXO) ([]*UTXO, btcutil.Amount, error) { + + log.Debug("selectAndLockUTXOs", "total", len(utxos), "targetAmount", targetAmount, + "feeRate", feeRate, "stickyUTXO", stickyUTXO != nil) + + stickyCount := 0 + if stickyUTXO != nil { + targetAmount -= stickyUTXO.Amount + // 如果剩余所需金额为0,且没有其他UTXO,则直接返回指定输入 + if len(utxos) == 0 && targetAmount <= 0 { + return []*UTXO{stickyUTXO}, stickyUTXO.Amount, nil + } + stickyCount = 1 + } + + selected, total, err := b.selectUTXOs(utxos, targetAmount, feeRate, stickyCount) + if err != nil { + log.Error("selectAndLockUTXOs selectUTXOs failed", "utxos", len(utxos), "targetAmount", targetAmount, + "feeRate", feeRate, "err", err) + return nil, 0, err + } + // 如果存在指定输入,则将其添加到选中的UTXO列表中,并累加金额 + if stickyUTXO != nil { + selected = append(selected, stickyUTXO) + total += stickyUTXO.Amount + } + + // 锁定选中的UTXO + lockedUTXOs := make([]*UTXO, 0, len(selected)) + for _, utxo := range selected { + // 使用wallet.LeaseOutput锁定UTXO + expiry, err := b.Wallet.LeaseOutput(utxoLockID, utxo.OutPoint, utxoLeaseDuration) + if err != nil { + log.Error("selectAndLockUTXOs lease output failed", "outpoint", utxo.OutPoint.String(), "err", err) + // 锁定失败,释放已锁定的UTXO + b.releaseUTXOsExcept(lockedUTXOs, stickyUTXO) + return nil, 0, fmt.Errorf("lease output failed: %w", err) + } + + lockedUTXOs = append(lockedUTXOs, utxo) + log.Debug("selectAndLockUTXOs locked UTXO", "outpoint", utxo.OutPoint.String(), "amount", utxo.Amount, "expiry", expiry) + } + + log.Debug("selectAndLockUTXOs success", "utxos", len(utxos), "selected", len(lockedUTXOs), + "totalAmount", total, "targetAmount", targetAmount, "feeRate", feeRate) + + return lockedUTXOs, total, nil +} + +// releaseUTXOsExcept 释放UTXO锁定,可选保留指定输入不释放 +func (b *btcWallet) releaseUTXOsExcept(utxos []*UTXO, keep *UTXO) { + if len(utxos) == 0 { + return + } + + log.Debug("releaseUTXOs start", "count", len(utxos)) + + for _, utxo := range utxos { + if keep != nil && utxo.OutPoint == keep.OutPoint { + log.Debug("releaseUTXOsExcept keep sticky UTXO", "outpoint", utxo.OutPoint.String(), "amount", utxo.Amount) + continue + } + err := b.Wallet.ReleaseOutput(utxoLockID, utxo.OutPoint) + if err != nil { + log.Error("releaseUTXOs release output failed", + "outpoint", utxo.OutPoint.String(), + "err", err) + } + } +} + +// UTXO 结构 +type UTXO struct { + OutPoint wire.OutPoint + Amount btcutil.Amount + PkScript []byte +} + +// broadcastTransaction 广播交易 +// lockedUTXOs: 已锁定的UTXO列表,广播失败时会自动释放 +func (b *btcWallet) broadcastTransaction(tx *wire.MsgTx, btcTxHash string) error { + + if b.rpcClient != nil { + _, err := b.rpcClient.SendRawTransaction(tx, false) + if err != nil { + log.Error("BroadcastTransaction failed", "txHash", btcTxHash, "err", err) + return err + } + } else { + _, err := b.chainClient.SendRawTransaction(tx, false) + if err != nil { + log.Error("BroadcastTransaction failed", "txHash", btcTxHash, "err", err) + return err + } + } + log.Debug("broadcastTransaction success", "txHash", btcTxHash) + return nil +} + +func buildBtcSpv(txHash, blockHash string, blockTime int64, blockHeight uint64, txs [][]byte, txIndex uint32) *lighttypes.BtcSpv { + return &lighttypes.BtcSpv{ + TxHash: txHash, + Time: blockTime, + Height: blockHeight, + BlockHash: blockHash, + TxIndex: txIndex, + BranchProof: merkle.GetMerkleBranch(txs, txIndex), + } +} + +func buildTxHashesFromVerbose(txIDs []string, targetTxHash string) ([][]byte, uint32, error) { + txs := make([][]byte, 0, len(txIDs)) + txIndex := uint32(0) + found := false + for idx, txID := range txIDs { + hash, err := chainhash.NewHashFromStr(txID) + if err != nil { + return nil, 0, err + } + txs = append(txs, hash.CloneBytes()) + if txID == targetTxHash { + txIndex = uint32(idx) + found = true + } + } + if !found { + return nil, 0, fmt.Errorf("tx not found in block") + } + return txs, txIndex, nil +} + +func buildTxHashesFromBlockTxs(blockTxs []*wire.MsgTx, targetTxHash chainhash.Hash) ([][]byte, uint32, error) { + txs := make([][]byte, 0, len(blockTxs)) + txIndex := uint32(0) + found := false + for idx, tx := range blockTxs { + hash := tx.TxHash() + txs = append(txs, hash.CloneBytes()) + if hash == targetTxHash { + txIndex = uint32(idx) + found = true + } + } + if !found { + return nil, 0, fmt.Errorf("tx not found in block") + } + return txs, txIndex, nil +} + +// buildTxExistenceProof 计算交易存在性证明(SPV) +// 输入: pendingTx(需要包含 tx 和 blockHash) +// 输出: lighttypes.BtcSpv +func (b *btcWallet) buildTxExistenceProof(pending *btcPendingTx) (*lighttypes.BtcSpv, error) { + if pending == nil || pending.tx == nil { + return nil, fmt.Errorf("pending tx data missing") + } + if pending.blockHeight <= 0 { + return nil, fmt.Errorf("invalid pending block height") + } + txHashStr := pending.tx.TxHash().String() + if b.rpcClient != nil { + block, err := b.rpcClient.GetBlockVerbose(&pending.blockHash) + if err != nil { + log.Error("buildTxExistenceProof GetBlockVerbose failed, fallback neutrino", + "txHash", txHashStr, "blockHash", pending.blockHash.String(), "err", err) + } else { + txs, txIndex, err := buildTxHashesFromVerbose(block.Tx, txHashStr) + if err != nil { + log.Error("buildTxExistenceProof buildTxHashesFromVerbose failed", + "txHash", txHashStr, "blockHash", pending.blockHash.String(), "err", err) + return nil, err + } + return buildBtcSpv( + txHashStr, pending.blockHash.String(), block.Time, uint64(block.Height), txs, txIndex, + ), nil + } + } + + block, err := b.chainClient.GetBlock(&pending.blockHash) + if err != nil { + log.Error("buildTxExistenceProof GetBlock failed", + "txHash", txHashStr, "blockHash", pending.blockHash.String(), "err", err) + return nil, err + } + + txs, txIndex, err := buildTxHashesFromBlockTxs(block.Transactions, pending.txHash) + if err != nil { + log.Error("buildTxExistenceProof buildTxHashesFromBlockTxs failed", + "txHash", txHashStr, "blockHash", pending.blockHash.String(), "err", err) + return nil, err + } + return buildBtcSpv( + txHashStr, pending.blockHash.String(), block.Header.Timestamp.Unix(), uint64(pending.blockHeight), txs, txIndex, + ), nil +} + +// GetBalance 获取余额 +func (b *btcWallet) getBalance() (btcutil.Amount, error) { + + // 获取已确认余额 + balance, err := b.Wallet.CalculateBalance(b.requiredConfs) + if err != nil { + return 0, fmt.Errorf("calculate balance failed: %w", err) + } + + return balance, nil +} + +// GetDepositChannel 获取充值通知channel +func (b *btcWallet) GetDepositChannel() <-chan *btcPendingTx { + return b.depositChan +} + +// GetWithdrawChannel 获取提现通知channel +func (b *btcWallet) GetWithdrawChannel() <-chan *btcPendingTx { + return b.withdrawChan +} diff --git a/plugin/dapp/lightclient/rpc/lightclient/neutrino/cache.go b/plugin/dapp/lightclient/rpc/lightclient/neutrino/cache.go new file mode 100644 index 0000000000..67253d16fa --- /dev/null +++ b/plugin/dapp/lightclient/rpc/lightclient/neutrino/cache.go @@ -0,0 +1,50 @@ +package neutrino + +import ( + "math" + "sync" + + rtypes "github.com/33cn/plugin/plugin/dapp/rgbx/types" +) + +type pendingTxCache struct { + lock sync.RWMutex + pendingCache map[string]*rtypes.PendingTx +} + +func newPendingTxCache(size int) *pendingTxCache { + return &pendingTxCache{pendingCache: make(map[string]*rtypes.PendingTx, size)} +} + +func (p *pendingTxCache) addTx(hash string, value *rtypes.PendingTx) { + p.lock.Lock() + defer p.lock.Unlock() + p.pendingCache[hash] = value +} + +func (p *pendingTxCache) getTx(hash string) *rtypes.PendingTx { + p.lock.RLock() + defer p.lock.RUnlock() + return p.pendingCache[hash] +} + +func (p *pendingTxCache) removeTx(hash string) *rtypes.PendingTx { + p.lock.Lock() + defer p.lock.Unlock() + tx := p.pendingCache[hash] + delete(p.pendingCache, hash) + return tx +} + +func (p *pendingTxCache) getMinPendingHeight() int64 { + p.lock.RLock() + defer p.lock.RUnlock() + minHeight := int64(math.MaxInt64) + for _, tx := range p.pendingCache { + if tx.TxBlockHeight < minHeight { + minHeight = tx.TxBlockHeight + } + } + + return minHeight +} diff --git a/plugin/dapp/lightclient/rpc/lightclient/neutrino/cache_test.go b/plugin/dapp/lightclient/rpc/lightclient/neutrino/cache_test.go new file mode 100644 index 0000000000..3796000b3f --- /dev/null +++ b/plugin/dapp/lightclient/rpc/lightclient/neutrino/cache_test.go @@ -0,0 +1,32 @@ +package neutrino + +import ( + "testing" + + rtypes "github.com/33cn/plugin/plugin/dapp/rgbx/types" + "github.com/stretchr/testify/require" +) + +func TestPendingTxCache(t *testing.T) { + + c := newPendingTxCache(10) + tx := &rtypes.PendingTx{ActionType: 1} + c.addTx("test", tx) + tx1 := c.getTx("test") + require.Equal(t, tx.ActionType, tx1.ActionType) + require.Equal(t, 1, len(c.pendingCache)) + tx2 := c.removeTx("test") + require.Equal(t, tx, tx2) + + require.Equal(t, 0, len(c.pendingCache)) + + tx.TxBlockHeight = 1 + c.addTx("test", tx) + require.Equal(t, int64(1), c.getMinPendingHeight()) + + c.addTx("other", &rtypes.PendingTx{TxBlockHeight: 10}) + require.Equal(t, int64(1), c.getMinPendingHeight()) + + c.addTx("low", &rtypes.PendingTx{TxBlockHeight: 0}) + require.Equal(t, int64(0), c.getMinPendingHeight()) +} diff --git a/plugin/dapp/lightclient/rpc/lightclient/neutrino/client.go b/plugin/dapp/lightclient/rpc/lightclient/neutrino/client.go new file mode 100644 index 0000000000..3fceb1f4c4 --- /dev/null +++ b/plugin/dapp/lightclient/rpc/lightclient/neutrino/client.go @@ -0,0 +1,255 @@ +// Copyright Fuzamei Corp. 2018 All Rights Reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package neutrino integrate btc light client neutrino +package neutrino + +import ( + "context" + "encoding/json" + "sync" + "time" + + "github.com/33cn/chain33/client" + "github.com/33cn/chain33/common/address" + "github.com/33cn/chain33/common/crypto" + "github.com/33cn/chain33/queue" + "github.com/33cn/chain33/rpc/grpcclient" + "github.com/33cn/chain33/system/crypto/secp256k1" + "github.com/33cn/chain33/types" + "github.com/lightninglabs/neutrino/headerfs" + + "github.com/33cn/chain33/common/log/log15" + "github.com/33cn/plugin/plugin/dapp/lightclient/rpc/lightclient" + rtypes "github.com/33cn/plugin/plugin/dapp/rgbx/types" + _ "github.com/btcsuite/btcwallet/walletdb/bdb" + "github.com/lightninglabs/neutrino" +) + +var log = log15.New("module", "lightclient.neutrino") + +var _ lightclient.Lighter = &neutrinoClient{} + +func init() { + + lightclient.Register("neutrino", newClient) +} + +func newClient() lightclient.Lighter { + return &neutrinoClient{} +} + +type neutrinoClient struct { + ctx context.Context + qclient queue.Client + chain33Api client.QueueProtocolAPI + mainChainGrpc types.Chain33Client + cfg config + commitAddressType int32 + commitAddr string + commitKey crypto.PrivKey + commitKeyMu sync.RWMutex + initCommitKeyOnce sync.Once + neutrinoCfg neutrino.Config + tss *tssService + neutrinoCS *neutrino.ChainService + bw *btcWallet + rgbx *rgbx + bestBlock *headerfs.BlockStamp + lock sync.RWMutex + chain33FeeRate int64 + withdrawReqChan chan *rtypes.PendingTx +} + +// Init init client context +func (n *neutrinoClient) Init(ctx context.Context, q queue.Queue, cfg *lightclient.Config) error { + + n.ctx = ctx + n.qclient = q.Client() + n.chain33Api, _ = client.New(n.qclient, nil) + n.chain33FeeRate = 100000 + n.commitAddr = cfg.CommitAddr + commitAddressType, err := address.GetAddressType(n.commitAddr) + if err != nil { + panic("invalid address type for authAccount config, " + n.commitAddr) + } + n.commitAddressType = commitAddressType + if cfg.CommitKey != "" { + _, n.commitKey, err = getPrivKey(secp256k1.Name, cfg.CommitKey) + if err != nil { + return err + } + } + subCfg, _ := json.Marshal(cfg.Neutrino) + types.MustDecode(subCfg, &n.cfg) + chainCfg := q.GetConfig() + n.mainChainGrpc, err = grpcclient.NewMainChainClient(chainCfg, "") + if err != nil { + panic("init main chain grpc client err:" + err.Error()) + } + n.tss = newTssService(n) + err = n.initNeutrinoConfig(chainCfg) + if err != nil { + log.Error("Init", "initNeutrinoConfig error", err) + return err + } + if !n.cfg.IsOfficialNode { + return nil + } + n.withdrawReqChan = make(chan *rtypes.PendingTx, 256) + cs, err := neutrino.NewChainService(n.neutrinoCfg) + if err != nil { + log.Error("Init", "NewChainService error", err) + _ = n.neutrinoCfg.Database.Close() + return err + } + n.neutrinoCS = cs + bw, err := newBtcWallet(n) + if err != nil { + log.Error("Init", "newBtcWallet error", err) + return err + } + n.bw = bw + n.rgbx = newRGBX() + return nil + +} + +// Start starting routine +func (n *neutrinoClient) Start() { + + n.initCommitKey() + n.tss.start() + go n.subMsg() + go n.cleanUp() + if !n.cfg.IsOfficialNode { + return + } + if err := n.neutrinoCS.Start(); err != nil { + log.Error("Start", "neutrinoCS start error", err) + _ = n.neutrinoCfg.Database.Close() + panic(err) + } + + go n.handleBlockSync() + go n.submitBitcoinHeaders() + // 依赖tss地址的任务需要等待tss完成 + n.waitUntilDone("waitDKGCompleted", func() bool { + return n.tss.isDKGCompleted() + }, time.Second*3) + if err := n.bw.start(); err != nil { + log.Error("Start", "btcwallet start error", err) + n.bw.stop() + panic(err) + } + go n.depositWatcher() + go n.withdrawalProcessor() + n.rgbx.start(n) +} + +// handle subscription messages +func (n *neutrinoClient) subMsg() { + + n.qclient.Sub(moduleName) + for { + + select { + case <-n.ctx.Done(): + return + case msg := <-n.qclient.Recv(): + + if msg == nil { + log.Error("SubMsg", "err", "receive nil msg") + return + } + data, ok := msg.Data.(*types.TopicData) + if msg.Ty == types.EventReceiveSubData && ok && data.Topic == tssSignNotifyTopic { + n.tss.subChan <- data + } else { + log.Error("SubMsg receive invalid msg", "ty", msg.Ty, "ok", ok) + } + } + } +} + +func (n *neutrinoClient) cleanUp() { + + <-n.ctx.Done() + if n.cfg.IsOfficialNode { + if err := n.neutrinoCS.Stop(); err != nil { + log.Error("cleanUp Unable to stop neutrino server", "err", err) + } + n.bw.stop() + } + if err := n.neutrinoCfg.Database.Close(); err != nil { + log.Error("cleanUp Unable to close neutrino db", "err", err) + } +} + +func (n *neutrinoClient) handleBlockSync() { + + n.syncBestBlock() + interval := time.Duration(n.cfg.BtcBlockInterval)/3 + 1 + ticker := time.NewTicker(time.Second * interval) + checkTipTicker := time.NewTicker(time.Minute) + defer ticker.Stop() + defer checkTipTicker.Stop() + for { + + select { + + case <-n.ctx.Done(): + return + case <-ticker.C: + + n.syncBestBlock() + case <-checkTipTicker.C: + _, blkTip, err1 := n.neutrinoCS.BlockHeaders.ChainTip() + _, filTip, err2 := n.neutrinoCS.RegFilterHeaders.ChainTip() + if err1 != nil || err2 != nil { + log.Warn("read header tip failed", "blkErr", err1, "filErr", err2) + return + } + log.Info("checkTip", "blockTip", blkTip, "filterTip", filTip, "isCurrent", n.neutrinoCS.IsCurrent()) + if blkTip > filTip+1 { + // 已经有 block header 但 cfheader 跟不上,且不是仅差 1 的同步窗口 + log.Error("cfheaders lagging block headers; check btcd --peerblockfilters", + "blockTip", blkTip, "filterTip", filTip) + } + } + } +} + +func (n *neutrinoClient) syncBestBlock() { + blk, err := n.neutrinoCS.BestBlock() + if err != nil { + log.Error("syncBestBlock", "err", err) + return + } + // log.Debug("syncBestBlock", "height", blk.Height, "hash", blk.Hash.String()) + n.setBestBlock(blk) +} + +func (n *neutrinoClient) getBestBlock() *headerfs.BlockStamp { + n.lock.RLock() + defer n.lock.RUnlock() + return n.bestBlock +} + +func (n *neutrinoClient) setBestBlock(blk *headerfs.BlockStamp) { + n.lock.Lock() + defer n.lock.Unlock() + if blk != nil { + n.bestBlock = blk + } +} + +func (n *neutrinoClient) getBestBlockHeight() int32 { + n.lock.RLock() + defer n.lock.RUnlock() + if n.bestBlock != nil { + return n.bestBlock.Height + } + return 0 +} diff --git a/plugin/dapp/lightclient/rpc/lightclient/neutrino/client_test.go b/plugin/dapp/lightclient/rpc/lightclient/neutrino/client_test.go new file mode 100644 index 0000000000..60751959e0 --- /dev/null +++ b/plugin/dapp/lightclient/rpc/lightclient/neutrino/client_test.go @@ -0,0 +1,82 @@ +// Copyright Fuzamei Corp. 2018 All Rights Reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package neutrino + +import ( + "sync" + "testing" + + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/lightninglabs/neutrino/headerfs" + "github.com/stretchr/testify/assert" +) + +// TestBestBlockGetterSetter tests the concurrent-safe best block access +func TestBestBlockGetterSetter(t *testing.T) { + n := &neutrinoClient{} + + // Initially nil + assert.Nil(t, n.getBestBlock()) + + // Set a valid block + blk := &headerfs.BlockStamp{ + Height: 100, + Hash: [32]byte{1, 2, 3, 4}, + } + n.setBestBlock(blk) + + // Verify we can read it back + result := n.getBestBlock() + assert.NotNil(t, result) + assert.Equal(t, int32(100), result.Height) + assert.Equal(t, chainhash.Hash([32]byte{1, 2, 3, 4}), result.Hash) + + // Test setting nil doesn't overwrite existing value + n.setBestBlock(nil) + result = n.getBestBlock() + assert.NotNil(t, result) // Should still be the previous value + assert.Equal(t, int32(100), result.Height) +} + +// TestBestBlockConcurrentAccess tests thread safety +func TestBestBlockConcurrentAccess(t *testing.T) { + n := &neutrinoClient{} + + var wg sync.WaitGroup + numGoroutines := 100 + numIterations := 100 + + // Concurrent writes + wg.Add(numGoroutines) + for i := 0; i < numGoroutines; i++ { + go func(idx int) { + defer wg.Done() + for j := 0; j < numIterations; j++ { + blk := &headerfs.BlockStamp{ + Height: int32(idx*numIterations + j), + } + n.setBestBlock(blk) + } + }(i) + } + + // Concurrent reads + wg.Add(numGoroutines) + for i := 0; i < numGoroutines; i++ { + go func() { + defer wg.Done() + for j := 0; j < numIterations; j++ { + _ = n.getBestBlock() + } + }() + } + + wg.Wait() + + // Final value should be valid (not panic or corrupt) + result := n.getBestBlock() + assert.NotNil(t, result) + assert.GreaterOrEqual(t, result.Height, int32(0)) +} diff --git a/plugin/dapp/lightclient/rpc/lightclient/neutrino/config.go b/plugin/dapp/lightclient/rpc/lightclient/neutrino/config.go new file mode 100644 index 0000000000..81b21201d4 --- /dev/null +++ b/plugin/dapp/lightclient/rpc/lightclient/neutrino/config.go @@ -0,0 +1,198 @@ +package neutrino + +import ( + "os" + "path/filepath" + "time" + + "github.com/33cn/chain33/types" + ltypes "github.com/33cn/plugin/plugin/dapp/lightclient/lighttypes" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/rpcclient" + "github.com/btcsuite/btclog" + "github.com/btcsuite/btcwallet/chain" + "github.com/btcsuite/btcwallet/waddrmgr" + "github.com/btcsuite/btcwallet/wallet" + "github.com/btcsuite/btcwallet/walletdb" + "github.com/btcsuite/btcwallet/wtxmgr" + "github.com/lightninglabs/neutrino" +) + +// defaultBlockCacheSize is the size (in bytes) of blocks that will be +// keep in memory if no size is specified. +const defalutBlockCacheSize = 20 * 1024 * 1024 //20 MB + +type config struct { + + // IsOfficialNode 是否为官方主节点 + IsOfficialNode bool + // MaxPeers is the maximum number of connections the client maintains. + MaxPeer int `json:"maxPeer"` + // BlockCacheSize indicates the size (in bytes) of blocks the block + // cache will hold in memory at most. If a BlockCache is provided then + // BlockCacheSize is ignored. + BlockCacheSize uint64 `json:"blockCacheSize"` + // NetName btc network name + NetName string `json:"netName"` + // AddPeers is a slice of hosts that should be connected to on startup, + // and be maintained as persistent peers. + AddPeers []string `json:"addPeers"` + // ConnectPeers is a slice of hosts that should be connected to on + // startup, and be established as persistent peers. + // + // NOTE: If specified, we'll *only* connect to this set of peers and + // won't attempt to automatically seek outbound peers. + ConnectPeers []string `json:"connectPeers"` + + // BtcBlockInterval 区块时间间隔,second + BtcBlockInterval uint32 `json:"btcBlockInterval"` + // BlockConfirmations 区块确认数 + BlockConfirmations uint32 `json:"blockConfirmations"` + // BtcHeaderStartHeight 首次启动提交btc header的起始高度 + BtcHeaderStartHeight uint64 `json:"btcHeaderStartHeight"` + // MaxUtxoRescanTime, utxo 检索最大时长,hour, 0为永不超时 + MaxUtxoRescanTime int64 `json:"maxUtxoRescanTime"` + // BtcFullNodeRPC 可选,比特币全节点 RPC 配置,用于查询 block 构造SPV + BtcRPC btcRPCConfig `json:"btcRPC"` + // Tss tss config + Tss tssConfig `json:"tss"` +} + +type btcRPCConfig struct { + // Host 例如: 127.0.0.1:8332 或 btc-node.example.com:8332 + Host string `json:"host"` + // User/Pass 对应 bitcoin.conf 的 rpcuser/rpcpassword + User string `json:"user"` + Pass string `json:"pass"` + // Mode: ws(默认) 或 http + Mode string `json:"mode"` + // DisableTLS 是否禁用 TLS + DisableTLS bool `json:"disableTLS"` + // CertFile TLS 证书文件(可选) + CertFile string `json:"certFile"` +} + +func (c *btcRPCConfig) toConnConfig() (*rpcclient.ConnConfig, error) { + endpoint := "ws" + httpPostMode := false + if c.Mode == "http" { + endpoint = "http" + httpPostMode = true + } + conn := &rpcclient.ConnConfig{ + Host: c.Host, + Endpoint: endpoint, + User: c.User, + Pass: c.Pass, + DisableTLS: c.DisableTLS, + HTTPPostMode: httpPostMode, + } + if c.CertFile == "" { + return conn, nil + } + certs, err := os.ReadFile(c.CertFile) + if err != nil { + return nil, err + } + conn.Certificates = certs + return conn, nil +} + +type tssConfig struct { + // Peers peers name + Peers []string `json:"peers"` + // Threshold peer threshold + Threshold uint32 `json:"threshold"` + // Rank peer rank + Rank uint32 `json:"rank"` +} + +func (c config) getChainParams() chaincfg.Params { + + params := ltypes.GetBtcChainParams(c.NetName) + return *params +} + +func (n *neutrinoClient) initNeutrinoConfig(chainCfg *types.Chain33Config) error { + + if n.cfg.BlockCacheSize < 1024*1024 { + n.cfg.BlockCacheSize = defalutBlockCacheSize + } + if n.cfg.MaxPeer < 1 { + n.cfg.MaxPeer = 8 + } + + if n.cfg.BtcBlockInterval <= 0 { + n.cfg.BtcBlockInterval = 600 + } + if n.cfg.BtcHeaderStartHeight == 0 { + n.cfg.BtcHeaderStartHeight = 1 + } + if n.cfg.BlockConfirmations == 0 { + n.cfg.BlockConfirmations = defaultRequiredConfs + } + // convert to second + if n.cfg.MaxUtxoRescanTime > 0 { + n.cfg.MaxUtxoRescanTime *= int64(time.Hour / time.Second) + } + dbPath := filepath.Join(chainCfg.GetModuleConfig().BlockChain.DbPath, "lightclient") + _ = os.MkdirAll(dbPath, 0755) + _, db, err := openWalletDB(dbPath, "neutrino.db") + if err != nil { + log.Error("initNeutrinoConfig open db error", "err", err) + return err + } + + neutrino.MaxPeers = n.cfg.MaxPeer + neutrino.BanDuration = time.Hour * 48 + + n.neutrinoCfg = neutrino.Config{ + DataDir: dbPath, + Database: db, + ChainParams: n.cfg.getChainParams(), + ConnectPeers: n.cfg.ConnectPeers, + AddPeers: n.cfg.AddPeers, + //BlockCache: lru.NewCache[wire.InvVect, *neutrino.CacheableBlock](clientCfg.BlockCacheSize), + BlockCacheSize: n.cfg.BlockCacheSize, + } + + logCfg := chainCfg.GetModuleConfig().Log + dir := filepath.Dir(logCfg.LogFile) + logfile, err := os.OpenFile(filepath.Join(dir, "rgbx.log"), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) + if err != nil { + log.Error("initNeutrinoConfig open log file error", "err", err) + return err + } + logLevel := btclog.LevelInfo + if logCfg.Loglevel == "debug" || logCfg.Loglevel == "dbug" { + logLevel = btclog.LevelDebug + } + backend := btclog.NewBackend(logfile) + logger := func(subsystemTag string) btclog.Logger { + l := backend.Logger(subsystemTag) + l.SetLevel(logLevel) + return l + } + neutrino.UseLogger(logger("NEUT")) + waddrmgr.UseLogger(logger("WADM")) + wtxmgr.UseLogger(logger("WTXM")) + chain.UseLogger(logger("CHNS")) + wallet.UseLogger(logger("WLLT")) + return nil + +} + +func openWalletDB(path, dbName string) (exist bool, db walletdb.DB, err error) { + + dbPath := filepath.Join(path, dbName) + exist, err = fileExists(dbPath) + if err != nil { + return false, nil, err + } + if exist { + db, err = walletdb.Open("bdb", dbPath, true, time.Second*10, false) + } else { + db, err = walletdb.Create("bdb", dbPath, false, time.Second*10, false) + } + return exist, db, err +} diff --git a/plugin/dapp/lightclient/rpc/lightclient/neutrino/config_test.go b/plugin/dapp/lightclient/rpc/lightclient/neutrino/config_test.go new file mode 100644 index 0000000000..fe5833e347 --- /dev/null +++ b/plugin/dapp/lightclient/rpc/lightclient/neutrino/config_test.go @@ -0,0 +1,187 @@ +package neutrino + +import ( + "encoding/json" + "os" + "path/filepath" + "testing" + "time" + + "github.com/33cn/chain33/queue" + "github.com/33cn/chain33/types" + "github.com/33cn/chain33/util" + "github.com/33cn/plugin/plugin/dapp/lightclient/rpc/lightclient" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var lightConfig = ` + +[rpc.sub.light] +clients=["neutrino"] +commitAddr="14KEKbYtKKQm4wMthSK9J4La4nAiidGozt" + +[rpc.sub.light.neutrino] +netName="regtest" +addPeers=["127.0.0.1:8333"] +btcBlockInterval=10 +blockConfirmations=0 +maxUtxoRescanTime=24 + +` + +func Test_config(t *testing.T) { + + cfg := types.NewChain33Config(types.MergeCfg(types.GetDefaultCfgstring(), lightConfig)) + light := &lightclient.Config{} + types.MustDecode(cfg.GetSubConfig().RPC["light"], light) + require.Equal(t, 1, len(light.EnableClients)) + require.Equal(t, "neutrino", light.EnableClients[0]) + + sub, err := json.Marshal(light.Neutrino) + require.NoError(t, err) + subCfg := &config{} + types.MustDecode(sub, subCfg) + require.Equal(t, subCfg.NetName, "regtest") + + // test init neutrino config + n := &neutrinoClient{} + n.cfg = *subCfg + q := queue.New("test") + q.SetConfig(cfg) + require.NoError(t, err) + util.ResetDatadir(cfg.GetModuleConfig(), "$TEMP/") + err = n.initNeutrinoConfig(cfg) + if err != nil { + t.Skipf("initNeutrinoConfig needs walletdb/bdb (environment): %v", err) + } + + require.True(t, n.cfg.BlockCacheSize == defalutBlockCacheSize) + require.True(t, n.cfg.MaxPeer == 8) + require.True(t, n.cfg.BtcBlockInterval == 10) + require.True(t, n.cfg.BlockConfirmations == 0) + require.True(t, n.cfg.MaxUtxoRescanTime == int64(24*time.Hour/time.Second)) + dir := cfg.GetModuleConfig().BlockChain.DbPath + "/lightclient" + require.Equal(t, dir, n.neutrinoCfg.DataDir) + require.Equal(t, n.cfg.NetName, n.neutrinoCfg.ChainParams.Name) +} + +// TestBtcRPCConfig tests the btcRPCConfig.toConnConfig method +func TestBtcRPCConfig(t *testing.T) { + tests := []struct { + name string + config btcRPCConfig + wantErr bool + wantMode string + }{ + { + name: "ws mode (default)", + config: btcRPCConfig{ + Host: "127.0.0.1:8332", + User: "testuser", + Pass: "testpass", + Mode: "", + DisableTLS: true, + }, + wantErr: false, + wantMode: "ws", + }, + { + name: "http mode", + config: btcRPCConfig{ + Host: "127.0.0.1:8332", + User: "testuser", + Pass: "testpass", + Mode: "http", + DisableTLS: true, + }, + wantErr: false, + wantMode: "http", + }, + { + name: "with TLS cert", + config: btcRPCConfig{ + Host: "127.0.0.1:8332", + User: "testuser", + Pass: "testpass", + Mode: "ws", + DisableTLS: false, + CertFile: "/tmp/nonexistent_cert.pem", + }, + wantErr: true, // file doesn't exist + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + conn, err := tt.config.toConnConfig() + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + require.NotNil(t, conn) + assert.Equal(t, tt.config.Host, conn.Host) + assert.Equal(t, tt.config.User, conn.User) + assert.Equal(t, tt.config.Pass, conn.Pass) + assert.Equal(t, tt.wantMode, conn.Endpoint) + if tt.wantMode == "http" { + assert.True(t, conn.HTTPPostMode) + } + }) + } +} + +// TestConfigGetChainParams tests the config.getChainParams method +func TestConfigGetChainParams(t *testing.T) { + tests := []struct { + netName string + wantName string + }{ + {"mainnet", "mainnet"}, + {"testnet", "testnet3"}, + {"testnet4", "testnet4"}, + {"testnet3", "testnet3"}, + {"regtest", "regtest"}, + {"simnet", "simnet"}, + {"signet", "signet"}, + {"unknown", "mainnet"}, // default to mainnet + } + + for _, tt := range tests { + cfg := config{NetName: tt.netName} + params := cfg.getChainParams() + assert.Equal(t, tt.wantName, params.Name) + } +} + +// Test_openWalletDB verifies that openWalletDB correctly creates a new database +// when it doesn't exist and opens an existing one. +func Test_openWalletDB(t *testing.T) { + tmpDir := t.TempDir() + dbName := "test_neutrino.db" + + // Test 1: Create new database (doesn't exist yet) + exist, db, err := openWalletDB(tmpDir, dbName) + if err != nil { + t.Skipf("walletdb.Create requires bdb driver (environment): %v", err) + } + require.NoError(t, err) + assert.False(t, exist, "database should not exist before creation") + assert.NotNil(t, db, "database handle should not be nil after creation") + + // Verify database file was created + dbPath := filepath.Join(tmpDir, dbName) + _, statErr := os.Stat(dbPath) + assert.NoError(t, statErr, "database file should exist on disk") + + // Close the database before reopening + db.Close() + + // Test 2: Open existing database + exist2, db2, err2 := openWalletDB(tmpDir, dbName) + require.NoError(t, err2) + assert.True(t, exist2, "database should exist when reopening") + assert.NotNil(t, db2, "database handle should not be nil when reopening") + db2.Close() +} diff --git a/plugin/dapp/lightclient/rpc/lightclient/neutrino/rgbx.go b/plugin/dapp/lightclient/rpc/lightclient/neutrino/rgbx.go new file mode 100644 index 0000000000..fbebb9e3ba --- /dev/null +++ b/plugin/dapp/lightclient/rpc/lightclient/neutrino/rgbx.go @@ -0,0 +1,363 @@ +package neutrino + +import ( + "bytes" + "encoding/hex" + "runtime" + "strings" + "sync" + "time" + + "github.com/33cn/chain33/common/crypto" + "github.com/33cn/chain33/types" + rtypes "github.com/33cn/plugin/plugin/dapp/rgbx/types" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/lightninglabs/neutrino" + "github.com/lightninglabs/neutrino/headerfs" +) + +type rgbx struct { + client *neutrinoClient + commitTxKey crypto.PrivKey + pendingTxConfirmedHeight int64 + pendingTxPullHeight int64 + + lock sync.RWMutex + pendingCache *pendingTxCache + rescanChan chan *utxoRescanInfo + commitChan chan *utxoSpendInfo + requiredConfs int64 +} + +type utxoRescanInfo struct { + pendingTxHash string + outPointStr string + out wire.OutPoint + pkScript []byte + startHeight int32 + startTime int64 + confirmedHeight int32 +} + +type utxoSpendInfo struct { + spendingHeight uint32 + spendingInputIndex uint32 + pendingTxHash string + spendingTxHash string + spendingTx *wire.MsgTx + timeout bool +} + +func newRGBX() *rgbx { + r := &rgbx{} + r.pendingCache = newPendingTxCache(128) + r.rescanChan = make(chan *utxoRescanInfo, 1024) + r.commitChan = make(chan *utxoSpendInfo, 1024) + return r +} + +func (r *rgbx) start(cli *neutrinoClient) { + log.Debug("init rgbx service start") + r.client = cli + r.commitTxKey = cli.getCommitKey() + r.requiredConfs = int64(cli.cfg.BlockConfirmations) + + log.Debug("wait getRgbxConfirmedHeight") + cli.waitUntilDone("wait getRgbxConfirmedHeight", func() bool { + if height := cli.getRgbxConfirmedHeight(); height != nil { + r.pendingTxConfirmedHeight = height.GetData() + if r.pendingTxConfirmedHeight > 0 { + r.pendingTxPullHeight = height.GetData() + 1 + } + return true + } + return false + }, 0) + + // 等待轻节点同步 + log.Debug("wait neutrino sync", "currHeight", cli.getBestBlock().Height) + cli.waitUntilDone("wait neutrino sync", func() bool { + return cli.neutrinoCS.IsCurrent() + }, 0) + log.Debug("wait neutrino best block") + cli.waitUntilDone("wait neutrino best block", func() bool { + return cli.getBestBlock() != nil + }, 0) + + go r.pullPendingTx() + go r.handleCommitPendingTx() + for i := 0; i < runtime.NumCPU(); i++ { + go r.handleRescanUtxo() + } + + log.Debug("init rgbx service done") +} + +func (r *rgbx) pullPendingTx() { + ticker := time.NewTicker(time.Second * 10) + req := &rtypes.ReqListPendingTx{ + StartHeight: r.pendingTxPullHeight, + StartIndex: 0, + Count: 128, + } + for { + select { + + case <-r.client.ctx.Done(): + return + case <-ticker.C: + req.EndHeight = r.client.getMainchainHeight() - r.requiredConfs + 1 + if req.EndHeight < req.StartHeight { + continue + } + txs, err := r.client.getRgbxPendingTxs(req) + txNum := len(txs.GetPendingList()) + if txNum == 0 { + log.Debug("pullPendingTx", "err", err, "req", req, "txNum", txNum) + continue + } + + log.Debug("pullPendingTx", "txNum", txNum, "req", req) + for _, tx := range txs.GetPendingList() { + + txHash := hex.EncodeToString(tx.TxHash) + if tx.Confirmed { + log.Debug("tx already confirmed", "txHash", txHash) + continue + } + r.pendingCache.addTx(txHash, tx) + if tx.GetActionType() == rtypes.TyWithDrawAsset { + r.client.withdrawReqChan <- tx + continue + } + + hash, err := chainhash.NewHashFromStr(tx.Utxo.Hash) + if err != nil { + log.Error("pullPendingTx", "txHash", txHash, "err", err) + r.pendingCache.removeTx(txHash) + continue + } + r.rescanChan <- &utxoRescanInfo{ + pendingTxHash: txHash, + out: wire.OutPoint{ + Hash: *hash, + Index: tx.Utxo.Index, + }, + outPointStr: tx.Utxo.ToString(), + pkScript: tx.Utxo.PkScript, + startTime: tx.Timestamp, + startHeight: 0, + } + } + lastConfirmedTx := txs.GetPendingList()[txNum-1] + req.StartHeight = lastConfirmedTx.TxBlockHeight + req.StartIndex = lastConfirmedTx.TxIndex + } + } +} + +func (r *rgbx) rescanUtxo(info *utxoRescanInfo) (success bool) { + bestBlk := r.client.getBestBlock() + + // get start height from startTime + for height := bestBlk.Height; info.startHeight == 0 && height > 0; height-- { + + header, err := r.client.neutrinoCS.BlockHeaders.FetchHeaderByHeight(uint32(height)) + if err != nil { + log.Error("rescanUtxo", "pendHash", info.pendingTxHash, + "height", height, "FetchHeaderByHeight err", err) + return false + } + + if header.Timestamp.Unix() < info.startTime { + info.startHeight = height + 1 + } + } + if info.startHeight > bestBlk.Height || info.confirmedHeight > bestBlk.Height { + return false + } + + log.Debug("rescanUtxo", "startHeight", info.startHeight, + "pendHash", info.pendingTxHash, "utxo", info.outPointStr) + + spendReport, err := r.client.neutrinoCS.GetUtxo( + neutrino.WatchInputs(neutrino.InputWithScript{ + OutPoint: info.out, + PkScript: info.pkScript, + }), + neutrino.StartBlock(&headerfs.BlockStamp{Height: info.startHeight})) + if err != nil { + log.Error("rescanUtxo", "pendingTxHash", info.pendingTxHash, + "utxo", info.outPointStr, "err", err) + return false + } + + if spendReport == nil || spendReport.SpendingTx == nil { + info.startHeight = bestBlk.Height + 1 + return false + } + + // make sure enough confirmations + if spendReport.SpendingTxHeight+r.client.cfg.BlockConfirmations > uint32(bestBlk.Height) { + info.confirmedHeight = int32(spendReport.SpendingTxHeight + r.client.cfg.BlockConfirmations) + log.Debug("rescanUtxo confirmations", "bestHeight", bestBlk.Height, + "confirmedHeight", info.confirmedHeight, "utxo", info.outPointStr) + return false + } + + spendInfo := &utxoSpendInfo{ + spendingTxHash: spendReport.SpendingTx.TxHash().String(), + pendingTxHash: info.pendingTxHash, + spendingTx: spendReport.SpendingTx, + spendingHeight: spendReport.SpendingTxHeight, + spendingInputIndex: spendReport.SpendingInputIndex, + } + + r.commitChan <- spendInfo + log.Debug("rescanUtxo", "pendingHash", spendInfo.pendingTxHash, + "spendingHash", spendInfo.spendingTxHash, "utxo", info.outPointStr) + return true +} + +func (r *rgbx) handleRescanUtxo() { + rescanArr := make([]*utxoRescanInfo, 0, 16) + interval := r.client.cfg.BtcBlockInterval/2 + 1 + ticker := time.NewTicker(time.Second * time.Duration(interval)) + for { + select { + + case <-r.client.ctx.Done(): + return + + case info := <-r.rescanChan: + + if !r.rescanUtxo(info) && !r.mayTimeout(info) { + rescanArr = append(rescanArr, info) + } + + case <-ticker.C: + tempArr := rescanArr + rescanArr = rescanArr[:0] + for _, info := range tempArr { + if !r.rescanUtxo(info) && !r.mayTimeout(info) { + rescanArr = append(rescanArr, info) + } + } + } + } +} + +func (r *rgbx) mayTimeout(info *utxoRescanInfo) bool { + if r.client.cfg.MaxUtxoRescanTime == 0 || + types.Now().Unix() < info.startTime+r.client.cfg.MaxUtxoRescanTime { + return false + } + + spendInfo := &utxoSpendInfo{ + pendingTxHash: info.pendingTxHash, + timeout: true, + } + log.Debug("mayTimeout", "startTime", info.startTime, "pendingHash", info.pendingTxHash, + "utxo", info.outPointStr) + r.commitChan <- spendInfo + return true +} + +func (r *rgbx) createConfirmPayload(info *utxoSpendInfo, pendTx *rtypes.PendingTx) (*rtypes.ConfirmTx, error) { + + minPendingHeight := r.pendingCache.getMinPendingHeight() + confirm := &rtypes.ConfirmTx{ + ActionType: pendTx.ActionType, + ConfirmedBlockHeight: minPendingHeight - 1, + TxBlockHeight: pendTx.TxBlockHeight, + TxIndex: pendTx.TxIndex, + TxHash: pendTx.TxHash, + } + + if info.timeout { + confirm.Timeout = true + return confirm, nil + } + + proof := &rtypes.UtxoSpendingProof{ + SpendingInputIdx: info.spendingInputIndex, + OpRetOutputIdx: -1, + } + for idx, out := range info.spendingTx.TxOut { + if len(out.PkScript) > 0 && out.PkScript[0] == txscript.OP_RETURN { + proof.OpRetOutputIdx = int32(idx) + proof.OpRetOutputPkScript = out.PkScript + } + } + buf := bytes.NewBuffer(make([]byte, 0, info.spendingTx.SerializeSizeStripped())) + err := info.spendingTx.SerializeNoWitness(buf) + if err != nil { + log.Error("createConfirmPayload", "pendingTxHash", info.pendingTxHash, "serialize spending tx err", err) + return nil, err + } + proof.SpendingTx = buf.Bytes() + confirm.UtxoProof = proof + + return confirm, nil +} + +func (r *rgbx) commitPendingTx(request *confrimRequest) error { + + confirmHash := request.spendingInfo.pendingTxHash + confirm, err := r.createConfirmPayload(request.spendingInfo, request.pendTx) + if err != nil { + log.Error("commitPendingTx createConfirmPayload", "confirmHash", confirmHash, "err", err) + return err + } + txHash, err := r.client.submitMainchainTx(rtypes.RgbxX, rtypes.NameConfirmAction, confirm) + if err != nil && !strings.Contains(err.Error(), "already confirmed") { + log.Error("commitPendingTx submitMainchainTx", "txHash", txHash, "confirmHash", confirmHash, "err", err) + return err + } + + r.pendingCache.removeTx(confirmHash) + log.Debug("commitPendingTx success", "txHash", txHash, "confirmHash", confirmHash) + return nil +} + +type confrimRequest struct { + spendingInfo *utxoSpendInfo + pendTx *rtypes.PendingTx +} + +func (r *rgbx) handleCommitPendingTx() { + ticker := time.NewTicker(time.Second * 10) + retryList := make([]*confrimRequest, 0, 8) + for { + select { + + case <-r.client.ctx.Done(): + return + + case info := <-r.commitChan: + + pendTx := r.pendingCache.getTx(info.pendingTxHash) + if pendTx == nil { + log.Error("handleCommitPendingTx pendingTx not found", "pendingTxHash", info.pendingTxHash) + continue + } + request := &confrimRequest{spendingInfo: info, pendTx: pendTx} + if err := r.commitPendingTx(request); err != nil { + log.Error("handleCommitPendingTx commitPendingTx", "confirmHash", info.pendingTxHash, "err", err) + retryList = append(retryList, request) + } + + case <-ticker.C: + tempRequests := retryList + retryList = retryList[:0] + for _, request := range tempRequests { + if err := r.commitPendingTx(request); err != nil { + log.Error("handleCommitPendingTx retry", "confirmHash", request.spendingInfo.pendingTxHash, "commitPendingTx err", err) + retryList = append(retryList, request) + } + } + } + } +} diff --git a/plugin/dapp/lightclient/rpc/lightclient/neutrino/rgbx_crosschain_test.go b/plugin/dapp/lightclient/rpc/lightclient/neutrino/rgbx_crosschain_test.go new file mode 100644 index 0000000000..8e1a14c4ab --- /dev/null +++ b/plugin/dapp/lightclient/rpc/lightclient/neutrino/rgbx_crosschain_test.go @@ -0,0 +1,131 @@ +package neutrino + +import ( + "bytes" + "math" + "testing" + + lighttypes "github.com/33cn/plugin/plugin/dapp/lightclient/lighttypes" + rtypes "github.com/33cn/plugin/plugin/dapp/rgbx/types" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/stretchr/testify/require" +) + +func Test_encodeOutPoint_decodeOutPoint_roundTrip(t *testing.T) { + hash, err := chainhash.NewHashFromStr("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f") + require.NoError(t, err) + op := wire.OutPoint{Hash: *hash, Index: 1} + encoded := encodeOutPoint(&op) + decoded, err := decodeOutPoint(encoded) + require.NoError(t, err) + require.Equal(t, op.String(), decoded.String()) +} + +func Test_pending2WithdrawRequest(t *testing.T) { + chain33Hash := []byte{0xab, 0xcd} + pending := &rtypes.PendingTx{ + TxHash: chain33Hash, + Amount: 100_000, + FeeRate: 10, + TargetAddress: "bcrt1qexample", + ActionType: rtypes.TyWithDrawAsset, + } + req := pending2WithdrawRequest(pending) + require.Equal(t, pending.GetTxHash(), req.chain33WithDrawHash) + require.Equal(t, int64(100_000), int64(req.amount)) + require.Equal(t, int64(10), int64(req.feeRate)) + require.Equal(t, "bcrt1qexample", req.toAddress) +} + +func Test_rgbx_createConfirmPayload_timeout(t *testing.T) { + r := newRGBX() + r.pendingCache.addTx("deadbeef", &rtypes.PendingTx{TxBlockHeight: 100}) + pendTx := &rtypes.PendingTx{ + ActionType: rtypes.TyTransferAction, + TxBlockHeight: 50, + TxIndex: 2, + TxHash: []byte{1, 2, 3, 4}, + } + info := &utxoSpendInfo{pendingTxHash: "deadbeef", timeout: true} + confirm, err := r.createConfirmPayload(info, pendTx) + require.NoError(t, err) + require.True(t, confirm.Timeout) + require.Equal(t, pendTx.ActionType, confirm.ActionType) + require.Equal(t, pendTx.TxBlockHeight, confirm.TxBlockHeight) + require.Equal(t, pendTx.TxIndex, confirm.TxIndex) + require.Equal(t, int64(99), confirm.ConfirmedBlockHeight) // min height 100 - 1 + require.Nil(t, confirm.UtxoProof) +} + +func Test_rgbx_createConfirmPayload_withSpendingTxAndOpReturn(t *testing.T) { + r := newRGBX() + r.pendingCache.addTx("abc", &rtypes.PendingTx{TxBlockHeight: 10}) + + spendTx := wire.NewMsgTx(wire.TxVersion) + spendTx.AddTxIn(wire.NewTxIn(&wire.OutPoint{}, nil, nil)) + opRet, err := txscript.NullDataScript([]byte("rgbx:test")) + require.NoError(t, err) + spendTx.AddTxOut(wire.NewTxOut(0, opRet)) + spendTx.AddTxOut(wire.NewTxOut(1000, []byte{txscript.OP_0, 0x14})) + + info := &utxoSpendInfo{ + pendingTxHash: "abc", + spendingInputIndex: 0, + spendingTx: spendTx, + } + pendTx := &rtypes.PendingTx{ + ActionType: rtypes.TyMintAction, + TxBlockHeight: 5, + TxIndex: 1, + TxHash: []byte{0x11}, + } + confirm, err := r.createConfirmPayload(info, pendTx) + require.NoError(t, err) + require.False(t, confirm.Timeout) + require.NotNil(t, confirm.UtxoProof) + require.Equal(t, uint32(0), confirm.UtxoProof.SpendingInputIdx) + require.GreaterOrEqual(t, confirm.UtxoProof.OpRetOutputIdx, int32(0)) + require.Equal(t, byte(txscript.OP_RETURN), confirm.UtxoProof.OpRetOutputPkScript[0]) + + var back wire.MsgTx + require.NoError(t, back.DeserializeNoWitness(bytes.NewReader(confirm.UtxoProof.SpendingTx))) + require.Equal(t, spendTx.TxHash().String(), back.TxHash().String()) +} + +func Test_estimateBtcFee(t *testing.T) { + tx := wire.NewMsgTx(wire.TxVersion) + tx.AddTxIn(wire.NewTxIn(&wire.OutPoint{}, nil, nil)) + tx.AddTxOut(wire.NewTxOut(1000, []byte{txscript.OP_0, 0x14})) + fee := estimateBtcFee(tx, 10) + require.Greater(t, int64(fee), int64(0)) +} + +func Test_tssService_parseTxFromNotify(t *testing.T) { + ts := &tssService{} + _, _, err := ts.parseTxFromNotify(nil) + require.Error(t, err) + + tx := wire.NewMsgTx(wire.TxVersion) + tx.AddTxIn(wire.NewTxIn(&wire.OutPoint{}, nil, nil)) + buf := bytes.NewBuffer(nil) + require.NoError(t, tx.SerializeNoWitness(buf)) + notify := &lighttypes.TssSignNotify{ + BtcTxData: buf.Bytes(), + InputAmounts: []int64{1000}, + } + got, amounts, err := ts.parseTxFromNotify(notify) + require.NoError(t, err) + require.Len(t, amounts, 1) + require.Equal(t, tx.TxHash().String(), got.TxHash().String()) + + notify.InputAmounts = nil + _, _, err = ts.parseTxFromNotify(notify) + require.Error(t, err) +} + +func Test_pendingTxCache_getMinPendingHeight_empty(t *testing.T) { + c := newPendingTxCache(8) + require.Equal(t, int64(math.MaxInt64), c.getMinPendingHeight()) +} diff --git a/plugin/dapp/lightclient/rpc/lightclient/neutrino/rgbx_example_test.go b/plugin/dapp/lightclient/rpc/lightclient/neutrino/rgbx_example_test.go new file mode 100644 index 0000000000..1eadfb55f8 --- /dev/null +++ b/plugin/dapp/lightclient/rpc/lightclient/neutrino/rgbx_example_test.go @@ -0,0 +1,115 @@ +package neutrino + +import ( + "bytes" + "encoding/hex" + "fmt" + "testing" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/hdkeychain" + "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/require" +) + +var ( + // bcrt1qp4v07g5u0s0x6a2llrz3lxr8z7arz7vt5wcjpp + priv1 = "4964acfcd8f48deecda252e34a6d6cde93beceebbe7ec3391a3006edd3314fd1" + + btcFee = int64(0.01 * 1e8) + totalAmount = int64(49.99 * 1e8) +) + +func newTxSigHashes(tx *wire.MsgTx, pkScript []byte, amount int64) *txscript.TxSigHashes { + + prevOutFetcher := txscript.NewCannedPrevOutputFetcher(pkScript, amount) + return txscript.NewTxSigHashes(tx, prevOutFetcher) +} + +func Test_createRgbxTx(t *testing.T) { + + if testing.Short() { + t.Skip("skipping test in short mode") + } + privByte, err := hex.DecodeString(priv1) + require.NoError(t, err) + priv1, pub1 := btcec.PrivKeyFromBytes(privByte) + + hash, err := chainhash.NewHashFromStr("b4c0e95b0a43b7d6dc3b39bd9ef5e7b0304e5fbd8ea4670136a9c169a3615535") + require.NoError(t, err) + + txIn := &wire.TxIn{ + PreviousOutPoint: wire.OutPoint{ + Hash: *hash, + Index: 1, + }, + Sequence: 0xffffffff, + } + + tx := wire.NewMsgTx(wire.TxVersion) + tx.AddTxIn(txIn) + chain33Hash := "abd8bffa6c4a4018c1dfe062c943c6fbb2df1ecf3cae970ebfede4195a6ede0b" + chain33HashData, err := hex.DecodeString(chain33Hash) + require.NoError(t, err) + opRet, err := txscript.NullDataScript(chain33HashData) + require.NoError(t, err) + tx.AddTxOut(wire.NewTxOut(0, opRet)) + p2pkh, err := btcutil.NewAddressWitnessPubKeyHash(btcutil.Hash160(pub1.SerializeCompressed()), &chaincfg.RegressionNetParams) + require.NoError(t, err) + p2wpkhScript, err := txscript.PayToAddrScript(p2pkh) + require.NoError(t, err) + tx.AddTxOut(wire.NewTxOut(totalAmount-btcFee, p2wpkhScript)) + + hashCache := newTxSigHashes(tx, p2wpkhScript, totalAmount) + witness, err := txscript.WitnessSignature(tx, hashCache, 0, + totalAmount, p2wpkhScript, txscript.SigHashAll, priv1, true) + require.NoError(t, err) + txIn.Witness = witness + buf := bytes.NewBuffer(make([]byte, 0, tx.SerializeSize())) + require.NoError(t, tx.Serialize(buf)) + fmt.Println(hex.EncodeToString(buf.Bytes())) +} + +func Test_btcExtendKey(t *testing.T) { + + if testing.Short() { + t.Skip("skipping test in short mode") + } + //desc := "wpkh(tprv8ZgxMBicQKsPdWEKjojF9ZKgvZqo9LnH2gBctcW3rByRkP4UbzJdJJXbhagRmjAtYFPB2ZpqAT8iBTxVXgb24TUnjWMbLFeQpfDzDrAtPWB/84h/1h/0h/0/*)#2rz2j3d5" + masterPriv := "tprv8ZgxMBicQKsPdWEKjojF9ZKgvZqo9LnH2gBctcW3rByRkP4UbzJdJJXbhagRmjAtYFPB2ZpqAT8iBTxVXgb24TUnjWMbLFeQpfDzDrAtPWB" + + masterKey, err := hdkeychain.NewKeyFromString(masterPriv) + require.NoError(t, err) + ///84h/1h/0h/0/* + masterKey, _ = masterKey.Derive(hdkeychain.HardenedKeyStart + 84) + masterKey, _ = masterKey.Derive(hdkeychain.HardenedKeyStart + 1) + masterKey, _ = masterKey.Derive(hdkeychain.HardenedKeyStart + 0) + masterKey, _ = masterKey.Derive(0) + + key, err := masterKey.Derive(1) + require.NoError(t, err) + + priv, err := key.ECPrivKey() + + require.NoError(t, err) + fmt.Println(hex.EncodeToString(priv.Serialize())) + witnessProg := btcutil.Hash160(priv.PubKey().SerializeCompressed()) + addr, err := btcutil.NewAddressWitnessPubKeyHash(witnessProg, &chaincfg.RegressionNetParams) + require.NoError(t, err) + script, err := txscript.PayToAddrScript(addr) + require.NoError(t, err) + ty, _, _, err := txscript.ExtractPkScriptAddrs(script, &chaincfg.RegressionNetParams) + require.NoError(t, err) + require.Equal(t, txscript.WitnessV0PubKeyHashTy, ty) + fmt.Println("script:", hex.EncodeToString(script)) + address := addr.String() + fmt.Println(address) + + addr1, err := btcutil.DecodeAddress(address, &chaincfg.RegressionNetParams) + require.NoError(t, err) + require.Equal(t, address, addr1.EncodeAddress()) +} diff --git a/plugin/dapp/lightclient/rpc/lightclient/neutrino/rpc.go b/plugin/dapp/lightclient/rpc/lightclient/neutrino/rpc.go new file mode 100644 index 0000000000..20ab10cdbf --- /dev/null +++ b/plugin/dapp/lightclient/rpc/lightclient/neutrino/rpc.go @@ -0,0 +1,212 @@ +package neutrino + +import ( + "encoding/hex" + "errors" + "fmt" + + "github.com/33cn/chain33/system/crypto/secp256k1" + "github.com/33cn/chain33/types" + rtypes "github.com/33cn/plugin/plugin/dapp/rgbx/types" +) + +func (n *neutrinoClient) getCrossChainInfo() *rtypes.CrossChainInfo { + + req := &types.ReqString{Data: "btc"} + reply, err := n.mainChainGrpc.QueryChain(n.ctx, &types.ChainExecutor{ + Driver: rtypes.RgbxX, + FuncName: "GetCrossChainInfo", + Param: types.Encode(req), + }) + + if err != nil || !reply.GetIsOk() { + log.Error("getCrossChainInfo", "msg", string(reply.GetMsg()), "query err", err) + return nil + } + + msg := &rtypes.CrossChainInfo{} + + err = types.Decode(reply.GetMsg(), msg) + if err != nil { + log.Error("getValidatorPubKeys", "decode err", err) + return nil + } + + return msg +} + +func (n *neutrinoClient) createTx(exec, action string, payload []byte) (*types.Transaction, error) { + + req := &types.CreateTxIn{ + Execer: []byte(exec), + Payload: payload, + ActionName: action, + } + reply, err := n.mainChainGrpc.CreateTransaction(n.ctx, req) + if err != nil { + log.Error("createTx", "exec", exec, "action", action, "err", err) + return nil, err + } + tx := &types.Transaction{} + err = types.Decode(reply.GetData(), tx) + return tx, err +} + +func (n *neutrinoClient) getProperFeeRate() int64 { + + reply, err := n.mainChainGrpc.GetProperFee(n.ctx, &types.ReqProperFee{}) + if err != nil { + log.Error("getProperFeeRate", "err", err) + } else { + n.chain33FeeRate = reply.GetProperFee() + } + + return n.chain33FeeRate +} + +func (n *neutrinoClient) sendTx2MainChain(tx *types.Transaction) error { + + reply, err := n.mainChainGrpc.SendTransaction(n.ctx, tx) + if err == nil && !reply.GetIsOk() { + err = errors.New(string(reply.GetMsg())) + } + return err +} + +func (n *neutrinoClient) getRgbxConfirmedHeight() *types.Int64 { + reply, err := n.mainChainGrpc.QueryChain(n.ctx, &types.ChainExecutor{ + Driver: rtypes.RgbxX, + FuncName: "GetConfirmedHeight", + }) + if err != nil { + log.Error("getRgbxConfirmedHeight", "query err", err) + return nil + } + + data := &types.Int64{} + err = types.Decode(reply.GetMsg(), data) + if err != nil { + log.Error("getRgbxConfirmedHeight", "decode err", err) + return nil + } + return data +} + +func (n *neutrinoClient) getRgbxPendingTxs(req *rtypes.ReqListPendingTx) (*rtypes.PendingTxs, error) { + reply, err := n.mainChainGrpc.QueryChain(n.ctx, &types.ChainExecutor{ + Driver: rtypes.RgbxX, + FuncName: "ListPendingTx", + Param: types.Encode(req), + }) + if err != nil { + log.Error("getRgbxPendingTxs", "query err", err, "req", req.String()) + return nil, err + } + + data := &rtypes.PendingTxs{} + err = types.Decode(reply.GetMsg(), data) + if err != nil { + log.Error("getRgbxPendingTxs", "decode err", err) + return nil, err + } + return data, nil +} + +func (n *neutrinoClient) getTxDetail(txHash []byte) (*types.TransactionDetail, error) { + return n.mainChainGrpc.QueryTransaction(n.ctx, &types.ReqHash{Hash: txHash}) +} + +func (n *neutrinoClient) getRgbxWithdrawAsset(txHash []byte) (*rtypes.WithdrawAsset, error) { + if len(txHash) == 0 { + return nil, types.ErrInvalidParam + } + reply, err := n.getTxDetail(txHash) + if err != nil { + log.Error("getRgbxWithdrawAsset", "txHash", fmt.Sprintf("%x", txHash), "get txDetail err", err) + return nil, err + } + + action := &rtypes.RgbxAction{} + if err := types.Decode(reply.GetTx().Payload, action); err != nil { + log.Error("getRgbxWithdrawAsset decode action", "txHash", fmt.Sprintf("%x", txHash), "err", err) + return nil, err + } + if action.Ty != rtypes.TyWithDrawAsset { + log.Error("getRgbxWithdrawAsset invalid action", "txHash", fmt.Sprintf("%x", txHash), "action", action.String()) + return nil, fmt.Errorf("withdraw action not found") + } + + return action.GetWithdraw(), nil +} + +func (n *neutrinoClient) getRgbxPendingTx(height, index int64) (*rtypes.PendingTx, error) { + req := &rtypes.ReqGetPendingTx{ + Height: height, + Index: index, + } + reply, err := n.mainChainGrpc.QueryChain(n.ctx, &types.ChainExecutor{ + Driver: rtypes.RgbxX, + FuncName: "GetPendingTx", + Param: types.Encode(req), + }) + if err != nil { + log.Error("getRgbxPendingTx", "height", height, "index", index, "query err", err) + return nil, err + } + data := &rtypes.PendingTx{} + if err = types.Decode(reply.GetMsg(), data); err != nil { + log.Error("getRgbxPendingTx", "height", height, "index", index, "decode err", err) + return nil, err + } + return data, nil +} + +func (n *neutrinoClient) getRgbxPendingTxByHash(txHash []byte) (*rtypes.PendingTx, error) { + if len(txHash) == 0 { + return nil, types.ErrInvalidParam + } + detail, err := n.getTxDetail(txHash) + if err != nil { + log.Error("getRgbxPendingTxByHash", "txHash", fmt.Sprintf("%x", txHash), "get txDetail err", err) + return nil, err + } + return n.getRgbxPendingTx(detail.GetHeight(), detail.GetIndex()) +} + +func (n *neutrinoClient) submitMainchainTx(exec string, action string, payload types.Message) (string, error) { + tx, err := n.createTx(exec, action, types.Encode(payload)) + if err != nil { + log.Error("submitMainchainTx", "createTx err", err) + return "", err + } + txHash := hex.EncodeToString(tx.Hash()) + tx.Fee, err = tx.GetRealFee(n.getProperFeeRate()) + if err != nil { + log.Error("submitMainchainTx", "txHash", txHash, "GetRealFee err", err) + return "", err + } + tx.Sign(types.EncodeSignID(secp256k1.ID, n.commitAddressType), n.getCommitKey()) + err = n.sendTx2MainChain(tx) + if err != nil { + log.Error("submitMainchainTx", "txHash", txHash, "sendTx2MainChain err", err) + return "", err + } + return txHash, nil +} + +func (n *neutrinoClient) submitMainchainTxUntilSuccess(exec string, action string, payload types.Message) { + + n.waitUntilDone("submitMainchainTxUntilSuccess", func() bool { + _, err := n.submitMainchainTx(exec, action, payload) + return err == nil + }, 0) +} + +func (n *neutrinoClient) getMainchainHeight() int64 { + reply, err := n.mainChainGrpc.GetLastHeader(n.ctx, &types.ReqNil{}) + if err != nil { + log.Error("getMainchainHeight", "query err", err) + return 0 + } + return reply.GetHeight() +} diff --git a/plugin/dapp/lightclient/rpc/lightclient/neutrino/rpc_test.go b/plugin/dapp/lightclient/rpc/lightclient/neutrino/rpc_test.go new file mode 100644 index 0000000000..9fd3f2662e --- /dev/null +++ b/plugin/dapp/lightclient/rpc/lightclient/neutrino/rpc_test.go @@ -0,0 +1,34 @@ +// Copyright Fuzamei Corp. 2018 All Rights Reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package neutrino + +import ( + "testing" + + "github.com/33cn/chain33/types" + "github.com/stretchr/testify/assert" +) + +// TestGetRgbxWithdrawAssetParamValidation tests parameter validation +func TestGetRgbxWithdrawAssetParamValidation(t *testing.T) { + n := &neutrinoClient{} + + // Test with empty hash + result, err := n.getRgbxWithdrawAsset([]byte{}) + assert.Error(t, err) + assert.Equal(t, types.ErrInvalidParam, err) + assert.Nil(t, result) +} + +// TestGetRgbxPendingTxByHashParamValidation tests parameter validation +func TestGetRgbxPendingTxByHashParamValidation(t *testing.T) { + n := &neutrinoClient{} + + // Test with empty hash + result, err := n.getRgbxPendingTxByHash([]byte{}) + assert.Error(t, err) + assert.Equal(t, types.ErrInvalidParam, err) + assert.Nil(t, result) +} diff --git a/plugin/dapp/lightclient/rpc/lightclient/neutrino/tss.go b/plugin/dapp/lightclient/rpc/lightclient/neutrino/tss.go new file mode 100644 index 0000000000..0b6ab73197 --- /dev/null +++ b/plugin/dapp/lightclient/rpc/lightclient/neutrino/tss.go @@ -0,0 +1,639 @@ +package neutrino + +import ( + "bytes" + "encoding/hex" + "fmt" + "runtime" + "sync/atomic" + "time" + + "github.com/33cn/chain33/system/crypto/tss" + "github.com/33cn/chain33/system/crypto/tss/gg18" + "github.com/33cn/chain33/types" + "github.com/33cn/plugin/plugin/dapp/lightclient/lighttypes" + rtypes "github.com/33cn/plugin/plugin/dapp/rgbx/types" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcwallet/walletdb" +) + +const ( + moduleName = "dapp-lightclient-neutrino" + // TSS pubsub topic - notification only + tssSignNotifyTopic = "rgbx/tssSignNotify/1.0" + + // Database bucket and keys + tssBucketName = "rgbx-tss" + dkgResultKey = "dkg-result" + dkgSessionName = "rgbx-btc-dkg" +) + +type signTask struct { + idx int + sigHash []byte + sessionName string + signers []string + result chan *signResult +} + +type signResult struct { + sig []byte + err error +} + +type tssService struct { + client *neutrinoClient + cfg tssConfig + + // TSS related + dkgResult *tss.DKGResult + tssPublicKey *btcec.PublicKey + tssAddress btcutil.Address + pkScript []byte + dkgCompleted atomic.Bool + + // P2P channels + subChan chan *types.TopicData + selfPeerId string + signTaskCh chan *signTask +} + +func newTssService(n *neutrinoClient) *tssService { + t := &tssService{ + client: n, + subChan: make(chan *types.TopicData, 100), + signTaskCh: make(chan *signTask, 1024), + } + t.cfg = n.cfg.Tss + return t +} + +func (t *tssService) start() { + log.Debug("tssService start") + + // Subscribe to TSS sign notification topic + go t.subTopic(tssSignNotifyTopic) + + // Handle subscribed messages + go t.handleSubMsg() + + // Dedicated signing worker. + for i := 0; i < runtime.NumCPU(); i++ { + go t.handleSignTask() + } + + // Ensure DKG is completed + go t.init() +} + +func (t *tssService) handleSignTask() { + for { + select { + case <-t.client.ctx.Done(): + return + case task := <-t.signTaskCh: + task.result <- t.signMsg(task.sigHash, task.sessionName, task.signers) + } + } +} + +func (t *tssService) init() { + // Wait for cross chain info to be available + info := t.client.getCrossChainInfo() + for info == nil { + time.Sleep(3 * time.Second) + log.Debug("ensureDKG getCrossChainInfo wait 3 seconds...") + info = t.client.getCrossChainInfo() + } + + if info.GetTssAddress() != "" { + log.Debug("ensureDKG already exist, loading from database") + err := t.loadDKGFromDB() + if err != nil { + panic("ensureDKG loadDKGFromDB error: " + err.Error()) + } + t.dkgCompleted.Store(true) + return + } + log.Info("init tssService starting new DKG process") + + // Perform DKG process with retry + var dkgResult *tss.DKGResult + var err error + for { + dkgResult, err = gg18.ProcessDKG(t.cfg.Peers, t.cfg.Threshold, t.cfg.Rank, dkgSessionName) + if err == nil { + break + } + log.Error("init tssService ProcessDKG retry", "err", err) + time.Sleep(time.Minute) + } + + t.dkgResult = dkgResult + + // Extract public key from DKG result (PubX, PubY coordinates) + pubkey, err := tss.ParseBtcecPublicKey(dkgResult) + if err != nil { + log.Error("init tssService ParseBtcecPublicKey error", "err", err) + return + } + t.tssPublicKey = pubkey + + // Generate Bitcoin address from public key + err = t.generateTssAddress() + if err != nil { + log.Error("init tssService generateTssAddress error", "err", err) + return + } + + // Save DKG result to database with retry + t.saveDKGToDB() + + // Commit DKG result to main chain with retry + commitDKG := &rtypes.CommitDKG{ + AssetSymbol: rtypes.BTCSymbol, + DkgAddress: t.tssAddress.EncodeAddress(), + PkScript: t.pkScript, + } + t.client.submitMainchainTxUntilSuccess(rtypes.RgbxX, rtypes.NameCommitDKGAction, commitDKG) + t.dkgCompleted.Store(true) + for { + peers, err := tss.FetchConnectedPeers(t.client.qclient, time.Second*3) + if err == nil && len(peers) > 0 && peers[len(peers)-1].Self { + t.selfPeerId = peers[len(peers)-1].Name + break + } + log.Debug("init tssService waitForSelfPeerId FetchConnectedPeers retry", "err", err) + time.Sleep(time.Second * 3) + } + +} + +func (t *tssService) loadDKGFromDB() error { + var dkgData []byte + + err := walletdb.View(t.client.neutrinoCfg.Database, func(tx walletdb.ReadTx) error { + bucket := tx.ReadBucket([]byte(tssBucketName)) + if bucket == nil { + return walletdb.ErrBucketNotFound + } + + // Load DKG result + dkgData = bucket.Get([]byte(dkgResultKey)) + if dkgData == nil { + return types.ErrNotFound + } + + return nil + }) + if err != nil { + return err + } + + // Decode DKG result + var dkgResult tss.DKGResult + err = types.Decode(dkgData, &dkgResult) + if err != nil { + return err + } + t.dkgResult = &dkgResult + + // Extract public key from DKG result coordinates + pubKey, err := tss.ParseBtcecPublicKey(&dkgResult) + if err != nil { + log.Error("loadDKGFromDB ParseBtcecPublicKey error", "err", err) + return err + } + t.tssPublicKey = pubKey + + // Generate Bitcoin address + return t.generateTssAddress() +} + +// saveDKGToDB saves DKG result to database with retry until success +func (t *tssService) saveDKGToDB() { + // Encode DKG result + dkgData := types.Encode(t.dkgResult) + + for { + err := walletdb.Update(t.client.neutrinoCfg.Database, func(tx walletdb.ReadWriteTx) error { + bucket, err := tx.CreateTopLevelBucket([]byte(tssBucketName)) + if err != nil { + return err + } + + // Save DKG result + return bucket.Put([]byte(dkgResultKey), dkgData) + }) + + if err == nil { + log.Debug("saveDKGToDB success") + return + } + + log.Error("saveDKGToDB retry", "err", err) + time.Sleep(time.Second * 3) + } +} + +// generateBitcoinAddress generates Bitcoin address from TSS public key +func (t *tssService) generateTssAddress() error { + if t.tssPublicKey == nil { + return types.ErrInvalidParam + } + + // Generate Bitcoin address (P2WPKH format) + chainParams := &t.client.neutrinoCfg.ChainParams + pubKeyHash := btcutil.Hash160(t.tssPublicKey.SerializeCompressed()) + addr, err := btcutil.NewAddressWitnessPubKeyHash(pubKeyHash, chainParams) + if err != nil { + log.Error("generateTssAddress NewAddressWitnessPubKeyHash", "pubkey", hex.EncodeToString(t.tssPublicKey.SerializeCompressed()), "err", err) + return err + } + + t.tssAddress = addr + t.pkScript, err = txscript.PayToAddrScript(addr) + if err != nil { + panic("generateTssAddress payToAddrScript error , address: " + addr.String() + " err: " + err.Error()) + } + log.Info("generateTssAddress", "address", addr.String()) + + return nil +} + +func (t *tssService) waitForSufficientSigners() []string { + var signers []string + t.client.waitUntilDone("waitForSufficientSigners", func() bool { + signers = tss.GetValidPeerCombination(t.client.qclient, t.cfg.Threshold, t.dkgResult.Bks) + return len(signers) > 0 + }, time.Second*3) + return signers +} + +// processSignBtcTx processes a Bitcoin transaction using TSS protocol +// This is called by the main node to initiate TSS signing +func (t *tssService) processSignBtcTx(tx *wire.MsgTx, txType string, inputAmounts []int64, payload []byte) error { + + signers := t.waitForSufficientSigners() + buf := bytes.NewBuffer(make([]byte, 0, tx.SerializeSizeStripped())) + err := tx.SerializeNoWitness(buf) + if err != nil { + log.Error("processSignBtcTx SerializeNoWitness", "err", err) + return err + } + notify := &lighttypes.TssSignNotify{ + InputAmounts: inputAmounts, + TxType: txType, + Payload: payload, + BtcTxData: buf.Bytes(), + Signers: signers, + } + // Publish notification to all TSS nodes + t.pubMsg(tssSignNotifyTopic, types.Encode(notify)) + log.Debug("signMsg published notification", "txType", txType, "payload", hex.EncodeToString(payload), "signers", signers) + return t.signBtcTx(tx, inputAmounts, signers) +} + +func (t *tssService) signMsg(msg []byte, seesionName string, signers []string) *signResult { + result := &signResult{} + sigResult, err := gg18.ProcessSign(signers, msg, t.dkgResult, seesionName) + if err != nil { + log.Error("signMsg ProcessSign", "err", err) + result.err = err + return result + } + + signature, err := gg18.AliceToBtcecSignature(sigResult) + if err != nil { + log.Error("signMsg AliceToBtcecSignature", "err", err) + result.err = err + return result + } + signatureBytes := signature.Serialize() + log.Debug("signMsg success", "signature", hex.EncodeToString(signatureBytes)) + result.sig = signatureBytes + return result +} + +func (t *tssService) signBtcTx(tx *wire.MsgTx, inputAmounts []int64, signers []string) error { + if len(tx.TxIn) != len(inputAmounts) { + return fmt.Errorf("input count mismatch: tx=%d inputAmounts=%d", len(tx.TxIn), len(inputAmounts)) + } + prevOutFetcher := txscript.NewMultiPrevOutFetcher(make(map[wire.OutPoint]*wire.TxOut, len(tx.TxIn))) + for idx, txIn := range tx.TxIn { + prevOutFetcher.AddPrevOut(txIn.PreviousOutPoint, &wire.TxOut{ + Value: inputAmounts[idx], + PkScript: t.pkScript, + }) + } + + txSigHashes := txscript.NewTxSigHashes(tx, prevOutFetcher) + txHash := tx.TxID() + pubKeyBytes := t.tssPublicKey.SerializeCompressed() + sigTasks := make([]*signTask, 0, len(tx.TxIn)) + for idx := range tx.TxIn { + // 计算签名哈希(使用预计算的脚本) + sigHash, err := txscript.CalcWitnessSigHash(t.pkScript, txSigHashes, txscript.SigHashAll, tx, idx, inputAmounts[idx]) + if err != nil { + return fmt.Errorf("calc sig hash failed for input %d: %w", idx, err) + } + + sigTask := &signTask{ + idx: idx, + sigHash: sigHash, + sessionName: fmt.Sprintf("btctx-%s-%d", txHash, idx), + signers: signers, + result: make(chan *signResult, 1), + } + sigTasks = append(sigTasks, sigTask) + t.signTaskCh <- sigTask + } + + for _, sigTask := range sigTasks { + result := <-sigTask.result + if result.err != nil { + return fmt.Errorf("signMsg failed for input %d: %w", sigTask.idx, result.err) + } + sigWithHashType := append(result.sig, byte(txscript.SigHashAll)) + tx.TxIn[sigTask.idx].Witness = wire.TxWitness{sigWithHashType, pubKeyBytes} + log.Debug("signBtcTx applied signature to input", "idx", sigTask.idx) + } + + return nil +} + +func (t *tssService) parseTxFromNotify(notify *lighttypes.TssSignNotify) (*wire.MsgTx, []int64, error) { + if notify == nil { + return nil, nil, types.ErrInvalidParam + } + if len(notify.BtcTxData) == 0 { + return nil, nil, fmt.Errorf("empty BtcTxData") + } + if len(notify.InputAmounts) == 0 { + return nil, nil, fmt.Errorf("empty input amounts") + } + var tx wire.MsgTx + if err := tx.DeserializeNoWitness(bytes.NewReader(notify.BtcTxData)); err != nil { + return nil, nil, fmt.Errorf("deserialize tx failed: %w", err) + } + if len(tx.TxIn) != len(notify.InputAmounts) { + return nil, nil, fmt.Errorf("input count mismatch: tx=%d inputAmounts=%d", len(tx.TxIn), len(notify.InputAmounts)) + } + return &tx, notify.InputAmounts, nil +} + +func (t *tssService) validateWithdrawTx(tx *wire.MsgTx, inputAmounts []int64, req *withdrawRequest) error { + + btcAddr, err := btcutil.DecodeAddress(req.toAddress, &t.client.neutrinoCfg.ChainParams) + if err != nil { + log.Error("validateWithdrawTx decode address", "err", err, "address", req.toAddress, + "withdrawTxHash", hex.EncodeToString(req.chain33WithDrawHash)) + return fmt.Errorf("decode address failed") + } + btcAddrScript, err := txscript.PayToAddrScript(btcAddr) + if err != nil { + log.Error("validateWithdrawTx pay to addr script", "address", req.toAddress, + "withdrawTxHash", hex.EncodeToString(req.chain33WithDrawHash), "err", err) + return fmt.Errorf("pay to addr script failed") + } + var withdrawAmount, changeAmount int64 + for _, output := range tx.TxOut { + if len(output.PkScript) > 0 && output.PkScript[0] == txscript.OP_RETURN { + continue + } + if len(t.pkScript) > 0 && bytes.Equal(output.PkScript, t.pkScript) { + changeAmount += output.Value + continue + } + if !bytes.Equal(output.PkScript, btcAddrScript) { + log.Error("validateWithdrawTx unexpected output script", "address", req.toAddress, + "expected", hex.EncodeToString(btcAddrScript), "actual", hex.EncodeToString(output.PkScript), + "withdrawTxHash", hex.EncodeToString(req.chain33WithDrawHash)) + return fmt.Errorf("unexpected output script") + } + withdrawAmount += output.Value + } + + var totalInput int64 + for _, amount := range inputAmounts { + totalInput += amount + } + var totalOutput int64 + for _, out := range tx.TxOut { + totalOutput += out.Value + } + fee := totalInput - totalOutput + expectedFee := int64(estimateBtcFee(tx, btcutil.Amount(req.feeRate))) + // 控制手续费在合理范围内 + if fee > 2*expectedFee || fee < 0 { + log.Error("validateWithdrawSignNotify invalid fee", "fee", fee, "expected", expectedFee, + "withdrawTxHash", hex.EncodeToString(req.chain33WithDrawHash)) + return fmt.Errorf("invalid fee") + } + // 验证提现的关键是总支出不能超过提现金额,允许的最大磨损不能超过最小找零金额 + if totalInput-changeAmount > int64(req.amount)+minChangeAmount { + log.Error("validateWithdrawSignNotify withdraw overflowed", + "actualWithdraw", withdrawAmount, "changeAmount", changeAmount, + "totalInput", totalInput, "expectWithdraw", int64(req.amount), + "withdrawTxHash", hex.EncodeToString(req.chain33WithDrawHash)) + return fmt.Errorf("withdraw overflowed") + } + if withdrawAmount > int64(req.amount) || withdrawAmount < minChangeAmount { + log.Error("validateWithdrawSignNotify invalid withdraw amount", "actualWithdraw", withdrawAmount, + "expectWithdraw", int64(req.amount), "withdrawTxHash", hex.EncodeToString(req.chain33WithDrawHash)) + return fmt.Errorf("invalid withdraw amount") + } + return nil +} + +func (t *tssService) checkNonOfficialWithdrawSign(chain33WithDrawHash []byte) (*rtypes.PendingTx, error) { + + txHash := hex.EncodeToString(chain33WithDrawHash) + pendingTx, err := t.client.getRgbxPendingTxByHash(chain33WithDrawHash) + if err != nil { + log.Error("checkNonOfficialWithdrawSign getRgbxPendingTxByHash", "txHash", txHash, "err", err) + return nil, err + } + if pendingTx.GetConfirmed() { + return nil, fmt.Errorf("withdraw already confirmed") + } + return pendingTx, nil +} + +func (t *tssService) checkStickyInput(chain33WithDrawHash []byte, tx *wire.MsgTx) error { + if len(tx.TxIn) == 0 { + return fmt.Errorf("withdraw tx has no inputs") + } + stickyOutPoint := tx.TxIn[len(tx.TxIn)-1].PreviousOutPoint.String() + // 验证绑定的哈希是否一致 + expectedHash := t.client.getExpectedWithdrawHash(stickyOutPoint) + if len(expectedHash) > 0 && !bytes.Equal(expectedHash, chain33WithDrawHash) { + log.Error("checkStickyInput sticky input mismatch", "expected", hex.EncodeToString(expectedHash), + "actual", hex.EncodeToString(chain33WithDrawHash), "stickyOutPoint", stickyOutPoint) + return fmt.Errorf("invalid sticky input") + } + + // 如果本地已记录过成功签名的绑定utxo,后续请求必须一致 + expectUTXO := t.client.getWithdrawStickyUTXO(chain33WithDrawHash) + if expectUTXO != nil && expectUTXO.OutPoint.String() != stickyOutPoint { + log.Error("checkStickyInput sticky input changed", "expected", expectUTXO.OutPoint.String(), + "actual", stickyOutPoint, "chain33WithDrawHash", hex.EncodeToString(chain33WithDrawHash)) + return fmt.Errorf("invalid sticky input") + } + return nil +} + +// handleSignNotify handles incoming TSS sign notifications +// All nodes (including main node) receive this and participate in signing +func (t *tssService) handleSignNotify(msg []byte) { + + defer func() { + if r := recover(); r != nil { + log.Error("handleSignNotify panic", "err", r) + } + }() + if !t.dkgCompleted.Load() { + log.Error("handleSignNotify", "err", "DKG not completed") + return + } + + notify := &lighttypes.TssSignNotify{} + err := types.Decode(msg, notify) + if err != nil { + log.Error("handleSignNotify Decode", "err", err) + return + } + isSigner := false + for _, signer := range notify.Signers { + if signer == t.selfPeerId { + isSigner = true + break + } + } + if !isSigner { + log.Debug("handleSignNotify not signer", "signers", notify.Signers, "selfPeerId", t.selfPeerId, "type", notify.TxType) + return + } + + tx, inputAmounts, err := t.parseTxFromNotify(notify) + if err != nil { + log.Error("handleSignNotify parseTxFromNotify", "type", notify.TxType, "err", err) + return + } + + if notify.TxType == transactionTypeWithdraw { + chain33WithDrawHash := notify.Payload + if err = t.checkStickyInput(chain33WithDrawHash, tx); err != nil { + log.Error("handleSignNotify checkStickyInput", "err", err, + "withDrawHash", hex.EncodeToString(chain33WithDrawHash), "btcHash", tx.TxHash().String()) + return + } + pendingTx, err := t.checkNonOfficialWithdrawSign(chain33WithDrawHash) + if err != nil { + log.Error("handleSignNotify checkNonOfficialWithdrawSign", "type", notify.TxType, + "withDrawHash", hex.EncodeToString(chain33WithDrawHash), "btcHash", tx.TxHash().String(), "err", err) + return + } + req := pending2WithdrawRequest(pendingTx) + err = t.validateWithdrawTx(tx, inputAmounts, req) + if err != nil { + log.Error("handleSignNotify validateWithdrawTx", "err", err, + "withdrawHash", hex.EncodeToString(chain33WithDrawHash), "btcHash", tx.TxHash().String()) + return + } + } + + if err = t.signBtcTx(tx, inputAmounts, notify.Signers); err != nil { + log.Error("handleSignNotify signBtcTx", "type", notify.TxType, "err", err) + return + } + if notify.TxType == transactionTypeWithdraw { + stickyUTXO := &UTXO{ + OutPoint: tx.TxIn[len(tx.TxIn)-1].PreviousOutPoint, + } + if err = t.client.setWithdrawStickyUTXO(notify.Payload, stickyUTXO); err != nil { + log.Error("handleSignNotify setWithdrawStickyUTXO", "err", err, + "withdrawHash", hex.EncodeToString(notify.Payload), "btcHash", tx.TxHash().String()) + } + } + log.Debug("handleSignNotify success", "txType", notify.TxType, + "payload", hex.EncodeToString(notify.Payload), "btcHash", tx.TxHash().String()) +} + +// subTopic subscribes to a P2P topic +func (t *tssService) subTopic(topic string) { + data := &types.SubTopic{Topic: topic, Module: moduleName} + + for { + err := t.sendP2PMsg(types.EventSubTopic, data) + if err == nil { + log.Info("subTopic success", "topic", topic) + break + } + log.Debug("subTopic", "topic", topic, "err", err) + time.Sleep(time.Second) + } +} + +// pubMsg publishes a message to a P2P topic +func (t *tssService) pubMsg(topic string, msg []byte) { + data := &types.PublishTopicMsg{Topic: topic, Msg: msg} + tryCount := 0 + + for { + tryCount++ + err := t.sendP2PMsg(types.EventPubTopicMsg, data) + if err == nil || tryCount >= 3 { + break + } + log.Error("pubMsg", "topic", topic, "tryCount", tryCount, "err", err) + time.Sleep(time.Second) + } +} + +func (t *tssService) sendP2PMsg(ty int64, data interface{}) error { + msg := t.client.qclient.NewMessage("p2p", ty, data) + err := t.client.qclient.Send(msg, true) + if err != nil { + return err + } + + resp, err := t.client.qclient.WaitTimeout(msg, time.Second*5) + if err != nil { + return err + } + + reply, ok := resp.GetData().(*types.Reply) + if !ok { + return types.ErrTypeAsset + } + + if !reply.GetIsOk() { + return types.ErrInvalidParam + } + + return nil +} + +// handleSubMsg handles subscribed messages from P2P network +func (t *tssService) handleSubMsg() { + for { + select { + case <-t.client.ctx.Done(): + return + + case data := <-t.subChan: + if data.Topic == tssSignNotifyTopic { + t.handleSignNotify(data.GetData()) + } + } + } +} + +// isDKGCompleted checks if DKG is completed using atomic operation +func (t *tssService) isDKGCompleted() bool { + return t.dkgCompleted.Load() +} diff --git a/plugin/dapp/lightclient/rpc/lightclient/neutrino/utils.go b/plugin/dapp/lightclient/rpc/lightclient/neutrino/utils.go new file mode 100644 index 0000000000..2e9a56da84 --- /dev/null +++ b/plugin/dapp/lightclient/rpc/lightclient/neutrino/utils.go @@ -0,0 +1,127 @@ +package neutrino + +import ( + "fmt" + "os" + "time" + + "github.com/33cn/chain33/common" + "github.com/33cn/chain33/common/crypto" + "github.com/33cn/chain33/system/crypto/secp256k1" + "github.com/33cn/chain33/types" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/wire" +) + +func (n *neutrinoClient) getCommitKey() crypto.PrivKey { + n.commitKeyMu.RLock() + defer n.commitKeyMu.RUnlock() + return n.commitKey +} + +// initCommitKey 在后台 goroutine 中等待钱包解锁后导出 commit 私钥。 +// 先加写锁再启动 goroutine,确保 getCommitKey() 的调用者在 key 就绪之前阻塞, +// 从而保证 Start() 中后续依赖 commitKey 的流程(tss/rgbx)不会在 key 为空时执行。 +func (n *neutrinoClient) initCommitKey() { + n.initCommitKeyOnce.Do(func() { + n.commitKeyMu.Lock() + go func() { + defer n.commitKeyMu.Unlock() + ticker := time.NewTicker(time.Second * 3) + defer ticker.Stop() + for { + select { + case <-n.ctx.Done(): + return + case <-ticker.C: + + resp, err := n.chain33Api.ExecWalletFunc("wallet", "GetWalletStatus", &types.ReqNil{}) + if err != nil { + log.Error("getKeyFromWallet", "GetWalletStatus err", err) + continue + } + if !resp.(*types.WalletStatus).GetIsHasSeed() { + log.Info("getKeyFromWallet, wait wallet save seed...") + continue + } + + if resp.(*types.WalletStatus).GetIsWalletLock() { + log.Info("getKeyFromWallet, wait wallet unlock...") + continue + } + + resp, err = n.chain33Api.ExecWalletFunc("wallet", "DumpPrivkey", &types.ReqString{Data: n.commitAddr}) + if err != nil { + log.Error("getKeyFromWallet", "addr", n.commitAddr, "dump priv key err", err) + continue + } + _, key, err := getPrivKey(secp256k1.Name, resp.(*types.ReplyString).Data) + if err != nil { + log.Error("getKeyFromWallet", "addr", n.commitAddr, "getPrivKey err", err) + continue + } + n.commitKey = key + return + } + } + }() + }) +} + +func getPrivKey(cryptoName, privKey string) (crypto.Crypto, crypto.PrivKey, error) { + if privKey == "" { + return nil, nil, fmt.Errorf("getPrivKey: empty privKey") + } + driver, err := crypto.Load(cryptoName, -1) + if err != nil { + return nil, nil, fmt.Errorf("getPrivKey load crypto driver err: %w", err) + } + privByte, err := common.FromHex(privKey) + if err != nil { + return nil, nil, fmt.Errorf("getPrivKey decode hex key err: %w", err) + } + key, err := driver.PrivKeyFromBytes(privByte) + if err != nil { + return nil, nil, fmt.Errorf("getPrivKey priv key from bytes err: %w", err) + } + + return driver, key, nil +} + +func (n *neutrinoClient) waitUntilDone(_ string, done func() bool, interval time.Duration) { + if done() { + return + } + if interval <= 0 { + interval = time.Second * 3 + } + ticker := time.NewTicker(interval) + defer ticker.Stop() + for { + select { + + case <-ticker.C: + if done() { + return + } + case <-n.ctx.Done(): + return + } + } +} + +func fileExists(filePath string) (bool, error) { + _, err := os.Stat(filePath) + if err != nil { + if os.IsNotExist(err) { + return false, nil + } + return false, err + } + return true, nil +} + +func estimateBtcFee(tx *wire.MsgTx, feeRate btcutil.Amount) btcutil.Amount { + txSize := tx.SerializeSize() + len(tx.TxIn)*108 // 估算witness大小 + return btcutil.Amount(txSize) * feeRate +} diff --git a/plugin/dapp/lightclient/rpc/lightclient/neutrino/utils_test.go b/plugin/dapp/lightclient/rpc/lightclient/neutrino/utils_test.go new file mode 100644 index 0000000000..110d001d3b --- /dev/null +++ b/plugin/dapp/lightclient/rpc/lightclient/neutrino/utils_test.go @@ -0,0 +1,130 @@ +// Copyright Fuzamei Corp. 2018 All Rights Reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package neutrino + +import ( + "context" + "os" + "path/filepath" + "testing" + "time" + + "github.com/33cn/chain33/system/crypto/secp256k1" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/wire" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestFileExists(t *testing.T) { + tmpDir := t.TempDir() + + // Test 1: file does not exist + exists, err := fileExists(filepath.Join(tmpDir, "nonexistent.db")) + require.NoError(t, err) + assert.False(t, exists, "file should not exist") + + // Test 2: file exists + testFile := filepath.Join(tmpDir, "test.db") + err = os.WriteFile(testFile, []byte("test"), 0644) + require.NoError(t, err) + + exists, err = fileExists(testFile) + require.NoError(t, err) + assert.True(t, exists, "file should exist") + + // Test 3: directory exists (should return true) + exists, err = fileExists(tmpDir) + require.NoError(t, err) + assert.True(t, exists, "directory should exist") +} + +func TestGetPrivKey(t *testing.T) { + // Test with valid private key + privKeyHex := "0xcc38546e9e659d15e6b4893f0ab32a06d103931a8230b0bde71459d2b27d6944" + + cryptoDriver, privKey, err := getPrivKey(secp256k1.Name, privKeyHex) + require.NoError(t, err) + require.NotNil(t, cryptoDriver) + require.NotNil(t, privKey) + + // Test error cases (previously panics) + _, _, err = getPrivKey(secp256k1.Name, "") + assert.Error(t, err, "should return error with empty privKey") + + _, _, err = getPrivKey(secp256k1.Name, "invalid_hex") + assert.Error(t, err, "should return error with invalid hex") + + _, _, err = getPrivKey("invalid_crypto", privKeyHex) + assert.Error(t, err, "should return error with invalid crypto driver") +} + +func TestEstimateBtcFee(t *testing.T) { + // Create a simple tx with 1 input and 2 outputs + tx := wire.NewMsgTx(2) + tx.AddTxIn(&wire.TxIn{ + PreviousOutPoint: wire.OutPoint{Index: 0}, + }) + tx.AddTxOut(&wire.TxOut{Value: 1000}) + tx.AddTxOut(&wire.TxOut{Value: 2000}) + + feeRate := btcutil.Amount(10) // 10 sat/byte + fee := estimateBtcFee(tx, feeRate) + + // fee = (tx.SerializeSize() + len(tx.TxIn)*108) * feeRate + expectedSize := tx.SerializeSize() + len(tx.TxIn)*108 + expectedFee := btcutil.Amount(expectedSize) * feeRate + + assert.Equal(t, int64(expectedFee), int64(fee)) + assert.Greater(t, int64(fee), int64(0)) +} + +func TestWaitUntilDone(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + n := &neutrinoClient{ctx: ctx} + + // Test 1: done immediately + callCount := 0 + done := func() bool { + callCount++ + return true + } + n.waitUntilDone("test1", done, 0) + assert.Equal(t, 1, callCount) + + // Test 2: done after retries + callCount = 0 + doneAfter3 := func() bool { + callCount++ + return callCount >= 3 + } + n.waitUntilDone("test2", doneAfter3, time.Millisecond*10) + assert.Equal(t, 3, callCount) + + // Test 3: context cancelled + ctx2, cancel2 := context.WithCancel(context.Background()) + n2 := &neutrinoClient{ctx: ctx2} + cancel2() // cancel immediately + + neverDone := func() bool { return false } + // Should return immediately due to context cancelled + n2.waitUntilDone("test3", neverDone, time.Millisecond*10) +} + +func TestGetCommitKey(t *testing.T) { + n := &neutrinoClient{} + // Initially nil + assert.Nil(t, n.getCommitKey()) + + // Set commit key + testKey := "0xcc38546e9e659d15e6b4893f0ab32a06d103931a8230b0bde71459d2b27d6944" + _, n.commitKey, _ = getPrivKey(secp256k1.Name, testKey) + + key := n.getCommitKey() + assert.NotNil(t, key) + assert.Equal(t, n.commitKey, key) +} diff --git a/plugin/dapp/lightclient/rpc/lightclient/types.go b/plugin/dapp/lightclient/rpc/lightclient/types.go new file mode 100644 index 0000000000..7b735036a3 --- /dev/null +++ b/plugin/dapp/lightclient/rpc/lightclient/types.go @@ -0,0 +1,52 @@ +package lightclient + +import ( + "context" + "sync" + + "github.com/33cn/chain33/queue" +) + +var ( + clients = make(map[string]func() Lighter) + lock sync.Mutex +) + +// Lighter light client interface +type Lighter interface { + // Init init client context + Init(ctx context.Context, q queue.Queue, cfg *Config) error + // Start client routine + Start() +} + +// Config light client config +type Config struct { + EnableClients []string `json:"clients,omitempty"` + CommitAddr string `json:"commitAddr,omitempty"` + CommitKey string `json:"commitKey,omitempty"` + Neutrino interface{} `json:"neutrino,omitempty"` + Btc interface{} `json:"btc,omitempty"` +} + +// Register register light client +func Register(name string, newCli func() Lighter) { + + lock.Lock() + defer lock.Unlock() + _, ok := clients[name] + if ok { + panic("Register duplicate client, name=" + name) + } + + clients[name] = newCli +} + +// New create light client +func New(name string) Lighter { + create := clients[name] + if create != nil { + return create() + } + return nil +} diff --git a/plugin/dapp/lightclient/rpc/rpc.go b/plugin/dapp/lightclient/rpc/rpc.go new file mode 100644 index 0000000000..24ff0d65b0 --- /dev/null +++ b/plugin/dapp/lightclient/rpc/rpc.go @@ -0,0 +1,7 @@ +package rpc + +/* + * 实现json rpc和grpc service接口 + * json rpc用Jrpc结构作为接收实例 + * grpc使用channelClient结构作为接收实例 + */ diff --git a/plugin/dapp/lightclient/rpc/types.go b/plugin/dapp/lightclient/rpc/types.go new file mode 100644 index 0000000000..38a7777179 --- /dev/null +++ b/plugin/dapp/lightclient/rpc/types.go @@ -0,0 +1,57 @@ +package rpc + +import ( + "github.com/33cn/chain33/common/log/log15" + rpctypes "github.com/33cn/chain33/rpc/types" + "github.com/33cn/chain33/types" + lightclienttypes "github.com/33cn/plugin/plugin/dapp/lightclient/lighttypes" + "github.com/33cn/plugin/plugin/dapp/lightclient/rpc/lightclient" + _ "github.com/33cn/plugin/plugin/dapp/lightclient/rpc/lightclient/btc" + _ "github.com/33cn/plugin/plugin/dapp/lightclient/rpc/lightclient/neutrino" +) + +/* + * rpc相关结构定义和初始化 + */ + +var ( + log = log15.New("module", "lightclient.rpc") +) + +// 实现grpc的service接口 +type channelClient struct { + rpctypes.ChannelClient +} + +// Jrpc 实现json rpc调用实例 +type Jrpc struct { + cli *channelClient +} + +// Grpc grpc +type Grpc struct { + *channelClient +} + +// Init init rpc +func Init(name string, s rpctypes.RPCServer) { + cli := &channelClient{} + grpc := &Grpc{channelClient: cli} + cli.Init(name, s, &Jrpc{cli: cli}, grpc) + //存在grpc service时注册grpc server,需要生成对应的pb.go文件 + lightclienttypes.RegisterLightclientServer(s.GRPC(), grpc) + + lightCfg := &lightclient.Config{} + types.MustDecode(s.GetQueueClient().GetConfig().GetSubConfig().RPC["light"], lightCfg) + for _, c := range lightCfg.EnableClients { + log.Info("Init", "new light client", c) + if lc := lightclient.New(c); lc != nil { + err := lc.Init(s.Context(), s.GetQueueClient().GetQueue(), lightCfg) + if err != nil { + log.Error("Init", "light", c, "lightClient init err", err) + panic(err) + } + go lc.Start() + } + } +} diff --git a/plugin/dapp/rgbx/cmd/Makefile b/plugin/dapp/rgbx/cmd/Makefile new file mode 100644 index 0000000000..8ea1cd1096 --- /dev/null +++ b/plugin/dapp/rgbx/cmd/Makefile @@ -0,0 +1,2 @@ +all: + bash build.sh $(OUT) $(FLAG) diff --git a/plugin/dapp/rgbx/cmd/build.sh b/plugin/dapp/rgbx/cmd/build.sh new file mode 100644 index 0000000000..73665b5194 --- /dev/null +++ b/plugin/dapp/rgbx/cmd/build.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# 官方ci集成脚本 +set -e +set -o pipefail + +strpwd=$(pwd) +strcmd=${strpwd##*dapp/} +strapp=${strcmd%/cmd*} + +OUT_DIR="${1}/$strapp" +#FLAG=$2 + +mkdir -p "${OUT_DIR}" +cp -r ./ci/* "${OUT_DIR}" + +CHAIN33_PATH=$(go list -f "{{.Dir}}" github.com/33cn/chain33) +PLUGIN_PATH=$(go list -f "{{.Dir}}" github.com/33cn/plugin) + +# copy base configs for ci compose +cp "${CHAIN33_PATH}/cmd/chain33/chain33.test.toml" "${OUT_DIR}" +cp "${PLUGIN_PATH}/chain33.para.toml" "${OUT_DIR}" diff --git a/plugin/dapp/rgbx/cmd/ci/Dockerfile b/plugin/dapp/rgbx/cmd/ci/Dockerfile new file mode 100644 index 0000000000..b24b4a12dc --- /dev/null +++ b/plugin/dapp/rgbx/cmd/ci/Dockerfile @@ -0,0 +1,9 @@ +FROM ubuntu:22.04 + +WORKDIR /root + +COPY chain33 chain33 +COPY chain33-cli chain33-cli +COPY *.toml ./ + +CMD /root/chain33 -f /root/$ChainConfig diff --git a/plugin/dapp/rgbx/cmd/ci/Dockerfile.btcd b/plugin/dapp/rgbx/cmd/ci/Dockerfile.btcd new file mode 100644 index 0000000000..8ead68d0b3 --- /dev/null +++ b/plugin/dapp/rgbx/cmd/ci/Dockerfile.btcd @@ -0,0 +1,19 @@ +FROM golang:1.22 AS builder + +ARG BTCD_VERSION=v0.24.2 +WORKDIR /src + +RUN git clone --depth 1 --branch ${BTCD_VERSION} https://github.com/btcsuite/btcd.git . +RUN go install -v . ./cmd/btcctl + +FROM ubuntu:22.04 + +WORKDIR /root +COPY --from=builder /go/bin/btcd /usr/local/bin/btcd +COPY --from=builder /go/bin/btcctl /usr/local/bin/btcctl + +RUN mkdir -p /root/.btcd + +EXPOSE 18444 18443 + +CMD ["btcd"] diff --git a/plugin/dapp/rgbx/cmd/ci/docker-compose.sh b/plugin/dapp/rgbx/cmd/ci/docker-compose.sh new file mode 100755 index 0000000000..69c639de9f --- /dev/null +++ b/plugin/dapp/rgbx/cmd/ci/docker-compose.sh @@ -0,0 +1,772 @@ +#!/usr/bin/env bash + +set -e +set -o pipefail + +ROOT_DIR=$(cd "$(dirname "$0")" && pwd) +export PATH="${ROOT_DIR}:${PATH}" + +# shellcheck disable=SC1091 +source "${ROOT_DIR}/scripts/assertions.sh" +# shellcheck disable=SC1091 +source "${ROOT_DIR}/scripts/bootstrap.sh" + +ACTION="run" +PROJECT="" +if [ "$#" -gt 0 ]; then + case "${1}" in + run | up | down | init | config | test) + ACTION="${1}" + PROJECT="${2:-build}" + ;; + *) + PROJECT="${1}" + ;; + esac +fi +if [ -z "${PROJECT}" ]; then + PROJECT="build" +fi + +export COMPOSE_PROJECT_NAME="${PROJECT}" + +if command -v docker-compose >/dev/null 2>&1; then + COMPOSE_BIN="docker-compose" +elif docker compose version >/dev/null 2>&1; then + COMPOSE_BIN="docker compose" +else + fail "docker compose is required" +fi + +function compose_cmd() { + ${COMPOSE_BIN} "$@" +} + +MAIN_CLI="compose_cmd exec -T main /root/chain33-cli --conf=chain33.test.toml" +PARA1_CLI="compose_cmd exec -T para1 /root/chain33-cli --conf=chain33.para1.toml --paraName=user.p.rgbx." +PARA2_CLI="compose_cmd exec -T para2 /root/chain33-cli --conf=chain33.para2.toml --paraName=user.p.rgbx." +PARA3_CLI="compose_cmd exec -T para3 /root/chain33-cli --conf=chain33.para3.toml --paraName=user.p.rgbx." +PARA4_CLI="compose_cmd exec -T para4 /root/chain33-cli --conf=chain33.para4.toml --paraName=user.p.rgbx." +BTCD_RPC_USER="${BTCD_RPC_USER:-root}" +BTCD_RPC_PASS="${BTCD_RPC_PASS:-1314}" +BTC_CTL="compose_cmd exec -T btcd /usr/local/bin/btcctl --configfile=/tmp/btcctl.conf --rpcserver=127.0.0.1:18443 --rpcuser=${BTCD_RPC_USER} --rpcpass=${BTCD_RPC_PASS}" + +BTC_NETWORK="${BTC_NETWORK:-regtest}" +BTC_P2P_ADDR="${BTC_P2P_ADDR:-btcd:18444}" +BTC_RPC_ADDR="${BTC_RPC_ADDR:-btcd:18443}" +HOST_BTC_RPC_ADDR="${HOST_BTC_RPC_ADDR:-127.0.0.1:18443}" +PARA_TITLE="${PARA_TITLE:-user.p.rgbx.}" +TSS_THRESHOLD="${TSS_THRESHOLD:-3}" +AUTO_DISCOVER_TSS_PEERS="${AUTO_DISCOVER_TSS_PEERS:-true}" + +# 14KEKbYtKKQm4wMthSK9J4La4nAiidGozt +GENESIS_KEY="CC38546E9E659D15E6B4893F0AB32A06D103931A8230B0BDE71459D2B27D6944" + +# 4 para validators: one official TSS node + three validating nodes. +AUTH_ADDR1="1KSBd17H7ZK8iT37aJztFB22XGwsPTdwE4" +AUTH_ADDR2="1JRNjdEqp4LJ5fqycUBm9ayCKSeeskgMKR" +AUTH_ADDR3="1NLHPEcbTWWxxU3dGUZBhayjrCHD3psX7k" +AUTH_ADDR4="1MCftFynyvG2F4ED5mdHYgziDxx6vDrScs" +AUTH_KEY1="0x6da92a632ab7deb67d38c0f6560bcfed28167998f6496db64c258d5e8393a81b" +AUTH_KEY2="0x19c069234f9d3e61135fefbeb7791b149cdf6af536f26bebb310d4cd22c3fee4" +AUTH_KEY3="0x7a80a1f75d7360c6123c32a78ecf978c1ac55636f87892df38d8b85a9aeff115" +AUTH_KEY4="0xcacb1f5d51700aea07fca2246ab43b0917d70405c65edea9b5063d72eb5c6b71" +TSS_PEERS="${TSS_PEERS:-${AUTH_ADDR1},${AUTH_ADDR2},${AUTH_ADDR3},${AUTH_ADDR4}}" + +MINT_SYMBOL="${MINT_SYMBOL:-BTC}" +WITHDRAW_DEST_ADDR="${WITHDRAW_DEST_ADDR:-bcrt1qnnwpfpljh5n8m3a8xtf3x5ayvhjjplxmhuexyh}" +BTC_FUNDING_PRIV_HEX="${BTC_FUNDING_PRIV_HEX:-0000000000000000000000000000000000000000000000000000000000000001}" +BTC_DEPOSIT_AMOUNT_SATS="${BTC_DEPOSIT_AMOUNT_SATS:-20000000}" +BTC_WITHDRAW_AMOUNT_SATS="${BTC_WITHDRAW_AMOUNT_SATS:-500000}" +BTC_WITHDRAW_FEE_RATE="${BTC_WITHDRAW_FEE_RATE:-20}" +XBTC_TRANSFER_AMOUNT="${XBTC_TRANSFER_AMOUNT:-500000}" + +BTCD_RPC_CERT_IN_CONTAINER="${BTCD_RPC_CERT_IN_CONTAINER:-/btcd/rpc.cert}" +USER_B_KEY="${USER_B_KEY:-${AUTH_KEY2}}" +USER_B_ADDR="${USER_B_ADDR:-${AUTH_ADDR2}}" +# Fixed regtest mining identity (privHex=...0001 -> mrCDr...). +BTCD_MINING_ADDR="mrCDrCybB6J1vRfbwM5hemdJz73FwDBC8r" +BTC_FUNDING_WIF="${BTC_FUNDING_WIF:-}" +USER_MAIN_ADDR="${USER_MAIN_ADDR:-14KEKbYtKKQm4wMthSK9J4La4nAiidGozt}" + +function join_csv_as_toml_array() { + local csv="${1}" + local out="" + IFS=',' read -r -a items <<<"${csv}" + local i + for ((i = 0; i < ${#items[@]}; i++)); do + local item + item=$(echo "${items[$i]}" | xargs) + if [ -n "${item}" ]; then + if [ -n "${out}" ]; then + out="${out}," + fi + out="${out}\"${item}\"" + fi + done + echo "[${out}]" +} + +function config_main() { + log_step "configure chain33.test.toml" + local main_cfg="${ROOT_DIR}/chain33.test.toml" + if [ -f "${main_cfg}" ]; then + chmod u+w "${main_cfg}" 2>/dev/null || true + fi + perl -i -pe 's/^Title=.*/Title="local"/' "${main_cfg}" + perl -i -pe 's/^TestNet=.*/TestNet=true/' "${main_cfg}" + perl -i -pe 's/^jrpcBindAddr=.*/jrpcBindAddr="0.0.0.0:8801"/' "${main_cfg}" + perl -i -pe 's/^grpcBindAddr=.*/grpcBindAddr="0.0.0.0:8802"/' "${main_cfg}" + perl -i -pe 's/^whitelist=.*/whitelist=["*"]/' "${main_cfg}" + perl -i -pe 's/^isLevelFee=.*/isLevelFee=false/' "${main_cfg}" + + if ! grep -q '^\[exec.sub.rgbx\]' "${main_cfg}" 2>/dev/null; then + cat >>"${main_cfg}" </dev/null || true + fi + cp "${ROOT_DIR}/chain33.para.toml" "${path}" + chmod u+w "${path}" 2>/dev/null || true + perl -i -pe 's/^Title=.*/Title="'"${PARA_TITLE}"'"/' "${path}" + perl -i -pe 's/^TestNet=.*/TestNet=true/' "${path}" + perl -i -pe 's/^mainChainGrpcAddr=.*/mainChainGrpcAddr="main:8802"/' "${path}" + perl -i -pe 's/^jrpcBindAddr=.*/jrpcBindAddr="0.0.0.0:8801"/' "${path}" + perl -i -pe 's/^authAccount=.*/authAccount="'"${auth_addr}"'"/' "${path}" + perl -i -pe 's/^startHeight=.*/startHeight=1/' "${path}" + perl -i -pe 's/^enableTSS=.*/enableTSS=true/' "${path}" + if ! grep -q '^enableTSS=' "${path}" 2>/dev/null; then + perl -i -0pe 's/\[crypto\]\n/[crypto]\nenableTSS=true\n/' "${path}" + fi + # Para nodes must enable DHT before peer discovery and TSS peer collection. + perl -i -pe 's/^types=.*/types=["dht"]/' "${path}" + perl -i -pe 's/^enable=.*/enable=true/' "${path}" + perl -i -pe 's/^waitPid=.*/waitPid=false/' "${path}" + + local toml_peers + toml_peers=$(join_csv_as_toml_array "${TSS_PEERS}") + cat >>"${path}" </dev/null 2>&1; then + return 0 + fi + sleep 1 + done + fail "cli not ready: ${cli}" +} + +function wait_btcd_ready() { + log_step "wait btcd ready" + # Ensure btcd home/config paths exist under bind-mounted volume. + compose_cmd exec -T btcd sh -c 'mkdir -p /root/.btcd /tmp && [ -f /root/.btcd/btcd.conf ] || : > /root/.btcd/btcd.conf' >/dev/null 2>&1 || true + local retries=10 + local i + local last_err="" + for ((i = 0; i < retries; i++)); do + local out + if out=$(${BTC_CTL} --"${BTC_NETWORK}" getblockcount 2>&1); then + return 0 + fi + last_err="${out}" + sleep 1 + done + if [ -n "${last_err}" ]; then + log_step "btcd probe last error: ${last_err}" + fi + compose_cmd ps btcd || true + compose_cmd logs --tail=120 btcd || true + fail "btcd not ready" +} + +function block_wait() { + local cli="$1" + local delta="$2" + local cur + local expect + cur=$(${cli} block last_header | jq ".height") + expect=$((cur + delta)) + local count=300 + while [ "${count}" -gt 0 ]; do + local now + now=$(${cli} block last_header | jq ".height") + if [ "${now}" -ge "${expect}" ]; then + return 0 + fi + count=$((count - 1)) + sleep 0.2 + done + fail "wait block timeout for ${cli}, expect=${expect}" +} + +function tx_wait() { + local cli="$1" + local tx_hash="$2" + local retries=20 + local i + for ((i = 0; i < retries; i++)); do + if ${cli} tx query_hash -s "${tx_hash}" >/dev/null 2>&1; then + return 0 + fi + sleep 0.5 + done + fail "tx not found: ${tx_hash}" +} + +function prepare_btcd_mining_identity() { + local raw_output + local key_info + raw_output=$(compose_cmd run --rm --no-deps -T main /root/chain33-cli rgbx btcKeyInfo --net "${BTC_NETWORK}" --privHex "${BTC_FUNDING_PRIV_HEX}") + # docker compose may print progress lines before command output; keep JSON payload only. + key_info=$(echo "${raw_output}" | awk 'BEGIN{keep=0} /^\{/ {keep=1} keep==1 {print}') + assert_non_empty "${key_info}" "btcKeyInfo json output empty" + BTC_FUNDING_WIF=$(echo "${key_info}" | jq -r '.wif') + local derived_addr + derived_addr=$(echo "${key_info}" | jq -r '.address') + assert_non_empty "${BTC_FUNDING_WIF}" "btc funding wif empty" + assert_non_empty "${derived_addr}" "btcd mining address empty" + assert_eq "${derived_addr}" "${BTCD_MINING_ADDR}" "btc funding key does not match fixed BTCD_MINING_ADDR" + log_step "prepared btcd mining identity address=${BTCD_MINING_ADDR}" +} + +function get_main_addr_by_label() { + local label="$1" + ${MAIN_CLI} account list | jq -r --arg l "${label}" '[.[]? | select(.label == $l) | .addr][0] // empty' +} + +function query_xbtc_balance() { + local addr="$1" + ${MAIN_CLI} asset balance -a "${addr}" --asset_exec=rgbx --asset_symbol=XBTC | jq -r '.balance // "0"' +} + +function wait_xbtc_balance_not_less_than() { + local addr="$1" + local expected="$2" + local retries="${3:-30}" + local i + for ((i = 0; i < retries; i++)); do + local balance + balance=$(query_xbtc_balance "${addr}") + if awk "BEGIN{exit !(${balance} >= ${expected})}"; then + return 0 + fi + sleep 1 + done + fail "xbtc balance not reached, addr=${addr}, expected>=${expected}" +} + +function mine_btcd_blocks() { + local count="$1" + ${BTC_CTL} --"${BTC_NETWORK}" generate "${count}" >/dev/null +} + +function build_mature_coinbase_utxo() { + assert_non_empty "${BTCD_MINING_ADDR}" "BTCD_MINING_ADDR empty" + local best_height + best_height=$(${BTC_CTL} --"${BTC_NETWORK}" getblockcount) + local mature_height=$((best_height - 100)) + if [ "${mature_height}" -lt 1 ]; then + fail "no mature coinbase yet: bestHeight=${best_height}" + fi + + local block_hash + block_hash=$(${BTC_CTL} --"${BTC_NETWORK}" getblockhash "${mature_height}") + local coinbase_tx + coinbase_tx=$(${BTC_CTL} --"${BTC_NETWORK}" getblock "${block_hash}" 1 | jq -r '.tx[0] // empty') + assert_non_empty "${coinbase_tx}" "coinbase tx empty at height=${mature_height}" + local tx_json + tx_json=$(${BTC_CTL} --"${BTC_NETWORK}" getrawtransaction "${coinbase_tx}" 1) + local vout + vout=$(echo "${tx_json}" | jq -r --arg a "${BTCD_MINING_ADDR}" '.vout[] | select(.scriptPubKey.addresses[]? == $a) | .n' | head -1) + assert_non_empty "${vout}" "coinbase vout not found for mining address" + local amount_sats + amount_sats=$(echo "${tx_json}" | jq -r --argjson v "${vout}" '.vout[] | select(.n == $v) | (.value * 100000000 | floor)') + local pk_script + pk_script=$(echo "${tx_json}" | jq -r --argjson v "${vout}" '.vout[] | select(.n == $v) | .scriptPubKey.hex') + assert_non_empty "${amount_sats}" "coinbase amount sats empty" + assert_non_empty "${pk_script}" "coinbase pkScript empty" + echo "${coinbase_tx}:${vout}:${amount_sats}:${pk_script}" +} + +function wait_no_withdraw_pending_for_user() { + local from_addr="$1" + local retries=30 + local i + local cnt=0 + for ((i = 0; i < retries; i++)); do + cnt=$(${MAIN_CLI} rgbx listPendByFrom -f "${from_addr}" | jq '[.pendingList[]? | select(.actionType == 106)] | length') + if [ "${cnt}" -eq 0 ]; then + return 0 + fi + mine_btcd_blocks 1 + sleep 2 + done + fail "withdraw pending not cleared for ${from_addr}, cnt=${cnt}" +} + +function query_latest_received_sats() { + local addr="$1" + local txs + if ! txs=$(${BTC_CTL} --"${BTC_NETWORK}" searchrawtransactions "${addr}" 1 0 1 0 true 2>/dev/null); then + echo 0 + return 0 + fi + echo "${txs}" | jq -r --arg addr "${addr}" ' + [ + .[0]?.vout[]? + | select((((.scriptPubKey.addresses? // []) | index($addr)) != null) or (.scriptPubKey.address? == $addr)) + | (.value * 100000000 | floor) + ] | add // 0' +} + +function wait_btc_address_received_not_less_than() { + local addr="$1" + local expected_sats="$2" + local retries=3 + local i + for ((i = 0; i < retries; i++)); do + local received_sats + received_sats=$(query_btc_address_received_sats "${addr}") + if awk "BEGIN{exit !(${received_sats} >= ${expected_sats})}"; then + log_step "btc address received: addr=${addr}, sats=${received_sats}" + return 0 + fi + mine_btcd_blocks 1 + sleep 1 + done + fail "btc withdraw destination balance not received, addr=${addr}, expect>=${expected_sats}" +} + +function prepare_accounts() { + log_step "prepare wallets and auth keys" + + save_seed_and_unlock "${MAIN_CLI}" + import_default_keys "${MAIN_CLI}" + ${MAIN_CLI} account import_key -k "${USER_B_KEY}" -l rgbxUserB >/dev/null || true + ${MAIN_CLI} send coins transfer -t "${USER_B_ADDR}" -a 100 -k "${GENESIS_KEY}" >/dev/null + + save_seed_and_unlock "${PARA1_CLI}" || true + save_seed_and_unlock "${PARA2_CLI}" || true + save_seed_and_unlock "${PARA3_CLI}" || true + save_seed_and_unlock "${PARA4_CLI}" || true + + ${PARA1_CLI} account import_key -k "${AUTH_KEY1}" -l paraAuth1 >/dev/null || true + ${PARA2_CLI} account import_key -k "${AUTH_KEY2}" -l paraAuth2 >/dev/null || true + ${PARA3_CLI} account import_key -k "${AUTH_KEY3}" -l paraAuth3 >/dev/null || true + ${PARA4_CLI} account import_key -k "${AUTH_KEY4}" -l paraAuth4 >/dev/null || true +} + +function collect_peer_names_from_cli() { + local cli="$1" + ${cli} net peer | jq -r '[.. | objects | .name? | strings] | .[]' 2>/dev/null || true +} + +function peer_count_from_cli() { + local cli="$1" + local peers + peers=$(collect_peer_names_from_cli "${cli}" | sort | uniq | sed '/^$/d') + echo "${peers}" | sed '/^$/d' | wc -l | xargs +} + +function wait_para_dht_discovery() { + log_step "wait para dht discovery" + local retries=30 + local i + for ((i = 0; i < retries; i++)); do + local count + count=$(peer_count_from_cli "${PARA1_CLI}") + # In a 4-para topology, para1 should discover at least the other 3 peers, + if [ "${count}" -ge 4 ]; then + log_step "dht ready: para1=${count}" + return 0 + fi + sleep 1 + done + fail "dht peer discovery timeout; check p2p.dht and container network" +} + +function discover_tss_peer_names() { + local retries=30 + local i + for ((i = 0; i < retries; i++)); do + # All para nodes use the same tss peers config. Query from one node is enough. + local peers + peers=$(collect_peer_names_from_cli "${PARA1_CLI}" | sort | uniq | sed '/^$/d') + local count + count=$(echo "${peers}" | sed '/^$/d' | wc -l | xargs) + if [ "${count}" -ge 4 ]; then + echo "${peers}" | head -4 | paste -sd, - + return 0 + fi + sleep 1 + done + return 1 +} + +function rewrite_tss_peers_only() { + local toml_peers + toml_peers=$(join_csv_as_toml_array "${TSS_PEERS}") + local file + for file in chain33.para1.toml chain33.para2.toml chain33.para3.toml chain33.para4.toml; do + perl -i -pe "s/^peers=.*/peers=${toml_peers}/" "${ROOT_DIR}/${file}" + done +} + +function copy_para_toml_into_container() { + local svc="$1" + local f="chain33.${svc}.toml" + local cid + cid=$(compose_cmd ps -q "${svc}" 2>/dev/null | head -1) + assert_non_empty "${cid}" "no running container for ${svc}; copy ${f} skipped" + docker cp "${ROOT_DIR}/${f}" "${cid}:/root/${f}" +} + +function restart_para_nodes_with_new_toml() { + log_step "push updated para toml into para1-4 and restart those services (main/btcd unchanged)" + local svc + for svc in para1 para2 para3 para4; do + copy_para_toml_into_container "${svc}" + done + compose_cmd restart para1 para2 para3 para4 + sleep 3 +} + +function apply_dynamic_tss_peers_and_restart() { + if [ "${AUTO_DISCOVER_TSS_PEERS}" != "true" ]; then + log_step "skip dynamic peer discovery; use static TSS_PEERS=${TSS_PEERS}" + return 0 + fi + + wait_para_dht_discovery + + local discovered + discovered=$(discover_tss_peer_names) || fail "failed to discover 4 peer names from net peer" + TSS_PEERS="${discovered}" + log_step "discovered TSS_PEERS=${TSS_PEERS}" + + log_step "rewrite TSS peers in para toml, copy into containers, restart para only" + rewrite_tss_peers_only + restart_para_nodes_with_new_toml + wait_cli_ready "${PARA1_CLI}" + wait_cli_ready "${PARA2_CLI}" + wait_cli_ready "${PARA3_CLI}" + wait_cli_ready "${PARA4_CLI}" +} + +function setup_para_nodegroup_on_main() { + log_step "setup para nodegroup on main chain" + ${MAIN_CLI} send coins transfer -t "${AUTH_ADDR1}" -a 100 -k "${GENESIS_KEY}" >/dev/null + ${MAIN_CLI} send coins transfer -t "${AUTH_ADDR2}" -a 100 -k "${GENESIS_KEY}" >/dev/null + ${MAIN_CLI} send coins transfer -t "${AUTH_ADDR3}" -a 100 -k "${GENESIS_KEY}" >/dev/null + ${MAIN_CLI} send coins transfer -t "${AUTH_ADDR4}" -a 100 -k "${GENESIS_KEY}" >/dev/null + + ${MAIN_CLI} send coins send_exec -e paracross -a 20 -k "${AUTH_KEY1}" >/dev/null + ${MAIN_CLI} send coins send_exec -e paracross -a 20 -k "${AUTH_KEY2}" >/dev/null + ${MAIN_CLI} send coins send_exec -e paracross -a 20 -k "${AUTH_KEY3}" >/dev/null + ${MAIN_CLI} send coins send_exec -e paracross -a 20 -k "${AUTH_KEY4}" >/dev/null + + local addrs="${AUTH_ADDR1},${AUTH_ADDR2},${AUTH_ADDR3},${AUTH_ADDR4}" + local apply_hash + apply_hash=$(${MAIN_CLI} send para nodegroup apply --paraName="${PARA_TITLE}" -a "${addrs}" -c 5 -k "${AUTH_KEY1}") + assert_length "${apply_hash}" 66 + tx_wait "${MAIN_CLI}" "${apply_hash}" + + local approve_hash + approve_hash=$(${MAIN_CLI} send para nodegroup approve --paraName="${PARA_TITLE}" -i "${apply_hash}" -c 5 -k "${AUTH_KEY1}") + assert_length "${approve_hash}" 66 + tx_wait "${MAIN_CLI}" "${approve_hash}" + ${MAIN_CLI} para nodegroup addrs --paraName="${PARA_TITLE}" +} + +function ensure_btc_crosschain_prerequisite() { + log_step "check BTC cross-chain prerequisite only (no mint bootstrap)" + set +e + local info + info=$(${MAIN_CLI} rgbx getCross -s "${MINT_SYMBOL}" 2>/dev/null) + local rc=$? + set -e + if [ "${rc}" -ne 0 ]; then + fail "BTC cross-chain info not ready; please pre-configure rgbx BTC asset and cross-chain metadata before running this CI" + fi + local symbol + symbol=$(echo "${info}" | jq -r '.assetSymbol // empty') + if [ -z "${symbol}" ]; then + fail "BTC cross-chain info missing; this test does not create asset via mint" + fi +} + +function wait_auto_dkg_commit() { + log_step "wait auto DKG commit by neutrino+tss" + local retries=60 + local i + for ((i = 0; i < retries; i++)); do + set +e + local info + info=$(${MAIN_CLI} rgbx getCross -s "${MINT_SYMBOL}" 2>/dev/null) + local rc=$? + set -e + if [ "${rc}" -eq 0 ]; then + local tss_addr + tss_addr=$(echo "${info}" | jq -r '.tssAddress // empty') + if [ -n "${tss_addr}" ]; then + log_step "auto DKG done, tssAddress=${tss_addr}" + return 0 + fi + fi + sleep 1 + done + fail "auto DKG commit timeout" +} + +function scenario_user_deposit_via_btc_tx() { + log_step "scenario: user deposit via btc tx -> service auto submit deposit" + local before_balance + before_balance=$(query_xbtc_balance "${USER_MAIN_ADDR}") + # make sure segwit is activated + mine_btcd_blocks 450 + local utxo + utxo=$(build_mature_coinbase_utxo) + assert_non_empty "${utxo}" "funding utxo empty" + + local tss_addr + tss_addr=$(${MAIN_CLI} rgbx getCross -s "${MINT_SYMBOL}" | jq -r '.tssAddress // empty') + assert_non_empty "${tss_addr}" "tssAddress empty before deposit" + + local deposit_tx_hash + deposit_tx_hash=$(compose_cmd exec -T main /root/chain33-cli rgbx btcDepositTx \ + --net "${BTC_NETWORK}" \ + --rpcHost "${BTC_RPC_ADDR}" \ + --rpcUser "${BTCD_RPC_USER}" \ + --rpcPass "${BTCD_RPC_PASS}" \ + --disableTLS=false \ + --rpcCertFile "${BTCD_RPC_CERT_IN_CONTAINER}" \ + --wif "${BTC_FUNDING_WIF}" \ + --utxo "${utxo}" \ + --tssAddress "${tss_addr}" \ + --chain33Address "${USER_MAIN_ADDR}" \ + --amount "${BTC_DEPOSIT_AMOUNT_SATS}" \ + --fee 500) + assert_length "${deposit_tx_hash}" 64 "btc deposit tx hash length mismatch" + + mine_btcd_blocks 2 + local expected_balance + expected_balance=$(awk "BEGIN{printf \"%.8f\", ${before_balance}+${BTC_DEPOSIT_AMOUNT_SATS}/100000000}") + wait_xbtc_balance_not_less_than "${USER_MAIN_ADDR}" "${expected_balance}" +} + +function scenario_user_transfer_crosschain_asset() { + log_step "scenario: user A transfer cross-chain asset(XBTC) to user B on mainchain" + local before_a + local before_b + before_a=$(query_xbtc_balance "${USER_MAIN_ADDR}") + before_b=$(query_xbtc_balance "${USER_B_ADDR}") + + local transfer_hash + local xbtc_transfer_amount + xbtc_transfer_amount=$(awk "BEGIN{printf \"%.8f\", ${XBTC_TRANSFER_AMOUNT}/100000000}") + transfer_hash=$(${MAIN_CLI} send rgbx transfer -a "${xbtc_transfer_amount}" -s XBTC \ + -t "${USER_B_ADDR}" -k "${GENESIS_KEY}") + assert_length "${transfer_hash}" 66 "transfer tx hash" + # tx_wait "${MAIN_CLI}" "${transfer_hash}" + + local after_a + local after_b + after_a=$(query_xbtc_balance "${USER_MAIN_ADDR}") + after_b=$(query_xbtc_balance "${USER_B_ADDR}") + expected_a=$(awk "BEGIN{printf \"%.4f\", ${before_a} - ${xbtc_transfer_amount}}") + expected_b=$(awk "BEGIN{printf \"%.4f\", ${before_b} + ${xbtc_transfer_amount}}") + assert_balance "${after_a}" "${expected_a}" "user A xbtc not decreased after transfer" + assert_balance "${after_b}" "${expected_b}" "user B xbtc not increased after transfer" +} + +function scenario_user_withdraw_auto_confirm() { + log_step "scenario: user withdraw on mainchain -> service auto confirm" + local before_balance + before_balance=$(query_xbtc_balance "${USER_MAIN_ADDR}") + + local withdraw_hash + local btc_withdraw_amount + btc_withdraw_amount=$(awk "BEGIN{printf \"%.8f\", ${BTC_WITHDRAW_AMOUNT_SATS}/100000000}") + withdraw_hash=$(${MAIN_CLI} send rgbx withdraw -a "${btc_withdraw_amount}" -f "${BTC_WITHDRAW_FEE_RATE}" \ + -d "${WITHDRAW_DEST_ADDR}" -s "${MINT_SYMBOL}" -k "${GENESIS_KEY}") + assert_length "${withdraw_hash}" 66 + # tx_wait "${MAIN_CLI}" "${withdraw_hash}" + sleep 10 # wait for withdraw tx to be committed + wait_no_withdraw_pending_for_user "${USER_MAIN_ADDR}" + received_sats=$(query_latest_received_sats "${WITHDRAW_DEST_ADDR}") + local expected_received=$((BTC_WITHDRAW_AMOUNT_SATS - 5000)) + # 允许 ±1000 sats 的误差 + diff=$((received_sats - expected_received)) + if ((diff < 0)); then diff=$((-diff)); fi + if ((diff >= 1000)); then + fail "btc withdraw amount mismatch, expect≈${expected_received}, actual=${received_sats}" + fi + + local after_balance + after_balance=$(query_xbtc_balance "${USER_MAIN_ADDR}") + expected_balance=$(awk "BEGIN{printf \"%.4f\", ${before_balance} - ${btc_withdraw_amount}}") + assert_balance "${after_balance}" "${expected_balance}" "xbtc balance not decreased after withdraw settle" +} + +function scenario_restart_recovery() { + log_step "scenario: restart recovery and pending continuity" + local before + before=$(${MAIN_CLI} rgbx listPend -s 0 -i 0 -c 20 | jq -r '.pendingList | length') + + compose_cmd restart main >/dev/null + wait_cli_ready "${MAIN_CLI}" + save_seed_and_unlock "${MAIN_CLI}" || true + + local after + after=$(${MAIN_CLI} rgbx listPend -s 0 -i 0 -c 20 | jq -r '.pendingList | length') + assert_true "$([ "${after}" -ge 0 ] && echo true || echo false)" "pending list query failed after restart" + log_step "pending continuity check before=${before}, after=${after}" +} + +function scenario_para_health() { + log_step "scenario: 4 para nodes sync and network health" + ${PARA1_CLI} net is_sync >/dev/null + ${PARA2_CLI} net is_sync >/dev/null + ${PARA3_CLI} net is_sync >/dev/null + ${PARA4_CLI} net is_sync >/dev/null +} + +function ensure_btcd_network_consistency() { + if [ "${BTC_NETWORK}" != "regtest" ]; then + fail "unsupported BTC_NETWORK=${BTC_NETWORK}; this CI uses btcd --regtest only" + fi +} + +function run_tests() { + ensure_btcd_network_consistency + prepare_btcd_mining_identity + wait_btcd_ready + scenario_para_health + setup_para_nodegroup_on_main + # ensure_btc_crosschain_prerequisite + wait_auto_dkg_commit + scenario_user_deposit_via_btc_tx + scenario_user_transfer_crosschain_asset + scenario_user_withdraw_auto_confirm + scenario_restart_recovery +} + +function print_logs_hint() { + log_step "collect logs with: ${COMPOSE_BIN} logs --tail=200" + log_step "neutrino peer endpoint: ${BTC_P2P_ADDR}" + log_step "neutrino rpc endpoint: ${BTC_RPC_ADDR}" + log_step "for neutrino config: netName=${BTC_NETWORK}, connectPeers=[\"${BTC_P2P_ADDR}\"], btcRPC.host=\"${BTC_RPC_ADDR}\", btcRPC.disableTLS=false" + log_step "TSS roles: para1 official(rank=0), para2-4 validators(rank=1), threshold=${TSS_THRESHOLD}" +} + +function do_up_only() { + ensure_btcd_network_consistency + mkdir -p "${ROOT_DIR}/btcd-data" + init_env + prepare_btcd_mining_identity + start_env + wait_btcd_ready + apply_dynamic_tss_peers_and_restart + prepare_accounts +} + +function do_run_all() { + do_up_only + run_tests + print_logs_hint +} + +function do_down() { + compose_cmd down --remove-orphans +} + +case "${ACTION}" in +run) + do_run_all + ;; +up) + do_up_only + ;; +init) + init_env + ;; +config) + prepare_accounts + ;; +test) + run_tests + ;; +down) + do_down + ;; +*) + fail "unknown action: ${ACTION}" + ;; +esac diff --git a/plugin/dapp/rgbx/cmd/ci/docker-compose.yml b/plugin/dapp/rgbx/cmd/ci/docker-compose.yml new file mode 100644 index 0000000000..e71494042b --- /dev/null +++ b/plugin/dapp/rgbx/cmd/ci/docker-compose.yml @@ -0,0 +1,85 @@ +services: + main: + build: + context: . + environment: + ChainConfig: "chain33.test.toml" + ports: + - "8801:8801" + - "8802:8802" + volumes: + - ./btcd-data:/btcd:ro + + para1: + build: + context: . + environment: + ChainConfig: "chain33.para1.toml" + depends_on: + main: + condition: service_started + btcd: + condition: service_healthy + volumes: + - ./btcd-data:/btcd:ro + + para2: + build: + context: . + environment: + ChainConfig: "chain33.para2.toml" + depends_on: + - main + volumes: + - ./btcd-data:/btcd:ro + + para3: + build: + context: . + environment: + ChainConfig: "chain33.para3.toml" + depends_on: + - main + volumes: + - ./btcd-data:/btcd:ro + + para4: + build: + context: . + environment: + ChainConfig: "chain33.para4.toml" + depends_on: + - main + volumes: + - ./btcd-data:/btcd:ro + + btcd: + # RPC TLS cert auto-gen uses os.Hostname(); must match Docker DNS name "btcd" + # so para/lightclient can dial btcd:18443 without SAN mismatch. + hostname: btcd + build: + context: . + dockerfile: Dockerfile.btcd + args: + BTCD_VERSION: "${BTCD_VERSION:-v0.24.2}" + command: + - btcd + - --regtest + - --txindex + - --addrindex + - --rpclisten=0.0.0.0:18443 + - --rpcuser=${BTCD_RPC_USER:-root} + - --rpcpass=${BTCD_RPC_PASS:-1314} + - --listen=0.0.0.0:18444 + - --miningaddr=mrCDrCybB6J1vRfbwM5hemdJz73FwDBC8r + - --debuglevel=info + ports: + - "18443:18443" + - "18444:18444" + volumes: + - ./btcd-data:/root/.btcd + healthcheck: + test: ["CMD", "btcctl", "--configfile=/tmp/btcctl.conf", "--regtest", "--rpcserver=127.0.0.1:18443", "--rpcuser=${BTCD_RPC_USER:-root}", "--rpcpass=${BTCD_RPC_PASS:-1314}", "getblockcount"] + interval: 5s + timeout: 5s + retries: 30 diff --git a/plugin/dapp/rgbx/cmd/ci/scripts/assertions.sh b/plugin/dapp/rgbx/cmd/ci/scripts/assertions.sh new file mode 100755 index 0000000000..860cd5346a --- /dev/null +++ b/plugin/dapp/rgbx/cmd/ci/scripts/assertions.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash + +set -e +set -o pipefail + +function log_step() { + echo "[$(date +'%Y-%m-%d %H:%M:%S')] $*" +} + +function fail() { + log_step "ERROR: $*" + exit 1 +} + +function assert_eq() { + local actual="${1}" + local expect="${2}" + local message="${3:-assert_eq failed}" + if [ "${actual}" != "${expect}" ]; then + fail "${message}, expect=${expect}, actual=${actual}" + fi +} + +function assert_non_empty() { + local value="${1}" + local message="${2:-assert_non_empty failed}" + if [ -z "${value}" ] || [ "${value}" = "null" ]; then + fail "${message}" + fi +} + +function assert_balance { + local actual="${1}" + local expect="${2}" + local message="${3:-assert_balance failed}" + if [ "${actual}" != "${expect}" ]; then + fail "${message}, expect=${expect}, actual=${actual}" + fi +} + +function assert_length() { + local value="${1}" + local length="${2}" + local message="${3:-assert_length failed}" + if [ "${#value}" -ne "${length}" ]; then + fail "${message}, expect=${length}, actual=${#value}, value=${value}" + fi +} + +function assert_true() { + local expr="${1}" + local message="${2:-assert_true failed}" + if [ "${expr}" != "true" ]; then + fail "${message}, actual=${expr}" + fi +} + +function wait_until() { + local retries="${1}" + local interval="${2}" + local check_cmd="${3}" + local message="${4:-wait_until timeout}" + + local i + for ((i = 0; i < retries; i++)); do + if eval "${check_cmd}" >/dev/null 2>&1; then + return 0 + fi + sleep "${interval}" + done + fail "${message}" +} diff --git a/plugin/dapp/rgbx/cmd/ci/scripts/bootstrap.sh b/plugin/dapp/rgbx/cmd/ci/scripts/bootstrap.sh new file mode 100755 index 0000000000..df1195b153 --- /dev/null +++ b/plugin/dapp/rgbx/cmd/ci/scripts/bootstrap.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +set -e +set -o pipefail + +function save_seed_and_unlock() { + local cli="${1}" + ${cli} seed save -p 1314fuzamei -s "tortoise main civil member grace happy century convince father cage beach hip maid merry rib" >/dev/null + ${cli} wallet unlock -p 1314fuzamei -t 0 >/dev/null +} + +function import_default_keys() { + local cli="${1}" + ${cli} account import_key -k 4257D8692EF7FE13C68B65D6A52F03933DB2FA5CE8FAF210B5B8B80C721CED01 -l minerAddr >/dev/null + ${cli} account import_key -k CC38546E9E659D15E6B4893F0AB32A06D103931A8230B0BDE71459D2B27D6944 -l returnAddr >/dev/null +} + +function enable_mining() { + local cli="${1}" + ${cli} wallet auto_mine -f 1 >/dev/null +} diff --git a/plugin/dapp/rgbx/cmd/ci/testcase.sh b/plugin/dapp/rgbx/cmd/ci/testcase.sh new file mode 100755 index 0000000000..96b57cbfbc --- /dev/null +++ b/plugin/dapp/rgbx/cmd/ci/testcase.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +function rgbx() { + echo "testcase.sh is intentionally empty; use docker-compose.sh" +} diff --git a/plugin/dapp/rgbx/commands/btc.go b/plugin/dapp/rgbx/commands/btc.go new file mode 100644 index 0000000000..51eb6f913b --- /dev/null +++ b/plugin/dapp/rgbx/commands/btc.go @@ -0,0 +1,575 @@ +package commands + +import ( + "encoding/hex" + "fmt" + "math" + "os" + "strconv" + "strings" + + rtypes "github.com/33cn/plugin/plugin/dapp/rgbx/types" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/rpcclient" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/spf13/cobra" +) + +func btcAddrScriptCMD() *cobra.Command { + cmd := &cobra.Command{ + Use: "addrScript", + Short: "convert btc address to pkScript hex", + Run: btcAddrScript, + } + cmd.Flags().StringP("address", "a", "", "bitcoin address") + cmd.Flags().String("net", "mainnet", "bitcoin network: mainnet|testnet|regtest|simnet") + markRequired(cmd, "address") + return cmd +} + +func btcAddrScript(cmd *cobra.Command, _ []string) { + address, _ := cmd.Flags().GetString("address") + netName, _ := cmd.Flags().GetString("net") + + params, err := parseNetParams(netName) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "invalid net: %s, err: %v\n", netName, err) + return + } + addr, err := btcutil.DecodeAddress(address, params) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "invalid address: %s, decode err: %v\n", address, err) + return + } + script, err := txscript.PayToAddrScript(addr) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "convert address to script failed: %v\n", err) + return + } + fmt.Println(hex.EncodeToString(script)) +} + +func btcDepositTxCMD() *cobra.Command { + cmd := &cobra.Command{ + Use: "btcDepositTx", + Short: "build, sign and broadcast btc deposit tx", + Run: btcDepositTx, + Example: "btcDepositTx --net regtest --rpcHost 127.0.0.1:18443 " + + "--wif --utxo --tssAddress --chain33Address " + + "--amount 100000 --fee 300", + } + cmd.Flags().String("net", "regtest", "bitcoin network: mainnet|testnet|regtest|simnet") + cmd.Flags().String("rpcHost", "127.0.0.1:18443", "bitcoin rpc host") + cmd.Flags().String("rpcUser", "", "bitcoin rpc user (optional)") + cmd.Flags().String("rpcPass", "", "bitcoin rpc password (optional)") + cmd.Flags().Bool("disableTLS", true, "disable rpc tls") + cmd.Flags().String("rpcCertFile", "", "bitcoin rpc cert file path (optional, required when TLS enabled)") + cmd.Flags().String("wif", "", "sender private key in WIF format") + cmd.Flags().String("utxo", "", "single input utxo, format: txid:vout:amountSats:pkScriptHex") + cmd.Flags().String("tssAddress", "", "tss deposit address") + cmd.Flags().String("chain33Address", "", "chain33 deposit address for OP_RETURN rgbx:deposit:") + cmd.Flags().Int64("amount", 0, "deposit amount in satoshis") + cmd.Flags().Int64("fee", 0, "tx fee in satoshis") + cmd.Flags().String("changeAddress", "", "optional change address, default from private key") + markRequired(cmd, "wif", "utxo", "tssAddress", "chain33Address", "amount", "fee") + return cmd +} + +type depositUTXO struct { + hash *chainhash.Hash + vout uint32 + amount int64 + pkScript []byte +} + +func parseDepositUTXO(raw string) (*depositUTXO, error) { + parts := strings.Split(raw, ":") + if len(parts) != 4 { + return nil, fmt.Errorf("invalid utxo format: %s", raw) + } + hash, err := chainhash.NewHashFromStr(strings.TrimSpace(parts[0])) + if err != nil { + return nil, fmt.Errorf("invalid utxo txid: %w", err) + } + vout64, err := strconv.ParseUint(strings.TrimSpace(parts[1]), 10, 32) + if err != nil { + return nil, fmt.Errorf("invalid utxo vout: %w", err) + } + amount, err := strconv.ParseInt(strings.TrimSpace(parts[2]), 10, 64) + if err != nil || amount <= 0 { + return nil, fmt.Errorf("invalid utxo amount: %s", parts[2]) + } + pkScript, err := hex.DecodeString(strings.TrimSpace(parts[3])) + if err != nil { + return nil, fmt.Errorf("invalid utxo pkScript: %w", err) + } + return &depositUTXO{ + hash: hash, + vout: uint32(vout64), + amount: amount, + pkScript: pkScript, + }, nil +} + +func btcDepositTx(cmd *cobra.Command, _ []string) { + netName, _ := cmd.Flags().GetString("net") + rpcHost, _ := cmd.Flags().GetString("rpcHost") + rpcUser, _ := cmd.Flags().GetString("rpcUser") + rpcPass, _ := cmd.Flags().GetString("rpcPass") + disableTLS, _ := cmd.Flags().GetBool("disableTLS") + rpcCertFile, _ := cmd.Flags().GetString("rpcCertFile") + wifStr, _ := cmd.Flags().GetString("wif") + utxoRaw, _ := cmd.Flags().GetString("utxo") + tssAddrStr, _ := cmd.Flags().GetString("tssAddress") + chain33Addr, _ := cmd.Flags().GetString("chain33Address") + amount, _ := cmd.Flags().GetInt64("amount") + fee, _ := cmd.Flags().GetInt64("fee") + changeAddrStr, _ := cmd.Flags().GetString("changeAddress") + + params, err := parseNetParams(netName) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "invalid net: %s, err: %v\n", netName, err) + return + } + if amount <= 0 || fee < 0 { + _, _ = fmt.Fprintf(os.Stderr, "invalid amount(%d) or fee(%d)\n", amount, fee) + return + } + utxo, err := parseDepositUTXO(utxoRaw) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "invalid utxo: %v\n", err) + return + } + if utxo.amount < amount+fee { + _, _ = fmt.Fprintf(os.Stderr, "insufficient utxo amount, have=%d need=%d\n", utxo.amount, amount+fee) + return + } + + wif, err := btcutil.DecodeWIF(strings.TrimSpace(wifStr)) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "invalid wif: %v\n", err) + return + } + tssAddr, err := btcutil.DecodeAddress(strings.TrimSpace(tssAddrStr), params) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "invalid tssAddress: %v\n", err) + return + } + var changeAddr btcutil.Address + if strings.TrimSpace(changeAddrStr) == "" { + changeAddr, err = btcutil.NewAddressPubKeyHash( + btcutil.Hash160(wif.PrivKey.PubKey().SerializeCompressed()), params, + ) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "create default change address failed: %v\n", err) + return + } + } else { + changeAddr, err = btcutil.DecodeAddress(strings.TrimSpace(changeAddrStr), params) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "invalid changeAddress: %v\n", err) + return + } + } + + tx := wire.NewMsgTx(wire.TxVersion) + tx.AddTxIn(wire.NewTxIn(wire.NewOutPoint(utxo.hash, utxo.vout), nil, nil)) + + tssScript, err := txscript.PayToAddrScript(tssAddr) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "build tss script failed: %v\n", err) + return + } + tx.AddTxOut(wire.NewTxOut(amount, tssScript)) + + opData := []byte("rgbx:deposit:" + strings.TrimSpace(chain33Addr)) + opScript, err := txscript.NullDataScript(opData) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "build op_return failed: %v\n", err) + return + } + tx.AddTxOut(wire.NewTxOut(0, opScript)) + + change := utxo.amount - amount - fee + if change > 546 { + changeScript, err := txscript.PayToAddrScript(changeAddr) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "build change script failed: %v\n", err) + return + } + tx.AddTxOut(wire.NewTxOut(change, changeScript)) + } + + class := txscript.GetScriptClass(utxo.pkScript) + switch class { + case txscript.PubKeyHashTy: + sigScript, err := txscript.SignatureScript(tx, 0, utxo.pkScript, txscript.SigHashAll, wif.PrivKey, true) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "sign p2pkh failed: %v\n", err) + return + } + tx.TxIn[0].SignatureScript = sigScript + default: + _, _ = fmt.Fprintf(os.Stderr, "unsupported utxo script class: %s, only p2pkh supported now\n", class.String()) + return + } + + connCfg := &rpcclient.ConnConfig{ + Host: rpcHost, + User: rpcUser, + Pass: rpcPass, + HTTPPostMode: true, + DisableTLS: disableTLS, + } + if !disableTLS && strings.TrimSpace(rpcCertFile) != "" { + certs, err := os.ReadFile(strings.TrimSpace(rpcCertFile)) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "read rpc cert file failed: %v\n", err) + return + } + connCfg.Certificates = certs + } + rpcCli, err := rpcclient.New(connCfg, nil) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "create rpc client failed: %v\n", err) + return + } + defer rpcCli.Shutdown() + + txHash, err := rpcCli.SendRawTransaction(tx, false) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "broadcast tx failed: %v\n", err) + return + } + fmt.Println(txHash.String()) +} + +func btcKeyInfoCMD() *cobra.Command { + cmd := &cobra.Command{ + Use: "btcKeyInfo", + Short: "derive btc key info from private key hex", + Run: btcKeyInfo, + Example: "btcKeyInfo --net regtest --privHex 0000000000000000000000000000000000000000000000000000000000000001", + } + cmd.Flags().String("net", "regtest", "bitcoin network: mainnet|testnet|regtest|simnet") + cmd.Flags().String("privHex", "", "private key hex (32 bytes)") + markRequired(cmd, "privHex") + return cmd +} + +func btcKeyInfo(cmd *cobra.Command, _ []string) { + netName, _ := cmd.Flags().GetString("net") + privHex, _ := cmd.Flags().GetString("privHex") + + params, err := parseNetParams(netName) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "invalid net: %s, err: %v\n", netName, err) + return + } + keyBytes, err := hex.DecodeString(strings.TrimSpace(privHex)) + if err != nil || len(keyBytes) != 32 { + _, _ = fmt.Fprintf(os.Stderr, "invalid privHex, require 32-byte hex\n") + return + } + privKey, _ := btcec.PrivKeyFromBytes(keyBytes) + wif, err := btcutil.NewWIF(privKey, params, true) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "create wif failed: %v\n", err) + return + } + addr, err := btcutil.NewAddressPubKeyHash( + btcutil.Hash160(privKey.PubKey().SerializeCompressed()), params, + ) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "derive address failed: %v\n", err) + return + } + script, err := txscript.PayToAddrScript(addr) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "derive pkScript failed: %v\n", err) + return + } + + fmt.Printf("{\"wif\":\"%s\",\"address\":\"%s\",\"pkScript\":\"%s\"}\n", + wif.String(), addr.String(), hex.EncodeToString(script)) +} + +func parseNetParams(net string) (*chaincfg.Params, error) { + switch strings.ToLower(strings.TrimSpace(net)) { + case "mainnet", "main": + return &chaincfg.MainNetParams, nil + case "testnet", "testnet3", "test": + return &chaincfg.TestNet3Params, nil + case "regtest": + return &chaincfg.RegressionNetParams, nil + case "simnet": + return &chaincfg.SimNetParams, nil + default: + return nil, fmt.Errorf("unsupported net %q", net) + } +} + +func commitDKGCMD() *cobra.Command { + cmd := &cobra.Command{ + Use: "commitDKG", + Aliases: []string{"cdkg"}, + Short: "commit dkg address for cross-chain asset", + Run: commitDKG, + Example: "commitDKG -s BTC -d -p ", + } + commitDKGFlags(cmd) + return cmd +} + +func commitDKGFlags(cmd *cobra.Command) { + cmd.Flags().StringP("assetSymbol", "s", rtypes.BTCSymbol, "cross-chain asset symbol") + cmd.Flags().StringP("dkgAddress", "d", "", "dkg/tss bitcoin address") + cmd.Flags().StringP("pkScript", "p", "", "dkg address pkScript hex") + markRequired(cmd, "dkgAddress", "pkScript") +} + +func commitDKG(cmd *cobra.Command, _ []string) { + symbol, _ := cmd.Flags().GetString("assetSymbol") + dkgAddress, _ := cmd.Flags().GetString("dkgAddress") + pkScriptHex, _ := cmd.Flags().GetString("pkScript") + + pkScript, err := hex.DecodeString(pkScriptHex) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "invalid pkScript: %s, decode err: %v\n", pkScriptHex, err) + return + } + + sendCreateTxRPC(cmd, rtypes.NameCommitDKGAction, &rtypes.CommitDKG{ + AssetSymbol: symbol, + DkgAddress: dkgAddress, + PkScript: pkScript, + }) +} + +func depositAssetCMD() *cobra.Command { + cmd := &cobra.Command{ + Use: "deposit", + Aliases: []string{"dep"}, + Short: "submit btc deposit proof", + Run: depositAsset, + Example: "deposit -a 1000 -d --txData --blockHeight 1 --txIndex 0 --blockHash ", + } + depositAssetFlags(cmd) + return cmd +} + +func depositAssetFlags(cmd *cobra.Command) { + cmd.Flags().Int64P("amount", "a", 0, "deposit amount") + cmd.Flags().StringP("depositAddress", "d", "", "target chain33 address") + cmd.Flags().StringP("assetSymbol", "s", rtypes.BTCSymbol, "cross-chain asset symbol") + cmd.Flags().Uint64("blockHeight", 0, "btc proof block height") + cmd.Flags().Uint32("txIndex", 0, "btc tx index in block") + cmd.Flags().String("blockHash", "", "btc proof block hash") + cmd.Flags().String("txData", "", "btc raw transaction hex") + cmd.Flags().String("merkleProof", "", "comma-separated merkle proof hex list") + markRequired(cmd, "amount", "depositAddress", "blockHeight", "txIndex", "blockHash", "txData") +} + +func depositAsset(cmd *cobra.Command, _ []string) { + amount, _ := cmd.Flags().GetInt64("amount") + depositAddress, _ := cmd.Flags().GetString("depositAddress") + symbol, _ := cmd.Flags().GetString("assetSymbol") + blockHeight, _ := cmd.Flags().GetUint64("blockHeight") + txIndex, _ := cmd.Flags().GetUint32("txIndex") + blockHash, _ := cmd.Flags().GetString("blockHash") + txDataHex, _ := cmd.Flags().GetString("txData") + merkleProofStr, _ := cmd.Flags().GetString("merkleProof") + + txData, err := hex.DecodeString(txDataHex) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "invalid txData: %s, decode err: %v\n", txDataHex, err) + return + } + merkleProof, err := decodeHexList(merkleProofStr) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "invalid merkleProof: %s, decode err: %v\n", merkleProofStr, err) + return + } + + sendCreateTxRPC(cmd, rtypes.NameDepositAssetAction, &rtypes.DepositAsset{ + Amount: amount, + DepositAddress: depositAddress, + AssetSymbol: symbol, + TxProof: &rtypes.BtcTxProof{ + BlockHeight: blockHeight, + TxIndex: txIndex, + BlockHash: blockHash, + TxData: txData, + MerkleProof: merkleProof, + }, + }) +} + +func withdrawAssetCMD() *cobra.Command { + cmd := &cobra.Command{ + Use: "withdraw", + Aliases: []string{"wd"}, + Short: "withdraw cross-chain asset to btc address", + Run: withdrawAsset, + Example: "withdraw -a 1000 -f 10 -d -s BTC", + } + withdrawAssetFlags(cmd) + return cmd +} + +func withdrawAssetFlags(cmd *cobra.Command) { + cmd.Flags().Float64P("amount", "a", 0, "withdraw amount") + cmd.Flags().Int64P("feeRate", "f", 1, "btc fee rate (sat/vbyte)") + cmd.Flags().StringP("destinationAddr", "d", "", "btc destination address") + cmd.Flags().StringP("assetSymbol", "s", rtypes.BTCSymbol, "cross-chain asset symbol") + markRequired(cmd, "amount", "feeRate", "destinationAddr") +} + +func withdrawAsset(cmd *cobra.Command, _ []string) { + amount, _ := cmd.Flags().GetFloat64("amount") + feeRate, _ := cmd.Flags().GetInt64("feeRate") + destAddr, _ := cmd.Flags().GetString("destinationAddr") + symbol, _ := cmd.Flags().GetString("assetSymbol") + + amount = amount * math.Pow(10, 8) + sendCreateTxRPC(cmd, rtypes.NameWithdrawAssetAction, &rtypes.WithdrawAsset{ + Amount: int64(amount), + FeeRate: feeRate, + DestinationAddr: destAddr, + AssetSymbol: symbol, + }) +} + +func confirmTxCMD() *cobra.Command { + cmd := &cobra.Command{ + Use: "confirm", + Aliases: []string{"cf"}, + Short: "confirm pending rgbx tx", + Run: confirmTx, + Example: "confirm --actionType 106 --txBlockHeight 100 --txIndex 1 --txHash ", + } + confirmTxFlags(cmd) + return cmd +} + +func confirmTxFlags(cmd *cobra.Command) { + cmd.Flags().Int32("actionType", 0, "pending action type") + cmd.Flags().Int64("confirmedBlockHeight", 0, "confirmed btc block height") + cmd.Flags().Int64("txBlockHeight", 0, "pending tx block height") + cmd.Flags().Int64("txIndex", 0, "pending tx index") + cmd.Flags().String("txHash", "", "pending tx hash hex") + cmd.Flags().Bool("timeout", false, "whether confirm by timeout") + + cmd.Flags().Uint32("spendingInputIdx", 0, "btc spending tx input index") + cmd.Flags().Int32("opRetOutputIdx", -1, "btc op_return output index") + cmd.Flags().String("spendingTx", "", "btc spending tx raw hex") + cmd.Flags().String("opRetOutputPkScript", "", "op_return pkScript hex") + + cmd.Flags().Uint64("btcBlockHeight", 0, "btc proof block height") + cmd.Flags().Uint32("btcTxIndex", 0, "btc proof tx index") + cmd.Flags().String("btcBlockHash", "", "btc proof block hash") + cmd.Flags().String("btcTxData", "", "btc proof tx raw hex") + cmd.Flags().String("btcMerkleProof", "", "comma-separated merkle proof hex list") + markRequired(cmd, "actionType", "txBlockHeight", "txIndex", "txHash") +} + +func confirmTx(cmd *cobra.Command, _ []string) { + actionType, _ := cmd.Flags().GetInt32("actionType") + confirmedHeight, _ := cmd.Flags().GetInt64("confirmedBlockHeight") + txBlockHeight, _ := cmd.Flags().GetInt64("txBlockHeight") + txIndex, _ := cmd.Flags().GetInt64("txIndex") + txHashHex, _ := cmd.Flags().GetString("txHash") + timeout, _ := cmd.Flags().GetBool("timeout") + + spendingInputIdx, _ := cmd.Flags().GetUint32("spendingInputIdx") + opRetOutputIdx, _ := cmd.Flags().GetInt32("opRetOutputIdx") + spendingTxHex, _ := cmd.Flags().GetString("spendingTx") + opRetPkScriptHex, _ := cmd.Flags().GetString("opRetOutputPkScript") + + btcBlockHeight, _ := cmd.Flags().GetUint64("btcBlockHeight") + btcTxIndex, _ := cmd.Flags().GetUint32("btcTxIndex") + btcBlockHash, _ := cmd.Flags().GetString("btcBlockHash") + btcTxDataHex, _ := cmd.Flags().GetString("btcTxData") + btcMerkleProofStr, _ := cmd.Flags().GetString("btcMerkleProof") + + txHash, err := hex.DecodeString(txHashHex) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "invalid txHash: %s, decode err: %v\n", txHashHex, err) + return + } + + spendingTx, err := decodeHexOptional(spendingTxHex) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "invalid spendingTx: %s, decode err: %v\n", spendingTxHex, err) + return + } + opRetPkScript, err := decodeHexOptional(opRetPkScriptHex) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "invalid opRetOutputPkScript: %s, decode err: %v\n", opRetPkScriptHex, err) + return + } + btcTxData, err := decodeHexOptional(btcTxDataHex) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "invalid btcTxData: %s, decode err: %v\n", btcTxDataHex, err) + return + } + btcMerkleProof, err := decodeHexList(btcMerkleProofStr) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "invalid btcMerkleProof: %s, decode err: %v\n", btcMerkleProofStr, err) + return + } + + sendCreateTxRPC(cmd, rtypes.NameConfirmAction, &rtypes.ConfirmTx{ + ActionType: actionType, + ConfirmedBlockHeight: confirmedHeight, + TxBlockHeight: txBlockHeight, + TxIndex: txIndex, + TxHash: txHash, + Timeout: timeout, + UtxoProof: &rtypes.UtxoSpendingProof{ + SpendingInputIdx: spendingInputIdx, + OpRetOutputIdx: opRetOutputIdx, + SpendingTx: spendingTx, + OpRetOutputPkScript: opRetPkScript, + }, + BtcTxProof: &rtypes.BtcTxProof{ + BlockHeight: btcBlockHeight, + TxIndex: btcTxIndex, + BlockHash: btcBlockHash, + TxData: btcTxData, + MerkleProof: btcMerkleProof, + }, + }) +} + +func decodeHexOptional(s string) ([]byte, error) { + if strings.TrimSpace(s) == "" { + return nil, nil + } + return hex.DecodeString(s) +} + +func decodeHexList(raw string) ([][]byte, error) { + raw = strings.TrimSpace(raw) + if raw == "" { + return nil, nil + } + parts := strings.Split(raw, ",") + result := make([][]byte, 0, len(parts)) + for _, part := range parts { + part = strings.TrimSpace(part) + if part == "" { + continue + } + b, err := hex.DecodeString(part) + if err != nil { + return nil, err + } + result = append(result, b) + } + return result, nil +} diff --git a/plugin/dapp/rgbx/commands/commands.go b/plugin/dapp/rgbx/commands/commands.go new file mode 100644 index 0000000000..3314c88b25 --- /dev/null +++ b/plugin/dapp/rgbx/commands/commands.go @@ -0,0 +1,92 @@ +/*Package commands implement dapp client commands*/ +package commands + +import ( + "encoding/json" + jsonrpc "github.com/33cn/chain33/rpc/jsonclient" + rpctypes "github.com/33cn/chain33/rpc/types" + "github.com/33cn/chain33/types" + rtypes "github.com/33cn/plugin/plugin/dapp/rgbx/types" + "github.com/spf13/cobra" +) + +/* + * 实现合约对应客户端 + */ + +// Cmd rgbx client command +func Cmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "rgbx", + Short: "rgbx command", + Args: cobra.MinimumNArgs(1), + } + cmd.AddCommand( + mintAssetCMD(), + transferAssetCMD(), + commitDKGCMD(), + btcAddrScriptCMD(), + btcDepositTxCMD(), + btcKeyInfoCMD(), + depositAssetCMD(), + withdrawAssetCMD(), + confirmTxCMD(), + getAssetCMD(), + getCrossChainInfoCMD(), + getPendingTxCMD(), + listPendingTxCMD(), + listPendingTxByFromCMD(), + getConfirmedHeightCMD(), + ) + return cmd +} + +func sendQueryRPC(cmd *cobra.Command, funcName string, req, reply types.Message, runResult bool) { + rpcAddr, _ := cmd.Flags().GetString("rpc_laddr") + paraName, _ := cmd.Flags().GetString("paraName") + payLoad := types.MustPBToJSON(req) + query := &rpctypes.Query4Jrpc{ + Execer: types.GetExecName(rtypes.RgbxX, paraName), + FuncName: funcName, + Payload: payLoad, + } + + ctx := jsonrpc.NewRPCCtx(rpcAddr, "Chain33.Query", query, reply) + if runResult { + _, _ = ctx.RunResult() + return + } + ctx.SetResultCb(func(res interface{}) (interface{}, error) { + msg, ok := res.(types.Message) + if !ok { + return res, nil + } + resData, err := types.PBToJSONUTF8(msg) + if err != nil { + return res, nil + } + return json.RawMessage(resData), nil + }) + ctx.Run() +} + +func markRequired(cmd *cobra.Command, params ...string) { + for _, param := range params { + _ = cmd.MarkFlagRequired(param) + } +} + +func sendCreateTxRPC(cmd *cobra.Command, actionName string, req types.Message) { + rpcAddr, _ := cmd.Flags().GetString("rpc_laddr") + paraName, _ := cmd.Flags().GetString("paraName") + payLoad := types.MustPBToJSON(req) + pm := &rpctypes.CreateTxIn{ + Execer: types.GetExecName(rtypes.RgbxX, paraName), + ActionName: actionName, + Payload: payLoad, + } + + var res string + ctx := jsonrpc.NewRPCCtx(rpcAddr, "Chain33.CreateTransaction", pm, &res) + ctx.RunWithoutMarshal() +} diff --git a/plugin/dapp/rgbx/commands/createtx.go b/plugin/dapp/rgbx/commands/createtx.go new file mode 100644 index 0000000000..a8f5514878 --- /dev/null +++ b/plugin/dapp/rgbx/commands/createtx.go @@ -0,0 +1,171 @@ +package commands + +import ( + "encoding/hex" + "fmt" + "math" + "os" + "strconv" + "strings" + + "github.com/33cn/chain33/types" + rtypes "github.com/33cn/plugin/plugin/dapp/rgbx/types" + "github.com/spf13/cobra" +) + +func mintAssetCMD() *cobra.Command { + cmd := &cobra.Command{ + Use: "mint", + //Aliases: []string{"cg"}, + Short: "mint asset", + Run: mintAsset, + Example: "mint -t type -s symbol -a totalAmount -m metaHashHex -o genesisUtxo(hash:index:pkScript)", + } + mintAssetFlags(cmd) + return cmd +} + +func transferAssetCMD() *cobra.Command { + cmd := &cobra.Command{ + Use: "transfer", + //Aliases: []string{"cg"}, + Short: "transfer asset", + Run: transferAsset, + Example: "transfer", + } + transferAssetFlags(cmd) + return cmd +} + +func mintAssetFlags(cmd *cobra.Command) { + cmd.Flags().Uint32P("type", "t", 0, "asset type") + cmd.Flags().Int64P("totalAmount", "a", 10000, "asset total amount") + cmd.Flags().StringP("symbol", "s", "", "asset symbol") + cmd.Flags().StringP("metaHash", "m", "", "asset metaData or human-readable identifier hash") + cmd.Flags().StringP("genesisOut", "o", "", "genesis utxo for binding, hash:index:scriptPubkey format") + cmd.Flags().Int32P("precision", "p", -1, "asset precision length") + markRequired(cmd, "totalAmount", "symbol", "genesisOut") +} + +func mintAsset(cmd *cobra.Command, args []string) { + + symbol, _ := cmd.Flags().GetString("symbol") + metaHashStr, _ := cmd.Flags().GetString("metaHash") + genesisOutStr, _ := cmd.Flags().GetString("genesisOut") + totalAmount, _ := cmd.Flags().GetInt64("totalAmount") + ty, _ := cmd.Flags().GetUint32("type") + precision, _ := cmd.Flags().GetInt32("precision") + + if symbol == "" || len(symbol) > rtypes.MaxAssetSymbolLength { + _, _ = fmt.Fprintf(os.Stderr, "invalid asset symbol: %s, "+ + "length must less than %d\n", symbol, rtypes.MaxAssetSymbolLength) + return + } + + metaHash, err := hex.DecodeString(metaHashStr) + if err != nil || len(metaHash) > rtypes.MetaHashLen { + _, _ = fmt.Fprintf(os.Stderr, "invalid meta hash: %s, decode err:%s", metaHashStr, err) + return + } + + if precision < 0 || precision > 8 { + precision = 8 + } + + totalAmount = totalAmount * int64(math.Pow(10, float64(precision))) + + if totalAmount < 1 || + totalAmount > rtypes.MaxAssetAmount { + _, _ = fmt.Fprintf(os.Stderr, "invalid total amount: %d, overflow", totalAmount) + return + } + + strs := strings.Split(genesisOutStr, ":") + if len(strs) != 3 { + _, _ = fmt.Fprintf(os.Stderr, "invalid genesis out: %s, must hash:index:pkScript format", genesisOutStr) + return + } + + hash := strs[0] + pkScript, err1 := hex.DecodeString(strs[2]) + index, err2 := strconv.ParseUint(strs[1], 10, 32) + + if err1 != nil || err2 != nil { + _, _ = fmt.Fprintf(os.Stderr, "invalid genesis out: %s", genesisOutStr) + return + } + + mint := &rtypes.MintAsset{ + Symbol: symbol, + TotalAmount: totalAmount, + Precision: uint32(precision), + MetaHash: metaHash, + Type: ty, + GenesisOut: &rtypes.OutPoint{ + Hash: hash, + Index: uint32(index), + PkScript: pkScript, + }, + } + + sendCreateTxRPC(cmd, rtypes.NameMintAction, mint) +} + +func transferAssetFlags(cmd *cobra.Command) { + + cmd.Flags().Float64P("amount", "a", 1, "asset amount") + cmd.Flags().StringP("symbol", "s", "", "asset symbol") + cmd.Flags().StringP("from", "f", "", "from address, hash:index format for utxo, use sign address if not set") + cmd.Flags().StringP("to", "t", "", "to address, hash:index format for utxo") + cmd.Flags().StringP("pkScript", "p", "", "from pkScript( set only when from is an utxo)") + cmd.Flags().StringP("change", "c", "", "to address, hash:index format for utxo") + markRequired(cmd, "amount", "symbol", "to") +} + +func transferAsset(cmd *cobra.Command, args []string) { + + symbol, _ := cmd.Flags().GetString("symbol") + from, _ := cmd.Flags().GetString("from") + to, _ := cmd.Flags().GetString("to") + change, _ := cmd.Flags().GetString("change") + amount, _ := cmd.Flags().GetFloat64("amount") + pkScriptStr, _ := cmd.Flags().GetString("pkScript") + + if symbol == "" || len(symbol) > rtypes.MaxAssetSymbolLength { + _, _ = fmt.Fprintf(os.Stderr, "invalid asset symbol: %s, "+ + "length must less than %d\n", symbol, rtypes.MaxAssetSymbolLength) + return + } + + precision := 8 + req := &types.ReqString{ + Data: symbol, + } + reply := &rtypes.RgbxAsset{} + sendQueryRPC(cmd, "GetAsset", req, reply, true) + if reply.GetSymbol() != "" { + precision = int(reply.Precision) + } + + amount = amount * math.Pow(10, float64(precision)) + if amount < 1 || + amount > rtypes.MaxAssetAmount { + _, _ = fmt.Fprintf(os.Stderr, "invalid amount: %f, overflow", amount) + return + } + pkScript, err := hex.DecodeString(pkScriptStr) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "invalid pkScript: %s, decode err: %s", pkScriptStr, err) + return + } + + transfer := &rtypes.TransferAsset{ + Symbol: symbol, + Amount: int64(amount), + FromUtxo: from, + To: to, + ChangeAddr: change, + FromUtxoPkScript: pkScript, + } + sendCreateTxRPC(cmd, rtypes.NameTransferAction, transfer) +} diff --git a/plugin/dapp/rgbx/commands/query.go b/plugin/dapp/rgbx/commands/query.go new file mode 100644 index 0000000000..8d0a08a43e --- /dev/null +++ b/plugin/dapp/rgbx/commands/query.go @@ -0,0 +1,180 @@ +package commands + +import ( + "fmt" + "os" + + "github.com/33cn/chain33/types" + rtypes "github.com/33cn/plugin/plugin/dapp/rgbx/types" + "github.com/spf13/cobra" +) + +func listPendingTxCMD() *cobra.Command { + cmd := &cobra.Command{ + Use: "listPend", + Aliases: []string{"lp"}, + Short: "list pending tx", + Run: listPending, + Example: "listPend -s startHeight -i startIndex -c count", + } + listPendingFlags(cmd) + return cmd +} + +func listPendingFlags(cmd *cobra.Command) { + + cmd.Flags().Uint64P("startHeight", "s", 0, "list start Height") + cmd.Flags().Uint64P("startIndex", "i", 0, "list start tx index") + cmd.Flags().Uint32P("count", "c", 1, "list tx count") +} + +func listPending(cmd *cobra.Command, _ []string) { + + startHeight, _ := cmd.Flags().GetUint64("startHeight") + startIndex, _ := cmd.Flags().GetUint64("startIndex") + count, _ := cmd.Flags().GetUint32("count") + + req := &rtypes.ReqListPendingTx{ + StartHeight: int64(startHeight), + StartIndex: int64(startIndex), + Count: int32(count), + } + reply := &rtypes.PendingTxs{} + sendQueryRPC(cmd, "ListPendingTx", req, reply, false) +} + +func getPendingTxCMD() *cobra.Command { + cmd := &cobra.Command{ + Use: "getPend", + Aliases: []string{"gp"}, + Short: "get pending tx by height", + Run: getPending, + Example: "getPend -h height -i index", + } + getPendingFlags(cmd) + return cmd +} + +func getPendingFlags(cmd *cobra.Command) { + + cmd.Flags().Uint64P("height", "h", 0, "block height") + cmd.Flags().Uint64P("index", "i", 0, "tx index") +} + +func getPending(cmd *cobra.Command, _ []string) { + + height, _ := cmd.Flags().GetUint64("height") + index, _ := cmd.Flags().GetUint64("index") + + req := &rtypes.ReqGetPendingTx{ + Height: int64(height), + Index: int64(index), + } + reply := &rtypes.PendingTx{} + sendQueryRPC(cmd, "GetPendingTx", req, reply, false) +} + +func getAssetCMD() *cobra.Command { + cmd := &cobra.Command{ + Use: "getAsset", + Aliases: []string{"ga"}, + Short: "get rgbx asset", + Run: getAsset, + Example: "getAsset -s symbol", + } + getAssetFlags(cmd) + return cmd +} + +func getAssetFlags(cmd *cobra.Command) { + + cmd.Flags().StringP("symbol", "s", "", "asset symbol") + markRequired(cmd, "symbol") +} + +func getAsset(cmd *cobra.Command, _ []string) { + + symbol, _ := cmd.Flags().GetString("symbol") + if symbol == "" || len(symbol) > rtypes.MaxAssetSymbolLength { + _, _ = fmt.Fprintf(os.Stderr, "invalid asset symbol: %s, "+ + "length must less than %d\n", symbol, rtypes.MaxAssetSymbolLength) + return + } + + req := &types.ReqString{ + Data: symbol, + } + reply := &rtypes.RgbxAsset{} + sendQueryRPC(cmd, "GetAsset", req, reply, false) +} + +func getConfirmedHeightCMD() *cobra.Command { + cmd := &cobra.Command{ + Use: "getConfirmedHeight", + Aliases: []string{"gch"}, + Short: "get rgbx confirmed height", + Run: getConfirmedHeight, + Example: "getConfirmedHeight", + } + return cmd +} + +func getConfirmedHeight(cmd *cobra.Command, _ []string) { + + reply := &types.Int64{} + sendQueryRPC(cmd, "GetConfirmedHeight", &types.ReqNil{}, reply, false) +} + +func getCrossChainInfoCMD() *cobra.Command { + cmd := &cobra.Command{ + Use: "getCross", + Aliases: []string{"gc"}, + Short: "get cross-chain info", + Run: getCrossChainInfo, + Example: "getCross -s BTC", + } + getCrossChainInfoFlags(cmd) + return cmd +} + +func getCrossChainInfoFlags(cmd *cobra.Command) { + cmd.Flags().StringP("symbol", "s", "BTC", "cross-chain asset symbol") + markRequired(cmd, "symbol") +} + +func getCrossChainInfo(cmd *cobra.Command, _ []string) { + symbol, _ := cmd.Flags().GetString("symbol") + if symbol == "" || len(symbol) > rtypes.MaxAssetSymbolLength { + _, _ = fmt.Fprintf(os.Stderr, "invalid asset symbol: %s, length must less than %d\n", symbol, rtypes.MaxAssetSymbolLength) + return + } + reply := &rtypes.CrossChainInfo{} + sendQueryRPC(cmd, "GetCrossChainInfo", &types.ReqString{Data: symbol}, reply, false) +} + +func listPendingTxByFromCMD() *cobra.Command { + cmd := &cobra.Command{ + Use: "listPendByFrom", + Aliases: []string{"lpf"}, + Short: "list pending tx by from address", + Run: listPendingByFrom, + Example: "listPendByFrom -f 1xxxxxxxxxxxxxxxx", + } + listPendingTxByFromFlags(cmd) + return cmd +} + +func listPendingTxByFromFlags(cmd *cobra.Command) { + cmd.Flags().StringP("from", "f", "", "from address") + markRequired(cmd, "from") +} + +func listPendingByFrom(cmd *cobra.Command, _ []string) { + from, _ := cmd.Flags().GetString("from") + if from == "" { + _, _ = fmt.Fprintln(os.Stderr, "from address is required") + return + } + reply := &rtypes.PendingTxs{} + sendQueryRPC(cmd, "ListPendingTxByFrom", &types.ReqString{Data: from}, reply, false) +} diff --git a/plugin/dapp/rgbx/executor/account.go b/plugin/dapp/rgbx/executor/account.go new file mode 100644 index 0000000000..87b9788eb4 --- /dev/null +++ b/plugin/dapp/rgbx/executor/account.go @@ -0,0 +1,14 @@ +package executor + +import ( + "github.com/33cn/chain33/account" + rtypes "github.com/33cn/plugin/plugin/dapp/rgbx/types" +) + +func (r *rgbx) newAccount(symbol string) (*account.DB, error) { + return account.NewAccountDB(r.GetAPI().GetConfig(), rtypes.RgbxX, formatSymbol(symbol), r.GetStateDB()) +} + +func (r *rgbx) crossChainLockAddress(accDB *account.DB) string { + return accDB.ExecAddress(rtypes.RgbxX + "-crosschain-lock") +} diff --git a/plugin/dapp/rgbx/executor/checktx.go b/plugin/dapp/rgbx/executor/checktx.go new file mode 100644 index 0000000000..df2cef6e5b --- /dev/null +++ b/plugin/dapp/rgbx/executor/checktx.go @@ -0,0 +1,409 @@ +package executor + +import ( + "bytes" + "encoding/hex" + "errors" + "strings" + + "github.com/33cn/chain33/common/address" + "github.com/33cn/chain33/types" + rtypes "github.com/33cn/plugin/plugin/dapp/rgbx/types" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" +) + +var ( + ErrInvalidSymbolLength = errors.New("invalid asset symbol length") + ErrInvalidAssetAmount = errors.New("invalid asset amount") + ErrInvalidMetaHashLength = errors.New("invalid meta hash length") + ErrNilGenesisOut = errors.New("nil genesis output") + ErrDuplicateAssetSymbol = errors.New("duplicate asset symbol") + ErrAssetNotExist = errors.New("asset not exist") + ErrDecodeBtcTx = errors.New("decode btc tx error") + ErrPendingTxNotExist = errors.New("pending tx not exist") + ErrConfirmPayloadNotExist = errors.New("confirm payload not exist") + ErrTxAlreadyConfirmed = errors.New("tx already confirmed") + ErrConfirmedHashNotEqual = errors.New("confirmed hash not equal") + ErrSpendingInputNotEqual = errors.New("spending input not equal") + ErrOpRetOutputPkScriptNotEqual = errors.New("ErrOpRetOutputPkScriptNotEqual") + ErrInvalidCommitAddress = errors.New("ErrInvalidCommitAddress") + ErrFromUtxoPkScriptNotSet = errors.New("ErrFromUtxoPkScriptNotSet") + ErrInvalidAssetPrecision = errors.New("ErrInvalidAssetPrecision") + ErrInvalidAssetSender = errors.New("ErrInvalidAssetSender") + ErrInvalidFromUtxo = errors.New("invalid from utxo") + ErrInvalidSpendingTxIn = errors.New("ErrInvalidSpendingTxIn") + ErrInvalidWithdrawAmount = errors.New("invalid withdraw amount") + ErrInvalidWithdrawDestination = errors.New("invalid withdraw destination") + ErrInvalidWithdrawDestinationScript = errors.New("invalid withdraw destination script") + ErrInvalidDepositAmount = errors.New("invalid deposit amount") + ErrInvalidDepositAddress = errors.New("invalid deposit address") + ErrInvalidDepositCommitment = errors.New("invalid deposit opreturn commitment") + ErrInvalidWithdrawFeeRate = errors.New("invalid withdraw fee rate") + ErrInvalidAssetSymbol = errors.New("invalid asset symbol") + ErrInvalidBtcTxProof = errors.New("invalid btc tx proof") + ErrWithdrawConfirmTimeoutNotAllowed = errors.New("withdraw confirm timeout not allowed") + ErrInvalidBtcProofIndex = errors.New("invalid btc proof tx index") + ErrInvalidBtcBlockHash = errors.New("invalid btc block hash") + ErrGetBtcHeader = errors.New("get btc header error") + ErrInvalidBtcProofBlock = errors.New("invalid btc proof block info") + ErrInvalidBtcProofCommitment = errors.New("invalid btc withdraw commitment") + ErrInvalidBtcProofMerkle = errors.New("invalid btc merkle proof") + ErrCalcBtcMerkleRoot = errors.New("calc btc merkle root error") + ErrInvalidCrossChainInfo = errors.New("invalid cross chain info") + ErrNewAccountDB = errors.New("new account db error") + ErrGetCrossChainInfo = errors.New("get cross chain info error") + ErrDuplicateDepositProof = errors.New("duplicate deposit proof") + ErrInvalidGuardianCommitter = errors.New("invalid guardian committer") + ErrDuplicateDKGCommit = errors.New("duplicate dkg commit") + ErrGetGuardianNodeAddress = errors.New("get guardian node address error") + ErrGetDkgConfirmations = errors.New("get dkg confirmations error") + ErrInvalidDkgAddress = errors.New("invalid dkg address") +) + +const ( + maxBtcFeeRate = int64(1000) + minBtcWithdrawAmount = int64(546) +) + +// CheckTx 实现自定义检验交易接口,供框架调用 +func (r *rgbx) CheckTx(tx *types.Transaction, index int) error { + + txHash := hex.EncodeToString(tx.Hash()) + action := &rtypes.RgbxAction{} + err := types.Decode(tx.GetPayload(), action) + if err != nil { + elog.Error("CheckTx", "txHash", txHash, "Decode payload error", err) + return types.ErrActionNotSupport + } + + switch action.Ty { + case rtypes.TyMintAction: + err = r.checkMint(txHash, action.GetMint()) + case rtypes.TyTransferAction: + err = r.checkTransfer(tx, txHash, action.GetTransfer()) + case rtypes.TyCommitDKGAction: + err = r.checkCommitDKG(txHash, tx.From(), action.GetCommitDKG()) + case rtypes.TyDepositAsset: + err = r.checkDeposit(txHash, action.GetDeposit()) + case rtypes.TyWithDrawAsset: + err = r.checkWithdraw(tx.From(), txHash, action.GetWithdraw()) + case rtypes.TyConfirmAction: + err = r.checkConfirm(tx.From(), txHash, action.GetConfirm()) + default: + err = types.ErrActionNotSupport + + } + if err != nil { + elog.Error("rgbx CheckTx", "txHash", txHash, "actionName", tx.ActionName(), + "err", err, "action", string(types.MustPBToJSON(action))) + } + return err +} + +func (r *rgbx) checkMint(txHash string, mint *rtypes.MintAsset) error { + + if len(mint.GetSymbol()) < 1 || len(mint.GetSymbol()) > rtypes.MaxAssetSymbolLength { + elog.Error("checkMint", "txHash", txHash, + "symbol", mint.Symbol, "symbolLen", len(mint.GetSymbol())) + return ErrInvalidSymbolLength + } + + if isCrossChainSymbol(mint.GetSymbol()) { + return ErrInvalidAssetSymbol + } + + ty := rtypes.AssetType(mint.GetType()) + if mint.GetTotalAmount() <= 0 || mint.GetTotalAmount() > rtypes.MaxAssetAmount || + (ty == rtypes.Collectible && mint.GetTotalAmount() != 1) { + elog.Error("checkMint", "txHash", txHash, "symbol", mint.Symbol, + "amount", mint.GetTotalAmount(), "type", ty.String()) + return ErrInvalidAssetAmount + } + if ty != rtypes.Collectible && mint.GetPrecision() > rtypes.MaxPrecision { + elog.Error("checkMint", "txHash", txHash, "symbol", mint.Symbol, + "precision", mint.GetPrecision(), "maxPrecision", rtypes.MaxPrecision) + return ErrInvalidAssetPrecision + } + + if len(mint.GetMetaHash()) > rtypes.MetaHashLen { + elog.Error("checkMint", "txHash", txHash, "symbol", mint.Symbol, + "metaHashLen", len(mint.GetMetaHash())) + return ErrInvalidMetaHashLength + } + + _, err := r.GetStateDB().Get(formatAssetKey(mint.GetSymbol())) + if !errors.Is(err, types.ErrNotFound) { + elog.Error("checkMint duplicate asset", "txHash", txHash, "symbol", mint.Symbol) + return ErrDuplicateAssetSymbol + } + + if mint.GetGenesisOut().GetHash() == "" || mint.GetGenesisOut().GetPkScript() == nil { + elog.Error("checkMint invalid genesis out", "txHash", txHash, "symbol", mint.Symbol) + return ErrNilGenesisOut + } + + return nil +} + +func (r *rgbx) checkTransfer(tx *types.Transaction, txHash string, transfer *rtypes.TransferAsset) error { + + if transfer.GetAmount() <= 0 { + elog.Error("checkTransfer amount", "txHash", txHash, "symbol", transfer.GetSymbol(), "amount", transfer.GetAmount()) + return ErrInvalidAssetAmount + } + fromAddr := tx.From() + if isCrossChainSymbol(transfer.GetSymbol()) { + return r.checkCrossChainTransfer(txHash, fromAddr, transfer) + } + fromUtxo := transfer.GetFromUtxo() + if fromUtxo != "" { + if !rtypes.IsUtxoAddress(fromUtxo) || len(transfer.GetFromUtxoPkScript()) == 0 { + elog.Error("checkTransfer invalid fromUtxo", "txHash", txHash, "symbol", transfer.GetSymbol(), "fromUtxo", fromUtxo) + return ErrInvalidFromUtxo + } + fromAddr = fromUtxo + } + if address.CheckAddress(transfer.GetTo(), -1) != nil || + (transfer.GetChangeAddr() != "" && address.CheckAddress(transfer.GetChangeAddr(), -1) != nil) { + elog.Error("checkTransfer address", "txHash", txHash, "symbol", transfer.GetSymbol(), + "from", fromAddr, "to", transfer.GetTo(), "changeAddr", transfer.GetChangeAddr()) + return types.ErrInvalidAddress + } + + asset := &rtypes.RgbxAsset{} + err := readDB(r.GetStateDB(), formatAssetKey(transfer.GetSymbol()), asset) + if err != nil { + elog.Error("checkTransfer get asset", "txHash", txHash, "symbol", transfer.GetSymbol(), + "err", err) + return ErrAssetNotExist + } + + assetTy := rtypes.AssetType(asset.GetType()) + if assetTy == rtypes.Normal { + accDb, err := r.newAccount(transfer.GetSymbol()) + if err != nil { + elog.Error("checkTransfer newAccount", "txHash", txHash, "symbol", transfer.GetSymbol(), "err", err) + return ErrNewAccountDB + } + balance := accDb.LoadAccount(fromAddr).GetBalance() + if balance < transfer.GetAmount() { + elog.Error("checkTransfer insufficient balance", "txHash", txHash, "from", fromAddr, + "symbol", transfer.GetSymbol(), "need", transfer.GetAmount(), "balance", balance) + return types.ErrInsufficientBalance + } + } else if fromAddr != asset.Owner { + elog.Error("checkTransfer invalid owner", "txHash", txHash, "symbol", transfer.GetSymbol(), + "from", fromAddr, "assetOwner", asset.Owner) + return ErrInvalidAssetSender + } + return nil +} + +func (r *rgbx) checkCrossChainTransfer(txHash, fromAddr string, transfer *rtypes.TransferAsset) error { + + accDB, err := r.newAccount(transfer.GetSymbol()) + if err != nil { + elog.Error("checkCrossChainTransfer newCrossChainAccount", "txHash", txHash, "symbol", transfer.GetSymbol(), "err", err) + return ErrNewAccountDB + } + if accDB.LoadAccount(fromAddr).GetBalance() < transfer.GetAmount() { + elog.Error("checkCrossChainTransfer insufficient balance", "txHash", txHash, "from", fromAddr, + "symbol", transfer.GetSymbol(), "need", transfer.GetAmount()) + return types.ErrInsufficientBalance + } + return nil +} + +func (r *rgbx) checkCommitDKG(txHash, fromAddr string, commitDKG *rtypes.CommitDKG) error { + + symbol := commitDKG.GetAssetSymbol() + pkScript, err := r.decodeBtcAddressScript(commitDKG.GetDkgAddress()) + if err != nil || !bytes.Equal(pkScript, commitDKG.GetPkScript()) { + elog.Error("checkCommitDKG decode btc address script", "txHash", txHash, + "symbol", symbol, "dkgAddress", commitDKG.GetDkgAddress(), + "pkScript", hex.EncodeToString(commitDKG.GetPkScript()), "expectPkScript", hex.EncodeToString(pkScript), "err", err) + return ErrInvalidDkgAddress + } + guardianAddrs, err := r.getGuardianNodeAddress(rgbxCfg.GuardianParachainTitle) + if err != nil { + elog.Error("checkCommitDKG getGuardianNodeAddress", "txHash", txHash, "symbol", symbol, "err", err) + return ErrGetGuardianNodeAddress + } + if !strings.Contains(guardianAddrs, fromAddr) { + elog.Error("checkCommitDKG invalid committer", "txHash", txHash, "symbol", symbol, "fromAddr", fromAddr) + return ErrInvalidGuardianCommitter + } + + _, err = r.GetStateDB().Get(formatCrossChainInfoKey(symbol)) + if err == nil { + elog.Error("checkCommitDKG duplicate cross chain info", "txHash", txHash, "symbol", symbol) + return ErrDuplicateDKGCommit + } + return nil +} + +func (r *rgbx) checkWithdraw(fromAddr, txHash string, withdraw *rtypes.WithdrawAsset) error { + + symbol := ensureCrossChainSymbol(withdraw.GetAssetSymbol()) + if withdraw.GetAmount() < minBtcWithdrawAmount { + elog.Error("checkWithdraw amount", "txHash", txHash, "amount", withdraw.GetAmount()) + return ErrInvalidWithdrawAmount + } + + if _, err := r.decodeBtcAddressScript(withdraw.GetDestinationAddr()); err != nil { + elog.Error("checkWithdraw invalid btc destination", "txHash", txHash, "address", withdraw.GetDestinationAddr(), "err", err) + return ErrInvalidWithdrawDestination + } + if withdraw.GetFeeRate() < 1 || withdraw.GetFeeRate() > maxBtcFeeRate { + elog.Error("checkWithdraw feeRate", "txHash", txHash, "feeRate", withdraw.GetFeeRate()) + return ErrInvalidWithdrawFeeRate + } + accDB, err := r.newAccount(symbol) + if err != nil { + elog.Error("checkWithdraw newAccount", "txHash", txHash, "symbol", withdraw.GetAssetSymbol(), "err", err) + return err + } + balance := accDB.LoadAccount(fromAddr).GetBalance() + if balance < withdraw.GetAmount() { + elog.Error("checkWithdraw insufficient balance", "txHash", txHash, "from", fromAddr, + "symbol", symbol, "need", withdraw.GetAmount(), "balance", balance) + return types.ErrInsufficientBalance + } + return nil +} + +func (r *rgbx) checkDeposit(txHash string, deposit *rtypes.DepositAsset) error { + if deposit.GetAmount() <= 0 { + elog.Error("checkDeposit amount", "txHash", txHash, "amount", deposit.GetAmount()) + return ErrInvalidDepositAmount + } + addr := deposit.GetDepositAddress() + if addr == "" || (!rtypes.IsUtxoAddress(addr) && address.CheckAddress(addr, -1) != nil) { + elog.Error("checkDeposit address invalid", "txHash", txHash, "address", addr) + return ErrInvalidDepositAddress + } + _, err := r.GetStateDB().Get(formatDepositUsedKey(deposit.GetTxProof().GetTxData())) + if !errors.Is(err, types.ErrNotFound) { + elog.Error("checkDeposit duplicate proof", "txHash", txHash, "symbol", deposit.GetAssetSymbol(), "err", err) + return ErrDuplicateDepositProof + } + btcTx, err := r.validateBtcTxProof(txHash, deposit.GetTxProof()) + if err != nil { + elog.Error("checkDeposit validate btc tx proof", "txHash", txHash, "btcProof", btcProof2String(deposit.GetTxProof()), "err", err) + return err + } + if !hasDepositCommitment(btcTx, addr) { + elog.Error("checkDeposit commitment mismatch", "txHash", txHash, "depositAddress", addr) + return ErrInvalidDepositCommitment + } + if err = r.validateDepositTxContent(txHash, deposit, btcTx); err != nil { + return err + } + return nil +} + +func (r *rgbx) checkConfirm(fromAddr, txHash string, confirm *rtypes.ConfirmTx) error { + + confirmTxHash := hex.EncodeToString(confirm.TxHash) + action := rtypes.GetActionName(confirm.GetActionType()) + + if rgbxCfg.CommitAddress != "" && fromAddr != rgbxCfg.CommitAddress { + elog.Error("checkConfirm fromAddr", "action", action, + "txHash", txHash, "confirmTxHash", confirmTxHash, + "fromAddr", fromAddr, "commitAddr", rgbxCfg.CommitAddress) + return ErrInvalidCommitAddress + } + + pendingTx := &rtypes.PendingTx{} + err := readDB(r.GetLocalDB(), formatPendingTxKey(confirm.TxBlockHeight, confirm.TxIndex), pendingTx) + if err != nil { + elog.Error("checkConfirm read pending tx", "action", action, + "txHash", txHash, "confirmTxHash", confirmTxHash, + "height", confirm.TxBlockHeight, "index", confirm.TxIndex, "err", err) + return ErrPendingTxNotExist + } + + if pendingTx.Confirmed { + elog.Error("checkConfirm tx already confirmed", "action", action, + "txHash", txHash, "confirmTxHash", confirmTxHash) + return ErrTxAlreadyConfirmed + } + + _, err = r.GetStateDB().Get(formatPayloadKey(confirm.GetTxHash())) + if err != nil { + elog.Error("checkConfirm get payload", "action", action, + "txHash", txHash, "confirmTxHash", confirmTxHash, "err", err) + return ErrConfirmPayloadNotExist + } + if !bytes.Equal(confirm.GetTxHash(), pendingTx.GetTxHash()) { + elog.Error("checkConfirm tx hash not equal", "action", action, + "txHash", txHash, "confirmTxHash", confirmTxHash, + "expectConfirmHash", hex.EncodeToString(pendingTx.GetTxHash())) + return ErrConfirmedHashNotEqual + } + + if confirm.GetActionType() == rtypes.TyWithDrawAsset { + if confirm.Timeout { + elog.Error("checkConfirm timeout not supported for withdraw", "action", action, + "txHash", txHash, "confirmTxHash", confirmTxHash) + return ErrWithdrawConfirmTimeoutNotAllowed + } + return r.checkWithdrawConfirm(txHash, confirmTxHash, confirm, pendingTx) + } + + if confirm.Timeout { + elog.Debug("checkConfirm timeout", "action", action, + "txHash", txHash, "confirmTxHash", confirmTxHash) + return nil + } + + btcSpendHash := chainhash.DoubleHashH(confirm.GetUtxoProof().GetSpendingTx()).String() + spendingTx := wire.MsgTx{} + err = spendingTx.DeserializeNoWitness(bytes.NewReader(confirm.GetUtxoProof().GetSpendingTx())) + if err != nil { + elog.Error("checkConfirm decode spending tx", "action", action, + "txHash", txHash, "confirmTxHash", hex.EncodeToString(confirm.GetTxHash()), + "btcSpendingTx", hex.EncodeToString(confirm.GetUtxoProof().GetSpendingTx()), + "decode err", err) + return ErrDecodeBtcTx + } + + spendingInputIdx := int(confirm.GetUtxoProof().GetSpendingInputIdx()) + if spendingInputIdx >= len(spendingTx.TxIn) { + elog.Error("checkConfirm spending tx input", "action", action, + "txHash", txHash, "confirmTxHash", hex.EncodeToString(confirm.GetTxHash()), + "inputIdx", spendingInputIdx, "txInLen", len(spendingTx.TxIn), "btcSpendHash", btcSpendHash) + return ErrInvalidSpendingTxIn + } + + // check input + expectInput := pendingTx.Utxo.ToString() + actualInput := spendingTx.TxIn[int(confirm.GetUtxoProof().GetSpendingInputIdx())].PreviousOutPoint.String() + if expectInput != actualInput { + elog.Error("checkConfirm input utxo not equal", "action", action, + "txHash", txHash, "confirmTxHash", hex.EncodeToString(confirm.GetTxHash()), + "expectInput", expectInput, "actualInput", actualInput, "btcSpendHash", btcSpendHash) + return ErrSpendingInputNotEqual + } + + opRetOutIdx := int(confirm.GetUtxoProof().GetOpRetOutputIdx()) + // 表示op_return输出不存在,即utxo已经在btc链花费, 但没有构建rgbx所约束的op_return输出 + if opRetOutIdx < 0 || opRetOutIdx >= len(spendingTx.TxOut) { + elog.Debug("checkConfirm opReturn output not exist", + "action", action, "txHash", txHash, + "confirmTxHash", confirmTxHash, "btcSpendHash", btcSpendHash) + return nil + } + + // 提供的op_return pkScript参数非法,和btc原始交易中的输出不符 + if !bytes.Equal(confirm.GetUtxoProof().OpRetOutputPkScript, + spendingTx.TxOut[int(confirm.GetUtxoProof().GetOpRetOutputIdx())].PkScript) { + elog.Error("checkConfirm opReturn pkScript not equal", + "action", action, "txHash", txHash, + "confirmTxHash", confirmTxHash, "btcSpendHash", btcSpendHash) + return ErrOpRetOutputPkScriptNotEqual + } + + return nil +} diff --git a/plugin/dapp/rgbx/executor/checktx_test.go b/plugin/dapp/rgbx/executor/checktx_test.go new file mode 100644 index 0000000000..9de2a47f31 --- /dev/null +++ b/plugin/dapp/rgbx/executor/checktx_test.go @@ -0,0 +1,522 @@ +package executor + +import ( + "bytes" + "errors" + "fmt" + "strings" + "testing" + + "github.com/33cn/chain33/client/mocks" + "github.com/33cn/chain33/common/crypto" + "github.com/33cn/chain33/system/dapp" + "github.com/33cn/chain33/types" + "github.com/33cn/chain33/util" + "github.com/33cn/plugin/plugin/dapp/lightclient/lighttypes" + ltypes "github.com/33cn/plugin/plugin/dapp/lightclient/lighttypes" + paratypes "github.com/33cn/plugin/plugin/dapp/paracross/types" + rtypes "github.com/33cn/plugin/plugin/dapp/rgbx/types" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +var testCommitAddr string +var testPriv crypto.PrivKey +var testCfg *types.Chain33Config + +func init() { + testCommitAddr, testPriv = util.Genaddress() + rgbxCfg.CommitAddress = testCommitAddr + testCfg = types.NewChain33Config(types.GetDefaultCfgstring()) + Init(driverName, testCfg, nil) +} + +type testCase struct { + action types.Message + expectErr error +} + +func Test_CheckTx(t *testing.T) { + + r := newRgbx() + + action := &rtypes.RgbxAction{} + tx := &types.Transaction{Payload: []byte("testdata")} + require.Equal(t, types.ErrActionNotSupport, r.CheckTx(tx, 0)) + + tx.Payload = types.Encode(action) + require.Equal(t, types.ErrActionNotSupport, r.CheckTx(tx, 0)) +} + +func testCheck(t *testing.T, driver dapp.Driver, tx *types.Transaction, action types.Message, expectErr error, idx int) { + + tx.Payload = types.Encode(action) + require.Equalf(t, expectErr, driver.CheckTx(tx, 0), "testcase: %d", idx) +} + +func Test_checkMint(t *testing.T) { + + r := newRgbx() + action := &rtypes.RgbxAction{} + action.Ty = rtypes.TyMintAction + + tx := &types.Transaction{} + mintAction := &rtypes.RgbxAction_Mint{} + action.Value = mintAction + + tcArr := []*testCase{ + { + expectErr: ErrInvalidSymbolLength, + action: &rtypes.MintAsset{Symbol: ""}, + }, + { + expectErr: ErrInvalidSymbolLength, + action: &rtypes.MintAsset{Symbol: "aaaabbbbccccdddde"}, + }, + { + expectErr: ErrInvalidAssetAmount, + action: &rtypes.MintAsset{Symbol: "test"}, + }, + { + expectErr: ErrInvalidAssetAmount, + action: &rtypes.MintAsset{Symbol: "test", TotalAmount: rtypes.MaxAssetAmount + 1}, + }, + { + expectErr: ErrInvalidAssetAmount, + action: &rtypes.MintAsset{Symbol: "test", Type: 1, TotalAmount: 2}, + }, + { + expectErr: ErrInvalidMetaHashLength, + action: &rtypes.MintAsset{Symbol: "test", TotalAmount: 1, MetaHash: []byte(strings.Repeat("abcd", 9))}, + }, + { + expectErr: ErrDuplicateAssetSymbol, + action: &rtypes.MintAsset{Symbol: "test", TotalAmount: 1, MetaHash: []byte("hash")}, + }, + { + expectErr: ErrNilGenesisOut, + action: &rtypes.MintAsset{Symbol: "test1", TotalAmount: 1, MetaHash: []byte("hash"), GenesisOut: &rtypes.OutPoint{Hash: "hash"}}, + }, + { + expectErr: nil, + action: &rtypes.MintAsset{Symbol: "test1", TotalAmount: 1, MetaHash: []byte("hash"), GenesisOut: &rtypes.OutPoint{ + Hash: "hash", + PkScript: []byte("pubkey"), + }}, + }, + } + + dir, state, _ := util.CreateTestDB() + defer util.CloseTestDB(dir, state) + api := &mocks.QueueProtocolAPI{} + r.SetAPI(api) + api.On("GetConfig").Return(testCfg) + r.SetStateDB(state) + err := state.Set(formatAssetKey("test"), []byte("test")) + require.Nil(t, err) + + for idx, tc := range tcArr { + mintAction.Mint = tc.action.(*rtypes.MintAsset) + testCheck(t, r, tx, action, tc.expectErr, idx) + } +} + +func Test_checkTransfer(t *testing.T) { + r := newRgbx() + action := &rtypes.RgbxAction{} + action.Ty = rtypes.TyTransferAction + addr, priv := util.Genaddress() + tx := &types.Transaction{} + tx.Sign(types.SECP256K1, priv) + value := &rtypes.RgbxAction_Transfer{} + action.Value = value + utxoAddr := "74503993e7c8d4280f6fbb99ae5aaa92231a1981a358e40f97e2b4f4dfbea13c:0" + tcArr := []*testCase{ + { + expectErr: ErrInvalidFromUtxo, + action: &rtypes.TransferAsset{FromUtxo: "f4dfbea13c:0", To: addr, Amount: 1, Symbol: "normal"}, + }, + { + expectErr: types.ErrInvalidAddress, + action: &rtypes.TransferAsset{To: utxoAddr, ChangeAddr: "invalidaddr", Amount: 1, Symbol: "normal"}, + }, + { + expectErr: ErrInvalidFromUtxo, + action: &rtypes.TransferAsset{FromUtxo: utxoAddr, To: addr, Amount: 1, Symbol: "normal"}, + }, + { + expectErr: ErrAssetNotExist, + action: &rtypes.TransferAsset{To: addr, Amount: 1}, + }, + { + expectErr: ErrInvalidAssetAmount, + action: &rtypes.TransferAsset{To: addr, Symbol: "normal"}, + }, + { + expectErr: types.ErrInsufficientBalance, + action: &rtypes.TransferAsset{To: addr, Symbol: "normal", Amount: 1}, + }, + { + expectErr: ErrInvalidAssetSender, + action: &rtypes.TransferAsset{To: addr, Symbol: "collect", Amount: 1}, + }, + { + expectErr: nil, + action: &rtypes.TransferAsset{To: addr, Symbol: "xbtc", Amount: 1}, + }, + { + expectErr: nil, + action: &rtypes.TransferAsset{FromUtxo: utxoAddr, To: addr, Symbol: "xbtc", Amount: 1}, + }, + { + expectErr: types.ErrInsufficientBalance, + action: &rtypes.TransferAsset{To: addr, Symbol: "xbtc", Amount: 2}, + }, + { + expectErr: nil, + action: &rtypes.TransferAsset{FromUtxo: utxoAddr, To: tx.From(), Symbol: "collect", Amount: 1, FromUtxoPkScript: []byte("pubkey")}, + }, + } + + dir, state, _ := util.CreateTestDB() + defer util.CloseTestDB(dir, state) + api := &mocks.QueueProtocolAPI{} + r.SetAPI(api) + api.On("GetConfig").Return(testCfg) + r.SetStateDB(state) + err := state.Set(formatAssetKey("normal"), types.Encode(&rtypes.RgbxAsset{})) + require.Nil(t, err) + err = state.Set(formatAssetKey("collect"), types.Encode(&rtypes.RgbxAsset{ + Type: 1, + Owner: utxoAddr, + })) + require.Nil(t, err) + err = state.Set(formatCrossChainInfoKey("btc"), types.Encode(&rtypes.CrossChainInfo{AssetSymbol: "BTC"})) + require.Nil(t, err) + // checkCrossChainTransfer 现在使用 newAccount,symbol 为 "xbtc" + crossAcc, err := r.(*rgbx).newAccount("xbtc") + require.Nil(t, err) + _, err = crossAcc.Mint(tx.From(), 1) + require.Nil(t, err) + + for idx, tc := range tcArr { + value.Transfer = tc.action.(*rtypes.TransferAsset) + testCheck(t, r, tx, action, tc.expectErr, idx) + } +} + +func Test_checkConfirm(t *testing.T) { + r := newRgbx() + action := &rtypes.RgbxAction{} + action.Ty = rtypes.TyConfirmAction + tx := &types.Transaction{} + tx.Sign(types.SECP256K1, testPriv) + value := &rtypes.RgbxAction_Confirm{} + action.Value = value + utxoAddr := "74503993e7c8d4280f6fbb99ae5aaa92231a1981a358e40f97e2b4f4dfbea13c:0" + + btcTx := wire.MsgTx{} + out, err := wire.NewOutPointFromString(utxoAddr) + require.Nil(t, err) + btcTx.TxIn = append(btcTx.TxIn, + &wire.TxIn{PreviousOutPoint: *out}, + &wire.TxIn{PreviousOutPoint: wire.OutPoint{ + Hash: out.Hash, + Index: 1, + }}) + btcTx.TxOut = append(btcTx.TxOut, wire.NewTxOut(0, []byte("testScript"))) + buf := bytes.NewBuffer(make([]byte, 0, btcTx.SerializeSizeStripped())) + err = btcTx.SerializeNoWitness(buf) + require.Nil(t, err) + + tcArr := []*testCase{ + { + expectErr: ErrPendingTxNotExist, + action: &rtypes.ConfirmTx{TxIndex: 1}, + }, + { + expectErr: ErrTxAlreadyConfirmed, + action: &rtypes.ConfirmTx{TxBlockHeight: 1}, + }, + { + expectErr: ErrConfirmedHashNotEqual, + action: &rtypes.ConfirmTx{TxBlockHeight: 2, TxIndex: 0, TxHash: []byte("hash")}, + }, + { + expectErr: nil, + action: &rtypes.ConfirmTx{Timeout: true}, + }, + { + expectErr: ErrWithdrawConfirmTimeoutNotAllowed, + action: &rtypes.ConfirmTx{Timeout: true, ActionType: rtypes.TyWithDrawAsset}, + }, + { + expectErr: ErrDecodeBtcTx, + action: &rtypes.ConfirmTx{UtxoProof: &rtypes.UtxoSpendingProof{SpendingTx: []byte("invalidBtcTxData")}}, + }, + { + expectErr: ErrInvalidSpendingTxIn, + action: &rtypes.ConfirmTx{UtxoProof: &rtypes.UtxoSpendingProof{SpendingTx: buf.Bytes(), SpendingInputIdx: 2}}, + }, + { + expectErr: ErrSpendingInputNotEqual, + action: &rtypes.ConfirmTx{UtxoProof: &rtypes.UtxoSpendingProof{SpendingTx: buf.Bytes(), SpendingInputIdx: 1}}, + }, + { + expectErr: nil, + action: &rtypes.ConfirmTx{UtxoProof: &rtypes.UtxoSpendingProof{SpendingTx: buf.Bytes(), OpRetOutputIdx: -1}}, + }, + { + expectErr: ErrOpRetOutputPkScriptNotEqual, + action: &rtypes.ConfirmTx{UtxoProof: &rtypes.UtxoSpendingProof{SpendingTx: buf.Bytes()}}, + }, + { + expectErr: nil, + action: &rtypes.ConfirmTx{UtxoProof: &rtypes.UtxoSpendingProof{SpendingTx: buf.Bytes(), OpRetOutputPkScript: []byte("testScript")}}, + }, + } + + dir, state, local := util.CreateTestDB() + defer util.CloseTestDB(dir, state) + api := &mocks.QueueProtocolAPI{} + r.SetAPI(api) + api.On("GetConfig").Return(testCfg) + r.SetStateDB(state) + r.SetLocalDB(local) + require.Nil(t, state.Set(formatPayloadKey(nil), types.Encode(&rtypes.PendingTx{}))) + require.Nil(t, state.Set(formatPayloadKey([]byte("hash")), types.Encode(&rtypes.MintAsset{Symbol: "x", TotalAmount: 1}))) + require.Nil(t, local.Set(formatPendingTxKey(0, 0), types.Encode(&rtypes.PendingTx{Utxo: &rtypes.OutPoint{Hash: out.Hash.String()}}))) + require.Nil(t, local.Set(formatPendingTxKey(2, 0), types.Encode(&rtypes.PendingTx{ + Utxo: &rtypes.OutPoint{Hash: out.Hash.String()}, + TxHash: []byte("other"), + }))) + require.Nil(t, local.Set(formatPendingTxKey(1, 0), types.Encode(&rtypes.PendingTx{Confirmed: true}))) + + for idx, tc := range tcArr { + value.Confirm = tc.action.(*rtypes.ConfirmTx) + testCheck(t, r, tx, action, tc.expectErr, idx) + } +} + +func newTestnetWitnessAddr(t *testing.T) (addr string, pkScript []byte) { + t.Helper() + priv, err := btcec.NewPrivateKey() + require.NoError(t, err) + pub := priv.PubKey().SerializeCompressed() + waddr, err := btcutil.NewAddressWitnessPubKeyHash(btcutil.Hash160(pub), &chaincfg.TestNet3Params) + require.NoError(t, err) + pk, err := txscript.PayToAddrScript(waddr) + require.NoError(t, err) + return waddr.String(), pk +} + +func Test_checkWithdraw(t *testing.T) { + r := newRgbx() + action := &rtypes.RgbxAction{} + action.Ty = rtypes.TyWithDrawAsset + userAddr, userPriv := util.Genaddress() + validDest, _ := newTestnetWitnessAddr(t) + tx := &types.Transaction{} + tx.Sign(types.SECP256K1, userPriv) + value := &rtypes.RgbxAction_Withdraw{} + action.Value = value + + tcArr := []*testCase{ + {expectErr: ErrInvalidWithdrawAmount, action: &rtypes.WithdrawAsset{ + AssetSymbol: "btc", Amount: minBtcWithdrawAmount - 1, DestinationAddr: validDest, FeeRate: 1, + }}, + {expectErr: ErrInvalidWithdrawFeeRate, action: &rtypes.WithdrawAsset{ + AssetSymbol: "btc", Amount: minBtcWithdrawAmount, DestinationAddr: validDest, FeeRate: 0, + }}, + {expectErr: ErrInvalidWithdrawFeeRate, action: &rtypes.WithdrawAsset{ + AssetSymbol: "btc", Amount: minBtcWithdrawAmount, DestinationAddr: validDest, FeeRate: maxBtcFeeRate + 1, + }}, + {expectErr: ErrInvalidWithdrawDestination, action: &rtypes.WithdrawAsset{ + AssetSymbol: "btc", Amount: minBtcWithdrawAmount, DestinationAddr: "not-a-btc-address", FeeRate: 1, + }}, + {expectErr: types.ErrInsufficientBalance, action: &rtypes.WithdrawAsset{ + AssetSymbol: "btc", Amount: 10001, DestinationAddr: validDest, FeeRate: 1, + }}, + {expectErr: nil, action: &rtypes.WithdrawAsset{ + AssetSymbol: "btc", Amount: minBtcWithdrawAmount, DestinationAddr: validDest, FeeRate: 10, + }}, + } + + dir, state, _ := util.CreateTestDB() + defer util.CloseTestDB(dir, state) + api := &mocks.QueueProtocolAPI{} + r.SetAPI(api) + api.On("GetConfig").Return(testCfg) + api.On("Query", ltypes.LightclientX, "GetBtcNetName", mock.Anything).Return(&types.ReplyString{Data: "testnet3"}, nil) + r.SetStateDB(state) + require.Nil(t, state.Set(formatCrossChainInfoKey("btc"), types.Encode(&rtypes.CrossChainInfo{AssetSymbol: "BTC"}))) + // checkWithdraw 现在使用 newAccount(withdraw.GetAssetSymbol()),即 newAccount("btc") + // formatSymbol("btc") -> "BTC",所以账户 symbol 是 "BTC" + acc, err := r.(*rgbx).newAccount("xbtc") + require.Nil(t, err) + _, err = acc.Mint(userAddr, 10000) + require.Nil(t, err) + + for idx, tc := range tcArr { + if idx == 1 { + require.Nil(t, state.Delete(formatCrossChainInfoKey("btc"))) + } + if idx == 2 { + require.Nil(t, state.Set(formatCrossChainInfoKey("btc"), types.Encode(&rtypes.CrossChainInfo{AssetSymbol: "BTC"}))) + } + value.Withdraw = tc.action.(*rtypes.WithdrawAsset) + testCheck(t, r, tx, action, tc.expectErr, idx) + } +} + +func Test_checkDeposit(t *testing.T) { + r := newRgbx() + action := &rtypes.RgbxAction{} + action.Ty = rtypes.TyDepositAsset + tx := &types.Transaction{} + value := &rtypes.RgbxAction_Deposit{} + action.Value = value + + depAddr, _ := util.Genaddress() + dupProofData := []byte("dup-tx-bytes") + var minimalBtcTx wire.MsgTx + minimalBtcTx.Version = 2 + buf := bytes.NewBuffer(make([]byte, 0, minimalBtcTx.SerializeSizeStripped())) + require.NoError(t, minimalBtcTx.SerializeNoWitness(buf)) + + tcArr := []*testCase{ + {expectErr: ErrInvalidDepositAmount, action: &rtypes.DepositAsset{ + AssetSymbol: "btc", Amount: 0, DepositAddress: depAddr, TxProof: &rtypes.BtcTxProof{TxData: []byte{1}}, + }}, + {expectErr: ErrInvalidDepositAddress, action: &rtypes.DepositAsset{ + AssetSymbol: "btc", Amount: 1, DepositAddress: "", TxProof: &rtypes.BtcTxProof{TxData: []byte{1}}, + }}, + {expectErr: ErrInvalidBtcTxProof, action: &rtypes.DepositAsset{ + AssetSymbol: "btc", Amount: 1, DepositAddress: depAddr, TxProof: nil, + }}, + {expectErr: ErrInvalidBtcTxProof, action: &rtypes.DepositAsset{ + AssetSymbol: "btc", Amount: 1, DepositAddress: depAddr, TxProof: &rtypes.BtcTxProof{TxData: []byte{0xff}}, + }}, + {expectErr: ErrDuplicateDepositProof, action: &rtypes.DepositAsset{ + AssetSymbol: "btc", Amount: 1, DepositAddress: depAddr, TxProof: &rtypes.BtcTxProof{TxData: dupProofData}, + }}, + } + + dir, state, _ := util.CreateTestDB() + defer util.CloseTestDB(dir, state) + api := &mocks.QueueProtocolAPI{} + r.SetAPI(api) + api.On("GetConfig").Return(testCfg) + r.SetStateDB(state) + require.Nil(t, state.Set(formatDepositUsedKey(dupProofData), []byte("1"))) + + for idx, tc := range tcArr { + value.Deposit = tc.action.(*rtypes.DepositAsset) + testCheck(t, r, tx, action, tc.expectErr, idx) + } + + // decode ok, header query fails + api.On("Query", ltypes.LightclientX, "GetBtcHeader", mock.Anything).Return(nil, errors.New("no header")) + value.Deposit = &rtypes.DepositAsset{ + AssetSymbol: "btc", + Amount: 1, + DepositAddress: depAddr, + TxProof: &rtypes.BtcTxProof{ + TxData: buf.Bytes(), + BlockHeight: 1, + BlockHash: "00", + TxIndex: 0, + MerkleProof: nil, + }, + } + testCheck(t, r, tx, action, ErrGetBtcHeader, len(tcArr)) +} + +func Test_checkCommitDKG(t *testing.T) { + r := newRgbx() + action := &rtypes.RgbxAction{} + action.Ty = rtypes.TyCommitDKGAction + tx := &types.Transaction{} + tx.Sign(types.SECP256K1, testPriv) + value := &rtypes.RgbxAction_CommitDKG{} + action.Value = value + + dkgAddr, validPk := newTestnetWitnessAddr(t) + + tcArr := []*testCase{ + {expectErr: ErrInvalidDkgAddress, action: &rtypes.CommitDKG{ + AssetSymbol: "btc", DkgAddress: dkgAddr, PkScript: []byte{0x01}, + }}, + {expectErr: ErrGetGuardianNodeAddress, action: &rtypes.CommitDKG{ + AssetSymbol: "btc", DkgAddress: dkgAddr, PkScript: validPk, + }}, + {expectErr: ErrInvalidGuardianCommitter, action: &rtypes.CommitDKG{ + AssetSymbol: "btc", DkgAddress: dkgAddr, PkScript: validPk, + }}, + {expectErr: ErrDuplicateDKGCommit, action: &rtypes.CommitDKG{ + AssetSymbol: "btc", DkgAddress: dkgAddr, PkScript: validPk, + }}, + {expectErr: nil, action: &rtypes.CommitDKG{ + AssetSymbol: "btc", DkgAddress: dkgAddr, PkScript: validPk, + }}, + } + + dir, state, _ := util.CreateTestDB() + defer util.CloseTestDB(dir, state) + api := &mocks.QueueProtocolAPI{} + r.SetAPI(api) + api.On("GetConfig").Return(testCfg) + api.On("Query", ltypes.LightclientX, "GetBtcNetName", mock.Anything).Return(&types.ReplyString{Data: "testnet3"}, nil) + r.SetStateDB(state) + + for idx, tc := range tcArr { + switch idx { + case 1: + api.ExpectedCalls = nil + api.On("GetConfig").Return(testCfg) + api.On("Query", ltypes.LightclientX, "GetBtcNetName", mock.Anything).Return(&types.ReplyString{Data: "testnet3"}, nil) + api.On("Query", paratypes.ParaX, "GetNodeGroupStatus", mock.Anything).Return(nil, errors.New("query fail")) + case 2: + api.ExpectedCalls = nil + api.On("GetConfig").Return(testCfg) + api.On("Query", ltypes.LightclientX, "GetBtcNetName", mock.Anything).Return(&types.ReplyString{Data: "testnet3"}, nil) + api.On("Query", paratypes.ParaX, "GetNodeGroupStatus", mock.Anything).Return( + ¶types.ParaNodeGroupStatus{TargetAddrs: "other1,other2"}, nil) + case 3: + api.ExpectedCalls = nil + api.On("GetConfig").Return(testCfg) + api.On("Query", ltypes.LightclientX, "GetBtcNetName", mock.Anything).Return(&types.ReplyString{Data: "testnet3"}, nil) + api.On("Query", paratypes.ParaX, "GetNodeGroupStatus", mock.Anything).Return( + ¶types.ParaNodeGroupStatus{TargetAddrs: testCommitAddr}, nil) + require.Nil(t, state.Set(formatCrossChainInfoKey("btc"), types.Encode(&rtypes.CrossChainInfo{AssetSymbol: "BTC"}))) + case 4: + api.ExpectedCalls = nil + api.On("GetConfig").Return(testCfg) + api.On("Query", ltypes.LightclientX, "GetBtcNetName", mock.Anything).Return(&types.ReplyString{Data: "testnet3"}, nil) + api.On("Query", paratypes.ParaX, "GetNodeGroupStatus", mock.Anything).Return( + ¶types.ParaNodeGroupStatus{TargetAddrs: testCommitAddr}, nil) + require.Nil(t, state.Delete(formatCrossChainInfoKey("btc"))) + } + value.CommitDKG = tc.action.(*rtypes.CommitDKG) + testCheck(t, r, tx, action, tc.expectErr, idx) + } +} + +func Test_decodeBtcAddressScript(t *testing.T) { + + params := lighttypes.GetBtcChainParams("regtest") + + priv, err := btcec.NewPrivateKey() + require.Nil(t, err) + pub := priv.PubKey().SerializeCompressed() + waddr, err := btcutil.NewAddressWitnessPubKeyHash(btcutil.Hash160(pub), params) + require.Nil(t, err) + fmt.Println(waddr.String()) + _, err = btcutil.DecodeAddress(waddr.String(), params) + require.Nil(t, err) +} diff --git a/plugin/dapp/rgbx/executor/crosschain.go b/plugin/dapp/rgbx/executor/crosschain.go new file mode 100644 index 0000000000..70cdf4cac4 --- /dev/null +++ b/plugin/dapp/rgbx/executor/crosschain.go @@ -0,0 +1,148 @@ +package executor + +import ( + "encoding/hex" + "errors" + "strings" + + "github.com/33cn/chain33/types" + paratypes "github.com/33cn/plugin/plugin/dapp/paracross/types" + rtypes "github.com/33cn/plugin/plugin/dapp/rgbx/types" +) + +const defaultGuardianParachainTitle = "user.p.rgbxguardians." + +func (r *rgbx) Exec_CommitDKG(commit *rtypes.CommitDKG, tx *types.Transaction, index int) (*types.Receipt, error) { + + symbol := formatSymbol(commit.GetAssetSymbol()) + receipt := &types.Receipt{Ty: types.ExecOk} + txHash := hex.EncodeToString(tx.Hash()) + commitAddr := tx.From() + addrs := &types.ReqAddrs{} + err := readDB(r.GetStateDB(), formatDkgConfirmationsKey(commit.DkgAddress), addrs) + if err != nil && !errors.Is(err, types.ErrNotFound) { + elog.Error("Exec_CommitDKG", "txHash", txHash, "symbol", symbol, "dkgAddr", commit.DkgAddress, "readDB err", err) + return nil, ErrGetDkgConfirmations + } + for _, addr := range addrs.Addrs { + if commitAddr == addr { + return receipt, nil + } + } + addrs.Addrs = append(addrs.Addrs, commitAddr) + guardianAddrs, err := r.getGuardianNodeAddress(rgbxCfg.GuardianParachainTitle) + if err != nil { + elog.Error("Exec_CommitDKG", "txHash", txHash, "symbol", symbol, "getGuardianNodeAddress err", err) + return nil, ErrGetGuardianNodeAddress + } + + encodeAddrs := types.Encode(addrs) + receipt.KV = append(receipt.KV, &types.KeyValue{ + Key: formatDkgConfirmationsKey(commit.GetDkgAddress()), + Value: encodeAddrs, + }) + + receipt.Logs = append(receipt.Logs, &types.ReceiptLog{ + Ty: rtypes.TyCommitDKGLog, + Log: encodeAddrs, + }) + + if len(strings.Split(guardianAddrs, ",")) == len(addrs.Addrs) { + + info := &rtypes.CrossChainInfo{ + AssetSymbol: symbol, + WrappedSymbol: formatCrossChainSymbol(symbol), + TssAddress: commit.DkgAddress, + PkScript: commit.PkScript, + } + receipt.KV = append(receipt.KV, &types.KeyValue{ + Key: formatCrossChainInfoKey(symbol), + Value: types.Encode(info), + }) + } + + return receipt, nil + +} + +func (r *rgbx) Exec_Deposit(deposit *rtypes.DepositAsset, tx *types.Transaction, index int) (*types.Receipt, error) { + + receipt := &types.Receipt{Ty: types.ExecOk} + txHash := tx.Hash() + symbol := ensureCrossChainSymbol(deposit.GetAssetSymbol()) + accDB, err := r.newAccount(symbol) + if err != nil { + elog.Error("Exec_Deposit newCrossChainAccount", "txHash", hex.EncodeToString(txHash), "symbol", symbol, + "err", err) + return nil, err + } + depositReceipt, err := accDB.Mint(deposit.GetDepositAddress(), deposit.GetAmount()) + if err != nil { + elog.Error("Exec_Deposit Mint", "txHash", hex.EncodeToString(txHash), "symbol", symbol, + "depositAddr", deposit.GetDepositAddress(), "amount", deposit.GetAmount(), "err", err) + return nil, err + } + receipt.KV = append(receipt.KV, depositReceipt.KV...) + receipt.Logs = append(receipt.Logs, depositReceipt.Logs...) + + receipt.KV = append(receipt.KV, &types.KeyValue{ + Key: formatDepositUsedKey(deposit.GetTxProof().GetTxData()), + Value: []byte("used"), + }) + + return receipt, nil + +} + +func (r *rgbx) Exec_Withdraw(withdraw *rtypes.WithdrawAsset, tx *types.Transaction, index int) (*types.Receipt, error) { + receipt := &types.Receipt{Ty: types.ExecOk} + txHash := tx.Hash() + symbol := ensureCrossChainSymbol(withdraw.GetAssetSymbol()) + accDB, err := r.newAccount(symbol) + if err != nil { + return nil, err + } + lockAddr := r.crossChainLockAddress(accDB) + lockReceipt, err := accDB.Transfer(tx.From(), lockAddr, withdraw.GetAmount()) + if err != nil { + elog.Error("Exec_Withdraw lock transfer", "txHash", hex.EncodeToString(txHash), "from", tx.From(), + "symbol", symbol, "amount", withdraw.GetAmount(), "err", err) + return nil, err + } + receipt.KV = append(receipt.KV, lockReceipt.KV...) + receipt.Logs = append(receipt.Logs, lockReceipt.Logs...) + + receipt.KV = append(receipt.KV, &types.KeyValue{ + Key: formatPayloadKey(txHash), + Value: types.Encode(withdraw), + }) + receipt.Logs = append(receipt.Logs, &types.ReceiptLog{ + Ty: rtypes.TyPendingTxLog, + Log: types.Encode(&rtypes.PendingTx{ + ActionType: rtypes.TyWithDrawAsset, + Timestamp: r.GetBlockTime(), + TxBlockHeight: r.GetHeight(), + TxIndex: int64(index), + TxHash: txHash, + FromAddress: tx.From(), + AssetSymbol: formatSymbol(withdraw.GetAssetSymbol()), + TargetAddress: withdraw.GetDestinationAddr(), + Amount: withdraw.GetAmount(), + FeeRate: withdraw.GetFeeRate(), + }), + }) + return receipt, nil +} + +func (r *rgbx) getGuardianNodeAddress(title string) (string, error) { + + params := ¶types.ReqParacrossNodeInfo{Title: title} + resp, err := r.GetAPI().Query(paratypes.ParaX, "GetNodeGroupStatus", params) + if err != nil { + elog.Error("getGuardianNodeAddress", "title", title, "err", err) + return "", err + } + + status := resp.(*paratypes.ParaNodeGroupStatus) + return status.TargetAddrs, nil +} diff --git a/plugin/dapp/rgbx/executor/crosschain_test.go b/plugin/dapp/rgbx/executor/crosschain_test.go new file mode 100644 index 0000000000..c4f1adad0f --- /dev/null +++ b/plugin/dapp/rgbx/executor/crosschain_test.go @@ -0,0 +1,141 @@ +package executor + +import ( + "testing" + + "github.com/33cn/chain33/client/mocks" + "github.com/33cn/chain33/types" + "github.com/33cn/chain33/util" + rtypes "github.com/33cn/plugin/plugin/dapp/rgbx/types" + "github.com/stretchr/testify/require" +) + +type kvSetter interface { + Set(key, value []byte) error + Delete(key []byte) error +} + +func applyStateKV(t *testing.T, stateDB kvSetter, kvs []*types.KeyValue) { + for _, kv := range kvs { + var errState error + if kv.Value == nil { + if stateDB != nil { + errState = stateDB.Delete(kv.Key) + } + require.Nil(t, errState) + } else { + if stateDB != nil { + errState = stateDB.Set(kv.Key, kv.Value) + } + require.Nil(t, errState) + } + } +} + +type kvSetOnly interface { + Set(key, value []byte) error +} + +func applyLocalKV(t *testing.T, localDB kvSetOnly, kvs []*types.KeyValue) { + for _, kv := range kvs { + if localDB == nil || kv.Value == nil { + continue + } + require.Nil(t, localDB.Set(kv.Key, kv.Value)) + } +} + +func TestCrossChainDepositWithdrawConfirmExec(t *testing.T) { + r := newRgbx() + dir, state, local := util.CreateTestDB() + defer util.CloseTestDB(dir, state) + + api := &mocks.QueueProtocolAPI{} + api.On("GetConfig").Return(testCfg) + r.SetAPI(api) + r.SetStateDB(state) + r.SetLocalDB(local) + + userAddr, userPriv := util.Genaddress() + + deposit := &rtypes.DepositAsset{ + Amount: 1000, + DepositAddress: userAddr, + AssetSymbol: "btc", + } + depositTx, err := r.GetExecutorType().CreateTransaction(rtypes.NameDepositAssetAction, deposit) + require.Nil(t, err) + depositTx.Sign(types.SECP256K1, userPriv) + depositReceipt, err := r.Exec(depositTx, 0) + require.Nil(t, err) + applyStateKV(t, state, depositReceipt.KV) + + depositLocal, err := r.ExecLocal(depositTx, &types.ReceiptData{Ty: depositReceipt.Ty, Logs: depositReceipt.Logs}, 0) + require.Nil(t, err) + applyLocalKV(t, local, depositLocal.KV) + + accDB, err := r.(*rgbx).newAccount("xbtc") + require.Nil(t, err) + userAcc := accDB.LoadAccount(userAddr) + require.Equal(t, int64(1000), userAcc.GetBalance()) + + withdraw := &rtypes.WithdrawAsset{ + Amount: 600, + FeeRate: 10, + DestinationAddr: "tb1qwj4z7vmxq0x9mep74jkh0exj4rlzu7xhj4h3kl", + AssetSymbol: "BTC", + } + withdrawTx, err := r.GetExecutorType().CreateTransaction(rtypes.NameWithdrawAssetAction, withdraw) + require.Nil(t, err) + withdrawTx.Sign(types.SECP256K1, userPriv) + withdrawReceipt, err := r.Exec(withdrawTx, 1) + require.Nil(t, err) + applyStateKV(t, state, withdrawReceipt.KV) + + withdrawLocal, err := r.ExecLocal(withdrawTx, &types.ReceiptData{Ty: withdrawReceipt.Ty, Logs: withdrawReceipt.Logs}, 1) + require.Nil(t, err) + applyLocalKV(t, local, withdrawLocal.KV) + listResp, err := r.(*rgbx).Query_ListPendingTxByFrom(&types.ReqString{Data: userAddr}) + require.Nil(t, err) + require.Len(t, listResp.(*rtypes.PendingTxs).GetPendingList(), 1) + + userAcc = accDB.LoadAccount(userAddr) + require.Equal(t, int64(400), userAcc.GetBalance()) + lockAddr := r.(*rgbx).crossChainLockAddress(accDB) + require.Equal(t, int64(600), accDB.LoadAccount(lockAddr).GetBalance()) + + confirm := &rtypes.ConfirmTx{ + ActionType: rtypes.TyWithDrawAsset, + TxBlockHeight: 0, + TxIndex: 1, + TxHash: withdrawTx.Hash(), + ConfirmedBlockHeight: 2, + } + confirmTx, err := r.GetExecutorType().CreateTransaction(rtypes.NameConfirmAction, confirm) + require.Nil(t, err) + confirmTx.Sign(types.SECP256K1, testPriv) + confirmReceipt, err := r.Exec(confirmTx, 2) + require.Nil(t, err) + applyStateKV(t, state, confirmReceipt.KV) + confirmLocal, err := r.ExecLocal(confirmTx, &types.ReceiptData{Ty: confirmReceipt.Ty, Logs: confirmReceipt.Logs}, 2) + require.Nil(t, err) + applyLocalKV(t, local, confirmLocal.KV) + + userAcc = accDB.LoadAccount(userAddr) + require.Equal(t, int64(400), userAcc.GetBalance()) + require.Equal(t, int64(0), accDB.LoadAccount(lockAddr).GetBalance()) + listResp, err = r.(*rgbx).Query_ListPendingTxByFrom(&types.ReqString{Data: userAddr}) + require.Nil(t, err) + require.Len(t, listResp.(*rtypes.PendingTxs).GetPendingList(), 0) +} + +func TestFormatCrossChainAccountSymbolByConfig(t *testing.T) { + origin := rgbxCfg.CrossChainAssetPrefix + defer func() { rgbxCfg.CrossChainAssetPrefix = origin }() + + rgbxCfg.CrossChainAssetPrefix = "rb" + require.Equal(t, "rbBTC", formatCrossChainSymbol("btc")) + + rgbxCfg.CrossChainAssetPrefix = "X" + require.Equal(t, "XBTC", formatCrossChainSymbol("btc")) +} diff --git a/plugin/dapp/rgbx/executor/exec.go b/plugin/dapp/rgbx/executor/exec.go new file mode 100644 index 0000000000..d8b9d1f9e2 --- /dev/null +++ b/plugin/dapp/rgbx/executor/exec.go @@ -0,0 +1,308 @@ +package executor + +import ( + "bytes" + "encoding/hex" + + log "github.com/33cn/chain33/common/log/log15" + "github.com/33cn/chain33/types" + rtypes "github.com/33cn/plugin/plugin/dapp/rgbx/types" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" +) + +/* + * 实现交易的链上执行接口 + * 关键数据上链(statedb)并生成交易回执(log) + */ + +func (r *rgbx) Exec_Mint(mint *rtypes.MintAsset, tx *types.Transaction, index int) (*types.Receipt, error) { + receipt := &types.Receipt{Ty: types.ExecOk} + + txHash := hex.EncodeToString(tx.Hash()) + elog.Debug("Exec_Mint", "txHash", txHash, "symbol", mint.Symbol, + "amount", mint.TotalAmount, "gensisOut", mint.GetGenesisOut().ToString()) + receipt.KV = append(receipt.KV, &types.KeyValue{ + Key: formatPayloadKey(tx.Hash()), + Value: types.Encode(mint), + }) + + receipt.Logs = append(receipt.Logs, &types.ReceiptLog{ + Ty: rtypes.TyPendingTxLog, + Log: types.Encode(&rtypes.PendingTx{ + ActionType: rtypes.TyMintAction, + Timestamp: r.GetBlockTime(), + TxBlockHeight: r.GetHeight(), + TxIndex: int64(index), + TxHash: tx.Hash(), + Utxo: mint.GetGenesisOut(), + }), + }) + + return receipt, nil +} + +func (r *rgbx) Exec_Transfer(transfer *rtypes.TransferAsset, tx *types.Transaction, index int) (*types.Receipt, error) { + + txHash := hex.EncodeToString(tx.Hash()) + + fromAddr := tx.From() + elog.Debug("Exec_Transfer", "txHash", txHash, "symbol", transfer.Symbol, "amount", transfer.Amount, + "from", fromAddr, "to", transfer.GetTo(), + "changeAddr", transfer.GetChangeAddr(), "fromUtxo", transfer.GetFromUtxo()) + + if isCrossChainSymbol(transfer.GetSymbol()) { + accDB, err := r.newAccount(transfer.GetSymbol()) + if err != nil { + elog.Error("Exec_Transfer newCrossChainAccount", "txHash", txHash, "from", tx.From(), + "to", transfer.To, "symbol", transfer.Symbol, "amount", transfer.Amount, "err", err) + return nil, err + } + receipt, err := accDB.Transfer(fromAddr, transfer.GetTo(), transfer.GetAmount()) + if err != nil { + elog.Error("Exec_Transfer cross transfer", "txHash", txHash, "from", tx.From(), + "to", transfer.To, "symbol", transfer.Symbol, "amount", transfer.Amount, "err", err) + return nil, err + } + return receipt, nil + } + + // from是btc utxo, 记录并等待confirm交易 + if transfer.GetFromUtxo() != "" { + receipt := &types.Receipt{ + Ty: types.ExecOk, + KV: []*types.KeyValue{{Key: formatPayloadKey(tx.Hash()), Value: types.Encode(transfer)}}, + } + // check tx 阶段已经校验过地址 + utxo, _ := rtypes.NewOutPointFromString(transfer.GetFromUtxo()) + utxo.PkScript = transfer.GetFromUtxoPkScript() + receipt.Logs = append(receipt.Logs, &types.ReceiptLog{ + Ty: rtypes.TyPendingTxLog, + Log: types.Encode(&rtypes.PendingTx{ + ActionType: rtypes.TyTransferAction, + Timestamp: r.GetBlockTime(), + TxBlockHeight: r.GetHeight(), + TxIndex: int64(index), + TxHash: tx.Hash(), + Utxo: utxo, + }), + }) + return receipt, nil + } + + asset := &rtypes.RgbxAsset{} + err := readDB(r.GetStateDB(), formatAssetKey(transfer.GetSymbol()), asset) + if err != nil { + elog.Error("Exec_Transfer get asset", "txHash", txHash, "symbol", transfer.GetSymbol(), + "err", err) + return nil, ErrAssetNotExist + } + + if asset.Type == uint32(rtypes.Collectible) { + return r.assetReceipt(asset, transfer.GetTo()), nil + } + + accDB, err := r.newAccount(transfer.GetSymbol()) + if err != nil { + elog.Error("Exec_Transfer newAccount", "txHash", txHash, "from", fromAddr, + "to", transfer.To, "symbol", transfer.Symbol, "amount", transfer.Amount, "err", err) + return nil, err + } + receipt, err := accDB.Transfer(fromAddr, transfer.GetTo(), transfer.GetAmount()) + if err != nil { + elog.Error("Exec_Transfer transfer", "txHash", txHash, "from", fromAddr, + "to", transfer.To, "symbol", transfer.Symbol, "amount", transfer.Amount, "err", err) + return nil, err + } + return receipt, nil +} + +func (r *rgbx) assetReceipt(asset *rtypes.RgbxAsset, owner string) *types.Receipt { + + if asset.Type == uint32(rtypes.Collectible) { + asset.Owner = owner + } + assetVal := types.Encode(asset) + receipt := &types.Receipt{ + Ty: types.ExecOk, + KV: []*types.KeyValue{{Key: formatAssetKey(asset.Symbol), Value: assetVal}}, + Logs: []*types.ReceiptLog{{Ty: rtypes.TyAssetLog, Log: assetVal}}, + } + return receipt +} + +func (r *rgbx) Exec_Confirm(confirm *rtypes.ConfirmTx, tx *types.Transaction, index int) (*types.Receipt, error) { + + txHash := hex.EncodeToString(tx.Hash()) + confirmHash := hex.EncodeToString(confirm.GetTxHash()) + action := rtypes.GetActionName(confirm.GetActionType()) + elog.Debug("Exec_Confirm", "opRetOutIdx", confirm.GetUtxoProof().GetOpRetOutputIdx(), + "timeout", confirm.GetTimeout(), "txHash", txHash, "confirmHash", confirmHash, + "action", action) + if confirm.GetTimeout() { + return &types.Receipt{Ty: types.ExecOk}, nil + } + if confirm.ActionType == rtypes.TyWithDrawAsset { + return r.confirmWithdrawSettlement(confirm, txHash, confirmHash) + } + + spendHash := chainhash.DoubleHashH(confirm.GetUtxoProof().GetSpendingTx()).String() + // 绑定资产的utxo已经在btc链上花费,但op return不存在或承诺数据不正确, + // 交易仅做标记并返回,相关资产永久冻结,无法转移 + commitment, _ := txscript.NullDataScript(confirm.GetTxHash()) + if confirm.GetUtxoProof().GetOpRetOutputIdx() < 0 || + !bytes.Equal(commitment, confirm.GetUtxoProof().OpRetOutputPkScript) { + + elog.Warn("checkConfirm op return commitment", "action", action, + "txHash", txHash, "confirmHash", confirmHash, "opRetIdx", confirm.GetUtxoProof().GetOpRetOutputIdx(), + "spendHash", spendHash, "commit", hex.EncodeToString(confirm.GetUtxoProof().OpRetOutputPkScript), + "expectCommit", hex.EncodeToString(commitment)) + return &types.Receipt{Ty: types.ExecOk}, nil + } + + if confirm.ActionType == rtypes.TyMintAction { + return r.mintAsset(confirm, txHash, confirmHash, spendHash) + } + return r.transferAsset(confirm, txHash, confirmHash, spendHash) +} + +func (r *rgbx) confirmWithdrawSettlement(confirm *rtypes.ConfirmTx, txHash, confirmHash string) (*types.Receipt, error) { + withdraw := &rtypes.WithdrawAsset{} + if err := readDB(r.GetStateDB(), formatPayloadKey(confirm.GetTxHash()), withdraw); err != nil { + elog.Error("confirmWithdrawSettlement read payload", "txHash", txHash, "confirmHash", confirmHash, "err", err) + return nil, err + } + symbol := ensureCrossChainSymbol(withdraw.GetAssetSymbol()) + accDB, err := r.newAccount(symbol) + if err != nil { + return nil, err + } + lockAddr := r.crossChainLockAddress(accDB) + receipt, err := accDB.Burn(lockAddr, withdraw.GetAmount()) + if err != nil { + elog.Error("confirmWithdrawSettlement burn lock", "txHash", txHash, "confirmHash", confirmHash, + "lockAddr", lockAddr, "symbol", withdraw.GetAssetSymbol(), "amount", withdraw.GetAmount(), "err", err) + return nil, err + } + return receipt, nil +} + +func (r *rgbx) mintAsset(confirm *rtypes.ConfirmTx, txHash, confirmHash, spendHash string) (*types.Receipt, error) { + + mint := &rtypes.MintAsset{} + err := readDB(r.GetStateDB(), formatPayloadKey(confirm.GetTxHash()), mint) + + if err != nil { + elog.Error("mintAsset readDB", "txHash", txHash, + "confirmHash", confirmHash, "err", err) + return nil, err + } + assetTy := rtypes.AssetType(mint.GetType()) + log.Debug("mintAsset", "symbol", mint.Symbol, "amount", mint.TotalAmount, + "txHash", txHash, "confirmHash", confirmHash, "assetTy", assetTy.String(), + "spendingTxHash", spendHash, "metaHash", mint.MetaHash) + asset := &rtypes.RgbxAsset{ + Symbol: formatSymbol(mint.Symbol), + Type: mint.Type, + TotalAmount: mint.TotalAmount, + MetaHash: mint.MetaHash, + GenesisBtcTxHash: spendHash, + Precision: mint.Precision, + } + // 默认opReturn的下一个utxo作为资产所有者, 如果不存在,资产将被永久冻结,无法转移 + owner := rtypes.FormatUtxo(spendHash, uint32(confirm.GetUtxoProof().GetOpRetOutputIdx()+1)) + receipt := r.assetReceipt(asset, owner) + if assetTy == rtypes.Collectible { + return receipt, nil + } + + // Normal asset + accDB, err := r.newAccount(mint.GetSymbol()) + if err != nil { + elog.Error("Exec_Transfer newAccount", "txHash", txHash, + "from", confirmHash, "err", err) + return nil, err + } + mintReceipt, err := accDB.Mint(owner, mint.GetTotalAmount()) + if err != nil { + elog.Error("mintAsset mint", "txHash", txHash, + "confirmHash", confirmHash, "symbol", mint.Symbol, "owner", owner, + "amount", mint.TotalAmount, "err", err) + return nil, err + } + + receipt.KV = append(receipt.KV, mintReceipt.KV...) + receipt.Logs = append(receipt.Logs, mintReceipt.Logs...) + return receipt, nil + +} + +func (r *rgbx) transferAsset(confirm *rtypes.ConfirmTx, txHash, confirmHash, spendHash string) (*types.Receipt, error) { + + transfer := &rtypes.TransferAsset{} + err := readDB(r.GetStateDB(), formatPayloadKey(confirm.GetTxHash()), transfer) + if err != nil { + elog.Error("transferAsset readDB", "txHash", txHash, + "confirmHash", confirmHash, "err", err) + return nil, err + } + + if isCrossChainSymbol(transfer.GetSymbol()) { + return nil, types.ErrNotSupport + } + + changeAddress := transfer.GetChangeAddr() + // 未指定找零地址时, 则使用opReturn的下一个utxo, 如果不存在,资产将被永久冻结,无法转移 + if changeAddress == "" { + changeAddress = rtypes.FormatUtxo(spendHash, uint32(confirm.GetUtxoProof().GetOpRetOutputIdx()+1)) + } + + log.Debug("transferAsset", "symbol", transfer.Symbol, "amount", transfer.Amount, + "txHash", txHash, "confirmHash", confirmHash, "spendHash", spendHash, + "from", transfer.FromUtxo, "to", transfer.To, "change", changeAddress) + + asset := &rtypes.RgbxAsset{} + err = readDB(r.GetStateDB(), formatAssetKey(transfer.GetSymbol()), asset) + if err != nil { + elog.Error("transferAsset get asset", "txHash", txHash, "confirmHash", confirmHash, + "symbol", transfer.GetSymbol(), "err", err) + return nil, ErrAssetNotExist + } + if asset.Type == uint32(rtypes.Collectible) { + return r.assetReceipt(asset, transfer.GetTo()), nil + } + accDB, err := r.newAccount(transfer.GetSymbol()) + if err != nil { + elog.Error("transferAsset newAccount", "txHash", txHash, + "confirmHash", confirmHash, "from", transfer.FromUtxo, "to", transfer.To, + "symbol", transfer.Symbol, "amount", transfer.Amount, "err", err) + return nil, err + } + + changeAmount := accDB.LoadAccount(transfer.GetFromUtxo()).GetBalance() - transfer.GetAmount() + receipt, err := accDB.Transfer(transfer.GetFromUtxo(), transfer.GetTo(), transfer.GetAmount()) + if err != nil { + elog.Error("transferAsset transfer", "txHash", txHash, "confirmHash", confirmHash, + "from", transfer.FromUtxo, "to", transfer.To, + "symbol", transfer.Symbol, "amount", transfer.Amount, "err", err) + return nil, err + } + + // handle change + if changeAmount <= 0 { + elog.Debug("transferAsset transfer zero change", "txHash", txHash, "confirmHash", confirmHash) + return receipt, nil + } + + changeReceipt, err := accDB.Transfer(transfer.GetFromUtxo(), changeAddress, changeAmount) + if err != nil { + elog.Error("transferAsset change", "txHash", txHash, "confirmHash", confirmHash, + "from", transfer.FromUtxo, "changeAddr", changeAddress, + "symbol", transfer.Symbol, "amount", changeAmount, "err", err) + return nil, err + } + + receipt.KV = append(receipt.KV, changeReceipt.KV...) + receipt.Logs = append(receipt.Logs, changeReceipt.Logs...) + return receipt, nil +} diff --git a/plugin/dapp/rgbx/executor/exec_del_local.go b/plugin/dapp/rgbx/executor/exec_del_local.go new file mode 100644 index 0000000000..49378bd34c --- /dev/null +++ b/plugin/dapp/rgbx/executor/exec_del_local.go @@ -0,0 +1,20 @@ +package executor + +import ( + "github.com/33cn/chain33/types" +) + +/* + * 实现区块回退时本地执行的数据清除 + */ + +// ExecDelLocal localdb kv数据自动回滚接口 +func (r *rgbx) ExecDelLocal(tx *types.Transaction, _ *types.ReceiptData, index int) (*types.LocalDBSet, error) { + kvs, err := r.DelRollbackKV(tx, tx.Execer) + if err != nil { + return nil, err + } + dbSet := &types.LocalDBSet{} + dbSet.KV = append(dbSet.KV, kvs...) + return dbSet, nil +} diff --git a/plugin/dapp/rgbx/executor/exec_local.go b/plugin/dapp/rgbx/executor/exec_local.go new file mode 100644 index 0000000000..1da90d04f0 --- /dev/null +++ b/plugin/dapp/rgbx/executor/exec_local.go @@ -0,0 +1,131 @@ +package executor + +import ( + "errors" + + "github.com/33cn/chain33/types" + rtypes "github.com/33cn/plugin/plugin/dapp/rgbx/types" +) + +/* + * 实现交易相关数据本地执行,数据不上链 + * 非关键数据,本地存储(localDB), 用于辅助查询,效率高 + */ + +func (r *rgbx) addPendingTxKV(dbSet *types.LocalDBSet, logData []byte, index int) { + + dbSet.KV = append(dbSet.KV, &types.KeyValue{ + Key: formatPendingTxKey(r.GetHeight(), int64(index)), + Value: logData, + }) +} + +func (r *rgbx) ExecLocal_Mint(_ *rtypes.MintAsset, tx *types.Transaction, receiptData *types.ReceiptData, index int) (*types.LocalDBSet, error) { + dbSet := &types.LocalDBSet{} + + for _, log := range receiptData.Logs { + + if log.Ty == rtypes.TyPendingTxLog { + r.addPendingTxKV(dbSet, log.Log, index) + } + } + + //auto gen for localdb auto rollback + return r.addAutoRollBack(tx, dbSet.KV), nil +} + +func (r *rgbx) ExecLocal_Transfer(_ *rtypes.TransferAsset, tx *types.Transaction, receiptData *types.ReceiptData, index int) (*types.LocalDBSet, error) { + dbSet := &types.LocalDBSet{} + for _, log := range receiptData.Logs { + + if log.Ty == rtypes.TyPendingTxLog { + r.addPendingTxKV(dbSet, log.Log, index) + } + } + + //auto gen for localdb auto rollback + return r.addAutoRollBack(tx, dbSet.KV), nil +} + +func (r *rgbx) ExecLocal_Withdraw(_ *rtypes.WithdrawAsset, tx *types.Transaction, receiptData *types.ReceiptData, index int) (*types.LocalDBSet, error) { + dbSet := &types.LocalDBSet{} + from := tx.From() + for _, log := range receiptData.Logs { + if log.Ty == rtypes.TyPendingTxLog { + r.addPendingTxKV(dbSet, log.Log, index) + + list := &rtypes.TxBlockIndexList{} + err := readDB(r.GetLocalDB(), formatPendingTxFromKey(from), list) + if err != nil && !errors.Is(err, types.ErrNotFound) { + return nil, err + } + list.BlockIndexList = append(list.BlockIndexList, &rtypes.TxBlockIndex{ + BlockHeight: r.GetHeight(), + TxIndex: int64(index), + }) + dbSet.KV = append(dbSet.KV, &types.KeyValue{ + Key: formatPendingTxFromKey(from), + Value: types.Encode(list), + }) + } + } + return r.addAutoRollBack(tx, dbSet.KV), nil +} + +func (r *rgbx) ExecLocal_Deposit(_ *rtypes.DepositAsset, tx *types.Transaction, receiptData *types.ReceiptData, index int) (*types.LocalDBSet, error) { + dbSet := &types.LocalDBSet{} + return dbSet, nil +} + +func (r *rgbx) ExecLocal_Confirm(confirm *rtypes.ConfirmTx, tx *types.Transaction, receiptData *types.ReceiptData, index int) (*types.LocalDBSet, error) { + dbSet := &types.LocalDBSet{} + // remove pending tx record + if !confirm.Timeout { + pending := &rtypes.PendingTx{} + err := readDB(r.GetLocalDB(), formatPendingTxKey(confirm.TxBlockHeight, confirm.TxIndex), pending) + if err != nil && !errors.Is(err, types.ErrNotFound) { + return nil, err + } + if err == nil && pending.GetFromAddress() != "" { + list := &rtypes.TxBlockIndexList{} + err = readDB(r.GetLocalDB(), formatPendingTxFromKey(pending.GetFromAddress()), list) + if err != nil && !errors.Is(err, types.ErrNotFound) { + return nil, err + } + if err == nil { + filtered := make([]*rtypes.TxBlockIndex, 0, len(list.GetBlockIndexList())) + for _, item := range list.GetBlockIndexList() { + if item.GetBlockHeight() == confirm.GetTxBlockHeight() && item.GetTxIndex() == confirm.GetTxIndex() { + continue + } + filtered = append(filtered, item) + } + if len(filtered) == 0 { + dbSet.KV = append(dbSet.KV, &types.KeyValue{Key: formatPendingTxFromKey(pending.GetFromAddress()), Value: nil}) + } else { + list.BlockIndexList = filtered + dbSet.KV = append(dbSet.KV, &types.KeyValue{ + Key: formatPendingTxFromKey(pending.GetFromAddress()), + Value: types.Encode(list), + }) + } + } + } + dbSet.KV = append(dbSet.KV, + &types.KeyValue{Key: formatPendingTxKey(confirm.TxBlockHeight, confirm.TxIndex), + Value: types.Encode(&rtypes.PendingTx{Confirmed: true})}) + } + dbSet.KV = append(dbSet.KV, &types.KeyValue{Key: []byte(confirmedHeightKey), + Value: types.Encode(&types.Int64{Data: confirm.ConfirmedBlockHeight})}) + + //auto gen for localdb auto rollback + return r.addAutoRollBack(tx, dbSet.KV), nil +} + +// 当区块回滚时,框架支持自动回滚localdb kv,需要对exec-local返回的kv进行封装 +func (r *rgbx) addAutoRollBack(tx *types.Transaction, kv []*types.KeyValue) *types.LocalDBSet { + + dbSet := &types.LocalDBSet{} + dbSet.KV = r.AddRollbackKV(tx, tx.Execer, kv) + return dbSet +} diff --git a/plugin/dapp/rgbx/executor/exec_local_test.go b/plugin/dapp/rgbx/executor/exec_local_test.go new file mode 100644 index 0000000000..5f15e83d03 --- /dev/null +++ b/plugin/dapp/rgbx/executor/exec_local_test.go @@ -0,0 +1,158 @@ +package executor + +import ( + "testing" + + "github.com/33cn/chain33/common/db" + "github.com/33cn/chain33/types" + "github.com/33cn/chain33/util" + rtypes "github.com/33cn/plugin/plugin/dapp/rgbx/types" + "github.com/stretchr/testify/require" +) + +func testSetKV(t *testing.T, db db.DB, kvSet *types.LocalDBSet, del bool) { + + var err error + for _, kv := range kvSet.GetKV() { + if !del { + err = db.Set(kv.Key, kv.Value) + continue + } + if kv.Value == nil { + err = db.Delete(kv.Key) + } else { + err = db.Set(kv.Key, kv.Value) + } + require.Nil(t, err) + } +} + +func TestRgbx_ExecLocal_Mint(t *testing.T) { + + r := newRgbx() + tx, err := r.GetExecutorType().CreateTransaction(rtypes.NameMintAction, &rtypes.MintAsset{}) + require.Nil(t, err) + + dir, db, local := util.CreateTestDB() + defer util.CloseTestDB(dir, db) + r.SetLocalDB(local) + kvSet, err := r.ExecLocal(tx, &types.ReceiptData{Logs: []*types.ReceiptLog{{ + Ty: rtypes.TyPendingTxLog, Log: types.Encode(&rtypes.PendingTx{ActionType: rtypes.TyMintAction})}}}, 0) + require.Nil(t, err) + testSetKV(t, db, kvSet, false) + + pendTx := &rtypes.PendingTx{} + key := formatPendingTxKey(0, 0) + require.Nil(t, readDB(local, key, pendTx)) + require.Equal(t, int32(rtypes.TyMintAction), pendTx.ActionType) + kvSet, err = r.ExecDelLocal(tx, nil, 0) + require.Nil(t, err) + testSetKV(t, db, kvSet, true) + _, err = db.Get(key) + require.Equal(t, types.ErrNotFound, err) +} + +func TestRgbx_ExecLocal_Transfer(t *testing.T) { + r := newRgbx() + tx, err := r.GetExecutorType().CreateTransaction(rtypes.NameTransferAction, &rtypes.TransferAsset{}) + require.Nil(t, err) + + dir, db, local := util.CreateTestDB() + defer util.CloseTestDB(dir, db) + r.SetLocalDB(local) + kvSet, err := r.ExecLocal(tx, &types.ReceiptData{Logs: []*types.ReceiptLog{{ + Ty: rtypes.TyPendingTxLog, Log: types.Encode(&rtypes.PendingTx{ActionType: rtypes.TyTransferAction})}}}, 0) + require.Nil(t, err) + testSetKV(t, db, kvSet, false) + + pendTx := &rtypes.PendingTx{} + key := formatPendingTxKey(0, 0) + require.Nil(t, readDB(local, key, pendTx)) + require.Equal(t, int32(rtypes.TyTransferAction), pendTx.ActionType) + kvSet, err = r.ExecDelLocal(tx, nil, 0) + require.Nil(t, err) + testSetKV(t, db, kvSet, true) + _, err = db.Get(key) + require.Equal(t, types.ErrNotFound, err) + +} + +func TestRgbx_ExecLocal_Confirm(t *testing.T) { + + r := newRgbx() + tx, err := r.GetExecutorType().CreateTransaction(rtypes.NameConfirmAction, &rtypes.ConfirmTx{ConfirmedBlockHeight: 1}) + require.Nil(t, err) + + dir, db, local := util.CreateTestDB() + defer util.CloseTestDB(dir, db) + r.SetLocalDB(local) + kvSet, err := r.ExecLocal(tx, &types.ReceiptData{Logs: []*types.ReceiptLog{{ + Ty: rtypes.TyPendingTxLog, Log: types.Encode(&rtypes.PendingTx{ActionType: rtypes.TyConfirmAction})}}}, 0) + require.Nil(t, err) + testSetKV(t, db, kvSet, false) + + pendTx := &rtypes.PendingTx{} + key := formatPendingTxKey(0, 0) + require.Nil(t, readDB(local, key, pendTx)) + require.True(t, pendTx.Confirmed) + heightData := &types.Int64{} + require.Nil(t, readDB(local, []byte(confirmedHeightKey), heightData)) + require.Equal(t, int64(1), heightData.Data) + + kvSet, err = r.ExecDelLocal(tx, nil, 0) + require.Nil(t, err) + testSetKV(t, db, kvSet, true) + _, err = db.Get(key) + require.Equal(t, types.ErrNotFound, err) + _, err = db.Get([]byte(confirmedHeightKey)) + require.Equal(t, types.ErrNotFound, err) +} + +func TestRgbx_ExecLocal_DepositAsset(t *testing.T) { + r := newRgbx() + tx, err := r.GetExecutorType().CreateTransaction(rtypes.NameDepositAssetAction, &rtypes.DepositAsset{}) + require.Nil(t, err) + + dir, db, local := util.CreateTestDB() + defer util.CloseTestDB(dir, db) + r.SetLocalDB(local) + kvSet, err := r.ExecLocal(tx, &types.ReceiptData{Logs: []*types.ReceiptLog{{ + Ty: rtypes.TyPendingTxLog, Log: types.Encode(&rtypes.PendingTx{ActionType: rtypes.TyDepositAsset})}}}, 0) + require.Nil(t, err) + testSetKV(t, db, kvSet, false) + + pendTx := &rtypes.PendingTx{} + key := formatPendingTxKey(0, 0) + require.Equal(t, types.ErrNotFound, readDB(local, key, pendTx)) + + kvSet, err = r.ExecDelLocal(tx, nil, 0) + require.Nil(t, err) + testSetKV(t, db, kvSet, true) + _, err = db.Get(key) + require.Equal(t, types.ErrNotFound, err) +} + +func TestRgbx_ExecLocal_WithdrawAsset(t *testing.T) { + r := newRgbx() + tx, err := r.GetExecutorType().CreateTransaction(rtypes.NameWithdrawAssetAction, &rtypes.WithdrawAsset{}) + require.Nil(t, err) + + dir, db, local := util.CreateTestDB() + defer util.CloseTestDB(dir, db) + r.SetLocalDB(local) + kvSet, err := r.ExecLocal(tx, &types.ReceiptData{Logs: []*types.ReceiptLog{{ + Ty: rtypes.TyPendingTxLog, Log: types.Encode(&rtypes.PendingTx{ActionType: rtypes.TyWithDrawAsset})}}}, 0) + require.Nil(t, err) + testSetKV(t, db, kvSet, false) + + pendTx := &rtypes.PendingTx{} + key := formatPendingTxKey(0, 0) + require.Nil(t, readDB(local, key, pendTx)) + require.Equal(t, int32(rtypes.TyWithDrawAsset), pendTx.ActionType) + + kvSet, err = r.ExecDelLocal(tx, nil, 0) + require.Nil(t, err) + testSetKV(t, db, kvSet, true) + _, err = db.Get(key) + require.Equal(t, types.ErrNotFound, err) +} diff --git a/plugin/dapp/rgbx/executor/exec_test.go b/plugin/dapp/rgbx/executor/exec_test.go new file mode 100644 index 0000000000..44b7c63aa2 --- /dev/null +++ b/plugin/dapp/rgbx/executor/exec_test.go @@ -0,0 +1,239 @@ +package executor + +import ( + "testing" + + "github.com/33cn/chain33/client/mocks" + "github.com/33cn/chain33/system/dapp" + "github.com/33cn/chain33/types" + "github.com/33cn/chain33/util" + paratypes "github.com/33cn/plugin/plugin/dapp/paracross/types" + rtypes "github.com/33cn/plugin/plugin/dapp/rgbx/types" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +func testExec(t *testing.T, driver dapp.Driver, actionName string, action types.Message, expectErr error, index int) *types.Receipt { + + tx, err := driver.GetExecutorType().CreateTransaction(actionName, action) + require.Nilf(t, err, "testcase %d", index) + tx.Sign(types.SECP256K1, testPriv) + recp, err := driver.Exec(tx, 0) + require.Equalf(t, expectErr, err, "testcase %d", index) + return recp +} + +func TestRgbx_Exec_Mint(t *testing.T) { + + r := newRgbx() + mint := &rtypes.MintAsset{} + testExec(t, r, rtypes.NameMintAction, mint, nil, 0) +} + +func TestRgbx_Exec_Transfer(t *testing.T) { + + r := newRgbx() + addr2, _ := util.Genaddress() + utxoAddr := "74503993e7c8d4280f6fbb99ae5aaa92231a1981a358e40f97e2b4f4dfbea13c:0" + tcArr := []*testCase{ + { + expectErr: nil, + action: &rtypes.TransferAsset{FromUtxo: utxoAddr}, + }, + { + expectErr: ErrAssetNotExist, + action: &rtypes.TransferAsset{Symbol: "test"}, + }, + { + expectErr: nil, + action: &rtypes.TransferAsset{Symbol: "collect"}, + }, + { + expectErr: nil, + action: &rtypes.TransferAsset{Symbol: "normal", Amount: 1}, + }, + { + expectErr: types.ErrNoBalance, + action: &rtypes.TransferAsset{Symbol: "normal", Amount: 1}, + }, + { + expectErr: nil, + action: &rtypes.TransferAsset{Symbol: "xbtc", To: addr2, Amount: 1}, + }, + } + + dir, state, _ := util.CreateTestDB() + defer util.CloseTestDB(dir, state) + api := &mocks.QueueProtocolAPI{} + r.SetAPI(api) + cfg := types.NewChain33Config(types.GetDefaultCfgstring()) + api.On("GetConfig").Return(cfg) + r.SetStateDB(state) + require.Nil(t, state.Set(formatAssetKey("normal"), types.Encode(&rtypes.RgbxAsset{}))) + require.Nil(t, state.Set(formatAssetKey("collect"), types.Encode(&rtypes.RgbxAsset{Type: 1}))) + + acc, err := r.(*rgbx).newAccount("normal") + require.Nil(t, err) + _, err = acc.Mint(testCommitAddr, 1) + require.Nil(t, err) + crossAcc, err := r.(*rgbx).newAccount("xbtc") + require.Nil(t, err) + _, err = crossAcc.Mint(testCommitAddr, 1) + require.Nil(t, err) + + for idx, tc := range tcArr { + testExec(t, r, rtypes.NameTransferAction, tc.action, tc.expectErr, idx) + } +} + +func TestRgbx_Exec_Confirm(t *testing.T) { + + r := newRgbx() + addr, _ := util.Genaddress() + utxoAddr := "74503993e7c8d4280f6fbb99ae5aaa92231a1981a358e40f97e2b4f4dfbea13c:0" + normal, normal1, collect, collect1 := "normal", "normal1", "collect", "collect1" + + mintScript, _ := txscript.NullDataScript([]byte(normal)) + mintScript1, _ := txscript.NullDataScript([]byte(collect)) + transferScript, _ := txscript.NullDataScript([]byte(normal1)) + transferScript1, _ := txscript.NullDataScript([]byte(collect1)) + tcArr := []*testCase{ + { + expectErr: nil, + action: &rtypes.ConfirmTx{Timeout: true}, + }, + { + expectErr: nil, + action: &rtypes.ConfirmTx{UtxoProof: &rtypes.UtxoSpendingProof{}}, + }, + { + expectErr: nil, + action: &rtypes.ConfirmTx{ActionType: rtypes.TyMintAction, TxHash: []byte(normal), UtxoProof: &rtypes.UtxoSpendingProof{OpRetOutputPkScript: mintScript}}, + }, + { + expectErr: nil, + action: &rtypes.ConfirmTx{ActionType: rtypes.TyMintAction, TxHash: []byte(collect), UtxoProof: &rtypes.UtxoSpendingProof{OpRetOutputPkScript: mintScript1}}, + }, + { + expectErr: nil, + action: &rtypes.ConfirmTx{TxHash: []byte(normal1), UtxoProof: &rtypes.UtxoSpendingProof{OpRetOutputPkScript: transferScript}}, + }, + { + expectErr: nil, + action: &rtypes.ConfirmTx{TxHash: []byte(collect1), UtxoProof: &rtypes.UtxoSpendingProof{OpRetOutputPkScript: transferScript1}}, + }, + } + + dir, state, _ := util.CreateTestDB() + defer util.CloseTestDB(dir, state) + api := &mocks.QueueProtocolAPI{} + r.SetAPI(api) + cfg := types.NewChain33Config(types.GetDefaultCfgstring()) + api.On("GetConfig").Return(cfg) + r.SetStateDB(state) + + require.Nil(t, state.Set(formatPayloadKey([]byte(normal)), types.Encode(&rtypes.MintAsset{Symbol: normal, TotalAmount: 1}))) + require.Nil(t, state.Set(formatPayloadKey([]byte(collect)), types.Encode(&rtypes.MintAsset{Symbol: collect, Type: 1, TotalAmount: 1}))) + + require.Nil(t, state.Set(formatAssetKey(normal1), types.Encode(&rtypes.RgbxAsset{}))) + require.Nil(t, state.Set(formatAssetKey(collect1), types.Encode(&rtypes.RgbxAsset{Type: 1, Symbol: collect1}))) + + require.Nil(t, state.Set(formatPayloadKey([]byte(normal1)), + types.Encode(&rtypes.TransferAsset{Symbol: normal1, Amount: 1, FromUtxo: utxoAddr, To: addr}))) + require.Nil(t, state.Set(formatPayloadKey([]byte(collect1)), + types.Encode(&rtypes.TransferAsset{Symbol: collect1, To: addr}))) + + accDB, err := r.(*rgbx).newAccount(normal1) + require.Nil(t, err) + _, err = accDB.Mint(utxoAddr, 2) + require.Nil(t, err) + + for idx, tc := range tcArr { + recp := testExec(t, r, rtypes.NameConfirmAction, tc.action, tc.expectErr, idx) + if len(recp.GetKV()) > 0 { + util.SaveKVList(state, recp.KV) + } + } + // check mint + asset := &rtypes.RgbxAsset{} + require.Nil(t, readDB(state, formatAssetKey(normal), asset)) + require.Equal(t, formatSymbol(normal), asset.Symbol) + require.Nil(t, readDB(state, formatAssetKey(collect), asset)) + require.Equal(t, formatSymbol(collect), asset.Symbol) + require.Equal(t, rtypes.Collectible, rtypes.AssetType(asset.Type)) + owner := rtypes.FormatUtxo(chainhash.DoubleHashH(nil).String(), 1) + require.Equal(t, owner, asset.Owner) + + // check transfer + require.Nil(t, readDB(state, formatAssetKey(collect1), asset)) + require.Equal(t, addr, asset.Owner) + require.Equal(t, int64(0), accDB.LoadAccount(utxoAddr).Balance) + require.Equal(t, int64(1), accDB.LoadAccount(addr).Balance) + changeAddr := rtypes.FormatUtxo(chainhash.DoubleHashH(nil).String(), 1) + require.Equal(t, int64(1), accDB.LoadAccount(changeAddr).Balance) +} + +func TestRgbx_Exec_Deposit(t *testing.T) { + r := newRgbx() + deposit := &rtypes.DepositAsset{ + Amount: 100, + DepositAddress: testCommitAddr, + AssetSymbol: "btc", + TxProof: &rtypes.BtcTxProof{}, + } + dir, state, _ := util.CreateTestDB() + defer util.CloseTestDB(dir, state) + api := &mocks.QueueProtocolAPI{} + r.SetAPI(api) + cfg := types.NewChain33Config(types.GetDefaultCfgstring()) + api.On("GetConfig").Return(cfg) + r.SetStateDB(state) + testExec(t, r, rtypes.NameDepositAssetAction, deposit, nil, 0) +} + +func TestRgbx_Exec_Withdraw(t *testing.T) { + r := newRgbx() + withdraw := &rtypes.WithdrawAsset{ + Amount: 600, + FeeRate: 10, + DestinationAddr: "tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kygt080", + AssetSymbol: "BTC", + } + dir, state, _ := util.CreateTestDB() + defer util.CloseTestDB(dir, state) + api := &mocks.QueueProtocolAPI{} + r.SetAPI(api) + cfg := types.NewChain33Config(types.GetDefaultCfgstring()) + api.On("GetConfig").Return(cfg) + r.SetStateDB(state) + // withdraw 使用 newAccount 直接操作账户,symbol 为 "BTC" + // 但为了测试通过,需要设置 cross chain info 和账户余额 + require.NoError(t, state.Set(formatCrossChainInfoKey("BTC"), types.Encode(&rtypes.CrossChainInfo{AssetSymbol: "BTC"}))) + acc, err := r.(*rgbx).newAccount("xBTC") + require.Nil(t, err) + _, err = acc.Mint(testCommitAddr, 1000) + require.Nil(t, err) + testExec(t, r, rtypes.NameWithdrawAssetAction, withdraw, nil, 0) +} + +func TestRgbx_Exec_CommitDKG(t *testing.T) { + r := newRgbx() + dkgAddr, pkScript := newTestnetWitnessAddr(t) + commit := &rtypes.CommitDKG{AssetSymbol: "btc", DkgAddress: dkgAddr, PkScript: pkScript} + + dir, state, _ := util.CreateTestDB() + defer util.CloseTestDB(dir, state) + api := &mocks.QueueProtocolAPI{} + r.SetAPI(api) + cfg := types.NewChain33Config(types.GetDefaultCfgstring()) + api.On("GetConfig").Return(cfg) + api.On("Query", paratypes.ParaX, "GetNodeGroupStatus", mock.Anything).Return( + ¶types.ParaNodeGroupStatus{TargetAddrs: testCommitAddr}, nil) + r.SetStateDB(state) + + recp := testExec(t, r, rtypes.NameCommitDKGAction, commit, nil, 0) + require.NotNil(t, recp) + require.NotEmpty(t, recp.KV) +} diff --git a/plugin/dapp/rgbx/executor/exec_transfer_confirm_test.go b/plugin/dapp/rgbx/executor/exec_transfer_confirm_test.go new file mode 100644 index 0000000000..359d0484d3 --- /dev/null +++ b/plugin/dapp/rgbx/executor/exec_transfer_confirm_test.go @@ -0,0 +1,181 @@ +package executor + +import ( + "testing" + + "github.com/33cn/chain33/client/mocks" + "github.com/33cn/chain33/types" + "github.com/33cn/chain33/util" + rtypes "github.com/33cn/plugin/plugin/dapp/rgbx/types" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/stretchr/testify/require" +) + +func Test_rgbx_transferAsset(t *testing.T) { + r := newRgbx() + addr, _ := util.Genaddress() + utxoAddr := "74503993e7c8d4280f6fbb99ae5aaa92231a1981a358e40f97e2b4f4dfbea13c:0" + spendHash := chainhash.DoubleHashH([]byte("spend")).String() + + dir, state, _ := util.CreateTestDB() + defer util.CloseTestDB(dir, state) + api := &mocks.QueueProtocolAPI{} + r.SetAPI(api) + cfg := types.NewChain33Config(types.GetDefaultCfgstring()) + api.On("GetConfig").Return(cfg) + r.SetStateDB(state) + + var err error + require.NoError(t, state.Set(formatPayloadKey([]byte("xc")), types.Encode(&rtypes.TransferAsset{ + Symbol: "xn", + }))) + _, err = r.(*rgbx).transferAsset( + &rtypes.ConfirmTx{ + TxHash: []byte("xc"), + UtxoProof: &rtypes.UtxoSpendingProof{OpRetOutputIdx: 0}, + }, + "txH", "cH", spendHash, + ) + require.Equal(t, types.ErrNotSupport, err) + + _, err = r.(*rgbx).transferAsset( + &rtypes.ConfirmTx{ + TxHash: []byte("missing"), + UtxoProof: &rtypes.UtxoSpendingProof{OpRetOutputIdx: 0}, + }, + "txH", "cH", spendHash, + ) + require.Error(t, err) + + require.NoError(t, state.Set(formatPayloadKey([]byte("p0")), types.Encode(&rtypes.TransferAsset{ + Symbol: "nosuch", Amount: 1, FromUtxo: utxoAddr, To: addr, + }))) + _, err = r.(*rgbx).transferAsset( + &rtypes.ConfirmTx{TxHash: []byte("p0"), UtxoProof: &rtypes.UtxoSpendingProof{OpRetOutputIdx: 0}}, + "txH", "cH", spendHash, + ) + require.Equal(t, ErrAssetNotExist, err) + + require.NoError(t, state.Set(formatAssetKey("col"), types.Encode(&rtypes.RgbxAsset{Symbol: "col", Type: 1}))) + require.NoError(t, state.Set(formatPayloadKey([]byte("p1")), types.Encode(&rtypes.TransferAsset{ + Symbol: "col", To: addr, Amount: 1, + }))) + recp, err := r.(*rgbx).transferAsset( + &rtypes.ConfirmTx{TxHash: []byte("p1"), UtxoProof: &rtypes.UtxoSpendingProof{OpRetOutputIdx: 0}}, + "txH", "cH", spendHash, + ) + require.NoError(t, err) + require.NotEmpty(t, recp.KV) + + require.NoError(t, state.Set(formatAssetKey("norm"), types.Encode(&rtypes.RgbxAsset{}))) + acc, err := r.(*rgbx).newAccount("norm") + require.NoError(t, err) + _, err = acc.Mint(utxoAddr, 5) + require.NoError(t, err) + require.NoError(t, state.Set(formatPayloadKey([]byte("p2")), types.Encode(&rtypes.TransferAsset{ + Symbol: "norm", Amount: 5, FromUtxo: utxoAddr, To: addr, + }))) + recp, err = r.(*rgbx).transferAsset( + &rtypes.ConfirmTx{TxHash: []byte("p2"), UtxoProof: &rtypes.UtxoSpendingProof{OpRetOutputIdx: 0}}, + "txH", "cH", spendHash, + ) + require.NoError(t, err) + require.NotEmpty(t, recp.KV) + + require.NoError(t, state.Set(formatPayloadKey([]byte("p3")), types.Encode(&rtypes.TransferAsset{ + Symbol: "norm", Amount: 3, FromUtxo: utxoAddr, To: addr, ChangeAddr: addr, + }))) + _, err = acc.Mint(utxoAddr, 10) + require.NoError(t, err) + recp, err = r.(*rgbx).transferAsset( + &rtypes.ConfirmTx{TxHash: []byte("p3"), UtxoProof: &rtypes.UtxoSpendingProof{OpRetOutputIdx: 0}}, + "txH", "cH", spendHash, + ) + require.NoError(t, err) + require.NotEmpty(t, recp.KV) + + require.NoError(t, state.Set(formatPayloadKey([]byte("p4")), types.Encode(&rtypes.TransferAsset{ + Symbol: "norm", Amount: 9999, FromUtxo: utxoAddr, To: addr, + }))) + _, err = r.(*rgbx).transferAsset( + &rtypes.ConfirmTx{TxHash: []byte("p4"), UtxoProof: &rtypes.UtxoSpendingProof{OpRetOutputIdx: 0}}, + "txH", "cH", spendHash, + ) + require.Error(t, err) +} + +func Test_rgbx_transferAsset_defaultChangeUtxo(t *testing.T) { + r := newRgbx() + addr, _ := util.Genaddress() + utxoAddr := "74503993e7c8d4280f6fbb99ae5aaa92231a1981a358e40f97e2b4f4dfbea13c:0" + spendHash := chainhash.DoubleHashH([]byte("x")).String() + changeUtxo := rtypes.FormatUtxo(spendHash, 1) + + dir, state, _ := util.CreateTestDB() + defer util.CloseTestDB(dir, state) + api := &mocks.QueueProtocolAPI{} + r.SetAPI(api) + api.On("GetConfig").Return(types.NewChain33Config(types.GetDefaultCfgstring())) + r.SetStateDB(state) + + require.NoError(t, state.Set(formatAssetKey("norm2"), types.Encode(&rtypes.RgbxAsset{}))) + acc, err := r.(*rgbx).newAccount("norm2") + require.NoError(t, err) + _, err = acc.Mint(utxoAddr, 10) + require.NoError(t, err) + require.NoError(t, state.Set(formatPayloadKey([]byte("pch")), types.Encode(&rtypes.TransferAsset{ + Symbol: "norm2", Amount: 3, FromUtxo: utxoAddr, To: addr, + }))) + recp, err := r.(*rgbx).transferAsset( + &rtypes.ConfirmTx{ + TxHash: []byte("pch"), + UtxoProof: &rtypes.UtxoSpendingProof{OpRetOutputIdx: 0}, + }, + "txH", "cH", spendHash, + ) + require.NoError(t, err) + require.NotEmpty(t, recp.KV) + require.Equal(t, int64(7), acc.LoadAccount(changeUtxo).Balance) +} + +func Test_rgbx_confirmWithdrawSettlement(t *testing.T) { + r := newRgbx() + wHash := []byte("withdraw1") + + dir, state, _ := util.CreateTestDB() + defer util.CloseTestDB(dir, state) + api := &mocks.QueueProtocolAPI{} + r.SetAPI(api) + api.On("GetConfig").Return(types.NewChain33Config(types.GetDefaultCfgstring())) + r.SetStateDB(state) + + _, err := r.(*rgbx).confirmWithdrawSettlement( + &rtypes.ConfirmTx{TxHash: wHash}, + "txH", "cH", + ) + require.Error(t, err) + + // confirmWithdrawSettlement 使用 newAccount,symbol 直接为 "BTC" + withdraw := &rtypes.WithdrawAsset{AssetSymbol: "BTC", Amount: 100} + require.NoError(t, state.Set(formatPayloadKey(wHash), types.Encode(withdraw))) + acc, err := r.(*rgbx).newAccount("xBTC") + require.NoError(t, err) + lockAddr := r.(*rgbx).crossChainLockAddress(acc) + _, err = acc.Mint(lockAddr, 100) + require.NoError(t, err) + + recp, err := r.(*rgbx).confirmWithdrawSettlement( + &rtypes.ConfirmTx{TxHash: wHash}, + "txH", "cH", + ) + require.NoError(t, err) + require.NotNil(t, recp) + + wHash2 := []byte("withdraw2") + require.NoError(t, state.Set(formatPayloadKey(wHash2), types.Encode(&rtypes.WithdrawAsset{AssetSymbol: "BTC", Amount: 500}))) + _, err = r.(*rgbx).confirmWithdrawSettlement( + &rtypes.ConfirmTx{TxHash: wHash2}, + "txH", "cH", + ) + require.Error(t, err) +} diff --git a/plugin/dapp/rgbx/executor/kv.go b/plugin/dapp/rgbx/executor/kv.go new file mode 100644 index 0000000000..6487c64869 --- /dev/null +++ b/plugin/dapp/rgbx/executor/kv.go @@ -0,0 +1,91 @@ +package executor + +import ( + "crypto/sha256" + "strings" + + "github.com/33cn/chain33/common/address" + "github.com/33cn/chain33/common/db" + "github.com/33cn/chain33/system/dapp" + "github.com/33cn/chain33/types" +) + +/* + * 用户合约存取kv数据时,key值前缀需要满足一定规范 + * 即key = keyPrefix + userKey + * 需要字段前缀查询时,使用’-‘作为分割符号 + */ + +const ( + //KeyPrefixStateDB state db key必须前缀 + KeyPrefixStateDB = "mavl-rgbx-" + //KeyPrefixLocalDB local db的key必须前缀 + KeyPrefixLocalDB = "LODB-rgbx-" + + dkgConfirmationsKeyPrefix = KeyPrefixStateDB + "dkg-confirmations-" + crossChainInfoKeyPrefix = KeyPrefixStateDB + "crosschain-info-" + depositUsedKeyPrefix = KeyPrefixStateDB + "deposited-" +) + +func formatDkgConfirmationsKey(dkgResult string) []byte { + return []byte(dkgConfirmationsKeyPrefix + dkgResult) +} + +func formatCrossChainInfoKey(symbol string) []byte { + return []byte(crossChainInfoKeyPrefix + formatSymbol(symbol)) +} + +func formatDepositUsedKey(txData []byte) []byte { + hash := sha256.Sum256(txData) + return append([]byte(depositUsedKeyPrefix), hash[:]...) +} + +func formatSymbol(symbol string) string { + return strings.ToUpper(symbol) +} + +func isCrossChainSymbol(symbol string) bool { + return strings.HasPrefix(formatSymbol(symbol), rgbxCfg.CrossChainAssetPrefix) +} + +func ensureCrossChainSymbol(symbol string) string { + if isCrossChainSymbol(symbol) { + return symbol + } + return formatCrossChainSymbol(symbol) +} + +func formatCrossChainSymbol(symbol string) string { + return rgbxCfg.CrossChainAssetPrefix + formatSymbol(symbol) +} + +func formatPayloadKey(hash []byte) []byte { + return append([]byte(KeyPrefixStateDB+"payload-"), hash...) +} + +func formatAssetKey(symbol string) []byte { + return append([]byte(KeyPrefixStateDB+"asset-"), formatSymbol(symbol)...) +} + +const pendingTxKeyPrefix = KeyPrefixLocalDB + "pendtx-" +const pendingTxByFromPrefix = KeyPrefixLocalDB + "pendbyfrom-" + +func formatPendingTxKey(height, txIndex int64) []byte { + + return []byte(pendingTxKeyPrefix + dapp.HeightIndexStr(height, txIndex)) +} + +func formatPendingTxFromKey(fromAddr string) []byte { + return append([]byte(pendingTxByFromPrefix), address.FormatAddrKey(fromAddr)...) +} + +const confirmedHeightKey = KeyPrefixLocalDB + "confirmed-height" + +func readDB(kdb db.KV, key []byte, result types.Message) error { + + val, err := kdb.Get(key) + if err != nil { + return err + } + return types.Decode(val, result) +} diff --git a/plugin/dapp/rgbx/executor/query.go b/plugin/dapp/rgbx/executor/query.go new file mode 100644 index 0000000000..c0c262c7af --- /dev/null +++ b/plugin/dapp/rgbx/executor/query.go @@ -0,0 +1,155 @@ +package executor + +import ( + "errors" + + "github.com/33cn/chain33/types" + rtypes "github.com/33cn/plugin/plugin/dapp/rgbx/types" +) + +const maxListCount = 1000 + +func (r *rgbx) Query_ListPendingTx(req *rtypes.ReqListPendingTx) (types.Message, error) { + + if req.GetCount() <= 0 || req.GetCount() > maxListCount { + return nil, types.ErrInvalidParam + } + startKey := formatPendingTxKey(req.GetStartHeight(), req.GetStartIndex()) + values, err := r.GetLocalDB().List([]byte(pendingTxKeyPrefix), startKey, req.GetCount(), 1) + if err != nil && !errors.Is(err, types.ErrNotFound) { + elog.Error("Query_GetPendingTxs", "list err", err, "req", req.String()) + return nil, err + } + + pendingTxs := &rtypes.PendingTxs{} + for _, v := range values { + tx := &rtypes.PendingTx{} + err := types.Decode(v, tx) + if err != nil { + elog.Error("Query_GetPendingTxs", "decode err", err) + continue + } + // 0 means no end height limit + if req.GetEndHeight() > 0 && tx.GetTxBlockHeight() > req.GetEndHeight() { + break + } + + pendingTxs.PendingList = append(pendingTxs.PendingList, tx) + } + + return pendingTxs, nil +} + +func (r *rgbx) Query_GetConfirmedHeight(_ *types.ReqNil) (types.Message, error) { + + v, err := r.GetLocalDB().Get([]byte(confirmedHeightKey)) + + reply := &types.Int64{} + if errors.Is(err, types.ErrNotFound) { + return reply, nil + } + if err != nil { + elog.Error("Query_GetConfirmedHeight", "get db err", err) + return nil, err + } + + err = types.Decode(v, reply) + if err != nil { + elog.Error("Query_GetConfirmedHeight", "decode err", err) + return nil, err + } + + return reply, nil + +} + +func (r *rgbx) Query_GetAsset(req *types.ReqString) (types.Message, error) { + + symbol := req.GetData() + v, err := r.GetStateDB().Get(formatAssetKey(symbol)) + reply := &rtypes.RgbxAsset{} + if err != nil { + elog.Error("Query_GetAsset", "symbol", symbol, "get db err", err) + return nil, err + } + + err = types.Decode(v, reply) + if err != nil { + elog.Error("Query_GetAsset", "symbol", symbol, "decode err", err) + return nil, err + } + + return reply, nil +} + +func (r *rgbx) Query_GetPendingTx(req *rtypes.ReqGetPendingTx) (types.Message, error) { + + v, err := r.GetLocalDB().Get(formatPendingTxKey(req.GetHeight(), req.GetIndex())) + + reply := &rtypes.PendingTx{} + if err != nil { + elog.Error("Query_GetPendingTx", "height", req.GetHeight(), + "index", req.GetIndex(), "get db err", err) + return nil, err + } + + err = types.Decode(v, reply) + if err != nil { + elog.Error("Query_GetPendingTx", "height", req.GetHeight(), + "index", req.GetIndex(), "decode err", err) + return nil, err + } + + return reply, nil +} + +func (r *rgbx) Query_GetCrossChainInfo(req *types.ReqString) (types.Message, error) { + + symbol := req.GetData() + v, err := r.GetStateDB().Get(formatCrossChainInfoKey(symbol)) + reply := &rtypes.CrossChainInfo{} + if errors.Is(err, types.ErrNotFound) { + return reply, nil + } + if err != nil { + elog.Error("Query_GetCrossChainInfo", "symbol", symbol, "get db err", err) + return nil, err + } + + err = types.Decode(v, reply) + if err != nil { + elog.Error("Query_GetCrossChainInfo", "symbol", symbol, "decode err", err) + return nil, err + } + + return reply, nil +} + +func (r *rgbx) Query_ListPendingTxByFrom(req *types.ReqString) (types.Message, error) { + fromAddr := req.GetData() + if fromAddr == "" { + return nil, types.ErrInvalidParam + } + list := &rtypes.TxBlockIndexList{} + err := readDB(r.GetLocalDB(), formatPendingTxFromKey(fromAddr), list) + if errors.Is(err, types.ErrNotFound) { + return &rtypes.PendingTxs{}, nil + } + if err != nil { + elog.Error("Query_ListPendingTxByFrom", "from", fromAddr, "read list err", err) + return nil, err + } + reply := &rtypes.PendingTxs{} + for _, item := range list.GetBlockIndexList() { + tx := &rtypes.PendingTx{} + err = readDB(r.GetLocalDB(), formatPendingTxKey(item.GetBlockHeight(), item.GetTxIndex()), tx) + if err != nil { + continue + } + if tx.GetConfirmed() { + continue + } + reply.PendingList = append(reply.PendingList, tx) + } + return reply, nil +} diff --git a/plugin/dapp/rgbx/executor/query_test.go b/plugin/dapp/rgbx/executor/query_test.go new file mode 100644 index 0000000000..7e84ca57fb --- /dev/null +++ b/plugin/dapp/rgbx/executor/query_test.go @@ -0,0 +1,139 @@ +package executor + +import ( + "github.com/33cn/chain33/client/mocks" + "github.com/33cn/chain33/types" + "github.com/33cn/chain33/util" + rtypes "github.com/33cn/plugin/plugin/dapp/rgbx/types" + "github.com/stretchr/testify/require" + "testing" +) + +func TestRgbx_Query_ListPendingTx(t *testing.T) { + + r := newRgbx() + dir, db, local := util.CreateTestDB() + defer util.CloseTestDB(dir, db) + r.SetLocalDB(local) + funcName := "ListPendingTx" + _, err := r.Query(funcName, types.Encode(&rtypes.ReqListPendingTx{})) + require.Equal(t, types.ErrInvalidParam, err) + _, err = r.Query(funcName, types.Encode(&rtypes.ReqListPendingTx{Count: maxListCount + 1})) + require.Equal(t, types.ErrInvalidParam, err) + + key1, key2, key3 := formatPendingTxKey(0, 1), formatPendingTxKey(0, 2), formatPendingTxKey(0, 3) + require.Nil(t, db.Set(key1, []byte("invalidData"))) + require.Nil(t, db.Set(key2, types.Encode(&rtypes.PendingTx{TxIndex: 2}))) + require.Nil(t, db.Set(key3, types.Encode(&rtypes.PendingTx{TxIndex: 3}))) + msg, err := r.Query(funcName, types.Encode(&rtypes.ReqListPendingTx{Count: 10})) + require.Nil(t, err) + require.Equal(t, 2, len(msg.(*rtypes.PendingTxs).GetPendingList())) + msg, err = r.Query(funcName, types.Encode(&rtypes.ReqListPendingTx{Count: 10, StartIndex: 3})) + require.Nil(t, err) + require.Equal(t, 0, len(msg.(*rtypes.PendingTxs).GetPendingList())) +} + +func TestRgbx_Query_ListPendingTx_EndHeight(t *testing.T) { + r := newRgbx() + dir, db, local := util.CreateTestDB() + defer util.CloseTestDB(dir, db) + r.SetLocalDB(local) + funcName := "ListPendingTx" + + require.NoError(t, db.Set(formatPendingTxKey(1, 1), types.Encode(&rtypes.PendingTx{ + TxBlockHeight: 1, + TxIndex: 1, + }))) + require.NoError(t, db.Set(formatPendingTxKey(2, 1), types.Encode(&rtypes.PendingTx{ + TxBlockHeight: 2, + TxIndex: 1, + }))) + require.NoError(t, db.Set(formatPendingTxKey(3, 1), types.Encode(&rtypes.PendingTx{ + TxBlockHeight: 3, + TxIndex: 1, + }))) + + msg, err := r.Query(funcName, types.Encode(&rtypes.ReqListPendingTx{Count: 10, EndHeight: 2})) + require.NoError(t, err) + list := msg.(*rtypes.PendingTxs).GetPendingList() + require.Len(t, list, 2) + require.Equal(t, int64(1), list[0].GetTxBlockHeight()) + require.Equal(t, int64(2), list[1].GetTxBlockHeight()) + + msg, err = r.Query(funcName, types.Encode(&rtypes.ReqListPendingTx{Count: 10, EndHeight: 1})) + require.NoError(t, err) + list = msg.(*rtypes.PendingTxs).GetPendingList() + require.Len(t, list, 1) + require.Equal(t, int64(1), list[0].GetTxBlockHeight()) +} + +func TestRgbx_Query_GetPendingTx(t *testing.T) { + r := newRgbx() + dir, db, local := util.CreateTestDB() + defer util.CloseTestDB(dir, db) + r.SetLocalDB(local) + funcName := "GetPendingTx" + require.Nil(t, db.Set(formatPendingTxKey(0, 1), types.Encode(&rtypes.PendingTx{TxIndex: 1}))) + _, err := r.Query(funcName, types.Encode(&rtypes.ReqGetPendingTx{})) + require.Equal(t, types.ErrNotFound, err) + msg, err := r.Query(funcName, types.Encode(&rtypes.ReqGetPendingTx{Index: 1})) + require.Equal(t, int64(1), msg.(*rtypes.PendingTx).GetTxIndex()) +} + +func TestRgbx_Query_GetConfirmedHeight(t *testing.T) { + r := newRgbx() + dir, db, local := util.CreateTestDB() + defer util.CloseTestDB(dir, db) + r.SetLocalDB(local) + funcName := "GetConfirmedHeight" + _, err := r.Query(funcName, nil) + require.Nil(t, err) + require.Nil(t, db.Set([]byte(confirmedHeightKey), types.Encode(&types.Int64{Data: 1}))) + msg, err := r.Query(funcName, nil) + require.Equal(t, int64(1), msg.(*types.Int64).GetData()) +} + +func TestRgbx_Query_GetAsset(t *testing.T) { + + r := newRgbx() + dir, db, local := util.CreateTestDB() + defer util.CloseTestDB(dir, db) + api := &mocks.QueueProtocolAPI{} + r.SetAPI(api) + cfg := types.NewChain33Config(types.GetDefaultCfgstring()) + api.On("GetConfig").Return(cfg) + r.SetStateDB(local) + funcName := "GetAsset" + _, err := r.Query(funcName, nil) + require.Equal(t, types.ErrNotFound, err) + require.Nil(t, db.Set(formatAssetKey("test"), types.Encode(&rtypes.RgbxAsset{Symbol: "test"}))) + msg, err := r.Query(funcName, types.Encode(&types.ReqString{Data: "test"})) + require.Equal(t, "test", msg.(*rtypes.RgbxAsset).Symbol) +} + +func TestRgbx_Query_GetCrossChainInfo(t *testing.T) { + r := newRgbx() + dir, db, local := util.CreateTestDB() + defer util.CloseTestDB(dir, db) + api := &mocks.QueueProtocolAPI{} + r.SetAPI(api) + cfg := types.NewChain33Config(types.GetDefaultCfgstring()) + api.On("GetConfig").Return(cfg) + r.SetStateDB(local) + funcName := "GetCrossChainInfo" + + msg, err := r.Query(funcName, types.Encode(&types.ReqString{Data: "btc"})) + require.NoError(t, err) + require.Equal(t, "", msg.(*rtypes.CrossChainInfo).GetAssetSymbol()) + + info := &rtypes.CrossChainInfo{AssetSymbol: "BTC", TssAddress: "tb1qxx"} + require.NoError(t, db.Set(formatCrossChainInfoKey("btc"), types.Encode(info))) + msg, err = r.Query(funcName, types.Encode(&types.ReqString{Data: "btc"})) + require.NoError(t, err) + require.Equal(t, "BTC", msg.(*rtypes.CrossChainInfo).GetAssetSymbol()) + require.Equal(t, "tb1qxx", msg.(*rtypes.CrossChainInfo).GetTssAddress()) + + require.NoError(t, db.Set(formatCrossChainInfoKey("bad"), []byte("not-protobuf"))) + _, err = r.Query(funcName, types.Encode(&types.ReqString{Data: "bad"})) + require.Error(t, err) +} diff --git a/plugin/dapp/rgbx/executor/rgbx.go b/plugin/dapp/rgbx/executor/rgbx.go new file mode 100644 index 0000000000..20448afbb9 --- /dev/null +++ b/plugin/dapp/rgbx/executor/rgbx.go @@ -0,0 +1,85 @@ +package executor + +import ( + "strings" + "sync" + + log "github.com/33cn/chain33/common/log/log15" + drivers "github.com/33cn/chain33/system/dapp" + "github.com/33cn/chain33/types" + rgbxtypes "github.com/33cn/plugin/plugin/dapp/rgbx/types" +) + +/* + * 执行器相关定义 + * 重载基类相关接口 + */ + +var ( + //日志 + elog = log.New("module", "rgbx.executor") + cfgInitOnce sync.Once +) + +var driverName = rgbxtypes.RgbxX + +type config struct { + CommitAddress string `json:"commitAddress"` + CrossChainAssetPrefix string `json:"crossChainAssetPrefix"` + GuardianParachainTitle string `json:"guardianParachainTitle"` +} + +var rgbxCfg = config{} + +func initCfg(sub []byte) { + + cfgInitOnce.Do(func() { + types.MustDecode(sub, &rgbxCfg) + prefix := strings.TrimSpace(rgbxCfg.CrossChainAssetPrefix) + if prefix == "" { + prefix = "X" + } + rgbxCfg.CrossChainAssetPrefix = formatSymbol(prefix) + rgbxCfg.GuardianParachainTitle = strings.TrimSpace(rgbxCfg.GuardianParachainTitle) + if rgbxCfg.GuardianParachainTitle == "" { + rgbxCfg.GuardianParachainTitle = defaultGuardianParachainTitle + } + }) +} + +// Init register dapp +func Init(_ string, cfg *types.Chain33Config, sub []byte) { + initCfg(sub) + drivers.Register(cfg, GetName(), newRgbx, cfg.GetDappFork(driverName, "Enable")) + InitExecType() +} + +// InitExecType Init Exec Type +func InitExecType() { + ety := types.LoadExecutorType(driverName) + ety.InitFuncList(types.ListMethod(&rgbx{})) +} + +type rgbx struct { + drivers.DriverBase +} + +func newRgbx() drivers.Driver { + t := &rgbx{} + t.SetChild(t) + t.SetExecutorType(types.LoadExecutorType(driverName)) + return t +} + +// GetName get driver name +func GetName() string { + return newRgbx().GetName() +} + +func (r *rgbx) GetDriverName() string { + return driverName +} + +func (r *rgbx) ExecutorOrder() int64 { + return drivers.ExecLocalSameTime +} diff --git a/plugin/dapp/rgbx/executor/validate_proof.go b/plugin/dapp/rgbx/executor/validate_proof.go new file mode 100644 index 0000000000..04f4b22b21 --- /dev/null +++ b/plugin/dapp/rgbx/executor/validate_proof.go @@ -0,0 +1,233 @@ +package executor + +import ( + "bytes" + "encoding/hex" + "fmt" + + "github.com/33cn/chain33/common/merkle" + "github.com/33cn/chain33/types" + ltypes "github.com/33cn/plugin/plugin/dapp/lightclient/lighttypes" + rtypes "github.com/33cn/plugin/plugin/dapp/rgbx/types" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" +) + +const withdrawCommitmentPrefix = "rgbx:withdraw:" +const depositCommitmentPrefix = "rgbx:deposit:" + +func btcProof2String(txProof *rtypes.BtcTxProof) string { + return fmt.Sprintf("btcBlockHeight: %d, btcBlockHash: %s, btcTxIndex: %d, btcTxData: %s", + txProof.GetBlockHeight(), txProof.GetBlockHash(), + txProof.GetTxIndex(), hex.EncodeToString(txProof.GetTxData())) +} + +func merkelProof2String(merkleProof [][]byte) string { + result := "" + for _, proof := range merkleProof { + result += hex.EncodeToString(proof) + "|" + } + return result +} + +func (r *rgbx) checkWithdrawConfirm(txHash, confirmHash string, confirm *rtypes.ConfirmTx, pendingTx *rtypes.PendingTx) error { + btcTx, err := r.validateBtcTxProof(txHash, confirm.GetBtcTxProof()) + if err != nil { + elog.Error("checkWithdrawConfirm validate btc tx proof", "txHash", txHash, "confirmHash", confirmHash, + "btcProof", btcProof2String(confirm.GetBtcTxProof()), "err", err) + return err + } + if !hasWithdrawCommitment(btcTx, confirm.GetTxHash()) { + elog.Error("checkWithdrawConfirm commitment mismatch", "txHash", txHash, "confirmHash", confirmHash, + "btcProof", btcProof2String(confirm.GetBtcTxProof())) + return ErrInvalidBtcProofCommitment + } + if err = r.validateWithdrawTxContent(txHash, pendingTx, btcTx); err != nil { + return err + } + return nil +} + +func (r *rgbx) validateDepositTxContent(txHash string, deposit *rtypes.DepositAsset, btcTx *wire.MsgTx) error { + info, err := r.getCrossChainInfo(deposit.GetAssetSymbol()) + if err != nil { + elog.Error("validateDepositTxContent getCrossChainInfo", "txHash", txHash, "symbol", deposit.GetAssetSymbol(), "err", err) + return ErrGetCrossChainInfo + } + var amount int64 + for _, out := range btcTx.TxOut { + if bytes.Equal(out.PkScript, info.GetPkScript()) { + amount += out.Value + } + } + if amount != deposit.GetAmount() { + elog.Error("validateDepositTxContent amount mismatch", "txHash", txHash, + "expect", deposit.GetAmount(), "actual", amount) + return ErrInvalidDepositAmount + } + return nil +} + +func (r *rgbx) validateWithdrawTxContent(txHash string, pendingTx *rtypes.PendingTx, btcTx *wire.MsgTx) error { + if pendingTx == nil { + return ErrPendingTxNotExist + } + info, err := r.getCrossChainInfo(pendingTx.GetAssetSymbol()) + if err != nil { + elog.Error("validateWithdrawTxContent getCrossChainInfo", "txHash", txHash, "symbol", pendingTx.GetAssetSymbol(), "err", err) + return ErrInvalidCrossChainInfo + } + destScript, err := r.decodeBtcAddressScript(pendingTx.GetTargetAddress()) + if err != nil { + elog.Error("validateWithdrawTxContent decode target address", "txHash", txHash, "address", pendingTx.GetTargetAddress(), "err", err) + return ErrInvalidWithdrawDestination + } + var destAmount int64 + for _, out := range btcTx.TxOut { + if len(out.PkScript) > 0 && out.PkScript[0] == txscript.OP_RETURN { + continue + } + if bytes.Equal(out.PkScript, destScript) { + destAmount += out.Value + continue + } + if !bytes.Equal(out.PkScript, info.GetPkScript()) { + elog.Error("validateWithdrawTxContent unexpected output script", "txHash", txHash, "script", hex.EncodeToString(out.PkScript)) + return ErrInvalidWithdrawDestinationScript + } + } + if destAmount <= 0 || destAmount > pendingTx.GetAmount() { + elog.Error("validateWithdrawTxContent dest amount invalid", "txHash", txHash, + "destAmount", destAmount, "expectAmount", pendingTx.GetAmount()) + return ErrInvalidWithdrawAmount + } + + return nil +} + +func (r *rgbx) getCrossChainInfo(symbol string) (*rtypes.CrossChainInfo, error) { + if symbol == "" { + symbol = rtypes.BTCSymbol + } + info := &rtypes.CrossChainInfo{} + err := readDB(r.GetStateDB(), formatCrossChainInfoKey(symbol), info) + return info, err +} + +func (r *rgbx) decodeBtcAddressScript(addr string) ([]byte, error) { + if addr == "" { + return nil, types.ErrInvalidAddress + } + netName, err := r.getBtcNetName() + if err != nil { + return nil, err + } + params := ltypes.GetBtcChainParams(netName) + decoded, err := btcutil.DecodeAddress(addr, params) + if err != nil { + return nil, err + } + return txscript.PayToAddrScript(decoded) +} + +func (r *rgbx) validateBtcTxProof(txHash string, proof *rtypes.BtcTxProof) (*wire.MsgTx, error) { + if proof == nil || len(proof.GetTxData()) == 0 { + elog.Error("validateBtcTxProof empty btc proof", "txHash", txHash) + return nil, ErrInvalidBtcTxProof + } + var btcTx wire.MsgTx + if err := btcTx.DeserializeNoWitness(bytes.NewReader(proof.GetTxData())); err != nil { + elog.Error("validateBtcTxProof decode btc tx", "txHash", txHash, "err", err) + return nil, ErrInvalidBtcTxProof + } + + blockHashStr := proof.GetBlockHash() + header, err := r.getBtcHeader(proof.GetBlockHeight()) + if err != nil { + elog.Error("validateBtcTxProof get btc header", "txHash", txHash, + "blockHash", blockHashStr, "height", proof.GetBlockHeight(), "err", err) + return nil, ErrGetBtcHeader + } + if header.GetHash() != blockHashStr || header.GetHeight() != proof.GetBlockHeight() { + elog.Error("validateBtcTxProof header mismatch", "txHash", txHash, + "expectHash", blockHashStr, "actualHash", header.GetHash(), + "expectHeight", proof.GetBlockHeight(), "actualHeight", header.GetHeight()) + return nil, ErrInvalidBtcProofBlock + } + + txID := btcTx.TxHash() + merkleRoot := merkle.GetMerkleRootFromBranch(proof.GetMerkleProof(), txID.CloneBytes(), proof.GetTxIndex()) + headerMerkleRoot, err := chainhash.NewHashFromStr(header.GetMerkleRoot()) + if err != nil { + elog.Error("validateBtcTxProof invalid header merkleRoot", "txHash", txHash, + "merkleRoot", header.GetMerkleRoot(), "err", err) + return nil, ErrInvalidBtcProofMerkle + } + if !bytes.Equal(merkleRoot, headerMerkleRoot.CloneBytes()) { + elog.Error("validateBtcTxProof merkle root not match", "txHash", txHash, + "expectMerkleRoot", header.GetMerkleRoot(), "actualMerkleRoot", hex.EncodeToString(merkleRoot), + "merkleProof", merkelProof2String(proof.GetMerkleProof())) + return nil, ErrInvalidBtcProofMerkle + } + return &btcTx, nil +} + +func (r *rgbx) getBtcNetName() (string, error) { + msg, err := r.GetAPI().Query(ltypes.LightclientX, "GetBtcNetName", &types.ReqNil{}) + if err != nil { + elog.Error("getBtcNetName query", "err", err) + return "", err + } + return msg.(*types.ReplyString).Data, nil +} + +func (r *rgbx) getBtcHeader(height uint64) (*ltypes.BtcHeader, error) { + + msg, err := r.GetAPI().Query(ltypes.LightclientX, "GetBtcHeader", <ypes.ReqGetBtcHeader{Height: height}) + if err != nil { + elog.Error("getBtcHeader query", "height", height, "err", err) + return nil, err + } + header, ok := msg.(*ltypes.BtcHeader) + if !ok || header == nil { + elog.Error("getBtcHeader invalid header", "height", height) + return nil, types.ErrInvalidParam + } + return header, nil +} + +func hasWithdrawCommitment(tx *wire.MsgTx, chain33TxHash []byte) bool { + expectData := append([]byte(withdrawCommitmentPrefix), chain33TxHash...) + return hasExpectedOpReturnData(tx, expectData) +} + +func hasDepositCommitment(tx *wire.MsgTx, depositAddress string) bool { + if rtypes.IsUtxoAddress(depositAddress) { + if len(tx.TxIn) > 0 { + firstInputUtxo := tx.TxIn[0].PreviousOutPoint + return depositAddress == rtypes.FormatUtxo(firstInputUtxo.Hash.String(), firstInputUtxo.Index) + } + return false + } + expectData := append([]byte(depositCommitmentPrefix), []byte(depositAddress)...) + return hasExpectedOpReturnData(tx, expectData) +} + +func hasExpectedOpReturnData(tx *wire.MsgTx, expectData []byte) bool { + expectScript, err := txscript.NullDataScript(expectData) + if err != nil { + elog.Error("hasExpectedOpReturnData null data script", "err", err) + return false + } + for _, out := range tx.TxOut { + if len(out.PkScript) == 0 || out.PkScript[0] != txscript.OP_RETURN { + continue + } + if bytes.Equal(out.PkScript, expectScript) { + return true + } + } + return false +} diff --git a/plugin/dapp/rgbx/executor/validate_proof_test.go b/plugin/dapp/rgbx/executor/validate_proof_test.go new file mode 100644 index 0000000000..c6ea15b24e --- /dev/null +++ b/plugin/dapp/rgbx/executor/validate_proof_test.go @@ -0,0 +1,369 @@ +package executor + +import ( + "bytes" + "errors" + "testing" + + "github.com/33cn/chain33/client/mocks" + "github.com/33cn/chain33/common/merkle" + "github.com/33cn/chain33/types" + "github.com/33cn/chain33/util" + ltypes "github.com/33cn/plugin/plugin/dapp/lightclient/lighttypes" + rtypes "github.com/33cn/plugin/plugin/dapp/rgbx/types" + "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/require" +) + +func Test_btcProof2String_and_merkelProof2String(t *testing.T) { + proof := &rtypes.BtcTxProof{ + BlockHeight: 12, + BlockHash: "abc", + TxIndex: 3, + TxData: []byte{1, 2}, + } + s := btcProof2String(proof) + require.Contains(t, s, "12") + require.Contains(t, s, "abc") + require.Contains(t, s, "3") + require.Contains(t, s, "0102") + + require.Equal(t, "", merkelProof2String(nil)) + require.Contains(t, merkelProof2String([][]byte{{0xaa}, {0xbb}}), "aa") +} + +func Test_hasExpectedOpReturnData_and_commitments(t *testing.T) { + data := []byte("rgbx:test") + script, err := txscript.NullDataScript(data) + require.NoError(t, err) + tx := &wire.MsgTx{} + tx.TxOut = append(tx.TxOut, wire.NewTxOut(0, script)) + require.True(t, hasExpectedOpReturnData(tx, data)) + require.False(t, hasExpectedOpReturnData(tx, []byte("other"))) + + chain33Hash := []byte{9, 8, 7} + wdData := append([]byte(withdrawCommitmentPrefix), chain33Hash...) + wdScript, err := txscript.NullDataScript(wdData) + require.NoError(t, err) + txWd := &wire.MsgTx{} + txWd.TxOut = append(txWd.TxOut, wire.NewTxOut(0, wdScript)) + require.True(t, hasWithdrawCommitment(txWd, chain33Hash)) + + depAddr, _ := util.Genaddress() + depData := append([]byte(depositCommitmentPrefix), []byte(depAddr)...) + depScript, err := txscript.NullDataScript(depData) + require.NoError(t, err) + txDep := &wire.MsgTx{} + txDep.TxOut = append(txDep.TxOut, wire.NewTxOut(0, depScript)) + require.True(t, hasDepositCommitment(txDep, depAddr)) + + utxo := "74503993e7c8d4280f6fbb99ae5aaa92231a1981a358e40f97e2b4f4dfbea13c:0" + out, err := wire.NewOutPointFromString(utxo) + require.NoError(t, err) + tx2 := &wire.MsgTx{} + tx2.TxIn = []*wire.TxIn{{PreviousOutPoint: *out}} + require.True(t, hasDepositCommitment(tx2, utxo)) + require.False(t, hasDepositCommitment(tx2, "0000000000000000000000000000000000000000000000000000000000000000:1")) +} + +func Test_rgbx_validateDepositTxContent(t *testing.T) { + r := newRgbx() + dir, state, _ := util.CreateTestDB() + defer util.CloseTestDB(dir, state) + api := &mocks.QueueProtocolAPI{} + r.SetAPI(api) + api.On("GetConfig").Return(types.NewChain33Config(types.GetDefaultCfgstring())) + r.SetStateDB(state) + + tssScript := []byte{0x51, 0x52} + deposit := &rtypes.DepositAsset{AssetSymbol: "btc", Amount: 1000} + btcTx := &wire.MsgTx{} + btcTx.TxOut = append(btcTx.TxOut, wire.NewTxOut(1000, tssScript)) + + err := r.(*rgbx).validateDepositTxContent("h1", deposit, btcTx) + require.Equal(t, ErrGetCrossChainInfo, err) + + require.NoError(t, state.Set(formatCrossChainInfoKey("btc"), types.Encode(&rtypes.CrossChainInfo{ + AssetSymbol: "BTC", + PkScript: tssScript, + }))) + + err = r.(*rgbx).validateDepositTxContent("h1", deposit, btcTx) + require.NoError(t, err) + + deposit.Amount = 999 + err = r.(*rgbx).validateDepositTxContent("h1", deposit, btcTx) + require.Equal(t, ErrInvalidDepositAmount, err) +} + +func Test_rgbx_validateWithdrawTxContent(t *testing.T) { + r := newRgbx() + dir, state, _ := util.CreateTestDB() + defer util.CloseTestDB(dir, state) + api := &mocks.QueueProtocolAPI{} + r.SetAPI(api) + api.On("GetConfig").Return(types.NewChain33Config(types.GetDefaultCfgstring())) + api.On("Query", ltypes.LightclientX, "GetBtcNetName", mock.Anything).Return(&types.ReplyString{Data: "testnet3"}, nil) + r.SetStateDB(state) + + destAddr, destScript := newTestnetWitnessAddr(t) + tssScript := []byte{0x51, 0x20, 0xab, 0xcd} + + err := r.(*rgbx).validateWithdrawTxContent("h1", nil, &wire.MsgTx{}) + require.Equal(t, ErrPendingTxNotExist, err) + + pending := &rtypes.PendingTx{AssetSymbol: "btc", TargetAddress: destAddr, Amount: 50000} + err = r.(*rgbx).validateWithdrawTxContent("h1", pending, &wire.MsgTx{}) + require.Equal(t, ErrInvalidCrossChainInfo, err) + + require.NoError(t, state.Set(formatCrossChainInfoKey("btc"), types.Encode(&rtypes.CrossChainInfo{ + AssetSymbol: "BTC", + PkScript: tssScript, + }))) + + pendingBad := &rtypes.PendingTx{AssetSymbol: "btc", TargetAddress: "not-valid", Amount: 50000} + err = r.(*rgbx).validateWithdrawTxContent("h1", pendingBad, &wire.MsgTx{}) + require.Equal(t, ErrInvalidWithdrawDestination, err) + + btcTx := &wire.MsgTx{} + btcTx.TxOut = append(btcTx.TxOut, + wire.NewTxOut(40000, destScript), + wire.NewTxOut(10000, []byte{0x76}), // unexpected script + ) + err = r.(*rgbx).validateWithdrawTxContent("h1", pending, btcTx) + require.Equal(t, ErrInvalidWithdrawDestinationScript, err) + + btcTx2 := &wire.MsgTx{} + btcTx2.TxOut = append(btcTx2.TxOut, + wire.NewTxOut(40000, destScript), + wire.NewTxOut(10000, tssScript), + ) + err = r.(*rgbx).validateWithdrawTxContent("h1", pending, btcTx2) + require.NoError(t, err) + + btcTx3 := &wire.MsgTx{} + btcTx3.TxOut = append(btcTx3.TxOut, wire.NewTxOut(0, destScript)) // destAmount 0 + btcTx3.TxOut = append(btcTx3.TxOut, wire.NewTxOut(60000, tssScript)) + err = r.(*rgbx).validateWithdrawTxContent("h1", pending, btcTx3) + require.Equal(t, ErrInvalidWithdrawAmount, err) + + btcTx4 := &wire.MsgTx{} + btcTx4.TxOut = append(btcTx4.TxOut, wire.NewTxOut(60000, destScript)) // > pending amount + btcTx4.TxOut = append(btcTx4.TxOut, wire.NewTxOut(10000, tssScript)) + err = r.(*rgbx).validateWithdrawTxContent("h1", pending, btcTx4) + require.Equal(t, ErrInvalidWithdrawAmount, err) +} + +func newBtcTxProofFixture(t *testing.T) (*rgbx, *rtypes.BtcTxProof, string) { + t.Helper() + r := newRgbx().(*rgbx) + var btcTx wire.MsgTx + btcTx.Version = 2 + btcTx.TxOut = append(btcTx.TxOut, wire.NewTxOut(1000, []byte{0x51})) + buf := new(bytes.Buffer) + require.NoError(t, btcTx.SerializeNoWitness(buf)) + txID := btcTx.TxHash() + leaves := [][]byte{txID.CloneBytes()} + root := merkle.GetMerkleRoot(leaves) + _, branch := merkle.GetMerkleRootAndBranch(leaves, 0) + rootHash, err := chainhash.NewHash(root) + require.NoError(t, err) + proof := &rtypes.BtcTxProof{ + TxData: buf.Bytes(), + BlockHeight: 100, + BlockHash: "deadbeef", + TxIndex: 0, + MerkleProof: branch, + } + return r, proof, rootHash.String() +} + +func Test_rgbx_validateBtcTxProof_emptyAndDecode(t *testing.T) { + r := newRgbx().(*rgbx) + _, err := r.validateBtcTxProof("tx1", nil) + require.Equal(t, ErrInvalidBtcTxProof, err) + _, err = r.validateBtcTxProof("tx1", &rtypes.BtcTxProof{TxData: []byte{0xff}}) + require.Equal(t, ErrInvalidBtcTxProof, err) +} + +func Test_rgbx_validateBtcTxProof_getHeaderError(t *testing.T) { + r, proof, _ := newBtcTxProofFixture(t) + api := &mocks.QueueProtocolAPI{} + api.On("GetConfig").Return(types.NewChain33Config(types.GetDefaultCfgstring())) + api.On("Query", ltypes.LightclientX, "GetBtcHeader", mock.MatchedBy(func(req types.Message) bool { + h, ok := req.(*ltypes.ReqGetBtcHeader) + return ok && h != nil && h.Height == 100 + })).Return(nil, errors.New("nope")) + r.SetAPI(api) + _, err := r.validateBtcTxProof("tx1", proof) + require.Equal(t, ErrGetBtcHeader, err) +} + +func Test_rgbx_validateBtcTxProof_headerMismatch(t *testing.T) { + r, proof, rootStr := newBtcTxProofFixture(t) + api := &mocks.QueueProtocolAPI{} + api.On("GetConfig").Return(types.NewChain33Config(types.GetDefaultCfgstring())) + api.On("Query", ltypes.LightclientX, "GetBtcHeader", mock.Anything).Return(<ypes.BtcHeader{ + Hash: rootStr, + Height: 101, + MerkleRoot: rootStr, + }, nil) + r.SetAPI(api) + _, err := r.validateBtcTxProof("tx1", proof) + require.Equal(t, ErrInvalidBtcProofBlock, err) +} + +func Test_rgbx_validateBtcTxProof_headerHashMismatch(t *testing.T) { + r, proof, rootStr := newBtcTxProofFixture(t) + api := &mocks.QueueProtocolAPI{} + api.On("GetConfig").Return(types.NewChain33Config(types.GetDefaultCfgstring())) + api.On("Query", ltypes.LightclientX, "GetBtcHeader", mock.Anything).Return(<ypes.BtcHeader{ + Hash: rootStr, + Height: 100, + MerkleRoot: rootStr, + }, nil) + r.SetAPI(api) + _, err := r.validateBtcTxProof("tx1", proof) + require.Equal(t, ErrInvalidBtcProofBlock, err) +} + +func Test_rgbx_validateBtcTxProof_invalidMerkleRootStr(t *testing.T) { + r, proof, _ := newBtcTxProofFixture(t) + api := &mocks.QueueProtocolAPI{} + api.On("GetConfig").Return(types.NewChain33Config(types.GetDefaultCfgstring())) + api.On("Query", ltypes.LightclientX, "GetBtcHeader", mock.Anything).Return(<ypes.BtcHeader{ + Hash: "deadbeef", + Height: 100, + MerkleRoot: "gg", + }, nil) + r.SetAPI(api) + _, err := r.validateBtcTxProof("tx1", proof) + require.Equal(t, ErrInvalidBtcProofMerkle, err) +} + +func Test_rgbx_validateBtcTxProof_invalidHeaderType(t *testing.T) { + r, proof, _ := newBtcTxProofFixture(t) + api := &mocks.QueueProtocolAPI{} + api.On("GetConfig").Return(types.NewChain33Config(types.GetDefaultCfgstring())) + api.On("Query", ltypes.LightclientX, "GetBtcHeader", mock.Anything).Return(&types.ReplyString{Data: "x"}, nil) + r.SetAPI(api) + _, err := r.validateBtcTxProof("tx1", proof) + require.Equal(t, ErrGetBtcHeader, err) +} + +func Test_rgbx_validateBtcTxProof_merkleBranchMismatch(t *testing.T) { + r, proof, rootStr := newBtcTxProofFixture(t) + badProof := &rtypes.BtcTxProof{ + TxData: proof.TxData, + BlockHeight: proof.BlockHeight, + BlockHash: proof.BlockHash, + TxIndex: proof.TxIndex, + MerkleProof: [][]byte{bytes.Repeat([]byte{1}, 32)}, + } + api := &mocks.QueueProtocolAPI{} + api.On("GetConfig").Return(types.NewChain33Config(types.GetDefaultCfgstring())) + api.On("Query", ltypes.LightclientX, "GetBtcHeader", mock.Anything).Return(<ypes.BtcHeader{ + Hash: "deadbeef", + Height: 100, + MerkleRoot: rootStr, + }, nil) + r.SetAPI(api) + _, err := r.validateBtcTxProof("tx1", badProof) + require.Equal(t, ErrInvalidBtcProofMerkle, err) +} + +func Test_rgbx_validateBtcTxProof_success(t *testing.T) { + r, proof, rootStr := newBtcTxProofFixture(t) + api := &mocks.QueueProtocolAPI{} + api.On("GetConfig").Return(types.NewChain33Config(types.GetDefaultCfgstring())) + api.On("Query", ltypes.LightclientX, "GetBtcHeader", mock.Anything).Return(<ypes.BtcHeader{ + Hash: "deadbeef", + Height: 100, + MerkleRoot: rootStr, + }, nil) + r.SetAPI(api) + tx, err := r.validateBtcTxProof("tx1", proof) + require.NoError(t, err) + require.Equal(t, int32(2), tx.Version) +} + +func Test_rgbx_checkWithdrawConfirm(t *testing.T) { + r := newRgbx() + api := &mocks.QueueProtocolAPI{} + r.SetAPI(api) + cfg := types.NewChain33Config(types.GetDefaultCfgstring()) + api.On("GetConfig").Return(cfg) + api.On("Query", ltypes.LightclientX, "GetBtcNetName", mock.Anything).Return(&types.ReplyString{Data: "testnet3"}, nil) + + confirm := &rtypes.ConfirmTx{TxHash: []byte{1, 2, 3}} + pending := &rtypes.PendingTx{AssetSymbol: "btc", Amount: 1000} + destAddr, destPk := newTestnetWitnessAddr(t) + pending.TargetAddress = destAddr + tssScript := []byte{0x51, 0x20, 0x11, 0x22} + + dir, state, _ := util.CreateTestDB() + defer util.CloseTestDB(dir, state) + r.SetStateDB(state) + require.NoError(t, state.Set(formatCrossChainInfoKey("btc"), types.Encode(&rtypes.CrossChainInfo{ + AssetSymbol: "BTC", + PkScript: tssScript, + }))) + + commitData := append([]byte(withdrawCommitmentPrefix), confirm.GetTxHash()...) + commitScript, err := txscript.NullDataScript(commitData) + require.NoError(t, err) + var btcTx wire.MsgTx + btcTx.Version = 2 + btcTx.TxOut = append(btcTx.TxOut, + wire.NewTxOut(0, commitScript), + wire.NewTxOut(500, destPk), + wire.NewTxOut(500, tssScript), + ) + buf := new(bytes.Buffer) + require.NoError(t, btcTx.SerializeNoWitness(buf)) + txID := btcTx.TxHash() + leaves := [][]byte{txID.CloneBytes()} + root := merkle.GetMerkleRoot(leaves) + _, branch := merkle.GetMerkleRootAndBranch(leaves, 0) + rootHash, err := chainhash.NewHash(root) + require.NoError(t, err) + proof := &rtypes.BtcTxProof{ + TxData: buf.Bytes(), + BlockHeight: 1, + BlockHash: "dead", + TxIndex: 0, + MerkleProof: branch, + } + confirm.BtcTxProof = proof + + api.On("Query", ltypes.LightclientX, "GetBtcHeader", mock.Anything).Return(<ypes.BtcHeader{ + Hash: "dead", + Height: 1, + MerkleRoot: rootHash.String(), + }, nil) + + err = r.(*rgbx).checkWithdrawConfirm("a", "b", confirm, pending) + require.NoError(t, err) + + confirmMismatch := &rtypes.ConfirmTx{TxHash: []byte{9, 9, 9}, BtcTxProof: proof} + err = r.(*rgbx).checkWithdrawConfirm("a", "b", confirmMismatch, pending) + require.Equal(t, ErrInvalidBtcProofCommitment, err) +} + +func Test_rgbx_decodeBtcAddressScript_empty(t *testing.T) { + r := newRgbx() + _, err := r.(*rgbx).decodeBtcAddressScript("") + require.Equal(t, types.ErrInvalidAddress, err) +} + +func Test_rgbx_getBtcNetName_queryError(t *testing.T) { + r := newRgbx() + api := &mocks.QueueProtocolAPI{} + r.SetAPI(api) + api.On("Query", ltypes.LightclientX, "GetBtcNetName", mock.Anything).Return(nil, errors.New("down")) + _, err := r.(*rgbx).getBtcNetName() + require.Error(t, err) +} diff --git a/plugin/dapp/rgbx/plugin.go b/plugin/dapp/rgbx/plugin.go new file mode 100644 index 0000000000..c9d8f333e8 --- /dev/null +++ b/plugin/dapp/rgbx/plugin.go @@ -0,0 +1,23 @@ +package types + +import ( + "github.com/33cn/chain33/pluginmgr" + "github.com/33cn/plugin/plugin/dapp/rgbx/commands" + "github.com/33cn/plugin/plugin/dapp/rgbx/executor" + "github.com/33cn/plugin/plugin/dapp/rgbx/rpc" + rgbxtypes "github.com/33cn/plugin/plugin/dapp/rgbx/types" +) + +/* + * 初始化dapp相关的组件 + */ + +func init() { + pluginmgr.Register(&pluginmgr.PluginBase{ + Name: rgbxtypes.RgbxX, + ExecName: executor.GetName(), + Exec: executor.Init, + Cmd: commands.Cmd, + RPC: rpc.Init, + }) +} diff --git a/plugin/dapp/rgbx/proto/Makefile b/plugin/dapp/rgbx/proto/Makefile new file mode 100644 index 0000000000..446c4d8473 --- /dev/null +++ b/plugin/dapp/rgbx/proto/Makefile @@ -0,0 +1,2 @@ +all: + bash ./create_protobuf.sh diff --git a/plugin/dapp/rgbx/proto/create_protobuf.sh b/plugin/dapp/rgbx/proto/create_protobuf.sh new file mode 100644 index 0000000000..a181afdb9a --- /dev/null +++ b/plugin/dapp/rgbx/proto/create_protobuf.sh @@ -0,0 +1,4 @@ +#!/bin/bash +# proto生成命令,将pb.go文件生成到types/目录下, chain33_path支持引用chain33框架的proto文件 +chain33_path=$(go list -f '{{.Dir}}' "github.com/33cn/chain33") +protoc --go_out=plugins=grpc:../types ./*.proto --proto_path=. --proto_path="${chain33_path}/types/proto/" diff --git a/plugin/dapp/rgbx/proto/rgbx.proto b/plugin/dapp/rgbx/proto/rgbx.proto new file mode 100644 index 0000000000..5137d253fb --- /dev/null +++ b/plugin/dapp/rgbx/proto/rgbx.proto @@ -0,0 +1,166 @@ +syntax = "proto3"; + +package types; +option go_package = "../types"; + +message RgbxAction { + int32 ty = 1; + oneof value { + mintAsset mint = 2; + transferAsset transfer = 3; + confirmTx confirm = 4; + commitDKG commitDKG = 5; + depositAsset deposit = 6; + withdrawAsset withdraw = 7; + } +} + +message rgbxAsset { + + uint32 type = 1; + uint32 precision = 2; + int64 totalAmount = 3; + string symbol = 4; + bytes metaHash = 5; + string genesisBtcTxHash = 6; + string owner = 7; +} + +message mintAsset { + uint32 type = 1; + uint32 precision = 2; + int64 totalAmount = 3; + string symbol = 4; + bytes metaHash = 5; + // represents the outpoint of the transaction's + // input that resulted in the creation of the asset. + outPoint genesisOut = 6; +} + +message outPoint { + string hash = 1; + uint32 index = 2; + bytes pkScript = 3; +} + +message utxoSpendingProof { + uint32 spendingInputIdx = 1; + int32 opRetOutputIdx = 2; + bytes spendingTx = 3; + bytes opRetOutputPkScript = 4; +} + +message transferAsset { + + string symbol = 1; + int64 amount = 2; + string fromUtxo = 3; + string to = 4; + string changeAddr = 5; + bytes fromUtxoPkScript = 6; +} + +message confirmTx { + + int32 actionType = 1; + int64 confirmedBlockHeight = 2; + int64 txBlockHeight = 3; + int64 txIndex = 4; + bytes txHash = 5; + bool timeout = 6; + utxoSpendingProof utxoProof = 7; + btcTxProof btcTxProof = 8; +} + +message btcTxProof { + uint64 blockHeight = 1; + uint32 txIndex = 2; + string blockHash = 3; + bytes txData = 4; + repeated bytes merkleProof = 5; +} + +message commitDKG { + + string assetSymbol = 1; + string dkgAddress = 2; + bytes pkScript = 3; +} + +message depositAsset { + + int64 amount = 1; + string depositAddress = 2; + string assetSymbol = 3; + btcTxProof txProof = 4; +} + +message withdrawAsset { + + int64 amount = 1; + int64 feeRate = 2; + string destinationAddr = 3; + string assetSymbol = 4; +} + +message crossChainInfo { + + string assetSymbol = 1; + string wrappedSymbol = 2; + string tssAddress = 3; + bytes pkScript = 4; +} + +message pendingTx { + int32 actionType = 1; + int64 timestamp = 2; + int64 txBlockHeight = 3; + int64 txIndex = 4; + int64 amount = 5; + int64 feeRate = 6; + bytes txHash = 7; + outPoint utxo = 8; + string fromAddress = 9; + string assetSymbol = 10; + string targetAddress = 11; + bool confirmed = 12; +} + +message pendingTxs { + repeated pendingTx pendingList = 1; +} + +message utxo { + string outPoint = 1; + int64 amount = 2; + bytes pkScript = 3; +} + +message ReqListPendingTx { + int64 startHeight = 1; + int64 startIndex = 2; + int32 count = 3; + int64 endHeight = 4; +} + +message ReqGetPendingTx { + int64 height = 1; + int64 index = 2; +} + +message btcCommitment { + bytes protocol = 1; + uint32 action = 2; + bytes payload = 3; +} + +message txBlockIndex { + int64 blockHeight = 1; + int64 txIndex = 2; +} + +message txBlockIndexList { + repeated txBlockIndex blockIndexList = 1; +} + +service rgbx {} diff --git a/plugin/dapp/rgbx/rpc/rpc.go b/plugin/dapp/rgbx/rpc/rpc.go new file mode 100644 index 0000000000..24ff0d65b0 --- /dev/null +++ b/plugin/dapp/rgbx/rpc/rpc.go @@ -0,0 +1,7 @@ +package rpc + +/* + * 实现json rpc和grpc service接口 + * json rpc用Jrpc结构作为接收实例 + * grpc使用channelClient结构作为接收实例 + */ diff --git a/plugin/dapp/rgbx/rpc/types.go b/plugin/dapp/rgbx/rpc/types.go new file mode 100644 index 0000000000..8448e9a4da --- /dev/null +++ b/plugin/dapp/rgbx/rpc/types.go @@ -0,0 +1,34 @@ +package rpc + +import ( + rpctypes "github.com/33cn/chain33/rpc/types" + rgbxtypes "github.com/33cn/plugin/plugin/dapp/rgbx/types" +) + +/* + * rpc相关结构定义和初始化 + */ + +// 实现grpc的service接口 +type channelClient struct { + rpctypes.ChannelClient +} + +// Jrpc 实现json rpc调用实例 +type Jrpc struct { + cli *channelClient +} + +// Grpc grpc +type Grpc struct { + *channelClient +} + +// Init init rpc +func Init(name string, s rpctypes.RPCServer) { + cli := &channelClient{} + grpc := &Grpc{channelClient: cli} + cli.Init(name, s, &Jrpc{cli: cli}, grpc) + //存在grpc service时注册grpc server,需要生成对应的pb.go文件 + rgbxtypes.RegisterRgbxServer(s.GRPC(), grpc) +} diff --git a/plugin/dapp/rgbx/types/address.go b/plugin/dapp/rgbx/types/address.go new file mode 100644 index 0000000000..94b039b14f --- /dev/null +++ b/plugin/dapp/rgbx/types/address.go @@ -0,0 +1,59 @@ +package types + +import ( + "fmt" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "strconv" + "strings" +) + +// ToString encode to string +func (o *OutPoint) ToString() string { + if o == nil { + return "nil-OutPoint" + } + return FormatUtxo(o.Hash, o.Index) +} + +// FormatUtxo format utxo as string +func FormatUtxo(hash string, index uint32) string { + + buf := make([]byte, 2*chainhash.HashSize+1, 2*chainhash.HashSize+1+10) + copy(buf, hash) + buf[2*chainhash.HashSize] = ':' + buf = strconv.AppendUint(buf, uint64(index), 10) + return string(buf) +} + +// FromString decode from string +func (o *OutPoint) FromString(s string) error { + + strs := strings.Split(s, ":") + if len(strs) != 2 { + return fmt.Errorf("invalid outpoint: %s", s) + } + + o.Hash = strs[0] + v, err := strconv.ParseInt(strs[1], 10, 32) + if err != nil { + return err + } + o.Index = uint32(v) + return nil +} + +// NewOutPointFromString new out point +func NewOutPointFromString(s string) (*OutPoint, error) { + o := &OutPoint{} + err := o.FromString(s) + return o, err +} + +// IsUtxoAddress check if utxo address +func IsUtxoAddress(addr string) bool { + + if strings.Contains(addr, ":") && len(addr) > 2*chainhash.HashSize { + return true + } + return false +} diff --git a/plugin/dapp/rgbx/types/asset.go b/plugin/dapp/rgbx/types/asset.go new file mode 100644 index 0000000000..3a36958fc4 --- /dev/null +++ b/plugin/dapp/rgbx/types/asset.go @@ -0,0 +1,46 @@ +package types + +const ( + // MaxAssetSymbolLength is the maximum byte length of an asset's symbol. + // This byte length is equivalent to character count for single-byte + // UTF-8 characters. + MaxAssetSymbolLength = 16 + + // MetaHashLen is the length of the metadata hash. + MetaHashLen = 32 + // MaxPrecision max Precision + MaxPrecision = 8 + // MaxAssetAmount max amount + MaxAssetAmount = 1e8 * 900 * 1e8 // 900 亿 +) + +// AssetType denotes the asset types supported by the Taproot RgbxAsset protocol. +type AssetType uint8 + +const ( + // Normal is an asset that can be represented in multiple units, + // resembling a divisible asset. + Normal AssetType = 0 + + // Collectible is a unique asset, one that cannot be represented in + // multiple units. + Collectible AssetType = 1 +) + +// cross chain asset symbol +const ( + // BTCSymbol bitcoin + BTCSymbol = "BTC" +) + +// String returns a human-readable description of the type. +func (t AssetType) String() string { + switch t { + case Normal: + return "Normal" + case Collectible: + return "Collectible" + default: + return "" + } +} diff --git a/plugin/dapp/rgbx/types/rgbx.go b/plugin/dapp/rgbx/types/rgbx.go new file mode 100644 index 0000000000..f5a59c2a0c --- /dev/null +++ b/plugin/dapp/rgbx/types/rgbx.go @@ -0,0 +1,134 @@ +package types + +import ( + "reflect" + + "github.com/33cn/chain33/types" + //"reflect" +) + +/* + * 交易相关类型定义 + * 交易action通常有对应的log结构,用于交易回执日志记录 + * 每一种action和log需要用id数值和name名称加以区分 + */ + +// action类型id和name,这些常量可以自定义修改 +const ( + TyUnknownAction = iota + 100 + TyMintAction + TyTransferAction + TyConfirmAction + TyCommitDKGAction + TyDepositAsset + TyWithDrawAsset + + NameMintAction = "Mint" + NameTransferAction = "Transfer" + NameConfirmAction = "Confirm" + NameCommitDKGAction = "CommitDKG" + // 须与 RgbxAction protobuf oneof 字段名一致(chain33 ExecTypeBase.SetChild 用 strings.ToLower 映射), + // 例如 oneof deposit -> 此处为 "Deposit",不能为 "DepositAsset"。 + NameDepositAssetAction = "Deposit" + NameWithdrawAssetAction = "Withdraw" +) + +// log类型id值 +const ( + TyUnknownLog = iota + 100 + TyAssetLog + TyPendingTxLog + TyCommitDKGLog + TyDepositAssetLog + TyWithdrawAssetLog + + NameAssetLog = "AssetLog" + NamePendingTxLog = "PendingTxLog" + NameCommitDKGLog = "CommitDKGLog" + NameDepositAssetLog = "DepositAssetLog" + NameWithdrawAssetLog = "WithdrawAssetLog" +) + +var ( + //RgbxX 执行器名称定义 + RgbxX = "rgbx" + //定义actionMap + actionMap = map[string]int32{ + NameMintAction: TyMintAction, + NameTransferAction: TyTransferAction, + NameConfirmAction: TyConfirmAction, + NameCommitDKGAction: TyCommitDKGAction, + NameDepositAssetAction: TyDepositAsset, + NameWithdrawAssetAction: TyWithDrawAsset, + } + //定义log的id和具体log类型及名称,填入具体自定义log类型 + logMap = map[int64]*types.LogInfo{ + TyAssetLog: {Ty: reflect.TypeOf(RgbxAsset{}), Name: NameAssetLog}, + TyPendingTxLog: {Ty: reflect.TypeOf(PendingTx{}), Name: NamePendingTxLog}, + TyCommitDKGLog: {Ty: reflect.TypeOf(types.ReqAddrs{}), Name: NameCommitDKGLog}, + } +) + +// init defines a register function +func init() { + types.AllowUserExec = append(types.AllowUserExec, []byte(RgbxX)) + //注册合约启用高度 + types.RegFork(RgbxX, InitFork) + types.RegExec(RgbxX, InitExecutor) +} + +// InitFork defines register fork +func InitFork(cfg *types.Chain33Config) { + cfg.RegisterDappFork(RgbxX, "Enable", 0) +} + +// InitExecutor defines register executor +func InitExecutor(cfg *types.Chain33Config) { + types.RegistorExecutor(RgbxX, NewType(cfg)) +} + +type rgbxType struct { + types.ExecTypeBase +} + +func NewType(cfg *types.Chain33Config) *rgbxType { + c := &rgbxType{} + c.SetChild(c) + c.SetConfig(cfg) + return c +} + +// GetPayload 获取合约action结构 +func (r *rgbxType) GetPayload() types.Message { + return &RgbxAction{} +} + +// GeTypeMap 获取合约action的id和name信息 +func (r *rgbxType) GetTypeMap() map[string]int32 { + return actionMap +} + +// GetLogMap 获取合约log相关信息 +func (r *rgbxType) GetLogMap() map[int64]*types.LogInfo { + return logMap +} + +// GetActionName get action name by action type +func GetActionName(ty int32) string { + switch ty { + case TyMintAction: + return NameMintAction + case TyTransferAction: + return NameTransferAction + case TyConfirmAction: + return NameConfirmAction + case TyCommitDKGAction: + return NameCommitDKGAction + case TyDepositAsset: + return NameDepositAssetAction + case TyWithDrawAsset: + return NameWithdrawAssetAction + default: + return "unknownAction" + } +} diff --git a/plugin/dapp/rgbx/types/rgbx.pb.go b/plugin/dapp/rgbx/types/rgbx.pb.go new file mode 100644 index 0000000000..c801a7b176 --- /dev/null +++ b/plugin/dapp/rgbx/types/rgbx.pb.go @@ -0,0 +1,2150 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.26.0 +// protoc v3.17.0 +// source: rgbx.proto + +package types + +import ( + context "context" + grpc "google.golang.org/grpc" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type RgbxAction struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Ty int32 `protobuf:"varint,1,opt,name=ty,proto3" json:"ty,omitempty"` + // Types that are assignable to Value: + // *RgbxAction_Mint + // *RgbxAction_Transfer + // *RgbxAction_Confirm + // *RgbxAction_CommitDKG + // *RgbxAction_Deposit + // *RgbxAction_Withdraw + Value isRgbxAction_Value `protobuf_oneof:"value"` +} + +func (x *RgbxAction) Reset() { + *x = RgbxAction{} + if protoimpl.UnsafeEnabled { + mi := &file_rgbx_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RgbxAction) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RgbxAction) ProtoMessage() {} + +func (x *RgbxAction) ProtoReflect() protoreflect.Message { + mi := &file_rgbx_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RgbxAction.ProtoReflect.Descriptor instead. +func (*RgbxAction) Descriptor() ([]byte, []int) { + return file_rgbx_proto_rawDescGZIP(), []int{0} +} + +func (x *RgbxAction) GetTy() int32 { + if x != nil { + return x.Ty + } + return 0 +} + +func (m *RgbxAction) GetValue() isRgbxAction_Value { + if m != nil { + return m.Value + } + return nil +} + +func (x *RgbxAction) GetMint() *MintAsset { + if x, ok := x.GetValue().(*RgbxAction_Mint); ok { + return x.Mint + } + return nil +} + +func (x *RgbxAction) GetTransfer() *TransferAsset { + if x, ok := x.GetValue().(*RgbxAction_Transfer); ok { + return x.Transfer + } + return nil +} + +func (x *RgbxAction) GetConfirm() *ConfirmTx { + if x, ok := x.GetValue().(*RgbxAction_Confirm); ok { + return x.Confirm + } + return nil +} + +func (x *RgbxAction) GetCommitDKG() *CommitDKG { + if x, ok := x.GetValue().(*RgbxAction_CommitDKG); ok { + return x.CommitDKG + } + return nil +} + +func (x *RgbxAction) GetDeposit() *DepositAsset { + if x, ok := x.GetValue().(*RgbxAction_Deposit); ok { + return x.Deposit + } + return nil +} + +func (x *RgbxAction) GetWithdraw() *WithdrawAsset { + if x, ok := x.GetValue().(*RgbxAction_Withdraw); ok { + return x.Withdraw + } + return nil +} + +type isRgbxAction_Value interface { + isRgbxAction_Value() +} + +type RgbxAction_Mint struct { + Mint *MintAsset `protobuf:"bytes,2,opt,name=mint,proto3,oneof"` +} + +type RgbxAction_Transfer struct { + Transfer *TransferAsset `protobuf:"bytes,3,opt,name=transfer,proto3,oneof"` +} + +type RgbxAction_Confirm struct { + Confirm *ConfirmTx `protobuf:"bytes,4,opt,name=confirm,proto3,oneof"` +} + +type RgbxAction_CommitDKG struct { + CommitDKG *CommitDKG `protobuf:"bytes,5,opt,name=commitDKG,proto3,oneof"` +} + +type RgbxAction_Deposit struct { + Deposit *DepositAsset `protobuf:"bytes,6,opt,name=deposit,proto3,oneof"` +} + +type RgbxAction_Withdraw struct { + Withdraw *WithdrawAsset `protobuf:"bytes,7,opt,name=withdraw,proto3,oneof"` +} + +func (*RgbxAction_Mint) isRgbxAction_Value() {} + +func (*RgbxAction_Transfer) isRgbxAction_Value() {} + +func (*RgbxAction_Confirm) isRgbxAction_Value() {} + +func (*RgbxAction_CommitDKG) isRgbxAction_Value() {} + +func (*RgbxAction_Deposit) isRgbxAction_Value() {} + +func (*RgbxAction_Withdraw) isRgbxAction_Value() {} + +type RgbxAsset struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Type uint32 `protobuf:"varint,1,opt,name=type,proto3" json:"type,omitempty"` + Precision uint32 `protobuf:"varint,2,opt,name=precision,proto3" json:"precision,omitempty"` + TotalAmount int64 `protobuf:"varint,3,opt,name=totalAmount,proto3" json:"totalAmount,omitempty"` + Symbol string `protobuf:"bytes,4,opt,name=symbol,proto3" json:"symbol,omitempty"` + MetaHash []byte `protobuf:"bytes,5,opt,name=metaHash,proto3" json:"metaHash,omitempty"` + GenesisBtcTxHash string `protobuf:"bytes,6,opt,name=genesisBtcTxHash,proto3" json:"genesisBtcTxHash,omitempty"` + Owner string `protobuf:"bytes,7,opt,name=owner,proto3" json:"owner,omitempty"` +} + +func (x *RgbxAsset) Reset() { + *x = RgbxAsset{} + if protoimpl.UnsafeEnabled { + mi := &file_rgbx_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RgbxAsset) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RgbxAsset) ProtoMessage() {} + +func (x *RgbxAsset) ProtoReflect() protoreflect.Message { + mi := &file_rgbx_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RgbxAsset.ProtoReflect.Descriptor instead. +func (*RgbxAsset) Descriptor() ([]byte, []int) { + return file_rgbx_proto_rawDescGZIP(), []int{1} +} + +func (x *RgbxAsset) GetType() uint32 { + if x != nil { + return x.Type + } + return 0 +} + +func (x *RgbxAsset) GetPrecision() uint32 { + if x != nil { + return x.Precision + } + return 0 +} + +func (x *RgbxAsset) GetTotalAmount() int64 { + if x != nil { + return x.TotalAmount + } + return 0 +} + +func (x *RgbxAsset) GetSymbol() string { + if x != nil { + return x.Symbol + } + return "" +} + +func (x *RgbxAsset) GetMetaHash() []byte { + if x != nil { + return x.MetaHash + } + return nil +} + +func (x *RgbxAsset) GetGenesisBtcTxHash() string { + if x != nil { + return x.GenesisBtcTxHash + } + return "" +} + +func (x *RgbxAsset) GetOwner() string { + if x != nil { + return x.Owner + } + return "" +} + +type MintAsset struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Type uint32 `protobuf:"varint,1,opt,name=type,proto3" json:"type,omitempty"` + Precision uint32 `protobuf:"varint,2,opt,name=precision,proto3" json:"precision,omitempty"` + TotalAmount int64 `protobuf:"varint,3,opt,name=totalAmount,proto3" json:"totalAmount,omitempty"` + Symbol string `protobuf:"bytes,4,opt,name=symbol,proto3" json:"symbol,omitempty"` + MetaHash []byte `protobuf:"bytes,5,opt,name=metaHash,proto3" json:"metaHash,omitempty"` + // represents the outpoint of the transaction's + // input that resulted in the creation of the asset. + GenesisOut *OutPoint `protobuf:"bytes,6,opt,name=genesisOut,proto3" json:"genesisOut,omitempty"` +} + +func (x *MintAsset) Reset() { + *x = MintAsset{} + if protoimpl.UnsafeEnabled { + mi := &file_rgbx_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MintAsset) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MintAsset) ProtoMessage() {} + +func (x *MintAsset) ProtoReflect() protoreflect.Message { + mi := &file_rgbx_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MintAsset.ProtoReflect.Descriptor instead. +func (*MintAsset) Descriptor() ([]byte, []int) { + return file_rgbx_proto_rawDescGZIP(), []int{2} +} + +func (x *MintAsset) GetType() uint32 { + if x != nil { + return x.Type + } + return 0 +} + +func (x *MintAsset) GetPrecision() uint32 { + if x != nil { + return x.Precision + } + return 0 +} + +func (x *MintAsset) GetTotalAmount() int64 { + if x != nil { + return x.TotalAmount + } + return 0 +} + +func (x *MintAsset) GetSymbol() string { + if x != nil { + return x.Symbol + } + return "" +} + +func (x *MintAsset) GetMetaHash() []byte { + if x != nil { + return x.MetaHash + } + return nil +} + +func (x *MintAsset) GetGenesisOut() *OutPoint { + if x != nil { + return x.GenesisOut + } + return nil +} + +type OutPoint struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Hash string `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` + Index uint32 `protobuf:"varint,2,opt,name=index,proto3" json:"index,omitempty"` + PkScript []byte `protobuf:"bytes,3,opt,name=pkScript,proto3" json:"pkScript,omitempty"` +} + +func (x *OutPoint) Reset() { + *x = OutPoint{} + if protoimpl.UnsafeEnabled { + mi := &file_rgbx_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OutPoint) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OutPoint) ProtoMessage() {} + +func (x *OutPoint) ProtoReflect() protoreflect.Message { + mi := &file_rgbx_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OutPoint.ProtoReflect.Descriptor instead. +func (*OutPoint) Descriptor() ([]byte, []int) { + return file_rgbx_proto_rawDescGZIP(), []int{3} +} + +func (x *OutPoint) GetHash() string { + if x != nil { + return x.Hash + } + return "" +} + +func (x *OutPoint) GetIndex() uint32 { + if x != nil { + return x.Index + } + return 0 +} + +func (x *OutPoint) GetPkScript() []byte { + if x != nil { + return x.PkScript + } + return nil +} + +type UtxoSpendingProof struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + SpendingInputIdx uint32 `protobuf:"varint,1,opt,name=spendingInputIdx,proto3" json:"spendingInputIdx,omitempty"` + OpRetOutputIdx int32 `protobuf:"varint,2,opt,name=opRetOutputIdx,proto3" json:"opRetOutputIdx,omitempty"` + SpendingTx []byte `protobuf:"bytes,3,opt,name=spendingTx,proto3" json:"spendingTx,omitempty"` + OpRetOutputPkScript []byte `protobuf:"bytes,4,opt,name=opRetOutputPkScript,proto3" json:"opRetOutputPkScript,omitempty"` +} + +func (x *UtxoSpendingProof) Reset() { + *x = UtxoSpendingProof{} + if protoimpl.UnsafeEnabled { + mi := &file_rgbx_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UtxoSpendingProof) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UtxoSpendingProof) ProtoMessage() {} + +func (x *UtxoSpendingProof) ProtoReflect() protoreflect.Message { + mi := &file_rgbx_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UtxoSpendingProof.ProtoReflect.Descriptor instead. +func (*UtxoSpendingProof) Descriptor() ([]byte, []int) { + return file_rgbx_proto_rawDescGZIP(), []int{4} +} + +func (x *UtxoSpendingProof) GetSpendingInputIdx() uint32 { + if x != nil { + return x.SpendingInputIdx + } + return 0 +} + +func (x *UtxoSpendingProof) GetOpRetOutputIdx() int32 { + if x != nil { + return x.OpRetOutputIdx + } + return 0 +} + +func (x *UtxoSpendingProof) GetSpendingTx() []byte { + if x != nil { + return x.SpendingTx + } + return nil +} + +func (x *UtxoSpendingProof) GetOpRetOutputPkScript() []byte { + if x != nil { + return x.OpRetOutputPkScript + } + return nil +} + +type TransferAsset struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Symbol string `protobuf:"bytes,1,opt,name=symbol,proto3" json:"symbol,omitempty"` + Amount int64 `protobuf:"varint,2,opt,name=amount,proto3" json:"amount,omitempty"` + FromUtxo string `protobuf:"bytes,3,opt,name=fromUtxo,proto3" json:"fromUtxo,omitempty"` + To string `protobuf:"bytes,4,opt,name=to,proto3" json:"to,omitempty"` + ChangeAddr string `protobuf:"bytes,5,opt,name=changeAddr,proto3" json:"changeAddr,omitempty"` + FromUtxoPkScript []byte `protobuf:"bytes,6,opt,name=fromUtxoPkScript,proto3" json:"fromUtxoPkScript,omitempty"` +} + +func (x *TransferAsset) Reset() { + *x = TransferAsset{} + if protoimpl.UnsafeEnabled { + mi := &file_rgbx_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TransferAsset) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TransferAsset) ProtoMessage() {} + +func (x *TransferAsset) ProtoReflect() protoreflect.Message { + mi := &file_rgbx_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TransferAsset.ProtoReflect.Descriptor instead. +func (*TransferAsset) Descriptor() ([]byte, []int) { + return file_rgbx_proto_rawDescGZIP(), []int{5} +} + +func (x *TransferAsset) GetSymbol() string { + if x != nil { + return x.Symbol + } + return "" +} + +func (x *TransferAsset) GetAmount() int64 { + if x != nil { + return x.Amount + } + return 0 +} + +func (x *TransferAsset) GetFromUtxo() string { + if x != nil { + return x.FromUtxo + } + return "" +} + +func (x *TransferAsset) GetTo() string { + if x != nil { + return x.To + } + return "" +} + +func (x *TransferAsset) GetChangeAddr() string { + if x != nil { + return x.ChangeAddr + } + return "" +} + +func (x *TransferAsset) GetFromUtxoPkScript() []byte { + if x != nil { + return x.FromUtxoPkScript + } + return nil +} + +type ConfirmTx struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ActionType int32 `protobuf:"varint,1,opt,name=actionType,proto3" json:"actionType,omitempty"` + ConfirmedBlockHeight int64 `protobuf:"varint,2,opt,name=confirmedBlockHeight,proto3" json:"confirmedBlockHeight,omitempty"` + TxBlockHeight int64 `protobuf:"varint,3,opt,name=txBlockHeight,proto3" json:"txBlockHeight,omitempty"` + TxIndex int64 `protobuf:"varint,4,opt,name=txIndex,proto3" json:"txIndex,omitempty"` + TxHash []byte `protobuf:"bytes,5,opt,name=txHash,proto3" json:"txHash,omitempty"` + Timeout bool `protobuf:"varint,6,opt,name=timeout,proto3" json:"timeout,omitempty"` + UtxoProof *UtxoSpendingProof `protobuf:"bytes,7,opt,name=utxoProof,proto3" json:"utxoProof,omitempty"` + BtcTxProof *BtcTxProof `protobuf:"bytes,8,opt,name=btcTxProof,proto3" json:"btcTxProof,omitempty"` +} + +func (x *ConfirmTx) Reset() { + *x = ConfirmTx{} + if protoimpl.UnsafeEnabled { + mi := &file_rgbx_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ConfirmTx) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConfirmTx) ProtoMessage() {} + +func (x *ConfirmTx) ProtoReflect() protoreflect.Message { + mi := &file_rgbx_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ConfirmTx.ProtoReflect.Descriptor instead. +func (*ConfirmTx) Descriptor() ([]byte, []int) { + return file_rgbx_proto_rawDescGZIP(), []int{6} +} + +func (x *ConfirmTx) GetActionType() int32 { + if x != nil { + return x.ActionType + } + return 0 +} + +func (x *ConfirmTx) GetConfirmedBlockHeight() int64 { + if x != nil { + return x.ConfirmedBlockHeight + } + return 0 +} + +func (x *ConfirmTx) GetTxBlockHeight() int64 { + if x != nil { + return x.TxBlockHeight + } + return 0 +} + +func (x *ConfirmTx) GetTxIndex() int64 { + if x != nil { + return x.TxIndex + } + return 0 +} + +func (x *ConfirmTx) GetTxHash() []byte { + if x != nil { + return x.TxHash + } + return nil +} + +func (x *ConfirmTx) GetTimeout() bool { + if x != nil { + return x.Timeout + } + return false +} + +func (x *ConfirmTx) GetUtxoProof() *UtxoSpendingProof { + if x != nil { + return x.UtxoProof + } + return nil +} + +func (x *ConfirmTx) GetBtcTxProof() *BtcTxProof { + if x != nil { + return x.BtcTxProof + } + return nil +} + +type BtcTxProof struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + BlockHeight uint64 `protobuf:"varint,1,opt,name=blockHeight,proto3" json:"blockHeight,omitempty"` + TxIndex uint32 `protobuf:"varint,2,opt,name=txIndex,proto3" json:"txIndex,omitempty"` + BlockHash string `protobuf:"bytes,3,opt,name=blockHash,proto3" json:"blockHash,omitempty"` + TxData []byte `protobuf:"bytes,4,opt,name=txData,proto3" json:"txData,omitempty"` + MerkleProof [][]byte `protobuf:"bytes,5,rep,name=merkleProof,proto3" json:"merkleProof,omitempty"` +} + +func (x *BtcTxProof) Reset() { + *x = BtcTxProof{} + if protoimpl.UnsafeEnabled { + mi := &file_rgbx_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BtcTxProof) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BtcTxProof) ProtoMessage() {} + +func (x *BtcTxProof) ProtoReflect() protoreflect.Message { + mi := &file_rgbx_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BtcTxProof.ProtoReflect.Descriptor instead. +func (*BtcTxProof) Descriptor() ([]byte, []int) { + return file_rgbx_proto_rawDescGZIP(), []int{7} +} + +func (x *BtcTxProof) GetBlockHeight() uint64 { + if x != nil { + return x.BlockHeight + } + return 0 +} + +func (x *BtcTxProof) GetTxIndex() uint32 { + if x != nil { + return x.TxIndex + } + return 0 +} + +func (x *BtcTxProof) GetBlockHash() string { + if x != nil { + return x.BlockHash + } + return "" +} + +func (x *BtcTxProof) GetTxData() []byte { + if x != nil { + return x.TxData + } + return nil +} + +func (x *BtcTxProof) GetMerkleProof() [][]byte { + if x != nil { + return x.MerkleProof + } + return nil +} + +type CommitDKG struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + AssetSymbol string `protobuf:"bytes,1,opt,name=assetSymbol,proto3" json:"assetSymbol,omitempty"` + DkgAddress string `protobuf:"bytes,2,opt,name=dkgAddress,proto3" json:"dkgAddress,omitempty"` + PkScript []byte `protobuf:"bytes,3,opt,name=pkScript,proto3" json:"pkScript,omitempty"` +} + +func (x *CommitDKG) Reset() { + *x = CommitDKG{} + if protoimpl.UnsafeEnabled { + mi := &file_rgbx_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CommitDKG) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CommitDKG) ProtoMessage() {} + +func (x *CommitDKG) ProtoReflect() protoreflect.Message { + mi := &file_rgbx_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CommitDKG.ProtoReflect.Descriptor instead. +func (*CommitDKG) Descriptor() ([]byte, []int) { + return file_rgbx_proto_rawDescGZIP(), []int{8} +} + +func (x *CommitDKG) GetAssetSymbol() string { + if x != nil { + return x.AssetSymbol + } + return "" +} + +func (x *CommitDKG) GetDkgAddress() string { + if x != nil { + return x.DkgAddress + } + return "" +} + +func (x *CommitDKG) GetPkScript() []byte { + if x != nil { + return x.PkScript + } + return nil +} + +type DepositAsset struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Amount int64 `protobuf:"varint,1,opt,name=amount,proto3" json:"amount,omitempty"` + DepositAddress string `protobuf:"bytes,2,opt,name=depositAddress,proto3" json:"depositAddress,omitempty"` + AssetSymbol string `protobuf:"bytes,3,opt,name=assetSymbol,proto3" json:"assetSymbol,omitempty"` + TxProof *BtcTxProof `protobuf:"bytes,4,opt,name=txProof,proto3" json:"txProof,omitempty"` +} + +func (x *DepositAsset) Reset() { + *x = DepositAsset{} + if protoimpl.UnsafeEnabled { + mi := &file_rgbx_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DepositAsset) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DepositAsset) ProtoMessage() {} + +func (x *DepositAsset) ProtoReflect() protoreflect.Message { + mi := &file_rgbx_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DepositAsset.ProtoReflect.Descriptor instead. +func (*DepositAsset) Descriptor() ([]byte, []int) { + return file_rgbx_proto_rawDescGZIP(), []int{9} +} + +func (x *DepositAsset) GetAmount() int64 { + if x != nil { + return x.Amount + } + return 0 +} + +func (x *DepositAsset) GetDepositAddress() string { + if x != nil { + return x.DepositAddress + } + return "" +} + +func (x *DepositAsset) GetAssetSymbol() string { + if x != nil { + return x.AssetSymbol + } + return "" +} + +func (x *DepositAsset) GetTxProof() *BtcTxProof { + if x != nil { + return x.TxProof + } + return nil +} + +type WithdrawAsset struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Amount int64 `protobuf:"varint,1,opt,name=amount,proto3" json:"amount,omitempty"` + FeeRate int64 `protobuf:"varint,2,opt,name=feeRate,proto3" json:"feeRate,omitempty"` + DestinationAddr string `protobuf:"bytes,3,opt,name=destinationAddr,proto3" json:"destinationAddr,omitempty"` + AssetSymbol string `protobuf:"bytes,4,opt,name=assetSymbol,proto3" json:"assetSymbol,omitempty"` +} + +func (x *WithdrawAsset) Reset() { + *x = WithdrawAsset{} + if protoimpl.UnsafeEnabled { + mi := &file_rgbx_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *WithdrawAsset) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WithdrawAsset) ProtoMessage() {} + +func (x *WithdrawAsset) ProtoReflect() protoreflect.Message { + mi := &file_rgbx_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WithdrawAsset.ProtoReflect.Descriptor instead. +func (*WithdrawAsset) Descriptor() ([]byte, []int) { + return file_rgbx_proto_rawDescGZIP(), []int{10} +} + +func (x *WithdrawAsset) GetAmount() int64 { + if x != nil { + return x.Amount + } + return 0 +} + +func (x *WithdrawAsset) GetFeeRate() int64 { + if x != nil { + return x.FeeRate + } + return 0 +} + +func (x *WithdrawAsset) GetDestinationAddr() string { + if x != nil { + return x.DestinationAddr + } + return "" +} + +func (x *WithdrawAsset) GetAssetSymbol() string { + if x != nil { + return x.AssetSymbol + } + return "" +} + +type CrossChainInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + AssetSymbol string `protobuf:"bytes,1,opt,name=assetSymbol,proto3" json:"assetSymbol,omitempty"` + WrappedSymbol string `protobuf:"bytes,2,opt,name=wrappedSymbol,proto3" json:"wrappedSymbol,omitempty"` + TssAddress string `protobuf:"bytes,3,opt,name=tssAddress,proto3" json:"tssAddress,omitempty"` + PkScript []byte `protobuf:"bytes,4,opt,name=pkScript,proto3" json:"pkScript,omitempty"` +} + +func (x *CrossChainInfo) Reset() { + *x = CrossChainInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_rgbx_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CrossChainInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CrossChainInfo) ProtoMessage() {} + +func (x *CrossChainInfo) ProtoReflect() protoreflect.Message { + mi := &file_rgbx_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CrossChainInfo.ProtoReflect.Descriptor instead. +func (*CrossChainInfo) Descriptor() ([]byte, []int) { + return file_rgbx_proto_rawDescGZIP(), []int{11} +} + +func (x *CrossChainInfo) GetAssetSymbol() string { + if x != nil { + return x.AssetSymbol + } + return "" +} + +func (x *CrossChainInfo) GetWrappedSymbol() string { + if x != nil { + return x.WrappedSymbol + } + return "" +} + +func (x *CrossChainInfo) GetTssAddress() string { + if x != nil { + return x.TssAddress + } + return "" +} + +func (x *CrossChainInfo) GetPkScript() []byte { + if x != nil { + return x.PkScript + } + return nil +} + +type PendingTx struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ActionType int32 `protobuf:"varint,1,opt,name=actionType,proto3" json:"actionType,omitempty"` + Timestamp int64 `protobuf:"varint,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + TxBlockHeight int64 `protobuf:"varint,3,opt,name=txBlockHeight,proto3" json:"txBlockHeight,omitempty"` + TxIndex int64 `protobuf:"varint,4,opt,name=txIndex,proto3" json:"txIndex,omitempty"` + Amount int64 `protobuf:"varint,5,opt,name=amount,proto3" json:"amount,omitempty"` + FeeRate int64 `protobuf:"varint,6,opt,name=feeRate,proto3" json:"feeRate,omitempty"` + TxHash []byte `protobuf:"bytes,7,opt,name=txHash,proto3" json:"txHash,omitempty"` + Utxo *OutPoint `protobuf:"bytes,8,opt,name=utxo,proto3" json:"utxo,omitempty"` + FromAddress string `protobuf:"bytes,9,opt,name=fromAddress,proto3" json:"fromAddress,omitempty"` + AssetSymbol string `protobuf:"bytes,10,opt,name=assetSymbol,proto3" json:"assetSymbol,omitempty"` + TargetAddress string `protobuf:"bytes,11,opt,name=targetAddress,proto3" json:"targetAddress,omitempty"` + Confirmed bool `protobuf:"varint,12,opt,name=confirmed,proto3" json:"confirmed,omitempty"` +} + +func (x *PendingTx) Reset() { + *x = PendingTx{} + if protoimpl.UnsafeEnabled { + mi := &file_rgbx_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PendingTx) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PendingTx) ProtoMessage() {} + +func (x *PendingTx) ProtoReflect() protoreflect.Message { + mi := &file_rgbx_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PendingTx.ProtoReflect.Descriptor instead. +func (*PendingTx) Descriptor() ([]byte, []int) { + return file_rgbx_proto_rawDescGZIP(), []int{12} +} + +func (x *PendingTx) GetActionType() int32 { + if x != nil { + return x.ActionType + } + return 0 +} + +func (x *PendingTx) GetTimestamp() int64 { + if x != nil { + return x.Timestamp + } + return 0 +} + +func (x *PendingTx) GetTxBlockHeight() int64 { + if x != nil { + return x.TxBlockHeight + } + return 0 +} + +func (x *PendingTx) GetTxIndex() int64 { + if x != nil { + return x.TxIndex + } + return 0 +} + +func (x *PendingTx) GetAmount() int64 { + if x != nil { + return x.Amount + } + return 0 +} + +func (x *PendingTx) GetFeeRate() int64 { + if x != nil { + return x.FeeRate + } + return 0 +} + +func (x *PendingTx) GetTxHash() []byte { + if x != nil { + return x.TxHash + } + return nil +} + +func (x *PendingTx) GetUtxo() *OutPoint { + if x != nil { + return x.Utxo + } + return nil +} + +func (x *PendingTx) GetFromAddress() string { + if x != nil { + return x.FromAddress + } + return "" +} + +func (x *PendingTx) GetAssetSymbol() string { + if x != nil { + return x.AssetSymbol + } + return "" +} + +func (x *PendingTx) GetTargetAddress() string { + if x != nil { + return x.TargetAddress + } + return "" +} + +func (x *PendingTx) GetConfirmed() bool { + if x != nil { + return x.Confirmed + } + return false +} + +type PendingTxs struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + PendingList []*PendingTx `protobuf:"bytes,1,rep,name=pendingList,proto3" json:"pendingList,omitempty"` +} + +func (x *PendingTxs) Reset() { + *x = PendingTxs{} + if protoimpl.UnsafeEnabled { + mi := &file_rgbx_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PendingTxs) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PendingTxs) ProtoMessage() {} + +func (x *PendingTxs) ProtoReflect() protoreflect.Message { + mi := &file_rgbx_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PendingTxs.ProtoReflect.Descriptor instead. +func (*PendingTxs) Descriptor() ([]byte, []int) { + return file_rgbx_proto_rawDescGZIP(), []int{13} +} + +func (x *PendingTxs) GetPendingList() []*PendingTx { + if x != nil { + return x.PendingList + } + return nil +} + +type Utxo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + OutPoint string `protobuf:"bytes,1,opt,name=outPoint,proto3" json:"outPoint,omitempty"` + Amount int64 `protobuf:"varint,2,opt,name=amount,proto3" json:"amount,omitempty"` + PkScript []byte `protobuf:"bytes,3,opt,name=pkScript,proto3" json:"pkScript,omitempty"` +} + +func (x *Utxo) Reset() { + *x = Utxo{} + if protoimpl.UnsafeEnabled { + mi := &file_rgbx_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Utxo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Utxo) ProtoMessage() {} + +func (x *Utxo) ProtoReflect() protoreflect.Message { + mi := &file_rgbx_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Utxo.ProtoReflect.Descriptor instead. +func (*Utxo) Descriptor() ([]byte, []int) { + return file_rgbx_proto_rawDescGZIP(), []int{14} +} + +func (x *Utxo) GetOutPoint() string { + if x != nil { + return x.OutPoint + } + return "" +} + +func (x *Utxo) GetAmount() int64 { + if x != nil { + return x.Amount + } + return 0 +} + +func (x *Utxo) GetPkScript() []byte { + if x != nil { + return x.PkScript + } + return nil +} + +type ReqListPendingTx struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + StartHeight int64 `protobuf:"varint,1,opt,name=startHeight,proto3" json:"startHeight,omitempty"` + StartIndex int64 `protobuf:"varint,2,opt,name=startIndex,proto3" json:"startIndex,omitempty"` + Count int32 `protobuf:"varint,3,opt,name=count,proto3" json:"count,omitempty"` + EndHeight int64 `protobuf:"varint,4,opt,name=endHeight,proto3" json:"endHeight,omitempty"` +} + +func (x *ReqListPendingTx) Reset() { + *x = ReqListPendingTx{} + if protoimpl.UnsafeEnabled { + mi := &file_rgbx_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ReqListPendingTx) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReqListPendingTx) ProtoMessage() {} + +func (x *ReqListPendingTx) ProtoReflect() protoreflect.Message { + mi := &file_rgbx_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReqListPendingTx.ProtoReflect.Descriptor instead. +func (*ReqListPendingTx) Descriptor() ([]byte, []int) { + return file_rgbx_proto_rawDescGZIP(), []int{15} +} + +func (x *ReqListPendingTx) GetStartHeight() int64 { + if x != nil { + return x.StartHeight + } + return 0 +} + +func (x *ReqListPendingTx) GetStartIndex() int64 { + if x != nil { + return x.StartIndex + } + return 0 +} + +func (x *ReqListPendingTx) GetCount() int32 { + if x != nil { + return x.Count + } + return 0 +} + +func (x *ReqListPendingTx) GetEndHeight() int64 { + if x != nil { + return x.EndHeight + } + return 0 +} + +type ReqGetPendingTx struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Height int64 `protobuf:"varint,1,opt,name=height,proto3" json:"height,omitempty"` + Index int64 `protobuf:"varint,2,opt,name=index,proto3" json:"index,omitempty"` +} + +func (x *ReqGetPendingTx) Reset() { + *x = ReqGetPendingTx{} + if protoimpl.UnsafeEnabled { + mi := &file_rgbx_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ReqGetPendingTx) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReqGetPendingTx) ProtoMessage() {} + +func (x *ReqGetPendingTx) ProtoReflect() protoreflect.Message { + mi := &file_rgbx_proto_msgTypes[16] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReqGetPendingTx.ProtoReflect.Descriptor instead. +func (*ReqGetPendingTx) Descriptor() ([]byte, []int) { + return file_rgbx_proto_rawDescGZIP(), []int{16} +} + +func (x *ReqGetPendingTx) GetHeight() int64 { + if x != nil { + return x.Height + } + return 0 +} + +func (x *ReqGetPendingTx) GetIndex() int64 { + if x != nil { + return x.Index + } + return 0 +} + +type BtcCommitment struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Protocol []byte `protobuf:"bytes,1,opt,name=protocol,proto3" json:"protocol,omitempty"` + Action uint32 `protobuf:"varint,2,opt,name=action,proto3" json:"action,omitempty"` + Payload []byte `protobuf:"bytes,3,opt,name=payload,proto3" json:"payload,omitempty"` +} + +func (x *BtcCommitment) Reset() { + *x = BtcCommitment{} + if protoimpl.UnsafeEnabled { + mi := &file_rgbx_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BtcCommitment) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BtcCommitment) ProtoMessage() {} + +func (x *BtcCommitment) ProtoReflect() protoreflect.Message { + mi := &file_rgbx_proto_msgTypes[17] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BtcCommitment.ProtoReflect.Descriptor instead. +func (*BtcCommitment) Descriptor() ([]byte, []int) { + return file_rgbx_proto_rawDescGZIP(), []int{17} +} + +func (x *BtcCommitment) GetProtocol() []byte { + if x != nil { + return x.Protocol + } + return nil +} + +func (x *BtcCommitment) GetAction() uint32 { + if x != nil { + return x.Action + } + return 0 +} + +func (x *BtcCommitment) GetPayload() []byte { + if x != nil { + return x.Payload + } + return nil +} + +type TxBlockIndex struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + BlockHeight int64 `protobuf:"varint,1,opt,name=blockHeight,proto3" json:"blockHeight,omitempty"` + TxIndex int64 `protobuf:"varint,2,opt,name=txIndex,proto3" json:"txIndex,omitempty"` +} + +func (x *TxBlockIndex) Reset() { + *x = TxBlockIndex{} + if protoimpl.UnsafeEnabled { + mi := &file_rgbx_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TxBlockIndex) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TxBlockIndex) ProtoMessage() {} + +func (x *TxBlockIndex) ProtoReflect() protoreflect.Message { + mi := &file_rgbx_proto_msgTypes[18] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TxBlockIndex.ProtoReflect.Descriptor instead. +func (*TxBlockIndex) Descriptor() ([]byte, []int) { + return file_rgbx_proto_rawDescGZIP(), []int{18} +} + +func (x *TxBlockIndex) GetBlockHeight() int64 { + if x != nil { + return x.BlockHeight + } + return 0 +} + +func (x *TxBlockIndex) GetTxIndex() int64 { + if x != nil { + return x.TxIndex + } + return 0 +} + +type TxBlockIndexList struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + BlockIndexList []*TxBlockIndex `protobuf:"bytes,1,rep,name=blockIndexList,proto3" json:"blockIndexList,omitempty"` +} + +func (x *TxBlockIndexList) Reset() { + *x = TxBlockIndexList{} + if protoimpl.UnsafeEnabled { + mi := &file_rgbx_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TxBlockIndexList) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TxBlockIndexList) ProtoMessage() {} + +func (x *TxBlockIndexList) ProtoReflect() protoreflect.Message { + mi := &file_rgbx_proto_msgTypes[19] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TxBlockIndexList.ProtoReflect.Descriptor instead. +func (*TxBlockIndexList) Descriptor() ([]byte, []int) { + return file_rgbx_proto_rawDescGZIP(), []int{19} +} + +func (x *TxBlockIndexList) GetBlockIndexList() []*TxBlockIndex { + if x != nil { + return x.BlockIndexList + } + return nil +} + +var File_rgbx_proto protoreflect.FileDescriptor + +var file_rgbx_proto_rawDesc = []byte{ + 0x0a, 0x0a, 0x72, 0x67, 0x62, 0x78, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x74, 0x79, + 0x70, 0x65, 0x73, 0x22, 0xc6, 0x02, 0x0a, 0x0a, 0x52, 0x67, 0x62, 0x78, 0x41, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, + 0x74, 0x79, 0x12, 0x26, 0x0a, 0x04, 0x6d, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x10, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x6d, 0x69, 0x6e, 0x74, 0x41, 0x73, 0x73, + 0x65, 0x74, 0x48, 0x00, 0x52, 0x04, 0x6d, 0x69, 0x6e, 0x74, 0x12, 0x32, 0x0a, 0x08, 0x74, 0x72, + 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x74, + 0x79, 0x70, 0x65, 0x73, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x41, 0x73, 0x73, + 0x65, 0x74, 0x48, 0x00, 0x52, 0x08, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x12, 0x2c, + 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x10, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x54, + 0x78, 0x48, 0x00, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x12, 0x30, 0x0a, 0x09, + 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x44, 0x4b, 0x47, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x10, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x44, 0x4b, + 0x47, 0x48, 0x00, 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x44, 0x4b, 0x47, 0x12, 0x2f, + 0x0a, 0x07, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x13, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x41, + 0x73, 0x73, 0x65, 0x74, 0x48, 0x00, 0x52, 0x07, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x12, + 0x32, 0x0a, 0x08, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x14, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, + 0x61, 0x77, 0x41, 0x73, 0x73, 0x65, 0x74, 0x48, 0x00, 0x52, 0x08, 0x77, 0x69, 0x74, 0x68, 0x64, + 0x72, 0x61, 0x77, 0x42, 0x07, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0xd5, 0x01, 0x0a, + 0x09, 0x72, 0x67, 0x62, 0x78, 0x41, 0x73, 0x73, 0x65, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, + 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1c, + 0x0a, 0x09, 0x70, 0x72, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x09, 0x70, 0x72, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b, + 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x16, + 0x0a, 0x06, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x48, 0x61, + 0x73, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x48, 0x61, + 0x73, 0x68, 0x12, 0x2a, 0x0a, 0x10, 0x67, 0x65, 0x6e, 0x65, 0x73, 0x69, 0x73, 0x42, 0x74, 0x63, + 0x54, 0x78, 0x48, 0x61, 0x73, 0x68, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x67, 0x65, + 0x6e, 0x65, 0x73, 0x69, 0x73, 0x42, 0x74, 0x63, 0x54, 0x78, 0x48, 0x61, 0x73, 0x68, 0x12, 0x14, + 0x0a, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6f, + 0x77, 0x6e, 0x65, 0x72, 0x22, 0xc4, 0x01, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x74, 0x41, 0x73, 0x73, + 0x65, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x72, 0x65, 0x63, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x70, 0x72, 0x65, 0x63, 0x69, + 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6d, 0x6f, + 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, + 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x12, 0x1a, + 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x48, 0x61, 0x73, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x48, 0x61, 0x73, 0x68, 0x12, 0x2f, 0x0a, 0x0a, 0x67, 0x65, + 0x6e, 0x65, 0x73, 0x69, 0x73, 0x4f, 0x75, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, + 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x6f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, + 0x0a, 0x67, 0x65, 0x6e, 0x65, 0x73, 0x69, 0x73, 0x4f, 0x75, 0x74, 0x22, 0x50, 0x0a, 0x08, 0x6f, + 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x69, + 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x69, 0x6e, 0x64, 0x65, + 0x78, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6b, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, 0x6b, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x22, 0xb9, 0x01, + 0x0a, 0x11, 0x75, 0x74, 0x78, 0x6f, 0x53, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x72, + 0x6f, 0x6f, 0x66, 0x12, 0x2a, 0x0a, 0x10, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x49, + 0x6e, 0x70, 0x75, 0x74, 0x49, 0x64, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x73, + 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x49, 0x64, 0x78, 0x12, + 0x26, 0x0a, 0x0e, 0x6f, 0x70, 0x52, 0x65, 0x74, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x49, 0x64, + 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x6f, 0x70, 0x52, 0x65, 0x74, 0x4f, 0x75, + 0x74, 0x70, 0x75, 0x74, 0x49, 0x64, 0x78, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x70, 0x65, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x54, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x73, 0x70, 0x65, + 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x78, 0x12, 0x30, 0x0a, 0x13, 0x6f, 0x70, 0x52, 0x65, 0x74, + 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x50, 0x6b, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x13, 0x6f, 0x70, 0x52, 0x65, 0x74, 0x4f, 0x75, 0x74, 0x70, 0x75, + 0x74, 0x50, 0x6b, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x22, 0xb7, 0x01, 0x0a, 0x0d, 0x74, 0x72, + 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x41, 0x73, 0x73, 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, + 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x79, 0x6d, + 0x62, 0x6f, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x66, + 0x72, 0x6f, 0x6d, 0x55, 0x74, 0x78, 0x6f, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, + 0x72, 0x6f, 0x6d, 0x55, 0x74, 0x78, 0x6f, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x6f, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x02, 0x74, 0x6f, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x41, 0x64, 0x64, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x68, 0x61, + 0x6e, 0x67, 0x65, 0x41, 0x64, 0x64, 0x72, 0x12, 0x2a, 0x0a, 0x10, 0x66, 0x72, 0x6f, 0x6d, 0x55, + 0x74, 0x78, 0x6f, 0x50, 0x6b, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x10, 0x66, 0x72, 0x6f, 0x6d, 0x55, 0x74, 0x78, 0x6f, 0x50, 0x6b, 0x53, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x22, 0xbc, 0x02, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x54, + 0x78, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, + 0x65, 0x12, 0x32, 0x0a, 0x14, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x42, 0x6c, + 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x14, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, + 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x24, 0x0a, 0x0d, 0x74, 0x78, 0x42, 0x6c, 0x6f, 0x63, 0x6b, + 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x74, 0x78, + 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x74, + 0x78, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x74, 0x78, + 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x12, 0x18, 0x0a, + 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, + 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x36, 0x0a, 0x09, 0x75, 0x74, 0x78, 0x6f, 0x50, + 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x74, 0x79, 0x70, + 0x65, 0x73, 0x2e, 0x75, 0x74, 0x78, 0x6f, 0x53, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, + 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x09, 0x75, 0x74, 0x78, 0x6f, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, + 0x31, 0x0a, 0x0a, 0x62, 0x74, 0x63, 0x54, 0x78, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x62, 0x74, 0x63, 0x54, + 0x78, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x0a, 0x62, 0x74, 0x63, 0x54, 0x78, 0x50, 0x72, 0x6f, + 0x6f, 0x66, 0x22, 0xa0, 0x01, 0x0a, 0x0a, 0x62, 0x74, 0x63, 0x54, 0x78, 0x50, 0x72, 0x6f, 0x6f, + 0x66, 0x12, 0x20, 0x0a, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, + 0x67, 0x68, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x78, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x74, 0x78, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x1c, 0x0a, + 0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x74, + 0x78, 0x44, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x74, 0x78, 0x44, + 0x61, 0x74, 0x61, 0x12, 0x20, 0x0a, 0x0b, 0x6d, 0x65, 0x72, 0x6b, 0x6c, 0x65, 0x50, 0x72, 0x6f, + 0x6f, 0x66, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0b, 0x6d, 0x65, 0x72, 0x6b, 0x6c, 0x65, + 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x22, 0x69, 0x0a, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x44, + 0x4b, 0x47, 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x73, 0x73, 0x65, 0x74, 0x53, 0x79, 0x6d, 0x62, 0x6f, + 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x73, 0x73, 0x65, 0x74, 0x53, 0x79, + 0x6d, 0x62, 0x6f, 0x6c, 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x6b, 0x67, 0x41, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x6b, 0x67, 0x41, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6b, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, 0x6b, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x22, 0x9d, 0x01, 0x0a, 0x0c, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x41, 0x73, 0x73, 0x65, + 0x74, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x64, 0x65, 0x70, + 0x6f, 0x73, 0x69, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0e, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x73, 0x73, 0x65, 0x74, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x73, 0x73, 0x65, 0x74, 0x53, 0x79, 0x6d, + 0x62, 0x6f, 0x6c, 0x12, 0x2b, 0x0a, 0x07, 0x74, 0x78, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x62, 0x74, 0x63, + 0x54, 0x78, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x07, 0x74, 0x78, 0x50, 0x72, 0x6f, 0x6f, 0x66, + 0x22, 0x8d, 0x01, 0x0a, 0x0d, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x41, 0x73, 0x73, + 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x66, 0x65, + 0x65, 0x52, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x66, 0x65, 0x65, + 0x52, 0x61, 0x74, 0x65, 0x12, 0x28, 0x0a, 0x0f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x41, 0x64, 0x64, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x64, + 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x64, 0x64, 0x72, 0x12, 0x20, + 0x0a, 0x0b, 0x61, 0x73, 0x73, 0x65, 0x74, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x73, 0x73, 0x65, 0x74, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, + 0x22, 0x94, 0x01, 0x0a, 0x0e, 0x63, 0x72, 0x6f, 0x73, 0x73, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x49, + 0x6e, 0x66, 0x6f, 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x73, 0x73, 0x65, 0x74, 0x53, 0x79, 0x6d, 0x62, + 0x6f, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x73, 0x73, 0x65, 0x74, 0x53, + 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x12, 0x24, 0x0a, 0x0d, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x64, + 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x77, 0x72, + 0x61, 0x70, 0x70, 0x65, 0x64, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x12, 0x1e, 0x0a, 0x0a, 0x74, + 0x73, 0x73, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x74, 0x73, 0x73, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x70, + 0x6b, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, + 0x6b, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x22, 0x80, 0x03, 0x0a, 0x09, 0x70, 0x65, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x54, 0x78, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, + 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x12, 0x24, 0x0a, 0x0d, 0x74, 0x78, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, + 0x69, 0x67, 0x68, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x74, 0x78, 0x42, 0x6c, + 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x78, 0x49, + 0x6e, 0x64, 0x65, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x74, 0x78, 0x49, 0x6e, + 0x64, 0x65, 0x78, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x66, + 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x66, 0x65, + 0x65, 0x52, 0x61, 0x74, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x12, 0x23, 0x0a, + 0x04, 0x75, 0x74, 0x78, 0x6f, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x74, 0x79, + 0x70, 0x65, 0x73, 0x2e, 0x6f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x04, 0x75, 0x74, + 0x78, 0x6f, 0x12, 0x20, 0x0a, 0x0b, 0x66, 0x72, 0x6f, 0x6d, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x66, 0x72, 0x6f, 0x6d, 0x41, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x73, 0x73, 0x65, 0x74, 0x53, 0x79, 0x6d, + 0x62, 0x6f, 0x6c, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x73, 0x73, 0x65, 0x74, + 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x12, 0x24, 0x0a, 0x0d, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1c, 0x0a, 0x09, + 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x09, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x65, 0x64, 0x22, 0x40, 0x0a, 0x0a, 0x70, 0x65, + 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x78, 0x73, 0x12, 0x32, 0x0a, 0x0b, 0x70, 0x65, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x4c, 0x69, 0x73, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, + 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x78, 0x52, + 0x0b, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x4c, 0x69, 0x73, 0x74, 0x22, 0x56, 0x0a, 0x04, + 0x75, 0x74, 0x78, 0x6f, 0x12, 0x1a, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, + 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6b, 0x53, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, 0x6b, 0x53, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x22, 0x88, 0x01, 0x0a, 0x10, 0x52, 0x65, 0x71, 0x4c, 0x69, 0x73, 0x74, + 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x78, 0x12, 0x20, 0x0a, 0x0b, 0x73, 0x74, 0x61, + 0x72, 0x74, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, + 0x73, 0x74, 0x61, 0x72, 0x74, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x73, + 0x74, 0x61, 0x72, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x14, 0x0a, 0x05, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x65, 0x6e, 0x64, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x65, 0x6e, 0x64, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, + 0x3f, 0x0a, 0x0f, 0x52, 0x65, 0x71, 0x47, 0x65, 0x74, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, + 0x54, 0x78, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, + 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, + 0x22, 0x5d, 0x0a, 0x0d, 0x62, 0x74, 0x63, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, + 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x16, 0x0a, + 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x61, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, + 0x4a, 0x0a, 0x0c, 0x74, 0x78, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, + 0x20, 0x0a, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, + 0x74, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x78, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x07, 0x74, 0x78, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x4f, 0x0a, 0x10, 0x74, + 0x78, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x4c, 0x69, 0x73, 0x74, 0x12, + 0x3b, 0x0a, 0x0e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x4c, 0x69, 0x73, + 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, + 0x74, 0x78, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x52, 0x0e, 0x62, 0x6c, + 0x6f, 0x63, 0x6b, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x4c, 0x69, 0x73, 0x74, 0x32, 0x06, 0x0a, 0x04, + 0x72, 0x67, 0x62, 0x78, 0x42, 0x0a, 0x5a, 0x08, 0x2e, 0x2e, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_rgbx_proto_rawDescOnce sync.Once + file_rgbx_proto_rawDescData = file_rgbx_proto_rawDesc +) + +func file_rgbx_proto_rawDescGZIP() []byte { + file_rgbx_proto_rawDescOnce.Do(func() { + file_rgbx_proto_rawDescData = protoimpl.X.CompressGZIP(file_rgbx_proto_rawDescData) + }) + return file_rgbx_proto_rawDescData +} + +var file_rgbx_proto_msgTypes = make([]protoimpl.MessageInfo, 20) +var file_rgbx_proto_goTypes = []interface{}{ + (*RgbxAction)(nil), // 0: types.RgbxAction + (*RgbxAsset)(nil), // 1: types.rgbxAsset + (*MintAsset)(nil), // 2: types.mintAsset + (*OutPoint)(nil), // 3: types.outPoint + (*UtxoSpendingProof)(nil), // 4: types.utxoSpendingProof + (*TransferAsset)(nil), // 5: types.transferAsset + (*ConfirmTx)(nil), // 6: types.confirmTx + (*BtcTxProof)(nil), // 7: types.btcTxProof + (*CommitDKG)(nil), // 8: types.commitDKG + (*DepositAsset)(nil), // 9: types.depositAsset + (*WithdrawAsset)(nil), // 10: types.withdrawAsset + (*CrossChainInfo)(nil), // 11: types.crossChainInfo + (*PendingTx)(nil), // 12: types.pendingTx + (*PendingTxs)(nil), // 13: types.pendingTxs + (*Utxo)(nil), // 14: types.utxo + (*ReqListPendingTx)(nil), // 15: types.ReqListPendingTx + (*ReqGetPendingTx)(nil), // 16: types.ReqGetPendingTx + (*BtcCommitment)(nil), // 17: types.btcCommitment + (*TxBlockIndex)(nil), // 18: types.txBlockIndex + (*TxBlockIndexList)(nil), // 19: types.txBlockIndexList +} +var file_rgbx_proto_depIdxs = []int32{ + 2, // 0: types.RgbxAction.mint:type_name -> types.mintAsset + 5, // 1: types.RgbxAction.transfer:type_name -> types.transferAsset + 6, // 2: types.RgbxAction.confirm:type_name -> types.confirmTx + 8, // 3: types.RgbxAction.commitDKG:type_name -> types.commitDKG + 9, // 4: types.RgbxAction.deposit:type_name -> types.depositAsset + 10, // 5: types.RgbxAction.withdraw:type_name -> types.withdrawAsset + 3, // 6: types.mintAsset.genesisOut:type_name -> types.outPoint + 4, // 7: types.confirmTx.utxoProof:type_name -> types.utxoSpendingProof + 7, // 8: types.confirmTx.btcTxProof:type_name -> types.btcTxProof + 7, // 9: types.depositAsset.txProof:type_name -> types.btcTxProof + 3, // 10: types.pendingTx.utxo:type_name -> types.outPoint + 12, // 11: types.pendingTxs.pendingList:type_name -> types.pendingTx + 18, // 12: types.txBlockIndexList.blockIndexList:type_name -> types.txBlockIndex + 13, // [13:13] is the sub-list for method output_type + 13, // [13:13] is the sub-list for method input_type + 13, // [13:13] is the sub-list for extension type_name + 13, // [13:13] is the sub-list for extension extendee + 0, // [0:13] is the sub-list for field type_name +} + +func init() { file_rgbx_proto_init() } +func file_rgbx_proto_init() { + if File_rgbx_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_rgbx_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RgbxAction); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rgbx_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RgbxAsset); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rgbx_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MintAsset); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rgbx_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OutPoint); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rgbx_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UtxoSpendingProof); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rgbx_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TransferAsset); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rgbx_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ConfirmTx); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rgbx_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BtcTxProof); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rgbx_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CommitDKG); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rgbx_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DepositAsset); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rgbx_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*WithdrawAsset); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rgbx_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CrossChainInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rgbx_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PendingTx); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rgbx_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PendingTxs); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rgbx_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Utxo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rgbx_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ReqListPendingTx); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rgbx_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ReqGetPendingTx); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rgbx_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BtcCommitment); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rgbx_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TxBlockIndex); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_rgbx_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TxBlockIndexList); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_rgbx_proto_msgTypes[0].OneofWrappers = []interface{}{ + (*RgbxAction_Mint)(nil), + (*RgbxAction_Transfer)(nil), + (*RgbxAction_Confirm)(nil), + (*RgbxAction_CommitDKG)(nil), + (*RgbxAction_Deposit)(nil), + (*RgbxAction_Withdraw)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_rgbx_proto_rawDesc, + NumEnums: 0, + NumMessages: 20, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_rgbx_proto_goTypes, + DependencyIndexes: file_rgbx_proto_depIdxs, + MessageInfos: file_rgbx_proto_msgTypes, + }.Build() + File_rgbx_proto = out.File + file_rgbx_proto_rawDesc = nil + file_rgbx_proto_goTypes = nil + file_rgbx_proto_depIdxs = nil +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConnInterface + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion6 + +// RgbxClient is the client API for Rgbx service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type RgbxClient interface { +} + +type rgbxClient struct { + cc grpc.ClientConnInterface +} + +func NewRgbxClient(cc grpc.ClientConnInterface) RgbxClient { + return &rgbxClient{cc} +} + +// RgbxServer is the server API for Rgbx service. +type RgbxServer interface { +} + +// UnimplementedRgbxServer can be embedded to have forward compatible implementations. +type UnimplementedRgbxServer struct { +} + +func RegisterRgbxServer(s *grpc.Server, srv RgbxServer) { + s.RegisterService(&_Rgbx_serviceDesc, srv) +} + +var _Rgbx_serviceDesc = grpc.ServiceDesc{ + ServiceName: "types.rgbx", + HandlerType: (*RgbxServer)(nil), + Methods: []grpc.MethodDesc{}, + Streams: []grpc.StreamDesc{}, + Metadata: "rgbx.proto", +} diff --git a/plugin/dapp/rgbx/types/types_test.go b/plugin/dapp/rgbx/types/types_test.go new file mode 100644 index 0000000000..c27721da18 --- /dev/null +++ b/plugin/dapp/rgbx/types/types_test.go @@ -0,0 +1,40 @@ +package types + +import ( + "fmt" + "testing" + + "github.com/33cn/chain33/types" + "github.com/btcsuite/btcd/txscript" + "github.com/stretchr/testify/require" +) + +func Test_GetActionName(t *testing.T) { + + require.Equal(t, NameMintAction, GetActionName(TyMintAction)) + require.Equal(t, NameTransferAction, GetActionName(TyTransferAction)) + require.Equal(t, NameConfirmAction, GetActionName(TyConfirmAction)) + + require.Equal(t, "unknownAction", GetActionName(0)) +} + +func Test_UtxoAddress(t *testing.T) { + + utxoAddr := "74503993e7c8d4280f6fbb99ae5aaa92231a1981a358e40f97e2b4f4dfbea13c:0" + require.True(t, IsUtxoAddress(utxoAddr)) + out, err := NewOutPointFromString(utxoAddr) + require.NoError(t, err) + require.Equal(t, utxoAddr, out.ToString()) +} + +func Test_btcCommitment(t *testing.T) { + + tx := &types.Transaction{Execer: []byte("coins")} + commit := &BtcCommitment{ + Action: TyConfirmAction, + Payload: tx.Hash(), + } + script, err := txscript.NullDataScript(types.Encode(commit)) + require.NoError(t, err) + fmt.Println(len(script)) +}