mirror of
https://github.com/aljazceru/ark.git
synced 2026-01-31 09:04:46 +01:00
Add integration tests and regtest support (#128)
* regtest support + integration tests (e2e) * add integration CI * add PR trigger on integration CI * wait for ocean to be unlocked at startup * integration tests: add tests flags and build docker images at startup * use nigiri chopsticks-liquid * fix after reviews * Update client/init.go Co-authored-by: Pietralberto Mazza <18440657+altafan@users.noreply.github.com> Signed-off-by: Louis Singer <41042567+louisinger@users.noreply.github.com> * do not trigger integration on PR --------- Signed-off-by: Louis Singer <41042567+louisinger@users.noreply.github.com> Co-authored-by: Pietralberto Mazza <18440657+altafan@users.noreply.github.com>
This commit is contained in:
@@ -20,6 +20,10 @@ jobs:
|
||||
go-version: ">1.17.2"
|
||||
- uses: actions/checkout@v3
|
||||
- run: go get -v -t -d ./...
|
||||
|
||||
- name: Run Nigiri
|
||||
uses: vulpemventures/nigiri-github-action@v1
|
||||
|
||||
- name: integration testing
|
||||
run: make integrationtest
|
||||
|
||||
103
client/common.go
103
client/common.go
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||
"github.com/urfave/cli/v2"
|
||||
"github.com/vulpemventures/go-elements/address"
|
||||
"github.com/vulpemventures/go-elements/elementsutil"
|
||||
"github.com/vulpemventures/go-elements/network"
|
||||
@@ -34,6 +35,13 @@ const (
|
||||
DUST = 450
|
||||
)
|
||||
|
||||
var passwordFlag = cli.StringFlag{
|
||||
Name: "password",
|
||||
Usage: "password to unlock the wallet",
|
||||
Required: false,
|
||||
Hidden: true,
|
||||
}
|
||||
|
||||
func hashPassword(password []byte) []byte {
|
||||
hash := sha256.Sum256(password)
|
||||
return hash[:]
|
||||
@@ -64,22 +72,30 @@ func verifyPassword(password []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func readPassword() ([]byte, error) {
|
||||
fmt.Print("unlock your wallet with password: ")
|
||||
passwordInput, err := term.ReadPassword(int(syscall.Stdin))
|
||||
fmt.Println() // new line
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func readPassword(ctx *cli.Context, verify bool) ([]byte, error) {
|
||||
password := []byte(ctx.String("password"))
|
||||
|
||||
if len(password) == 0 {
|
||||
fmt.Print("unlock your wallet with password: ")
|
||||
var err error
|
||||
password, err = term.ReadPassword(int(syscall.Stdin))
|
||||
fmt.Println() // new line
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if err := verifyPassword(passwordInput); err != nil {
|
||||
return nil, err
|
||||
if verify {
|
||||
if err := verifyPassword(password); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return passwordInput, nil
|
||||
return password, nil
|
||||
}
|
||||
|
||||
func privateKeyFromPassword() (*secp256k1.PrivateKey, error) {
|
||||
func privateKeyFromPassword(ctx *cli.Context) (*secp256k1.PrivateKey, error) {
|
||||
state, err := getState()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -95,7 +111,7 @@ func privateKeyFromPassword() (*secp256k1.PrivateKey, error) {
|
||||
return nil, fmt.Errorf("invalid encrypted private key: %s", err)
|
||||
}
|
||||
|
||||
password, err := readPassword()
|
||||
password, err := readPassword(ctx, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -256,9 +272,25 @@ func getOffchainBalance(
|
||||
return balance, amountByExpiration, nil
|
||||
}
|
||||
|
||||
func getBaseURL() (string, error) {
|
||||
state, err := getState()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
baseURL := state[EXPLORER]
|
||||
if len(baseURL) <= 0 {
|
||||
return "", fmt.Errorf("missing explorer base url")
|
||||
}
|
||||
|
||||
return baseURL, nil
|
||||
}
|
||||
|
||||
func getTxBlocktime(txid string) (confirmed bool, blocktime int64, err error) {
|
||||
_, net := getNetwork()
|
||||
baseUrl := explorerUrl[net.Name]
|
||||
baseUrl, err := getBaseURL()
|
||||
if err != nil {
|
||||
return false, 0, err
|
||||
}
|
||||
resp, err := http.Get(fmt.Sprintf("%s/tx/%s", baseUrl, txid))
|
||||
if err != nil {
|
||||
return false, 0, err
|
||||
@@ -301,9 +333,16 @@ func getNetwork() (*common.Network, *network.Network) {
|
||||
if !ok {
|
||||
return &common.MainNet, &network.Liquid
|
||||
}
|
||||
return networkFromString(net)
|
||||
}
|
||||
|
||||
func networkFromString(net string) (*common.Network, *network.Network) {
|
||||
if net == "testnet" {
|
||||
return &common.TestNet, &network.Testnet
|
||||
}
|
||||
if net == "regtest" {
|
||||
return &common.RegTest, &network.Regtest
|
||||
}
|
||||
return &common.MainNet, &network.Liquid
|
||||
}
|
||||
|
||||
@@ -432,11 +471,13 @@ func handleRoundStream(
|
||||
return "", err
|
||||
}
|
||||
|
||||
// validate the congestion tree
|
||||
if err := tree.ValidateCongestionTree(
|
||||
congestionTree, poolTx, aspPubkey, int64(roundLifetime),
|
||||
); err != nil {
|
||||
return "", err
|
||||
if !isOnchainOnly(receivers) {
|
||||
// validate the congestion tree
|
||||
if err := tree.ValidateCongestionTree(
|
||||
congestionTree, poolTx, aspPubkey, int64(roundLifetime),
|
||||
); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if err := common.ValidateConnectors(poolTx, connectors); err != nil {
|
||||
@@ -772,7 +813,7 @@ func getRedeemBranches(
|
||||
}
|
||||
|
||||
redeemBranch, err := newRedeemBranch(
|
||||
ctx, explorer, congestionTrees[vtxo.poolTxid], vtxo,
|
||||
explorer, congestionTrees[vtxo.poolTxid], vtxo,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -1060,15 +1101,10 @@ func addInputs(
|
||||
return err
|
||||
}
|
||||
|
||||
witnessUtxo := transaction.TxOutput{
|
||||
Asset: assetID,
|
||||
Value: value,
|
||||
Script: script,
|
||||
Nonce: []byte{0x00},
|
||||
}
|
||||
witnessUtxo := transaction.NewTxOutput(assetID, value, script)
|
||||
|
||||
if err := updater.AddInWitnessUtxo(
|
||||
len(updater.Pset.Inputs)-1, &witnessUtxo,
|
||||
len(updater.Pset.Inputs)-1, witnessUtxo,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1077,3 +1113,18 @@ func addInputs(
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func isOnchainOnly(receivers []*arkv1.Output) bool {
|
||||
for _, receiver := range receivers {
|
||||
isOnChain, _, _, err := decodeReceiverAddress(receiver.Address)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if !isOnChain {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -10,10 +10,11 @@ var dumpCommand = cli.Command{
|
||||
Name: "dump-privkey",
|
||||
Usage: "Dumps private key of the Ark wallet",
|
||||
Action: dumpAction,
|
||||
Flags: []cli.Flag{&passwordFlag},
|
||||
}
|
||||
|
||||
func dumpAction(ctx *cli.Context) error {
|
||||
privateKey, err := privateKeyFromPassword()
|
||||
privateKey, err := privateKeyFromPassword(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -40,8 +40,10 @@ type explorer struct {
|
||||
}
|
||||
|
||||
func NewExplorer() Explorer {
|
||||
_, net := getNetwork()
|
||||
baseUrl := explorerUrl[net.Name]
|
||||
baseUrl, err := getBaseURL()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return &explorer{
|
||||
cache: make(map[string]string),
|
||||
@@ -76,10 +78,7 @@ func (e *explorer) Broadcast(txStr string) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
txStr, err = tx.ToHex()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
txStr, _ = tx.ToHex()
|
||||
}
|
||||
txid := tx.TxHash().String()
|
||||
e.cache[txid] = txStr
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -11,15 +13,10 @@ import (
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||
"github.com/urfave/cli/v2"
|
||||
"github.com/vulpemventures/go-elements/network"
|
||||
)
|
||||
|
||||
var (
|
||||
passwordFlag = cli.StringFlag{
|
||||
Name: "password",
|
||||
Usage: "password to encrypt private key",
|
||||
Required: true,
|
||||
}
|
||||
|
||||
privateKeyFlag = cli.StringFlag{
|
||||
Name: "prvkey",
|
||||
Usage: "optional, private key to encrypt",
|
||||
@@ -34,35 +31,54 @@ var (
|
||||
Usage: "the url of the ASP to connect to",
|
||||
Required: true,
|
||||
}
|
||||
explorerFlag = cli.StringFlag{
|
||||
Name: "explorer",
|
||||
Usage: "the url of the explorer to use",
|
||||
}
|
||||
)
|
||||
|
||||
var initCommand = cli.Command{
|
||||
Name: "init",
|
||||
Usage: "Initialize your Ark wallet with an encryption password, and connect it to an ASP",
|
||||
Action: initAction,
|
||||
Flags: []cli.Flag{&passwordFlag, &privateKeyFlag, &networkFlag, &urlFlag},
|
||||
Flags: []cli.Flag{&passwordFlag, &privateKeyFlag, &networkFlag, &urlFlag, &explorerFlag},
|
||||
}
|
||||
|
||||
func initAction(ctx *cli.Context) error {
|
||||
key := ctx.String("prvkey")
|
||||
password := ctx.String("password")
|
||||
net := strings.ToLower(ctx.String("network"))
|
||||
url := ctx.String("ark-url")
|
||||
explorer := ctx.String("explorer")
|
||||
|
||||
var explorerURL string
|
||||
|
||||
if len(password) <= 0 {
|
||||
return fmt.Errorf("invalid password")
|
||||
}
|
||||
if len(url) <= 0 {
|
||||
return fmt.Errorf("invalid ark url")
|
||||
}
|
||||
if net != "mainnet" && net != "testnet" {
|
||||
if net != "mainnet" && net != "testnet" && net != "regtest" {
|
||||
return fmt.Errorf("invalid network")
|
||||
}
|
||||
|
||||
if err := connectToAsp(ctx.Context, net, url); err != nil {
|
||||
if len(explorer) > 0 {
|
||||
explorerURL = explorer
|
||||
_, network := networkFromString(net)
|
||||
if err := testEsploraEndpoint(network, explorerURL); err != nil {
|
||||
return fmt.Errorf("failed to connect with explorer: %s", err)
|
||||
}
|
||||
} else {
|
||||
explorerURL = explorerUrl[net]
|
||||
}
|
||||
|
||||
if err := connectToAsp(ctx.Context, net, url, explorerURL); err != nil {
|
||||
return err
|
||||
}
|
||||
return initWallet(ctx, key, password)
|
||||
|
||||
password, err := readPassword(ctx, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return initWallet(key, password)
|
||||
}
|
||||
|
||||
func generateRandomPrivateKey() (*secp256k1.PrivateKey, error) {
|
||||
@@ -73,7 +89,7 @@ func generateRandomPrivateKey() (*secp256k1.PrivateKey, error) {
|
||||
return privKey, nil
|
||||
}
|
||||
|
||||
func connectToAsp(ctx context.Context, net, url string) error {
|
||||
func connectToAsp(ctx context.Context, net, url, explorer string) error {
|
||||
client, close, err := getClient(url)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -91,10 +107,11 @@ func connectToAsp(ctx context.Context, net, url string) error {
|
||||
ASP_PUBKEY: resp.Pubkey,
|
||||
ROUND_LIFETIME: strconv.Itoa(int(resp.GetRoundLifetime())),
|
||||
UNILATERAL_EXIT_DELAY: strconv.Itoa(int(resp.GetUnilateralExitDelay())),
|
||||
EXPLORER: explorer,
|
||||
})
|
||||
}
|
||||
|
||||
func initWallet(ctx *cli.Context, key, password string) error {
|
||||
func initWallet(key string, password []byte) error {
|
||||
var privateKey *secp256k1.PrivateKey
|
||||
if len(key) <= 0 {
|
||||
privKey, err := generateRandomPrivateKey()
|
||||
@@ -113,7 +130,7 @@ func initWallet(ctx *cli.Context, key, password string) error {
|
||||
|
||||
cypher := newAES128Cypher()
|
||||
buf := privateKey.Serialize()
|
||||
encryptedPrivateKey, err := cypher.encrypt(buf, []byte(password))
|
||||
encryptedPrivateKey, err := cypher.encrypt(buf, password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -134,3 +151,20 @@ func initWallet(ctx *cli.Context, key, password string) error {
|
||||
fmt.Println("wallet initialized")
|
||||
return nil
|
||||
}
|
||||
|
||||
func testEsploraEndpoint(net *network.Network, url string) error {
|
||||
resp, err := http.Get(fmt.Sprintf("%s/asset/%s", url, net.AssetID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf(string(body))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ const (
|
||||
PASSWORD_HASH = "password_hash"
|
||||
PUBKEY = "public_key"
|
||||
NETWORK = "network"
|
||||
EXPLORER = "explorer"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -36,6 +37,7 @@ var (
|
||||
explorerUrl = map[string]string{
|
||||
network.Liquid.Name: "https://blockstream.info/liquid/api",
|
||||
network.Testnet.Name: "https://blockstream.info/liquidtestnet/api",
|
||||
network.Regtest.Name: "http://localhost:3001",
|
||||
}
|
||||
|
||||
initialState = map[string]string{
|
||||
|
||||
@@ -27,7 +27,7 @@ var onboardCommand = cli.Command{
|
||||
Name: "onboard",
|
||||
Usage: "Onboard the Ark by lifting your funds",
|
||||
Action: onboardAction,
|
||||
Flags: []cli.Flag{&amountOnboardFlag},
|
||||
Flags: []cli.Flag{&amountOnboardFlag, &passwordFlag},
|
||||
}
|
||||
|
||||
func onboardAction(ctx *cli.Context) error {
|
||||
@@ -92,11 +92,9 @@ func onboardAction(ctx *cli.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
explorer := NewExplorer()
|
||||
txid, err := explorer.Broadcast(pset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ptx, _ := psetv2.NewPsetFromBase64(pset)
|
||||
utx, _ := ptx.UnsignedTx()
|
||||
txid := utx.TxHash().String()
|
||||
|
||||
congestionTree, err := treeFactoryFn(psetv2.InputArgs{
|
||||
Txid: txid,
|
||||
|
||||
@@ -2,7 +2,6 @@ package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
@@ -40,7 +39,7 @@ var (
|
||||
var redeemCommand = cli.Command{
|
||||
Name: "redeem",
|
||||
Usage: "Redeem your offchain funds, either collaboratively or unilaterally",
|
||||
Flags: []cli.Flag{&addressFlag, &amountToRedeemFlag, &forceFlag, &enableExpiryCoinselectFlag},
|
||||
Flags: []cli.Flag{&addressFlag, &amountToRedeemFlag, &forceFlag, &passwordFlag, &enableExpiryCoinselectFlag},
|
||||
Action: redeemAction,
|
||||
}
|
||||
|
||||
@@ -68,7 +67,7 @@ func redeemAction(ctx *cli.Context) error {
|
||||
fmt.Printf("WARNING: unilateral exit (--force) ignores --amount flag, it will redeem all your VTXOs\n")
|
||||
}
|
||||
|
||||
return unilateralRedeem(ctx.Context, client)
|
||||
return unilateralRedeem(ctx, client)
|
||||
}
|
||||
|
||||
return collaborativeRedeem(ctx, client, addr, amount)
|
||||
@@ -137,7 +136,7 @@ func collaborativeRedeem(
|
||||
})
|
||||
}
|
||||
|
||||
secKey, err := privateKeyFromPassword()
|
||||
secKey, err := privateKeyFromPassword(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -178,14 +177,14 @@ func collaborativeRedeem(
|
||||
return nil
|
||||
}
|
||||
|
||||
func unilateralRedeem(ctx context.Context, client arkv1.ArkServiceClient) error {
|
||||
func unilateralRedeem(ctx *cli.Context, client arkv1.ArkServiceClient) error {
|
||||
offchainAddr, _, _, err := getAddress()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
explorer := NewExplorer()
|
||||
vtxos, err := getVtxos(ctx, explorer, client, offchainAddr, false)
|
||||
vtxos, err := getVtxos(ctx.Context, explorer, client, offchainAddr, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -196,16 +195,18 @@ func unilateralRedeem(ctx context.Context, client arkv1.ArkServiceClient) error
|
||||
totalVtxosAmount += vtxo.amount
|
||||
}
|
||||
|
||||
ok := askForConfirmation(fmt.Sprintf("redeem %d sats ?", totalVtxosAmount))
|
||||
if !ok {
|
||||
return fmt.Errorf("aborting unilateral exit")
|
||||
if len(ctx.String("password")) == 0 {
|
||||
ok := askForConfirmation(fmt.Sprintf("redeem %d sats ?", totalVtxosAmount))
|
||||
if !ok {
|
||||
return fmt.Errorf("aborting unilateral exit")
|
||||
}
|
||||
}
|
||||
|
||||
// transactionsMap avoid duplicates
|
||||
transactionsMap := make(map[string]struct{}, 0)
|
||||
transactions := make([]string, 0)
|
||||
|
||||
redeemBranches, err := getRedeemBranches(ctx, explorer, client, vtxos)
|
||||
redeemBranches, err := getRedeemBranches(ctx.Context, explorer, client, vtxos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ var sendCommand = cli.Command{
|
||||
Name: "send",
|
||||
Usage: "Send your onchain or offchain funds to one or many receivers",
|
||||
Action: sendAction,
|
||||
Flags: []cli.Flag{&receiversFlag, &toFlag, &amountFlag, &enableExpiryCoinselectFlag},
|
||||
Flags: []cli.Flag{&receiversFlag, &toFlag, &amountFlag, &passwordFlag, &enableExpiryCoinselectFlag},
|
||||
}
|
||||
|
||||
func sendAction(ctx *cli.Context) error {
|
||||
@@ -186,7 +186,7 @@ func sendOffchain(ctx *cli.Context, receivers []receiver) error {
|
||||
})
|
||||
}
|
||||
|
||||
secKey, err := privateKeyFromPassword()
|
||||
secKey, err := privateKeyFromPassword(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -219,7 +219,7 @@ func sendOffchain(ctx *cli.Context, receivers []receiver) error {
|
||||
})
|
||||
}
|
||||
|
||||
func sendOnchain(_ *cli.Context, receivers []receiver) (string, error) {
|
||||
func sendOnchain(ctx *cli.Context, receivers []receiver) (string, error) {
|
||||
pset, err := psetv2.New(nil, nil, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -349,7 +349,7 @@ func sendOnchain(_ *cli.Context, receivers []receiver) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
prvKey, err := privateKeyFromPassword()
|
||||
prvKey, err := privateKeyFromPassword(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
@@ -22,7 +21,7 @@ type redeemBranch struct {
|
||||
}
|
||||
|
||||
func newRedeemBranch(
|
||||
ctx context.Context, explorer Explorer,
|
||||
explorer Explorer,
|
||||
congestionTree tree.CongestionTree, vtxo vtxo,
|
||||
) (*redeemBranch, error) {
|
||||
sweepClosure, seconds, err := findSweepClosure(congestionTree)
|
||||
|
||||
@@ -14,3 +14,8 @@ var TestNet = Network{
|
||||
Name: "testnet",
|
||||
Addr: "tark",
|
||||
}
|
||||
|
||||
var RegTest = Network{
|
||||
Name: "regtest",
|
||||
Addr: TestNet.Addr,
|
||||
}
|
||||
|
||||
47
docker-compose.regtest.yml
Normal file
47
docker-compose.regtest.yml
Normal file
@@ -0,0 +1,47 @@
|
||||
version: "3.7"
|
||||
|
||||
services:
|
||||
oceand:
|
||||
container_name: oceand
|
||||
image: ghcr.io/vulpemventures/oceand:latest
|
||||
restart: unless-stopped
|
||||
user: 0:0
|
||||
environment:
|
||||
- OCEAN_LOG_LEVEL=5
|
||||
- OCEAN_NO_TLS=true
|
||||
- OCEAN_NO_PROFILER=true
|
||||
- OCEAN_ELECTRUM_URL=tcp://electrs-liquid:50001
|
||||
- OCEAN_NETWORK=regtest
|
||||
- OCEAN_UTXO_EXPIRY_DURATION_IN_SECONDS=60
|
||||
- OCEAN_DB_TYPE=badger
|
||||
ports:
|
||||
- "18000:18000"
|
||||
arkd:
|
||||
container_name: arkd
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- oceand
|
||||
environment:
|
||||
- ARK_WALLET_ADDR=oceand:18000
|
||||
- ARK_ROUND_INTERVAL=10
|
||||
- ARK_NETWORK=regtest
|
||||
ports:
|
||||
- "6000:6000"
|
||||
|
||||
volumes:
|
||||
oceand:
|
||||
external: false
|
||||
ocean:
|
||||
external: false
|
||||
arkd:
|
||||
external: false
|
||||
ark:
|
||||
external: false
|
||||
|
||||
networks:
|
||||
default:
|
||||
name: nigiri
|
||||
external: true
|
||||
@@ -23,7 +23,7 @@ help:
|
||||
## 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) \;
|
||||
@go test -v -count=1 -race -timeout 200s github.com/ark-network/ark/test/e2e
|
||||
|
||||
## lint: lint codebase
|
||||
lint:
|
||||
@@ -40,7 +40,7 @@ run: clean
|
||||
## 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) \;
|
||||
@find . -name go.mod -execdir go test -v -count=1 -race ./internal/... \;
|
||||
|
||||
## vet: code analysis
|
||||
vet:
|
||||
|
||||
@@ -69,8 +69,8 @@ func (c *Config) Validate() error {
|
||||
if c.RoundInterval < 2 {
|
||||
return fmt.Errorf("invalid round interval, must be at least 2 seconds")
|
||||
}
|
||||
if c.Network.Name != "liquid" && c.Network.Name != "testnet" {
|
||||
return fmt.Errorf("invalid network, must be either liquid or testnet")
|
||||
if c.Network.Name != "liquid" && c.Network.Name != "testnet" && c.Network.Name != "regtest" {
|
||||
return fmt.Errorf("invalid network, must be liquid, testnet or regtest")
|
||||
}
|
||||
if len(c.WalletAddr) <= 0 {
|
||||
return fmt.Errorf("missing onchain wallet address")
|
||||
@@ -239,11 +239,14 @@ func (c *Config) appService() error {
|
||||
}
|
||||
|
||||
func (c *Config) mainChain() network.Network {
|
||||
net := network.Liquid
|
||||
if c.Network.Name != "mainnet" {
|
||||
net = network.Testnet
|
||||
switch c.Network.Name {
|
||||
case "testnet":
|
||||
return network.Testnet
|
||||
case "regtest":
|
||||
return network.Regtest
|
||||
default:
|
||||
return network.Liquid
|
||||
}
|
||||
return net
|
||||
}
|
||||
|
||||
type supportedType map[string]struct{}
|
||||
|
||||
@@ -121,7 +121,9 @@ func getNetwork() (common.Network, error) {
|
||||
return common.MainNet, nil
|
||||
case "testnet":
|
||||
return common.TestNet, nil
|
||||
case "regtest":
|
||||
return common.RegTest, nil
|
||||
default:
|
||||
return common.Network{}, fmt.Errorf("unknown network")
|
||||
return common.Network{}, fmt.Errorf("unknown network %s", viper.GetString(Network))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -415,7 +415,7 @@ func (s *service) handleOnboarding(onboarding onboarding) {
|
||||
}
|
||||
|
||||
if err != nil || !isConfirmed {
|
||||
time.Sleep(30 * time.Second)
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -614,28 +614,30 @@ func (s *service) updateVtxoSet(round *domain.Round) {
|
||||
}
|
||||
|
||||
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() {
|
||||
if len(newVtxos) > 0 {
|
||||
for {
|
||||
if err := s.startWatchingVtxos(newVtxos); err != nil {
|
||||
log.WithError(err).Warn(
|
||||
"failed to start watching vtxos, retrying in a moment...",
|
||||
)
|
||||
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("started watching %d vtxos", len(newVtxos))
|
||||
return
|
||||
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
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *service) propagateEvents(round *domain.Round) {
|
||||
@@ -673,6 +675,10 @@ func (s *service) scheduleSweepVtxosForRound(round *domain.Round) {
|
||||
}
|
||||
|
||||
func (s *service) getNewVtxos(round *domain.Round) []domain.Vtxo {
|
||||
if len(round.CongestionTree) <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
leaves := round.CongestionTree.Leaves()
|
||||
vtxos := make([]domain.Vtxo, 0)
|
||||
for _, node := range leaves {
|
||||
|
||||
@@ -71,6 +71,11 @@ func (s *sweeper) removeTask(treeRootTxid string) {
|
||||
func (s *sweeper) schedule(
|
||||
expirationTimestamp int64, roundTxid string, congestionTree tree.CongestionTree,
|
||||
) error {
|
||||
if len(congestionTree) <= 0 { // skip
|
||||
log.Debugf("skipping sweep scheduling (round tx %s), empty congestion tree", roundTxid)
|
||||
return nil
|
||||
}
|
||||
|
||||
root, err := congestionTree.Root()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -181,12 +181,6 @@ func (r *Round) RegisterPayments(payments []Payment) ([]RoundEvent, error) {
|
||||
}
|
||||
|
||||
func (r *Round) StartFinalization(connectorAddress string, 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")
|
||||
}
|
||||
|
||||
@@ -323,32 +323,6 @@ func testStartFinalization(t *testing.T) {
|
||||
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",
|
||||
|
||||
@@ -95,7 +95,7 @@ func deserializeEvent(buf []byte) (domain.RoundEvent, error) {
|
||||
}
|
||||
{
|
||||
var event = domain.RoundFinalizationStarted{}
|
||||
if err := json.Unmarshal(buf, &event); err == nil && len(event.CongestionTree) > 0 {
|
||||
if err := json.Unmarshal(buf, &event); err == nil && len(event.Connectors) > 0 {
|
||||
return event, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ package oceanwallet
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
pb "github.com/ark-network/ark/api-spec/protobuf/gen/ocean/v1"
|
||||
"github.com/ark-network/ark/internal/core/domain"
|
||||
@@ -47,12 +47,21 @@ func NewService(addr string) (ports.WalletService, error) {
|
||||
}
|
||||
|
||||
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")
|
||||
|
||||
isReady := false
|
||||
|
||||
for !isReady {
|
||||
status, err := svc.Status(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
isReady = status.IsInitialized() && status.IsUnlocked()
|
||||
|
||||
if !isReady {
|
||||
log.Info("Wallet must be initialized and unlocked to proceed. Waiting for wallet to be ready...")
|
||||
time.Sleep(3 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
// Create ark account at startup if needed.
|
||||
|
||||
@@ -119,11 +119,18 @@ func (b *txBuilder) BuildPoolTx(
|
||||
// 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 := tree.CraftCongestionTree(
|
||||
b.net.AssetID, aspPubkey, getOffchainReceivers(payments), minRelayFee, b.roundLifetime, b.exitDelay,
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
|
||||
var sharedOutputScript []byte
|
||||
var sharedOutputAmount uint64
|
||||
var treeFactoryFn tree.TreeFactory
|
||||
|
||||
if !isOnchainOnly(payments) {
|
||||
treeFactoryFn, sharedOutputScript, sharedOutputAmount, err = tree.CraftCongestionTree(
|
||||
b.net.AssetID, aspPubkey, getOffchainReceivers(payments), minRelayFee, b.roundLifetime, b.exitDelay,
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
connectorAddress, err = b.wallet.DeriveConnectorAddress(context.Background())
|
||||
@@ -143,12 +150,14 @@ func (b *txBuilder) BuildPoolTx(
|
||||
return
|
||||
}
|
||||
|
||||
tree, err := treeFactoryFn(psetv2.InputArgs{
|
||||
Txid: unsignedTx.TxHash().String(),
|
||||
TxIndex: 0,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
if treeFactoryFn != nil {
|
||||
congestionTree, err = treeFactoryFn(psetv2.InputArgs{
|
||||
Txid: unsignedTx.TxHash().String(),
|
||||
TxIndex: 0,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
poolTx, err = ptx.ToBase64()
|
||||
@@ -156,7 +165,6 @@ func (b *txBuilder) BuildPoolTx(
|
||||
return
|
||||
}
|
||||
|
||||
congestionTree = tree
|
||||
return
|
||||
}
|
||||
|
||||
@@ -219,21 +227,26 @@ func (b *txBuilder) createPoolTx(
|
||||
if nbOfInputs > 1 {
|
||||
connectorsAmount -= minRelayFee
|
||||
}
|
||||
targetAmount := sharedOutputAmount + connectorsAmount
|
||||
targetAmount := connectorsAmount
|
||||
|
||||
outputs := []psetv2.OutputArgs{
|
||||
{
|
||||
outputs := make([]psetv2.OutputArgs, 0)
|
||||
|
||||
if sharedOutputScript != nil && sharedOutputAmount > 0 {
|
||||
targetAmount += sharedOutputAmount
|
||||
|
||||
outputs = append(outputs, psetv2.OutputArgs{
|
||||
Asset: b.net.AssetID,
|
||||
Amount: sharedOutputAmount,
|
||||
Script: sharedOutputScript,
|
||||
},
|
||||
{
|
||||
Asset: b.net.AssetID,
|
||||
Amount: connectorsAmount,
|
||||
Script: connectorScript,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
outputs = append(outputs, psetv2.OutputArgs{
|
||||
Asset: b.net.AssetID,
|
||||
Amount: connectorsAmount,
|
||||
Script: connectorScript,
|
||||
})
|
||||
|
||||
for _, receiver := range receivers {
|
||||
targetAmount += receiver.Amount
|
||||
|
||||
|
||||
@@ -139,3 +139,14 @@ func addInputs(
|
||||
func taprootOutputScript(taprootKey *secp256k1.PublicKey) ([]byte, error) {
|
||||
return txscript.NewScriptBuilder().AddOp(txscript.OP_1).AddData(schnorr.SerializePubKey(taprootKey)).Script()
|
||||
}
|
||||
|
||||
func isOnchainOnly(payments []domain.Payment) bool {
|
||||
for _, p := range payments {
|
||||
for _, r := range p.Receivers {
|
||||
if !r.IsOnchain() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
205
server/test/e2e/e2e_test.go
Normal file
205
server/test/e2e/e2e_test.go
Normal file
@@ -0,0 +1,205 @@
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const composePath = "../../../docker-compose.regtest.yml"
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
_, err := runCommand("docker-compose", "-f", composePath, "up", "-d", "--build")
|
||||
if err != nil {
|
||||
fmt.Printf("error starting docker-compose: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
_, err = runOceanCommand("config", "init", "--no-tls")
|
||||
if err != nil {
|
||||
fmt.Printf("error initializing ocean config: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
_, err = runOceanCommand("wallet", "create", "--password", password)
|
||||
if err != nil {
|
||||
fmt.Printf("error creating ocean wallet: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
_, err = runOceanCommand("wallet", "unlock", "--password", password)
|
||||
if err != nil {
|
||||
fmt.Printf("error unlocking ocean wallet: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
_, err = runOceanCommand("account", "create", "--label", "ark", "--unconf")
|
||||
if err != nil {
|
||||
fmt.Printf("error creating ocean account: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
addrJSON, err := runOceanCommand("account", "derive", "--account-name", "ark")
|
||||
if err != nil {
|
||||
fmt.Printf("error deriving ocean account: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var addr struct {
|
||||
Addresses []string `json:"addresses"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal([]byte(addrJSON), &addr); err != nil {
|
||||
fmt.Printf("error unmarshalling ocean account: %s (%s)", err, addrJSON)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
_, err = runCommand("nigiri", "faucet", "--liquid", addr.Addresses[0])
|
||||
if err != nil {
|
||||
fmt.Printf("error funding ocean account: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
_, err = runArkCommand("init", "--ark-url", "localhost:6000", "--password", password, "--network", "regtest", "--explorer", "http://chopsticks-liquid:3000")
|
||||
if err != nil {
|
||||
fmt.Printf("error initializing ark config: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var receive arkReceive
|
||||
receiveStr, err := runArkCommand("receive")
|
||||
if err != nil {
|
||||
fmt.Printf("error getting ark receive addresses: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal([]byte(receiveStr), &receive); err != nil {
|
||||
fmt.Printf("error unmarshalling ark receive addresses: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
_, err = runCommand("nigiri", "faucet", "--liquid", receive.Onchain)
|
||||
if err != nil {
|
||||
fmt.Printf("error funding ark account: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
code := m.Run()
|
||||
|
||||
_, err = runCommand("docker-compose", "-f", composePath, "down")
|
||||
if err != nil {
|
||||
fmt.Printf("error stopping docker-compose: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
func TestOnboard(t *testing.T) {
|
||||
var balance arkBalance
|
||||
balanceStr, err := runArkCommand("balance")
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, json.Unmarshal([]byte(balanceStr), &balance))
|
||||
balanceBefore := balance.Offchain.Total
|
||||
|
||||
_, err = runArkCommand("onboard", "--amount", "1000", "--password", password)
|
||||
require.NoError(t, err)
|
||||
err = generateBlock()
|
||||
require.NoError(t, err)
|
||||
|
||||
balanceStr, err = runArkCommand("balance")
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, json.Unmarshal([]byte(balanceStr), &balance))
|
||||
require.Equal(t, balanceBefore+1000, balance.Offchain.Total)
|
||||
}
|
||||
|
||||
func TestSendOffchain(t *testing.T) {
|
||||
_, err := runArkCommand("onboard", "--amount", "1000", "--password", password)
|
||||
require.NoError(t, err)
|
||||
err = generateBlock()
|
||||
require.NoError(t, err)
|
||||
|
||||
var receive arkReceive
|
||||
receiveStr, err := runArkCommand("receive")
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, json.Unmarshal([]byte(receiveStr), &receive))
|
||||
|
||||
_, err = runArkCommand("send", "--amount", "1000", "--to", receive.Offchain, "--password", password)
|
||||
require.NoError(t, err)
|
||||
|
||||
var balance arkBalance
|
||||
balanceStr, err := runArkCommand("balance")
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, json.Unmarshal([]byte(balanceStr), &balance))
|
||||
require.NotZero(t, balance.Offchain.Total)
|
||||
}
|
||||
|
||||
func TestUnilateralExit(t *testing.T) {
|
||||
_, err := runArkCommand("onboard", "--amount", "1000", "--password", password)
|
||||
require.NoError(t, err)
|
||||
err = generateBlock()
|
||||
require.NoError(t, err)
|
||||
|
||||
var balance arkBalance
|
||||
balanceStr, err := runArkCommand("balance")
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, json.Unmarshal([]byte(balanceStr), &balance))
|
||||
require.NotZero(t, balance.Offchain.Total)
|
||||
require.Len(t, balance.Onchain.Locked, 0)
|
||||
|
||||
_, err = runArkCommand("redeem", "--force", "--password", password)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = generateBlock()
|
||||
require.NoError(t, err)
|
||||
|
||||
balanceStr, err = runArkCommand("balance")
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, json.Unmarshal([]byte(balanceStr), &balance))
|
||||
require.Zero(t, balance.Offchain.Total)
|
||||
require.Len(t, balance.Onchain.Locked, 1)
|
||||
|
||||
lockedBalance := balance.Onchain.Locked[0].Amount
|
||||
require.NotZero(t, lockedBalance)
|
||||
}
|
||||
|
||||
func TestCollaborativeExit(t *testing.T) {
|
||||
_, err := runArkCommand("onboard", "--amount", "1000", "--password", password)
|
||||
require.NoError(t, err)
|
||||
err = generateBlock()
|
||||
require.NoError(t, err)
|
||||
|
||||
var receive arkReceive
|
||||
receiveStr, err := runArkCommand("receive")
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, json.Unmarshal([]byte(receiveStr), &receive))
|
||||
|
||||
var balance arkBalance
|
||||
balanceStr, err := runArkCommand("balance")
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, json.Unmarshal([]byte(balanceStr), &balance))
|
||||
|
||||
balanceBefore := balance.Offchain.Total
|
||||
balanceOnchainBefore := balance.Onchain.Spendable
|
||||
|
||||
_, err = runArkCommand("redeem", "--amount", "1000", "--address", receive.Onchain, "--password", password)
|
||||
require.NoError(t, err)
|
||||
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
balanceStr, err = runArkCommand("balance")
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, json.Unmarshal([]byte(balanceStr), &balance))
|
||||
|
||||
require.Equal(t, balanceBefore-1000, balance.Offchain.Total)
|
||||
require.Equal(t, balanceOnchainBefore+1000, balance.Onchain.Spendable)
|
||||
}
|
||||
109
server/test/e2e/utils.go
Normal file
109
server/test/e2e/utils.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
password = "password"
|
||||
)
|
||||
|
||||
type arkBalance struct {
|
||||
Offchain struct {
|
||||
Total int `json:"total"`
|
||||
} `json:"offchain_balance"`
|
||||
Onchain struct {
|
||||
Spendable int `json:"spendable_amount"`
|
||||
Locked []struct {
|
||||
Amount int `json:"amount"`
|
||||
SpendableAt string `json:"spendable_at"`
|
||||
} `json:"locked_amount"`
|
||||
} `json:"onchain_balance"`
|
||||
}
|
||||
|
||||
type arkReceive struct {
|
||||
Offchain string `json:"offchain_address"`
|
||||
Onchain string `json:"onchain_address"`
|
||||
}
|
||||
|
||||
func runOceanCommand(arg ...string) (string, error) {
|
||||
args := append([]string{"exec", "oceand", "ocean"}, arg...)
|
||||
return runCommand("docker", args...)
|
||||
}
|
||||
|
||||
func runArkCommand(arg ...string) (string, error) {
|
||||
args := append([]string{"exec", "-t", "arkd", "ark"}, arg...)
|
||||
return runCommand("docker", args...)
|
||||
}
|
||||
|
||||
func generateBlock() error {
|
||||
if _, err := runCommand("nigiri", "rpc", "--liquid", "generatetoaddress", "1", "el1qqwk722tghgkgmh3r2ph4d2apwj0dy9xnzlenzklx8jg3z299fpaw56trre9gpk6wmw0u4qycajqeva3t7lzp7wnacvwxha59r"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
time.Sleep(6 * time.Second)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runCommand(name string, arg ...string) (string, error) {
|
||||
errb := new(strings.Builder)
|
||||
cmd := newCommand(name, arg...)
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
stderr, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
output := new(strings.Builder)
|
||||
errorb := new(strings.Builder)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
io.Copy(output, stdout)
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
io.Copy(errorb, stderr)
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
if err := cmd.Wait(); err != nil {
|
||||
if errMsg := errorb.String(); len(errMsg) > 0 {
|
||||
return "", fmt.Errorf(errMsg)
|
||||
}
|
||||
|
||||
if outMsg := output.String(); len(outMsg) > 0 {
|
||||
return "", fmt.Errorf(outMsg)
|
||||
}
|
||||
|
||||
return "", err
|
||||
}
|
||||
|
||||
if errMsg := errb.String(); len(errMsg) > 0 {
|
||||
return "", fmt.Errorf(errMsg)
|
||||
}
|
||||
|
||||
return strings.Trim(output.String(), "\n"), nil
|
||||
}
|
||||
|
||||
func newCommand(name string, arg ...string) *exec.Cmd {
|
||||
cmd := exec.Command(name, arg...)
|
||||
return cmd
|
||||
}
|
||||
Reference in New Issue
Block a user