Rename folders (#97)

* Rename arkd folder & drop cli

* Rename ark cli folder & update docs

* Update readme

* Fix

* scripts: add build-all

* Add target to build cli for all platforms

* Update build scripts

---------

Co-authored-by: tiero <3596602+tiero@users.noreply.github.com>
This commit is contained in:
Pietralberto Mazza
2024-02-09 19:32:58 +01:00
committed by GitHub
parent 0d8c7bffb2
commit dc00d60585
119 changed files with 154 additions and 449 deletions

24
server/.gitignore vendored Executable file
View File

@@ -0,0 +1,24 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with 'go test -c'
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
/build/
/dist/
DS_Store
._.DS_Store
**/.DS_Store
**/._.DS_Store
go.work
go.work.sum

99
server/.goreleaser.yaml Executable file
View File

@@ -0,0 +1,99 @@
builds:
- id: "arkd"
main: ./cmd/arkd
ldflags:
- -s -X 'main.version={{.Version}}' -X 'main.commit={{.Commit}}' -X 'main.date={{.Date}}'
goos:
- linux
- darwin
goarch:
- amd64
- arm64
binary: arkd
## flag the semver v**.**.**-<tag>.* as pre-release on Github
release:
prerelease: auto
checksum:
name_template: "checksums.txt"
snapshot:
name_template: "{{ .Tag }}-next"
changelog:
use: github-native
archives:
- id: arkd
format: binary
builds:
- arkd
name_template: "arkd-v{{ .Version }}-{{ .Os }}-{{ .Arch }}"
dockers:
###########################
# tag latest & prerelease #
###########################
#amd64
- image_templates:
- "ghcr.io/ark-network/arkd:{{ .Tag }}-amd64"
# push always either release or prerelease with a docker tag with the semver only
skip_push: "false"
use: buildx
ids:
- arkd
dockerfile: goreleaser.Dockerfile
# GOOS of the built binaries/packages that should be used.
goos: linux
# GOARCH of the built binaries/packages that should be used.
goarch: amd64
# Template of the docker build flags.
build_flag_templates:
- "--platform=linux/amd64"
- "--pull"
- "--label=org.opencontainers.image.created={{.Date}}"
- "--label=org.opencontainers.image.title=arkd"
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
- "--label=org.opencontainers.image.version={{.Version}}"
- "--build-arg=VERSION={{.Version}}"
- "--build-arg=COMMIT={{.Commit}}"
- "--build-arg=DATE={{.Date}}"
- image_templates:
- "ghcr.io/ark-network/arkd:{{ .Tag }}-arm64v8"
# push always either release or prerelease with a docker tag with the semver only
skip_push: "false"
use: buildx
ids:
- arkd
dockerfile: goreleaser.Dockerfile
# GOOS of the built binaries/packages that should be used.
goos: linux
# GOARCH of the built binaries/packages that should be used.
goarch: arm64
# Template of the docker build flags.
build_flag_templates:
- "--platform=linux/arm64/v8"
- "--pull"
- "--label=org.opencontainers.image.created={{.Date}}"
- "--label=org.opencontainers.image.title=arkd"
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
- "--label=org.opencontainers.image.version={{.Version}}"
- "--build-arg=VERSION={{.Version}}"
- "--build-arg=COMMIT={{.Commit}}"
- "--build-arg=DATE={{.Date}}"
docker_manifests:
- name_template: ghcr.io/ark-network/arkd:{{ .Tag }}
image_templates:
- ghcr.io/ark-network/arkd:{{ .Tag }}-amd64
- ghcr.io/ark-network/arkd:{{ .Tag }}-arm64v8
skip_push: "false"
- name_template: ghcr.io/ark-network/arkd:latest
image_templates:
- ghcr.io/ark-network/arkd:{{ .Tag }}-amd64
- ghcr.io/ark-network/arkd:{{ .Tag }}-arm64v8
skip_push: auto

22
server/LICENSE Executable file
View File

@@ -0,0 +1,22 @@
MIT License
Copyright (c) 2023 ark-network
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

61
server/Makefile Executable file
View File

@@ -0,0 +1,61 @@
.PHONY: build clean cov help intergrationtest lint run test vet proto proto-lint
## build: build for all platforms
build:
@echo "Building arkd binary..."
@bash ./scripts/build
## clean: cleans the binary
clean:
@echo "Cleaning..."
@go clean
## cov: generates coverage report
cov:
@echo "Coverage..."
@go test -cover ./...
## help: prints this help message
help:
@echo "Usage: \n"
@sed -n 's/^##//p' ${MAKEFILE_LIST} | column -t -s ':' | sed -e 's/^/ /'
## intergrationtest: runs integration tests
integrationtest:
@echo "Running integration tests..."
@find . -name go.mod -execdir go test -v -count=1 -race $(go list ./... | grep internal/test) \;
## lint: lint codebase
lint:
@echo "Linting code..."
@golangci-lint run --fix
## run: run in dev mode
run: clean
@echo "Running arkd in dev mode..."
@export ARK_WALLET_ADDR=localhost:18000; \
export ARK_ROUND_INTERVAL=30; \
go run ./cmd/arkd
## test: runs unit and component tests
test:
@echo "Running unit tests..."
@find . -name go.mod -execdir go test -v -count=1 -race ./... $(go list ./... | grep -v internal/test) \;
## vet: code analysis
vet:
@echo "Running code analysis..."
@go vet ./...
## proto: compile proto stubs
proto: proto-lint
@echo "Compiling stubs..."
@docker run --volume "$(shell pwd):/workspace" --workdir /workspace buf generate buf.build/vulpemventures/ocean
@docker run --volume "$(shell pwd):/workspace" --workdir /workspace buf generate
## proto-lint: lint protos
proto-lint:
@echo "Linting protos..."
@docker build -q -t buf -f buf.Dockerfile . &> /dev/null
@docker run --volume "$(shell pwd):/workspace" --workdir /workspace buf lint

0
server/README.md Executable file
View File

View File

@@ -0,0 +1,568 @@
{
"swagger": "2.0",
"info": {
"title": "ark/v1/service.proto",
"version": "version not set"
},
"tags": [
{
"name": "ArkService"
}
],
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"paths": {
"/v1/events": {
"get": {
"operationId": "ArkService_GetEventStream",
"responses": {
"200": {
"description": "A successful response.(streaming responses)",
"schema": {
"type": "object",
"properties": {
"result": {
"$ref": "#/definitions/v1GetEventStreamResponse"
},
"error": {
"$ref": "#/definitions/rpcStatus"
}
},
"title": "Stream result of v1GetEventStreamResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"tags": [
"ArkService"
]
}
},
"/v1/faucet/{address}": {
"post": {
"operationId": "ArkService_Faucet",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/v1FaucetResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "address",
"in": "path",
"required": true,
"type": "string"
}
],
"tags": [
"ArkService"
]
}
},
"/v1/payment/claim": {
"post": {
"operationId": "ArkService_ClaimPayment",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/v1ClaimPaymentResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/v1ClaimPaymentRequest"
}
}
],
"tags": [
"ArkService"
]
}
},
"/v1/payment/finalize": {
"post": {
"operationId": "ArkService_FinalizePayment",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/v1FinalizePaymentResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/v1FinalizePaymentRequest"
}
}
],
"tags": [
"ArkService"
]
}
},
"/v1/payment/register": {
"post": {
"operationId": "ArkService_RegisterPayment",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/v1RegisterPaymentResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/v1RegisterPaymentRequest"
}
}
],
"tags": [
"ArkService"
]
}
},
"/v1/ping/{paymentId}": {
"get": {
"operationId": "ArkService_Ping",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/v1PingResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "paymentId",
"in": "path",
"required": true,
"type": "string"
}
],
"tags": [
"ArkService"
]
}
},
"/v1/pubkey": {
"get": {
"operationId": "ArkService_GetPubkey",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/v1GetPubkeyResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"tags": [
"ArkService"
]
}
},
"/v1/round/{txid}": {
"get": {
"operationId": "ArkService_GetRound",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/v1GetRoundResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "txid",
"in": "path",
"required": true,
"type": "string"
}
],
"tags": [
"ArkService"
]
}
},
"/v1/vtxos/{address}": {
"get": {
"operationId": "ArkService_ListVtxos",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/v1ListVtxosResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "address",
"in": "path",
"required": true,
"type": "string"
}
],
"tags": [
"ArkService"
]
}
}
},
"definitions": {
"protobufAny": {
"type": "object",
"properties": {
"@type": {
"type": "string"
}
},
"additionalProperties": {}
},
"rpcStatus": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"format": "int32"
},
"message": {
"type": "string"
},
"details": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/protobufAny"
}
}
}
},
"v1ClaimPaymentRequest": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Mocks wabisabi's credentials."
},
"outputs": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/v1Output"
},
"description": "List of receivers for a registered payment."
}
}
},
"v1ClaimPaymentResponse": {
"type": "object"
},
"v1FaucetResponse": {
"type": "object"
},
"v1FinalizePaymentRequest": {
"type": "object",
"properties": {
"signedForfeitTxs": {
"type": "array",
"items": {
"type": "string"
},
"description": "Forfeit txs signed by the user."
}
}
},
"v1FinalizePaymentResponse": {
"type": "object"
},
"v1GetEventStreamResponse": {
"type": "object",
"properties": {
"roundFinalization": {
"$ref": "#/definitions/v1RoundFinalizationEvent"
},
"roundFinalized": {
"$ref": "#/definitions/v1RoundFinalizedEvent"
},
"roundFailed": {
"$ref": "#/definitions/v1RoundFailed"
}
}
},
"v1GetPubkeyResponse": {
"type": "object",
"properties": {
"pubkey": {
"type": "string"
}
}
},
"v1GetRoundResponse": {
"type": "object",
"properties": {
"round": {
"$ref": "#/definitions/v1Round"
}
}
},
"v1Input": {
"type": "object",
"properties": {
"txid": {
"type": "string"
},
"vout": {
"type": "integer",
"format": "int64"
}
}
},
"v1ListVtxosResponse": {
"type": "object",
"properties": {
"vtxos": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/v1Vtxo"
}
}
}
},
"v1Node": {
"type": "object",
"properties": {
"txid": {
"type": "string"
},
"tx": {
"type": "string"
},
"parentTxid": {
"type": "string"
}
}
},
"v1Output": {
"type": "object",
"properties": {
"address": {
"type": "string",
"description": "Either the offchain or onchain address."
},
"amount": {
"type": "string",
"format": "uint64",
"description": "Amount to send in satoshis."
}
}
},
"v1PingResponse": {
"type": "object"
},
"v1RegisterPaymentRequest": {
"type": "object",
"properties": {
"inputs": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/v1Input"
}
}
}
},
"v1RegisterPaymentResponse": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Mocks wabisabi's credentials."
}
}
},
"v1Round": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"start": {
"type": "string",
"format": "int64"
},
"end": {
"type": "string",
"format": "int64"
},
"txid": {
"type": "string"
},
"congestionTree": {
"$ref": "#/definitions/v1Tree"
}
}
},
"v1RoundFailed": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"reason": {
"type": "string"
}
}
},
"v1RoundFinalizationEvent": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"poolPartialTx": {
"type": "string"
},
"forfeitTxs": {
"type": "array",
"items": {
"type": "string"
}
},
"congestionTree": {
"$ref": "#/definitions/v1Tree"
}
}
},
"v1RoundFinalizedEvent": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"poolTxid": {
"type": "string"
}
}
},
"v1Tree": {
"type": "object",
"properties": {
"levels": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/v1TreeLevel"
}
}
}
},
"v1TreeLevel": {
"type": "object",
"properties": {
"nodes": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/v1Node"
}
}
}
},
"v1Vtxo": {
"type": "object",
"properties": {
"outpoint": {
"$ref": "#/definitions/v1Input"
},
"receiver": {
"$ref": "#/definitions/v1Output"
},
"spent": {
"type": "boolean"
},
"poolTxid": {
"type": "string"
}
}
}
}
}

View File

@@ -0,0 +1,325 @@
{
"swagger": "2.0",
"info": {
"title": "ocean/v1/account.proto",
"version": "version not set"
},
"tags": [
{
"name": "AccountService"
}
],
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"paths": {},
"definitions": {
"TemplateFormat": {
"type": "string",
"enum": [
"FORMAT_UNSPECIFIED",
"FORMAT_DESCRIPTOR",
"FORMAT_MINISCRIPT",
"FORMAT_IONIO",
"FORMAT_RAW"
],
"default": "FORMAT_UNSPECIFIED"
},
"googlerpcStatus": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"format": "int32"
},
"message": {
"type": "string"
},
"details": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/protobufAny"
}
}
}
},
"protobufAny": {
"type": "object",
"properties": {
"@type": {
"type": "string"
}
},
"additionalProperties": {}
},
"v1AccountInfo": {
"type": "object",
"properties": {
"namespace": {
"type": "string",
"description": "Account namespace."
},
"label": {
"type": "string",
"description": "Account label."
},
"derivationPath": {
"type": "string",
"description": "Derivation path."
},
"xpubs": {
"type": "array",
"items": {
"type": "string"
},
"description": "Xpubs."
},
"masterBlindingKey": {
"type": "string",
"description": "The master blinding key of the account to derive blinding keypairs from."
}
}
},
"v1BalanceInfo": {
"type": "object",
"properties": {
"confirmedBalance": {
"type": "string",
"format": "uint64",
"description": "Balance of utxos with 1+ confirmations."
},
"unconfirmedBalance": {
"type": "string",
"format": "uint64",
"description": "Balance of utxos with no confirmations."
},
"lockedBalance": {
"type": "string",
"format": "uint64",
"description": "Balance of locked utxos."
},
"totalBalance": {
"type": "string",
"format": "uint64",
"description": "Total balance."
}
}
},
"v1BalanceResponse": {
"type": "object",
"properties": {
"balance": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/v1BalanceInfo"
},
"description": "The balance (total, confirmed, unconfirmed) per each asset."
}
}
},
"v1BlockDetails": {
"type": "object",
"properties": {
"hash": {
"type": "string",
"description": "Hash of the block."
},
"height": {
"type": "string",
"format": "uint64",
"description": "Heighth (index) of the block."
},
"timestamp": {
"type": "string",
"format": "int64",
"description": "Timestamp of the block."
}
}
},
"v1CreateAccountBIP44Response": {
"type": "object",
"properties": {
"info": {
"$ref": "#/definitions/v1AccountInfo",
"description": "Info about the new account."
}
}
},
"v1CreateAccountCustomResponse": {
"type": "object",
"properties": {
"info": {
"$ref": "#/definitions/v1AccountInfo",
"description": "Info about the new account."
}
}
},
"v1CreateAccountMultiSigResponse": {
"type": "object",
"properties": {
"info": {
"$ref": "#/definitions/v1AccountInfo",
"description": "Info about the new account."
}
}
},
"v1DeleteAccountResponse": {
"type": "object"
},
"v1DeriveAddressesResponse": {
"type": "object",
"properties": {
"addresses": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"v1DeriveChangeAddressesResponse": {
"type": "object",
"properties": {
"addresses": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"v1ListAddressesResponse": {
"type": "object",
"properties": {
"addresses": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"v1ListUtxosResponse": {
"type": "object",
"properties": {
"spendableUtxos": {
"$ref": "#/definitions/v1Utxos",
"description": "List of spendable utxos."
},
"lockedUtxos": {
"$ref": "#/definitions/v1Utxos",
"description": "List of currently locked utxos."
}
}
},
"v1SetAccountLabelResponse": {
"type": "object",
"properties": {
"info": {
"$ref": "#/definitions/v1AccountInfo",
"description": "Info about the updated account."
}
}
},
"v1SetAccountTemplateResponse": {
"type": "object"
},
"v1Template": {
"type": "object",
"properties": {
"format": {
"$ref": "#/definitions/TemplateFormat"
},
"value": {
"type": "string"
}
}
},
"v1Utxo": {
"type": "object",
"properties": {
"txid": {
"type": "string",
"description": "Txid of the uxo."
},
"index": {
"type": "integer",
"format": "int64",
"description": "Output index."
},
"asset": {
"type": "string",
"description": "Asset."
},
"value": {
"type": "string",
"format": "uint64",
"description": "Value."
},
"script": {
"type": "string",
"description": "Script."
},
"assetBlinder": {
"type": "string",
"description": "Asset blinder for confidential utxo."
},
"valueBlinder": {
"type": "string",
"description": "Value blinder for confidential utxo."
},
"accountName": {
"type": "string",
"description": "Namespace of the account owning the utxo."
},
"spentStatus": {
"$ref": "#/definitions/v1UtxoStatus",
"description": "Info about utxo's spent status."
},
"confirmedStatus": {
"$ref": "#/definitions/v1UtxoStatus",
"description": "Info about utxo's confirmation status."
},
"redeemScript": {
"type": "string",
"description": "Redeem script locking the utxo in case its owned by a multisig account."
}
}
},
"v1UtxoStatus": {
"type": "object",
"properties": {
"txid": {
"type": "string"
},
"blockInfo": {
"$ref": "#/definitions/v1BlockDetails"
},
"txhex": {
"type": "string"
}
}
},
"v1Utxos": {
"type": "object",
"properties": {
"accountName": {
"type": "string",
"description": "Account namespace."
},
"utxos": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/v1Utxo"
},
"description": "List of utxos."
}
}
}
}
}

View File

@@ -0,0 +1,265 @@
{
"swagger": "2.0",
"info": {
"title": "ocean/v1/notification.proto",
"version": "version not set"
},
"tags": [
{
"name": "NotificationService"
}
],
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"paths": {},
"definitions": {
"googlerpcStatus": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"format": "int32"
},
"message": {
"type": "string"
},
"details": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/protobufAny"
}
}
}
},
"protobufAny": {
"type": "object",
"properties": {
"@type": {
"type": "string"
}
},
"additionalProperties": {}
},
"v1AddWebhookResponse": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "The id of the new webhook."
}
}
},
"v1BlockDetails": {
"type": "object",
"properties": {
"hash": {
"type": "string",
"description": "Hash of the block."
},
"height": {
"type": "string",
"format": "uint64",
"description": "Heighth (index) of the block."
},
"timestamp": {
"type": "string",
"format": "int64",
"description": "Timestamp of the block."
}
}
},
"v1ListWebhooksResponse": {
"type": "object",
"properties": {
"webhookInfo": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/v1WebhookInfo"
},
"description": "The list of info about the webhooks regitered for an action."
}
}
},
"v1RemoveWebhookResponse": {
"type": "object"
},
"v1TransactionNotificationsResponse": {
"type": "object",
"properties": {
"eventType": {
"$ref": "#/definitions/v1TxEventType",
"description": "Tx event type."
},
"accountNames": {
"type": "array",
"items": {
"type": "string"
},
"description": "Account names."
},
"txhex": {
"type": "string",
"description": "Tx in hex format."
},
"txid": {
"type": "string",
"description": "Txid of transaction."
},
"blockDetails": {
"$ref": "#/definitions/v1BlockDetails",
"description": "Details of the block including the tx."
}
}
},
"v1TxEventType": {
"type": "string",
"enum": [
"TX_EVENT_TYPE_UNSPECIFIED",
"TX_EVENT_TYPE_BROADCASTED",
"TX_EVENT_TYPE_UNCONFIRMED",
"TX_EVENT_TYPE_CONFIRMED"
],
"default": "TX_EVENT_TYPE_UNSPECIFIED",
"description": " - TX_EVENT_TYPE_BROADCASTED: Tx broadcasted.\n - TX_EVENT_TYPE_UNCONFIRMED: Tx unconfirmed.\n - TX_EVENT_TYPE_CONFIRMED: Tx confirmed."
},
"v1UnwatchExternalScriptResponse": {
"type": "object"
},
"v1Utxo": {
"type": "object",
"properties": {
"txid": {
"type": "string",
"description": "Txid of the uxo."
},
"index": {
"type": "integer",
"format": "int64",
"description": "Output index."
},
"asset": {
"type": "string",
"description": "Asset."
},
"value": {
"type": "string",
"format": "uint64",
"description": "Value."
},
"script": {
"type": "string",
"description": "Script."
},
"assetBlinder": {
"type": "string",
"description": "Asset blinder for confidential utxo."
},
"valueBlinder": {
"type": "string",
"description": "Value blinder for confidential utxo."
},
"accountName": {
"type": "string",
"description": "Namespace of the account owning the utxo."
},
"spentStatus": {
"$ref": "#/definitions/v1UtxoStatus",
"description": "Info about utxo's spent status."
},
"confirmedStatus": {
"$ref": "#/definitions/v1UtxoStatus",
"description": "Info about utxo's confirmation status."
},
"redeemScript": {
"type": "string",
"description": "Redeem script locking the utxo in case its owned by a multisig account."
}
}
},
"v1UtxoEventType": {
"type": "string",
"enum": [
"UTXO_EVENT_TYPE_UNSPECIFIED",
"UTXO_EVENT_TYPE_NEW",
"UTXO_EVENT_TYPE_CONFIRMED",
"UTXO_EVENT_TYPE_LOCKED",
"UTXO_EVENT_TYPE_UNLOCKED",
"UTXO_EVENT_TYPE_SPENT",
"UTXO_EVENT_TYPE_CONFIRMED_SPENT"
],
"default": "UTXO_EVENT_TYPE_UNSPECIFIED"
},
"v1UtxoStatus": {
"type": "object",
"properties": {
"txid": {
"type": "string"
},
"blockInfo": {
"$ref": "#/definitions/v1BlockDetails"
},
"txhex": {
"type": "string"
}
}
},
"v1UtxosNotificationsResponse": {
"type": "object",
"properties": {
"eventType": {
"$ref": "#/definitions/v1UtxoEventType",
"description": "The event's type occured for the utxos."
},
"utxos": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/v1Utxo"
},
"description": "List of utxos for which occured the event."
}
}
},
"v1WatchExternalScriptResponse": {
"type": "object",
"properties": {
"label": {
"type": "string"
}
}
},
"v1WebhookEventType": {
"type": "string",
"enum": [
"WEBHOOK_EVENT_TYPE_UNSPECIFIED",
"WEBHOOK_EVENT_TYPE_TRANSACTION",
"WEBHOOK_EVENT_TYPE_UTXO"
],
"default": "WEBHOOK_EVENT_TYPE_UNSPECIFIED",
"description": " - WEBHOOK_EVENT_TYPE_TRANSACTION: Receive notification about transactions.\n - WEBHOOK_EVENT_TYPE_UTXO: Receive notifications about utxos."
},
"v1WebhookInfo": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "The id of the webhook."
},
"endpoint": {
"type": "string",
"description": "The endpoint of the external service to reach."
},
"isSecured": {
"type": "boolean",
"description": "Whether the outgoing requests are authenticated."
}
}
}
}
}

View File

@@ -0,0 +1,393 @@
{
"swagger": "2.0",
"info": {
"title": "ocean/v1/transaction.proto",
"version": "version not set"
},
"tags": [
{
"name": "TransactionService"
}
],
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"paths": {},
"definitions": {
"SelectUtxosRequestStrategy": {
"type": "string",
"enum": [
"STRATEGY_UNSPECIFIED",
"STRATEGY_BRANCH_BOUND",
"STRATEGY_FRAGMENT"
],
"default": "STRATEGY_UNSPECIFIED",
"description": "Coin-selection algorithm."
},
"googlerpcStatus": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"format": "int32"
},
"message": {
"type": "string"
},
"details": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/protobufAny"
}
}
}
},
"protobufAny": {
"type": "object",
"properties": {
"@type": {
"type": "string"
}
},
"additionalProperties": {}
},
"v1BlindPsetResponse": {
"type": "object",
"properties": {
"pset": {
"type": "string",
"description": "Updated partial transaction with blinded inputs/outputs in base64 format."
}
}
},
"v1BlockDetails": {
"type": "object",
"properties": {
"hash": {
"type": "string",
"description": "Hash of the block."
},
"height": {
"type": "string",
"format": "uint64",
"description": "Heighth (index) of the block."
},
"timestamp": {
"type": "string",
"format": "int64",
"description": "Timestamp of the block."
}
}
},
"v1BroadcastTransactionResponse": {
"type": "object",
"properties": {
"txid": {
"type": "string",
"description": "Hash of the broadcasted transaction."
}
}
},
"v1BurnResponse": {
"type": "object",
"properties": {
"txHex": {
"type": "string",
"description": "Signed tx in hex format."
}
}
},
"v1ClaimPegInResponse": {
"type": "object",
"properties": {
"txHex": {
"type": "string",
"description": "Signed tx in hex format."
}
}
},
"v1CreatePsetResponse": {
"type": "object",
"properties": {
"pset": {
"type": "string",
"description": "New partial transaction in base64 format."
}
}
},
"v1EstimateFeesResponse": {
"type": "object",
"properties": {
"feeAmount": {
"type": "string",
"format": "uint64"
}
}
},
"v1GetTransactionResponse": {
"type": "object",
"properties": {
"txHex": {
"type": "string",
"description": "Raw transaction in hex format."
},
"blockDetails": {
"$ref": "#/definitions/v1BlockDetails",
"description": "Deatils of the block including the transaction."
}
}
},
"v1Input": {
"type": "object",
"properties": {
"txid": {
"type": "string",
"description": "Previous output txid."
},
"index": {
"type": "integer",
"format": "int64",
"description": "Previous tx output index."
},
"script": {
"type": "string",
"description": "Prevout script."
},
"scriptsigSize": {
"type": "string",
"format": "uint64",
"description": "Input scriptsig size."
},
"witnessSize": {
"type": "string",
"format": "uint64",
"description": "Input witness size."
}
}
},
"v1MintResponse": {
"type": "object",
"properties": {
"txHex": {
"type": "string",
"description": "Signed tx in hex format."
}
}
},
"v1Output": {
"type": "object",
"properties": {
"asset": {
"type": "string",
"description": "Asset hash."
},
"amount": {
"type": "string",
"format": "uint64",
"description": "Sent amount."
},
"address": {
"type": "string",
"description": "Address to send funds to."
},
"script": {
"type": "string",
"description": "ScriptPubkey to send funds to (alternative to address)."
},
"blindingPubkey": {
"type": "string",
"description": "Blinding public key to make output confidential (alternative to address)."
}
}
},
"v1PegInAddressResponse": {
"type": "object",
"properties": {
"accountName": {
"type": "string",
"description": "Account name."
},
"mainChainAddress": {
"type": "string",
"description": "Main-chain deposit address to send bitcoin to."
},
"claimScript": {
"type": "string",
"description": "Claim script committed to by the main-chain address."
}
}
},
"v1RemintResponse": {
"type": "object",
"properties": {
"txHex": {
"type": "string",
"description": "Signed tx in hex format."
}
}
},
"v1SelectUtxosResponse": {
"type": "object",
"properties": {
"utxos": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/v1Utxo"
},
"description": "List of selected utxos."
},
"change": {
"type": "string",
"format": "uint64",
"description": "Eventual change amount if utxos cumulative sum exceeds the target amount."
},
"expirationDate": {
"type": "string",
"format": "int64",
"description": "Expiration date for the selected utxo, which are temporary locked to\nprevent double spending them."
}
}
},
"v1SignPsetResponse": {
"type": "object",
"properties": {
"pset": {
"type": "string",
"description": "Signed partial transaction in base64 format."
}
}
},
"v1SignPsetWithSchnorrKeyResponse": {
"type": "object",
"properties": {
"signedTx": {
"type": "string"
}
}
},
"v1SignTransactionResponse": {
"type": "object",
"properties": {
"txHex": {
"type": "string",
"description": "Raw signed transaction."
}
}
},
"v1TransferResponse": {
"type": "object",
"properties": {
"txHex": {
"type": "string",
"description": "Signed tx in hex format."
}
}
},
"v1UnblindedInput": {
"type": "object",
"properties": {
"index": {
"type": "integer",
"format": "int64",
"description": "Index of the pset input."
},
"asset": {
"type": "string",
"description": "Revealed prevout asset."
},
"amount": {
"type": "string",
"format": "uint64",
"description": "Revealed prevout amount."
},
"assetBlinder": {
"type": "string",
"description": "Prevout asset blinder."
},
"amountBlinder": {
"type": "string",
"description": "Prevout amount blinder."
}
}
},
"v1UpdatePsetResponse": {
"type": "object",
"properties": {
"pset": {
"type": "string",
"description": "Updated partial transaction in base64 format."
}
}
},
"v1Utxo": {
"type": "object",
"properties": {
"txid": {
"type": "string",
"description": "Txid of the uxo."
},
"index": {
"type": "integer",
"format": "int64",
"description": "Output index."
},
"asset": {
"type": "string",
"description": "Asset."
},
"value": {
"type": "string",
"format": "uint64",
"description": "Value."
},
"script": {
"type": "string",
"description": "Script."
},
"assetBlinder": {
"type": "string",
"description": "Asset blinder for confidential utxo."
},
"valueBlinder": {
"type": "string",
"description": "Value blinder for confidential utxo."
},
"accountName": {
"type": "string",
"description": "Namespace of the account owning the utxo."
},
"spentStatus": {
"$ref": "#/definitions/v1UtxoStatus",
"description": "Info about utxo's spent status."
},
"confirmedStatus": {
"$ref": "#/definitions/v1UtxoStatus",
"description": "Info about utxo's confirmation status."
},
"redeemScript": {
"type": "string",
"description": "Redeem script locking the utxo in case its owned by a multisig account."
}
}
},
"v1UtxoStatus": {
"type": "object",
"properties": {
"txid": {
"type": "string"
},
"blockInfo": {
"$ref": "#/definitions/v1BlockDetails"
},
"txhex": {
"type": "string"
}
}
}
}
}

View File

@@ -0,0 +1,44 @@
{
"swagger": "2.0",
"info": {
"title": "ocean/v1/types.proto",
"version": "version not set"
},
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"paths": {},
"definitions": {
"googlerpcStatus": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"format": "int32"
},
"message": {
"type": "string"
},
"details": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/protobufAny"
}
}
}
},
"protobufAny": {
"type": "object",
"properties": {
"@type": {
"type": "string"
}
},
"additionalProperties": {}
}
}
}

View File

@@ -0,0 +1,197 @@
{
"swagger": "2.0",
"info": {
"title": "ocean/v1/wallet.proto",
"version": "version not set"
},
"tags": [
{
"name": "WalletService"
}
],
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"paths": {},
"definitions": {
"GetInfoResponseNetwork": {
"type": "string",
"enum": [
"NETWORK_UNSPECIFIED",
"NETWORK_MAINNET",
"NETWORK_TESTNET",
"NETWORK_REGTEST"
],
"default": "NETWORK_UNSPECIFIED"
},
"googlerpcStatus": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"format": "int32"
},
"message": {
"type": "string"
},
"details": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/protobufAny"
}
}
}
},
"protobufAny": {
"type": "object",
"properties": {
"@type": {
"type": "string"
}
},
"additionalProperties": {}
},
"v1AccountInfo": {
"type": "object",
"properties": {
"namespace": {
"type": "string",
"description": "Account namespace."
},
"label": {
"type": "string",
"description": "Account label."
},
"derivationPath": {
"type": "string",
"description": "Derivation path."
},
"xpubs": {
"type": "array",
"items": {
"type": "string"
},
"description": "Xpubs."
},
"masterBlindingKey": {
"type": "string",
"description": "The master blinding key of the account to derive blinding keypairs from."
}
}
},
"v1AuthResponse": {
"type": "object",
"properties": {
"verified": {
"type": "boolean"
}
}
},
"v1BuildInfo": {
"type": "object",
"properties": {
"version": {
"type": "string",
"description": "Number of the version."
},
"commit": {
"type": "string",
"description": "Hash of the commit."
},
"date": {
"type": "string",
"description": "Date of the commit."
}
}
},
"v1ChangePasswordResponse": {
"type": "object"
},
"v1CreateWalletResponse": {
"type": "object"
},
"v1GenSeedResponse": {
"type": "object",
"properties": {
"mnemonic": {
"type": "string",
"description": "A mnemonic from where deriving signing and blinding key pairs."
}
}
},
"v1GetInfoResponse": {
"type": "object",
"properties": {
"network": {
"$ref": "#/definitions/GetInfoResponseNetwork",
"title": "The Liquid network of the wallet"
},
"nativeAsset": {
"type": "string",
"description": "The Liquid Bitcoin (LBTC) asset hash of the network."
},
"rootPath": {
"type": "string",
"description": "The root derivation path of the HD wallet."
},
"birthdayBlockHash": {
"type": "string",
"description": "The hash of the block at wallet creation time."
},
"birthdayBlockHeight": {
"type": "integer",
"format": "int64",
"description": "The height of the block at wallet creation time."
},
"accounts": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/v1AccountInfo"
},
"description": "List containing info about the wallet accounts."
},
"buildInfo": {
"$ref": "#/definitions/v1BuildInfo",
"description": "Info about the current version of the ocean wallet."
}
}
},
"v1LockResponse": {
"type": "object"
},
"v1RestoreWalletResponse": {
"type": "object",
"properties": {
"message": {
"type": "string",
"description": "String message returned within the process."
}
}
},
"v1StatusResponse": {
"type": "object",
"properties": {
"initialized": {
"type": "boolean",
"description": "Whether the wallet is initialized with seeds."
},
"synced": {
"type": "boolean",
"description": "Whether the wallet is in sync, meaning it's keeping track of every utxo\nof every account."
},
"unlocked": {
"type": "boolean",
"description": "Whether the wallet is unlocked."
}
}
},
"v1UnlockResponse": {
"type": "object"
}
}
}

View File

@@ -0,0 +1,179 @@
syntax = "proto3";
package ark.v1;
import "google/api/annotations.proto";
service ArkService {
rpc RegisterPayment(RegisterPaymentRequest) returns (RegisterPaymentResponse) {
option (google.api.http) = {
post: "/v1/payment/register"
body: "*"
};
};
rpc ClaimPayment(ClaimPaymentRequest) returns (ClaimPaymentResponse) {
option (google.api.http) = {
post: "/v1/payment/claim"
body: "*"
};
};
rpc FinalizePayment(FinalizePaymentRequest) returns (FinalizePaymentResponse) {
option (google.api.http) = {
post: "/v1/payment/finalize"
body: "*"
};
};
rpc GetRound(GetRoundRequest) returns (GetRoundResponse) {
option (google.api.http) = {
get: "/v1/round/{txid}"
};
};
rpc GetEventStream(GetEventStreamRequest) returns (stream GetEventStreamResponse) {
option (google.api.http) = {
get: "/v1/events"
};
};
rpc Ping(PingRequest) returns (PingResponse) {
option (google.api.http) = {
get: "/v1/ping/{payment_id}"
};
};
rpc Faucet(FaucetRequest) returns (FaucetResponse) {
option (google.api.http) = {
post: "/v1/faucet/{address}"
};
}
rpc ListVtxos(ListVtxosRequest) returns (ListVtxosResponse) {
option (google.api.http) = {
get: "/v1/vtxos/{address}"
};
}
rpc GetPubkey(GetPubkeyRequest) returns (GetPubkeyResponse) {
option (google.api.http) = {
get: "/v1/pubkey"
};
}
}
message RegisterPaymentRequest {
repeated Input inputs = 1;
}
message RegisterPaymentResponse {
// Mocks wabisabi's credentials.
string id = 1;
}
message ClaimPaymentRequest {
// Mocks wabisabi's credentials.
string id = 1;
// List of receivers for a registered payment.
repeated Output outputs = 2;
}
message ClaimPaymentResponse {}
message FinalizePaymentRequest {
// Forfeit txs signed by the user.
repeated string signed_forfeit_txs = 1;
}
message FinalizePaymentResponse {}
message GetRoundRequest {
string txid = 1;
}
message GetRoundResponse {
Round round = 1;
}
message Round {
string id = 1;
int64 start = 2;
int64 end = 3;
string txid = 4;
Tree congestion_tree = 5;
}
message Input {
string txid = 1;
uint32 vout = 2;
}
message Output {
// Either the offchain or onchain address.
string address = 1;
// Amount to send in satoshis.
uint64 amount = 2;
}
message RoundFinalizationEvent {
string id = 1;
string pool_partial_tx = 2;
repeated string forfeit_txs = 3;
Tree congestion_tree = 4;
}
message Tree {
repeated TreeLevel levels = 1;
}
message TreeLevel {
repeated Node nodes = 1;
}
message Node {
string txid = 1;
string tx = 2;
string parent_txid = 3;
}
message RoundFinalizedEvent {
string id = 1;
string pool_txid = 2;
}
message RoundFailed {
string id = 1;
string reason = 2;
}
message GetEventStreamRequest {}
message GetEventStreamResponse {
oneof event {
RoundFinalizationEvent round_finalization = 1;
RoundFinalizedEvent round_finalized = 2;
RoundFailed round_failed = 3;
}
}
message PingRequest {
string payment_id = 1;
}
message PingResponse {}
message FaucetRequest {
string address = 1;
}
message FaucetResponse {}
message ListVtxosRequest {
string address = 1;
}
message ListVtxosResponse {
repeated Vtxo vtxos = 1;
}
message Vtxo {
Input outpoint = 1;
Output receiver = 2;
bool spent = 3;
string pool_txid = 4;
}
message GetPubkeyRequest {}
message GetPubkeyResponse {
string pubkey = 1;
}

View File

@@ -0,0 +1,7 @@
# Generated by buf. DO NOT EDIT.
version: v1
deps:
- remote: buf.build
owner: googleapis
repository: googleapis
commit: b30c5775bfb3485d9da2e87b26590ac9

View File

@@ -0,0 +1,12 @@
version: v1
name: buf.build/ark-network/ark
deps:
- buf.build/googleapis/googleapis
breaking:
use:
- FILE
ignore_unstable_packages: true
lint:
use:
- DEFAULT

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,848 @@
// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.
// source: ark/v1/service.proto
/*
Package arkv1 is a reverse proxy.
It translates gRPC into RESTful JSON APIs.
*/
package arkv1
import (
"context"
"io"
"net/http"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/grpc-ecosystem/grpc-gateway/v2/utilities"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
)
// Suppress "imported and not used" errors
var _ codes.Code
var _ io.Reader
var _ status.Status
var _ = runtime.String
var _ = utilities.NewDoubleArray
var _ = metadata.Join
func request_ArkService_RegisterPayment_0(ctx context.Context, marshaler runtime.Marshaler, client ArkServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq RegisterPaymentRequest
var metadata runtime.ServerMetadata
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.RegisterPayment(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_ArkService_RegisterPayment_0(ctx context.Context, marshaler runtime.Marshaler, server ArkServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq RegisterPaymentRequest
var metadata runtime.ServerMetadata
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.RegisterPayment(ctx, &protoReq)
return msg, metadata, err
}
func request_ArkService_ClaimPayment_0(ctx context.Context, marshaler runtime.Marshaler, client ArkServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ClaimPaymentRequest
var metadata runtime.ServerMetadata
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.ClaimPayment(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_ArkService_ClaimPayment_0(ctx context.Context, marshaler runtime.Marshaler, server ArkServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ClaimPaymentRequest
var metadata runtime.ServerMetadata
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.ClaimPayment(ctx, &protoReq)
return msg, metadata, err
}
func request_ArkService_FinalizePayment_0(ctx context.Context, marshaler runtime.Marshaler, client ArkServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq FinalizePaymentRequest
var metadata runtime.ServerMetadata
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.FinalizePayment(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_ArkService_FinalizePayment_0(ctx context.Context, marshaler runtime.Marshaler, server ArkServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq FinalizePaymentRequest
var metadata runtime.ServerMetadata
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.FinalizePayment(ctx, &protoReq)
return msg, metadata, err
}
func request_ArkService_GetRound_0(ctx context.Context, marshaler runtime.Marshaler, client ArkServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetRoundRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["txid"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "txid")
}
protoReq.Txid, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "txid", err)
}
msg, err := client.GetRound(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_ArkService_GetRound_0(ctx context.Context, marshaler runtime.Marshaler, server ArkServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetRoundRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["txid"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "txid")
}
protoReq.Txid, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "txid", err)
}
msg, err := server.GetRound(ctx, &protoReq)
return msg, metadata, err
}
func request_ArkService_GetEventStream_0(ctx context.Context, marshaler runtime.Marshaler, client ArkServiceClient, req *http.Request, pathParams map[string]string) (ArkService_GetEventStreamClient, runtime.ServerMetadata, error) {
var protoReq GetEventStreamRequest
var metadata runtime.ServerMetadata
stream, err := client.GetEventStream(ctx, &protoReq)
if err != nil {
return nil, metadata, err
}
header, err := stream.Header()
if err != nil {
return nil, metadata, err
}
metadata.HeaderMD = header
return stream, metadata, nil
}
func request_ArkService_Ping_0(ctx context.Context, marshaler runtime.Marshaler, client ArkServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq PingRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["payment_id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "payment_id")
}
protoReq.PaymentId, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "payment_id", err)
}
msg, err := client.Ping(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_ArkService_Ping_0(ctx context.Context, marshaler runtime.Marshaler, server ArkServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq PingRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["payment_id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "payment_id")
}
protoReq.PaymentId, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "payment_id", err)
}
msg, err := server.Ping(ctx, &protoReq)
return msg, metadata, err
}
func request_ArkService_Faucet_0(ctx context.Context, marshaler runtime.Marshaler, client ArkServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq FaucetRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["address"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "address")
}
protoReq.Address, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "address", err)
}
msg, err := client.Faucet(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_ArkService_Faucet_0(ctx context.Context, marshaler runtime.Marshaler, server ArkServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq FaucetRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["address"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "address")
}
protoReq.Address, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "address", err)
}
msg, err := server.Faucet(ctx, &protoReq)
return msg, metadata, err
}
func request_ArkService_ListVtxos_0(ctx context.Context, marshaler runtime.Marshaler, client ArkServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListVtxosRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["address"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "address")
}
protoReq.Address, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "address", err)
}
msg, err := client.ListVtxos(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_ArkService_ListVtxos_0(ctx context.Context, marshaler runtime.Marshaler, server ArkServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListVtxosRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["address"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "address")
}
protoReq.Address, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "address", err)
}
msg, err := server.ListVtxos(ctx, &protoReq)
return msg, metadata, err
}
func request_ArkService_GetPubkey_0(ctx context.Context, marshaler runtime.Marshaler, client ArkServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetPubkeyRequest
var metadata runtime.ServerMetadata
msg, err := client.GetPubkey(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_ArkService_GetPubkey_0(ctx context.Context, marshaler runtime.Marshaler, server ArkServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetPubkeyRequest
var metadata runtime.ServerMetadata
msg, err := server.GetPubkey(ctx, &protoReq)
return msg, metadata, err
}
// RegisterArkServiceHandlerServer registers the http handlers for service ArkService to "mux".
// UnaryRPC :call ArkServiceServer directly.
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterArkServiceHandlerFromEndpoint instead.
func RegisterArkServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server ArkServiceServer) error {
mux.Handle("POST", pattern_ArkService_RegisterPayment_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/ark.v1.ArkService/RegisterPayment", runtime.WithHTTPPathPattern("/v1/payment/register"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_ArkService_RegisterPayment_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_ArkService_RegisterPayment_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_ArkService_ClaimPayment_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/ark.v1.ArkService/ClaimPayment", runtime.WithHTTPPathPattern("/v1/payment/claim"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_ArkService_ClaimPayment_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_ArkService_ClaimPayment_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_ArkService_FinalizePayment_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/ark.v1.ArkService/FinalizePayment", runtime.WithHTTPPathPattern("/v1/payment/finalize"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_ArkService_FinalizePayment_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_ArkService_FinalizePayment_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_ArkService_GetRound_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/ark.v1.ArkService/GetRound", runtime.WithHTTPPathPattern("/v1/round/{txid}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_ArkService_GetRound_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_ArkService_GetRound_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_ArkService_GetEventStream_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport")
_, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
})
mux.Handle("GET", pattern_ArkService_Ping_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/ark.v1.ArkService/Ping", runtime.WithHTTPPathPattern("/v1/ping/{payment_id}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_ArkService_Ping_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_ArkService_Ping_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_ArkService_Faucet_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/ark.v1.ArkService/Faucet", runtime.WithHTTPPathPattern("/v1/faucet/{address}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_ArkService_Faucet_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_ArkService_Faucet_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_ArkService_ListVtxos_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/ark.v1.ArkService/ListVtxos", runtime.WithHTTPPathPattern("/v1/vtxos/{address}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_ArkService_ListVtxos_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_ArkService_ListVtxos_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_ArkService_GetPubkey_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/ark.v1.ArkService/GetPubkey", runtime.WithHTTPPathPattern("/v1/pubkey"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_ArkService_GetPubkey_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_ArkService_GetPubkey_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
// RegisterArkServiceHandlerFromEndpoint is same as RegisterArkServiceHandler but
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
func RegisterArkServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
conn, err := grpc.DialContext(ctx, endpoint, opts...)
if err != nil {
return err
}
defer func() {
if err != nil {
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
return
}
go func() {
<-ctx.Done()
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
}()
}()
return RegisterArkServiceHandler(ctx, mux, conn)
}
// RegisterArkServiceHandler registers the http handlers for service ArkService to "mux".
// The handlers forward requests to the grpc endpoint over "conn".
func RegisterArkServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
return RegisterArkServiceHandlerClient(ctx, mux, NewArkServiceClient(conn))
}
// RegisterArkServiceHandlerClient registers the http handlers for service ArkService
// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "ArkServiceClient".
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "ArkServiceClient"
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
// "ArkServiceClient" to call the correct interceptors.
func RegisterArkServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client ArkServiceClient) error {
mux.Handle("POST", pattern_ArkService_RegisterPayment_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/ark.v1.ArkService/RegisterPayment", runtime.WithHTTPPathPattern("/v1/payment/register"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_ArkService_RegisterPayment_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_ArkService_RegisterPayment_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_ArkService_ClaimPayment_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/ark.v1.ArkService/ClaimPayment", runtime.WithHTTPPathPattern("/v1/payment/claim"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_ArkService_ClaimPayment_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_ArkService_ClaimPayment_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_ArkService_FinalizePayment_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/ark.v1.ArkService/FinalizePayment", runtime.WithHTTPPathPattern("/v1/payment/finalize"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_ArkService_FinalizePayment_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_ArkService_FinalizePayment_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_ArkService_GetRound_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/ark.v1.ArkService/GetRound", runtime.WithHTTPPathPattern("/v1/round/{txid}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_ArkService_GetRound_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_ArkService_GetRound_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_ArkService_GetEventStream_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/ark.v1.ArkService/GetEventStream", runtime.WithHTTPPathPattern("/v1/events"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_ArkService_GetEventStream_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_ArkService_GetEventStream_0(annotatedContext, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_ArkService_Ping_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/ark.v1.ArkService/Ping", runtime.WithHTTPPathPattern("/v1/ping/{payment_id}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_ArkService_Ping_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_ArkService_Ping_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_ArkService_Faucet_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/ark.v1.ArkService/Faucet", runtime.WithHTTPPathPattern("/v1/faucet/{address}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_ArkService_Faucet_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_ArkService_Faucet_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_ArkService_ListVtxos_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/ark.v1.ArkService/ListVtxos", runtime.WithHTTPPathPattern("/v1/vtxos/{address}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_ArkService_ListVtxos_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_ArkService_ListVtxos_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_ArkService_GetPubkey_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/ark.v1.ArkService/GetPubkey", runtime.WithHTTPPathPattern("/v1/pubkey"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_ArkService_GetPubkey_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_ArkService_GetPubkey_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
var (
pattern_ArkService_RegisterPayment_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "payment", "register"}, ""))
pattern_ArkService_ClaimPayment_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "payment", "claim"}, ""))
pattern_ArkService_FinalizePayment_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "payment", "finalize"}, ""))
pattern_ArkService_GetRound_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "round", "txid"}, ""))
pattern_ArkService_GetEventStream_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "events"}, ""))
pattern_ArkService_Ping_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "ping", "payment_id"}, ""))
pattern_ArkService_Faucet_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "faucet", "address"}, ""))
pattern_ArkService_ListVtxos_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "vtxos", "address"}, ""))
pattern_ArkService_GetPubkey_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "pubkey"}, ""))
)
var (
forward_ArkService_RegisterPayment_0 = runtime.ForwardResponseMessage
forward_ArkService_ClaimPayment_0 = runtime.ForwardResponseMessage
forward_ArkService_FinalizePayment_0 = runtime.ForwardResponseMessage
forward_ArkService_GetRound_0 = runtime.ForwardResponseMessage
forward_ArkService_GetEventStream_0 = runtime.ForwardResponseStream
forward_ArkService_Ping_0 = runtime.ForwardResponseMessage
forward_ArkService_Faucet_0 = runtime.ForwardResponseMessage
forward_ArkService_ListVtxos_0 = runtime.ForwardResponseMessage
forward_ArkService_GetPubkey_0 = runtime.ForwardResponseMessage
)

View File

@@ -0,0 +1,415 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
package arkv1
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// ArkServiceClient is the client API for ArkService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type ArkServiceClient interface {
RegisterPayment(ctx context.Context, in *RegisterPaymentRequest, opts ...grpc.CallOption) (*RegisterPaymentResponse, error)
ClaimPayment(ctx context.Context, in *ClaimPaymentRequest, opts ...grpc.CallOption) (*ClaimPaymentResponse, error)
FinalizePayment(ctx context.Context, in *FinalizePaymentRequest, opts ...grpc.CallOption) (*FinalizePaymentResponse, error)
GetRound(ctx context.Context, in *GetRoundRequest, opts ...grpc.CallOption) (*GetRoundResponse, error)
GetEventStream(ctx context.Context, in *GetEventStreamRequest, opts ...grpc.CallOption) (ArkService_GetEventStreamClient, error)
Ping(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*PingResponse, error)
Faucet(ctx context.Context, in *FaucetRequest, opts ...grpc.CallOption) (*FaucetResponse, error)
ListVtxos(ctx context.Context, in *ListVtxosRequest, opts ...grpc.CallOption) (*ListVtxosResponse, error)
GetPubkey(ctx context.Context, in *GetPubkeyRequest, opts ...grpc.CallOption) (*GetPubkeyResponse, error)
}
type arkServiceClient struct {
cc grpc.ClientConnInterface
}
func NewArkServiceClient(cc grpc.ClientConnInterface) ArkServiceClient {
return &arkServiceClient{cc}
}
func (c *arkServiceClient) RegisterPayment(ctx context.Context, in *RegisterPaymentRequest, opts ...grpc.CallOption) (*RegisterPaymentResponse, error) {
out := new(RegisterPaymentResponse)
err := c.cc.Invoke(ctx, "/ark.v1.ArkService/RegisterPayment", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *arkServiceClient) ClaimPayment(ctx context.Context, in *ClaimPaymentRequest, opts ...grpc.CallOption) (*ClaimPaymentResponse, error) {
out := new(ClaimPaymentResponse)
err := c.cc.Invoke(ctx, "/ark.v1.ArkService/ClaimPayment", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *arkServiceClient) FinalizePayment(ctx context.Context, in *FinalizePaymentRequest, opts ...grpc.CallOption) (*FinalizePaymentResponse, error) {
out := new(FinalizePaymentResponse)
err := c.cc.Invoke(ctx, "/ark.v1.ArkService/FinalizePayment", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *arkServiceClient) GetRound(ctx context.Context, in *GetRoundRequest, opts ...grpc.CallOption) (*GetRoundResponse, error) {
out := new(GetRoundResponse)
err := c.cc.Invoke(ctx, "/ark.v1.ArkService/GetRound", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *arkServiceClient) GetEventStream(ctx context.Context, in *GetEventStreamRequest, opts ...grpc.CallOption) (ArkService_GetEventStreamClient, error) {
stream, err := c.cc.NewStream(ctx, &ArkService_ServiceDesc.Streams[0], "/ark.v1.ArkService/GetEventStream", opts...)
if err != nil {
return nil, err
}
x := &arkServiceGetEventStreamClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type ArkService_GetEventStreamClient interface {
Recv() (*GetEventStreamResponse, error)
grpc.ClientStream
}
type arkServiceGetEventStreamClient struct {
grpc.ClientStream
}
func (x *arkServiceGetEventStreamClient) Recv() (*GetEventStreamResponse, error) {
m := new(GetEventStreamResponse)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *arkServiceClient) Ping(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*PingResponse, error) {
out := new(PingResponse)
err := c.cc.Invoke(ctx, "/ark.v1.ArkService/Ping", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *arkServiceClient) Faucet(ctx context.Context, in *FaucetRequest, opts ...grpc.CallOption) (*FaucetResponse, error) {
out := new(FaucetResponse)
err := c.cc.Invoke(ctx, "/ark.v1.ArkService/Faucet", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *arkServiceClient) ListVtxos(ctx context.Context, in *ListVtxosRequest, opts ...grpc.CallOption) (*ListVtxosResponse, error) {
out := new(ListVtxosResponse)
err := c.cc.Invoke(ctx, "/ark.v1.ArkService/ListVtxos", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *arkServiceClient) GetPubkey(ctx context.Context, in *GetPubkeyRequest, opts ...grpc.CallOption) (*GetPubkeyResponse, error) {
out := new(GetPubkeyResponse)
err := c.cc.Invoke(ctx, "/ark.v1.ArkService/GetPubkey", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// ArkServiceServer is the server API for ArkService service.
// All implementations should embed UnimplementedArkServiceServer
// for forward compatibility
type ArkServiceServer interface {
RegisterPayment(context.Context, *RegisterPaymentRequest) (*RegisterPaymentResponse, error)
ClaimPayment(context.Context, *ClaimPaymentRequest) (*ClaimPaymentResponse, error)
FinalizePayment(context.Context, *FinalizePaymentRequest) (*FinalizePaymentResponse, error)
GetRound(context.Context, *GetRoundRequest) (*GetRoundResponse, error)
GetEventStream(*GetEventStreamRequest, ArkService_GetEventStreamServer) error
Ping(context.Context, *PingRequest) (*PingResponse, error)
Faucet(context.Context, *FaucetRequest) (*FaucetResponse, error)
ListVtxos(context.Context, *ListVtxosRequest) (*ListVtxosResponse, error)
GetPubkey(context.Context, *GetPubkeyRequest) (*GetPubkeyResponse, error)
}
// UnimplementedArkServiceServer should be embedded to have forward compatible implementations.
type UnimplementedArkServiceServer struct {
}
func (UnimplementedArkServiceServer) RegisterPayment(context.Context, *RegisterPaymentRequest) (*RegisterPaymentResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RegisterPayment not implemented")
}
func (UnimplementedArkServiceServer) ClaimPayment(context.Context, *ClaimPaymentRequest) (*ClaimPaymentResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ClaimPayment not implemented")
}
func (UnimplementedArkServiceServer) FinalizePayment(context.Context, *FinalizePaymentRequest) (*FinalizePaymentResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method FinalizePayment not implemented")
}
func (UnimplementedArkServiceServer) GetRound(context.Context, *GetRoundRequest) (*GetRoundResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetRound not implemented")
}
func (UnimplementedArkServiceServer) GetEventStream(*GetEventStreamRequest, ArkService_GetEventStreamServer) error {
return status.Errorf(codes.Unimplemented, "method GetEventStream not implemented")
}
func (UnimplementedArkServiceServer) Ping(context.Context, *PingRequest) (*PingResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Ping not implemented")
}
func (UnimplementedArkServiceServer) Faucet(context.Context, *FaucetRequest) (*FaucetResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Faucet not implemented")
}
func (UnimplementedArkServiceServer) ListVtxos(context.Context, *ListVtxosRequest) (*ListVtxosResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListVtxos not implemented")
}
func (UnimplementedArkServiceServer) GetPubkey(context.Context, *GetPubkeyRequest) (*GetPubkeyResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetPubkey not implemented")
}
// UnsafeArkServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to ArkServiceServer will
// result in compilation errors.
type UnsafeArkServiceServer interface {
mustEmbedUnimplementedArkServiceServer()
}
func RegisterArkServiceServer(s grpc.ServiceRegistrar, srv ArkServiceServer) {
s.RegisterService(&ArkService_ServiceDesc, srv)
}
func _ArkService_RegisterPayment_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(RegisterPaymentRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ArkServiceServer).RegisterPayment(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ark.v1.ArkService/RegisterPayment",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ArkServiceServer).RegisterPayment(ctx, req.(*RegisterPaymentRequest))
}
return interceptor(ctx, in, info, handler)
}
func _ArkService_ClaimPayment_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ClaimPaymentRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ArkServiceServer).ClaimPayment(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ark.v1.ArkService/ClaimPayment",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ArkServiceServer).ClaimPayment(ctx, req.(*ClaimPaymentRequest))
}
return interceptor(ctx, in, info, handler)
}
func _ArkService_FinalizePayment_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(FinalizePaymentRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ArkServiceServer).FinalizePayment(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ark.v1.ArkService/FinalizePayment",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ArkServiceServer).FinalizePayment(ctx, req.(*FinalizePaymentRequest))
}
return interceptor(ctx, in, info, handler)
}
func _ArkService_GetRound_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetRoundRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ArkServiceServer).GetRound(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ark.v1.ArkService/GetRound",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ArkServiceServer).GetRound(ctx, req.(*GetRoundRequest))
}
return interceptor(ctx, in, info, handler)
}
func _ArkService_GetEventStream_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(GetEventStreamRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(ArkServiceServer).GetEventStream(m, &arkServiceGetEventStreamServer{stream})
}
type ArkService_GetEventStreamServer interface {
Send(*GetEventStreamResponse) error
grpc.ServerStream
}
type arkServiceGetEventStreamServer struct {
grpc.ServerStream
}
func (x *arkServiceGetEventStreamServer) Send(m *GetEventStreamResponse) error {
return x.ServerStream.SendMsg(m)
}
func _ArkService_Ping_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(PingRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ArkServiceServer).Ping(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ark.v1.ArkService/Ping",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ArkServiceServer).Ping(ctx, req.(*PingRequest))
}
return interceptor(ctx, in, info, handler)
}
func _ArkService_Faucet_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(FaucetRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ArkServiceServer).Faucet(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ark.v1.ArkService/Faucet",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ArkServiceServer).Faucet(ctx, req.(*FaucetRequest))
}
return interceptor(ctx, in, info, handler)
}
func _ArkService_ListVtxos_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListVtxosRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ArkServiceServer).ListVtxos(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ark.v1.ArkService/ListVtxos",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ArkServiceServer).ListVtxos(ctx, req.(*ListVtxosRequest))
}
return interceptor(ctx, in, info, handler)
}
func _ArkService_GetPubkey_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetPubkeyRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ArkServiceServer).GetPubkey(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ark.v1.ArkService/GetPubkey",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ArkServiceServer).GetPubkey(ctx, req.(*GetPubkeyRequest))
}
return interceptor(ctx, in, info, handler)
}
// ArkService_ServiceDesc is the grpc.ServiceDesc for ArkService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var ArkService_ServiceDesc = grpc.ServiceDesc{
ServiceName: "ark.v1.ArkService",
HandlerType: (*ArkServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "RegisterPayment",
Handler: _ArkService_RegisterPayment_Handler,
},
{
MethodName: "ClaimPayment",
Handler: _ArkService_ClaimPayment_Handler,
},
{
MethodName: "FinalizePayment",
Handler: _ArkService_FinalizePayment_Handler,
},
{
MethodName: "GetRound",
Handler: _ArkService_GetRound_Handler,
},
{
MethodName: "Ping",
Handler: _ArkService_Ping_Handler,
},
{
MethodName: "Faucet",
Handler: _ArkService_Faucet_Handler,
},
{
MethodName: "ListVtxos",
Handler: _ArkService_ListVtxos_Handler,
},
{
MethodName: "GetPubkey",
Handler: _ArkService_GetPubkey_Handler,
},
},
Streams: []grpc.StreamDesc{
{
StreamName: "GetEventStream",
Handler: _ArkService_GetEventStream_Handler,
ServerStreams: true,
},
},
Metadata: "ark/v1/service.proto",
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,487 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
package oceanv1
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// AccountServiceClient is the client API for AccountService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type AccountServiceClient interface {
// CreateAccountBIP44 creates a new BIP44 account.
CreateAccountBIP44(ctx context.Context, in *CreateAccountBIP44Request, opts ...grpc.CallOption) (*CreateAccountBIP44Response, error)
// CreateAccountMultiSig creates a new multisig account.
CreateAccountMultiSig(ctx context.Context, in *CreateAccountMultiSigRequest, opts ...grpc.CallOption) (*CreateAccountMultiSigResponse, error)
// CreateAccountCustom creates a new custom account for which loading a template.
CreateAccountCustom(ctx context.Context, in *CreateAccountCustomRequest, opts ...grpc.CallOption) (*CreateAccountCustomResponse, error)
// SetAccountLabel sets a label for the account that can be used later to refer to it.
SetAccountLabel(ctx context.Context, in *SetAccountLabelRequest, opts ...grpc.CallOption) (*SetAccountLabelResponse, error)
// SetAccountTemplate sets the template for the account used to generate new addresses.
SetAccountTemplate(ctx context.Context, in *SetAccountTemplateRequest, opts ...grpc.CallOption) (*SetAccountTemplateResponse, error)
// DeriveAddresses generates new address(es) for the account.
DeriveAddresses(ctx context.Context, in *DeriveAddressesRequest, opts ...grpc.CallOption) (*DeriveAddressesResponse, error)
// DeriveChangeAddresses generates new change address(es) for the account.
DeriveChangeAddresses(ctx context.Context, in *DeriveChangeAddressesRequest, opts ...grpc.CallOption) (*DeriveChangeAddressesResponse, error)
// ListAddresses returns all derived addresses for the account.
ListAddresses(ctx context.Context, in *ListAddressesRequest, opts ...grpc.CallOption) (*ListAddressesResponse, error)
// Balance returns the balance for the account, or for specific list of
// account's addresses.
Balance(ctx context.Context, in *BalanceRequest, opts ...grpc.CallOption) (*BalanceResponse, error)
// ListUtxos returns the utxos for the account, or specific list of
// account's addresses.
ListUtxos(ctx context.Context, in *ListUtxosRequest, opts ...grpc.CallOption) (*ListUtxosResponse, error)
// DeleteAccount deletes an existing account. The operation is allowed only
// if the account has zero balance.
DeleteAccount(ctx context.Context, in *DeleteAccountRequest, opts ...grpc.CallOption) (*DeleteAccountResponse, error)
}
type accountServiceClient struct {
cc grpc.ClientConnInterface
}
func NewAccountServiceClient(cc grpc.ClientConnInterface) AccountServiceClient {
return &accountServiceClient{cc}
}
func (c *accountServiceClient) CreateAccountBIP44(ctx context.Context, in *CreateAccountBIP44Request, opts ...grpc.CallOption) (*CreateAccountBIP44Response, error) {
out := new(CreateAccountBIP44Response)
err := c.cc.Invoke(ctx, "/ocean.v1.AccountService/CreateAccountBIP44", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *accountServiceClient) CreateAccountMultiSig(ctx context.Context, in *CreateAccountMultiSigRequest, opts ...grpc.CallOption) (*CreateAccountMultiSigResponse, error) {
out := new(CreateAccountMultiSigResponse)
err := c.cc.Invoke(ctx, "/ocean.v1.AccountService/CreateAccountMultiSig", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *accountServiceClient) CreateAccountCustom(ctx context.Context, in *CreateAccountCustomRequest, opts ...grpc.CallOption) (*CreateAccountCustomResponse, error) {
out := new(CreateAccountCustomResponse)
err := c.cc.Invoke(ctx, "/ocean.v1.AccountService/CreateAccountCustom", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *accountServiceClient) SetAccountLabel(ctx context.Context, in *SetAccountLabelRequest, opts ...grpc.CallOption) (*SetAccountLabelResponse, error) {
out := new(SetAccountLabelResponse)
err := c.cc.Invoke(ctx, "/ocean.v1.AccountService/SetAccountLabel", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *accountServiceClient) SetAccountTemplate(ctx context.Context, in *SetAccountTemplateRequest, opts ...grpc.CallOption) (*SetAccountTemplateResponse, error) {
out := new(SetAccountTemplateResponse)
err := c.cc.Invoke(ctx, "/ocean.v1.AccountService/SetAccountTemplate", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *accountServiceClient) DeriveAddresses(ctx context.Context, in *DeriveAddressesRequest, opts ...grpc.CallOption) (*DeriveAddressesResponse, error) {
out := new(DeriveAddressesResponse)
err := c.cc.Invoke(ctx, "/ocean.v1.AccountService/DeriveAddresses", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *accountServiceClient) DeriveChangeAddresses(ctx context.Context, in *DeriveChangeAddressesRequest, opts ...grpc.CallOption) (*DeriveChangeAddressesResponse, error) {
out := new(DeriveChangeAddressesResponse)
err := c.cc.Invoke(ctx, "/ocean.v1.AccountService/DeriveChangeAddresses", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *accountServiceClient) ListAddresses(ctx context.Context, in *ListAddressesRequest, opts ...grpc.CallOption) (*ListAddressesResponse, error) {
out := new(ListAddressesResponse)
err := c.cc.Invoke(ctx, "/ocean.v1.AccountService/ListAddresses", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *accountServiceClient) Balance(ctx context.Context, in *BalanceRequest, opts ...grpc.CallOption) (*BalanceResponse, error) {
out := new(BalanceResponse)
err := c.cc.Invoke(ctx, "/ocean.v1.AccountService/Balance", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *accountServiceClient) ListUtxos(ctx context.Context, in *ListUtxosRequest, opts ...grpc.CallOption) (*ListUtxosResponse, error) {
out := new(ListUtxosResponse)
err := c.cc.Invoke(ctx, "/ocean.v1.AccountService/ListUtxos", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *accountServiceClient) DeleteAccount(ctx context.Context, in *DeleteAccountRequest, opts ...grpc.CallOption) (*DeleteAccountResponse, error) {
out := new(DeleteAccountResponse)
err := c.cc.Invoke(ctx, "/ocean.v1.AccountService/DeleteAccount", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// AccountServiceServer is the server API for AccountService service.
// All implementations should embed UnimplementedAccountServiceServer
// for forward compatibility
type AccountServiceServer interface {
// CreateAccountBIP44 creates a new BIP44 account.
CreateAccountBIP44(context.Context, *CreateAccountBIP44Request) (*CreateAccountBIP44Response, error)
// CreateAccountMultiSig creates a new multisig account.
CreateAccountMultiSig(context.Context, *CreateAccountMultiSigRequest) (*CreateAccountMultiSigResponse, error)
// CreateAccountCustom creates a new custom account for which loading a template.
CreateAccountCustom(context.Context, *CreateAccountCustomRequest) (*CreateAccountCustomResponse, error)
// SetAccountLabel sets a label for the account that can be used later to refer to it.
SetAccountLabel(context.Context, *SetAccountLabelRequest) (*SetAccountLabelResponse, error)
// SetAccountTemplate sets the template for the account used to generate new addresses.
SetAccountTemplate(context.Context, *SetAccountTemplateRequest) (*SetAccountTemplateResponse, error)
// DeriveAddresses generates new address(es) for the account.
DeriveAddresses(context.Context, *DeriveAddressesRequest) (*DeriveAddressesResponse, error)
// DeriveChangeAddresses generates new change address(es) for the account.
DeriveChangeAddresses(context.Context, *DeriveChangeAddressesRequest) (*DeriveChangeAddressesResponse, error)
// ListAddresses returns all derived addresses for the account.
ListAddresses(context.Context, *ListAddressesRequest) (*ListAddressesResponse, error)
// Balance returns the balance for the account, or for specific list of
// account's addresses.
Balance(context.Context, *BalanceRequest) (*BalanceResponse, error)
// ListUtxos returns the utxos for the account, or specific list of
// account's addresses.
ListUtxos(context.Context, *ListUtxosRequest) (*ListUtxosResponse, error)
// DeleteAccount deletes an existing account. The operation is allowed only
// if the account has zero balance.
DeleteAccount(context.Context, *DeleteAccountRequest) (*DeleteAccountResponse, error)
}
// UnimplementedAccountServiceServer should be embedded to have forward compatible implementations.
type UnimplementedAccountServiceServer struct {
}
func (UnimplementedAccountServiceServer) CreateAccountBIP44(context.Context, *CreateAccountBIP44Request) (*CreateAccountBIP44Response, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateAccountBIP44 not implemented")
}
func (UnimplementedAccountServiceServer) CreateAccountMultiSig(context.Context, *CreateAccountMultiSigRequest) (*CreateAccountMultiSigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateAccountMultiSig not implemented")
}
func (UnimplementedAccountServiceServer) CreateAccountCustom(context.Context, *CreateAccountCustomRequest) (*CreateAccountCustomResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateAccountCustom not implemented")
}
func (UnimplementedAccountServiceServer) SetAccountLabel(context.Context, *SetAccountLabelRequest) (*SetAccountLabelResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetAccountLabel not implemented")
}
func (UnimplementedAccountServiceServer) SetAccountTemplate(context.Context, *SetAccountTemplateRequest) (*SetAccountTemplateResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetAccountTemplate not implemented")
}
func (UnimplementedAccountServiceServer) DeriveAddresses(context.Context, *DeriveAddressesRequest) (*DeriveAddressesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeriveAddresses not implemented")
}
func (UnimplementedAccountServiceServer) DeriveChangeAddresses(context.Context, *DeriveChangeAddressesRequest) (*DeriveChangeAddressesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeriveChangeAddresses not implemented")
}
func (UnimplementedAccountServiceServer) ListAddresses(context.Context, *ListAddressesRequest) (*ListAddressesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListAddresses not implemented")
}
func (UnimplementedAccountServiceServer) Balance(context.Context, *BalanceRequest) (*BalanceResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Balance not implemented")
}
func (UnimplementedAccountServiceServer) ListUtxos(context.Context, *ListUtxosRequest) (*ListUtxosResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListUtxos not implemented")
}
func (UnimplementedAccountServiceServer) DeleteAccount(context.Context, *DeleteAccountRequest) (*DeleteAccountResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeleteAccount not implemented")
}
// UnsafeAccountServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to AccountServiceServer will
// result in compilation errors.
type UnsafeAccountServiceServer interface {
mustEmbedUnimplementedAccountServiceServer()
}
func RegisterAccountServiceServer(s grpc.ServiceRegistrar, srv AccountServiceServer) {
s.RegisterService(&AccountService_ServiceDesc, srv)
}
func _AccountService_CreateAccountBIP44_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CreateAccountBIP44Request)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AccountServiceServer).CreateAccountBIP44(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ocean.v1.AccountService/CreateAccountBIP44",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AccountServiceServer).CreateAccountBIP44(ctx, req.(*CreateAccountBIP44Request))
}
return interceptor(ctx, in, info, handler)
}
func _AccountService_CreateAccountMultiSig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CreateAccountMultiSigRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AccountServiceServer).CreateAccountMultiSig(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ocean.v1.AccountService/CreateAccountMultiSig",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AccountServiceServer).CreateAccountMultiSig(ctx, req.(*CreateAccountMultiSigRequest))
}
return interceptor(ctx, in, info, handler)
}
func _AccountService_CreateAccountCustom_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CreateAccountCustomRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AccountServiceServer).CreateAccountCustom(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ocean.v1.AccountService/CreateAccountCustom",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AccountServiceServer).CreateAccountCustom(ctx, req.(*CreateAccountCustomRequest))
}
return interceptor(ctx, in, info, handler)
}
func _AccountService_SetAccountLabel_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SetAccountLabelRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AccountServiceServer).SetAccountLabel(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ocean.v1.AccountService/SetAccountLabel",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AccountServiceServer).SetAccountLabel(ctx, req.(*SetAccountLabelRequest))
}
return interceptor(ctx, in, info, handler)
}
func _AccountService_SetAccountTemplate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SetAccountTemplateRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AccountServiceServer).SetAccountTemplate(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ocean.v1.AccountService/SetAccountTemplate",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AccountServiceServer).SetAccountTemplate(ctx, req.(*SetAccountTemplateRequest))
}
return interceptor(ctx, in, info, handler)
}
func _AccountService_DeriveAddresses_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(DeriveAddressesRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AccountServiceServer).DeriveAddresses(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ocean.v1.AccountService/DeriveAddresses",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AccountServiceServer).DeriveAddresses(ctx, req.(*DeriveAddressesRequest))
}
return interceptor(ctx, in, info, handler)
}
func _AccountService_DeriveChangeAddresses_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(DeriveChangeAddressesRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AccountServiceServer).DeriveChangeAddresses(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ocean.v1.AccountService/DeriveChangeAddresses",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AccountServiceServer).DeriveChangeAddresses(ctx, req.(*DeriveChangeAddressesRequest))
}
return interceptor(ctx, in, info, handler)
}
func _AccountService_ListAddresses_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListAddressesRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AccountServiceServer).ListAddresses(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ocean.v1.AccountService/ListAddresses",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AccountServiceServer).ListAddresses(ctx, req.(*ListAddressesRequest))
}
return interceptor(ctx, in, info, handler)
}
func _AccountService_Balance_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(BalanceRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AccountServiceServer).Balance(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ocean.v1.AccountService/Balance",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AccountServiceServer).Balance(ctx, req.(*BalanceRequest))
}
return interceptor(ctx, in, info, handler)
}
func _AccountService_ListUtxos_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListUtxosRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AccountServiceServer).ListUtxos(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ocean.v1.AccountService/ListUtxos",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AccountServiceServer).ListUtxos(ctx, req.(*ListUtxosRequest))
}
return interceptor(ctx, in, info, handler)
}
func _AccountService_DeleteAccount_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(DeleteAccountRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AccountServiceServer).DeleteAccount(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ocean.v1.AccountService/DeleteAccount",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AccountServiceServer).DeleteAccount(ctx, req.(*DeleteAccountRequest))
}
return interceptor(ctx, in, info, handler)
}
// AccountService_ServiceDesc is the grpc.ServiceDesc for AccountService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var AccountService_ServiceDesc = grpc.ServiceDesc{
ServiceName: "ocean.v1.AccountService",
HandlerType: (*AccountServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "CreateAccountBIP44",
Handler: _AccountService_CreateAccountBIP44_Handler,
},
{
MethodName: "CreateAccountMultiSig",
Handler: _AccountService_CreateAccountMultiSig_Handler,
},
{
MethodName: "CreateAccountCustom",
Handler: _AccountService_CreateAccountCustom_Handler,
},
{
MethodName: "SetAccountLabel",
Handler: _AccountService_SetAccountLabel_Handler,
},
{
MethodName: "SetAccountTemplate",
Handler: _AccountService_SetAccountTemplate_Handler,
},
{
MethodName: "DeriveAddresses",
Handler: _AccountService_DeriveAddresses_Handler,
},
{
MethodName: "DeriveChangeAddresses",
Handler: _AccountService_DeriveChangeAddresses_Handler,
},
{
MethodName: "ListAddresses",
Handler: _AccountService_ListAddresses_Handler,
},
{
MethodName: "Balance",
Handler: _AccountService_Balance_Handler,
},
{
MethodName: "ListUtxos",
Handler: _AccountService_ListUtxos_Handler,
},
{
MethodName: "DeleteAccount",
Handler: _AccountService_DeleteAccount_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "ocean/v1/account.proto",
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,392 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
package oceanv1
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// NotificationServiceClient is the client API for NotificationService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type NotificationServiceClient interface {
// WatchExternalScript allows to get notified about utxos/txs related to the given
// external script, ie. not derived from a wallet account.
// The service answers with the label assigned to the given script.
// The label is used as identifier of the utxos/txs received from the streams.
WatchExternalScript(ctx context.Context, in *WatchExternalScriptRequest, opts ...grpc.CallOption) (*WatchExternalScriptResponse, error)
// UnwatchExternalScript allows to stop watching for the script identified with
// the given label.
UnwatchExternalScript(ctx context.Context, in *UnwatchExternalScriptRequest, opts ...grpc.CallOption) (*UnwatchExternalScriptResponse, error)
// Notifies about events related to wallet transactions.
TransactionNotifications(ctx context.Context, in *TransactionNotificationsRequest, opts ...grpc.CallOption) (NotificationService_TransactionNotificationsClient, error)
// Notifies about events realted to wallet utxos.
UtxosNotifications(ctx context.Context, in *UtxosNotificationsRequest, opts ...grpc.CallOption) (NotificationService_UtxosNotificationsClient, error)
// Adds a webhook registered for some kind of event.
AddWebhook(ctx context.Context, in *AddWebhookRequest, opts ...grpc.CallOption) (*AddWebhookResponse, error)
// Removes some previously added webhook.
RemoveWebhook(ctx context.Context, in *RemoveWebhookRequest, opts ...grpc.CallOption) (*RemoveWebhookResponse, error)
// Returns registered webhooks.
ListWebhooks(ctx context.Context, in *ListWebhooksRequest, opts ...grpc.CallOption) (*ListWebhooksResponse, error)
}
type notificationServiceClient struct {
cc grpc.ClientConnInterface
}
func NewNotificationServiceClient(cc grpc.ClientConnInterface) NotificationServiceClient {
return &notificationServiceClient{cc}
}
func (c *notificationServiceClient) WatchExternalScript(ctx context.Context, in *WatchExternalScriptRequest, opts ...grpc.CallOption) (*WatchExternalScriptResponse, error) {
out := new(WatchExternalScriptResponse)
err := c.cc.Invoke(ctx, "/ocean.v1.NotificationService/WatchExternalScript", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *notificationServiceClient) UnwatchExternalScript(ctx context.Context, in *UnwatchExternalScriptRequest, opts ...grpc.CallOption) (*UnwatchExternalScriptResponse, error) {
out := new(UnwatchExternalScriptResponse)
err := c.cc.Invoke(ctx, "/ocean.v1.NotificationService/UnwatchExternalScript", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *notificationServiceClient) TransactionNotifications(ctx context.Context, in *TransactionNotificationsRequest, opts ...grpc.CallOption) (NotificationService_TransactionNotificationsClient, error) {
stream, err := c.cc.NewStream(ctx, &NotificationService_ServiceDesc.Streams[0], "/ocean.v1.NotificationService/TransactionNotifications", opts...)
if err != nil {
return nil, err
}
x := &notificationServiceTransactionNotificationsClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type NotificationService_TransactionNotificationsClient interface {
Recv() (*TransactionNotificationsResponse, error)
grpc.ClientStream
}
type notificationServiceTransactionNotificationsClient struct {
grpc.ClientStream
}
func (x *notificationServiceTransactionNotificationsClient) Recv() (*TransactionNotificationsResponse, error) {
m := new(TransactionNotificationsResponse)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *notificationServiceClient) UtxosNotifications(ctx context.Context, in *UtxosNotificationsRequest, opts ...grpc.CallOption) (NotificationService_UtxosNotificationsClient, error) {
stream, err := c.cc.NewStream(ctx, &NotificationService_ServiceDesc.Streams[1], "/ocean.v1.NotificationService/UtxosNotifications", opts...)
if err != nil {
return nil, err
}
x := &notificationServiceUtxosNotificationsClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type NotificationService_UtxosNotificationsClient interface {
Recv() (*UtxosNotificationsResponse, error)
grpc.ClientStream
}
type notificationServiceUtxosNotificationsClient struct {
grpc.ClientStream
}
func (x *notificationServiceUtxosNotificationsClient) Recv() (*UtxosNotificationsResponse, error) {
m := new(UtxosNotificationsResponse)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *notificationServiceClient) AddWebhook(ctx context.Context, in *AddWebhookRequest, opts ...grpc.CallOption) (*AddWebhookResponse, error) {
out := new(AddWebhookResponse)
err := c.cc.Invoke(ctx, "/ocean.v1.NotificationService/AddWebhook", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *notificationServiceClient) RemoveWebhook(ctx context.Context, in *RemoveWebhookRequest, opts ...grpc.CallOption) (*RemoveWebhookResponse, error) {
out := new(RemoveWebhookResponse)
err := c.cc.Invoke(ctx, "/ocean.v1.NotificationService/RemoveWebhook", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *notificationServiceClient) ListWebhooks(ctx context.Context, in *ListWebhooksRequest, opts ...grpc.CallOption) (*ListWebhooksResponse, error) {
out := new(ListWebhooksResponse)
err := c.cc.Invoke(ctx, "/ocean.v1.NotificationService/ListWebhooks", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// NotificationServiceServer is the server API for NotificationService service.
// All implementations should embed UnimplementedNotificationServiceServer
// for forward compatibility
type NotificationServiceServer interface {
// WatchExternalScript allows to get notified about utxos/txs related to the given
// external script, ie. not derived from a wallet account.
// The service answers with the label assigned to the given script.
// The label is used as identifier of the utxos/txs received from the streams.
WatchExternalScript(context.Context, *WatchExternalScriptRequest) (*WatchExternalScriptResponse, error)
// UnwatchExternalScript allows to stop watching for the script identified with
// the given label.
UnwatchExternalScript(context.Context, *UnwatchExternalScriptRequest) (*UnwatchExternalScriptResponse, error)
// Notifies about events related to wallet transactions.
TransactionNotifications(*TransactionNotificationsRequest, NotificationService_TransactionNotificationsServer) error
// Notifies about events realted to wallet utxos.
UtxosNotifications(*UtxosNotificationsRequest, NotificationService_UtxosNotificationsServer) error
// Adds a webhook registered for some kind of event.
AddWebhook(context.Context, *AddWebhookRequest) (*AddWebhookResponse, error)
// Removes some previously added webhook.
RemoveWebhook(context.Context, *RemoveWebhookRequest) (*RemoveWebhookResponse, error)
// Returns registered webhooks.
ListWebhooks(context.Context, *ListWebhooksRequest) (*ListWebhooksResponse, error)
}
// UnimplementedNotificationServiceServer should be embedded to have forward compatible implementations.
type UnimplementedNotificationServiceServer struct {
}
func (UnimplementedNotificationServiceServer) WatchExternalScript(context.Context, *WatchExternalScriptRequest) (*WatchExternalScriptResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method WatchExternalScript not implemented")
}
func (UnimplementedNotificationServiceServer) UnwatchExternalScript(context.Context, *UnwatchExternalScriptRequest) (*UnwatchExternalScriptResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UnwatchExternalScript not implemented")
}
func (UnimplementedNotificationServiceServer) TransactionNotifications(*TransactionNotificationsRequest, NotificationService_TransactionNotificationsServer) error {
return status.Errorf(codes.Unimplemented, "method TransactionNotifications not implemented")
}
func (UnimplementedNotificationServiceServer) UtxosNotifications(*UtxosNotificationsRequest, NotificationService_UtxosNotificationsServer) error {
return status.Errorf(codes.Unimplemented, "method UtxosNotifications not implemented")
}
func (UnimplementedNotificationServiceServer) AddWebhook(context.Context, *AddWebhookRequest) (*AddWebhookResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AddWebhook not implemented")
}
func (UnimplementedNotificationServiceServer) RemoveWebhook(context.Context, *RemoveWebhookRequest) (*RemoveWebhookResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RemoveWebhook not implemented")
}
func (UnimplementedNotificationServiceServer) ListWebhooks(context.Context, *ListWebhooksRequest) (*ListWebhooksResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListWebhooks not implemented")
}
// UnsafeNotificationServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to NotificationServiceServer will
// result in compilation errors.
type UnsafeNotificationServiceServer interface {
mustEmbedUnimplementedNotificationServiceServer()
}
func RegisterNotificationServiceServer(s grpc.ServiceRegistrar, srv NotificationServiceServer) {
s.RegisterService(&NotificationService_ServiceDesc, srv)
}
func _NotificationService_WatchExternalScript_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(WatchExternalScriptRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(NotificationServiceServer).WatchExternalScript(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ocean.v1.NotificationService/WatchExternalScript",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(NotificationServiceServer).WatchExternalScript(ctx, req.(*WatchExternalScriptRequest))
}
return interceptor(ctx, in, info, handler)
}
func _NotificationService_UnwatchExternalScript_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(UnwatchExternalScriptRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(NotificationServiceServer).UnwatchExternalScript(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ocean.v1.NotificationService/UnwatchExternalScript",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(NotificationServiceServer).UnwatchExternalScript(ctx, req.(*UnwatchExternalScriptRequest))
}
return interceptor(ctx, in, info, handler)
}
func _NotificationService_TransactionNotifications_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(TransactionNotificationsRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(NotificationServiceServer).TransactionNotifications(m, &notificationServiceTransactionNotificationsServer{stream})
}
type NotificationService_TransactionNotificationsServer interface {
Send(*TransactionNotificationsResponse) error
grpc.ServerStream
}
type notificationServiceTransactionNotificationsServer struct {
grpc.ServerStream
}
func (x *notificationServiceTransactionNotificationsServer) Send(m *TransactionNotificationsResponse) error {
return x.ServerStream.SendMsg(m)
}
func _NotificationService_UtxosNotifications_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(UtxosNotificationsRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(NotificationServiceServer).UtxosNotifications(m, &notificationServiceUtxosNotificationsServer{stream})
}
type NotificationService_UtxosNotificationsServer interface {
Send(*UtxosNotificationsResponse) error
grpc.ServerStream
}
type notificationServiceUtxosNotificationsServer struct {
grpc.ServerStream
}
func (x *notificationServiceUtxosNotificationsServer) Send(m *UtxosNotificationsResponse) error {
return x.ServerStream.SendMsg(m)
}
func _NotificationService_AddWebhook_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(AddWebhookRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(NotificationServiceServer).AddWebhook(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ocean.v1.NotificationService/AddWebhook",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(NotificationServiceServer).AddWebhook(ctx, req.(*AddWebhookRequest))
}
return interceptor(ctx, in, info, handler)
}
func _NotificationService_RemoveWebhook_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(RemoveWebhookRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(NotificationServiceServer).RemoveWebhook(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ocean.v1.NotificationService/RemoveWebhook",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(NotificationServiceServer).RemoveWebhook(ctx, req.(*RemoveWebhookRequest))
}
return interceptor(ctx, in, info, handler)
}
func _NotificationService_ListWebhooks_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListWebhooksRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(NotificationServiceServer).ListWebhooks(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ocean.v1.NotificationService/ListWebhooks",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(NotificationServiceServer).ListWebhooks(ctx, req.(*ListWebhooksRequest))
}
return interceptor(ctx, in, info, handler)
}
// NotificationService_ServiceDesc is the grpc.ServiceDesc for NotificationService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var NotificationService_ServiceDesc = grpc.ServiceDesc{
ServiceName: "ocean.v1.NotificationService",
HandlerType: (*NotificationServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "WatchExternalScript",
Handler: _NotificationService_WatchExternalScript_Handler,
},
{
MethodName: "UnwatchExternalScript",
Handler: _NotificationService_UnwatchExternalScript_Handler,
},
{
MethodName: "AddWebhook",
Handler: _NotificationService_AddWebhook_Handler,
},
{
MethodName: "RemoveWebhook",
Handler: _NotificationService_RemoveWebhook_Handler,
},
{
MethodName: "ListWebhooks",
Handler: _NotificationService_ListWebhooks_Handler,
},
},
Streams: []grpc.StreamDesc{
{
StreamName: "TransactionNotifications",
Handler: _NotificationService_TransactionNotifications_Handler,
ServerStreams: true,
},
{
StreamName: "UtxosNotifications",
Handler: _NotificationService_UtxosNotifications_Handler,
ServerStreams: true,
},
},
Metadata: "ocean/v1/notification.proto",
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,689 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
package oceanv1
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// TransactionServiceClient is the client API for TransactionService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type TransactionServiceClient interface {
// GetTransaction returns the hex of a transaction given its id.
GetTransaction(ctx context.Context, in *GetTransactionRequest, opts ...grpc.CallOption) (*GetTransactionResponse, error)
// SelectUtxos returns a selction of utxos, to be used in another
// transaction, for provided target amount and strategy.
// Selected utxos are locked for predefined amount of time to prevent
// double-spending them.
SelectUtxos(ctx context.Context, in *SelectUtxosRequest, opts ...grpc.CallOption) (*SelectUtxosResponse, error)
// EstimateFees returns the fee amount to pay for a tx containing the given
// inputs and outputs.
EstimateFees(ctx context.Context, in *EstimateFeesRequest, opts ...grpc.CallOption) (*EstimateFeesResponse, error)
// SignTransaction signs a raw transaction in hex format.
SignTransaction(ctx context.Context, in *SignTransactionRequest, opts ...grpc.CallOption) (*SignTransactionResponse, error)
// BroadcastTransaction broadacats a raw transaction in hex format.
BroadcastTransaction(ctx context.Context, in *BroadcastTransactionRequest, opts ...grpc.CallOption) (*BroadcastTransactionResponse, error)
// CreatePset returns an unsigned pset for given inputs and outputs.
CreatePset(ctx context.Context, in *CreatePsetRequest, opts ...grpc.CallOption) (*CreatePsetResponse, error)
// UpdatePset adds the given inputs and outputs to the partial transaction.
UpdatePset(ctx context.Context, in *UpdatePsetRequest, opts ...grpc.CallOption) (*UpdatePsetResponse, error)
// BlindPset updates the given pset with required ins and outs blinded.
BlindPset(ctx context.Context, in *BlindPsetRequest, opts ...grpc.CallOption) (*BlindPsetResponse, error)
// SignPset updates the given pset adding the required signatures.
SignPset(ctx context.Context, in *SignPsetRequest, opts ...grpc.CallOption) (*SignPsetResponse, error)
// Mint returns a transaction that issues a new asset.
Mint(ctx context.Context, in *MintRequest, opts ...grpc.CallOption) (*MintResponse, error)
// Remint returns a transaction that re-issues an existing asset.
Remint(ctx context.Context, in *RemintRequest, opts ...grpc.CallOption) (*RemintResponse, error)
// Burn returns a transaction that burns some funds.
Burn(ctx context.Context, in *BurnRequest, opts ...grpc.CallOption) (*BurnResponse, error)
// Transfer returns a transaction to send funds to some receiver.
Transfer(ctx context.Context, in *TransferRequest, opts ...grpc.CallOption) (*TransferResponse, error)
// PegInAddress returns what's necessary to peg funds of the Bitcoin
// main-chain and have them available on the Liquid side-chain.
// Bitcoin funds must be sent to the main-chain address while the claim
// output script must be used to redeem the LBTC ones.
PegInAddress(ctx context.Context, in *PegInAddressRequest, opts ...grpc.CallOption) (*PegInAddressResponse, error)
// ClaimPegIn returns a transaction to claim funds pegged on the Bitcoin
// main-chain to have them available on the Liquid side-chain.
ClaimPegIn(ctx context.Context, in *ClaimPegInRequest, opts ...grpc.CallOption) (*ClaimPegInResponse, error)
// SignPsetWithSchnorrKey signs all taproot inputs of the provided tx with
// the key at the given derivation path.
SignPsetWithSchnorrKey(ctx context.Context, in *SignPsetWithSchnorrKeyRequest, opts ...grpc.CallOption) (*SignPsetWithSchnorrKeyResponse, error)
}
type transactionServiceClient struct {
cc grpc.ClientConnInterface
}
func NewTransactionServiceClient(cc grpc.ClientConnInterface) TransactionServiceClient {
return &transactionServiceClient{cc}
}
func (c *transactionServiceClient) GetTransaction(ctx context.Context, in *GetTransactionRequest, opts ...grpc.CallOption) (*GetTransactionResponse, error) {
out := new(GetTransactionResponse)
err := c.cc.Invoke(ctx, "/ocean.v1.TransactionService/GetTransaction", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *transactionServiceClient) SelectUtxos(ctx context.Context, in *SelectUtxosRequest, opts ...grpc.CallOption) (*SelectUtxosResponse, error) {
out := new(SelectUtxosResponse)
err := c.cc.Invoke(ctx, "/ocean.v1.TransactionService/SelectUtxos", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *transactionServiceClient) EstimateFees(ctx context.Context, in *EstimateFeesRequest, opts ...grpc.CallOption) (*EstimateFeesResponse, error) {
out := new(EstimateFeesResponse)
err := c.cc.Invoke(ctx, "/ocean.v1.TransactionService/EstimateFees", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *transactionServiceClient) SignTransaction(ctx context.Context, in *SignTransactionRequest, opts ...grpc.CallOption) (*SignTransactionResponse, error) {
out := new(SignTransactionResponse)
err := c.cc.Invoke(ctx, "/ocean.v1.TransactionService/SignTransaction", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *transactionServiceClient) BroadcastTransaction(ctx context.Context, in *BroadcastTransactionRequest, opts ...grpc.CallOption) (*BroadcastTransactionResponse, error) {
out := new(BroadcastTransactionResponse)
err := c.cc.Invoke(ctx, "/ocean.v1.TransactionService/BroadcastTransaction", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *transactionServiceClient) CreatePset(ctx context.Context, in *CreatePsetRequest, opts ...grpc.CallOption) (*CreatePsetResponse, error) {
out := new(CreatePsetResponse)
err := c.cc.Invoke(ctx, "/ocean.v1.TransactionService/CreatePset", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *transactionServiceClient) UpdatePset(ctx context.Context, in *UpdatePsetRequest, opts ...grpc.CallOption) (*UpdatePsetResponse, error) {
out := new(UpdatePsetResponse)
err := c.cc.Invoke(ctx, "/ocean.v1.TransactionService/UpdatePset", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *transactionServiceClient) BlindPset(ctx context.Context, in *BlindPsetRequest, opts ...grpc.CallOption) (*BlindPsetResponse, error) {
out := new(BlindPsetResponse)
err := c.cc.Invoke(ctx, "/ocean.v1.TransactionService/BlindPset", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *transactionServiceClient) SignPset(ctx context.Context, in *SignPsetRequest, opts ...grpc.CallOption) (*SignPsetResponse, error) {
out := new(SignPsetResponse)
err := c.cc.Invoke(ctx, "/ocean.v1.TransactionService/SignPset", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *transactionServiceClient) Mint(ctx context.Context, in *MintRequest, opts ...grpc.CallOption) (*MintResponse, error) {
out := new(MintResponse)
err := c.cc.Invoke(ctx, "/ocean.v1.TransactionService/Mint", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *transactionServiceClient) Remint(ctx context.Context, in *RemintRequest, opts ...grpc.CallOption) (*RemintResponse, error) {
out := new(RemintResponse)
err := c.cc.Invoke(ctx, "/ocean.v1.TransactionService/Remint", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *transactionServiceClient) Burn(ctx context.Context, in *BurnRequest, opts ...grpc.CallOption) (*BurnResponse, error) {
out := new(BurnResponse)
err := c.cc.Invoke(ctx, "/ocean.v1.TransactionService/Burn", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *transactionServiceClient) Transfer(ctx context.Context, in *TransferRequest, opts ...grpc.CallOption) (*TransferResponse, error) {
out := new(TransferResponse)
err := c.cc.Invoke(ctx, "/ocean.v1.TransactionService/Transfer", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *transactionServiceClient) PegInAddress(ctx context.Context, in *PegInAddressRequest, opts ...grpc.CallOption) (*PegInAddressResponse, error) {
out := new(PegInAddressResponse)
err := c.cc.Invoke(ctx, "/ocean.v1.TransactionService/PegInAddress", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *transactionServiceClient) ClaimPegIn(ctx context.Context, in *ClaimPegInRequest, opts ...grpc.CallOption) (*ClaimPegInResponse, error) {
out := new(ClaimPegInResponse)
err := c.cc.Invoke(ctx, "/ocean.v1.TransactionService/ClaimPegIn", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *transactionServiceClient) SignPsetWithSchnorrKey(ctx context.Context, in *SignPsetWithSchnorrKeyRequest, opts ...grpc.CallOption) (*SignPsetWithSchnorrKeyResponse, error) {
out := new(SignPsetWithSchnorrKeyResponse)
err := c.cc.Invoke(ctx, "/ocean.v1.TransactionService/SignPsetWithSchnorrKey", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// TransactionServiceServer is the server API for TransactionService service.
// All implementations should embed UnimplementedTransactionServiceServer
// for forward compatibility
type TransactionServiceServer interface {
// GetTransaction returns the hex of a transaction given its id.
GetTransaction(context.Context, *GetTransactionRequest) (*GetTransactionResponse, error)
// SelectUtxos returns a selction of utxos, to be used in another
// transaction, for provided target amount and strategy.
// Selected utxos are locked for predefined amount of time to prevent
// double-spending them.
SelectUtxos(context.Context, *SelectUtxosRequest) (*SelectUtxosResponse, error)
// EstimateFees returns the fee amount to pay for a tx containing the given
// inputs and outputs.
EstimateFees(context.Context, *EstimateFeesRequest) (*EstimateFeesResponse, error)
// SignTransaction signs a raw transaction in hex format.
SignTransaction(context.Context, *SignTransactionRequest) (*SignTransactionResponse, error)
// BroadcastTransaction broadacats a raw transaction in hex format.
BroadcastTransaction(context.Context, *BroadcastTransactionRequest) (*BroadcastTransactionResponse, error)
// CreatePset returns an unsigned pset for given inputs and outputs.
CreatePset(context.Context, *CreatePsetRequest) (*CreatePsetResponse, error)
// UpdatePset adds the given inputs and outputs to the partial transaction.
UpdatePset(context.Context, *UpdatePsetRequest) (*UpdatePsetResponse, error)
// BlindPset updates the given pset with required ins and outs blinded.
BlindPset(context.Context, *BlindPsetRequest) (*BlindPsetResponse, error)
// SignPset updates the given pset adding the required signatures.
SignPset(context.Context, *SignPsetRequest) (*SignPsetResponse, error)
// Mint returns a transaction that issues a new asset.
Mint(context.Context, *MintRequest) (*MintResponse, error)
// Remint returns a transaction that re-issues an existing asset.
Remint(context.Context, *RemintRequest) (*RemintResponse, error)
// Burn returns a transaction that burns some funds.
Burn(context.Context, *BurnRequest) (*BurnResponse, error)
// Transfer returns a transaction to send funds to some receiver.
Transfer(context.Context, *TransferRequest) (*TransferResponse, error)
// PegInAddress returns what's necessary to peg funds of the Bitcoin
// main-chain and have them available on the Liquid side-chain.
// Bitcoin funds must be sent to the main-chain address while the claim
// output script must be used to redeem the LBTC ones.
PegInAddress(context.Context, *PegInAddressRequest) (*PegInAddressResponse, error)
// ClaimPegIn returns a transaction to claim funds pegged on the Bitcoin
// main-chain to have them available on the Liquid side-chain.
ClaimPegIn(context.Context, *ClaimPegInRequest) (*ClaimPegInResponse, error)
// SignPsetWithSchnorrKey signs all taproot inputs of the provided tx with
// the key at the given derivation path.
SignPsetWithSchnorrKey(context.Context, *SignPsetWithSchnorrKeyRequest) (*SignPsetWithSchnorrKeyResponse, error)
}
// UnimplementedTransactionServiceServer should be embedded to have forward compatible implementations.
type UnimplementedTransactionServiceServer struct {
}
func (UnimplementedTransactionServiceServer) GetTransaction(context.Context, *GetTransactionRequest) (*GetTransactionResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetTransaction not implemented")
}
func (UnimplementedTransactionServiceServer) SelectUtxos(context.Context, *SelectUtxosRequest) (*SelectUtxosResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SelectUtxos not implemented")
}
func (UnimplementedTransactionServiceServer) EstimateFees(context.Context, *EstimateFeesRequest) (*EstimateFeesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method EstimateFees not implemented")
}
func (UnimplementedTransactionServiceServer) SignTransaction(context.Context, *SignTransactionRequest) (*SignTransactionResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SignTransaction not implemented")
}
func (UnimplementedTransactionServiceServer) BroadcastTransaction(context.Context, *BroadcastTransactionRequest) (*BroadcastTransactionResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method BroadcastTransaction not implemented")
}
func (UnimplementedTransactionServiceServer) CreatePset(context.Context, *CreatePsetRequest) (*CreatePsetResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreatePset not implemented")
}
func (UnimplementedTransactionServiceServer) UpdatePset(context.Context, *UpdatePsetRequest) (*UpdatePsetResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdatePset not implemented")
}
func (UnimplementedTransactionServiceServer) BlindPset(context.Context, *BlindPsetRequest) (*BlindPsetResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method BlindPset not implemented")
}
func (UnimplementedTransactionServiceServer) SignPset(context.Context, *SignPsetRequest) (*SignPsetResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SignPset not implemented")
}
func (UnimplementedTransactionServiceServer) Mint(context.Context, *MintRequest) (*MintResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Mint not implemented")
}
func (UnimplementedTransactionServiceServer) Remint(context.Context, *RemintRequest) (*RemintResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Remint not implemented")
}
func (UnimplementedTransactionServiceServer) Burn(context.Context, *BurnRequest) (*BurnResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Burn not implemented")
}
func (UnimplementedTransactionServiceServer) Transfer(context.Context, *TransferRequest) (*TransferResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Transfer not implemented")
}
func (UnimplementedTransactionServiceServer) PegInAddress(context.Context, *PegInAddressRequest) (*PegInAddressResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method PegInAddress not implemented")
}
func (UnimplementedTransactionServiceServer) ClaimPegIn(context.Context, *ClaimPegInRequest) (*ClaimPegInResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ClaimPegIn not implemented")
}
func (UnimplementedTransactionServiceServer) SignPsetWithSchnorrKey(context.Context, *SignPsetWithSchnorrKeyRequest) (*SignPsetWithSchnorrKeyResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SignPsetWithSchnorrKey not implemented")
}
// UnsafeTransactionServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to TransactionServiceServer will
// result in compilation errors.
type UnsafeTransactionServiceServer interface {
mustEmbedUnimplementedTransactionServiceServer()
}
func RegisterTransactionServiceServer(s grpc.ServiceRegistrar, srv TransactionServiceServer) {
s.RegisterService(&TransactionService_ServiceDesc, srv)
}
func _TransactionService_GetTransaction_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetTransactionRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TransactionServiceServer).GetTransaction(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ocean.v1.TransactionService/GetTransaction",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TransactionServiceServer).GetTransaction(ctx, req.(*GetTransactionRequest))
}
return interceptor(ctx, in, info, handler)
}
func _TransactionService_SelectUtxos_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SelectUtxosRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TransactionServiceServer).SelectUtxos(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ocean.v1.TransactionService/SelectUtxos",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TransactionServiceServer).SelectUtxos(ctx, req.(*SelectUtxosRequest))
}
return interceptor(ctx, in, info, handler)
}
func _TransactionService_EstimateFees_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(EstimateFeesRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TransactionServiceServer).EstimateFees(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ocean.v1.TransactionService/EstimateFees",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TransactionServiceServer).EstimateFees(ctx, req.(*EstimateFeesRequest))
}
return interceptor(ctx, in, info, handler)
}
func _TransactionService_SignTransaction_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SignTransactionRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TransactionServiceServer).SignTransaction(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ocean.v1.TransactionService/SignTransaction",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TransactionServiceServer).SignTransaction(ctx, req.(*SignTransactionRequest))
}
return interceptor(ctx, in, info, handler)
}
func _TransactionService_BroadcastTransaction_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(BroadcastTransactionRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TransactionServiceServer).BroadcastTransaction(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ocean.v1.TransactionService/BroadcastTransaction",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TransactionServiceServer).BroadcastTransaction(ctx, req.(*BroadcastTransactionRequest))
}
return interceptor(ctx, in, info, handler)
}
func _TransactionService_CreatePset_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CreatePsetRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TransactionServiceServer).CreatePset(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ocean.v1.TransactionService/CreatePset",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TransactionServiceServer).CreatePset(ctx, req.(*CreatePsetRequest))
}
return interceptor(ctx, in, info, handler)
}
func _TransactionService_UpdatePset_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(UpdatePsetRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TransactionServiceServer).UpdatePset(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ocean.v1.TransactionService/UpdatePset",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TransactionServiceServer).UpdatePset(ctx, req.(*UpdatePsetRequest))
}
return interceptor(ctx, in, info, handler)
}
func _TransactionService_BlindPset_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(BlindPsetRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TransactionServiceServer).BlindPset(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ocean.v1.TransactionService/BlindPset",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TransactionServiceServer).BlindPset(ctx, req.(*BlindPsetRequest))
}
return interceptor(ctx, in, info, handler)
}
func _TransactionService_SignPset_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SignPsetRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TransactionServiceServer).SignPset(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ocean.v1.TransactionService/SignPset",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TransactionServiceServer).SignPset(ctx, req.(*SignPsetRequest))
}
return interceptor(ctx, in, info, handler)
}
func _TransactionService_Mint_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(MintRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TransactionServiceServer).Mint(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ocean.v1.TransactionService/Mint",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TransactionServiceServer).Mint(ctx, req.(*MintRequest))
}
return interceptor(ctx, in, info, handler)
}
func _TransactionService_Remint_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(RemintRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TransactionServiceServer).Remint(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ocean.v1.TransactionService/Remint",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TransactionServiceServer).Remint(ctx, req.(*RemintRequest))
}
return interceptor(ctx, in, info, handler)
}
func _TransactionService_Burn_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(BurnRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TransactionServiceServer).Burn(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ocean.v1.TransactionService/Burn",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TransactionServiceServer).Burn(ctx, req.(*BurnRequest))
}
return interceptor(ctx, in, info, handler)
}
func _TransactionService_Transfer_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(TransferRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TransactionServiceServer).Transfer(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ocean.v1.TransactionService/Transfer",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TransactionServiceServer).Transfer(ctx, req.(*TransferRequest))
}
return interceptor(ctx, in, info, handler)
}
func _TransactionService_PegInAddress_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(PegInAddressRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TransactionServiceServer).PegInAddress(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ocean.v1.TransactionService/PegInAddress",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TransactionServiceServer).PegInAddress(ctx, req.(*PegInAddressRequest))
}
return interceptor(ctx, in, info, handler)
}
func _TransactionService_ClaimPegIn_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ClaimPegInRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TransactionServiceServer).ClaimPegIn(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ocean.v1.TransactionService/ClaimPegIn",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TransactionServiceServer).ClaimPegIn(ctx, req.(*ClaimPegInRequest))
}
return interceptor(ctx, in, info, handler)
}
func _TransactionService_SignPsetWithSchnorrKey_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SignPsetWithSchnorrKeyRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TransactionServiceServer).SignPsetWithSchnorrKey(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ocean.v1.TransactionService/SignPsetWithSchnorrKey",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TransactionServiceServer).SignPsetWithSchnorrKey(ctx, req.(*SignPsetWithSchnorrKeyRequest))
}
return interceptor(ctx, in, info, handler)
}
// TransactionService_ServiceDesc is the grpc.ServiceDesc for TransactionService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var TransactionService_ServiceDesc = grpc.ServiceDesc{
ServiceName: "ocean.v1.TransactionService",
HandlerType: (*TransactionServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "GetTransaction",
Handler: _TransactionService_GetTransaction_Handler,
},
{
MethodName: "SelectUtxos",
Handler: _TransactionService_SelectUtxos_Handler,
},
{
MethodName: "EstimateFees",
Handler: _TransactionService_EstimateFees_Handler,
},
{
MethodName: "SignTransaction",
Handler: _TransactionService_SignTransaction_Handler,
},
{
MethodName: "BroadcastTransaction",
Handler: _TransactionService_BroadcastTransaction_Handler,
},
{
MethodName: "CreatePset",
Handler: _TransactionService_CreatePset_Handler,
},
{
MethodName: "UpdatePset",
Handler: _TransactionService_UpdatePset_Handler,
},
{
MethodName: "BlindPset",
Handler: _TransactionService_BlindPset_Handler,
},
{
MethodName: "SignPset",
Handler: _TransactionService_SignPset_Handler,
},
{
MethodName: "Mint",
Handler: _TransactionService_Mint_Handler,
},
{
MethodName: "Remint",
Handler: _TransactionService_Remint_Handler,
},
{
MethodName: "Burn",
Handler: _TransactionService_Burn_Handler,
},
{
MethodName: "Transfer",
Handler: _TransactionService_Transfer_Handler,
},
{
MethodName: "PegInAddress",
Handler: _TransactionService_PegInAddress_Handler,
},
{
MethodName: "ClaimPegIn",
Handler: _TransactionService_ClaimPegIn_Handler,
},
{
MethodName: "SignPsetWithSchnorrKey",
Handler: _TransactionService_SignPsetWithSchnorrKey_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "ocean/v1/transaction.proto",
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,441 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
package oceanv1
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// WalletServiceClient is the client API for WalletService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type WalletServiceClient interface {
// GenSeed returns signing and blinding seed that should be used to create a
// new HD Wallet.
GenSeed(ctx context.Context, in *GenSeedRequest, opts ...grpc.CallOption) (*GenSeedResponse, error)
// CreateWallet creates an HD Wallet based on signing, blinding seeds,
// encrypts them with the password and persists the encrypted seeds.
CreateWallet(ctx context.Context, in *CreateWalletRequest, opts ...grpc.CallOption) (*CreateWalletResponse, error)
// Unlock tries to unlock the HD Wallet using the given password.
Unlock(ctx context.Context, in *UnlockRequest, opts ...grpc.CallOption) (*UnlockResponse, error)
// Lock locks the HD wallet.
Lock(ctx context.Context, in *LockRequest, opts ...grpc.CallOption) (*LockResponse, error)
// ChangePassword changes the password used to encrypt/decrypt the HD seeds.
// It requires the wallet to be locked.
ChangePassword(ctx context.Context, in *ChangePasswordRequest, opts ...grpc.CallOption) (*ChangePasswordResponse, error)
// RestoreWallet restores an HD Wallet based on signing and blinding seeds,
// encrypts them with the password and persists the encrypted seeds.
RestoreWallet(ctx context.Context, in *RestoreWalletRequest, opts ...grpc.CallOption) (WalletService_RestoreWalletClient, error)
// Status returns info about the status of the wallet.
Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error)
// GetInfo returns info about the HD wallet.
GetInfo(ctx context.Context, in *GetInfoRequest, opts ...grpc.CallOption) (*GetInfoResponse, error)
// Auth verifies whether the given password is valid without unlocking the wallet
Auth(ctx context.Context, in *AuthRequest, opts ...grpc.CallOption) (*AuthResponse, error)
}
type walletServiceClient struct {
cc grpc.ClientConnInterface
}
func NewWalletServiceClient(cc grpc.ClientConnInterface) WalletServiceClient {
return &walletServiceClient{cc}
}
func (c *walletServiceClient) GenSeed(ctx context.Context, in *GenSeedRequest, opts ...grpc.CallOption) (*GenSeedResponse, error) {
out := new(GenSeedResponse)
err := c.cc.Invoke(ctx, "/ocean.v1.WalletService/GenSeed", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletServiceClient) CreateWallet(ctx context.Context, in *CreateWalletRequest, opts ...grpc.CallOption) (*CreateWalletResponse, error) {
out := new(CreateWalletResponse)
err := c.cc.Invoke(ctx, "/ocean.v1.WalletService/CreateWallet", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletServiceClient) Unlock(ctx context.Context, in *UnlockRequest, opts ...grpc.CallOption) (*UnlockResponse, error) {
out := new(UnlockResponse)
err := c.cc.Invoke(ctx, "/ocean.v1.WalletService/Unlock", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletServiceClient) Lock(ctx context.Context, in *LockRequest, opts ...grpc.CallOption) (*LockResponse, error) {
out := new(LockResponse)
err := c.cc.Invoke(ctx, "/ocean.v1.WalletService/Lock", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletServiceClient) ChangePassword(ctx context.Context, in *ChangePasswordRequest, opts ...grpc.CallOption) (*ChangePasswordResponse, error) {
out := new(ChangePasswordResponse)
err := c.cc.Invoke(ctx, "/ocean.v1.WalletService/ChangePassword", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletServiceClient) RestoreWallet(ctx context.Context, in *RestoreWalletRequest, opts ...grpc.CallOption) (WalletService_RestoreWalletClient, error) {
stream, err := c.cc.NewStream(ctx, &WalletService_ServiceDesc.Streams[0], "/ocean.v1.WalletService/RestoreWallet", opts...)
if err != nil {
return nil, err
}
x := &walletServiceRestoreWalletClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type WalletService_RestoreWalletClient interface {
Recv() (*RestoreWalletResponse, error)
grpc.ClientStream
}
type walletServiceRestoreWalletClient struct {
grpc.ClientStream
}
func (x *walletServiceRestoreWalletClient) Recv() (*RestoreWalletResponse, error) {
m := new(RestoreWalletResponse)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *walletServiceClient) Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error) {
out := new(StatusResponse)
err := c.cc.Invoke(ctx, "/ocean.v1.WalletService/Status", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletServiceClient) GetInfo(ctx context.Context, in *GetInfoRequest, opts ...grpc.CallOption) (*GetInfoResponse, error) {
out := new(GetInfoResponse)
err := c.cc.Invoke(ctx, "/ocean.v1.WalletService/GetInfo", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletServiceClient) Auth(ctx context.Context, in *AuthRequest, opts ...grpc.CallOption) (*AuthResponse, error) {
out := new(AuthResponse)
err := c.cc.Invoke(ctx, "/ocean.v1.WalletService/Auth", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// WalletServiceServer is the server API for WalletService service.
// All implementations should embed UnimplementedWalletServiceServer
// for forward compatibility
type WalletServiceServer interface {
// GenSeed returns signing and blinding seed that should be used to create a
// new HD Wallet.
GenSeed(context.Context, *GenSeedRequest) (*GenSeedResponse, error)
// CreateWallet creates an HD Wallet based on signing, blinding seeds,
// encrypts them with the password and persists the encrypted seeds.
CreateWallet(context.Context, *CreateWalletRequest) (*CreateWalletResponse, error)
// Unlock tries to unlock the HD Wallet using the given password.
Unlock(context.Context, *UnlockRequest) (*UnlockResponse, error)
// Lock locks the HD wallet.
Lock(context.Context, *LockRequest) (*LockResponse, error)
// ChangePassword changes the password used to encrypt/decrypt the HD seeds.
// It requires the wallet to be locked.
ChangePassword(context.Context, *ChangePasswordRequest) (*ChangePasswordResponse, error)
// RestoreWallet restores an HD Wallet based on signing and blinding seeds,
// encrypts them with the password and persists the encrypted seeds.
RestoreWallet(*RestoreWalletRequest, WalletService_RestoreWalletServer) error
// Status returns info about the status of the wallet.
Status(context.Context, *StatusRequest) (*StatusResponse, error)
// GetInfo returns info about the HD wallet.
GetInfo(context.Context, *GetInfoRequest) (*GetInfoResponse, error)
// Auth verifies whether the given password is valid without unlocking the wallet
Auth(context.Context, *AuthRequest) (*AuthResponse, error)
}
// UnimplementedWalletServiceServer should be embedded to have forward compatible implementations.
type UnimplementedWalletServiceServer struct {
}
func (UnimplementedWalletServiceServer) GenSeed(context.Context, *GenSeedRequest) (*GenSeedResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GenSeed not implemented")
}
func (UnimplementedWalletServiceServer) CreateWallet(context.Context, *CreateWalletRequest) (*CreateWalletResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateWallet not implemented")
}
func (UnimplementedWalletServiceServer) Unlock(context.Context, *UnlockRequest) (*UnlockResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Unlock not implemented")
}
func (UnimplementedWalletServiceServer) Lock(context.Context, *LockRequest) (*LockResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Lock not implemented")
}
func (UnimplementedWalletServiceServer) ChangePassword(context.Context, *ChangePasswordRequest) (*ChangePasswordResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ChangePassword not implemented")
}
func (UnimplementedWalletServiceServer) RestoreWallet(*RestoreWalletRequest, WalletService_RestoreWalletServer) error {
return status.Errorf(codes.Unimplemented, "method RestoreWallet not implemented")
}
func (UnimplementedWalletServiceServer) Status(context.Context, *StatusRequest) (*StatusResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Status not implemented")
}
func (UnimplementedWalletServiceServer) GetInfo(context.Context, *GetInfoRequest) (*GetInfoResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetInfo not implemented")
}
func (UnimplementedWalletServiceServer) Auth(context.Context, *AuthRequest) (*AuthResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Auth not implemented")
}
// UnsafeWalletServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to WalletServiceServer will
// result in compilation errors.
type UnsafeWalletServiceServer interface {
mustEmbedUnimplementedWalletServiceServer()
}
func RegisterWalletServiceServer(s grpc.ServiceRegistrar, srv WalletServiceServer) {
s.RegisterService(&WalletService_ServiceDesc, srv)
}
func _WalletService_GenSeed_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GenSeedRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletServiceServer).GenSeed(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ocean.v1.WalletService/GenSeed",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletServiceServer).GenSeed(ctx, req.(*GenSeedRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletService_CreateWallet_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CreateWalletRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletServiceServer).CreateWallet(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ocean.v1.WalletService/CreateWallet",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletServiceServer).CreateWallet(ctx, req.(*CreateWalletRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletService_Unlock_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(UnlockRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletServiceServer).Unlock(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ocean.v1.WalletService/Unlock",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletServiceServer).Unlock(ctx, req.(*UnlockRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletService_Lock_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(LockRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletServiceServer).Lock(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ocean.v1.WalletService/Lock",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletServiceServer).Lock(ctx, req.(*LockRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletService_ChangePassword_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ChangePasswordRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletServiceServer).ChangePassword(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ocean.v1.WalletService/ChangePassword",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletServiceServer).ChangePassword(ctx, req.(*ChangePasswordRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletService_RestoreWallet_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(RestoreWalletRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(WalletServiceServer).RestoreWallet(m, &walletServiceRestoreWalletServer{stream})
}
type WalletService_RestoreWalletServer interface {
Send(*RestoreWalletResponse) error
grpc.ServerStream
}
type walletServiceRestoreWalletServer struct {
grpc.ServerStream
}
func (x *walletServiceRestoreWalletServer) Send(m *RestoreWalletResponse) error {
return x.ServerStream.SendMsg(m)
}
func _WalletService_Status_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(StatusRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletServiceServer).Status(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ocean.v1.WalletService/Status",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletServiceServer).Status(ctx, req.(*StatusRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletService_GetInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetInfoRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletServiceServer).GetInfo(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ocean.v1.WalletService/GetInfo",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletServiceServer).GetInfo(ctx, req.(*GetInfoRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletService_Auth_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(AuthRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletServiceServer).Auth(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ocean.v1.WalletService/Auth",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletServiceServer).Auth(ctx, req.(*AuthRequest))
}
return interceptor(ctx, in, info, handler)
}
// WalletService_ServiceDesc is the grpc.ServiceDesc for WalletService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var WalletService_ServiceDesc = grpc.ServiceDesc{
ServiceName: "ocean.v1.WalletService",
HandlerType: (*WalletServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "GenSeed",
Handler: _WalletService_GenSeed_Handler,
},
{
MethodName: "CreateWallet",
Handler: _WalletService_CreateWallet_Handler,
},
{
MethodName: "Unlock",
Handler: _WalletService_Unlock_Handler,
},
{
MethodName: "Lock",
Handler: _WalletService_Lock_Handler,
},
{
MethodName: "ChangePassword",
Handler: _WalletService_ChangePassword_Handler,
},
{
MethodName: "Status",
Handler: _WalletService_Status_Handler,
},
{
MethodName: "GetInfo",
Handler: _WalletService_GetInfo_Handler,
},
{
MethodName: "Auth",
Handler: _WalletService_Auth_Handler,
},
},
Streams: []grpc.StreamDesc{
{
StreamName: "RestoreWallet",
Handler: _WalletService_RestoreWallet_Handler,
ServerStreams: true,
},
},
Metadata: "ocean/v1/wallet.proto",
}

17
server/buf.Dockerfile Normal file
View File

@@ -0,0 +1,17 @@
FROM golang:1.21-alpine3.18 as builder
RUN apk add --no-cache git
RUN go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@latest
RUN go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2@latest
RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26.0
RUN go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1.0
FROM bufbuild/buf
COPY --from=builder /go/bin/protoc-gen-openapiv2 /usr/local/bin/protoc-gen-openapiv2
COPY --from=builder /go/bin/protoc-gen-grpc-gateway /usr/local/bin/protoc-gen-grpc-gateway
COPY --from=builder /go/bin/protoc-gen-go /usr/local/bin/protoc-gen-go
COPY --from=builder /go/bin/protoc-gen-go-grpc /usr/local/bin/protoc-gen-go-grpc
ENTRYPOINT ["/usr/local/bin/buf"]

21
server/buf.gen.yaml Executable file
View File

@@ -0,0 +1,21 @@
version: v1
managed:
enabled: true
go_package_prefix:
default: github.com/ark-network/ark/api-spec/protobuf/gen
except:
- buf.build/googleapis/googleapis
plugins:
# Golang
- plugin: go
out: api-spec/protobuf/gen
opt: paths=source_relative
- plugin: go-grpc
out: api-spec/protobuf/gen
opt: paths=source_relative,require_unimplemented_servers=false
- plugin: grpc-gateway
out: api-spec/protobuf/gen
opt: paths=source_relative
#OpenApi
- plugin: openapiv2
out: api-spec/openapi/swagger

4
server/buf.work.yaml Executable file
View File

@@ -0,0 +1,4 @@
version: v1
directories:
- api-spec/protobuf

70
server/cmd/arkd/main.go Executable file
View File

@@ -0,0 +1,70 @@
package main
import (
"os"
"os/signal"
"syscall"
appconfig "github.com/ark-network/ark/internal/app-config"
"github.com/ark-network/ark/internal/config"
grpcservice "github.com/ark-network/ark/internal/interface/grpc"
log "github.com/sirupsen/logrus"
)
//nolint:all
var (
version = "dev"
commit = "none"
date = "unknown"
)
func main() {
cfg, err := config.LoadConfig()
if err != nil {
log.WithError(err).Fatal("invalid config")
}
log.SetLevel(log.Level(cfg.LogLevel))
svcConfig := grpcservice.Config{
Port: cfg.Port,
NoTLS: cfg.NoTLS,
}
if cfg.RoundLifetime%512 != 0 {
setLifetime := cfg.RoundLifetime
cfg.RoundLifetime = cfg.RoundLifetime - (cfg.RoundLifetime % 512)
log.Infof("round lifetime must be a multiple of 512, %d -> %d", setLifetime, cfg.RoundLifetime)
}
appConfig := &appconfig.Config{
DbType: cfg.DbType,
DbDir: cfg.DbDir,
RoundInterval: cfg.RoundInterval,
Network: cfg.Network,
SchedulerType: cfg.SchedulerType,
TxBuilderType: cfg.TxBuilderType,
BlockchainScannerType: cfg.BlockchainScannerType,
WalletAddr: cfg.WalletAddr,
MinRelayFee: cfg.MinRelayFee,
RoundLifetime: cfg.RoundLifetime,
}
svc, err := grpcservice.NewService(svcConfig, appConfig)
if err != nil {
log.Fatal(err)
}
log.RegisterExitHandler(svc.Stop)
log.Info("starting service...")
if err := svc.Start(); err != nil {
log.Fatal(err)
}
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT)
<-sigChan
log.Info("shutting down service...")
log.Exit(0)
}

84
server/go.mod Normal file
View File

@@ -0,0 +1,84 @@
module github.com/ark-network/ark
go 1.21.0
replace github.com/ark-network/ark/common => ../common
require (
github.com/ark-network/ark/common v0.0.0
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0
github.com/dgraph-io/badger/v4 v4.1.0
github.com/go-co-op/gocron v1.36.0
github.com/gogo/protobuf v1.3.2
github.com/google/uuid v1.4.0
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1
github.com/sirupsen/logrus v1.9.3
github.com/spf13/viper v1.17.0
github.com/stretchr/testify v1.8.4
github.com/timshannon/badgerhold/v4 v4.0.3
github.com/urfave/cli/v2 v2.26.0
github.com/vulpemventures/go-elements v0.5.3
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17
google.golang.org/grpc v1.59.0
google.golang.org/protobuf v1.31.0
)
require github.com/stretchr/objx v0.5.0 // indirect
require (
github.com/FactomProject/basen v0.0.0-20150613233007-fe3947df716e // indirect
github.com/FactomProject/btcutilecc v0.0.0-20130527213604-d3a63a5752ec // indirect
)
require (
github.com/btcsuite/btcd v0.23.1
github.com/btcsuite/btcd/btcec/v2 v2.3.2
github.com/btcsuite/btcd/btcutil v1.1.3
github.com/btcsuite/btcd/btcutil/psbt v1.1.4 // indirect
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect
github.com/dgraph-io/ristretto v0.1.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/golang/glog v1.1.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/flatbuffers v23.5.9+incompatible // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/klauspost/compress v1.17.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sagikazarmark/locafero v0.3.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.10.0 // indirect
github.com/spf13/cast v1.5.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/vulpemventures/fastsha256 v0.0.0-20160815193821-637e65642941 // indirect
github.com/vulpemventures/go-bip32 v0.0.0-20200624192635-867c159da4d7
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
go.opencensus.io v0.24.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/net v0.17.0
golang.org/x/sys v0.13.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/genproto v0.0.0-20231030173426-d783a09b4405 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

742
server/go.sum Normal file
View File

@@ -0,0 +1,742 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
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.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
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/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/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=
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/FactomProject/basen v0.0.0-20150613233007-fe3947df716e h1:ahyvB3q25YnZWly5Gq1ekg6jcmWaGj/vG/MhF4aisoc=
github.com/FactomProject/basen v0.0.0-20150613233007-fe3947df716e/go.mod h1:kGUqhHd//musdITWjFvNTHn90WG9bMLBEPQZ17Cmlpw=
github.com/FactomProject/btcutilecc v0.0.0-20130527213604-d3a63a5752ec h1:1Qb69mGp/UtRPn422BH4/Y4Q3SLUrD9KHuDkm8iodFc=
github.com/FactomProject/btcutilecc v0.0.0-20130527213604-d3a63a5752ec/go.mod h1:CD8UlnlLDiqb36L110uqiP2iSflVjx9g/3U9hCI4q2U=
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
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.23.0/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY=
github.com/btcsuite/btcd v0.23.1 h1:IB8cVQcC2X5mHbnfirLG5IZnkWYNTPlLZVrxUYSotbE=
github.com/btcsuite/btcd v0.23.1/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY=
github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA=
github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE=
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.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.3 h1:xfbtw8lwpp0G6NwSHb+UE67ryTFHJAiNuipusjXSohQ=
github.com/btcsuite/btcd/btcutil v1.1.3/go.mod h1:UR7dsSJzJUfMmFiiLlIrMq1lS9jh9EdCV7FStZSnpi0=
github.com/btcsuite/btcd/btcutil/psbt v1.1.4 h1:Edx4AfBn+YPam2KP5AobDitulGp4r1Oibm8oruzkMdI=
github.com/btcsuite/btcd/btcutil/psbt v1.1.4/go.mod h1:9AyU6EQVJ9Iw9zPyNT1lcdHd6cnEZdno5wLu5FY74os=
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/chaincfg/chainhash v1.0.1/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 v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
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/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cmars/basen v0.0.0-20150613233007-fe3947df716e h1:0XBUw73chJ1VYSsfvcPvVT7auykAJce9FpRr10L6Qhw=
github.com/cmars/basen v0.0.0-20150613233007-fe3947df716e/go.mod h1:P13beTBKr5Q18lJe1rIoLUqjM+CB1zYrRg44ZqGuQSA=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y=
github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
github.com/dgraph-io/badger/v4 v4.1.0 h1:E38jc0f+RATYrycSUf9LMv/t47XAy+3CApyYSq4APOQ=
github.com/dgraph-io/badger/v4 v4.1.0/go.mod h1:P50u28d39ibBRmIJuQC/NSdBOg46HnHw7al2SW5QRHg=
github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8=
github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
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.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
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.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/go-co-op/gocron v1.36.0 h1:sEmAwg57l4JWQgzaVWYfKZ+w13uHOqeOtwjo72Ll5Wc=
github.com/go-co-op/gocron v1.36.0/go.mod h1:3L/n6BkO7ABj+TrfSVXLRzsP26zmikL4ISkLQ0O8iNY=
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=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.1.1/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ=
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-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/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/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
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=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/flatbuffers v23.5.9+incompatible h1:mTPHyMn3/qO7lvBcm5S9p0olWUQgtQhBf2QWiz1U3qA=
github.com/google/flatbuffers v23.5.9+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
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.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
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/martian/v3 v3.1.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-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
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-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-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
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/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI=
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1 h1:6UKoz5ujsI55KNpsJH3UwCq3T8kKbZwNZBNPuTTje8U=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1/go.mod h1:YvJ2f6MplWDhfxiUC3KpyTy76kYUZA4W3pTv/wdKQ9Y=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
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=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
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/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
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/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/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM=
github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/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/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
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 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagikazarmark/locafero v0.3.0 h1:zT7VEGWC2DTflmccN/5T1etyKvxSxpHsjb9cJvm4SvQ=
github.com/sagikazarmark/locafero v0.3.0/go.mod h1:w+v7UsPNFwzF1cHuOajOOzoq4U7v/ig1mpRjqV+Bu1U=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY=
github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/spf13/viper v1.17.0 h1:I5txKw7MJasPL/BrfkbA0Jyo/oELqVmux4pR/UxOMfI=
github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0+yVI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
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/testify v1.1.5-0.20170601210322-f6abca593680/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
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.2/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/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
github.com/timshannon/badgerhold/v4 v4.0.3 h1:W6pd2qckoXw2cl8eH0ZCV/9CXNaXvaM26tzFi5Tj+v8=
github.com/timshannon/badgerhold/v4 v4.0.3/go.mod h1:IkZIr0kcZLMdD7YJfW/G6epb6ZXHD/h0XR2BTk/VZg8=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/urfave/cli/v2 v2.26.0 h1:3f3AMg3HpThFNT4I++TKOejZO8yU55t3JnnSr4S4QEI=
github.com/urfave/cli/v2 v2.26.0/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
github.com/vulpemventures/fastsha256 v0.0.0-20160815193821-637e65642941 h1:CTcw80hz/Sw8hqlKX5ZYvBUF5gAHSHwdjXxRf/cjDcI=
github.com/vulpemventures/fastsha256 v0.0.0-20160815193821-637e65642941/go.mod h1:GXBJykxW2kUcktGdsgyay7uwwWvkljASfljNcT0mbh8=
github.com/vulpemventures/go-bip32 v0.0.0-20200624192635-867c159da4d7 h1:X7DtNv+YWy76kELMZB/xVkIJ7YNp2vpgMFVsDcQA40U=
github.com/vulpemventures/go-bip32 v0.0.0-20200624192635-867c159da4d7/go.mod h1:Zrvx8XgpWvSPdz1lXnuN083CkoZnzwxBLEB03S8et1I=
github.com/vulpemventures/go-elements v0.5.3 h1:zaC/ynHFwCAzFSOMfzb6BcbD6FXASppSiGMycc95WVA=
github.com/vulpemventures/go-elements v0.5.3/go.mod h1:aBGuWXHaiAIUIcwqCdtEh2iQ3kJjKwHU9ywvhlcRSeU=
github.com/vulpemventures/go-secp256k1-zkp v1.1.6 h1:BmsrmXRLUibwa75Qkk8yELjpzCzlAjYFGLiLiOdq7Xo=
github.com/vulpemventures/go-secp256k1-zkp v1.1.6/go.mod h1:zo7CpgkuPgoe7fAV+inyxsI9IhGmcoFgyD8nqZaPSOM=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
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.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
golang.org/x/crypto v0.0.0-20170613210332-850760c427c5/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/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-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
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-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
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=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
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.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
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-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
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-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
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-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-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
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-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
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=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
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-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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-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-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/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-20200905004654-be1d3432aa8f/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=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
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=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
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-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-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-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-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
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=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
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.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
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-20200423170343-7949de9c1215/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-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-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20231030173426-d783a09b4405 h1:I6WNifs6pF9tNdSob2W24JtyxIYjzFB9qDlpUC76q+U=
google.golang.org/genproto v0.0.0-20231030173426-d783a09b4405/go.mod h1:3WDQMjmJk36UQhjQ89emUzb1mdaHcPeeAh4SCBKznB4=
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 h1:JpwMPBpFN3uKhdaekDpiNlImDdkUAyiJ6ez/uxGaUSo=
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4=
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.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
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.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
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=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
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.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
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=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
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=
launchpad.net/gocheck v0.0.0-20140225173054-000000000087 h1:Izowp2XBH6Ya6rv+hqbceQyw/gSGoXfH/UPoTGduL54=
launchpad.net/gocheck v0.0.0-20140225173054-000000000087/go.mod h1:hj7XX3B/0A+80Vse0e+BUHsHMTEhd0O4cpUHr/e/BUM=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

35
server/goreleaser.Dockerfile Executable file
View File

@@ -0,0 +1,35 @@
FROM debian:buster-slim
ARG TARGETPLATFORM
WORKDIR /app
COPY . .
RUN set -ex \
&& if [ "${TARGETPLATFORM}" = "linux/amd64" ]; then export TARGETPLATFORM=amd64; fi \
&& if [ "${TARGETPLATFORM}" = "linux/arm64" ]; then export TARGETPLATFORM=arm64; fi \
&& mv "arkd-linux-$TARGETPLATFORM" /usr/local/bin/arkd
# $USER name, and data $DIR to be used in the 'final' image
ARG USER=ark
ARG DIR=/home/ark
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates
# NOTE: Default GID == UID == 1000
RUN adduser --disabled-password \
--home "$DIR/" \
--gecos "" \
"$USER"
USER $USER
# Prevents 'VOLUME $DIR/.arkd/' being created as owned by 'root'
RUN mkdir -p "$DIR/.arkd/"
# Expose volume containing all arkd data
VOLUME $DIR/.arkd/
ENTRYPOINT [ "arkd" ]

View File

@@ -0,0 +1,249 @@
package appconfig
import (
"fmt"
"strings"
"github.com/ark-network/ark/common"
"github.com/ark-network/ark/internal/core/application"
"github.com/ark-network/ark/internal/core/ports"
"github.com/ark-network/ark/internal/infrastructure/db"
oceanwallet "github.com/ark-network/ark/internal/infrastructure/ocean-wallet"
scheduler "github.com/ark-network/ark/internal/infrastructure/scheduler/gocron"
txbuilder "github.com/ark-network/ark/internal/infrastructure/tx-builder/covenant"
txbuilderdummy "github.com/ark-network/ark/internal/infrastructure/tx-builder/dummy"
log "github.com/sirupsen/logrus"
"github.com/vulpemventures/go-elements/network"
)
var (
supportedDbs = supportedType{
"badger": {},
}
supportedSchedulers = supportedType{
"gocron": {},
}
supportedTxBuilders = supportedType{
"dummy": {},
"covenant": {},
}
supportedScanners = supportedType{
"ocean": {},
}
)
type Config struct {
DbType string
DbDir string
RoundInterval int64
Network common.Network
SchedulerType string
TxBuilderType string
BlockchainScannerType string
WalletAddr string
MinRelayFee uint64
RoundLifetime int64
repo ports.RepoManager
svc application.Service
wallet ports.WalletService
txBuilder ports.TxBuilder
scanner ports.BlockchainScanner
scheduler ports.SchedulerService
}
func (c *Config) Validate() error {
if !supportedDbs.supports(c.DbType) {
return fmt.Errorf("db type not supported, please select one of: %s", supportedDbs)
}
if !supportedSchedulers.supports(c.SchedulerType) {
return fmt.Errorf("scheduler type not supported, please select one of: %s", supportedSchedulers)
}
if !supportedTxBuilders.supports(c.TxBuilderType) {
return fmt.Errorf("tx builder type not supported, please select one of: %s", supportedTxBuilders)
}
if !supportedScanners.supports(c.BlockchainScannerType) {
return fmt.Errorf("blockchain scanner type not supported, please select one of: %s", supportedScanners)
}
if c.RoundInterval < 5 {
return fmt.Errorf("invalid round interval, must be at least 5 seconds")
}
if c.Network.Name != "liquid" && c.Network.Name != "testnet" {
return fmt.Errorf("invalid network, must be either liquid or testnet")
}
if len(c.WalletAddr) <= 0 {
return fmt.Errorf("missing onchain wallet address")
}
if c.MinRelayFee < 30 {
return fmt.Errorf("invalid min relay fee, must be at least 30 sats")
}
if err := c.repoManager(); err != nil {
return err
}
if err := c.walletService(); err != nil {
return fmt.Errorf("failed to connect to wallet: %s", err)
}
if err := c.txBuilderService(); err != nil {
return err
}
if err := c.scannerService(); err != nil {
return err
}
if err := c.schedulerService(); err != nil {
return err
}
if err := c.appService(); err != nil {
return err
}
// round life time must be a multiple of 512
if c.RoundLifetime <= 0 || c.RoundLifetime%512 != 0 {
return fmt.Errorf("invalid round lifetime, must be greater than 0 and a multiple of 512")
}
seq, err := common.BIP68Encode(uint(c.RoundLifetime))
if err != nil {
return fmt.Errorf("invalid round lifetime, %s", err)
}
seconds, err := common.BIP68Decode(seq)
if err != nil {
return fmt.Errorf("invalid round lifetime, %s", err)
}
if seconds != uint(c.RoundLifetime) {
return fmt.Errorf("invalid round lifetime, must be a multiple of 512")
}
return nil
}
func (c *Config) AppService() application.Service {
return c.svc
}
func (c *Config) repoManager() error {
var svc ports.RepoManager
var err error
switch c.DbType {
case "badger":
logger := log.New()
svc, err = db.NewService(db.ServiceConfig{
EventStoreType: c.DbType,
RoundStoreType: c.DbType,
VtxoStoreType: c.DbType,
EventStoreConfig: []interface{}{c.DbDir, logger},
RoundStoreConfig: []interface{}{c.DbDir, logger},
VtxoStoreConfig: []interface{}{c.DbDir, logger},
})
default:
return fmt.Errorf("unknown db type")
}
if err != nil {
return err
}
c.repo = svc
return nil
}
func (c *Config) walletService() error {
svc, err := oceanwallet.NewService(c.WalletAddr)
if err != nil {
return err
}
c.wallet = svc
return nil
}
func (c *Config) txBuilderService() error {
var svc ports.TxBuilder
var err error
net := c.mainChain()
switch c.TxBuilderType {
case "dummy":
svc = txbuilderdummy.NewTxBuilder(c.wallet, net)
case "covenant":
svc = txbuilder.NewTxBuilder(c.wallet, net, c.RoundLifetime)
default:
err = fmt.Errorf("unknown tx builder type")
}
if err != nil {
return err
}
c.txBuilder = svc
return nil
}
func (c *Config) scannerService() error {
var svc ports.BlockchainScanner
var err error
switch c.BlockchainScannerType {
case "ocean":
svc = c.wallet
default:
err = fmt.Errorf("unknown blockchain scanner type")
}
if err != nil {
return err
}
c.scanner = svc
return nil
}
func (c *Config) schedulerService() error {
var svc ports.SchedulerService
var err error
switch c.SchedulerType {
case "gocron":
svc = scheduler.NewScheduler()
default:
err = fmt.Errorf("unknown scheduler type")
}
if err != nil {
return err
}
c.scheduler = svc
return nil
}
func (c *Config) appService() error {
net := c.mainChain()
svc, err := application.NewService(
c.Network, net, c.RoundInterval, c.RoundLifetime, c.MinRelayFee,
c.wallet, c.repo, c.txBuilder, c.scanner, c.scheduler,
)
if err != nil {
return err
}
c.svc = svc
return nil
}
func (c *Config) mainChain() network.Network {
net := network.Liquid
if c.Network.Name != "mainnet" {
net = network.Testnet
}
return net
}
type supportedType map[string]struct{}
func (t supportedType) String() string {
types := make([]string, 0, len(t))
for tt := range t {
types = append(types, tt)
}
return strings.Join(types, " | ")
}
func (t supportedType) supports(typeStr string) bool {
_, ok := t[typeStr]
return ok
}

View File

@@ -0,0 +1,122 @@
package config
import (
"fmt"
"os"
"path/filepath"
"strings"
common "github.com/ark-network/ark/common"
"github.com/spf13/viper"
)
type Config struct {
WalletAddr string
RoundInterval int64
Port uint32
DbType string
DbDir string
SchedulerType string
TxBuilderType string
BlockchainScannerType string
NoTLS bool
Network common.Network
LogLevel int
MinRelayFee uint64
RoundLifetime int64
}
var (
Datadir = "DATADIR"
WalletAddr = "WALLET_ADDR"
RoundInterval = "ROUND_INTERVAL"
Port = "PORT"
DbType = "DB_TYPE"
SchedulerType = "SCHEDULER_TYPE"
TxBuilderType = "TX_BUILDER_TYPE"
BlockchainScannerType = "BC_SCANNER_TYPE"
Insecure = "INSECURE"
LogLevel = "LOG_LEVEL"
Network = "NETWORK"
MinRelayFee = "MIN_RELAY_FEE"
RoundLifetime = "ROUND_LIFETIME"
defaultDatadir = common.AppDataDir("arkd", false)
defaultRoundInterval = 60
defaultPort = 6000
defaultDbType = "badger"
defaultSchedulerType = "gocron"
defaultTxBuilderType = "covenant"
defaultBlockchainScannerType = "ocean"
defaultInsecure = true
defaultNetwork = "testnet"
defaultLogLevel = 5
defaultMinRelayFee = 30
defaultRoundLifetime = 512
)
func LoadConfig() (*Config, error) {
viper.SetEnvPrefix("ARK")
viper.AutomaticEnv()
viper.SetDefault(Datadir, defaultDatadir)
viper.SetDefault(RoundInterval, defaultRoundInterval)
viper.SetDefault(Port, defaultPort)
viper.SetDefault(DbType, defaultDbType)
viper.SetDefault(SchedulerType, defaultSchedulerType)
viper.SetDefault(TxBuilderType, defaultTxBuilderType)
viper.SetDefault(BlockchainScannerType, defaultBlockchainScannerType)
viper.SetDefault(Insecure, defaultInsecure)
viper.SetDefault(LogLevel, defaultLogLevel)
viper.SetDefault(Network, defaultNetwork)
viper.SetDefault(RoundLifetime, defaultRoundLifetime)
viper.SetDefault(MinRelayFee, defaultMinRelayFee)
net, err := getNetwork()
if err != nil {
return nil, err
}
if err := initDatadir(); err != nil {
return nil, fmt.Errorf("error while creating datadir: %s", err)
}
return &Config{
WalletAddr: viper.GetString(WalletAddr),
RoundInterval: viper.GetInt64(RoundInterval),
Port: viper.GetUint32(Port),
DbType: viper.GetString(DbType),
SchedulerType: viper.GetString(SchedulerType),
TxBuilderType: viper.GetString(TxBuilderType),
BlockchainScannerType: viper.GetString(BlockchainScannerType),
NoTLS: viper.GetBool(Insecure),
DbDir: filepath.Join(viper.GetString(Datadir), "db"),
LogLevel: viper.GetInt(LogLevel),
Network: net,
MinRelayFee: viper.GetUint64(MinRelayFee),
RoundLifetime: viper.GetInt64(RoundLifetime),
}, nil
}
func initDatadir() error {
datadir := viper.GetString(Datadir)
return makeDirectoryIfNotExists(datadir)
}
func makeDirectoryIfNotExists(path string) error {
if _, err := os.Stat(path); os.IsNotExist(err) {
return os.MkdirAll(path, os.ModeDir|0755)
}
return nil
}
func getNetwork() (common.Network, error) {
switch strings.ToLower(viper.GetString(Network)) {
case "mainnet":
return common.MainNet, nil
case "testnet":
return common.TestNet, nil
default:
return common.Network{}, fmt.Errorf("unknown network")
}
}

View File

@@ -0,0 +1,601 @@
package application
import (
"bytes"
"context"
"encoding/hex"
"fmt"
"time"
"github.com/ark-network/ark/common"
"github.com/ark-network/ark/internal/core/domain"
"github.com/ark-network/ark/internal/core/ports"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
log "github.com/sirupsen/logrus"
"github.com/vulpemventures/go-elements/network"
"github.com/vulpemventures/go-elements/psetv2"
)
var (
paymentsThreshold = int64(128)
dustAmount = uint64(450)
faucetVtxo = domain.VtxoKey{
Txid: "0000000000000000000000000000000000000000000000000000000000000000",
VOut: 0,
}
)
type Service interface {
Start() error
Stop()
SpendVtxos(ctx context.Context, inputs []domain.VtxoKey) (string, error)
ClaimVtxos(ctx context.Context, creds string, receivers []domain.Receiver) error
SignVtxos(ctx context.Context, forfeitTxs []string) error
FaucetVtxos(ctx context.Context, pubkey *secp256k1.PublicKey) error
GetRoundByTxid(ctx context.Context, poolTxid string) (*domain.Round, error)
GetEventsChannel(ctx context.Context) <-chan domain.RoundEvent
UpdatePaymentStatus(ctx context.Context, id string) error
ListVtxos(ctx context.Context, pubkey *secp256k1.PublicKey) ([]domain.Vtxo, error)
GetPubkey(ctx context.Context) (string, error)
}
type service struct {
network common.Network
onchainNework network.Network
pubkey *secp256k1.PublicKey
roundLifetime int64
roundInterval int64
minRelayFee uint64
wallet ports.WalletService
repoManager ports.RepoManager
builder ports.TxBuilder
scanner ports.BlockchainScanner
sweeper *sweeper
paymentRequests *paymentsMap
forfeitTxs *forfeitTxsMap
eventsCh chan domain.RoundEvent
}
func NewService(
network common.Network, onchainNetwork network.Network,
roundInterval, roundLifetime int64, minRelayFee uint64,
walletSvc ports.WalletService, repoManager ports.RepoManager,
builder ports.TxBuilder, scanner ports.BlockchainScanner,
scheduler ports.SchedulerService,
) (Service, error) {
eventsCh := make(chan domain.RoundEvent)
paymentRequests := newPaymentsMap(nil)
genesisHash, _ := chainhash.NewHashFromStr(onchainNetwork.GenesisBlockHash)
forfeitTxs := newForfeitTxsMap(genesisHash)
pubkey, err := walletSvc.GetPubkey(context.Background())
if err != nil {
return nil, fmt.Errorf("failed to fetch pubkey: %s", err)
}
sweeper := newSweeper(walletSvc, repoManager, builder, scheduler)
svc := &service{
network, onchainNetwork, pubkey,
roundLifetime, roundInterval, minRelayFee,
walletSvc, repoManager, builder, scanner, sweeper,
paymentRequests, forfeitTxs, eventsCh,
}
repoManager.RegisterEventsHandler(
func(round *domain.Round) {
svc.updateProjectionStore(round)
svc.propagateEvents(round)
},
)
if err := svc.restoreWatchingVtxos(); err != nil {
return nil, fmt.Errorf("failed to restore watching vtxos: %s", err)
}
go svc.listenToRedemptions()
return svc, nil
}
func (s *service) Start() error {
log.Debug("starting sweeper service")
if err := s.sweeper.start(); err != nil {
return err
}
log.Debug("starting app service")
go s.start()
return nil
}
func (s *service) Stop() {
s.sweeper.stop()
// nolint
vtxos, _ := s.repoManager.Vtxos().GetSpendableVtxos(
context.Background(), "",
)
if len(vtxos) > 0 {
s.stopWatchingVtxos(vtxos)
}
s.wallet.Close()
log.Debug("closed connection to wallet")
s.repoManager.Close()
log.Debug("closed connection to db")
}
func (s *service) SpendVtxos(ctx context.Context, inputs []domain.VtxoKey) (string, error) {
vtxos, err := s.repoManager.Vtxos().GetVtxos(ctx, inputs)
if err != nil {
return "", err
}
for _, v := range vtxos {
if v.Spent {
return "", fmt.Errorf("input %s:%d already spent", v.Txid, v.VOut)
}
}
payment, err := domain.NewPayment(vtxos)
if err != nil {
return "", err
}
if err := s.paymentRequests.push(*payment); err != nil {
return "", err
}
return payment.Id, nil
}
func (s *service) ClaimVtxos(ctx context.Context, creds string, receivers []domain.Receiver) error {
// Check credentials
payment, ok := s.paymentRequests.view(creds)
if !ok {
return fmt.Errorf("invalid credentials")
}
if err := payment.AddReceivers(receivers); err != nil {
return err
}
return s.paymentRequests.update(*payment)
}
func (s *service) UpdatePaymentStatus(_ context.Context, id string) error {
return s.paymentRequests.updatePingTimestamp(id)
}
func (s *service) FaucetVtxos(ctx context.Context, userPubkey *secp256k1.PublicKey) error {
pubkey := hex.EncodeToString(userPubkey.SerializeCompressed())
payment, err := domain.NewPayment([]domain.Vtxo{
{
VtxoKey: faucetVtxo,
Receiver: domain.Receiver{
Pubkey: pubkey,
Amount: 10000,
},
},
})
if err != nil {
return err
}
if err := payment.AddReceivers([]domain.Receiver{
{Pubkey: pubkey, Amount: 1000},
{Pubkey: pubkey, Amount: 1000},
{Pubkey: pubkey, Amount: 1000},
{Pubkey: pubkey, Amount: 1000},
{Pubkey: pubkey, Amount: 1000},
{Pubkey: pubkey, Amount: 1000},
{Pubkey: pubkey, Amount: 1000},
{Pubkey: pubkey, Amount: 1000},
{Pubkey: pubkey, Amount: 1000},
{Pubkey: pubkey, Amount: 1000},
}); err != nil {
return err
}
if err := s.paymentRequests.push(*payment); err != nil {
return err
}
return s.paymentRequests.updatePingTimestamp(payment.Id)
}
func (s *service) SignVtxos(ctx context.Context, forfeitTxs []string) error {
return s.forfeitTxs.sign(forfeitTxs)
}
func (s *service) ListVtxos(ctx context.Context, pubkey *secp256k1.PublicKey) ([]domain.Vtxo, error) {
pk := hex.EncodeToString(pubkey.SerializeCompressed())
return s.repoManager.Vtxos().GetSpendableVtxos(ctx, pk)
}
func (s *service) GetEventsChannel(ctx context.Context) <-chan domain.RoundEvent {
return s.eventsCh
}
func (s *service) GetRoundByTxid(ctx context.Context, poolTxid string) (*domain.Round, error) {
return s.repoManager.Rounds().GetRoundWithTxid(ctx, poolTxid)
}
func (s *service) GetPubkey(ctx context.Context) (string, error) {
pubkey, err := common.EncodePubKey(s.network.PubKey, s.pubkey)
if err != nil {
return "", err
}
return pubkey, nil
}
func (s *service) start() {
s.startRound()
}
func (s *service) startRound() {
round := domain.NewRound(dustAmount)
changes, _ := round.StartRegistration()
if err := s.repoManager.Events().Save(
context.Background(), round.Id, changes...,
); err != nil {
log.WithError(err).Warn("failed to store new round events")
return
}
defer func() {
time.Sleep(time.Duration(s.roundInterval/2) * time.Second)
s.startFinalization()
}()
log.Debugf("started registration stage for new round: %s", round.Id)
}
func (s *service) startFinalization() {
ctx := context.Background()
round, err := s.repoManager.Rounds().GetCurrentRound(ctx)
if err != nil {
log.WithError(err).Warn("failed to retrieve current round")
return
}
var changes []domain.RoundEvent
defer func() {
if len(changes) > 0 {
if err := s.repoManager.Events().Save(ctx, round.Id, changes...); err != nil {
log.WithError(err).Warn("failed to store new round events")
}
}
if round.IsFailed() {
s.startRound()
return
}
time.Sleep(time.Duration((s.roundInterval/2)-1) * time.Second)
s.finalizeRound()
}()
if round.IsFailed() {
return
}
// TODO: understand how many payments must be popped from the queue and actually registered for the round
num := s.paymentRequests.len()
if num == 0 {
err := fmt.Errorf("no payments registered")
changes = round.Fail(fmt.Errorf("round aborted: %s", err))
log.WithError(err).Debugf("round %s aborted", round.Id)
return
}
if num > paymentsThreshold {
num = paymentsThreshold
}
payments := s.paymentRequests.pop(num)
changes, err = round.RegisterPayments(payments)
if err != nil {
changes = round.Fail(fmt.Errorf("failed to register payments: %s", err))
log.WithError(err).Warn("failed to register payments")
return
}
unsignedPoolTx, tree, err := s.builder.BuildPoolTx(s.pubkey, payments, s.minRelayFee)
if err != nil {
changes = round.Fail(fmt.Errorf("failed to create pool tx: %s", err))
log.WithError(err).Warn("failed to create pool tx")
return
}
log.Debugf("pool tx created for round %s", round.Id)
connectors, forfeitTxs, err := s.builder.BuildForfeitTxs(s.pubkey, unsignedPoolTx, payments)
if err != nil {
changes = round.Fail(fmt.Errorf("failed to create connectors and forfeit txs: %s", err))
log.WithError(err).Warn("failed to create connectors and forfeit txs")
return
}
log.Debugf("forfeit transactions created for round %s", round.Id)
events, err := round.StartFinalization(connectors, tree, unsignedPoolTx)
if err != nil {
changes = round.Fail(fmt.Errorf("failed to start finalization: %s", err))
log.WithError(err).Warn("failed to start finalization")
return
}
changes = append(changes, events...)
s.forfeitTxs.push(forfeitTxs)
log.Debugf("started finalization stage for round: %s", round.Id)
}
func (s *service) finalizeRound() {
defer s.startRound()
ctx := context.Background()
round, err := s.repoManager.Rounds().GetCurrentRound(ctx)
if err != nil {
log.WithError(err).Warn("failed to retrieve current round")
return
}
if round.IsFailed() {
return
}
var changes []domain.RoundEvent
defer func() {
if err := s.repoManager.Events().Save(ctx, round.Id, changes...); err != nil {
log.WithError(err).Warn("failed to store new round events")
return
}
}()
forfeitTxs, leftUnsigned := s.forfeitTxs.pop()
if len(leftUnsigned) > 0 {
err := fmt.Errorf("%d forfeit txs left to sign", len(leftUnsigned))
changes = round.Fail(fmt.Errorf("failed to finalize round: %s", err))
log.WithError(err).Warn("failed to finalize round")
return
}
log.Debugf("signing round transaction %s\n", round.Id)
signedPoolTx, err := s.wallet.SignPset(ctx, round.UnsignedTx, true)
if err != nil {
changes = round.Fail(fmt.Errorf("failed to sign round tx: %s", err))
log.WithError(err).Warn("failed to sign round tx")
return
}
txid, err := s.wallet.BroadcastTransaction(ctx, signedPoolTx)
if err != nil {
changes = round.Fail(fmt.Errorf("failed to broadcast pool tx: %s", err))
log.WithError(err).Warn("failed to broadcast pool tx")
return
}
now := time.Now().Unix()
expirationTimestamp := now + s.roundLifetime + 30 // add 30 secs to be sure that the tx is confirmed
if err := s.sweeper.schedule(expirationTimestamp, txid, round.CongestionTree); err != nil {
changes = round.Fail(fmt.Errorf("failed to schedule sweep tx: %s", err))
log.WithError(err).Warn("failed to schedule sweep tx")
return
}
changes, _ = round.EndFinalization(forfeitTxs, txid)
log.Debugf("finalized round %s with pool tx %s", round.Id, round.Txid)
}
func (s *service) listenToRedemptions() {
ctx := context.Background()
chVtxos := s.scanner.GetNotificationChannel(ctx)
for vtxoKeys := range chVtxos {
if len(vtxoKeys) > 0 {
for {
// TODO: make sure that the vtxos haven't been already spent, otherwise
// broadcast the corresponding forfeit tx and connector to prevent
// getting cheated.
vtxos, err := s.repoManager.Vtxos().RedeemVtxos(ctx, vtxoKeys)
if err != nil {
log.WithError(err).Warn("failed to redeem vtxos, retrying...")
time.Sleep(100 * time.Millisecond)
continue
}
if len(vtxos) > 0 {
log.Debugf("redeemed %d vtxos", len(vtxos))
}
break
}
}
}
}
func (s *service) updateProjectionStore(round *domain.Round) {
ctx := context.Background()
lastChange := round.Events()[len(round.Events())-1]
// Update the vtxo set only after a round is finalized.
if _, ok := lastChange.(domain.RoundFinalized); ok {
repo := s.repoManager.Vtxos()
spentVtxos := getSpentVtxos(round.Payments)
if len(spentVtxos) > 0 {
for {
if err := repo.SpendVtxos(ctx, spentVtxos); err != nil {
log.WithError(err).Warn("failed to add new vtxos, retrying soon")
time.Sleep(100 * time.Millisecond)
continue
}
log.Debugf("spent %d vtxos", len(spentVtxos))
break
}
}
newVtxos := s.getNewVtxos(round)
for {
if err := repo.AddVtxos(ctx, newVtxos); err != nil {
log.WithError(err).Warn("failed to add new vtxos, retrying soon")
time.Sleep(100 * time.Millisecond)
continue
}
log.Debugf("added %d new vtxos", len(newVtxos))
break
}
go func() {
for {
if err := s.startWatchingVtxos(newVtxos); err != nil {
log.WithError(err).Warn(
"failed to start watching vtxos, retrying in a moment...",
)
continue
}
log.Debugf("started watching %d vtxos", len(newVtxos))
return
}
}()
}
// Always update the status of the round.
for {
if err := s.repoManager.Rounds().AddOrUpdateRound(ctx, *round); err != nil {
time.Sleep(100 * time.Millisecond)
continue
}
break
}
}
func (s *service) propagateEvents(round *domain.Round) {
lastEvent := round.Events()[len(round.Events())-1]
switch e := lastEvent.(type) {
case domain.RoundFinalizationStarted:
forfeitTxs := s.forfeitTxs.view()
s.eventsCh <- domain.RoundFinalizationStarted{
Id: e.Id,
CongestionTree: e.CongestionTree,
Connectors: e.Connectors,
PoolTx: e.PoolTx,
UnsignedForfeitTxs: forfeitTxs,
}
case domain.RoundFinalized, domain.RoundFailed:
s.eventsCh <- e
}
}
func (s *service) getNewVtxos(round *domain.Round) []domain.Vtxo {
leaves := round.CongestionTree.Leaves()
vtxos := make([]domain.Vtxo, 0)
for _, node := range leaves {
tx, _ := psetv2.NewPsetFromBase64(node.Tx)
for i, out := range tx.Outputs {
for _, p := range round.Payments {
var pubkey string
found := false
for _, r := range p.Receivers {
if r.IsOnchain() {
continue
}
buf, _ := hex.DecodeString(r.Pubkey)
pk, _ := secp256k1.ParsePubKey(buf)
script, _ := s.builder.GetVtxoScript(pk, s.pubkey)
if bytes.Equal(script, out.Script) {
found = true
pubkey = r.Pubkey
break
}
}
if found {
vtxos = append(vtxos, domain.Vtxo{
VtxoKey: domain.VtxoKey{Txid: node.Txid, VOut: uint32(i)},
Receiver: domain.Receiver{Pubkey: pubkey, Amount: out.Value},
PoolTx: round.Txid,
})
break
}
}
}
}
return vtxos
}
func (s *service) startWatchingVtxos(vtxos []domain.Vtxo) error {
scripts, err := s.extractVtxosScripts(vtxos)
if err != nil {
return err
}
return s.scanner.WatchScripts(context.Background(), scripts)
}
func (s *service) stopWatchingVtxos(vtxos []domain.Vtxo) {
scripts, err := s.extractVtxosScripts(vtxos)
if err != nil {
log.WithError(err).Warn("failed to extract scripts from vtxos")
return
}
for {
if err := s.scanner.UnwatchScripts(context.Background(), scripts); err != nil {
log.WithError(err).Warn("failed to stop watching vtxos, retrying in a moment...")
time.Sleep(100 * time.Millisecond)
continue
}
log.Debugf("stopped watching %d vtxos", len(vtxos))
break
}
}
func (s *service) restoreWatchingVtxos() error {
vtxos, err := s.repoManager.Vtxos().GetSpendableVtxos(
context.Background(), "",
)
if err != nil {
return err
}
if len(vtxos) <= 0 {
return nil
}
if err := s.startWatchingVtxos(vtxos); err != nil {
return err
}
log.Debugf("restored watching %d vtxos", len(vtxos))
return nil
}
func (s *service) extractVtxosScripts(vtxos []domain.Vtxo) ([]string, error) {
indexedScripts := make(map[string]struct{})
for _, vtxo := range vtxos {
buf, err := hex.DecodeString(vtxo.Pubkey)
if err != nil {
return nil, err
}
userPubkey, err := secp256k1.ParsePubKey(buf)
if err != nil {
return nil, err
}
script, err := s.builder.GetVtxoScript(userPubkey, s.pubkey)
if err != nil {
return nil, err
}
indexedScripts[hex.EncodeToString(script)] = struct{}{}
}
scripts := make([]string, 0, len(indexedScripts))
for script := range indexedScripts {
scripts = append(scripts, script)
}
return scripts, nil
}
func getSpentVtxos(payments map[string]domain.Payment) []domain.VtxoKey {
vtxos := make([]domain.VtxoKey, 0)
for _, p := range payments {
for _, vtxo := range p.Inputs {
if vtxo.VtxoKey == faucetVtxo {
continue
}
vtxos = append(vtxos, vtxo.VtxoKey)
}
}
return vtxos
}

View File

@@ -0,0 +1,579 @@
package application
import (
"context"
"encoding/hex"
"fmt"
"time"
"github.com/ark-network/ark/common/tree"
"github.com/ark-network/ark/internal/core/domain"
"github.com/ark-network/ark/internal/core/ports"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
log "github.com/sirupsen/logrus"
"github.com/vulpemventures/go-elements/psetv2"
)
// sweeper is an unexported service running while the main application service is started
// it is responsible for sweeping onchain shared outputs that expired
// it also handles delaying the sweep events in case some parts of the tree are broadcasted
// when a round is finalized, the main application service schedules a sweep event on the newly created congestion tree
type sweeper struct {
wallet ports.WalletService
repoManager ports.RepoManager
builder ports.TxBuilder
scheduler ports.SchedulerService
// cache of scheduled tasks, avoid scheduling the same sweep event multiple times
scheduledTasks map[string]struct{}
}
func newSweeper(
wallet ports.WalletService,
repoManager ports.RepoManager,
builder ports.TxBuilder,
scheduler ports.SchedulerService,
) *sweeper {
return &sweeper{
wallet,
repoManager,
builder,
scheduler,
make(map[string]struct{}),
}
}
func (s *sweeper) start() error {
s.scheduler.Start()
allRounds, err := s.repoManager.Rounds().GetSweepableRounds(context.Background())
if err != nil {
return err
}
for _, round := range allRounds {
task := s.createTask(round.Txid, round.CongestionTree)
task()
}
return nil
}
func (s *sweeper) stop() {
s.scheduler.Stop()
}
// removeTask update the cached map of scheduled tasks
func (s *sweeper) removeTask(treeRootTxid string) {
delete(s.scheduledTasks, treeRootTxid)
}
// schedule set up a task to be executed once at the given timestamp
func (s *sweeper) schedule(
expirationTimestamp int64, roundTxid string, congestionTree tree.CongestionTree,
) error {
root, err := congestionTree.Root()
if err != nil {
return err
}
if _, scheduled := s.scheduledTasks[root.Txid]; scheduled {
return nil
}
task := s.createTask(roundTxid, congestionTree)
fancyTime := time.Unix(expirationTimestamp, 0).Format("2006-01-02 15:04:05")
log.Debugf("scheduled sweep task at %s", fancyTime)
if err := s.scheduler.ScheduleTaskOnce(expirationTimestamp, task); err != nil {
return err
}
s.scheduledTasks[root.Txid] = struct{}{}
return nil
}
// createTask returns a function passed as handler in the scheduler
// it tries to craft a sweep tx containing the onchain outputs of the given congestion tree
// if some parts of the tree have been broadcasted in the meantine, it will schedule the next taskes for the remaining parts of the tree
func (s *sweeper) createTask(
roundTxid string, congestionTree tree.CongestionTree,
) func() {
return func() {
ctx := context.Background()
root, err := congestionTree.Root()
if err != nil {
log.WithError(err).Error("error while getting root node")
return
}
s.removeTask(root.Txid)
log.Debugf("sweeper: %s", root.Txid)
sweepInputs := make([]ports.SweepInput, 0)
vtxoKeys := make([]domain.VtxoKey, 0) // vtxos associated to the sweep inputs
// inspect the congestion tree to find onchain shared outputs
sharedOutputs, err := s.findSweepableOutputs(ctx, congestionTree)
if err != nil {
log.WithError(err).Error("error while inspecting congestion tree")
return
}
for expiredAt, inputs := range sharedOutputs {
// if the shared outputs are not expired, schedule a sweep task for it
if time.Unix(expiredAt, 0).After(time.Now()) {
subtrees, err := computeSubTrees(congestionTree, inputs)
if err != nil {
log.WithError(err).Error("error while computing subtrees")
continue
}
for _, subTree := range subtrees {
// mitigate the risk to get BIP68 non-final errors by scheduling the task 30 seconds after the expiration time
if err := s.schedule(int64(expiredAt), roundTxid, subTree); err != nil {
log.WithError(err).Error("error while scheduling sweep task")
continue
}
}
continue
}
// iterate over the expired shared outputs
for _, input := range inputs {
// sweepableVtxos related to the sweep input
sweepableVtxos := make([]domain.VtxoKey, 0)
// check if input is the vtxo itself
vtxos, _ := s.repoManager.Vtxos().GetVtxos(
ctx,
[]domain.VtxoKey{
{
Txid: input.InputArgs.Txid,
VOut: input.InputArgs.TxIndex,
},
},
)
if len(vtxos) > 0 {
if !vtxos[0].Swept && !vtxos[0].Redeemed {
sweepableVtxos = append(sweepableVtxos, vtxos[0].VtxoKey)
}
} else {
// if it's not a vtxo, find all the vtxos leaves reachable from that input
vtxosLeaves, err := congestionTree.FindLeaves(input.InputArgs.Txid, input.InputArgs.TxIndex)
if err != nil {
log.WithError(err).Error("error while finding vtxos leaves")
continue
}
for _, leaf := range vtxosLeaves {
pset, err := psetv2.NewPsetFromBase64(leaf.Tx)
if err != nil {
log.Error(fmt.Errorf("error while decoding pset: %w", err))
continue
}
vtxo, err := extractVtxoOutpoint(pset)
if err != nil {
log.Error(err)
continue
}
sweepableVtxos = append(sweepableVtxos, *vtxo)
}
if len(sweepableVtxos) <= 0 {
continue
}
firstVtxo, err := s.repoManager.Vtxos().GetVtxos(ctx, sweepableVtxos[:1])
if err != nil {
log.Error(fmt.Errorf("error while getting vtxo: %w", err))
sweepInputs = append(sweepInputs, input) // add the input anyway in order to try to sweep it
continue
}
if firstVtxo[0].Swept || firstVtxo[0].Redeemed {
// we assume that if the first vtxo is swept or redeemed, the shared output has been spent
// skip, the output is already swept or spent by a unilateral redeem
continue
}
}
if len(sweepableVtxos) > 0 {
vtxoKeys = append(vtxoKeys, sweepableVtxos...)
sweepInputs = append(sweepInputs, input)
}
}
}
if len(sweepInputs) > 0 {
// build the sweep transaction with all the expired non-swept shared outputs
sweepTx, err := s.builder.BuildSweepTx(s.wallet, sweepInputs)
if err != nil {
log.WithError(err).Error("error while building sweep tx")
return
}
err = nil
txid := ""
// retry until the tx is broadcasted or the error is not BIP68 final
for len(txid) == 0 && (err == nil || err == fmt.Errorf("non-BIP68-final")) {
if err != nil {
log.Debugln("sweep tx not BIP68 final, retrying in 5 seconds")
time.Sleep(5 * time.Second)
}
txid, err = s.wallet.BroadcastTransaction(ctx, sweepTx)
}
if err != nil {
log.WithError(err).Error("error while broadcasting sweep tx")
return
}
if len(txid) > 0 {
log.Debugln("sweep tx broadcasted:", txid)
vtxosRepository := s.repoManager.Vtxos()
// mark the vtxos as swept
if err := vtxosRepository.SweepVtxos(ctx, vtxoKeys); err != nil {
log.Error(fmt.Errorf("error while deleting vtxos: %w", err))
return
}
log.Debugf("%d vtxos swept", len(vtxoKeys))
roundVtxos, err := vtxosRepository.GetVtxosForRound(ctx, roundTxid)
if err != nil {
log.WithError(err).Error("error while getting vtxos for round")
return
}
allSwept := true
for _, vtxo := range roundVtxos {
allSwept = allSwept && vtxo.Swept
if !allSwept {
break
}
}
if allSwept {
// update the round
roundRepo := s.repoManager.Rounds()
round, err := roundRepo.GetRoundWithTxid(ctx, roundTxid)
if err != nil {
log.WithError(err).Error("error while getting round")
return
}
round.Sweep()
if err := roundRepo.AddOrUpdateRound(ctx, *round); err != nil {
log.WithError(err).Error("error while marking round as swept")
return
}
}
}
}
}
}
// onchainOutputs iterates over all the nodes' outputs in the congestion tree and checks their onchain state
// returns the sweepable outputs as ports.SweepInput mapped by their expiration time
func (s *sweeper) findSweepableOutputs(
ctx context.Context,
congestionTree tree.CongestionTree,
) (map[int64][]ports.SweepInput, error) {
sweepableOutputs := make(map[int64][]ports.SweepInput)
blocktimeCache := make(map[string]int64) // txid -> blocktime
nodesToCheck := congestionTree[0] // init with the root
for len(nodesToCheck) > 0 {
newNodesToCheck := make([]tree.Node, 0)
for _, node := range nodesToCheck {
isPublished, blocktime, err := s.wallet.IsTransactionPublished(ctx, node.Txid)
if err != nil {
return nil, err
}
var expirationTime int64
var sweepInputs []ports.SweepInput
if !isPublished {
if _, ok := blocktimeCache[node.ParentTxid]; !ok {
isPublished, blocktime, err := s.wallet.IsTransactionPublished(ctx, node.ParentTxid)
if !isPublished || err != nil {
return nil, fmt.Errorf("tx %s not found", node.Txid)
}
blocktimeCache[node.ParentTxid] = blocktime
}
expirationTime, sweepInputs, err = s.nodeToSweepInputs(blocktimeCache[node.ParentTxid], node)
if err != nil {
return nil, err
}
} else {
// cache the blocktime for future use
blocktimeCache[node.Txid] = int64(blocktime)
// if the tx is onchain, it means that the input is spent
// add the children to the nodes in order to check them during the next iteration
// We will return the error below, but are we going to schedule the tasks for the "children roots"?
if !node.Leaf {
children := congestionTree.Children(node.Txid)
newNodesToCheck = append(newNodesToCheck, children...)
continue
}
// if the node is a leaf, the vtxos outputs should added as onchain outputs if they are not swept yet
vtxoExpiration, sweepInput, err := s.leafToSweepInput(ctx, blocktime, node)
if err != nil {
return nil, err
}
if sweepInput != nil {
expirationTime = vtxoExpiration
sweepInputs = []ports.SweepInput{*sweepInput}
}
}
if _, ok := sweepableOutputs[expirationTime]; !ok {
sweepableOutputs[expirationTime] = make([]ports.SweepInput, 0)
}
sweepableOutputs[expirationTime] = append(sweepableOutputs[expirationTime], sweepInputs...)
}
nodesToCheck = newNodesToCheck
}
return sweepableOutputs, nil
}
func (s *sweeper) leafToSweepInput(ctx context.Context, txBlocktime int64, node tree.Node) (int64, *ports.SweepInput, error) {
pset, err := psetv2.NewPsetFromBase64(node.Tx)
if err != nil {
return -1, nil, err
}
vtxo, err := extractVtxoOutpoint(pset)
if err != nil {
return -1, nil, err
}
fromRepo, err := s.repoManager.Vtxos().GetVtxos(ctx, []domain.VtxoKey{*vtxo})
if err != nil {
return -1, nil, err
}
if len(fromRepo) == 0 {
return -1, nil, fmt.Errorf("vtxo not found")
}
if fromRepo[0].Swept {
return -1, nil, nil
}
if fromRepo[0].Redeemed {
return -1, nil, nil
}
// if the vtxo is not swept or redeemed, add it to the onchain outputs
pubKeyBytes, err := hex.DecodeString(fromRepo[0].Pubkey)
if err != nil {
return -1, nil, err
}
pubKey, err := secp256k1.ParsePubKey(pubKeyBytes)
if err != nil {
return -1, nil, err
}
sweepLeaf, lifetime, err := s.builder.GetLeafSweepClosure(node, pubKey)
if err != nil {
return -1, nil, err
}
sweepInput := ports.SweepInput{
InputArgs: psetv2.InputArgs{
Txid: vtxo.Txid,
TxIndex: vtxo.VOut,
},
SweepLeaf: *sweepLeaf,
Amount: fromRepo[0].Amount,
}
return txBlocktime + lifetime, &sweepInput, nil
}
func (s *sweeper) nodeToSweepInputs(parentBlocktime int64, node tree.Node) (int64, []ports.SweepInput, error) {
pset, err := psetv2.NewPsetFromBase64(node.Tx)
if err != nil {
return -1, nil, err
}
if len(pset.Inputs) != 1 {
return -1, nil, fmt.Errorf("invalid node pset, expect 1 input, got %d", len(pset.Inputs))
}
// if the tx is not onchain, it means that the input is an existing shared output
input := pset.Inputs[0]
txid := chainhash.Hash(input.PreviousTxid).String()
index := input.PreviousTxIndex
sweepLeaf, lifetime, err := extractSweepLeaf(input)
if err != nil {
return -1, nil, err
}
expirationTime := parentBlocktime + lifetime
amount := uint64(0)
for _, out := range pset.Outputs {
amount += out.Value
}
sweepInputs := []ports.SweepInput{
{
InputArgs: psetv2.InputArgs{
Txid: txid,
TxIndex: index,
},
SweepLeaf: *sweepLeaf,
Amount: amount,
},
}
return expirationTime, sweepInputs, nil
}
func computeSubTrees(congestionTree tree.CongestionTree, inputs []ports.SweepInput) ([]tree.CongestionTree, error) {
subTrees := make(map[string]tree.CongestionTree, 0)
// for each sweepable input, create a sub congestion tree
// it allows to skip the part of the tree that has been broadcasted in the next task
for _, input := range inputs {
subTree, err := computeSubTree(congestionTree, input.InputArgs.Txid)
if err != nil {
log.WithError(err).Error("error while finding sub tree")
continue
}
root, err := subTree.Root()
if err != nil {
log.WithError(err).Error("error while getting root node")
continue
}
subTrees[root.Txid] = subTree
}
// filter out the sub trees, remove the ones that are included in others
filteredSubTrees := make([]tree.CongestionTree, 0)
for i, subTree := range subTrees {
notIncludedInOtherTrees := true
for j, otherSubTree := range subTrees {
if i == j {
continue
}
contains, err := containsTree(otherSubTree, subTree)
if err != nil {
log.WithError(err).Error("error while checking if a tree contains another")
continue
}
if contains {
notIncludedInOtherTrees = false
break
}
}
if notIncludedInOtherTrees {
filteredSubTrees = append(filteredSubTrees, subTree)
}
}
return filteredSubTrees, nil
}
func computeSubTree(congestionTree tree.CongestionTree, newRoot string) (tree.CongestionTree, error) {
for _, level := range congestionTree {
for _, node := range level {
if node.Txid == newRoot || node.ParentTxid == newRoot {
newTree := make(tree.CongestionTree, 0)
newTree = append(newTree, []tree.Node{node})
children := congestionTree.Children(node.Txid)
for len(children) > 0 {
newTree = append(newTree, children)
newChildren := make([]tree.Node, 0)
for _, child := range children {
newChildren = append(newChildren, congestionTree.Children(child.Txid)...)
}
children = newChildren
}
return newTree, nil
}
}
}
return nil, fmt.Errorf("failed to create subtree, new root not found")
}
func containsTree(tr0 tree.CongestionTree, tr1 tree.CongestionTree) (bool, error) {
tr1Root, err := tr1.Root()
if err != nil {
return false, err
}
for _, level := range tr0 {
for _, node := range level {
if node.Txid == tr1Root.Txid {
return true, nil
}
}
}
return false, nil
}
// given a congestion tree input, searches and returns the sweep leaf and its lifetime in seconds
func extractSweepLeaf(input psetv2.Input) (sweepLeaf *psetv2.TapLeafScript, lifetime int64, err error) {
for _, leaf := range input.TapLeafScript {
isSweep, _, seconds, err := tree.DecodeSweepScript(leaf.Script)
if err != nil {
return nil, 0, err
}
if isSweep {
lifetime = int64(seconds)
sweepLeaf = &leaf
break
}
}
if sweepLeaf == nil {
return nil, 0, fmt.Errorf("sweep leaf not found")
}
return sweepLeaf, lifetime, nil
}
// assuming the pset is a leaf in the congestion tree, returns the vtxos outputs
func extractVtxoOutpoint(pset *psetv2.Pset) (*domain.VtxoKey, error) {
if len(pset.Outputs) != 2 {
return nil, fmt.Errorf("invalid leaf pset, expect 2 outputs, got %d", len(pset.Outputs))
}
utx, err := pset.UnsignedTx()
if err != nil {
return nil, err
}
return &domain.VtxoKey{
Txid: utx.TxHash().String(),
VOut: 0,
}, nil
}

View File

@@ -0,0 +1,254 @@
package application
import (
"bytes"
"encoding/hex"
"fmt"
"sort"
"sync"
"time"
"github.com/ark-network/ark/common"
"github.com/ark-network/ark/internal/core/domain"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/vulpemventures/go-elements/psetv2"
)
type timedPayment struct {
domain.Payment
timestamp time.Time
pingTimestamp time.Time
}
type paymentsMap struct {
lock *sync.RWMutex
payments map[string]*timedPayment
}
func newPaymentsMap(payments []domain.Payment) *paymentsMap {
paymentsById := make(map[string]*timedPayment)
for _, p := range payments {
paymentsById[p.Id] = &timedPayment{p, time.Now(), time.Time{}}
}
lock := &sync.RWMutex{}
return &paymentsMap{lock, paymentsById}
}
func (m *paymentsMap) len() int64 {
m.lock.RLock()
defer m.lock.RUnlock()
count := int64(0)
for _, p := range m.payments {
if len(p.Receivers) > 0 {
count++
}
}
return count
}
func (m *paymentsMap) push(payment domain.Payment) error {
m.lock.Lock()
defer m.lock.Unlock()
if _, ok := m.payments[payment.Id]; ok {
return fmt.Errorf("duplicated inputs")
}
m.payments[payment.Id] = &timedPayment{payment, time.Now(), time.Time{}}
return nil
}
func (m *paymentsMap) pop(num int64) []domain.Payment {
m.lock.Lock()
defer m.lock.Unlock()
paymentsByTime := make([]timedPayment, 0, len(m.payments))
for _, p := range m.payments {
// Skip payments without registered receivers.
if len(p.Receivers) <= 0 {
continue
}
// Skip payments for which users didn't notify to be online in the last minute.
if p.pingTimestamp.IsZero() || time.Since(p.pingTimestamp).Minutes() > 1 {
continue
}
paymentsByTime = append(paymentsByTime, *p)
}
sort.SliceStable(paymentsByTime, func(i, j int) bool {
return paymentsByTime[i].timestamp.Before(paymentsByTime[j].timestamp)
})
if num < 0 || num > int64(len(paymentsByTime)) {
num = int64(len(paymentsByTime))
}
payments := make([]domain.Payment, 0, num)
for _, p := range paymentsByTime[:num] {
payments = append(payments, p.Payment)
delete(m.payments, p.Id)
}
return payments
}
func (m *paymentsMap) update(payment domain.Payment) error {
m.lock.Lock()
defer m.lock.Unlock()
p, ok := m.payments[payment.Id]
if !ok {
return fmt.Errorf("payment %s not found", payment.Id)
}
p.Payment = payment
return nil
}
func (m *paymentsMap) updatePingTimestamp(id string) error {
m.lock.Lock()
defer m.lock.Unlock()
payment, ok := m.payments[id]
if !ok {
return fmt.Errorf("payment %s not found", id)
}
payment.pingTimestamp = time.Now()
return nil
}
func (m *paymentsMap) view(id string) (*domain.Payment, bool) {
m.lock.RLock()
defer m.lock.RUnlock()
payment, ok := m.payments[id]
if !ok {
return nil, false
}
return &domain.Payment{
Id: payment.Id,
Inputs: payment.Inputs,
Receivers: payment.Receivers,
}, true
}
type signedTx struct {
tx string
signed bool
}
type forfeitTxsMap struct {
lock *sync.RWMutex
forfeitTxs map[string]*signedTx
genesisBlockHash *chainhash.Hash
}
func newForfeitTxsMap(genesisBlockHash *chainhash.Hash) *forfeitTxsMap {
return &forfeitTxsMap{&sync.RWMutex{}, make(map[string]*signedTx), genesisBlockHash}
}
func (m *forfeitTxsMap) push(txs []string) {
m.lock.Lock()
defer m.lock.Unlock()
faucetTxID, _ := hex.DecodeString(faucetVtxo.Txid)
for _, tx := range txs {
ptx, _ := psetv2.NewPsetFromBase64(tx)
utx, _ := ptx.UnsignedTx()
signed := false
// find the faucet vtxos, and mark them as signed
for _, input := range ptx.Inputs {
if bytes.Equal(input.PreviousTxid, faucetTxID) {
signed = true
break
}
}
m.forfeitTxs[utx.TxHash().String()] = &signedTx{tx, signed}
}
}
func (m *forfeitTxsMap) sign(txs []string) error {
m.lock.Lock()
defer m.lock.Unlock()
for _, tx := range txs {
ptx, _ := psetv2.NewPsetFromBase64(tx)
utx, _ := ptx.UnsignedTx()
txid := utx.TxHash().String()
if _, ok := m.forfeitTxs[txid]; ok {
for index, input := range ptx.Inputs {
if len(input.TapScriptSig) > 0 {
for _, tapScriptSig := range input.TapScriptSig {
leafHash, err := chainhash.NewHash(tapScriptSig.LeafHash)
if err != nil {
return err
}
preimage, err := common.TaprootPreimage(
m.genesisBlockHash,
ptx,
index,
leafHash,
)
if err != nil {
return err
}
sig, err := schnorr.ParseSignature(tapScriptSig.Signature)
if err != nil {
return err
}
pubkey, err := schnorr.ParsePubKey(tapScriptSig.PubKey)
if err != nil {
return err
}
if sig.Verify(preimage, pubkey) {
m.forfeitTxs[txid].signed = true
} else {
return fmt.Errorf("invalid signature")
}
}
}
}
}
}
return nil
}
func (m *forfeitTxsMap) pop() (signed, unsigned []string) {
m.lock.Lock()
defer m.lock.Unlock()
for _, t := range m.forfeitTxs {
if t.signed {
signed = append(signed, t.tx)
} else {
unsigned = append(unsigned, t.tx)
}
}
m.forfeitTxs = make(map[string]*signedTx)
return signed, unsigned
}
func (m *forfeitTxsMap) view() []string {
m.lock.RLock()
defer m.lock.RUnlock()
txs := make([]string, 0, len(m.forfeitTxs))
for _, tx := range m.forfeitTxs {
txs = append(txs, tx.tx)
}
return txs
}

View File

@@ -0,0 +1,44 @@
package domain
import "github.com/ark-network/ark/common/tree"
type RoundEvent interface {
isEvent()
}
func (r RoundStarted) isEvent() {}
func (r RoundFinalizationStarted) isEvent() {}
func (r RoundFinalized) isEvent() {}
func (r RoundFailed) isEvent() {}
func (r PaymentsRegistered) isEvent() {}
type RoundStarted struct {
Id string
Timestamp int64
}
type RoundFinalizationStarted struct {
Id string
CongestionTree tree.CongestionTree
Connectors []string
UnsignedForfeitTxs []string
PoolTx string
}
type RoundFinalized struct {
Id string
Txid string
ForfeitTxs []string
Timestamp int64
}
type RoundFailed struct {
Id string
Err string
Timestamp int64
}
type PaymentsRegistered struct {
Id string
Payments []Payment
}

View File

@@ -0,0 +1,129 @@
package domain
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"hash"
"github.com/google/uuid"
)
const dustAmount = 450
type Payment struct {
Id string
Inputs []Vtxo
Receivers []Receiver
}
func NewPayment(inputs []Vtxo) (*Payment, error) {
p := &Payment{
Id: uuid.New().String(),
Inputs: inputs,
}
if err := p.validate(true); err != nil {
return nil, err
}
return p, nil
}
func (p *Payment) AddReceivers(receivers []Receiver) (err error) {
if p.Receivers == nil {
p.Receivers = make([]Receiver, 0)
}
p.Receivers = append(p.Receivers, receivers...)
defer func() {
if err != nil {
p.Receivers = p.Receivers[:len(p.Receivers)-len(receivers)]
}
}()
err = p.validate(false)
return
}
func (p Payment) TotalInputAmount() uint64 {
tot := uint64(0)
for _, in := range p.Inputs {
tot += in.Amount
}
return tot
}
func (p Payment) TotalOutputAmount() uint64 {
tot := uint64(0)
for _, r := range p.Receivers {
tot += r.Amount
}
return tot
}
func (p Payment) validate(ignoreOuts bool) error {
if len(p.Id) <= 0 {
return fmt.Errorf("missing id")
}
if len(p.Inputs) <= 0 {
return fmt.Errorf("missing inputs")
}
if ignoreOuts {
return nil
}
if len(p.Receivers) <= 0 {
return fmt.Errorf("missing outputs")
}
// Check that input and output and output amounts match.
inAmount := p.TotalInputAmount()
outAmount := uint64(0)
for _, r := range p.Receivers {
if len(r.OnchainAddress) <= 0 && len(r.Pubkey) <= 0 {
return fmt.Errorf("missing receiver destination")
}
if r.Amount < dustAmount {
return fmt.Errorf("receiver amount must be greater than dust")
}
outAmount += r.Amount
}
if inAmount != outAmount {
return fmt.Errorf("input and output amounts mismatch")
}
return nil
}
type VtxoKey struct {
Txid string
VOut uint32
}
func (k VtxoKey) Hash() string {
calcHash := func(buf []byte, hasher hash.Hash) []byte {
_, _ = hasher.Write(buf)
return hasher.Sum(nil)
}
hash160 := func(buf []byte) []byte {
return calcHash(calcHash(buf, sha256.New()), sha256.New())
}
buf, _ := hex.DecodeString(k.Txid)
buf = append(buf, byte(k.VOut))
return hex.EncodeToString(hash160(buf))
}
type Receiver struct {
Pubkey string
Amount uint64
OnchainAddress string
}
func (r Receiver) IsOnchain() bool {
return len(r.OnchainAddress) > 0
}
type Vtxo struct {
VtxoKey
Receiver
PoolTx string
Spent bool
Redeemed bool
Swept bool
}

View File

@@ -0,0 +1,111 @@
package domain_test
import (
"testing"
"github.com/ark-network/ark/internal/core/domain"
"github.com/stretchr/testify/require"
)
var inputs = []domain.Vtxo{
{
VtxoKey: domain.VtxoKey{
Txid: "0000000000000000000000000000000000000000000000000000000000000000",
VOut: 0,
},
Receiver: domain.Receiver{
Pubkey: "030000000000000000000000000000000000000000000000000000000000000001",
Amount: 1000,
},
},
}
func TestPayment(t *testing.T) {
t.Run("new_payment", func(t *testing.T) {
t.Run("vaild", func(t *testing.T) {
payment, err := domain.NewPayment(inputs)
require.NoError(t, err)
require.NotNil(t, payment)
require.NotEmpty(t, payment.Id)
require.Exactly(t, inputs, payment.Inputs)
require.Empty(t, payment.Receivers)
})
t.Run("invaild", func(t *testing.T) {
fixtures := []struct {
inputs []domain.Vtxo
expectedErr string
}{
{
inputs: nil,
expectedErr: "missing inputs",
},
}
for _, f := range fixtures {
payment, err := domain.NewPayment(f.inputs)
require.EqualError(t, err, f.expectedErr)
require.Nil(t, payment)
}
})
})
t.Run("add_receivers", func(t *testing.T) {
t.Run("valid", func(t *testing.T) {
payment, err := domain.NewPayment(inputs)
require.NoError(t, err)
require.NotNil(t, payment)
err = payment.AddReceivers([]domain.Receiver{
{
Pubkey: "030000000000000000000000000000000000000000000000000000000000000001",
Amount: 450,
},
{
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
Amount: 550,
},
})
require.NoError(t, err)
})
t.Run("invalid", func(t *testing.T) {
fixtures := []struct {
receivers []domain.Receiver
expectedErr string
}{
{
receivers: nil,
expectedErr: "missing outputs",
},
{
receivers: []domain.Receiver{
{
Pubkey: "030000000000000000000000000000000000000000000000000000000000000001",
Amount: 400,
},
},
expectedErr: "receiver amount must be greater than dust",
},
{
receivers: []domain.Receiver{
{
Pubkey: "030000000000000000000000000000000000000000000000000000000000000001",
Amount: 600,
},
},
expectedErr: "input and output amounts mismatch",
},
}
payment, err := domain.NewPayment(inputs)
require.NoError(t, err)
require.NotNil(t, payment)
for _, f := range fixtures {
err := payment.AddReceivers(f.receivers)
require.EqualError(t, err, f.expectedErr)
}
})
})
}

View File

@@ -0,0 +1,253 @@
package domain
import (
"fmt"
"time"
"github.com/ark-network/ark/common/tree"
"github.com/google/uuid"
)
const (
UndefinedStage RoundStage = iota
RegistrationStage
FinalizationStage
)
type RoundStage int
func (s RoundStage) String() string {
switch s {
case RegistrationStage:
return "REGISTRATION_STAGE"
case FinalizationStage:
return "FINALIZATION_STAGE"
default:
return "UNDEFINED_STAGE"
}
}
type Stage struct {
Code RoundStage
Ended bool
Failed bool
}
type Round struct {
Id string
StartingTimestamp int64
EndingTimestamp int64
Stage Stage
Payments map[string]Payment
Txid string
UnsignedTx string
ForfeitTxs []string
CongestionTree tree.CongestionTree
Connectors []string
DustAmount uint64
Version uint
Swept bool // true if all the vtxos are vtxo.Swept
changes []RoundEvent
}
func NewRound(dustAmount uint64) *Round {
return &Round{
Id: uuid.New().String(),
DustAmount: dustAmount,
Payments: make(map[string]Payment),
changes: make([]RoundEvent, 0),
}
}
func NewRoundFromEvents(events []RoundEvent) *Round {
r := &Round{}
for _, event := range events {
r.On(event, true)
}
r.changes = append([]RoundEvent{}, events...)
return r
}
func (r *Round) Events() []RoundEvent {
return r.changes
}
func (r *Round) On(event RoundEvent, replayed bool) {
switch e := event.(type) {
case RoundStarted:
r.Stage.Code = RegistrationStage
r.Id = e.Id
r.StartingTimestamp = e.Timestamp
case RoundFinalizationStarted:
r.Stage.Code = FinalizationStage
r.CongestionTree = e.CongestionTree
r.Connectors = append([]string{}, e.Connectors...)
r.UnsignedTx = e.PoolTx
case RoundFinalized:
r.Stage.Ended = true
r.Txid = e.Txid
r.ForfeitTxs = append([]string{}, e.ForfeitTxs...)
r.EndingTimestamp = e.Timestamp
case RoundFailed:
r.Stage.Failed = true
r.EndingTimestamp = e.Timestamp
case PaymentsRegistered:
if r.Payments == nil {
r.Payments = make(map[string]Payment)
}
for _, p := range e.Payments {
r.Payments[p.Id] = p
}
}
if replayed {
r.Version++
}
}
func (r *Round) StartRegistration() ([]RoundEvent, error) {
empty := Stage{}
if r.Stage != empty {
return nil, fmt.Errorf("not in a valid stage to start payment registration")
}
event := RoundStarted{
Id: r.Id,
Timestamp: time.Now().Unix(),
}
r.raise(event)
return []RoundEvent{event}, nil
}
func (r *Round) RegisterPayments(payments []Payment) ([]RoundEvent, error) {
if r.Stage.Code != RegistrationStage || r.IsFailed() {
return nil, fmt.Errorf("not in a valid stage to register payments")
}
if len(payments) <= 0 {
return nil, fmt.Errorf("missing payments to register")
}
for _, p := range payments {
if err := p.validate(false); err != nil {
return nil, err
}
}
event := PaymentsRegistered{
Id: r.Id,
Payments: payments,
}
r.raise(event)
return []RoundEvent{event}, nil
}
func (r *Round) StartFinalization(connectors []string, congestionTree tree.CongestionTree, poolTx string) ([]RoundEvent, error) {
if len(connectors) <= 0 {
return nil, fmt.Errorf("missing list of connectors")
}
if len(congestionTree) <= 0 {
return nil, fmt.Errorf("missing congestion tree")
}
if len(poolTx) <= 0 {
return nil, fmt.Errorf("missing unsigned pool tx")
}
if r.Stage.Code != RegistrationStage || r.IsFailed() {
return nil, fmt.Errorf("not in a valid stage to start payment finalization")
}
if len(r.Payments) <= 0 {
return nil, fmt.Errorf("no payments registered")
}
event := RoundFinalizationStarted{
Id: r.Id,
CongestionTree: congestionTree,
Connectors: connectors,
PoolTx: poolTx,
}
r.raise(event)
return []RoundEvent{event}, nil
}
func (r *Round) EndFinalization(forfeitTxs []string, txid string) ([]RoundEvent, error) {
if len(forfeitTxs) <= 0 {
return nil, fmt.Errorf("missing list of signed forfeit txs")
}
if len(txid) <= 0 {
return nil, fmt.Errorf("missing pool txid")
}
if r.Stage.Code != FinalizationStage || r.IsFailed() {
return nil, fmt.Errorf("not in a valid stage to end payment finalization")
}
if r.Stage.Ended {
return nil, fmt.Errorf("round already finalized")
}
event := RoundFinalized{
Id: r.Id,
Txid: txid,
ForfeitTxs: forfeitTxs,
Timestamp: time.Now().Unix(),
}
r.raise(event)
return []RoundEvent{event}, nil
}
func (r *Round) Fail(err error) []RoundEvent {
if r.Stage.Failed {
return nil
}
event := RoundFailed{
Id: r.Id,
Err: err.Error(),
Timestamp: time.Now().Unix(),
}
r.raise(event)
return []RoundEvent{event}
}
func (r *Round) IsStarted() bool {
empty := Stage{}
return !r.IsFailed() && !r.IsEnded() && r.Stage != empty
}
func (r *Round) IsEnded() bool {
return !r.IsFailed() && r.Stage.Code == FinalizationStage && r.Stage.Ended
}
func (r *Round) IsFailed() bool {
return r.Stage.Failed
}
func (r *Round) TotalInputAmount() uint64 {
totInputs := 0
for _, p := range r.Payments {
totInputs += len(p.Inputs)
}
return uint64(totInputs * int(r.DustAmount))
}
func (r *Round) TotalOutputAmount() uint64 {
tot := uint64(0)
for _, p := range r.Payments {
tot += p.TotalOutputAmount()
}
return tot
}
func (r *Round) Sweep() {
r.Swept = true
}
func (r *Round) raise(event RoundEvent) {
if r.changes == nil {
r.changes = make([]RoundEvent, 0)
}
r.changes = append(r.changes, event)
r.On(event, false)
}

View File

@@ -0,0 +1,28 @@
package domain
import (
"context"
)
type RoundEventRepository interface {
Save(ctx context.Context, id string, events ...RoundEvent) error
Load(ctx context.Context, id string) (*Round, error)
}
type RoundRepository interface {
AddOrUpdateRound(ctx context.Context, round Round) error
GetCurrentRound(ctx context.Context) (*Round, error)
GetRoundWithId(ctx context.Context, id string) (*Round, error)
GetRoundWithTxid(ctx context.Context, txid string) (*Round, error)
GetSweepableRounds(ctx context.Context) ([]Round, error)
}
type VtxoRepository interface {
AddVtxos(ctx context.Context, vtxos []Vtxo) error
SpendVtxos(ctx context.Context, vtxos []VtxoKey) error
RedeemVtxos(ctx context.Context, vtxos []VtxoKey) ([]Vtxo, error)
GetVtxos(ctx context.Context, vtxos []VtxoKey) ([]Vtxo, error)
GetVtxosForRound(ctx context.Context, txid string) ([]Vtxo, error)
SweepVtxos(ctx context.Context, vtxos []VtxoKey) error
GetSpendableVtxos(ctx context.Context, pubkey string) ([]Vtxo, error)
}

View File

@@ -0,0 +1,576 @@
package domain_test
import (
"fmt"
"testing"
"github.com/ark-network/ark/common/tree"
"github.com/ark-network/ark/internal/core/domain"
"github.com/stretchr/testify/require"
)
var (
dustAmount = uint64(450)
payments = []domain.Payment{
{
Id: "0",
Inputs: []domain.Vtxo{{
VtxoKey: domain.VtxoKey{
Txid: txid,
VOut: 0,
},
Receiver: domain.Receiver{
Pubkey: pubkey,
Amount: 2000,
},
}},
Receivers: []domain.Receiver{
{
Pubkey: pubkey,
Amount: 700,
},
{
Pubkey: pubkey,
Amount: 700,
},
{
Pubkey: pubkey,
Amount: 600,
},
},
},
{
Id: "1",
Inputs: []domain.Vtxo{
{
VtxoKey: domain.VtxoKey{
Txid: txid,
VOut: 0,
},
Receiver: domain.Receiver{
Pubkey: pubkey,
Amount: 1000,
},
},
{
VtxoKey: domain.VtxoKey{
Txid: txid,
VOut: 0,
},
Receiver: domain.Receiver{
Pubkey: pubkey,
Amount: 1000,
},
},
},
Receivers: []domain.Receiver{{
Pubkey: pubkey,
Amount: 2000,
}},
},
}
emptyPtx = "cHNldP8BAgQCAAAAAQQBAAEFAQABBgEDAfsEAgAAAAA="
emptyTx = "0200000000000000000000"
txid = "0000000000000000000000000000000000000000000000000000000000000000"
pubkey = "030000000000000000000000000000000000000000000000000000000000000001"
congestionTree = tree.CongestionTree{
{
{
Txid: txid,
Tx: emptyPtx,
ParentTxid: txid,
},
},
{
{
Txid: txid,
Tx: emptyPtx,
ParentTxid: txid,
},
{
Txid: txid,
Tx: emptyPtx,
ParentTxid: txid,
},
},
{
{
Txid: txid,
Tx: emptyPtx,
ParentTxid: txid,
},
{
Txid: txid,
Tx: emptyPtx,
ParentTxid: txid,
},
{
Txid: txid,
Tx: emptyPtx,
ParentTxid: txid,
},
{
Txid: txid,
Tx: emptyPtx,
ParentTxid: txid,
},
},
}
connectors = []string{emptyPtx, emptyPtx, emptyPtx}
forfeitTxs = []string{emptyPtx, emptyPtx, emptyPtx, emptyPtx, emptyPtx, emptyPtx, emptyPtx, emptyPtx, emptyPtx}
poolTx = emptyTx
)
func TestRound(t *testing.T) {
testStartRegistration(t)
testRegisterPayments(t)
testStartFinalization(t)
testEndFinalization(t)
testFail(t)
}
func testStartRegistration(t *testing.T) {
t.Run("start_registration", func(t *testing.T) {
t.Run("valid", func(t *testing.T) {
round := domain.NewRound(dustAmount)
require.NotNil(t, round)
require.NotEmpty(t, round.Id)
require.Empty(t, round.Events())
require.False(t, round.IsStarted())
require.False(t, round.IsEnded())
require.False(t, round.IsFailed())
events, err := round.StartRegistration()
require.NoError(t, err)
require.Len(t, events, 1)
require.True(t, round.IsStarted())
require.False(t, round.IsEnded())
require.False(t, round.IsFailed())
event, ok := events[0].(domain.RoundStarted)
require.True(t, ok)
require.Equal(t, round.Id, event.Id)
require.Equal(t, round.StartingTimestamp, event.Timestamp)
})
t.Run("invalid", func(t *testing.T) {
fixtures := []struct {
round *domain.Round
expectedErr string
}{
{
round: &domain.Round{
Id: "id",
Stage: domain.Stage{
Code: domain.UndefinedStage,
Failed: true,
},
},
expectedErr: "not in a valid stage to start payment registration",
},
{
round: &domain.Round{
Id: "id",
Stage: domain.Stage{
Code: domain.RegistrationStage,
},
},
expectedErr: "not in a valid stage to start payment registration",
},
{
round: &domain.Round{
Id: "id",
Stage: domain.Stage{
Code: domain.FinalizationStage,
},
},
expectedErr: "not in a valid stage to start payment registration",
},
}
for _, f := range fixtures {
events, err := f.round.StartRegistration()
require.EqualError(t, err, f.expectedErr)
require.Empty(t, events)
}
})
})
}
func testRegisterPayments(t *testing.T) {
t.Run("register_payments", func(t *testing.T) {
t.Run("valid", func(t *testing.T) {
round := domain.NewRound(dustAmount)
events, err := round.StartRegistration()
require.NoError(t, err)
require.NotEmpty(t, events)
events, err = round.RegisterPayments(payments)
require.NoError(t, err)
require.Len(t, events, 1)
require.Condition(t, func() bool {
for _, payment := range payments {
_, ok := round.Payments[payment.Id]
if !ok {
return false
}
}
return true
})
event, ok := events[0].(domain.PaymentsRegistered)
require.True(t, ok)
require.Equal(t, round.Id, event.Id)
require.Equal(t, payments, event.Payments)
})
t.Run("invalid", func(t *testing.T) {
fixtures := []struct {
round *domain.Round
payments []domain.Payment
expectedErr string
}{
{
round: &domain.Round{
Id: "id",
Stage: domain.Stage{},
},
payments: payments,
expectedErr: "not in a valid stage to register payments",
},
{
round: &domain.Round{
Id: "id",
Stage: domain.Stage{
Code: domain.RegistrationStage,
Failed: true,
},
},
payments: payments,
expectedErr: "not in a valid stage to register payments",
},
{
round: &domain.Round{
Id: "id",
Stage: domain.Stage{
Code: domain.FinalizationStage,
},
},
payments: payments,
expectedErr: "not in a valid stage to register payments",
},
{
round: &domain.Round{
Id: "id",
Stage: domain.Stage{
Code: domain.RegistrationStage,
},
},
payments: nil,
expectedErr: "missing payments to register",
},
}
for _, f := range fixtures {
events, err := f.round.RegisterPayments(f.payments)
require.EqualError(t, err, f.expectedErr)
require.Empty(t, events)
}
})
})
}
func testStartFinalization(t *testing.T) {
t.Run("start_finalization", func(t *testing.T) {
t.Run("valid", func(t *testing.T) {
round := domain.NewRound(dustAmount)
events, err := round.StartRegistration()
require.NoError(t, err)
require.NotEmpty(t, events)
events, err = round.RegisterPayments(payments)
require.NoError(t, err)
require.NotEmpty(t, events)
events, err = round.StartFinalization(connectors, congestionTree, poolTx)
require.NoError(t, err)
require.Len(t, events, 1)
require.True(t, round.IsStarted())
require.False(t, round.IsEnded())
require.False(t, round.IsFailed())
event, ok := events[0].(domain.RoundFinalizationStarted)
require.True(t, ok)
require.Equal(t, round.Id, event.Id)
require.Exactly(t, connectors, event.Connectors)
require.Exactly(t, congestionTree, event.CongestionTree)
require.Exactly(t, poolTx, event.PoolTx)
})
t.Run("invalid", func(t *testing.T) {
paymentsById := map[string]domain.Payment{}
for _, p := range payments {
paymentsById[p.Id] = p
}
fixtures := []struct {
round *domain.Round
connectors []string
tree tree.CongestionTree
poolTx string
expectedErr string
}{
{
round: &domain.Round{
Id: "0",
Stage: domain.Stage{
Code: domain.RegistrationStage,
},
Payments: paymentsById,
},
connectors: nil,
tree: congestionTree,
poolTx: poolTx,
expectedErr: "missing list of connectors",
},
{
round: &domain.Round{
Id: "0",
Stage: domain.Stage{
Code: domain.RegistrationStage,
},
Payments: paymentsById,
},
connectors: connectors,
tree: nil,
poolTx: poolTx,
expectedErr: "missing congestion tree",
},
{
round: &domain.Round{
Id: "0",
Stage: domain.Stage{
Code: domain.RegistrationStage,
},
Payments: paymentsById,
},
connectors: connectors,
tree: congestionTree,
poolTx: "",
expectedErr: "missing unsigned pool tx",
},
{
round: &domain.Round{
Id: "0",
Stage: domain.Stage{
Code: domain.RegistrationStage,
},
Payments: nil,
},
connectors: connectors,
tree: congestionTree,
poolTx: poolTx,
expectedErr: "no payments registered",
},
{
round: &domain.Round{
Id: "0",
Stage: domain.Stage{
Code: domain.UndefinedStage,
},
Payments: paymentsById,
},
connectors: connectors,
tree: congestionTree,
poolTx: poolTx,
expectedErr: "not in a valid stage to start payment finalization",
},
{
round: &domain.Round{
Id: "0",
Stage: domain.Stage{
Code: domain.RegistrationStage,
Failed: true,
},
Payments: paymentsById,
},
connectors: connectors,
tree: congestionTree,
poolTx: poolTx,
expectedErr: "not in a valid stage to start payment finalization",
},
{
round: &domain.Round{
Id: "0",
Stage: domain.Stage{
Code: domain.FinalizationStage,
},
Payments: paymentsById,
},
connectors: connectors,
tree: congestionTree,
poolTx: poolTx,
expectedErr: "not in a valid stage to start payment finalization",
},
}
for _, f := range fixtures {
events, err := f.round.StartFinalization(f.connectors, f.tree, f.poolTx)
require.EqualError(t, err, f.expectedErr)
require.Empty(t, events)
}
})
})
}
func testEndFinalization(t *testing.T) {
t.Run("end_registration", func(t *testing.T) {
t.Run("valid", func(t *testing.T) {
round := domain.NewRound(dustAmount)
events, err := round.StartRegistration()
require.NoError(t, err)
require.NotEmpty(t, events)
events, err = round.RegisterPayments(payments)
require.NoError(t, err)
require.NotEmpty(t, events)
events, err = round.StartFinalization(connectors, congestionTree, poolTx)
require.NoError(t, err)
require.NotEmpty(t, events)
events, err = round.EndFinalization(forfeitTxs, txid)
require.NoError(t, err)
require.Len(t, events, 1)
require.False(t, round.IsStarted())
require.True(t, round.IsEnded())
require.False(t, round.IsFailed())
event, ok := events[0].(domain.RoundFinalized)
require.True(t, ok)
require.Equal(t, round.Id, event.Id)
require.Exactly(t, txid, event.Txid)
require.Exactly(t, forfeitTxs, event.ForfeitTxs)
require.Exactly(t, round.EndingTimestamp, event.Timestamp)
})
t.Run("invalid", func(t *testing.T) {
paymentsById := map[string]domain.Payment{}
for _, p := range payments {
paymentsById[p.Id] = p
}
fixtures := []struct {
round *domain.Round
forfeitTxs []string
txid string
expectedErr string
}{
{
round: &domain.Round{
Id: "0",
Stage: domain.Stage{
Code: domain.FinalizationStage,
},
},
forfeitTxs: nil,
txid: txid,
expectedErr: "missing list of signed forfeit txs",
},
{
round: &domain.Round{
Id: "0",
Stage: domain.Stage{
Code: domain.FinalizationStage,
},
},
forfeitTxs: forfeitTxs,
txid: "",
expectedErr: "missing pool txid",
},
{
round: &domain.Round{
Id: "0",
},
forfeitTxs: forfeitTxs,
txid: txid,
expectedErr: "not in a valid stage to end payment finalization",
},
{
round: &domain.Round{
Id: "0",
Stage: domain.Stage{
Code: domain.RegistrationStage,
},
},
forfeitTxs: forfeitTxs,
txid: txid,
expectedErr: "not in a valid stage to end payment finalization",
},
{
round: &domain.Round{
Id: "0",
Stage: domain.Stage{
Code: domain.FinalizationStage,
Failed: true,
},
},
forfeitTxs: []string{emptyPtx, emptyPtx, emptyPtx, emptyPtx},
txid: txid,
expectedErr: "not in a valid stage to end payment finalization",
},
{
round: &domain.Round{
Id: "0",
Stage: domain.Stage{
Code: domain.FinalizationStage,
Ended: true,
},
},
forfeitTxs: []string{emptyPtx, emptyPtx, emptyPtx, emptyPtx},
txid: txid,
expectedErr: "round already finalized",
},
}
for _, f := range fixtures {
events, err := f.round.EndFinalization(f.forfeitTxs, f.txid)
require.EqualError(t, err, f.expectedErr)
require.Empty(t, events)
}
})
})
}
func testFail(t *testing.T) {
t.Run("fail", func(t *testing.T) {
t.Run("valid", func(t *testing.T) {
round := domain.NewRound(dustAmount)
events, err := round.StartRegistration()
require.NoError(t, err)
require.NotEmpty(t, events)
events, err = round.RegisterPayments(payments)
require.NoError(t, err)
require.NotEmpty(t, events)
reason := fmt.Errorf("some valid reason")
events = round.Fail(reason)
require.Len(t, events, 1)
require.False(t, round.IsStarted())
require.False(t, round.IsEnded())
require.True(t, round.IsFailed())
event, ok := events[0].(domain.RoundFailed)
require.True(t, ok)
require.Exactly(t, round.Id, event.Id)
require.Exactly(t, round.EndingTimestamp, event.Timestamp)
require.EqualError(t, reason, event.Err)
events = round.Fail(reason)
require.Empty(t, events)
})
})
}

View File

@@ -0,0 +1,11 @@
package ports
import "github.com/ark-network/ark/internal/core/domain"
type RepoManager interface {
Events() domain.RoundEventRepository
Rounds() domain.RoundRepository
Vtxos() domain.VtxoRepository
RegisterEventsHandler(func(*domain.Round))
Close()
}

View File

@@ -0,0 +1,12 @@
package ports
import (
"github.com/ark-network/ark/internal/core/domain"
"golang.org/x/net/context"
)
type BlockchainScanner interface {
WatchScripts(ctx context.Context, scripts []string) error
UnwatchScripts(ctx context.Context, scripts []string) error
GetNotificationChannel(ctx context.Context) chan []domain.VtxoKey
}

View File

@@ -0,0 +1,9 @@
package ports
type SchedulerService interface {
Start()
Stop()
ScheduleTask(interval int64, immediate bool, task func()) error
ScheduleTaskOnce(delay int64, task func()) error
}

View File

@@ -0,0 +1,29 @@
package ports
import (
"github.com/ark-network/ark/common/tree"
"github.com/ark-network/ark/internal/core/domain"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/vulpemventures/go-elements/psetv2"
)
type SweepInput struct {
InputArgs psetv2.InputArgs
SweepLeaf psetv2.TapLeafScript
Amount uint64
}
type TxBuilder interface {
BuildPoolTx(
aspPubkey *secp256k1.PublicKey, payments []domain.Payment, minRelayFee uint64,
) (poolTx string, congestionTree tree.CongestionTree, err error)
BuildForfeitTxs(
aspPubkey *secp256k1.PublicKey, poolTx string, payments []domain.Payment,
) (connectors []string, forfeitTxs []string, err error)
BuildSweepTx(
wallet WalletService,
inputs []SweepInput,
) (signedSweepTx string, err error)
GetLeafSweepClosure(node tree.Node, userPubKey *secp256k1.PublicKey) (*psetv2.TapLeafScript, int64, error)
GetVtxoScript(userPubkey, aspPubkey *secp256k1.PublicKey) ([]byte, error)
}

View File

@@ -0,0 +1,37 @@
package ports
import (
"context"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
)
type WalletService interface {
BlockchainScanner
Status(ctx context.Context) (WalletStatus, error)
GetPubkey(ctx context.Context) (*secp256k1.PublicKey, error)
DeriveAddresses(ctx context.Context, num int) ([]string, error)
SignPset(
ctx context.Context, pset string, extractRawTx bool,
) (string, error)
SelectUtxos(ctx context.Context, asset string, amount uint64) ([]TxInput, uint64, error)
BroadcastTransaction(ctx context.Context, txHex string) (string, error)
SignPsetWithKey(ctx context.Context, pset string, inputIndexes []int) (string, error) // inputIndexes == nil means sign all inputs
IsTransactionPublished(ctx context.Context, txid string) (isPublished bool, blocktime int64, err error)
EstimateFees(ctx context.Context, pset string) (uint64, error)
Close()
}
type WalletStatus interface {
IsInitialized() bool
IsUnlocked() bool
IsSynced() bool
}
type TxInput interface {
GetTxid() string
GetIndex() uint32
GetScript() string
GetAsset() string
GetValue() uint64
}

View File

@@ -0,0 +1,154 @@
package badgerdb
import (
"context"
"fmt"
"path/filepath"
"sync"
"github.com/ark-network/ark/internal/core/domain"
dbtypes "github.com/ark-network/ark/internal/infrastructure/db/types"
"github.com/dgraph-io/badger/v4"
"github.com/timshannon/badgerhold/v4"
)
const eventStoreDir = "round-events"
type eventsDTO struct {
Events [][]byte
}
type eventRepository struct {
store *badgerhold.Store
lock *sync.RWMutex
chUpdates chan *domain.Round
handler func(round *domain.Round)
}
func NewRoundEventRepository(config ...interface{}) (dbtypes.EventStore, error) {
if len(config) != 2 {
return nil, fmt.Errorf("invalid config")
}
baseDir, ok := config[0].(string)
if !ok {
return nil, fmt.Errorf("invalid base directory")
}
var logger badger.Logger
if config[1] != nil {
logger, ok = config[1].(badger.Logger)
if !ok {
return nil, fmt.Errorf("invalid logger")
}
}
var dir string
if len(baseDir) > 0 {
dir = filepath.Join(baseDir, eventStoreDir)
}
store, err := createDB(dir, logger)
if err != nil {
return nil, fmt.Errorf("failed to open round events store: %s", err)
}
chEvents := make(chan *domain.Round)
lock := &sync.RWMutex{}
repo := &eventRepository{store, lock, chEvents, nil}
go repo.listen()
return repo, nil
}
func (r *eventRepository) Save(
ctx context.Context, id string, events ...domain.RoundEvent,
) error {
allEvents, err := r.get(ctx, id)
if err != nil {
return err
}
allEvents = append(allEvents, events...)
if err := r.upsert(ctx, id, allEvents); err != nil {
return err
}
go r.publishEvents(allEvents)
return nil
}
func (r *eventRepository) Load(
ctx context.Context, id string,
) (*domain.Round, error) {
events, err := r.get(ctx, id)
if err != nil {
return nil, err
}
return domain.NewRoundFromEvents(events), nil
}
func (r *eventRepository) RegisterEventsHandler(
handler func(round *domain.Round),
) {
r.lock.Lock()
defer r.lock.Unlock()
r.handler = handler
}
func (r *eventRepository) Close() {
close(r.chUpdates)
r.store.Close()
}
func (r *eventRepository) get(
ctx context.Context, id string,
) ([]domain.RoundEvent, error) {
dto := eventsDTO{}
var err error
if ctx.Value("tx") != nil {
tx := ctx.Value("tx").(*badger.Txn)
err = r.store.TxGet(tx, id, &dto)
} else {
err = r.store.Get(id, &dto)
}
if err != nil {
if err == badgerhold.ErrNotFound {
return nil, nil
}
return nil, fmt.Errorf("failed to get events with id %s: %s", id, err)
}
return deserializeEvents(dto.Events)
}
func (r *eventRepository) upsert(
ctx context.Context, id string, events []domain.RoundEvent,
) error {
buf, err := serializeEvents(events)
if err != nil {
return err
}
if ctx.Value("tx") != nil {
tx := ctx.Value("tx").(*badger.Txn)
err = r.store.TxUpsert(tx, id, buf)
} else {
err = r.store.Upsert(id, buf)
}
if err != nil {
return fmt.Errorf("failed to upsert events with id %s: %s", id, err)
}
return nil
}
func (r *eventRepository) listen() {
for updatedRound := range r.chUpdates {
r.lock.RLock()
if r.handler != nil {
r.handler(updatedRound)
}
r.lock.RUnlock()
}
}
func (r *eventRepository) publishEvents(events []domain.RoundEvent) {
r.lock.Lock()
defer r.lock.Unlock()
round := domain.NewRoundFromEvents(events)
r.chUpdates <- round
}

View File

@@ -0,0 +1,140 @@
package badgerdb
import (
"context"
"fmt"
"path/filepath"
"github.com/ark-network/ark/internal/core/domain"
dbtypes "github.com/ark-network/ark/internal/infrastructure/db/types"
"github.com/dgraph-io/badger/v4"
"github.com/timshannon/badgerhold/v4"
)
const roundStoreDir = "rounds"
type roundRepository struct {
store *badgerhold.Store
}
func NewRoundRepository(config ...interface{}) (dbtypes.RoundStore, error) {
if len(config) != 2 {
return nil, fmt.Errorf("invalid config")
}
baseDir, ok := config[0].(string)
if !ok {
return nil, fmt.Errorf("invalid base directory")
}
var logger badger.Logger
if config[1] != nil {
logger, ok = config[1].(badger.Logger)
if !ok {
return nil, fmt.Errorf("invalid logger")
}
}
var dir string
if len(baseDir) > 0 {
dir = filepath.Join(baseDir, roundStoreDir)
}
store, err := createDB(dir, logger)
if err != nil {
return nil, fmt.Errorf("failed to open round events store: %s", err)
}
return &roundRepository{store}, nil
}
func (r *roundRepository) AddOrUpdateRound(
ctx context.Context, round domain.Round,
) error {
return r.addOrUpdateRound(ctx, round)
}
func (r *roundRepository) GetCurrentRound(
ctx context.Context,
) (*domain.Round, error) {
query := badgerhold.Where("Stage.Ended").Eq(false).And("Stage.Failed").Eq(false)
rounds, err := r.findRound(ctx, query)
if err != nil {
return nil, err
}
if len(rounds) <= 0 {
return nil, fmt.Errorf("ongoing round not found")
}
return &rounds[0], nil
}
func (r *roundRepository) GetRoundWithId(
ctx context.Context, id string,
) (*domain.Round, error) {
query := badgerhold.Where("Id").Eq(id)
rounds, err := r.findRound(ctx, query)
if err != nil {
return nil, err
}
if len(rounds) <= 0 {
return nil, fmt.Errorf("round with id %s not found", id)
}
round := &rounds[0]
return round, nil
}
func (r *roundRepository) GetRoundWithTxid(
ctx context.Context, txid string,
) (*domain.Round, error) {
query := badgerhold.Where("Txid").Eq(txid)
rounds, err := r.findRound(ctx, query)
if err != nil {
return nil, err
}
if len(rounds) <= 0 {
return nil, fmt.Errorf("round with txid %s not found", txid)
}
round := &rounds[0]
return round, nil
}
func (r *roundRepository) GetSweepableRounds(
ctx context.Context,
) ([]domain.Round, error) {
query := badgerhold.Where("Stage.Code").Eq(domain.FinalizationStage).
And("Stage.Ended").Eq(true).And("Swept").Eq(false)
rounds, err := r.findRound(ctx, query)
if err != nil {
return nil, err
}
return rounds, nil
}
func (r *roundRepository) Close() {
r.store.Close()
}
func (r *roundRepository) findRound(
ctx context.Context, query *badgerhold.Query,
) ([]domain.Round, error) {
var rounds []domain.Round
var err error
if ctx.Value("tx") != nil {
tx := ctx.Value("tx").(*badger.Txn)
err = r.store.TxFind(tx, &rounds, query)
} else {
err = r.store.Find(&rounds, query)
}
return rounds, err
}
func (r *roundRepository) addOrUpdateRound(
ctx context.Context, round domain.Round,
) (err error) {
if ctx.Value("tx") != nil {
tx := ctx.Value("tx").(*badger.Txn)
err = r.store.TxUpsert(tx, round.Id, round)
} else {
err = r.store.Upsert(round.Id, round)
}
return
}

View File

@@ -0,0 +1,116 @@
package badgerdb
import (
"encoding/json"
"fmt"
"time"
"github.com/ark-network/ark/internal/core/domain"
"github.com/dgraph-io/badger/v4"
"github.com/dgraph-io/badger/v4/options"
"github.com/timshannon/badgerhold/v4"
)
func createDB(dbDir string, logger badger.Logger) (*badgerhold.Store, error) {
isInMemory := len(dbDir) <= 0
opts := badger.DefaultOptions(dbDir)
opts.Logger = logger
if isInMemory {
opts.InMemory = true
} else {
opts.Compression = options.ZSTD
}
db, err := badgerhold.Open(badgerhold.Options{
Encoder: badgerhold.DefaultEncode,
Decoder: badgerhold.DefaultDecode,
SequenceBandwith: 100,
Options: opts,
})
if err != nil {
return nil, err
}
if !isInMemory {
ticker := time.NewTicker(30 * time.Minute)
go func() {
for {
<-ticker.C
if err := db.Badger().RunValueLogGC(0.5); err != nil && err != badger.ErrNoRewrite {
logger.Errorf("%s", err)
}
}
}()
}
return db, nil
}
func serializeEvents(events []domain.RoundEvent) (*eventsDTO, error) {
rawEvents := make([][]byte, 0, len(events))
for _, event := range events {
buf, err := serializeEvent(event)
if err != nil {
return nil, err
}
rawEvents = append(rawEvents, buf)
}
return &eventsDTO{rawEvents}, nil
}
func deserializeEvents(rawEvents [][]byte) ([]domain.RoundEvent, error) {
events := make([]domain.RoundEvent, 0)
for _, buf := range rawEvents {
event, err := deserializeEvent(buf)
if err != nil {
return nil, err
}
events = append(events, event)
}
return events, nil
}
func serializeEvent(event domain.RoundEvent) ([]byte, error) {
switch eventType := event.(type) {
default:
return json.Marshal(eventType)
}
}
func deserializeEvent(buf []byte) (domain.RoundEvent, error) {
{
var event = domain.RoundFailed{}
if err := json.Unmarshal(buf, &event); err == nil && len(event.Err) > 0 {
return event, nil
}
}
{
var event = domain.RoundFinalized{}
if err := json.Unmarshal(buf, &event); err == nil && len(event.Txid) > 0 {
return event, nil
}
}
{
var event = domain.RoundFinalizationStarted{}
if err := json.Unmarshal(buf, &event); err == nil && len(event.CongestionTree) > 0 {
return event, nil
}
}
{
var event = domain.PaymentsRegistered{}
if err := json.Unmarshal(buf, &event); err == nil && len(event.Payments) > 0 {
return event, nil
}
}
{
var event = domain.RoundStarted{}
if err := json.Unmarshal(buf, &event); err == nil && event.Timestamp > 0 {
return event, nil
}
}
return nil, fmt.Errorf("unknown event")
}

View File

@@ -0,0 +1,245 @@
package badgerdb
import (
"context"
"fmt"
"path/filepath"
"strings"
"github.com/ark-network/ark/internal/core/domain"
dbtypes "github.com/ark-network/ark/internal/infrastructure/db/types"
"github.com/dgraph-io/badger/v4"
"github.com/timshannon/badgerhold/v4"
)
const vtxoStoreDir = "vtxos"
type vtxoRepository struct {
store *badgerhold.Store
}
func NewVtxoRepository(config ...interface{}) (dbtypes.VtxoStore, error) {
if len(config) != 2 {
return nil, fmt.Errorf("invalid config")
}
baseDir, ok := config[0].(string)
if !ok {
return nil, fmt.Errorf("invalid base directory")
}
var logger badger.Logger
if config[1] != nil {
logger, ok = config[1].(badger.Logger)
if !ok {
return nil, fmt.Errorf("invalid logger")
}
}
var dir string
if len(baseDir) > 0 {
dir = filepath.Join(baseDir, vtxoStoreDir)
}
store, err := createDB(dir, logger)
if err != nil {
return nil, fmt.Errorf("failed to open round events store: %s", err)
}
return &vtxoRepository{store}, nil
}
func (r *vtxoRepository) AddVtxos(
ctx context.Context, vtxos []domain.Vtxo,
) error {
return r.addVtxos(ctx, vtxos)
}
func (r *vtxoRepository) SpendVtxos(
ctx context.Context, vtxoKeys []domain.VtxoKey,
) error {
for _, vtxoKey := range vtxoKeys {
if err := r.spendVtxo(ctx, vtxoKey); err != nil {
return err
}
}
return nil
}
func (r *vtxoRepository) RedeemVtxos(
ctx context.Context, vtxoKeys []domain.VtxoKey,
) ([]domain.Vtxo, error) {
vtxos := make([]domain.Vtxo, 0, len(vtxoKeys))
for _, vtxoKey := range vtxoKeys {
vtxo, err := r.redeemVtxo(ctx, vtxoKey)
if err != nil {
return nil, err
}
if vtxo != nil {
vtxos = append(vtxos, *vtxo)
}
}
return vtxos, nil
}
func (r *vtxoRepository) GetVtxos(
ctx context.Context, vtxoKeys []domain.VtxoKey,
) ([]domain.Vtxo, error) {
vtxos := make([]domain.Vtxo, 0, len(vtxoKeys))
for _, vtxoKey := range vtxoKeys {
vtxo, err := r.getVtxo(ctx, vtxoKey)
if err != nil {
return nil, err
}
vtxos = append(vtxos, *vtxo)
}
return vtxos, nil
}
func (r *vtxoRepository) GetVtxosForRound(
ctx context.Context, txid string,
) ([]domain.Vtxo, error) {
query := badgerhold.Where("Txid").Eq(txid)
return r.findVtxos(ctx, query)
}
func (r *vtxoRepository) GetSpendableVtxos(
ctx context.Context, pubkey string,
) ([]domain.Vtxo, error) {
query := badgerhold.Where("Spent").Eq(false).And("Redeemed").Eq(false).And("Swept").Eq(false)
if len(pubkey) > 0 {
query = query.And("Pubkey").Eq(pubkey)
}
return r.findVtxos(ctx, query)
}
func (r *vtxoRepository) SweepVtxos(
ctx context.Context, vtxoKeys []domain.VtxoKey,
) error {
for _, vtxoKey := range vtxoKeys {
if err := r.sweepVtxo(ctx, vtxoKey); err != nil {
return err
}
}
return nil
}
func (r *vtxoRepository) Close() {
r.store.Close()
}
func (r *vtxoRepository) addVtxos(
ctx context.Context, vtxos []domain.Vtxo,
) (err error) {
for _, vtxo := range vtxos {
vtxoKey := vtxo.VtxoKey.Hash()
if ctx.Value("tx") != nil {
tx := ctx.Value("tx").(*badger.Txn)
err = r.store.TxInsert(tx, vtxoKey, vtxo)
} else {
err = r.store.Insert(vtxoKey, vtxo)
}
}
if err != nil && err == badgerhold.ErrKeyExists {
err = nil
}
return
}
func (r *vtxoRepository) getVtxo(
ctx context.Context, vtxoKey domain.VtxoKey,
) (*domain.Vtxo, error) {
var vtxo domain.Vtxo
var err error
if ctx.Value("tx") != nil {
tx := ctx.Value("tx").(*badger.Txn)
err = r.store.TxGet(tx, vtxoKey.Hash(), &vtxo)
} else {
err = r.store.Get(vtxoKey.Hash(), &vtxo)
}
if err != nil && err == badgerhold.ErrNotFound {
return nil, fmt.Errorf("vtxo %s:%d not found", vtxoKey.Txid, vtxoKey.VOut)
}
return &vtxo, nil
}
func (r *vtxoRepository) spendVtxo(ctx context.Context, vtxoKey domain.VtxoKey) error {
vtxo, err := r.getVtxo(ctx, vtxoKey)
if err != nil {
if strings.Contains(err.Error(), "not found") {
return nil
}
return err
}
if vtxo.Spent {
return nil
}
vtxo.Spent = true
if ctx.Value("tx") != nil {
tx := ctx.Value("tx").(*badger.Txn)
err = r.store.TxUpdate(tx, vtxoKey.Hash(), *vtxo)
} else {
err = r.store.Update(vtxoKey.Hash(), *vtxo)
}
return err
}
func (r *vtxoRepository) redeemVtxo(ctx context.Context, vtxoKey domain.VtxoKey) (*domain.Vtxo, error) {
vtxo, err := r.getVtxo(ctx, vtxoKey)
if err != nil {
if strings.Contains(err.Error(), "not found") {
return nil, nil
}
return nil, err
}
if vtxo.Redeemed {
return nil, nil
}
vtxo.Redeemed = true
if ctx.Value("tx") != nil {
tx := ctx.Value("tx").(*badger.Txn)
err = r.store.TxUpdate(tx, vtxoKey.Hash(), *vtxo)
} else {
err = r.store.Update(vtxoKey.Hash(), *vtxo)
}
if err != nil {
return nil, err
}
return vtxo, nil
}
func (r *vtxoRepository) findVtxos(ctx context.Context, query *badgerhold.Query) ([]domain.Vtxo, error) {
var vtxos []domain.Vtxo
var err error
if ctx.Value("tx") != nil {
tx := ctx.Value("tx").(*badger.Txn)
err = r.store.TxFind(tx, &vtxos, query)
} else {
err = r.store.Find(&vtxos, query)
}
return vtxos, err
}
func (r *vtxoRepository) sweepVtxo(ctx context.Context, vtxoKey domain.VtxoKey) error {
vtxo, err := r.getVtxo(ctx, vtxoKey)
if err != nil {
return err
}
if vtxo.Swept {
return nil
}
vtxo.Swept = true
if ctx.Value("tx") != nil {
tx := ctx.Value("tx").(*badger.Txn)
err = r.store.TxUpdate(tx, vtxoKey.Hash(), *vtxo)
} else {
err = r.store.Update(vtxoKey.Hash(), *vtxo)
}
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,90 @@
package db
import (
"fmt"
"github.com/ark-network/ark/internal/core/domain"
"github.com/ark-network/ark/internal/core/ports"
badgerdb "github.com/ark-network/ark/internal/infrastructure/db/badger"
dbtypes "github.com/ark-network/ark/internal/infrastructure/db/types"
)
var (
eventStoreTypes = map[string]func(...interface{}) (dbtypes.EventStore, error){
"badger": badgerdb.NewRoundEventRepository,
}
roundStoreTypes = map[string]func(...interface{}) (dbtypes.RoundStore, error){
"badger": badgerdb.NewRoundRepository,
}
vtxoStoreTypes = map[string]func(...interface{}) (dbtypes.VtxoStore, error){
"badger": badgerdb.NewVtxoRepository,
}
)
type ServiceConfig struct {
EventStoreType string
RoundStoreType string
VtxoStoreType string
EventStoreConfig []interface{}
RoundStoreConfig []interface{}
VtxoStoreConfig []interface{}
}
type service struct {
eventStore dbtypes.EventStore
roundStore dbtypes.RoundStore
vtxoStore dbtypes.VtxoStore
}
func NewService(config ServiceConfig) (ports.RepoManager, error) {
eventStoreFactory, ok := eventStoreTypes[config.EventStoreType]
if !ok {
return nil, fmt.Errorf("event store type not supported")
}
roundStoreFactory, ok := roundStoreTypes[config.RoundStoreType]
if !ok {
return nil, fmt.Errorf("round store type not supported")
}
vtxoStoreFactory, ok := vtxoStoreTypes[config.VtxoStoreType]
if !ok {
return nil, fmt.Errorf("vtxo store type not supported")
}
eventStore, err := eventStoreFactory(config.EventStoreConfig...)
if err != nil {
return nil, fmt.Errorf("failed to open event store: %s", err)
}
roundStore, err := roundStoreFactory(config.RoundStoreConfig...)
if err != nil {
return nil, fmt.Errorf("failed to open round store: %s", err)
}
vtxoStore, err := vtxoStoreFactory(config.VtxoStoreConfig...)
if err != nil {
return nil, fmt.Errorf("failed to open vtxo store: %s", err)
}
return &service{eventStore, roundStore, vtxoStore}, nil
}
func (s *service) RegisterEventsHandler(handler func(round *domain.Round)) {
s.eventStore.RegisterEventsHandler(handler)
}
func (s *service) Events() domain.RoundEventRepository {
return s.eventStore
}
func (s *service) Rounds() domain.RoundRepository {
return s.roundStore
}
func (s *service) Vtxos() domain.VtxoRepository {
return s.vtxoStore
}
func (s *service) Close() {
s.eventStore.Close()
s.roundStore.Close()
s.vtxoStore.Close()
}

View File

@@ -0,0 +1,417 @@
package db_test
import (
"context"
"reflect"
"testing"
"time"
"github.com/ark-network/ark/common/tree"
"github.com/ark-network/ark/internal/core/domain"
"github.com/ark-network/ark/internal/core/ports"
"github.com/ark-network/ark/internal/infrastructure/db"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const (
emptyPtx = "cHNldP8BAgQCAAAAAQQBAAEFAQABBgEDAfsEAgAAAAA="
emptyTx = "0200000000000000000000"
txid = "00000000000000000000000000000000000000000000000000000000000000000"
pubkey1 = "0300000000000000000000000000000000000000000000000000000000000000001"
pubkey2 = "0200000000000000000000000000000000000000000000000000000000000000002"
)
var congestionTree = [][]tree.Node{
{
{
Txid: txid,
Tx: emptyPtx,
ParentTxid: txid,
},
},
{
{
Txid: txid,
Tx: emptyPtx,
ParentTxid: txid,
},
{
Txid: txid,
Tx: emptyPtx,
ParentTxid: txid,
},
},
{
{
Txid: txid,
Tx: emptyPtx,
ParentTxid: txid,
},
{
Txid: txid,
Tx: emptyPtx,
ParentTxid: txid,
},
{
Txid: txid,
Tx: emptyPtx,
ParentTxid: txid,
},
{
Txid: txid,
Tx: emptyPtx,
ParentTxid: txid,
},
},
}
func TestService(t *testing.T) {
tests := []struct {
name string
config db.ServiceConfig
}{
{
name: "repo_manager_with_badger_stores",
config: db.ServiceConfig{
EventStoreType: "badger",
RoundStoreType: "badger",
VtxoStoreType: "badger",
EventStoreConfig: []interface{}{"", nil},
RoundStoreConfig: []interface{}{"", nil},
VtxoStoreConfig: []interface{}{"", nil},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
svc, err := db.NewService(tt.config)
require.NoError(t, err)
require.NotNil(t, svc)
testRoundEventRepository(t, svc)
testRoundRepository(t, svc)
testVtxoRepository(t, svc)
svc.Close()
})
}
}
func testRoundEventRepository(t *testing.T, svc ports.RepoManager) {
t.Run("test_event_repository", func(t *testing.T) {
fixtures := []struct {
roundId string
events []domain.RoundEvent
handler func(*domain.Round)
}{
{
roundId: "42dd81f7-cadd-482c-bf69-8e9209aae9f3",
events: []domain.RoundEvent{
domain.RoundStarted{
Id: "42dd81f7-cadd-482c-bf69-8e9209aae9f3",
Timestamp: 1701190270,
},
},
handler: func(round *domain.Round) {
require.NotNil(t, round)
require.Len(t, round.Events(), 1)
require.True(t, round.IsStarted())
require.False(t, round.IsFailed())
require.False(t, round.IsEnded())
},
},
{
roundId: "1ea610ff-bf3e-4068-9bfd-b6c3f553467e",
events: []domain.RoundEvent{
domain.RoundStarted{
Id: "1ea610ff-bf3e-4068-9bfd-b6c3f553467e",
Timestamp: 1701190270,
},
domain.RoundFinalizationStarted{
Id: "1ea610ff-bf3e-4068-9bfd-b6c3f553467e",
CongestionTree: congestionTree,
Connectors: []string{emptyPtx, emptyPtx},
PoolTx: emptyTx,
},
},
handler: func(round *domain.Round) {
require.NotNil(t, round)
require.Len(t, round.Events(), 2)
require.Len(t, round.CongestionTree, 3)
require.Equal(t, round.CongestionTree.NumberOfNodes(), 7)
require.Len(t, round.Connectors, 2)
},
},
{
roundId: "7578231e-428d-45ae-aaa4-e62c77ad5cec",
events: []domain.RoundEvent{
domain.RoundStarted{
Id: "7578231e-428d-45ae-aaa4-e62c77ad5cec",
Timestamp: 1701190270,
},
domain.RoundFinalizationStarted{
Id: "7578231e-428d-45ae-aaa4-e62c77ad5cec",
CongestionTree: congestionTree,
Connectors: []string{emptyPtx, emptyPtx},
PoolTx: emptyTx,
},
domain.RoundFinalized{
Id: "7578231e-428d-45ae-aaa4-e62c77ad5cec",
Txid: txid,
ForfeitTxs: []string{emptyPtx, emptyPtx, emptyPtx, emptyPtx},
Timestamp: 1701190300,
},
},
handler: func(round *domain.Round) {
require.NotNil(t, round)
require.Len(t, round.Events(), 3)
require.False(t, round.IsStarted())
require.False(t, round.IsFailed())
require.True(t, round.IsEnded())
require.NotEmpty(t, round.Txid)
},
},
}
ctx := context.Background()
for _, f := range fixtures {
svc.RegisterEventsHandler(f.handler)
err := svc.Events().Save(ctx, f.roundId, f.events...)
require.NoError(t, err)
round, err := svc.Events().Load(ctx, f.roundId)
require.NoError(t, err)
require.NotNil(t, round)
require.Equal(t, f.roundId, round.Id)
require.Len(t, round.Events(), len(f.events))
}
})
}
func testRoundRepository(t *testing.T, svc ports.RepoManager) {
t.Run("test_round_repository", func(t *testing.T) {
ctx := context.Background()
now := time.Now()
roundId := uuid.New().String()
round, err := svc.Rounds().GetRoundWithId(ctx, roundId)
require.Error(t, err)
require.Nil(t, round)
events := []domain.RoundEvent{
domain.RoundStarted{
Id: roundId,
Timestamp: now.Unix(),
},
}
round = domain.NewRoundFromEvents(events)
err = svc.Rounds().AddOrUpdateRound(ctx, *round)
require.NoError(t, err)
currentRound, err := svc.Rounds().GetCurrentRound(ctx)
require.NoError(t, err)
require.NotNil(t, currentRound)
require.Condition(t, roundsMatch(*round, *currentRound))
roundById, err := svc.Rounds().GetRoundWithId(ctx, roundId)
require.NoError(t, err)
require.NotNil(t, roundById)
require.Condition(t, roundsMatch(*round, *roundById))
newEvents := []domain.RoundEvent{
domain.PaymentsRegistered{
Id: roundId,
Payments: []domain.Payment{
{
Id: uuid.New().String(),
Inputs: []domain.Vtxo{{}},
Receivers: []domain.Receiver{{}},
},
{
Id: uuid.New().String(),
Inputs: []domain.Vtxo{{}},
Receivers: []domain.Receiver{{}, {}, {}},
},
},
},
domain.RoundFinalizationStarted{
Id: roundId,
CongestionTree: congestionTree,
Connectors: []string{emptyPtx, emptyPtx},
PoolTx: emptyTx,
},
}
events = append(events, newEvents...)
updatedRound := domain.NewRoundFromEvents(events)
err = svc.Rounds().AddOrUpdateRound(ctx, *updatedRound)
require.NoError(t, err)
currentRound, err = svc.Rounds().GetCurrentRound(ctx)
require.NoError(t, err)
require.NotNil(t, currentRound)
require.Condition(t, roundsMatch(*updatedRound, *currentRound))
roundById, err = svc.Rounds().GetRoundWithId(ctx, updatedRound.Id)
require.NoError(t, err)
require.NotNil(t, currentRound)
require.Condition(t, roundsMatch(*updatedRound, *roundById))
newEvents = []domain.RoundEvent{
domain.RoundFinalized{
Id: roundId,
Txid: txid,
ForfeitTxs: []string{emptyPtx, emptyPtx, emptyPtx, emptyPtx},
Timestamp: now.Add(60 * time.Second).Unix(),
},
}
events = append(events, newEvents...)
finalizedRound := domain.NewRoundFromEvents(events)
err = svc.Rounds().AddOrUpdateRound(ctx, *finalizedRound)
require.NoError(t, err)
currentRound, err = svc.Rounds().GetCurrentRound(ctx)
require.Error(t, err)
require.Nil(t, currentRound)
roundById, err = svc.Rounds().GetRoundWithId(ctx, roundId)
require.NoError(t, err)
require.NotNil(t, roundById)
require.Condition(t, roundsMatch(*finalizedRound, *roundById))
roundByTxid, err := svc.Rounds().GetRoundWithTxid(ctx, txid)
require.NoError(t, err)
require.NotNil(t, roundByTxid)
require.Condition(t, roundsMatch(*finalizedRound, *roundByTxid))
})
}
func testVtxoRepository(t *testing.T, svc ports.RepoManager) {
t.Run("test_vtxo_repository", func(t *testing.T) {
ctx := context.Background()
userVtxos := []domain.Vtxo{
{
VtxoKey: domain.VtxoKey{
Txid: txid,
VOut: 0,
},
Receiver: domain.Receiver{
Pubkey: pubkey1,
Amount: 1000,
},
},
{
VtxoKey: domain.VtxoKey{
Txid: txid,
VOut: 1,
},
Receiver: domain.Receiver{
Pubkey: pubkey1,
Amount: 2000,
},
},
}
newVtxos := append(userVtxos, domain.Vtxo{
VtxoKey: domain.VtxoKey{
Txid: txid,
VOut: 1,
},
Receiver: domain.Receiver{
Pubkey: pubkey2,
Amount: 2000,
},
})
vtxoKeys := make([]domain.VtxoKey, 0, len(userVtxos))
for _, v := range userVtxos {
vtxoKeys = append(vtxoKeys, v.VtxoKey)
}
vtxos, err := svc.Vtxos().GetVtxos(ctx, vtxoKeys)
require.Error(t, err)
require.Empty(t, vtxos)
spendableVtxos, err := svc.Vtxos().GetSpendableVtxos(ctx, pubkey1)
require.NoError(t, err)
require.Empty(t, spendableVtxos)
spendableVtxos, err = svc.Vtxos().GetSpendableVtxos(ctx, "")
require.NoError(t, err)
require.Empty(t, spendableVtxos)
err = svc.Vtxos().AddVtxos(ctx, newVtxos)
require.NoError(t, err)
vtxos, err = svc.Vtxos().GetVtxos(ctx, vtxoKeys)
require.NoError(t, err)
require.Exactly(t, userVtxos, vtxos)
spendableVtxos, err = svc.Vtxos().GetSpendableVtxos(ctx, pubkey1)
require.NoError(t, err)
require.Exactly(t, vtxos, spendableVtxos)
spendableVtxos, err = svc.Vtxos().GetSpendableVtxos(ctx, "")
require.NoError(t, err)
require.Exactly(t, userVtxos, spendableVtxos)
err = svc.Vtxos().SpendVtxos(ctx, vtxoKeys[:1])
require.NoError(t, err)
spentVtxos, err := svc.Vtxos().GetVtxos(ctx, vtxoKeys[:1])
require.NoError(t, err)
require.Len(t, spentVtxos, len(vtxoKeys[:1]))
for _, v := range spentVtxos {
require.True(t, v.Spent)
}
spendableVtxos, err = svc.Vtxos().GetSpendableVtxos(ctx, pubkey1)
require.NoError(t, err)
require.Exactly(t, vtxos[1:], spendableVtxos)
})
}
func roundsMatch(expected, got domain.Round) assert.Comparison {
return func() bool {
if expected.Id != got.Id {
return false
}
if expected.StartingTimestamp != got.StartingTimestamp {
return false
}
if expected.EndingTimestamp != got.EndingTimestamp {
return false
}
if expected.Stage != got.Stage {
return false
}
if !reflect.DeepEqual(expected.Payments, got.Payments) {
return false
}
if expected.Txid != got.Txid {
return false
}
if expected.UnsignedTx != got.UnsignedTx {
return false
}
if !reflect.DeepEqual(expected.ForfeitTxs, got.ForfeitTxs) {
return false
}
if !reflect.DeepEqual(expected.CongestionTree, got.CongestionTree) {
return false
}
if !reflect.DeepEqual(expected.Connectors, got.Connectors) {
return false
}
if expected.Version != got.Version {
return false
}
return true
}
}

View File

@@ -0,0 +1,19 @@
package dbtypes
import "github.com/ark-network/ark/internal/core/domain"
type EventStore interface {
domain.RoundEventRepository
RegisterEventsHandler(func(*domain.Round))
Close()
}
type RoundStore interface {
domain.RoundRepository
Close()
}
type VtxoStore interface {
domain.VtxoRepository
Close()
}

View File

@@ -0,0 +1,30 @@
package oceanwallet
import (
"context"
pb "github.com/ark-network/ark/api-spec/protobuf/gen/ocean/v1"
"github.com/vulpemventures/go-elements/address"
)
func (s *service) DeriveAddresses(
ctx context.Context, numOfAddresses int,
) ([]string, error) {
res, err := s.accountClient.DeriveAddresses(ctx, &pb.DeriveAddressesRequest{
AccountName: accountLabel,
NumOfAddresses: uint64(numOfAddresses),
})
if err != nil {
return nil, err
}
addresses := make([]string, 0, numOfAddresses)
for _, addr := range res.GetAddresses() {
if isConf, _ := address.IsConfidential(addr); !isConf {
addresses = append(addresses, addr)
continue
}
info, _ := address.FromConfidential(addr)
addresses = append(addresses, info.Address)
}
return addresses, nil
}

View File

@@ -0,0 +1,47 @@
package oceanwallet
import (
"context"
"crypto/sha256"
"encoding/hex"
pb "github.com/ark-network/ark/api-spec/protobuf/gen/ocean/v1"
"github.com/ark-network/ark/internal/core/domain"
"github.com/btcsuite/btcd/chaincfg/chainhash"
)
func (s *service) WatchScripts(ctx context.Context, scripts []string) error {
for _, script := range scripts {
if _, err := s.notifyClient.WatchExternalScript(ctx, &pb.WatchExternalScriptRequest{
Script: script,
}); err != nil {
return err
}
}
return nil
}
func (s *service) UnwatchScripts(ctx context.Context, scripts []string) error {
for _, script := range scripts {
scriptHash := calcScriptHash(script)
if _, err := s.notifyClient.UnwatchExternalScript(ctx, &pb.UnwatchExternalScriptRequest{
Label: scriptHash,
}); err != nil {
return err
}
}
return nil
}
func (s *service) GetNotificationChannel(ctx context.Context) chan []domain.VtxoKey {
return s.chVtxos
}
func calcScriptHash(script string) string {
buf, _ := hex.DecodeString(script)
hashedBuf := sha256.Sum256(buf)
hash, _ := chainhash.NewHash(hashedBuf[:])
return hash.String()
}

View File

@@ -0,0 +1,140 @@
package oceanwallet
import (
"context"
"fmt"
"io"
"strings"
pb "github.com/ark-network/ark/api-spec/protobuf/gen/ocean/v1"
"github.com/ark-network/ark/internal/core/domain"
"github.com/ark-network/ark/internal/core/ports"
log "github.com/sirupsen/logrus"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/status"
)
type service struct {
addr string
conn *grpc.ClientConn
walletClient pb.WalletServiceClient
accountClient pb.AccountServiceClient
txClient pb.TransactionServiceClient
notifyClient pb.NotificationServiceClient
chVtxos chan []domain.VtxoKey
}
func NewService(addr string) (ports.WalletService, error) {
conn, err := grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
return nil, err
}
walletClient := pb.NewWalletServiceClient(conn)
accountClient := pb.NewAccountServiceClient(conn)
txClient := pb.NewTransactionServiceClient(conn)
notifyClient := pb.NewNotificationServiceClient(conn)
chVtxos := make(chan []domain.VtxoKey)
svc := &service{
addr: addr,
conn: conn,
walletClient: walletClient,
accountClient: accountClient,
txClient: txClient,
notifyClient: notifyClient,
chVtxos: chVtxos,
}
ctx := context.Background()
status, err := svc.Status(ctx)
if err != nil {
return nil, err
}
if !(status.IsInitialized() && status.IsUnlocked()) {
return nil, fmt.Errorf("wallet must be already initialized and unlocked")
}
// Create ark account at startup if needed.
info, err := walletClient.GetInfo(ctx, &pb.GetInfoRequest{})
if err != nil {
return nil, err
}
found := false
for _, account := range info.GetAccounts() {
if account.GetLabel() == accountLabel {
found = true
break
}
}
if !found {
if _, err := accountClient.CreateAccountBIP44(ctx, &pb.CreateAccountBIP44Request{
Label: accountLabel,
Unconfidential: true,
}); err != nil {
return nil, err
}
}
go svc.listenToNotificaitons()
return svc, nil
}
func (s *service) Close() {
close(s.chVtxos)
s.conn.Close()
}
func (s *service) listenToNotificaitons() {
var stream pb.NotificationService_UtxosNotificationsClient
var err error
for {
stream, err = s.notifyClient.UtxosNotifications(context.Background(), &pb.UtxosNotificationsRequest{})
if err != nil {
continue
}
break
}
for {
msg, err := stream.Recv()
if err != nil {
if err == io.EOF || status.Convert(err).Code() == codes.Canceled {
return
}
log.WithError(err).Warn("received unexpected error from source")
return
}
if msg.GetEventType() != pb.UtxoEventType_UTXO_EVENT_TYPE_NEW &&
msg.GetEventType() != pb.UtxoEventType_UTXO_EVENT_TYPE_CONFIRMED {
continue
}
vtxos := toVtxos(msg.GetUtxos())
if len(vtxos) > 0 {
go func() {
s.chVtxos <- vtxos
}()
}
}
}
func toVtxos(utxos []*pb.Utxo) []domain.VtxoKey {
vtxos := make([]domain.VtxoKey, 0, len(utxos))
for _, utxo := range utxos {
// We want to notify for activity related to vtxos owner, therefore we skip
// returning anything related to the internal accounts of the wallet, like
// for example bip84-account0.
if strings.HasPrefix(utxo.GetAccountName(), "bip") {
continue
}
vtxos = append(vtxos, domain.VtxoKey{
Txid: utxo.GetTxid(),
VOut: utxo.GetIndex(),
})
}
return vtxos
}

View File

@@ -0,0 +1,270 @@
package oceanwallet
import (
"context"
"encoding/binary"
"encoding/hex"
"fmt"
"strings"
"time"
pb "github.com/ark-network/ark/api-spec/protobuf/gen/ocean/v1"
"github.com/ark-network/ark/common/tree"
"github.com/ark-network/ark/internal/core/ports"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/vulpemventures/go-elements/elementsutil"
"github.com/vulpemventures/go-elements/psetv2"
)
const (
zero32 = "0000000000000000000000000000000000000000000000000000000000000000"
)
func (s *service) SignPset(
ctx context.Context, pset string, extractRawTx bool,
) (string, error) {
res, err := s.txClient.SignPset(ctx, &pb.SignPsetRequest{
Pset: pset,
})
if err != nil {
return "", err
}
signedPset := res.GetPset()
if !extractRawTx {
return signedPset, nil
}
ptx, err := psetv2.NewPsetFromBase64(signedPset)
if err != nil {
return "", err
}
if err := psetv2.MaybeFinalizeAll(ptx); err != nil {
return "", fmt.Errorf("failed to finalize signed pset: %s", err)
}
extractedTx, err := psetv2.Extract(ptx)
if err != nil {
return "", fmt.Errorf("failed to extract signed pset: %s", err)
}
txHex, err := extractedTx.ToHex()
if err != nil {
return "", fmt.Errorf("failed to convert extracted tx to hex: %s", err)
}
return txHex, nil
}
func (s *service) SelectUtxos(ctx context.Context, asset string, amount uint64) ([]ports.TxInput, uint64, error) {
res, err := s.txClient.SelectUtxos(ctx, &pb.SelectUtxosRequest{
AccountName: accountLabel,
TargetAsset: asset,
TargetAmount: amount,
})
if err != nil {
return nil, 0, err
}
inputs := make([]ports.TxInput, 0, len(res.GetUtxos()))
for _, utxo := range res.GetUtxos() {
// check that the utxos are not confidential
if utxo.GetAssetBlinder() != zero32 || utxo.GetValueBlinder() != zero32 {
return nil, 0, fmt.Errorf("utxo is confidential")
}
inputs = append(inputs, utxo)
}
return inputs, res.GetChange(), nil
}
func (s *service) GetTransaction(
ctx context.Context, txid string,
) (string, int64, error) {
res, err := s.txClient.GetTransaction(ctx, &pb.GetTransactionRequest{
Txid: txid,
})
if err != nil {
return "", 0, err
}
if res.GetBlockDetails().GetTimestamp() > 0 {
return res.GetTxHex(), res.BlockDetails.GetTimestamp(), nil
}
// if not confirmed, we return now + 30 secs to estimate the next blocktime
return res.GetTxHex(), time.Now().Unix() + 30, nil
}
func (s *service) BroadcastTransaction(
ctx context.Context, txHex string,
) (string, error) {
res, err := s.txClient.BroadcastTransaction(
ctx, &pb.BroadcastTransactionRequest{
TxHex: txHex,
},
)
if err != nil {
if strings.Contains(err.Error(), "non-BIP68-final") {
return "", fmt.Errorf("non-BIP68-final")
}
return "", err
}
return res.GetTxid(), nil
}
func (s *service) IsTransactionPublished(
ctx context.Context, txid string,
) (bool, int64, error) {
_, blocktime, err := s.GetTransaction(ctx, txid)
if err != nil {
if strings.Contains(strings.ToLower(err.Error()), "missing transaction") {
return false, 0, nil
}
return false, 0, err
}
return true, blocktime, nil
}
func (s *service) SignPsetWithKey(ctx context.Context, b64 string, indexes []int) (string, error) {
pset, err := psetv2.NewPsetFromBase64(b64)
if err != nil {
return "", err
}
if indexes == nil {
for i := 0; i < len(pset.Inputs); i++ {
indexes = append(indexes, i)
}
}
key, masterKey, err := s.getPubkey(ctx)
if err != nil {
return "", err
}
fingerprint := binary.LittleEndian.Uint32(masterKey.FingerPrint)
extendedKey, err := masterKey.Serialize()
if err != nil {
return "", err
}
pset.Global.Xpubs = []psetv2.Xpub{{
ExtendedKey: extendedKey[:len(extendedKey)-4],
MasterFingerprint: fingerprint,
DerivationPath: derivationPath,
}}
updater, err := psetv2.NewUpdater(pset)
if err != nil {
return "", err
}
bip32derivation := psetv2.DerivationPathWithPubKey{
PubKey: key.SerializeCompressed(),
MasterKeyFingerprint: fingerprint,
Bip32Path: derivationPath,
}
for _, i := range indexes {
if len(pset.Inputs[i].TapLeafScript) == 0 {
return "", fmt.Errorf("no tap leaf script found for input %d", i)
}
leafHash := pset.Inputs[i].TapLeafScript[0].TapHash()
if err := updater.AddInTapBip32Derivation(i, psetv2.TapDerivationPathWithPubKey{
DerivationPathWithPubKey: bip32derivation,
LeafHashes: [][]byte{leafHash[:]},
}); err != nil {
return "", err
}
if err := updater.AddInSighashType(i, txscript.SigHashDefault); err != nil {
return "", err
}
}
unsignedPset, err := pset.ToBase64()
if err != nil {
return "", err
}
signedPset, err := s.txClient.SignPsetWithSchnorrKey(ctx, &pb.SignPsetWithSchnorrKeyRequest{
Tx: unsignedPset,
SighashType: uint32(txscript.SigHashDefault),
})
if err != nil {
return "", err
}
return signedPset.GetSignedTx(), nil
}
func (s *service) EstimateFees(
ctx context.Context, pset string,
) (uint64, error) {
tx, err := psetv2.NewPsetFromBase64(pset)
if err != nil {
return 0, err
}
inputs := make([]*pb.Input, 0, len(tx.Inputs))
outputs := make([]*pb.Output, 0, len(tx.Outputs))
for _, in := range tx.Inputs {
pbInput := &pb.Input{
Txid: chainhash.Hash(in.PreviousTxid).String(),
Index: in.PreviousTxIndex,
}
if len(in.TapLeafScript) == 1 {
isSweep, _, _, err := tree.DecodeSweepScript(in.TapLeafScript[0].Script)
if err != nil {
return 0, err
}
if isSweep {
pbInput.WitnessSize = 64
pbInput.ScriptsigSize = 0
}
} else {
if in.WitnessUtxo == nil {
return 0, fmt.Errorf("missing witness utxo, cannot estimate fees")
}
pbInput.Script = hex.EncodeToString(in.WitnessUtxo.Script)
}
inputs = append(inputs, pbInput)
}
for _, out := range tx.Outputs {
outputs = append(outputs, &pb.Output{
Asset: elementsutil.AssetHashFromBytes(
append([]byte{0x01}, out.Asset...),
),
Amount: out.Value,
Script: hex.EncodeToString(out.Script),
})
}
fee, err := s.txClient.EstimateFees(
ctx,
&pb.EstimateFeesRequest{
Inputs: inputs,
Outputs: outputs,
},
)
if err != nil {
return 0, fmt.Errorf("failed to estimate fees: %s", err)
}
// we add 5 sats in order to avoid min-relay-fee not met errors
return fee.GetFeeAmount() + 5, nil
}

View File

@@ -0,0 +1,95 @@
package oceanwallet
import (
"context"
"fmt"
pb "github.com/ark-network/ark/api-spec/protobuf/gen/ocean/v1"
"github.com/ark-network/ark/internal/core/ports"
"github.com/btcsuite/btcd/btcutil/hdkeychain"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/vulpemventures/go-bip32"
)
const accountLabel = "ark"
var derivationPath = []uint32{0, 0}
func (s *service) GetPubkey(ctx context.Context) (*secp256k1.PublicKey, error) {
key, _, err := s.getPubkey(ctx)
return key, err
}
func (s *service) Status(
ctx context.Context,
) (ports.WalletStatus, error) {
res, err := s.walletClient.Status(ctx, &pb.StatusRequest{})
if err != nil {
return nil, err
}
return walletStatus{res}, nil
}
type walletStatus struct {
*pb.StatusResponse
}
func (w walletStatus) IsInitialized() bool {
return w.StatusResponse.GetInitialized()
}
func (w walletStatus) IsUnlocked() bool {
return w.StatusResponse.GetUnlocked()
}
func (w walletStatus) IsSynced() bool {
return w.StatusResponse.GetSynced()
}
func (s *service) findAccount(ctx context.Context, label string) (*pb.AccountInfo, error) {
res, err := s.walletClient.GetInfo(ctx, &pb.GetInfoRequest{})
if err != nil {
return nil, err
}
if len(res.GetAccounts()) <= 0 {
return nil, fmt.Errorf("wallet is locked")
}
for _, account := range res.GetAccounts() {
if account.GetLabel() == label {
return account, nil
}
}
return nil, fmt.Errorf("account not found")
}
func (s *service) getPubkey(ctx context.Context) (*secp256k1.PublicKey, *bip32.Key, error) {
account, err := s.findAccount(ctx, accountLabel)
if err != nil {
return nil, nil, err
}
xpub := account.GetXpubs()[0]
node, err := hdkeychain.NewKeyFromString(xpub)
if err != nil {
return nil, nil, err
}
for _, i := range derivationPath {
node, err = node.Derive(i)
if err != nil {
return nil, nil, err
}
}
key, err := node.ECPubKey()
if err != nil {
return nil, nil, err
}
masterKey, err := bip32.B58Deserialize(xpub)
if err != nil {
return nil, nil, err
}
return key, masterKey, nil
}

View File

@@ -0,0 +1,45 @@
package scheduler
import (
"fmt"
"time"
"github.com/ark-network/ark/internal/core/ports"
"github.com/go-co-op/gocron"
)
type service struct {
scheduler *gocron.Scheduler
}
func NewScheduler() ports.SchedulerService {
svc := gocron.NewScheduler(time.UTC)
return &service{svc}
}
func (s *service) Start() {
s.scheduler.StartAsync()
}
func (s *service) Stop() {
s.scheduler.Stop()
}
func (s *service) ScheduleTask(interval int64, immediate bool, task func()) error {
if immediate {
_, err := s.scheduler.Every(int(interval)).Seconds().Do(task)
return err
}
_, err := s.scheduler.Every(int(interval)).Seconds().WaitForSchedule().Do(task)
return err
}
func (s *service) ScheduleTaskOnce(at int64, task func()) error {
delay := at - time.Now().Unix()
if delay < 0 {
return fmt.Errorf("cannot schedule task in the past")
}
_, err := s.scheduler.Every(int(delay)).Seconds().WaitForSchedule().LimitRunsTo(1).Do(task)
return err
}

View File

@@ -0,0 +1,483 @@
package txbuilder
import (
"context"
"encoding/hex"
"fmt"
"github.com/ark-network/ark/common/tree"
"github.com/ark-network/ark/internal/core/domain"
"github.com/ark-network/ark/internal/core/ports"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/vulpemventures/go-elements/address"
"github.com/vulpemventures/go-elements/network"
"github.com/vulpemventures/go-elements/psetv2"
"github.com/vulpemventures/go-elements/taproot"
)
const (
connectorAmount = uint64(450)
dustLimit = uint64(450)
)
type txBuilder struct {
wallet ports.WalletService
net *network.Network
roundLifetime int64 // in seconds
}
func NewTxBuilder(
wallet ports.WalletService, net network.Network, roundLifetime int64,
) ports.TxBuilder {
return &txBuilder{wallet, &net, roundLifetime}
}
func (b *txBuilder) GetVtxoScript(userPubkey, aspPubkey *secp256k1.PublicKey) ([]byte, error) {
outputScript, _, err := b.getLeafScriptAndTree(userPubkey, aspPubkey)
if err != nil {
return nil, err
}
return outputScript, nil
}
func (b *txBuilder) BuildSweepTx(wallet ports.WalletService, inputs []ports.SweepInput) (signedSweepTx string, err error) {
sweepPset, err := sweepTransaction(
wallet,
inputs,
b.net.AssetID,
)
if err != nil {
return "", err
}
sweepPsetBase64, err := sweepPset.ToBase64()
if err != nil {
return "", err
}
ctx := context.Background()
signedSweepPsetB64, err := wallet.SignPsetWithKey(ctx, sweepPsetBase64, nil)
if err != nil {
return "", err
}
signedPset, err := psetv2.NewPsetFromBase64(signedSweepPsetB64)
if err != nil {
return "", err
}
if err := psetv2.FinalizeAll(signedPset); err != nil {
return "", err
}
extractedTx, err := psetv2.Extract(signedPset)
if err != nil {
return "", err
}
return extractedTx.ToHex()
}
func (b *txBuilder) BuildForfeitTxs(
aspPubkey *secp256k1.PublicKey, poolTx string, payments []domain.Payment,
) (connectors []string, forfeitTxs []string, err error) {
connectorTxs, err := b.createConnectors(poolTx, payments, aspPubkey)
if err != nil {
return nil, nil, err
}
forfeitTxs, err = b.createForfeitTxs(aspPubkey, payments, connectorTxs)
if err != nil {
return nil, nil, err
}
for _, tx := range connectorTxs {
buf, _ := tx.ToBase64()
connectors = append(connectors, buf)
}
return connectors, forfeitTxs, nil
}
func (b *txBuilder) BuildPoolTx(
aspPubkey *secp256k1.PublicKey, payments []domain.Payment, minRelayFee uint64,
) (poolTx string, congestionTree tree.CongestionTree, err error) {
// The creation of the tree and the pool tx are tightly coupled:
// - building the tree requires knowing the shared outpoint (txid:vout)
// - building the pool tx requires knowing the shared output script and amount
// The idea here is to first create all the data for the outputs of the txs
// of the congestion tree to calculate the shared output script and amount.
// With these data the pool tx can be created, and once the shared utxo
// outpoint is obtained, the congestion tree can be finally created.
// The factory function `treeFactoryFn` returned below holds all outputs data
// generated in the process and takes the shared utxo outpoint as argument.
// This is safe as the memory allocated for `craftCongestionTree` is freed
// only after `BuildPoolTx` returns.
treeFactoryFn, sharedOutputScript, sharedOutputAmount, err := craftCongestionTree(
b.net.AssetID, aspPubkey, payments, minRelayFee, b.roundLifetime,
)
if err != nil {
return
}
ptx, err := b.createPoolTx(
sharedOutputAmount, sharedOutputScript, payments, aspPubkey,
)
if err != nil {
return
}
unsignedTx, err := ptx.UnsignedTx()
if err != nil {
return
}
tree, err := treeFactoryFn(psetv2.InputArgs{
Txid: unsignedTx.TxHash().String(),
TxIndex: 0,
})
if err != nil {
return
}
poolTx, err = ptx.ToBase64()
if err != nil {
return
}
congestionTree = tree
return
}
func (b *txBuilder) GetLeafSweepClosure(
node tree.Node, userPubKey *secp256k1.PublicKey,
) (*psetv2.TapLeafScript, int64, error) {
if !node.Leaf {
return nil, 0, fmt.Errorf("node is not a leaf")
}
pset, err := psetv2.NewPsetFromBase64(node.Tx)
if err != nil {
return nil, 0, err
}
input := pset.Inputs[0]
sweepLeaf, lifetime, err := extractSweepLeaf(input)
if err != nil {
return nil, 0, err
}
// craft the vtxo taproot tree
vtxoScript, err := tree.VtxoScript(userPubKey)
if err != nil {
return nil, 0, err
}
vtxoTaprootTree := taproot.AssembleTaprootScriptTree(
*vtxoScript,
sweepLeaf.TapElementsLeaf,
)
proofIndex := vtxoTaprootTree.LeafProofIndex[sweepLeaf.TapHash()]
proof := vtxoTaprootTree.LeafMerkleProofs[proofIndex]
return &psetv2.TapLeafScript{
TapElementsLeaf: proof.TapElementsLeaf,
ControlBlock: proof.ToControlBlock(sweepLeaf.ControlBlock.InternalKey),
}, lifetime, nil
}
func (b *txBuilder) getLeafScriptAndTree(
userPubkey, aspPubkey *secp256k1.PublicKey,
) ([]byte, *taproot.IndexedElementsTapScriptTree, error) {
redeemClosure, err := tree.VtxoScript(userPubkey)
if err != nil {
return nil, nil, err
}
sweepClosure, err := tree.SweepScript(aspPubkey, uint(b.roundLifetime))
if err != nil {
return nil, nil, err
}
taprootTree := taproot.AssembleTaprootScriptTree(
*redeemClosure, *sweepClosure,
)
root := taprootTree.RootNode.TapHash()
unspendableKey := tree.UnspendableKey()
taprootKey := taproot.ComputeTaprootOutputKey(unspendableKey, root[:])
outputScript, err := taprootOutputScript(taprootKey)
if err != nil {
return nil, nil, err
}
return outputScript, taprootTree, nil
}
func (b *txBuilder) createPoolTx(
sharedOutputAmount uint64, sharedOutputScript []byte,
payments []domain.Payment, aspPubKey *secp256k1.PublicKey,
) (*psetv2.Pset, error) {
aspScript, err := p2wpkhScript(aspPubKey, b.net)
if err != nil {
return nil, err
}
receivers := getOnchainReceivers(payments)
connectorsAmount := connectorAmount * countSpentVtxos(payments)
targetAmount := sharedOutputAmount + connectorsAmount
outputs := []psetv2.OutputArgs{
{
Asset: b.net.AssetID,
Amount: sharedOutputAmount,
Script: sharedOutputScript,
},
{
Asset: b.net.AssetID,
Amount: connectorsAmount,
Script: aspScript,
},
}
for _, receiver := range receivers {
targetAmount += receiver.Amount
receiverScript, err := address.ToOutputScript(receiver.OnchainAddress)
if err != nil {
return nil, err
}
outputs = append(outputs, psetv2.OutputArgs{
Asset: b.net.AssetID,
Amount: receiver.Amount,
Script: receiverScript,
})
}
ctx := context.Background()
utxos, change, err := b.wallet.SelectUtxos(ctx, b.net.AssetID, targetAmount)
if err != nil {
return nil, err
}
var dust uint64
if change > 0 {
if change < dustLimit {
dust = change
change = 0
} else {
outputs = append(outputs, psetv2.OutputArgs{
Asset: b.net.AssetID,
Amount: change,
Script: aspScript,
})
}
}
ptx, err := psetv2.New(nil, outputs, nil)
if err != nil {
return nil, err
}
updater, err := psetv2.NewUpdater(ptx)
if err != nil {
return nil, err
}
if err := addInputs(updater, utxos); err != nil {
return nil, err
}
b64, err := ptx.ToBase64()
if err != nil {
return nil, err
}
feeAmount, err := b.wallet.EstimateFees(ctx, b64)
if err != nil {
return nil, err
}
if dust > feeAmount {
feeAmount = dust
} else {
feeAmount += dust
}
if dust == 0 {
if feeAmount == change {
// fees = change, remove change output
ptx.Outputs = ptx.Outputs[:len(ptx.Outputs)-1]
} else if feeAmount < change {
// change covers the fees, reduce change amount
ptx.Outputs[len(ptx.Outputs)-1].Value = change - feeAmount
} else {
// change is not enough to cover fees, re-select utxos
if change > 0 {
// remove change output if present
ptx.Outputs = ptx.Outputs[:len(ptx.Outputs)-1]
}
newUtxos, change, err := b.wallet.SelectUtxos(ctx, b.net.AssetID, feeAmount-change)
if err != nil {
return nil, err
}
if change > 0 {
if err := updater.AddOutputs([]psetv2.OutputArgs{
{
Asset: b.net.AssetID,
Amount: change,
Script: aspScript,
},
}); err != nil {
return nil, err
}
}
if err := addInputs(updater, newUtxos); err != nil {
return nil, err
}
}
}
// add fee output
if err := updater.AddOutputs([]psetv2.OutputArgs{
{
Asset: b.net.AssetID,
Amount: feeAmount,
},
}); err != nil {
return nil, err
}
return ptx, nil
}
func (b *txBuilder) createConnectors(
poolTx string, payments []domain.Payment, aspPubkey *secp256k1.PublicKey,
) ([]*psetv2.Pset, error) {
txid, _ := getTxid(poolTx)
aspScript, err := p2wpkhScript(aspPubkey, b.net)
if err != nil {
return nil, err
}
connectorOutput := psetv2.OutputArgs{
Asset: b.net.AssetID,
Script: aspScript,
Amount: connectorAmount,
}
numberOfConnectors := countSpentVtxos(payments)
previousInput := psetv2.InputArgs{
Txid: txid,
TxIndex: 1,
}
if numberOfConnectors == 1 {
outputs := []psetv2.OutputArgs{connectorOutput}
connectorTx, err := craftConnectorTx(previousInput, outputs)
if err != nil {
return nil, err
}
return []*psetv2.Pset{connectorTx}, nil
}
totalConnectorAmount := connectorAmount * numberOfConnectors
connectors := make([]*psetv2.Pset, 0, numberOfConnectors-1)
for i := uint64(0); i < numberOfConnectors-1; i++ {
outputs := []psetv2.OutputArgs{connectorOutput}
totalConnectorAmount -= connectorAmount
if totalConnectorAmount > 0 {
outputs = append(outputs, psetv2.OutputArgs{
Asset: b.net.AssetID,
Script: aspScript,
Amount: totalConnectorAmount,
})
}
connectorTx, err := craftConnectorTx(previousInput, outputs)
if err != nil {
return nil, err
}
txid, _ := getPsetId(connectorTx)
previousInput = psetv2.InputArgs{
Txid: txid,
TxIndex: 1,
}
connectors = append(connectors, connectorTx)
}
return connectors, nil
}
func (b *txBuilder) createForfeitTxs(
aspPubkey *secp256k1.PublicKey, payments []domain.Payment, connectors []*psetv2.Pset,
) ([]string, error) {
aspScript, err := p2wpkhScript(aspPubkey, b.net)
if err != nil {
return nil, err
}
forfeitTxs := make([]string, 0)
for _, payment := range payments {
for _, vtxo := range payment.Inputs {
pubkeyBytes, err := hex.DecodeString(vtxo.Pubkey)
if err != nil {
return nil, fmt.Errorf("failed to decode pubkey: %s", err)
}
vtxoPubkey, err := secp256k1.ParsePubKey(pubkeyBytes)
if err != nil {
return nil, err
}
vtxoScript, vtxoTaprootTree, err := b.getLeafScriptAndTree(vtxoPubkey, aspPubkey)
if err != nil {
return nil, err
}
for _, connector := range connectors {
txs, err := craftForfeitTxs(
connector, vtxo, vtxoTaprootTree, vtxoScript, aspScript,
)
if err != nil {
return nil, err
}
forfeitTxs = append(forfeitTxs, txs...)
}
}
}
return forfeitTxs, nil
}
// given a congestion tree input, searches and returns the sweep leaf and its lifetime in seconds
func extractSweepLeaf(input psetv2.Input) (sweepLeaf *psetv2.TapLeafScript, lifetime int64, err error) {
for _, leaf := range input.TapLeafScript {
isSweep, _, seconds, err := tree.DecodeSweepScript(leaf.Script)
if err != nil {
return nil, 0, err
}
if isSweep {
lifetime = int64(seconds)
sweepLeaf = &leaf
break
}
}
if sweepLeaf == nil {
return nil, 0, fmt.Errorf("sweep leaf not found")
}
return sweepLeaf, lifetime, nil
}

View File

@@ -0,0 +1,229 @@
package txbuilder_test
import (
"crypto/rand"
"encoding/hex"
"encoding/json"
"os"
"testing"
"github.com/ark-network/ark/common"
"github.com/ark-network/ark/common/tree"
"github.com/ark-network/ark/internal/core/domain"
"github.com/ark-network/ark/internal/core/ports"
txbuilder "github.com/ark-network/ark/internal/infrastructure/tx-builder/covenant"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/vulpemventures/go-elements/network"
"github.com/vulpemventures/go-elements/psetv2"
)
const (
testingKey = "apub1qgvdtj5ttpuhkldavhq8thtm5auyk0ec4dcmrfdgu0u5hgp9we22v3hrs4x"
minRelayFee = uint64(30)
roundLifetime = int64(1209344)
)
var (
wallet *mockedWallet
pubkey *secp256k1.PublicKey
)
func TestMain(m *testing.M) {
wallet = &mockedWallet{}
wallet.On("EstimateFees", mock.Anything, mock.Anything).
Return(uint64(100), nil)
wallet.On("SelectUtxos", mock.Anything, mock.Anything, mock.Anything).
Return(randomInput, uint64(0), nil)
_, pubkey, _ = common.DecodePubKey(testingKey)
os.Exit(m.Run())
}
func TestBuildPoolTx(t *testing.T) {
builder := txbuilder.NewTxBuilder(wallet, network.Liquid, roundLifetime)
fixtures, err := parsePoolTxFixtures()
require.NoError(t, err)
require.NotEmpty(t, fixtures)
if len(fixtures.Valid) > 0 {
t.Run("valid", func(t *testing.T) {
for _, f := range fixtures.Valid {
poolTx, congestionTree, err := builder.BuildPoolTx(pubkey, f.Payments, minRelayFee)
require.NoError(t, err)
require.NotEmpty(t, poolTx)
require.NotEmpty(t, congestionTree)
require.Equal(t, f.ExpectedNumOfNodes, congestionTree.NumberOfNodes())
require.Len(t, congestionTree.Leaves(), f.ExpectedNumOfLeaves)
err = tree.ValidateCongestionTree(congestionTree, poolTx, pubkey, roundLifetime)
require.NoError(t, err)
}
})
}
if len(fixtures.Invalid) > 0 {
t.Run("invalid", func(t *testing.T) {
for _, f := range fixtures.Invalid {
poolTx, congestionTree, err := builder.BuildPoolTx(pubkey, f.Payments, minRelayFee)
require.EqualError(t, err, f.ExpectedErr)
require.Empty(t, poolTx)
require.Empty(t, congestionTree)
}
})
}
}
func TestBuildForfeitTxs(t *testing.T) {
builder := txbuilder.NewTxBuilder(wallet, network.Liquid, 1209344)
fixtures, err := parseForfeitTxsFixtures()
require.NoError(t, err)
require.NotEmpty(t, fixtures)
if len(fixtures.Valid) > 0 {
t.Run("valid", func(t *testing.T) {
for _, f := range fixtures.Valid {
connectors, forfeitTxs, err := builder.BuildForfeitTxs(
pubkey, f.PoolTx, f.Payments,
)
require.NoError(t, err)
require.Len(t, connectors, f.ExpectedNumOfConnectors)
require.Len(t, forfeitTxs, f.ExpectedNumOfForfeitTxs)
expectedInputTxid := f.PoolTxid
// Verify the chain of connectors
for _, connector := range connectors {
tx, err := psetv2.NewPsetFromBase64(connector)
require.NoError(t, err)
require.NotNil(t, tx)
require.Len(t, tx.Inputs, 1)
require.Len(t, tx.Outputs, 2)
inputTxid := chainhash.Hash(tx.Inputs[0].PreviousTxid).String()
require.Equal(t, expectedInputTxid, inputTxid)
require.Equal(t, 1, int(tx.Inputs[0].PreviousTxIndex))
expectedInputTxid = getTxid(tx)
}
// decode and check forfeit txs
for _, forfeitTx := range forfeitTxs {
tx, err := psetv2.NewPsetFromBase64(forfeitTx)
require.NoError(t, err)
require.Len(t, tx.Inputs, 2)
require.Len(t, tx.Outputs, 2)
}
}
})
}
if len(fixtures.Invalid) > 0 {
t.Run("invalid", func(t *testing.T) {
for _, f := range fixtures.Invalid {
connectors, forfeitTxs, err := builder.BuildForfeitTxs(
pubkey, f.PoolTx, f.Payments,
)
require.EqualError(t, err, f.ExpectedErr)
require.Empty(t, connectors)
require.Empty(t, forfeitTxs)
}
})
}
}
func randomInput() []ports.TxInput {
txid := randomHex(32)
input := &mockedInput{}
input.On("GetAsset").Return("5ac9f65c0efcc4775e0baec4ec03abdde22473cd3cf33c0419ca290e0751b225")
input.On("GetValue").Return(uint64(1000))
input.On("GetScript").Return("a914ea9f486e82efb3dd83a69fd96e3f0113757da03c87")
input.On("GetTxid").Return(txid)
input.On("GetIndex").Return(uint32(0))
return []ports.TxInput{input}
}
func randomHex(len int) string {
buf := make([]byte, len)
// nolint
rand.Read(buf)
return hex.EncodeToString(buf)
}
type poolTxFixtures struct {
Valid []struct {
Payments []domain.Payment
ExpectedNumOfNodes int
ExpectedNumOfLeaves int
}
Invalid []struct {
Payments []domain.Payment
ExpectedErr string
}
}
func parsePoolTxFixtures() (*poolTxFixtures, error) {
file, err := os.ReadFile("testdata/fixtures.json")
if err != nil {
return nil, err
}
v := map[string]interface{}{}
if err := json.Unmarshal(file, &v); err != nil {
return nil, err
}
vv := v["buildPoolTx"].(map[string]interface{})
file, _ = json.Marshal(vv)
var fixtures poolTxFixtures
if err := json.Unmarshal(file, &fixtures); err != nil {
return nil, err
}
return &fixtures, nil
}
type forfeitTxsFixtures struct {
Valid []struct {
Payments []domain.Payment
ExpectedNumOfConnectors int
ExpectedNumOfForfeitTxs int
PoolTx string
PoolTxid string
}
Invalid []struct {
Payments []domain.Payment
ExpectedErr string
PoolTx string
}
}
func parseForfeitTxsFixtures() (*forfeitTxsFixtures, error) {
file, err := os.ReadFile("testdata/fixtures.json")
if err != nil {
return nil, err
}
v := map[string]interface{}{}
if err := json.Unmarshal(file, &v); err != nil {
return nil, err
}
vv := v["buildForfeitTxs"].(map[string]interface{})
file, _ = json.Marshal(vv)
var fixtures forfeitTxsFixtures
if err := json.Unmarshal(file, &fixtures); err != nil {
return nil, err
}
return &fixtures, nil
}
func getTxid(tx *psetv2.Pset) string {
utx, _ := tx.UnsignedTx()
return utx.TxHash().String()
}

View File

@@ -0,0 +1,48 @@
package txbuilder
import (
"github.com/vulpemventures/go-elements/psetv2"
"github.com/vulpemventures/go-elements/transaction"
)
func craftConnectorTx(
input psetv2.InputArgs, outputs []psetv2.OutputArgs,
) (*psetv2.Pset, error) {
ptx, _ := psetv2.New(nil, nil, nil)
updater, _ := psetv2.NewUpdater(ptx)
if err := updater.AddInputs(
[]psetv2.InputArgs{input},
); err != nil {
return nil, err
}
// TODO: add prevout.
if err := updater.AddOutputs(outputs); err != nil {
return nil, err
}
return ptx, nil
}
func getConnectorInputs(pset *psetv2.Pset) ([]psetv2.InputArgs, []*transaction.TxOutput) {
txID, _ := getPsetId(pset)
inputs := make([]psetv2.InputArgs, 0, len(pset.Outputs))
witnessUtxos := make([]*transaction.TxOutput, 0, len(pset.Outputs))
for i, output := range pset.Outputs {
utx, _ := pset.UnsignedTx()
if output.Value == connectorAmount && len(output.Script) > 0 {
inputs = append(inputs, psetv2.InputArgs{
Txid: txID,
TxIndex: uint32(i),
})
witnessUtxos = append(witnessUtxos, utx.Outputs[i])
}
}
return inputs, witnessUtxos
}

View File

@@ -0,0 +1,104 @@
package txbuilder
import (
"github.com/ark-network/ark/common/tree"
"github.com/ark-network/ark/internal/core/domain"
"github.com/btcsuite/btcd/txscript"
"github.com/vulpemventures/go-elements/elementsutil"
"github.com/vulpemventures/go-elements/psetv2"
"github.com/vulpemventures/go-elements/taproot"
"github.com/vulpemventures/go-elements/transaction"
)
func craftForfeitTxs(
connectorTx *psetv2.Pset,
vtxo domain.Vtxo,
vtxoTaprootTree *taproot.IndexedElementsTapScriptTree,
vtxoScript, aspScript []byte,
) (forfeitTxs []string, err error) {
connectors, prevouts := getConnectorInputs(connectorTx)
for i, connectorInput := range connectors {
connectorPrevout := prevouts[i]
asset := elementsutil.AssetHashFromBytes(connectorPrevout.Asset)
pset, err := psetv2.New(nil, nil, nil)
if err != nil {
return nil, err
}
updater, err := psetv2.NewUpdater(pset)
if err != nil {
return nil, err
}
vtxoInput := psetv2.InputArgs{
Txid: vtxo.Txid,
TxIndex: vtxo.VOut,
}
vtxoAmount, _ := elementsutil.ValueToBytes(vtxo.Amount)
vtxoPrevout := &transaction.TxOutput{
Asset: connectorPrevout.Asset,
Value: vtxoAmount,
Script: vtxoScript,
}
if err := updater.AddInputs([]psetv2.InputArgs{connectorInput, vtxoInput}); err != nil {
return nil, err
}
if err = updater.AddInWitnessUtxo(0, connectorPrevout); err != nil {
return nil, err
}
if err := updater.AddInSighashType(0, txscript.SigHashAll); err != nil {
return nil, err
}
if err = updater.AddInWitnessUtxo(1, vtxoPrevout); err != nil {
return nil, err
}
if err := updater.AddInSighashType(1, txscript.SigHashDefault); err != nil {
return nil, err
}
unspendableKey := tree.UnspendableKey()
for _, proof := range vtxoTaprootTree.LeafMerkleProofs {
tapScript := psetv2.NewTapLeafScript(proof, unspendableKey)
if err := updater.AddInTapLeafScript(1, tapScript); err != nil {
return nil, err
}
}
connectorAmount, err := elementsutil.ValueFromBytes(connectorPrevout.Value)
if err != nil {
return nil, err
}
err = updater.AddOutputs([]psetv2.OutputArgs{
{
Asset: asset,
Amount: vtxo.Amount + connectorAmount - 30,
Script: aspScript,
},
{
Asset: asset,
Amount: 30,
},
})
if err != nil {
return nil, err
}
tx, err := pset.ToBase64()
if err != nil {
return nil, err
}
forfeitTxs = append(forfeitTxs, tx)
}
return forfeitTxs, nil
}

View File

@@ -0,0 +1,203 @@
package txbuilder_test
import (
"context"
"github.com/ark-network/ark/internal/core/domain"
"github.com/ark-network/ark/internal/core/ports"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/stretchr/testify/mock"
)
type mockedWallet struct {
mock.Mock
}
// BroadcastTransaction implements ports.WalletService.
func (m *mockedWallet) BroadcastTransaction(ctx context.Context, txHex string) (string, error) {
args := m.Called(ctx, txHex)
var res string
if a := args.Get(0); a != nil {
res = a.(string)
}
return res, args.Error(1)
}
// Close implements ports.WalletService.
func (m *mockedWallet) Close() {
m.Called()
}
// DeriveAddresses implements ports.WalletService.
func (m *mockedWallet) DeriveAddresses(ctx context.Context, num int) ([]string, error) {
args := m.Called(ctx, num)
var res []string
if a := args.Get(0); a != nil {
res = a.([]string)
}
return res, args.Error(1)
}
// GetPubkey implements ports.WalletService.
func (m *mockedWallet) GetPubkey(ctx context.Context) (*secp256k1.PublicKey, error) {
args := m.Called(ctx)
var res *secp256k1.PublicKey
if a := args.Get(0); a != nil {
res = a.(*secp256k1.PublicKey)
}
return res, args.Error(1)
}
// SignPset implements ports.WalletService.
func (m *mockedWallet) SignPset(ctx context.Context, pset string, extractRawTx bool) (string, error) {
args := m.Called(ctx, pset, extractRawTx)
var res string
if a := args.Get(0); a != nil {
res = a.(string)
}
return res, args.Error(1)
}
// Status implements ports.WalletService.
func (m *mockedWallet) Status(ctx context.Context) (ports.WalletStatus, error) {
args := m.Called(ctx)
var res ports.WalletStatus
if a := args.Get(0); a != nil {
res = a.(ports.WalletStatus)
}
return res, args.Error(1)
}
func (m *mockedWallet) SelectUtxos(ctx context.Context, asset string, amount uint64) ([]ports.TxInput, uint64, error) {
args := m.Called(ctx, asset, amount)
var res0 func() []ports.TxInput
if a := args.Get(0); a != nil {
res0 = a.(func() []ports.TxInput)
}
var res1 uint64
if a := args.Get(1); a != nil {
res1 = a.(uint64)
}
return res0(), res1, args.Error(2)
}
func (m *mockedWallet) EstimateFees(ctx context.Context, pset string) (uint64, error) {
args := m.Called(ctx, pset)
var res uint64
if a := args.Get(0); a != nil {
res = a.(uint64)
}
return res, args.Error(1)
}
func (m *mockedWallet) IsTransactionPublished(ctx context.Context, txid string) (bool, int64, error) {
args := m.Called(ctx, txid)
var res bool
if a := args.Get(0); a != nil {
res = a.(bool)
}
var blocktime int64
if b := args.Get(1); b != nil {
blocktime = b.(int64)
}
return res, blocktime, args.Error(2)
}
func (m *mockedWallet) SignPsetWithKey(ctx context.Context, pset string, inputIndexes []int) (string, error) {
args := m.Called(ctx, pset, inputIndexes)
var res string
if a := args.Get(0); a != nil {
res = a.(string)
}
return res, args.Error(1)
}
func (m *mockedWallet) WatchScripts(
ctx context.Context, scripts []string,
) error {
args := m.Called(ctx, scripts)
return args.Error(0)
}
func (m *mockedWallet) UnwatchScripts(
ctx context.Context, scripts []string,
) error {
args := m.Called(ctx, scripts)
return args.Error(0)
}
func (m *mockedWallet) GetNotificationChannel(ctx context.Context) chan []domain.VtxoKey {
args := m.Called(ctx)
var res chan []domain.VtxoKey
if a := args.Get(0); a != nil {
res = a.(chan []domain.VtxoKey)
}
return res
}
type mockedInput struct {
mock.Mock
}
func (m *mockedInput) GetTxid() string {
args := m.Called()
var res string
if a := args.Get(0); a != nil {
res = a.(string)
}
return res
}
func (m *mockedInput) GetIndex() uint32 {
args := m.Called()
var res uint32
if a := args.Get(0); a != nil {
res = a.(uint32)
}
return res
}
func (m *mockedInput) GetScript() string {
args := m.Called()
var res string
if a := args.Get(0); a != nil {
res = a.(string)
}
return res
}
func (m *mockedInput) GetAsset() string {
args := m.Called()
var res string
if a := args.Get(0); a != nil {
res = a.(string)
}
return res
}
func (m *mockedInput) GetValue() uint64 {
args := m.Called()
var res uint64
if a := args.Get(0); a != nil {
res = a.(uint64)
}
return res
}

View File

@@ -0,0 +1,135 @@
package txbuilder
import (
"context"
"fmt"
"github.com/ark-network/ark/common"
"github.com/ark-network/ark/common/tree"
"github.com/ark-network/ark/internal/core/ports"
"github.com/vulpemventures/go-elements/address"
"github.com/vulpemventures/go-elements/elementsutil"
"github.com/vulpemventures/go-elements/psetv2"
"github.com/vulpemventures/go-elements/taproot"
"github.com/vulpemventures/go-elements/transaction"
)
func sweepTransaction(
wallet ports.WalletService,
sweepInputs []ports.SweepInput,
lbtc string,
) (*psetv2.Pset, error) {
sweepPset, err := psetv2.New(nil, nil, nil)
if err != nil {
return nil, err
}
updater, err := psetv2.NewUpdater(sweepPset)
if err != nil {
return nil, err
}
amount := uint64(0)
for i, input := range sweepInputs {
leaf := input.SweepLeaf
isSweep, _, lifetime, err := tree.DecodeSweepScript(leaf.Script)
if err != nil {
return nil, err
}
if isSweep {
amount += input.Amount
if err := updater.AddInputs([]psetv2.InputArgs{input.InputArgs}); err != nil {
return nil, err
}
if err := updater.AddInTapLeafScript(i, leaf); err != nil {
return nil, err
}
assetHash, err := elementsutil.AssetHashToBytes(lbtc)
if err != nil {
return nil, err
}
value, err := elementsutil.ValueToBytes(input.Amount)
if err != nil {
return nil, err
}
root := leaf.ControlBlock.RootHash(leaf.Script)
taprootKey := taproot.ComputeTaprootOutputKey(leaf.ControlBlock.InternalKey, root)
script, err := taprootOutputScript(taprootKey)
if err != nil {
return nil, err
}
witnessUtxo := transaction.NewTxOutput(assetHash, value, script)
if err := updater.AddInWitnessUtxo(i, witnessUtxo); err != nil {
return nil, err
}
sequence, err := common.BIP68EncodeAsNumber(lifetime)
if err != nil {
return nil, err
}
updater.Pset.Inputs[i].Sequence = sequence
continue
}
return nil, fmt.Errorf("invalid sweep script")
}
ctx := context.Background()
sweepAddress, err := wallet.DeriveAddresses(ctx, 1)
if err != nil {
return nil, err
}
script, err := address.ToOutputScript(sweepAddress[0])
if err != nil {
return nil, err
}
if err := updater.AddOutputs([]psetv2.OutputArgs{
{
Asset: lbtc,
Amount: amount,
Script: script,
},
}); err != nil {
return nil, err
}
b64, err := sweepPset.ToBase64()
if err != nil {
return nil, err
}
fees, err := wallet.EstimateFees(ctx, b64)
if err != nil {
return nil, err
}
if amount < fees {
return nil, fmt.Errorf("insufficient funds (%d) to cover fees (%d) for sweep transaction", amount, fees)
}
updater.Pset.Outputs[0].Value = amount - fees
if err := updater.AddOutputs([]psetv2.OutputArgs{
{
Asset: lbtc,
Amount: fees,
},
}); err != nil {
return nil, err
}
return sweepPset, nil
}

View File

@@ -0,0 +1,229 @@
{
"buildPoolTx": {
"valid": [
{
"payments": [
{
"id": "0",
"inputs": [
{
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
"vout": 0,
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
"amount": 1100
}
],
"receivers": [
{
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
"amount": 1100
}
]
}
],
"expectedNumOfNodes": 1,
"expectedNumOfLeaves": 1
},
{
"payments": [
{
"id": "0",
"inputs": [
{
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
"vout": 0,
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
"amount": 1100
}
],
"receivers": [
{
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
"amount": 600
},
{
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
"amount": 500
}
]
}
],
"expectedNumOfNodes": 3,
"expectedNumOfLeaves": 2
},
{
"payments": [
{
"id": "0",
"inputs": [
{
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
"vout": 0,
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
"amount": 1100
}
],
"receivers": [
{
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
"amount": 600
},
{
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
"amount": 500
}
]
},
{
"id": "0",
"inputs": [
{
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
"vout": 0,
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
"amount": 1100
}
],
"receivers": [
{
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
"amount": 600
},
{
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
"amount": 500
}
]
},
{
"id": "0",
"inputs": [
{
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
"vout": 0,
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
"amount": 1100
}
],
"receivers": [
{
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
"amount": 600
},
{
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
"amount": 500
}
]
}
],
"expectedNumOfNodes": 11,
"expectedNumOfLeaves": 6
},
{
"payments": [
{
"id": "a242cdd8-f3d5-46c0-ae98-94135a2bee3f",
"inputs": [
{
"txid": "755c820771284d85ea4bbcc246565b4eddadc44237a7e57a0f9cb78a840d1d41",
"vout": 0,
"pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
"amount": 1000
},
{
"txid": "66a0df86fcdeb84b8877adfe0b2c556dba30305d72ddbd4c49355f6930355357",
"vout": 0,
"pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
"amount": 1000
},
{
"txid": "9913159bc7aa493ca53cbb9cbc88f97ba01137c814009dc7ef520c3fafc67909",
"vout": 1,
"pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
"amount": 500
},
{
"txid": "5e10e77a7cdedc153be5193a4b6055a7802706ded4f2a9efefe86ed2f9a6ae60",
"vout": 0,
"pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
"amount": 1000
},
{
"txid": "5e10e77a7cdedc153be5193a4b6055a7802706ded4f2a9efefe86ed2f9a6ae60",
"vout": 1,
"pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
"amount": 1000
}
],
"receivers": [
{
"pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
"amount": 1000
},
{
"pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
"amount": 1000
},
{
"pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
"amount": 1000
},
{
"pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
"amount": 1000
},
{
"pubkey": "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
"amount": 500
}
]
}
],
"expectedNumOfNodes": 9,
"expectedNumOfLeaves": 5
}
],
"invalid": []
},
"buildForfeitTxs": {
"valid": [
{
"payments": [
{
"id": "0",
"inputs": [
{
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
"vout": 0,
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
"amount": 600
},
{
"txid": "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
"vout": 1,
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
"amount": 500
}
],
"receivers": [
{
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
"amount": 600
},
{
"pubkey": "020000000000000000000000000000000000000000000000000000000000000002",
"amount": 500
}
]
}
],
"poolTx": "cHNldP8BAgQCAAAAAQQBAQEFAQMBBgEDAfsEAgAAAAABDiDk7dXxh4KQzgLO8i1ABtaLCe4aPL12GVhN1E9zM1ePLwEPBAAAAAABEAT/////AAEDCOgDAAAAAAAAAQQWABSNnpy01UJqd99eTg2M1IpdKId11gf8BHBzZXQCICWyUQcOKcoZBDzzPM1zJOLdqwPsxK4LXnfE/A5c9slaB/wEcHNldAgEAAAAAAABAwh4BQAAAAAAAAEEFgAUjZ6ctNVCanffXk4NjNSKXSiHddYH/ARwc2V0AiAlslEHDinKGQQ88zzNcyTi3asD7MSuC153xPwOXPbJWgf8BHBzZXQIBAAAAAAAAQMI9AEAAAAAAAABBAAH/ARwc2V0AiAlslEHDinKGQQ88zzNcyTi3asD7MSuC153xPwOXPbJWgf8BHBzZXQIBAAAAAAA",
"poolTxid": "7981fce656f266472cc742444527cb32a8bed8c90fed6d47adbfc4c8780d4d9a",
"expectedNumOfForfeitTxs": 4,
"expectedNumOfConnectors": 1
}
],
"invalid": []
}
}

View File

@@ -0,0 +1,448 @@
package txbuilder
import (
"encoding/hex"
"fmt"
"github.com/ark-network/ark/common/tree"
"github.com/ark-network/ark/internal/core/domain"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/vulpemventures/go-elements/psetv2"
"github.com/vulpemventures/go-elements/taproot"
)
type treeFactory func(outpoint psetv2.InputArgs) (tree.CongestionTree, error)
type node struct {
sweepKey *secp256k1.PublicKey
receivers []domain.Receiver
left *node
right *node
asset string
feeSats uint64
roundLifetime int64
_inputTaprootKey *secp256k1.PublicKey
_inputTaprootTree *taproot.IndexedElementsTapScriptTree
}
func (n *node) isLeaf() bool {
return len(n.receivers) == 1
}
func (n *node) getAmount() uint64 {
var amount uint64
for _, r := range n.receivers {
amount += r.Amount
}
if n.isLeaf() {
return amount
}
return amount + n.feeSats*uint64(n.countChildren())
}
func (n *node) countChildren() int {
result := 0
if n.left != nil {
result++
result += n.left.countChildren()
}
if n.right != nil {
result++
result += n.right.countChildren()
}
return result
}
func (n *node) getChildren() []*node {
if n.isLeaf() {
return nil
}
children := make([]*node, 0, 2)
if n.left != nil {
children = append(children, n.left)
}
if n.right != nil {
children = append(children, n.right)
}
return children
}
func (n *node) getOutputs() ([]psetv2.OutputArgs, error) {
if n.isLeaf() {
taprootKey, _, err := n.getVtxoWitnessData()
if err != nil {
return nil, err
}
script, err := taprootOutputScript(taprootKey)
if err != nil {
return nil, err
}
output := &psetv2.OutputArgs{
Asset: n.asset,
Amount: uint64(n.getAmount()),
Script: script,
}
return []psetv2.OutputArgs{*output}, nil
}
outputs := make([]psetv2.OutputArgs, 0, 2)
children := n.getChildren()
for _, child := range children {
childWitnessProgram, _, err := child.getWitnessData()
if err != nil {
return nil, err
}
script, err := taprootOutputScript(childWitnessProgram)
if err != nil {
return nil, err
}
outputs = append(outputs, psetv2.OutputArgs{
Asset: n.asset,
Amount: child.getAmount() + child.feeSats,
Script: script,
})
}
return outputs, nil
}
func (n *node) getWitnessData() (
*secp256k1.PublicKey, *taproot.IndexedElementsTapScriptTree, error,
) {
if n._inputTaprootKey != nil && n._inputTaprootTree != nil {
return n._inputTaprootKey, n._inputTaprootTree, nil
}
sweepClosure, err := tree.SweepScript(n.sweepKey, uint(n.roundLifetime))
if err != nil {
return nil, nil, err
}
if n.isLeaf() {
taprootKey, _, err := n.getVtxoWitnessData()
if err != nil {
return nil, nil, err
}
branchTaprootScript := tree.BranchScript(
taprootKey, nil, n.getAmount(), 0,
)
branchTaprootTree := taproot.AssembleTaprootScriptTree(
branchTaprootScript, *sweepClosure,
)
root := branchTaprootTree.RootNode.TapHash()
inputTapkey := taproot.ComputeTaprootOutputKey(
tree.UnspendableKey(),
root[:],
)
n._inputTaprootKey = inputTapkey
n._inputTaprootTree = branchTaprootTree
return inputTapkey, branchTaprootTree, nil
}
leftKey, _, err := n.left.getWitnessData()
if err != nil {
return nil, nil, err
}
rightKey, _, err := n.right.getWitnessData()
if err != nil {
return nil, nil, err
}
leftAmount := n.left.getAmount() + n.feeSats
rightAmount := n.right.getAmount() + n.feeSats
branchTaprootLeaf := tree.BranchScript(
leftKey, rightKey, leftAmount, rightAmount,
)
branchTaprootTree := taproot.AssembleTaprootScriptTree(
branchTaprootLeaf, *sweepClosure,
)
root := branchTaprootTree.RootNode.TapHash()
taprootKey := taproot.ComputeTaprootOutputKey(
tree.UnspendableKey(),
root[:],
)
n._inputTaprootKey = taprootKey
n._inputTaprootTree = branchTaprootTree
return taprootKey, branchTaprootTree, nil
}
func (n *node) getVtxoWitnessData() (
*secp256k1.PublicKey, *taproot.IndexedElementsTapScriptTree, error,
) {
if !n.isLeaf() {
return nil, nil, fmt.Errorf("cannot call vtxoWitness on a non-leaf node")
}
sweepClosure, err := tree.SweepScript(n.sweepKey, uint(n.roundLifetime))
if err != nil {
return nil, nil, err
}
key, err := hex.DecodeString(n.receivers[0].Pubkey)
if err != nil {
return nil, nil, err
}
pubkey, err := secp256k1.ParsePubKey(key)
if err != nil {
return nil, nil, err
}
vtxoLeaf, err := tree.VtxoScript(pubkey)
if err != nil {
return nil, nil, err
}
// TODO: add forfeit path
leafTaprootTree := taproot.AssembleTaprootScriptTree(
*vtxoLeaf, *sweepClosure,
)
root := leafTaprootTree.RootNode.TapHash()
taprootKey := taproot.ComputeTaprootOutputKey(
tree.UnspendableKey(),
root[:],
)
return taprootKey, leafTaprootTree, nil
}
func (n *node) getTreeNode(
input psetv2.InputArgs, tapTree *taproot.IndexedElementsTapScriptTree,
) (tree.Node, error) {
pset, err := n.getTx(input, tapTree)
if err != nil {
return tree.Node{}, err
}
txid, err := getPsetId(pset)
if err != nil {
return tree.Node{}, err
}
tx, err := pset.ToBase64()
if err != nil {
return tree.Node{}, err
}
parentTxid := chainhash.Hash(pset.Inputs[0].PreviousTxid).String()
return tree.Node{
Txid: txid,
Tx: tx,
ParentTxid: parentTxid,
Leaf: n.isLeaf(),
}, nil
}
func (n *node) getTx(
input psetv2.InputArgs, inputTapTree *taproot.IndexedElementsTapScriptTree,
) (*psetv2.Pset, error) {
pset, err := psetv2.New(nil, nil, nil)
if err != nil {
return nil, err
}
updater, err := psetv2.NewUpdater(pset)
if err != nil {
return nil, err
}
if err := addTaprootInput(
updater, input, tree.UnspendableKey(), inputTapTree,
); err != nil {
return nil, err
}
feeOutput := psetv2.OutputArgs{
Amount: uint64(n.feeSats),
Asset: n.asset,
}
outputs, err := n.getOutputs()
if err != nil {
return nil, err
}
if err := updater.AddOutputs(append(outputs, feeOutput)); err != nil {
return nil, err
}
return pset, nil
}
func (n *node) createFinalCongestionTree() treeFactory {
return func(poolTxInput psetv2.InputArgs) (tree.CongestionTree, error) {
congestionTree := make(tree.CongestionTree, 0)
_, taprootTree, err := n.getWitnessData()
if err != nil {
return nil, err
}
ins := []psetv2.InputArgs{poolTxInput}
inTrees := []*taproot.IndexedElementsTapScriptTree{taprootTree}
nodes := []*node{n}
for len(nodes) > 0 {
nextNodes := make([]*node, 0)
nextInputsArgs := make([]psetv2.InputArgs, 0)
nextTaprootTrees := make([]*taproot.IndexedElementsTapScriptTree, 0)
treeLevel := make([]tree.Node, 0)
for i, node := range nodes {
treeNode, err := node.getTreeNode(ins[i], inTrees[i])
if err != nil {
return nil, err
}
treeLevel = append(treeLevel, treeNode)
children := node.getChildren()
for i, child := range children {
_, taprootTree, err := child.getWitnessData()
if err != nil {
return nil, err
}
nextNodes = append(nextNodes, child)
nextInputsArgs = append(nextInputsArgs, psetv2.InputArgs{
Txid: treeNode.Txid,
TxIndex: uint32(i),
})
nextTaprootTrees = append(nextTaprootTrees, taprootTree)
}
}
congestionTree = append(congestionTree, treeLevel)
nodes = append([]*node{}, nextNodes...)
ins = append([]psetv2.InputArgs{}, nextInputsArgs...)
inTrees = append(
[]*taproot.IndexedElementsTapScriptTree{}, nextTaprootTrees...,
)
}
return congestionTree, nil
}
}
func craftCongestionTree(
asset string, aspPublicKey *secp256k1.PublicKey,
payments []domain.Payment, feeSatsPerNode uint64, roundLifetime int64,
) (
buildCongestionTree treeFactory,
sharedOutputScript []byte, sharedOutputAmount uint64, err error,
) {
receivers := getOffchainReceivers(payments)
root, err := createPartialCongestionTree(
receivers, aspPublicKey, asset, feeSatsPerNode, roundLifetime,
)
if err != nil {
return
}
taprootKey, _, err := root.getWitnessData()
if err != nil {
return
}
sharedOutputScript, err = taprootOutputScript(taprootKey)
if err != nil {
return
}
sharedOutputAmount = root.getAmount() + root.feeSats
buildCongestionTree = root.createFinalCongestionTree()
return
}
func createPartialCongestionTree(
receivers []domain.Receiver,
aspPublicKey *secp256k1.PublicKey,
asset string,
feeSatsPerNode uint64,
roundLifetime int64,
) (root *node, err error) {
if len(receivers) == 0 {
return nil, fmt.Errorf("no receivers provided")
}
nodes := make([]*node, 0, len(receivers))
for _, r := range receivers {
leafNode := &node{
sweepKey: aspPublicKey,
receivers: []domain.Receiver{r},
asset: asset,
feeSats: feeSatsPerNode,
roundLifetime: roundLifetime,
}
nodes = append(nodes, leafNode)
}
for len(nodes) > 1 {
nodes, err = createUpperLevel(nodes)
if err != nil {
return
}
}
return nodes[0], nil
}
func createUpperLevel(nodes []*node) ([]*node, error) {
if len(nodes)%2 != 0 {
last := nodes[len(nodes)-1]
pairs, err := createUpperLevel(nodes[:len(nodes)-1])
if err != nil {
return nil, err
}
return append(pairs, last), nil
}
pairs := make([]*node, 0, len(nodes)/2)
for i := 0; i < len(nodes); i += 2 {
left := nodes[i]
right := nodes[i+1]
branchNode := &node{
sweepKey: left.sweepKey,
receivers: append(left.receivers, right.receivers...),
left: left,
right: right,
asset: left.asset,
feeSats: left.feeSats,
roundLifetime: left.roundLifetime,
}
pairs = append(pairs, branchNode)
}
return pairs, nil
}

View File

@@ -0,0 +1,167 @@
package txbuilder
import (
"encoding/hex"
"fmt"
"github.com/ark-network/ark/internal/core/domain"
"github.com/ark-network/ark/internal/core/ports"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/txscript"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/vulpemventures/go-elements/address"
"github.com/vulpemventures/go-elements/elementsutil"
"github.com/vulpemventures/go-elements/network"
"github.com/vulpemventures/go-elements/payment"
"github.com/vulpemventures/go-elements/psetv2"
"github.com/vulpemventures/go-elements/taproot"
"github.com/vulpemventures/go-elements/transaction"
)
func p2wpkhScript(publicKey *secp256k1.PublicKey, net *network.Network) ([]byte, error) {
payment := payment.FromPublicKey(publicKey, net, nil)
addr, err := payment.WitnessPubKeyHash()
if err != nil {
return nil, err
}
return address.ToOutputScript(addr)
}
func getTxid(txStr string) (string, error) {
pset, err := psetv2.NewPsetFromBase64(txStr)
if err != nil {
return "", err
}
return getPsetId(pset)
}
func getPsetId(pset *psetv2.Pset) (string, error) {
utx, err := pset.UnsignedTx()
if err != nil {
return "", err
}
return utx.TxHash().String(), nil
}
func getOnchainReceivers(
payments []domain.Payment,
) []domain.Receiver {
receivers := make([]domain.Receiver, 0)
for _, payment := range payments {
for _, receiver := range payment.Receivers {
if receiver.IsOnchain() {
receivers = append(receivers, receiver)
}
}
}
return receivers
}
func getOffchainReceivers(
payments []domain.Payment,
) []domain.Receiver {
receivers := make([]domain.Receiver, 0)
for _, payment := range payments {
for _, receiver := range payment.Receivers {
if !receiver.IsOnchain() {
receivers = append(receivers, receiver)
}
}
}
return receivers
}
func toWitnessUtxo(in ports.TxInput) (*transaction.TxOutput, error) {
valueBytes, err := elementsutil.ValueToBytes(in.GetValue())
if err != nil {
return nil, fmt.Errorf("failed to convert value to bytes: %s", err)
}
assetBytes, err := elementsutil.AssetHashToBytes(in.GetAsset())
if err != nil {
return nil, fmt.Errorf("failed to convert asset to bytes: %s", err)
}
scriptBytes, err := hex.DecodeString(in.GetScript())
if err != nil {
return nil, fmt.Errorf("failed to decode script: %s", err)
}
return transaction.NewTxOutput(assetBytes, valueBytes, scriptBytes), nil
}
func countSpentVtxos(payments []domain.Payment) uint64 {
var sum uint64
for _, payment := range payments {
sum += uint64(len(payment.Inputs))
}
return sum
}
func addInputs(
updater *psetv2.Updater,
inputs []ports.TxInput,
) error {
for _, in := range inputs {
inputArg := psetv2.InputArgs{
Txid: in.GetTxid(),
TxIndex: in.GetIndex(),
}
witnessUtxo, err := toWitnessUtxo(in)
if err != nil {
return err
}
if err := updater.AddInputs([]psetv2.InputArgs{inputArg}); err != nil {
return err
}
index := int(updater.Pset.Global.InputCount) - 1
if err := updater.AddInWitnessUtxo(index, witnessUtxo); err != nil {
return err
}
if err := updater.AddInSighashType(index, txscript.SigHashAll); err != nil {
return err
}
}
return nil
}
// wrapper of updater methods adding a taproot input to the pset with all the necessary data to spend it via any taproot script
func addTaprootInput(
updater *psetv2.Updater,
input psetv2.InputArgs,
internalTaprootKey *secp256k1.PublicKey,
taprootTree *taproot.IndexedElementsTapScriptTree,
) error {
if err := updater.AddInputs([]psetv2.InputArgs{input}); err != nil {
return err
}
if err := updater.AddInTapInternalKey(0, schnorr.SerializePubKey(internalTaprootKey)); err != nil {
return err
}
for _, proof := range taprootTree.LeafMerkleProofs {
controlBlock := proof.ToControlBlock(internalTaprootKey)
if err := updater.AddInTapLeafScript(0, psetv2.TapLeafScript{
TapElementsLeaf: taproot.NewBaseTapElementsLeaf(proof.Script),
ControlBlock: controlBlock,
}); err != nil {
return err
}
}
return nil
}
func taprootOutputScript(taprootKey *secp256k1.PublicKey) ([]byte, error) {
return txscript.NewScriptBuilder().AddOp(txscript.OP_1).AddData(schnorr.SerializePubKey(taprootKey)).Script()
}

View File

@@ -0,0 +1,285 @@
package txbuilder
import (
"context"
"github.com/ark-network/ark/common/tree"
"github.com/ark-network/ark/internal/core/domain"
"github.com/ark-network/ark/internal/core/ports"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/vulpemventures/go-elements/address"
"github.com/vulpemventures/go-elements/network"
"github.com/vulpemventures/go-elements/payment"
"github.com/vulpemventures/go-elements/psetv2"
"github.com/vulpemventures/go-elements/transaction"
)
const (
connectorAmount = 450
sevenDays = 7 * 24 * 60 * 60
)
type txBuilder struct {
wallet ports.WalletService
net network.Network
}
func NewTxBuilder(
wallet ports.WalletService, net network.Network,
) ports.TxBuilder {
return &txBuilder{wallet, net}
}
// BuildSweepTx implements ports.TxBuilder.
func (*txBuilder) BuildSweepTx(wallet ports.WalletService, inputs []ports.SweepInput) (signedSweepTx string, err error) {
panic("unimplemented")
}
// BuildForfeitTxs implements ports.TxBuilder.
func (b *txBuilder) BuildForfeitTxs(
aspPubkey *secp256k1.PublicKey, poolTx string, payments []domain.Payment,
) (connectors []string, forfeitTxs []string, err error) {
poolTxID, err := getTxid(poolTx)
if err != nil {
return nil, nil, err
}
aspScript, err := p2wpkhScript(aspPubkey, b.net)
if err != nil {
return nil, nil, err
}
numberOfConnectors := countSpentVtxos(payments)
connectors, err = createConnectors(
poolTxID,
1,
psetv2.OutputArgs{
Asset: b.net.AssetID,
Amount: connectorAmount,
Script: aspScript,
},
aspScript,
numberOfConnectors,
)
if err != nil {
return nil, nil, err
}
connectorsAsInputs, err := connectorsToInputArgs(connectors)
if err != nil {
return nil, nil, err
}
forfeitTxs = make([]string, 0)
for _, payment := range payments {
for _, vtxo := range payment.Inputs {
for _, connector := range connectorsAsInputs {
forfeitTx, err := createForfeitTx(
connector,
psetv2.InputArgs{
Txid: vtxo.Txid,
TxIndex: vtxo.VOut,
},
vtxo.Amount,
aspScript,
b.net,
)
if err != nil {
return nil, nil, err
}
forfeitTxs = append(forfeitTxs, forfeitTx)
}
}
}
return connectors, forfeitTxs, nil
}
// BuildPoolTx implements ports.TxBuilder.
func (b *txBuilder) BuildPoolTx(
aspPubkey *secp256k1.PublicKey, payments []domain.Payment, minRelayFee uint64,
) (poolTx string, congestionTree tree.CongestionTree, err error) {
aspScriptBytes, err := p2wpkhScript(aspPubkey, b.net)
if err != nil {
return "", nil, err
}
offchainReceivers, onchainReceivers := receiversFromPayments(payments)
sharedOutputAmount := sumReceivers(offchainReceivers)
numberOfConnectors := countSpentVtxos(payments)
connectorOutputAmount := connectorAmount * numberOfConnectors
ctx := context.Background()
outputs := []psetv2.OutputArgs{
{
Asset: b.net.AssetID,
Amount: sharedOutputAmount,
Script: aspScriptBytes,
},
{
Asset: b.net.AssetID,
Amount: connectorOutputAmount,
Script: aspScriptBytes,
},
}
amountToSelect := sharedOutputAmount + connectorOutputAmount
for _, receiver := range onchainReceivers {
amountToSelect += receiver.Amount
receiverScript, err := address.ToOutputScript(receiver.OnchainAddress)
if err != nil {
return "", nil, err
}
outputs = append(outputs, psetv2.OutputArgs{
Asset: b.net.AssetID,
Amount: receiver.Amount,
Script: receiverScript,
})
}
utxos, change, err := b.wallet.SelectUtxos(ctx, b.net.AssetID, amountToSelect)
if err != nil {
return
}
if change > 0 {
outputs = append(outputs, psetv2.OutputArgs{
Asset: b.net.AssetID,
Amount: change,
Script: aspScriptBytes,
})
}
ptx, err := psetv2.New(toInputArgs(utxos), outputs, nil)
if err != nil {
return
}
utx, err := ptx.UnsignedTx()
if err != nil {
return
}
congestionTree, err = buildCongestionTree(
newOutputScriptFactory(aspPubkey, b.net),
b.net,
utx.TxHash().String(),
offchainReceivers,
)
if err != nil {
return
}
poolTx, err = ptx.ToBase64()
if err != nil {
return
}
return poolTx, congestionTree, err
}
func (b *txBuilder) GetVtxoScript(userPubkey, _ *secp256k1.PublicKey) ([]byte, error) {
p2wpkh := payment.FromPublicKey(userPubkey, &b.net, nil)
addr, _ := p2wpkh.WitnessPubKeyHash()
return address.ToOutputScript(addr)
}
func (b *txBuilder) GetLeafSweepClosure(
node tree.Node, userPubKey *secp256k1.PublicKey,
) (*psetv2.TapLeafScript, int64, error) {
panic("unimplemented")
}
func connectorsToInputArgs(connectors []string) ([]psetv2.InputArgs, error) {
inputs := make([]psetv2.InputArgs, 0, len(connectors)+1)
for i, psetb64 := range connectors {
tx, err := psetv2.NewPsetFromBase64(psetb64)
if err != nil {
return nil, err
}
utx, err := tx.UnsignedTx()
if err != nil {
return nil, err
}
txid := utx.TxHash().String()
for j := range tx.Outputs {
inputs = append(inputs, psetv2.InputArgs{
Txid: txid,
TxIndex: uint32(j),
})
if i != len(connectors)-1 {
break
}
}
}
return inputs, nil
}
func getTxid(txStr string) (string, error) {
pset, err := psetv2.NewPsetFromBase64(txStr)
if err != nil {
tx, err := transaction.NewTxFromHex(txStr)
if err != nil {
return "", err
}
return tx.TxHash().String(), nil
}
utx, err := pset.UnsignedTx()
if err != nil {
return "", err
}
return utx.TxHash().String(), nil
}
func countSpentVtxos(payments []domain.Payment) uint64 {
var sum uint64
for _, payment := range payments {
sum += uint64(len(payment.Inputs))
}
return sum
}
func receiversFromPayments(
payments []domain.Payment,
) (offchainReceivers, onchainReceivers []domain.Receiver) {
for _, payment := range payments {
for _, receiver := range payment.Receivers {
if receiver.IsOnchain() {
onchainReceivers = append(onchainReceivers, receiver)
} else {
offchainReceivers = append(offchainReceivers, receiver)
}
}
}
return
}
func sumReceivers(receivers []domain.Receiver) uint64 {
var sum uint64
for _, r := range receivers {
sum += r.Amount
}
return sum
}
func toInputArgs(
ins []ports.TxInput,
) []psetv2.InputArgs {
inputs := make([]psetv2.InputArgs, 0, len(ins))
for _, in := range ins {
inputs = append(inputs, psetv2.InputArgs{
Txid: in.GetTxid(),
TxIndex: in.GetIndex(),
})
}
return inputs
}

View File

@@ -0,0 +1,407 @@
package txbuilder_test
import (
"context"
"crypto/rand"
"encoding/hex"
"testing"
"github.com/ark-network/ark/common"
"github.com/ark-network/ark/internal/core/domain"
"github.com/ark-network/ark/internal/core/ports"
txbuilder "github.com/ark-network/ark/internal/infrastructure/tx-builder/dummy"
"github.com/btcsuite/btcd/chaincfg/chainhash"
secp256k1 "github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/stretchr/testify/require"
"github.com/vulpemventures/go-elements/network"
"github.com/vulpemventures/go-elements/psetv2"
)
const (
testingKey = "apub1qgvdtj5ttpuhkldavhq8thtm5auyk0ec4dcmrfdgu0u5hgp9we22v3hrs4x"
fakePoolTx = "cHNldP8BAgQCAAAAAQQBAQEFAQMBBgEDAfsEAgAAAAABDiDk7dXxh4KQzgLO8i1ABtaLCe4aPL12GVhN1E9zM1ePLwEPBAAAAAABEAT/////AAEDCOgDAAAAAAAAAQQWABSNnpy01UJqd99eTg2M1IpdKId11gf8BHBzZXQCICWyUQcOKcoZBDzzPM1zJOLdqwPsxK4LXnfE/A5c9slaB/wEcHNldAgEAAAAAAABAwh4BQAAAAAAAAEEFgAUjZ6ctNVCanffXk4NjNSKXSiHddYH/ARwc2V0AiAlslEHDinKGQQ88zzNcyTi3asD7MSuC153xPwOXPbJWgf8BHBzZXQIBAAAAAAAAQMI9AEAAAAAAAABBAAH/ARwc2V0AiAlslEHDinKGQQ88zzNcyTi3asD7MSuC153xPwOXPbJWgf8BHBzZXQIBAAAAAAA"
)
type input struct {
txid string
vout uint32
}
func (i *input) GetTxid() string {
return i.txid
}
func (i *input) GetIndex() uint32 {
return i.vout
}
func (i *input) GetScript() string {
return "a914ea9f486e82efb3dd83a69fd96e3f0113757da03c87"
}
func (i *input) GetAsset() string {
return "5ac9f65c0efcc4775e0baec4ec03abdde22473cd3cf33c0419ca290e0751b225"
}
func (i *input) GetValue() uint64 {
return 1000
}
type mockedWalletService struct{}
// BroadcastTransaction implements ports.WalletService.
func (*mockedWalletService) BroadcastTransaction(ctx context.Context, txHex string) (string, error) {
panic("unimplemented")
}
// Close implements ports.WalletService.
func (*mockedWalletService) Close() {
panic("unimplemented")
}
// DeriveAddresses implements ports.WalletService.
func (*mockedWalletService) DeriveAddresses(ctx context.Context, num int) ([]string, error) {
panic("unimplemented")
}
// GetPubkey implements ports.WalletService.
func (*mockedWalletService) GetPubkey(ctx context.Context) (*secp256k1.PublicKey, error) {
panic("unimplemented")
}
// SignPset implements ports.WalletService.
func (*mockedWalletService) SignPset(ctx context.Context, pset string, extractRawTx bool) (string, error) {
panic("unimplemented")
}
// Status implements ports.WalletService.
func (*mockedWalletService) Status(ctx context.Context) (ports.WalletStatus, error) {
panic("unimplemented")
}
func (*mockedWalletService) WatchScripts(ctx context.Context, scripts []string) error {
panic("unimplemented")
}
func (*mockedWalletService) UnwatchScripts(ctx context.Context, scripts []string) error {
panic("unimplemented")
}
func (*mockedWalletService) GetNotificationChannel(ctx context.Context) chan []domain.VtxoKey {
panic("unimplemented")
}
func (*mockedWalletService) SelectUtxos(ctx context.Context, asset string, amount uint64) ([]ports.TxInput, uint64, error) {
// random txid
bytes := make([]byte, 32)
if _, err := rand.Read(bytes); err != nil {
return nil, 0, err
}
fakeInput := input{
txid: hex.EncodeToString(bytes),
vout: 0,
}
return []ports.TxInput{&fakeInput}, 0, nil
}
func (*mockedWalletService) EstimateFees(ctx context.Context, pset string) (uint64, error) {
return 100, nil
}
func (*mockedWalletService) SignPsetWithKey(ctx context.Context, pset string, inputIndex []int) (string, error) {
panic("unimplemented")
}
func (*mockedWalletService) IsTransactionPublished(ctx context.Context, txid string) (bool, int64, error) {
panic("unimplemented")
}
func TestBuildCongestionTree(t *testing.T) {
builder := txbuilder.NewTxBuilder(&mockedWalletService{}, network.Liquid)
fixtures := []struct {
payments []domain.Payment
expectedNodesNum int // 2*len(receivers)-1
expectedLeavesNum int
}{
{
payments: []domain.Payment{
{
Id: "0",
Inputs: []domain.Vtxo{
{
VtxoKey: domain.VtxoKey{
Txid: "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
VOut: 0,
},
Receiver: domain.Receiver{
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
Amount: 600,
},
},
},
Receivers: []domain.Receiver{
{
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
Amount: 600,
},
{
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
Amount: 400,
},
},
},
},
expectedNodesNum: 3,
expectedLeavesNum: 2,
},
{
payments: []domain.Payment{
{
Id: "0",
Inputs: []domain.Vtxo{
{
VtxoKey: domain.VtxoKey{
Txid: "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
VOut: 0,
},
Receiver: domain.Receiver{
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
Amount: 600,
},
},
},
Receivers: []domain.Receiver{
{
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
Amount: 600,
},
{
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
Amount: 400,
},
},
},
{
Id: "0",
Inputs: []domain.Vtxo{
{
VtxoKey: domain.VtxoKey{
Txid: "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
VOut: 0,
},
Receiver: domain.Receiver{
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
Amount: 600,
},
},
},
Receivers: []domain.Receiver{
{
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
Amount: 600,
},
{
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
Amount: 400,
},
},
},
{
Id: "0",
Inputs: []domain.Vtxo{
{
VtxoKey: domain.VtxoKey{
Txid: "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
VOut: 0,
},
Receiver: domain.Receiver{
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
Amount: 600,
},
},
},
Receivers: []domain.Receiver{
{
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
Amount: 600,
},
{
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
Amount: 400,
},
},
},
},
expectedNodesNum: 11,
expectedLeavesNum: 6,
},
}
_, key, err := common.DecodePubKey(testingKey)
require.NoError(t, err)
require.NotNil(t, key)
for _, f := range fixtures {
poolTx, tree, err := builder.BuildPoolTx(key, f.payments, 30)
require.NoError(t, err)
require.Equal(t, f.expectedNodesNum, tree.NumberOfNodes())
require.Len(t, tree.Leaves(), f.expectedLeavesNum)
poolPset, err := psetv2.NewPsetFromBase64(poolTx)
require.NoError(t, err)
poolTxUnsigned, err := poolPset.UnsignedTx()
require.NoError(t, err)
poolTxID := poolTxUnsigned.TxHash().String()
// check the root
require.Len(t, tree[0], 1)
require.Equal(t, poolTxID, tree[0][0].ParentTxid)
// check the leaves
for _, leaf := range tree.Leaves() {
pset, err := psetv2.NewPsetFromBase64(leaf.Tx)
require.NoError(t, err)
require.Len(t, pset.Inputs, 1)
require.Len(t, pset.Outputs, 1)
inputTxID := chainhash.Hash(pset.Inputs[0].PreviousTxid).String()
require.Equal(t, leaf.ParentTxid, inputTxID)
}
// check the nodes
for _, level := range tree[:len(tree)-2] {
for _, node := range level {
pset, err := psetv2.NewPsetFromBase64(node.Tx)
require.NoError(t, err)
require.Len(t, pset.Inputs, 1)
require.Len(t, pset.Outputs, 2)
inputTxID := chainhash.Hash(pset.Inputs[0].PreviousTxid).String()
require.Equal(t, node.ParentTxid, inputTxID)
children := tree.Children(node.Txid)
require.Len(t, children, 2)
}
}
}
}
func TestBuildForfeitTxs(t *testing.T) {
builder := txbuilder.NewTxBuilder(&mockedWalletService{}, network.Liquid)
poolPset, err := psetv2.NewPsetFromBase64(fakePoolTx)
require.NoError(t, err)
poolTxUnsigned, err := poolPset.UnsignedTx()
require.NoError(t, err)
poolTxID := poolTxUnsigned.TxHash().String()
fixtures := []struct {
payments []domain.Payment
expectedNumOfForfeitTxs int
expectedNumOfConnectors int
}{
{
payments: []domain.Payment{
{
Id: "0",
Inputs: []domain.Vtxo{
{
VtxoKey: domain.VtxoKey{
Txid: "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
VOut: 0,
},
Receiver: domain.Receiver{
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
Amount: 600,
},
},
{
VtxoKey: domain.VtxoKey{
Txid: "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
VOut: 1,
},
Receiver: domain.Receiver{
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
Amount: 400,
},
},
},
Receivers: []domain.Receiver{
{
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
Amount: 600,
},
{
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
Amount: 400,
},
},
},
},
expectedNumOfForfeitTxs: 4,
expectedNumOfConnectors: 1,
},
}
_, key, err := common.DecodePubKey(testingKey)
require.NoError(t, err)
require.NotNil(t, key)
for _, f := range fixtures {
connectors, forfeitTxs, err := builder.BuildForfeitTxs(
key, fakePoolTx, f.payments,
)
require.NoError(t, err)
require.Len(t, connectors, f.expectedNumOfConnectors)
require.Len(t, forfeitTxs, f.expectedNumOfForfeitTxs)
// decode and check connectors
connectorsPsets := make([]*psetv2.Pset, 0, f.expectedNumOfConnectors)
for _, pset := range connectors {
p, err := psetv2.NewPsetFromBase64(pset)
require.NoError(t, err)
connectorsPsets = append(connectorsPsets, p)
}
for i, pset := range connectorsPsets {
require.Len(t, pset.Inputs, 1)
require.Len(t, pset.Outputs, 2)
expectedInputTxid := poolTxID
expectedInputVout := uint32(1)
if i > 0 {
tx, err := connectorsPsets[i-1].UnsignedTx()
require.NoError(t, err)
require.NotNil(t, tx)
expectedInputTxid = tx.TxHash().String()
}
inputTxid := chainhash.Hash(pset.Inputs[0].PreviousTxid).String()
require.Equal(t, expectedInputTxid, inputTxid)
require.Equal(t, expectedInputVout, pset.Inputs[0].PreviousTxIndex)
}
// decode and check forfeit txs
forfeitTxsPsets := make([]*psetv2.Pset, 0, f.expectedNumOfForfeitTxs)
for _, pset := range forfeitTxs {
p, err := psetv2.NewPsetFromBase64(pset)
require.NoError(t, err)
forfeitTxsPsets = append(forfeitTxsPsets, p)
}
// each forfeit tx should have 2 inputs and 2 outputs
for _, pset := range forfeitTxsPsets {
require.Len(t, pset.Inputs, 2)
require.Len(t, pset.Outputs, 1)
}
}
}

View File

@@ -0,0 +1,103 @@
package txbuilder
import (
"github.com/vulpemventures/go-elements/psetv2"
)
func createConnectors(
poolTxID string,
connectorOutputIndex uint32,
connectorOutput psetv2.OutputArgs,
changeScript []byte,
numberOfConnectors uint64,
) (connectorsPsets []string, err error) {
previousInput := psetv2.InputArgs{
Txid: poolTxID,
TxIndex: connectorOutputIndex,
}
if numberOfConnectors == 1 {
pset, err := psetv2.New(nil, nil, nil)
if err != nil {
return nil, err
}
updater, err := psetv2.NewUpdater(pset)
if err != nil {
return nil, err
}
err = updater.AddInputs([]psetv2.InputArgs{previousInput})
if err != nil {
return nil, err
}
err = updater.AddOutputs([]psetv2.OutputArgs{connectorOutput})
if err != nil {
return nil, err
}
base64, err := pset.ToBase64()
if err != nil {
return nil, err
}
return []string{base64}, nil
}
// compute the initial amount of the connectors output in pool transaction
remainingAmount := connectorAmount * numberOfConnectors
connectorsPset := make([]string, 0, numberOfConnectors-1)
for i := uint64(0); i < numberOfConnectors-1; i++ {
// create a new pset
pset, err := psetv2.New(nil, nil, nil)
if err != nil {
return nil, err
}
updater, err := psetv2.NewUpdater(pset)
if err != nil {
return nil, err
}
err = updater.AddInputs([]psetv2.InputArgs{previousInput})
if err != nil {
return nil, err
}
err = updater.AddOutputs([]psetv2.OutputArgs{connectorOutput})
if err != nil {
return nil, err
}
changeAmount := remainingAmount - connectorOutput.Amount
if changeAmount > 0 {
changeOutput := psetv2.OutputArgs{
Asset: connectorOutput.Asset,
Amount: changeAmount,
Script: changeScript,
}
err = updater.AddOutputs([]psetv2.OutputArgs{changeOutput})
if err != nil {
return nil, err
}
tx, _ := pset.UnsignedTx()
txid := tx.TxHash().String()
// make the change the next previousInput
previousInput = psetv2.InputArgs{
Txid: txid,
TxIndex: 1,
}
}
base64, err := pset.ToBase64()
if err != nil {
return nil, err
}
connectorsPset = append(connectorsPset, base64)
}
return connectorsPset, nil
}

View File

@@ -0,0 +1,42 @@
package txbuilder
import (
"github.com/vulpemventures/go-elements/network"
"github.com/vulpemventures/go-elements/psetv2"
)
func createForfeitTx(
connectorInput psetv2.InputArgs,
vtxoInput psetv2.InputArgs,
vtxoAmount uint64,
aspScript []byte,
net network.Network,
) (forfeitTx string, err error) {
pset, err := psetv2.New(nil, nil, nil)
if err != nil {
return "", err
}
updater, err := psetv2.NewUpdater(pset)
if err != nil {
return "", err
}
err = updater.AddInputs([]psetv2.InputArgs{connectorInput, vtxoInput})
if err != nil {
return "", err
}
err = updater.AddOutputs([]psetv2.OutputArgs{
{
Asset: net.AssetID,
Amount: vtxoAmount,
Script: aspScript,
},
})
if err != nil {
return "", err
}
return pset.ToBase64()
}

View File

@@ -0,0 +1,309 @@
package txbuilder
import (
"encoding/hex"
"github.com/ark-network/ark/common/tree"
"github.com/ark-network/ark/internal/core/domain"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/vulpemventures/go-elements/address"
"github.com/vulpemventures/go-elements/network"
"github.com/vulpemventures/go-elements/payment"
"github.com/vulpemventures/go-elements/psetv2"
)
const (
sharedOutputIndex = 0
)
type outputScriptFactory func(leaves []domain.Receiver) ([]byte, error)
func p2wpkhScript(publicKey *secp256k1.PublicKey, net network.Network) ([]byte, error) {
payment := payment.FromPublicKey(publicKey, &net, nil)
addr, err := payment.WitnessPubKeyHash()
if err != nil {
return nil, err
}
return address.ToOutputScript(addr)
}
// newOtputScriptFactory returns an output script factory func that lock funds using the ASP public key only on all branches psbt. The leaves are instead locked by the leaf public key.
func newOutputScriptFactory(aspPublicKey *secp256k1.PublicKey, net network.Network) outputScriptFactory {
return func(leaves []domain.Receiver) ([]byte, error) {
aspScript, err := p2wpkhScript(aspPublicKey, net)
if err != nil {
return nil, err
}
switch len(leaves) {
case 0:
return nil, nil
case 1: // it's a leaf
buf, err := hex.DecodeString(leaves[0].Pubkey)
if err != nil {
return nil, err
}
key, err := secp256k1.ParsePubKey(buf)
if err != nil {
return nil, err
}
return p2wpkhScript(key, net)
default: // it's a branch, lock funds with ASP public key
return aspScript, nil
}
}
}
// congestionTree builder iteratively creates a binary tree of Pset from a set of receivers
// it also expect createOutputScript func managing the output script creation and the network to use (mainly for L-BTC asset id)
func buildCongestionTree(
createOutputScript outputScriptFactory,
net network.Network,
poolTxID string,
receivers []domain.Receiver,
) (congestionTree tree.CongestionTree, err error) {
var nodes []*node
for _, r := range receivers {
nodes = append(nodes, newLeaf(createOutputScript, net, r))
}
for len(nodes) > 1 {
nodes, err = createTreeLevel(nodes)
if err != nil {
return nil, err
}
}
psets, err := nodes[0].psets(psetv2.InputArgs{
Txid: poolTxID,
TxIndex: sharedOutputIndex,
}, 0)
if err != nil {
return nil, err
}
maxLevel := 0
for _, psetWithLevel := range psets {
if psetWithLevel.level > maxLevel {
maxLevel = psetWithLevel.level
}
}
congestionTree = make(tree.CongestionTree, maxLevel+1)
for _, psetWithLevel := range psets {
utx, err := psetWithLevel.pset.UnsignedTx()
if err != nil {
return nil, err
}
txid := utx.TxHash().String()
psetB64, err := psetWithLevel.pset.ToBase64()
if err != nil {
return nil, err
}
parentTxid := chainhash.Hash(psetWithLevel.pset.Inputs[0].PreviousTxid).String()
congestionTree[psetWithLevel.level] = append(congestionTree[psetWithLevel.level], tree.Node{
Txid: txid,
Tx: psetB64,
ParentTxid: parentTxid,
Leaf: psetWithLevel.leaf,
})
}
return congestionTree, nil
}
func createTreeLevel(nodes []*node) ([]*node, error) {
if len(nodes)%2 != 0 {
last := nodes[len(nodes)-1]
pairs, err := createTreeLevel(nodes[:len(nodes)-1])
if err != nil {
return nil, err
}
return append(pairs, last), nil
}
pairs := make([]*node, 0, len(nodes)/2)
for i := 0; i < len(nodes); i += 2 {
pairs = append(pairs, newBranch(nodes[i], nodes[i+1]))
}
return pairs, nil
}
// internal struct to build a binary tree of Pset
type node struct {
receivers []domain.Receiver
left *node
right *node
createOutputScript outputScriptFactory
network network.Network
}
// create a node from a single receiver
func newLeaf(
createOutputScript outputScriptFactory,
network network.Network,
receiver domain.Receiver,
) *node {
return &node{
receivers: []domain.Receiver{receiver},
createOutputScript: createOutputScript,
network: network,
left: nil,
right: nil,
}
}
// aggregate two nodes into a branch node
func newBranch(
left *node,
right *node,
) *node {
return &node{
receivers: append(left.receivers, right.receivers...),
createOutputScript: left.createOutputScript,
network: left.network,
left: left,
right: right,
}
}
// is it the final node of the tree
func (n *node) isLeaf() bool {
return n.left == nil && n.right == nil
}
// compute the output amount of a node
func (n *node) amount() uint64 {
var amount uint64
for _, r := range n.receivers {
amount += r.Amount
}
return amount
}
// compute the output script of a node
func (n *node) script() ([]byte, error) {
return n.createOutputScript(n.receivers)
}
// use script & amount to create OutputArgs
func (n *node) output() (*psetv2.OutputArgs, error) {
script, err := n.script()
if err != nil {
return nil, err
}
return &psetv2.OutputArgs{
Asset: n.network.AssetID,
Amount: n.amount(),
Script: script,
}, nil
}
// create the node Pset from the previous node Pset represented by input arg
// if node is a branch, it adds two outputs to the Pset, one for the left branch and one for the right branch
// if node is a leaf, it only adds one output to the Pset (the node output)
func (n *node) pset(input psetv2.InputArgs) (*psetv2.Pset, error) {
pset, err := psetv2.New(nil, nil, nil)
if err != nil {
return nil, err
}
updater, err := psetv2.NewUpdater(pset)
if err != nil {
return nil, err
}
err = updater.AddInputs([]psetv2.InputArgs{input})
if err != nil {
return nil, err
}
if n.isLeaf() {
output, err := n.output()
if err != nil {
return nil, err
}
err = updater.AddOutputs([]psetv2.OutputArgs{*output})
if err != nil {
return nil, err
}
return pset, nil
}
outputLeft, err := n.left.output()
if err != nil {
return nil, err
}
outputRight, err := n.right.output()
if err != nil {
return nil, err
}
err = updater.AddOutputs([]psetv2.OutputArgs{*outputLeft, *outputRight})
if err != nil {
return nil, err
}
return pset, nil
}
type psetWithLevel struct {
pset *psetv2.Pset
level int
leaf bool
}
// create the node pset and all the psets of its children recursively, updating the input arg at each step
// the function stops when it reaches a leaf node
func (n *node) psets(input psetv2.InputArgs, level int) ([]psetWithLevel, error) {
pset, err := n.pset(input)
if err != nil {
return nil, err
}
nodeResult := []psetWithLevel{
{pset, level, n.isLeaf()},
}
if n.isLeaf() {
return nodeResult, nil
}
unsignedTx, err := pset.UnsignedTx()
if err != nil {
return nil, err
}
txID := unsignedTx.TxHash().String()
psetsLeft, err := n.left.psets(psetv2.InputArgs{
Txid: txID,
TxIndex: 0,
}, level+1)
if err != nil {
return nil, err
}
psetsRight, err := n.right.psets(psetv2.InputArgs{
Txid: txID,
TxIndex: 1,
}, level+1)
if err != nil {
return nil, err
}
return append(nodeResult, append(psetsLeft, psetsRight...)...), nil
}

View File

@@ -0,0 +1,46 @@
package grpcservice
import (
"crypto/tls"
"fmt"
"net"
)
type Config struct {
Port uint32
NoTLS bool
}
func (c Config) Validate() error {
lis, err := net.Listen("tcp", c.address())
if err != nil {
return fmt.Errorf("invalid port: %s", err)
}
defer lis.Close()
if !c.NoTLS {
return fmt.Errorf("tls termination not supported yet")
}
return nil
}
func (c Config) insecure() bool {
return c.NoTLS
}
func (c Config) address() string {
return fmt.Sprintf(":%d", c.Port)
}
func (c Config) listener() net.Listener {
lis, _ := net.Listen("tcp", c.address())
if c.insecure() {
return lis
}
return tls.NewListener(lis, c.tlsConfig())
}
func (c Config) tlsConfig() *tls.Config {
return nil
}

View File

@@ -0,0 +1,305 @@
package handlers
import (
"context"
"encoding/hex"
"sync"
arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1"
"github.com/ark-network/ark/common"
"github.com/ark-network/ark/common/tree"
"github.com/ark-network/ark/internal/core/application"
"github.com/ark-network/ark/internal/core/domain"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/google/uuid"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type listener struct {
id string
ch chan *arkv1.GetEventStreamResponse
}
type handler struct {
svc application.Service
listenersLock *sync.Mutex
listeners []*listener
}
func NewHandler(service application.Service) arkv1.ArkServiceServer {
h := &handler{
svc: service,
listenersLock: &sync.Mutex{},
listeners: make([]*listener, 0),
}
go h.listenToEvents()
return h
}
func (h *handler) Ping(ctx context.Context, req *arkv1.PingRequest) (*arkv1.PingResponse, error) {
if req.GetPaymentId() == "" {
return nil, status.Error(codes.InvalidArgument, "missing payment id")
}
if err := h.svc.UpdatePaymentStatus(ctx, req.GetPaymentId()); err != nil {
return nil, err
}
return &arkv1.PingResponse{}, nil
}
func (h *handler) RegisterPayment(ctx context.Context, req *arkv1.RegisterPaymentRequest) (*arkv1.RegisterPaymentResponse, error) {
vtxosKeys := make([]domain.VtxoKey, 0, len(req.GetInputs()))
for _, input := range req.GetInputs() {
vtxosKeys = append(vtxosKeys, domain.VtxoKey{
Txid: input.GetTxid(),
VOut: input.GetVout(),
})
}
id, err := h.svc.SpendVtxos(ctx, vtxosKeys)
if err != nil {
return nil, err
}
return &arkv1.RegisterPaymentResponse{
Id: id,
}, nil
}
func (h *handler) ClaimPayment(ctx context.Context, req *arkv1.ClaimPaymentRequest) (*arkv1.ClaimPaymentResponse, error) {
receivers, err := parseReceivers(req.GetOutputs())
if err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
if err := h.svc.ClaimVtxos(ctx, req.GetId(), receivers); err != nil {
return nil, err
}
return &arkv1.ClaimPaymentResponse{}, nil
}
func (h *handler) FinalizePayment(ctx context.Context, req *arkv1.FinalizePaymentRequest) (*arkv1.FinalizePaymentResponse, error) {
forfeitTxs, err := parseTxs(req.GetSignedForfeitTxs())
if err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
if err := h.svc.SignVtxos(ctx, forfeitTxs); err != nil {
return nil, err
}
return &arkv1.FinalizePaymentResponse{}, nil
}
func (h *handler) Faucet(ctx context.Context, req *arkv1.FaucetRequest) (*arkv1.FaucetResponse, error) {
_, pubkey, _, err := parseAddress(req.GetAddress())
if err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
if err := h.svc.FaucetVtxos(ctx, pubkey); err != nil {
return nil, err
}
return &arkv1.FaucetResponse{}, nil
}
func (h *handler) GetRound(ctx context.Context, req *arkv1.GetRoundRequest) (*arkv1.GetRoundResponse, error) {
if len(req.GetTxid()) <= 0 {
return nil, status.Error(codes.InvalidArgument, "missing pool txid")
}
round, err := h.svc.GetRoundByTxid(ctx, req.GetTxid())
if err != nil {
return nil, err
}
return &arkv1.GetRoundResponse{
Round: &arkv1.Round{
Id: round.Id,
Start: round.StartingTimestamp,
End: round.EndingTimestamp,
Txid: round.Txid,
CongestionTree: castCongestionTree(round.CongestionTree),
},
}, nil
}
func (h *handler) GetEventStream(_ *arkv1.GetEventStreamRequest, stream arkv1.ArkService_GetEventStreamServer) error {
listener := &listener{
id: uuid.NewString(),
ch: make(chan *arkv1.GetEventStreamResponse),
}
defer h.removeListener(listener.id)
defer close(listener.ch)
h.pushListener(listener)
for {
select {
case <-stream.Context().Done():
return nil
case ev := <-listener.ch:
if err := stream.Send(ev); err != nil {
return err
}
switch ev.Event.(type) {
case *arkv1.GetEventStreamResponse_RoundFinalized, *arkv1.GetEventStreamResponse_RoundFailed:
if err := stream.Send(ev); err != nil {
return err
}
return nil
}
}
}
}
func (h *handler) ListVtxos(ctx context.Context, req *arkv1.ListVtxosRequest) (*arkv1.ListVtxosResponse, error) {
hrp, userPubkey, aspPubkey, err := parseAddress(req.GetAddress())
if err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
vtxos, err := h.svc.ListVtxos(ctx, userPubkey)
if err != nil {
return nil, err
}
return &arkv1.ListVtxosResponse{
Vtxos: vtxoList(vtxos).toProto(hrp, aspPubkey),
}, nil
}
func (h *handler) GetPubkey(ctx context.Context, req *arkv1.GetPubkeyRequest) (*arkv1.GetPubkeyResponse, error) {
pubkey, err := h.svc.GetPubkey(ctx)
if err != nil {
return nil, err
}
return &arkv1.GetPubkeyResponse{
Pubkey: pubkey,
}, nil
}
func (h *handler) pushListener(l *listener) {
h.listenersLock.Lock()
defer h.listenersLock.Unlock()
h.listeners = append(h.listeners, l)
}
func (h *handler) removeListener(id string) {
h.listenersLock.Lock()
defer h.listenersLock.Unlock()
for i, listener := range h.listeners {
if listener.id == id {
h.listeners = append(h.listeners[:i], h.listeners[i+1:]...)
return
}
}
}
// listenToEvents forwards events from the application layer to the set of listeners
func (h *handler) listenToEvents() {
channel := h.svc.GetEventsChannel(context.Background())
for event := range channel {
var ev *arkv1.GetEventStreamResponse
switch e := event.(type) {
case domain.RoundFinalizationStarted:
ev = &arkv1.GetEventStreamResponse{
Event: &arkv1.GetEventStreamResponse_RoundFinalization{
RoundFinalization: &arkv1.RoundFinalizationEvent{
Id: e.Id,
PoolPartialTx: e.PoolTx,
CongestionTree: castCongestionTree(e.CongestionTree),
ForfeitTxs: e.UnsignedForfeitTxs,
},
},
}
case domain.RoundFinalized:
ev = &arkv1.GetEventStreamResponse{
Event: &arkv1.GetEventStreamResponse_RoundFinalized{
RoundFinalized: &arkv1.RoundFinalizedEvent{
Id: e.Id,
PoolTxid: e.Txid,
},
},
}
case domain.RoundFailed:
ev = &arkv1.GetEventStreamResponse{
Event: &arkv1.GetEventStreamResponse_RoundFailed{
RoundFailed: &arkv1.RoundFailed{
Id: e.Id,
Reason: e.Err,
},
},
}
}
if ev != nil {
for _, listener := range h.listeners {
listener.ch <- ev
}
}
}
}
type vtxoList []domain.Vtxo
func (v vtxoList) toProto(hrp string, aspKey *secp256k1.PublicKey) []*arkv1.Vtxo {
list := make([]*arkv1.Vtxo, 0, len(v))
for _, vv := range v {
addr := vv.OnchainAddress
if vv.Pubkey != "" {
buf, _ := hex.DecodeString(vv.Pubkey)
key, _ := secp256k1.ParsePubKey(buf)
addr, _ = common.EncodeAddress(hrp, key, aspKey)
}
list = append(list, &arkv1.Vtxo{
Outpoint: &arkv1.Input{
Txid: vv.Txid,
Vout: vv.VOut,
},
Receiver: &arkv1.Output{
Address: addr,
Amount: vv.Amount,
},
PoolTxid: vv.PoolTx,
Spent: vv.Spent,
})
}
return list
}
// castCongestionTree converts a tree.CongestionTree to a repeated arkv1.TreeLevel
func castCongestionTree(congestionTree tree.CongestionTree) *arkv1.Tree {
levels := make([]*arkv1.TreeLevel, 0, len(congestionTree))
for _, level := range congestionTree {
levelProto := &arkv1.TreeLevel{
Nodes: make([]*arkv1.Node, 0, len(level)),
}
for _, node := range level {
levelProto.Nodes = append(levelProto.Nodes, &arkv1.Node{
Txid: node.Txid,
Tx: node.Tx,
ParentTxid: node.ParentTxid,
})
}
levels = append(levels, levelProto)
}
return &arkv1.Tree{
Levels: levels,
}
}

View File

@@ -0,0 +1,64 @@
package handlers
import (
"encoding/hex"
"fmt"
arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1"
"github.com/ark-network/ark/common"
"github.com/ark-network/ark/internal/core/domain"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/vulpemventures/go-elements/address"
"github.com/vulpemventures/go-elements/psetv2"
)
func parseTxs(txs []string) ([]string, error) {
if len(txs) <= 0 {
return nil, fmt.Errorf("missing list of forfeit txs")
}
for _, tx := range txs {
if _, err := psetv2.NewPsetFromBase64(tx); err != nil {
return nil, fmt.Errorf("invalid tx format")
}
}
return txs, nil
}
func parseAddress(addr string) (string, *secp256k1.PublicKey, *secp256k1.PublicKey, error) {
if len(addr) <= 0 {
return "", nil, nil, fmt.Errorf("missing address")
}
return common.DecodeAddress(addr)
}
func parseReceivers(outs []*arkv1.Output) ([]domain.Receiver, error) {
receivers := make([]domain.Receiver, 0, len(outs))
for _, out := range outs {
if out.GetAmount() == 0 {
return nil, fmt.Errorf("missing output amount")
}
if len(out.GetAddress()) <= 0 {
return nil, fmt.Errorf("missing output address")
}
var pubkey, addr string
_, pk, _, err := common.DecodeAddress(out.GetAddress())
if err != nil {
if _, err := address.ToOutputScript(out.GetAddress()); err != nil {
return nil, fmt.Errorf("invalid output address: unknown format")
}
if isConf, _ := address.IsConfidential(out.GetAddress()); isConf {
return nil, fmt.Errorf("invalid output address: must be unconfidential")
}
addr = out.GetAddress()
}
if pk != nil {
pubkey = hex.EncodeToString(pk.SerializeCompressed())
}
receivers = append(receivers, domain.Receiver{
Pubkey: pubkey,
Amount: out.GetAmount(),
OnchainAddress: addr,
})
}
return receivers, nil
}

View File

@@ -0,0 +1,16 @@
package interceptors
import (
middleware "github.com/grpc-ecosystem/go-grpc-middleware"
"google.golang.org/grpc"
)
// UnaryInterceptor returns the unary interceptor
func UnaryInterceptor() grpc.ServerOption {
return grpc.UnaryInterceptor(middleware.ChainUnaryServer(unaryLogger))
}
// StreamInterceptor returns the stream interceptor with a logrus log
func StreamInterceptor() grpc.ServerOption {
return grpc.StreamInterceptor(middleware.ChainStreamServer(streamLogger))
}

View File

@@ -0,0 +1,28 @@
package interceptors
import (
"context"
log "github.com/sirupsen/logrus"
"google.golang.org/grpc"
)
func unaryLogger(
ctx context.Context,
req interface{},
info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler,
) (interface{}, error) {
log.Debugf("gRPC method: %s", info.FullMethod)
return handler(ctx, req)
}
func streamLogger(
srv interface{},
stream grpc.ServerStream,
info *grpc.StreamServerInfo,
handler grpc.StreamHandler,
) error {
log.Debugf("gRPC method: %s", info.FullMethod)
return handler(srv, stream)
}

View File

@@ -0,0 +1,64 @@
package grpcservice
import (
"fmt"
arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1"
appconfig "github.com/ark-network/ark/internal/app-config"
interfaces "github.com/ark-network/ark/internal/interface"
"github.com/ark-network/ark/internal/interface/grpc/handlers"
"github.com/ark-network/ark/internal/interface/grpc/interceptors"
log "github.com/sirupsen/logrus"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
type service struct {
config Config
appConfig *appconfig.Config
server *grpc.Server
}
func NewService(
svcConfig Config, appConfig *appconfig.Config,
) (interfaces.Service, error) {
if err := svcConfig.Validate(); err != nil {
return nil, fmt.Errorf("invalid service config: %s", err)
}
if err := appConfig.Validate(); err != nil {
return nil, fmt.Errorf("invalid app config: %s", err)
}
grpcConfig := []grpc.ServerOption{
interceptors.UnaryInterceptor(), interceptors.StreamInterceptor(),
}
if !svcConfig.NoTLS {
return nil, fmt.Errorf("tls termination not supported yet")
}
creds := insecure.NewCredentials()
grpcConfig = append(grpcConfig, grpc.Creds(creds))
server := grpc.NewServer(grpcConfig...)
handler := handlers.NewHandler(appConfig.AppService())
arkv1.RegisterArkServiceServer(server, handler)
return &service{svcConfig, appConfig, server}, nil
}
func (s *service) Start() error {
// nolint:all
go s.server.Serve(s.config.listener())
log.Infof("started listening at %s", s.config.address())
if err := s.appConfig.AppService().Start(); err != nil {
return fmt.Errorf("failed to start app service: %s", err)
}
log.Info("started app service")
return nil
}
func (s *service) Stop() {
s.server.Stop()
log.Info("stopped grpc server")
s.appConfig.AppService().Stop()
log.Info("stopped app service")
}

View File

@@ -0,0 +1,6 @@
package interfaces
type Service interface {
Start() error
Stop()
}

0
server/internal/test/.gitkeep Executable file
View File

0
server/pkg/.gitkeep Normal file
View File

18
server/scripts/build Executable file
View File

@@ -0,0 +1,18 @@
#!/bin/bash
set -e
PARENT_PATH=$(dirname $(
cd $(dirname $0)
pwd -P
))
OS=$(eval "go env GOOS")
ARCH=$(eval "go env GOARCH")
pushd $PARENT_PATH
mkdir -p build
GO111MODULE=on go build -o build/arkd-$OS-$ARCH cmd/arkd/main.go
popd

23
server/scripts/build-all Executable file
View File

@@ -0,0 +1,23 @@
#!/bin/bash
set -e
PARENT_PATH=$(dirname $(
cd $(dirname $0)
pwd -P
))
declare -a OS=("darwin" "linux")
declare -a ARCH=("amd64" "arm64")
pushd $PARENT_PATH
mkdir -p build
for os in "${OS[@]}"; do
for arch in "${ARCH[@]}"; do
echo "Building for $os $arch"
GOOS=$os GOARCH=$arch go build -o build/arkd-$os-$arch cmd/arkd/main.go
done
done
popd