mirror of
https://github.com/aljazceru/ark.git
synced 2025-12-17 12:14:21 +01:00
Cleanup (#121)
* Cleanup common * Cleanup client * Cleanup server * Renamings * Tidy up proto * Update ocean protos * Fixes * Fixes
This commit is contained in:
committed by
GitHub
parent
1650ea5935
commit
6d0d03e316
@@ -26,16 +26,20 @@ var balanceCommand = cli.Command{
|
|||||||
func balanceAction(ctx *cli.Context) error {
|
func balanceAction(ctx *cli.Context) error {
|
||||||
withExpiryDetails := ctx.Bool("expiry-details")
|
withExpiryDetails := ctx.Bool("expiry-details")
|
||||||
|
|
||||||
client, cancel, err := getClientFromState(ctx)
|
client, cancel, err := getClientFromState()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
offchainAddr, onchainAddr, err := getAddress()
|
offchainAddr, onchainAddr, redemptionAddr, err := getAddress()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
_, network := getNetwork()
|
||||||
|
// No need to check for error here becuase this function is called also by getAddress().
|
||||||
|
// nolint:all
|
||||||
|
unilateralExitDelay, _ := getUnilateralExitDelay()
|
||||||
|
|
||||||
wg := &sync.WaitGroup{}
|
wg := &sync.WaitGroup{}
|
||||||
wg.Add(3)
|
wg.Add(3)
|
||||||
@@ -45,7 +49,7 @@ func balanceAction(ctx *cli.Context) error {
|
|||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
explorer := NewExplorer()
|
explorer := NewExplorer()
|
||||||
balance, amountByExpiration, err := getOffchainBalance(
|
balance, amountByExpiration, err := getOffchainBalance(
|
||||||
ctx, explorer, client, offchainAddr, withExpiryDetails,
|
ctx.Context, explorer, client, offchainAddr, withExpiryDetails,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
chRes <- balanceRes{0, 0, nil, nil, err}
|
chRes <- balanceRes{0, 0, nil, nil, err}
|
||||||
@@ -57,7 +61,8 @@ func balanceAction(ctx *cli.Context) error {
|
|||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
balance, err := getOnchainBalance(onchainAddr)
|
explorer := NewExplorer()
|
||||||
|
balance, err := explorer.GetBalance(onchainAddr, network.AssetID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
chRes <- balanceRes{0, 0, nil, nil, err}
|
chRes <- balanceRes{0, 0, nil, nil, err}
|
||||||
return
|
return
|
||||||
@@ -67,13 +72,17 @@ func balanceAction(ctx *cli.Context) error {
|
|||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
availableBalance, futureBalance, err := getOnchainVtxosBalance()
|
explorer := NewExplorer()
|
||||||
|
|
||||||
|
spendableBalance, lockedBalance, err := explorer.GetRedeemedVtxosBalance(
|
||||||
|
redemptionAddr, unilateralExitDelay,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
chRes <- balanceRes{0, 0, nil, nil, err}
|
chRes <- balanceRes{0, 0, nil, nil, err}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
chRes <- balanceRes{0, availableBalance, futureBalance, nil, err}
|
chRes <- balanceRes{0, spendableBalance, lockedBalance, nil, err}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
@@ -90,11 +99,11 @@ func balanceAction(ctx *cli.Context) error {
|
|||||||
if res.offchainBalance > 0 {
|
if res.offchainBalance > 0 {
|
||||||
offchainBalance = res.offchainBalance
|
offchainBalance = res.offchainBalance
|
||||||
}
|
}
|
||||||
if res.onchainBalance > 0 {
|
if res.onchainSpendableBalance > 0 {
|
||||||
onchainBalance += res.onchainBalance
|
onchainBalance += res.onchainSpendableBalance
|
||||||
}
|
}
|
||||||
if res.amountByExpiration != nil {
|
if res.offchainBalanceByExpiration != nil {
|
||||||
for timestamp, amount := range res.amountByExpiration {
|
for timestamp, amount := range res.offchainBalanceByExpiration {
|
||||||
if nextExpiration == 0 || timestamp < nextExpiration {
|
if nextExpiration == 0 || timestamp < nextExpiration {
|
||||||
nextExpiration = timestamp
|
nextExpiration = timestamp
|
||||||
}
|
}
|
||||||
@@ -109,8 +118,8 @@ func balanceAction(ctx *cli.Context) error {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if res.futureBalance != nil {
|
if res.onchainLockedBalance != nil {
|
||||||
for timestamp, amount := range res.futureBalance {
|
for timestamp, amount := range res.onchainLockedBalance {
|
||||||
fancyTime := time.Unix(timestamp, 0).Format("2006-01-02 15:04:05")
|
fancyTime := time.Unix(timestamp, 0).Format("2006-01-02 15:04:05")
|
||||||
lockedOnchainBalance = append(
|
lockedOnchainBalance = append(
|
||||||
lockedOnchainBalance,
|
lockedOnchainBalance,
|
||||||
@@ -178,8 +187,8 @@ func balanceAction(ctx *cli.Context) error {
|
|||||||
|
|
||||||
type balanceRes struct {
|
type balanceRes struct {
|
||||||
offchainBalance uint64
|
offchainBalance uint64
|
||||||
onchainBalance uint64
|
onchainSpendableBalance uint64
|
||||||
futureBalance map[int64]uint64 // availableAt -> onchain balance
|
onchainLockedBalance map[int64]uint64
|
||||||
amountByExpiration map[int64]uint64 // expireAt -> offchain balance
|
offchainBalanceByExpiration map[int64]uint64
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1"
|
arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1"
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/credentials"
|
"google.golang.org/grpc/credentials"
|
||||||
"google.golang.org/grpc/credentials/insecure"
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
@@ -21,13 +21,10 @@ type vtxo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getVtxos(
|
func getVtxos(
|
||||||
ctx *cli.Context,
|
ctx context.Context, explorer Explorer, client arkv1.ArkServiceClient,
|
||||||
explorer Explorer,
|
addr string, withExpiration bool,
|
||||||
client arkv1.ArkServiceClient,
|
|
||||||
addr string,
|
|
||||||
withExpiration bool,
|
|
||||||
) ([]vtxo, error) {
|
) ([]vtxo, error) {
|
||||||
response, err := client.ListVtxos(ctx.Context, &arkv1.ListVtxosRequest{
|
response, err := client.ListVtxos(ctx, &arkv1.ListVtxosRequest{
|
||||||
Address: addr,
|
Address: addr,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -54,7 +51,7 @@ func getVtxos(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for vtxoTxid, branch := range redeemBranches {
|
for vtxoTxid, branch := range redeemBranches {
|
||||||
expiration, err := branch.ExpireAt()
|
expiration, err := branch.expireAt()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -70,19 +67,19 @@ func getVtxos(
|
|||||||
return vtxos, nil
|
return vtxos, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getClientFromState(ctx *cli.Context) (arkv1.ArkServiceClient, func(), error) {
|
func getClientFromState() (arkv1.ArkServiceClient, func(), error) {
|
||||||
state, err := getState()
|
state, err := getState()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
addr, ok := state["ark_url"].(string)
|
addr := state[ASP_URL]
|
||||||
if !ok {
|
if len(addr) <= 0 {
|
||||||
return nil, nil, fmt.Errorf("missing ark_url")
|
return nil, nil, fmt.Errorf("missing asp url")
|
||||||
}
|
}
|
||||||
return getClient(ctx, addr)
|
return getClient(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getClient(ctx *cli.Context, addr string) (arkv1.ArkServiceClient, func(), error) {
|
func getClient(addr string) (arkv1.ArkServiceClient, func(), error) {
|
||||||
creds := insecure.NewCredentials()
|
creds := insecure.NewCredentials()
|
||||||
port := 80
|
port := 80
|
||||||
if strings.HasPrefix(addr, "https://") {
|
if strings.HasPrefix(addr, "https://") {
|
||||||
|
|||||||
452
client/common.go
452
client/common.go
@@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -9,6 +10,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -18,7 +20,6 @@ import (
|
|||||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
"github.com/vulpemventures/go-elements/address"
|
"github.com/vulpemventures/go-elements/address"
|
||||||
"github.com/vulpemventures/go-elements/elementsutil"
|
"github.com/vulpemventures/go-elements/elementsutil"
|
||||||
"github.com/vulpemventures/go-elements/network"
|
"github.com/vulpemventures/go-elements/network"
|
||||||
@@ -44,9 +45,9 @@ func verifyPassword(password []byte) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
passwordHashString, ok := state["password_hash"].(string)
|
passwordHashString := state[PASSWORD_HASH]
|
||||||
if !ok {
|
if len(passwordHashString) <= 0 {
|
||||||
return fmt.Errorf("password hash not found")
|
return fmt.Errorf("missing password hash")
|
||||||
}
|
}
|
||||||
|
|
||||||
passwordHash, err := hex.DecodeString(passwordHashString)
|
passwordHash, err := hex.DecodeString(passwordHashString)
|
||||||
@@ -84,9 +85,9 @@ func privateKeyFromPassword() (*secp256k1.PrivateKey, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
encryptedPrivateKeyString, ok := state["encrypted_private_key"].(string)
|
encryptedPrivateKeyString := state[ENCRYPTED_PRVKEY]
|
||||||
if !ok {
|
if len(encryptedPrivateKeyString) <= 0 {
|
||||||
return nil, fmt.Errorf("encrypted private key not found")
|
return nil, fmt.Errorf("missing encrypted private key")
|
||||||
}
|
}
|
||||||
|
|
||||||
encryptedPrivateKey, err := hex.DecodeString(encryptedPrivateKeyString)
|
encryptedPrivateKey, err := hex.DecodeString(encryptedPrivateKeyString)
|
||||||
@@ -100,8 +101,8 @@ func privateKeyFromPassword() (*secp256k1.PrivateKey, error) {
|
|||||||
}
|
}
|
||||||
fmt.Println("wallet unlocked")
|
fmt.Println("wallet unlocked")
|
||||||
|
|
||||||
cypher := NewAES128Cypher()
|
cypher := newAES128Cypher()
|
||||||
privateKeyBytes, err := cypher.Decrypt(encryptedPrivateKey, password)
|
privateKeyBytes, err := cypher.decrypt(encryptedPrivateKey, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -116,9 +117,9 @@ func getWalletPublicKey() (*secp256k1.PublicKey, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
publicKeyString, ok := state["public_key"].(string)
|
publicKeyString := state[PUBKEY]
|
||||||
if !ok {
|
if len(publicKeyString) <= 0 {
|
||||||
return nil, fmt.Errorf("public key not found")
|
return nil, fmt.Errorf("missing public key")
|
||||||
}
|
}
|
||||||
|
|
||||||
publicKeyBytes, err := hex.DecodeString(publicKeyString)
|
publicKeyBytes, err := hex.DecodeString(publicKeyString)
|
||||||
@@ -129,15 +130,15 @@ func getWalletPublicKey() (*secp256k1.PublicKey, error) {
|
|||||||
return secp256k1.ParsePubKey(publicKeyBytes)
|
return secp256k1.ParsePubKey(publicKeyBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getServiceProviderPublicKey() (*secp256k1.PublicKey, error) {
|
func getAspPublicKey() (*secp256k1.PublicKey, error) {
|
||||||
state, err := getState()
|
state, err := getState()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
arkPubKey, ok := state["ark_pubkey"].(string)
|
arkPubKey := state[ASP_PUBKEY]
|
||||||
if !ok {
|
if len(arkPubKey) <= 0 {
|
||||||
return nil, fmt.Errorf("ark public key not found")
|
return nil, fmt.Errorf("missing asp public key")
|
||||||
}
|
}
|
||||||
|
|
||||||
pubKeyBytes, err := hex.DecodeString(arkPubKey)
|
pubKeyBytes, err := hex.DecodeString(arkPubKey)
|
||||||
@@ -148,32 +149,41 @@ func getServiceProviderPublicKey() (*secp256k1.PublicKey, error) {
|
|||||||
return secp256k1.ParsePubKey(pubKeyBytes)
|
return secp256k1.ParsePubKey(pubKeyBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLifetime() (int64, error) {
|
func getRoundLifetime() (int64, error) {
|
||||||
state, err := getState()
|
state, err := getState()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return -1, err
|
||||||
}
|
}
|
||||||
|
|
||||||
lifetime, ok := state["ark_lifetime"].(float64)
|
lifetime := state[ROUND_LIFETIME]
|
||||||
if !ok {
|
if len(lifetime) <= 0 {
|
||||||
return 0, fmt.Errorf("lifetime not found")
|
return -1, fmt.Errorf("missing round lifetime")
|
||||||
}
|
}
|
||||||
|
|
||||||
return int64(lifetime), nil
|
roundLifetime, err := strconv.Atoi(lifetime)
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
return int64(roundLifetime), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getExitDelay() (int64, error) {
|
func getUnilateralExitDelay() (int64, error) {
|
||||||
state, err := getState()
|
state, err := getState()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return -1, err
|
||||||
}
|
}
|
||||||
|
|
||||||
exitDelay, ok := state["exit_delay"].(float64)
|
delay := state[UNILATERAL_EXIT_DELAY]
|
||||||
if !ok {
|
if len(delay) <= 0 {
|
||||||
return 0, fmt.Errorf("exit delay not found")
|
return -1, fmt.Errorf("missing unilateral exit delay")
|
||||||
}
|
}
|
||||||
|
|
||||||
return int64(exitDelay), nil
|
redeemDelay, err := strconv.Atoi(delay)
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return int64(redeemDelay), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func coinSelect(vtxos []vtxo, amount uint64) ([]vtxo, uint64, error) {
|
func coinSelect(vtxos []vtxo, amount uint64) ([]vtxo, uint64, error) {
|
||||||
@@ -201,7 +211,7 @@ func coinSelect(vtxos []vtxo, amount uint64) ([]vtxo, uint64, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if selectedAmount < amount {
|
if selectedAmount < amount {
|
||||||
return nil, 0, fmt.Errorf("insufficient balance: %d to cover %d", selectedAmount, amount)
|
return nil, 0, fmt.Errorf("not enough funds to cover amount%d", amount)
|
||||||
}
|
}
|
||||||
|
|
||||||
change := selectedAmount - amount
|
change := selectedAmount - amount
|
||||||
@@ -217,7 +227,8 @@ func coinSelect(vtxos []vtxo, amount uint64) ([]vtxo, uint64, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getOffchainBalance(
|
func getOffchainBalance(
|
||||||
ctx *cli.Context, explorer Explorer, client arkv1.ArkServiceClient, addr string, withExpiration bool,
|
ctx context.Context, explorer Explorer, client arkv1.ArkServiceClient,
|
||||||
|
addr string, withExpiration bool,
|
||||||
) (uint64, map[int64]uint64, error) {
|
) (uint64, map[int64]uint64, error) {
|
||||||
amountByExpiration := make(map[int64]uint64, 0)
|
amountByExpiration := make(map[int64]uint64, 0)
|
||||||
|
|
||||||
@@ -243,120 +254,6 @@ func getOffchainBalance(
|
|||||||
return balance, amountByExpiration, nil
|
return balance, amountByExpiration, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type utxo struct {
|
|
||||||
Txid string `json:"txid"`
|
|
||||||
Vout uint32 `json:"vout"`
|
|
||||||
Amount uint64 `json:"value"`
|
|
||||||
Asset string `json:"asset"`
|
|
||||||
Status struct {
|
|
||||||
Confirmed bool `json:"confirmed"`
|
|
||||||
Blocktime int64 `json:"block_time"`
|
|
||||||
} `json:"status"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func getOnchainUtxos(addr string) ([]utxo, error) {
|
|
||||||
_, net := getNetwork()
|
|
||||||
baseUrl := explorerUrl[net.Name]
|
|
||||||
resp, err := http.Get(fmt.Sprintf("%s/address/%s/utxo", baseUrl, addr))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer resp.Body.Close()
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return nil, fmt.Errorf(string(body))
|
|
||||||
}
|
|
||||||
payload := []utxo{}
|
|
||||||
if err := json.Unmarshal(body, &payload); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return payload, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getOnchainBalance(addr string) (uint64, error) {
|
|
||||||
payload, err := getOnchainUtxos(addr)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, net := getNetwork()
|
|
||||||
balance := uint64(0)
|
|
||||||
for _, p := range payload {
|
|
||||||
if p.Asset != net.AssetID {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
balance += p.Amount
|
|
||||||
}
|
|
||||||
return balance, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getOnchainVtxosBalance() (availableBalance uint64, futureBalance map[int64]uint64, err error) {
|
|
||||||
userPubKey, err := getWalletPublicKey()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
aspPublicKey, err := getServiceProviderPublicKey()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
exitDelay, err := getExitDelay()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
vtxoTapKey, _, err := computeVtxoTaprootScript(userPubKey, aspPublicKey, uint(exitDelay))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, net := getNetwork()
|
|
||||||
|
|
||||||
payment, err := payment.FromTweakedKey(vtxoTapKey, net, nil)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
addr, err := payment.TaprootAddress()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
utxos, err := getOnchainUtxos(addr)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
availableBalance = uint64(0)
|
|
||||||
futureBalance = make(map[int64]uint64, 0)
|
|
||||||
now := time.Now()
|
|
||||||
for _, utxo := range utxos {
|
|
||||||
blocktime := now
|
|
||||||
if utxo.Status.Confirmed {
|
|
||||||
blocktime = time.Unix(utxo.Status.Blocktime, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
availableAt := blocktime.Add(time.Duration(exitDelay) * time.Second)
|
|
||||||
if availableAt.After(now) {
|
|
||||||
if _, ok := futureBalance[availableAt.Unix()]; !ok {
|
|
||||||
futureBalance[availableAt.Unix()] = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
futureBalance[availableAt.Unix()] += utxo.Amount
|
|
||||||
} else {
|
|
||||||
availableBalance += utxo.Amount
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTxBlocktime(txid string) (confirmed bool, blocktime int64, err error) {
|
func getTxBlocktime(txid string) (confirmed bool, blocktime int64, err error) {
|
||||||
_, net := getNetwork()
|
_, net := getNetwork()
|
||||||
baseUrl := explorerUrl[net.Name]
|
baseUrl := explorerUrl[net.Name]
|
||||||
@@ -392,35 +289,13 @@ func getTxBlocktime(txid string) (confirmed bool, blocktime int64, err error) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func broadcast(txHex string) (string, error) {
|
|
||||||
_, net := getNetwork()
|
|
||||||
body := bytes.NewBuffer([]byte(txHex))
|
|
||||||
|
|
||||||
baseUrl := explorerUrl[net.Name]
|
|
||||||
resp, err := http.Post(fmt.Sprintf("%s/tx", baseUrl), "text/plain", body)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
bodyResponse, err := io.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return "", fmt.Errorf(string(bodyResponse))
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(bodyResponse), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getNetwork() (*common.Network, *network.Network) {
|
func getNetwork() (*common.Network, *network.Network) {
|
||||||
state, err := getState()
|
state, err := getState()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &common.TestNet, &network.Testnet
|
return &common.TestNet, &network.Testnet
|
||||||
}
|
}
|
||||||
|
|
||||||
net, ok := state["network"]
|
net, ok := state[NETWORK]
|
||||||
if !ok {
|
if !ok {
|
||||||
return &common.MainNet, &network.Liquid
|
return &common.MainNet, &network.Liquid
|
||||||
}
|
}
|
||||||
@@ -430,30 +305,54 @@ func getNetwork() (*common.Network, *network.Network) {
|
|||||||
return &common.MainNet, &network.Liquid
|
return &common.MainNet, &network.Liquid
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAddress() (offchainAddr, onchainAddr string, err error) {
|
func getAddress() (offchainAddr, onchainAddr, redemptionAddr string, err error) {
|
||||||
publicKey, err := getWalletPublicKey()
|
userPubkey, err := getWalletPublicKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
aspPublicKey, err := getServiceProviderPublicKey()
|
aspPubkey, err := getAspPublicKey()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
unilateralExitDelay, err := getUnilateralExitDelay()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
arkNet, liquidNet := getNetwork()
|
arkNet, liquidNet := getNetwork()
|
||||||
|
|
||||||
arkAddr, err := common.EncodeAddress(arkNet.Addr, publicKey, aspPublicKey)
|
arkAddr, err := common.EncodeAddress(arkNet.Addr, userPubkey, aspPubkey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
p2wpkh := payment.FromPublicKey(publicKey, liquidNet, nil)
|
p2wpkh := payment.FromPublicKey(userPubkey, liquidNet, nil)
|
||||||
liquidAddr, err := p2wpkh.WitnessPubKeyHash()
|
liquidAddr, err := p2wpkh.WitnessPubKeyHash()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vtxoTapKey, _, err := computeVtxoTaprootScript(
|
||||||
|
userPubkey, aspPubkey, uint(unilateralExitDelay),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, net := getNetwork()
|
||||||
|
|
||||||
|
payment, err := payment.FromTweakedKey(vtxoTapKey, net, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
redemptionAddr, err = payment.TaprootAddress()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
offchainAddr = arkAddr
|
offchainAddr = arkAddr
|
||||||
onchainAddr = liquidAddr
|
onchainAddr = liquidAddr
|
||||||
|
|
||||||
@@ -471,14 +370,10 @@ func printJSON(resp interface{}) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func handleRoundStream(
|
func handleRoundStream(
|
||||||
ctx *cli.Context,
|
ctx context.Context, client arkv1.ArkServiceClient, paymentID string,
|
||||||
client arkv1.ArkServiceClient,
|
vtxosToSign []vtxo, secKey *secp256k1.PrivateKey, receivers []*arkv1.Output,
|
||||||
paymentID string,
|
|
||||||
vtxosToSign []vtxo,
|
|
||||||
secKey *secp256k1.PrivateKey,
|
|
||||||
receivers []*arkv1.Output,
|
|
||||||
) (poolTxID string, err error) {
|
) (poolTxID string, err error) {
|
||||||
stream, err := client.GetEventStream(ctx.Context, &arkv1.GetEventStreamRequest{})
|
stream, err := client.GetEventStream(ctx, &arkv1.GetEventStreamRequest{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -502,54 +397,53 @@ func handleRoundStream(
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if event.GetRoundFailed() != nil {
|
if e := event.GetRoundFailed(); e != nil {
|
||||||
pingStop()
|
pingStop()
|
||||||
return "", fmt.Errorf("round failed: %s", event.GetRoundFailed().GetReason())
|
return "", fmt.Errorf("round failed: %s", e.GetReason())
|
||||||
}
|
}
|
||||||
|
|
||||||
if event.GetRoundFinalization() != nil {
|
if e := event.GetRoundFinalization(); e != nil {
|
||||||
// stop pinging as soon as we receive some forfeit txs
|
// stop pinging as soon as we receive some forfeit txs
|
||||||
pingStop()
|
pingStop()
|
||||||
fmt.Println("round finalization started")
|
fmt.Println("round finalization started")
|
||||||
|
|
||||||
poolPartialTx := event.GetRoundFinalization().GetPoolPartialTx()
|
poolTxStr := e.GetPoolPartialTx()
|
||||||
poolTransaction, err := psetv2.NewPsetFromBase64(poolPartialTx)
|
poolTx, err := psetv2.NewPsetFromBase64(poolTxStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
congestionTree, err := toCongestionTree(event.GetRoundFinalization().GetCongestionTree())
|
congestionTree, err := toCongestionTree(e.GetCongestionTree())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
aspPublicKey, err := getServiceProviderPublicKey()
|
aspPubkey, err := getAspPublicKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
seconds, err := getLifetime()
|
roundLifetime, err := getRoundLifetime()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate the congestion tree
|
// validate the congestion tree
|
||||||
if err := tree.ValidateCongestionTree(
|
if err := tree.ValidateCongestionTree(
|
||||||
congestionTree,
|
congestionTree, poolTxStr, aspPubkey, int64(roundLifetime),
|
||||||
poolPartialTx,
|
|
||||||
aspPublicKey,
|
|
||||||
int64(seconds),
|
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
exitDelay, err := getExitDelay()
|
exitDelay, err := getUnilateralExitDelay()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, receiver := range receivers {
|
for _, receiver := range receivers {
|
||||||
isOnChain, onchainScript, userPubKey, err := decodeReceiverAddress(receiver.Address)
|
isOnChain, onchainScript, userPubkey, err := decodeReceiverAddress(
|
||||||
|
receiver.Address,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -558,10 +452,13 @@ func handleRoundStream(
|
|||||||
// collaborative exit case
|
// collaborative exit case
|
||||||
// search for the output in the pool tx
|
// search for the output in the pool tx
|
||||||
found := false
|
found := false
|
||||||
for _, output := range poolTransaction.Outputs {
|
for _, output := range poolTx.Outputs {
|
||||||
if bytes.Equal(output.Script, onchainScript) {
|
if bytes.Equal(output.Script, onchainScript) {
|
||||||
if output.Value != receiver.Amount {
|
if output.Value != receiver.Amount {
|
||||||
return "", fmt.Errorf("invalid collaborative exit output amount: got %d, want %d", output.Value, receiver.Amount)
|
return "", fmt.Errorf(
|
||||||
|
"invalid collaborative exit output amount: got %d, want %d",
|
||||||
|
output.Value, receiver.Amount,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
found = true
|
found = true
|
||||||
@@ -570,7 +467,9 @@ func handleRoundStream(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !found {
|
if !found {
|
||||||
return "", fmt.Errorf("collaborative exit output not found: %s", receiver.Address)
|
return "", fmt.Errorf(
|
||||||
|
"collaborative exit output not found: %s", receiver.Address,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
continue
|
continue
|
||||||
@@ -581,7 +480,9 @@ func handleRoundStream(
|
|||||||
found := false
|
found := false
|
||||||
|
|
||||||
// compute the receiver output taproot key
|
// compute the receiver output taproot key
|
||||||
outputTapKey, _, err := computeVtxoTaprootScript(userPubKey, aspPublicKey, uint(exitDelay))
|
outputTapKey, _, err := computeVtxoTaprootScript(
|
||||||
|
userPubkey, aspPubkey, uint(exitDelay),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -597,7 +498,9 @@ func handleRoundStream(
|
|||||||
if len(output.Script) == 0 {
|
if len(output.Script) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if bytes.Equal(output.Script[2:], schnorr.SerializePubKey(outputTapKey)) {
|
if bytes.Equal(
|
||||||
|
output.Script[2:], schnorr.SerializePubKey(outputTapKey),
|
||||||
|
) {
|
||||||
if output.Value != receiver.Amount {
|
if output.Value != receiver.Amount {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -613,13 +516,15 @@ func handleRoundStream(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !found {
|
if !found {
|
||||||
return "", fmt.Errorf("off-chain send output not found: %s", receiver.Address)
|
return "", fmt.Errorf(
|
||||||
|
"off-chain send output not found: %s", receiver.Address,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("congestion tree validated")
|
fmt.Println("congestion tree validated")
|
||||||
|
|
||||||
forfeits := event.GetRoundFinalization().GetForfeitTxs()
|
forfeits := e.GetForfeitTxs()
|
||||||
signedForfeits := make([]string, 0)
|
signedForfeits := make([]string, 0)
|
||||||
|
|
||||||
fmt.Print("signing forfeit txs... ")
|
fmt.Print("signing forfeit txs... ")
|
||||||
@@ -665,7 +570,7 @@ func handleRoundStream(
|
|||||||
|
|
||||||
fmt.Printf("%d signed\n", len(signedForfeits))
|
fmt.Printf("%d signed\n", len(signedForfeits))
|
||||||
fmt.Print("finalizing payment... ")
|
fmt.Print("finalizing payment... ")
|
||||||
_, err = client.FinalizePayment(ctx.Context, &arkv1.FinalizePaymentRequest{
|
_, err = client.FinalizePayment(ctx, &arkv1.FinalizePaymentRequest{
|
||||||
SignedForfeitTxs: signedForfeits,
|
SignedForfeitTxs: signedForfeits,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -687,8 +592,10 @@ func handleRoundStream(
|
|||||||
|
|
||||||
// send 1 ping message every 5 seconds to signal to the ark service that we are still alive
|
// send 1 ping message every 5 seconds to signal to the ark service that we are still alive
|
||||||
// returns a function that can be used to stop the pinging
|
// returns a function that can be used to stop the pinging
|
||||||
func ping(ctx *cli.Context, client arkv1.ArkServiceClient, req *arkv1.PingRequest) func() {
|
func ping(
|
||||||
_, err := client.Ping(ctx.Context, req)
|
ctx context.Context, client arkv1.ArkServiceClient, req *arkv1.PingRequest,
|
||||||
|
) func() {
|
||||||
|
_, err := client.Ping(ctx, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -698,7 +605,7 @@ func ping(ctx *cli.Context, client arkv1.ArkServiceClient, req *arkv1.PingReques
|
|||||||
go func(t *time.Ticker) {
|
go func(t *time.Ticker) {
|
||||||
for range t.C {
|
for range t.C {
|
||||||
// nolint
|
// nolint
|
||||||
client.Ping(ctx.Context, req)
|
client.Ping(ctx, req)
|
||||||
}
|
}
|
||||||
}(ticker)
|
}(ticker)
|
||||||
|
|
||||||
@@ -758,18 +665,15 @@ func castCongestionTree(congestionTree tree.CongestionTree) *arkv1.Tree {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func decodeReceiverAddress(addr string) (
|
func decodeReceiverAddress(addr string) (
|
||||||
isOnChainAddress bool,
|
bool, []byte, *secp256k1.PublicKey, error,
|
||||||
onchainScript []byte,
|
|
||||||
userPubKey *secp256k1.PublicKey,
|
|
||||||
err error,
|
|
||||||
) {
|
) {
|
||||||
outputScript, err := address.ToOutputScript(addr)
|
outputScript, err := address.ToOutputScript(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_, userPubKey, _, err = common.DecodeAddress(addr)
|
_, userPubkey, _, err := common.DecodeAddress(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return false, nil, nil, err
|
||||||
}
|
}
|
||||||
return false, nil, userPubKey, nil
|
return false, nil, userPubkey, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, outputScript, nil, nil
|
return true, outputScript, nil, nil
|
||||||
@@ -777,18 +681,20 @@ func decodeReceiverAddress(addr string) (
|
|||||||
|
|
||||||
func findSweepClosure(
|
func findSweepClosure(
|
||||||
congestionTree tree.CongestionTree,
|
congestionTree tree.CongestionTree,
|
||||||
) (sweepClosure *taproot.TapElementsLeaf, seconds uint, err error) {
|
) (*taproot.TapElementsLeaf, uint, error) {
|
||||||
root, err := congestionTree.Root()
|
root, err := congestionTree.Root()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// find the sweep closure
|
// find the sweep closure
|
||||||
tx, err := psetv2.NewPsetFromBase64(root.Tx)
|
tx, err := psetv2.NewPsetFromBase64(root.Tx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var seconds uint
|
||||||
|
var sweepClosure *taproot.TapElementsLeaf
|
||||||
for _, tapLeaf := range tx.Inputs[0].TapLeafScript {
|
for _, tapLeaf := range tx.Inputs[0].TapLeafScript {
|
||||||
closure := &tree.CSVSigClosure{}
|
closure := &tree.CSVSigClosure{}
|
||||||
valid, err := closure.Decode(tapLeaf.Script)
|
valid, err := closure.Decode(tapLeaf.Script)
|
||||||
@@ -806,21 +712,19 @@ func findSweepClosure(
|
|||||||
return nil, 0, fmt.Errorf("sweep closure not found")
|
return nil, 0, fmt.Errorf("sweep closure not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return sweepClosure, seconds, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRedeemBranches(
|
func getRedeemBranches(
|
||||||
ctx *cli.Context,
|
ctx context.Context, explorer Explorer, client arkv1.ArkServiceClient,
|
||||||
explorer Explorer,
|
|
||||||
client arkv1.ArkServiceClient,
|
|
||||||
vtxos []vtxo,
|
vtxos []vtxo,
|
||||||
) (map[string]RedeemBranch, error) {
|
) (map[string]*redeemBranch, error) {
|
||||||
congestionTrees := make(map[string]tree.CongestionTree, 0) // poolTxid -> congestionTree
|
congestionTrees := make(map[string]tree.CongestionTree, 0)
|
||||||
redeemBranches := make(map[string]RedeemBranch, 0) // vtxo.txid -> redeemBranch
|
redeemBranches := make(map[string]*redeemBranch, 0)
|
||||||
|
|
||||||
for _, vtxo := range vtxos {
|
for _, vtxo := range vtxos {
|
||||||
if _, ok := congestionTrees[vtxo.poolTxid]; !ok {
|
if _, ok := congestionTrees[vtxo.poolTxid]; !ok {
|
||||||
round, err := client.GetRound(ctx.Context, &arkv1.GetRoundRequest{
|
round, err := client.GetRound(ctx, &arkv1.GetRoundRequest{
|
||||||
Txid: vtxo.poolTxid,
|
Txid: vtxo.poolTxid,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -836,7 +740,9 @@ func getRedeemBranches(
|
|||||||
congestionTrees[vtxo.poolTxid] = congestionTree
|
congestionTrees[vtxo.poolTxid] = congestionTree
|
||||||
}
|
}
|
||||||
|
|
||||||
redeemBranch, err := newRedeemBranch(ctx, explorer, congestionTrees[vtxo.poolTxid], vtxo)
|
redeemBranch, err := newRedeemBranch(
|
||||||
|
ctx, explorer, congestionTrees[vtxo.poolTxid], vtxo,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -848,18 +754,16 @@ func getRedeemBranches(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func computeVtxoTaprootScript(
|
func computeVtxoTaprootScript(
|
||||||
userPubKey *secp256k1.PublicKey,
|
userPubkey, aspPubkey *secp256k1.PublicKey, exitDelay uint,
|
||||||
aspPublicKey *secp256k1.PublicKey,
|
|
||||||
exitDelay uint,
|
|
||||||
) (*secp256k1.PublicKey, *taproot.TapscriptElementsProof, error) {
|
) (*secp256k1.PublicKey, *taproot.TapscriptElementsProof, error) {
|
||||||
redeemClosure := &tree.CSVSigClosure{
|
redeemClosure := &tree.CSVSigClosure{
|
||||||
Pubkey: userPubKey,
|
Pubkey: userPubkey,
|
||||||
Seconds: exitDelay,
|
Seconds: exitDelay,
|
||||||
}
|
}
|
||||||
|
|
||||||
forfeitClosure := &tree.ForfeitClosure{
|
forfeitClosure := &tree.ForfeitClosure{
|
||||||
Pubkey: userPubKey,
|
Pubkey: userPubkey,
|
||||||
AspPubkey: aspPublicKey,
|
AspPubkey: aspPubkey,
|
||||||
}
|
}
|
||||||
|
|
||||||
redeemLeaf, err := redeemClosure.Leaf()
|
redeemLeaf, err := redeemClosure.Leaf()
|
||||||
@@ -872,7 +776,9 @@ func computeVtxoTaprootScript(
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
vtxoTaprootTree := taproot.AssembleTaprootScriptTree(*redeemLeaf, *forfeitLeaf)
|
vtxoTaprootTree := taproot.AssembleTaprootScriptTree(
|
||||||
|
*redeemLeaf, *forfeitLeaf,
|
||||||
|
)
|
||||||
root := vtxoTaprootTree.RootNode.TapHash()
|
root := vtxoTaprootTree.RootNode.TapHash()
|
||||||
|
|
||||||
unspendableKey := tree.UnspendableKey()
|
unspendableKey := tree.UnspendableKey()
|
||||||
@@ -886,9 +792,7 @@ func computeVtxoTaprootScript(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func addVtxoInput(
|
func addVtxoInput(
|
||||||
updater *psetv2.Updater,
|
updater *psetv2.Updater, inputArgs psetv2.InputArgs, exitDelay uint,
|
||||||
inputArgs psetv2.InputArgs,
|
|
||||||
exitDelay uint,
|
|
||||||
tapLeafProof *taproot.TapscriptElementsProof,
|
tapLeafProof *taproot.TapscriptElementsProof,
|
||||||
) error {
|
) error {
|
||||||
sequence, err := common.BIP68EncodeAsNumber(exitDelay)
|
sequence, err := common.BIP68EncodeAsNumber(exitDelay)
|
||||||
@@ -912,18 +816,20 @@ func addVtxoInput(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func coinSelectOnchain(targetAmount uint64, exclude []utxo) (utxos []utxo, delayedUtxos []utxo, change uint64, err error) {
|
func coinSelectOnchain(
|
||||||
_, onchainAddr, err := getAddress()
|
explorer Explorer, targetAmount uint64, exclude []utxo,
|
||||||
|
) ([]utxo, []utxo, uint64, error) {
|
||||||
|
_, onchainAddr, _, err := getAddress()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, 0, err
|
return nil, nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
fromExplorer, err := getOnchainUtxos(onchainAddr)
|
fromExplorer, err := explorer.GetUtxos(onchainAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, 0, err
|
return nil, nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
utxos = make([]utxo, 0)
|
utxos := make([]utxo, 0)
|
||||||
selectedAmount := uint64(0)
|
selectedAmount := uint64(0)
|
||||||
for _, utxo := range fromExplorer {
|
for _, utxo := range fromExplorer {
|
||||||
if selectedAmount >= targetAmount {
|
if selectedAmount >= targetAmount {
|
||||||
@@ -944,22 +850,24 @@ func coinSelectOnchain(targetAmount uint64, exclude []utxo) (utxos []utxo, delay
|
|||||||
return utxos, nil, selectedAmount - targetAmount, nil
|
return utxos, nil, selectedAmount - targetAmount, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
userPubKey, err := getWalletPublicKey()
|
userPubkey, err := getWalletPublicKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, 0, err
|
return nil, nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
aspPublicKey, err := getServiceProviderPublicKey()
|
aspPubkey, err := getAspPublicKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, 0, err
|
return nil, nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
exitDelay, err := getExitDelay()
|
unilateralExitDelay, err := getUnilateralExitDelay()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, 0, err
|
return nil, nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
vtxoTapKey, _, err := computeVtxoTaprootScript(userPubKey, aspPublicKey, uint(exitDelay))
|
vtxoTapKey, _, err := computeVtxoTaprootScript(
|
||||||
|
userPubkey, aspPubkey, uint(unilateralExitDelay),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, 0, err
|
return nil, nil, 0, err
|
||||||
}
|
}
|
||||||
@@ -976,18 +884,20 @@ func coinSelectOnchain(targetAmount uint64, exclude []utxo) (utxos []utxo, delay
|
|||||||
return nil, nil, 0, err
|
return nil, nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
fromExplorer, err = getOnchainUtxos(addr)
|
fromExplorer, err = explorer.GetUtxos(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, 0, err
|
return nil, nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
delayedUtxos = make([]utxo, 0)
|
delayedUtxos := make([]utxo, 0)
|
||||||
for _, utxo := range fromExplorer {
|
for _, utxo := range fromExplorer {
|
||||||
if selectedAmount >= targetAmount {
|
if selectedAmount >= targetAmount {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
availableAt := time.Unix(utxo.Status.Blocktime, 0).Add(time.Duration(exitDelay) * time.Second)
|
availableAt := time.Unix(utxo.Status.Blocktime, 0).Add(
|
||||||
|
time.Duration(unilateralExitDelay) * time.Second,
|
||||||
|
)
|
||||||
if availableAt.After(time.Now()) {
|
if availableAt.After(time.Now()) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -1003,19 +913,18 @@ func coinSelectOnchain(targetAmount uint64, exclude []utxo) (utxos []utxo, delay
|
|||||||
}
|
}
|
||||||
|
|
||||||
if selectedAmount < targetAmount {
|
if selectedAmount < targetAmount {
|
||||||
return nil, nil, 0, fmt.Errorf("insufficient balance: %d to cover %d", selectedAmount, targetAmount)
|
return nil, nil, 0, fmt.Errorf(
|
||||||
|
"not enough funds to cover amount %d", targetAmount,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return utxos, delayedUtxos, selectedAmount - targetAmount, nil
|
return utxos, delayedUtxos, selectedAmount - targetAmount, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func addInputs(
|
func addInputs(
|
||||||
updater *psetv2.Updater,
|
updater *psetv2.Updater, utxos, delayedUtxos []utxo, net *network.Network,
|
||||||
selected []utxo, // the utxos to add owned by the P2WPKH script
|
|
||||||
delayedSelected []utxo, // the utxos to add owned by the VTXO script
|
|
||||||
net *network.Network,
|
|
||||||
) error {
|
) error {
|
||||||
_, onchainAddr, err := getAddress()
|
_, onchainAddr, _, err := getAddress()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1025,23 +934,22 @@ func addInputs(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, coin := range selected {
|
for _, utxo := range utxos {
|
||||||
fmt.Println("adding input", coin.Txid, coin.Vout)
|
|
||||||
if err := updater.AddInputs([]psetv2.InputArgs{
|
if err := updater.AddInputs([]psetv2.InputArgs{
|
||||||
{
|
{
|
||||||
Txid: coin.Txid,
|
Txid: utxo.Txid,
|
||||||
TxIndex: coin.Vout,
|
TxIndex: utxo.Vout,
|
||||||
},
|
},
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
assetID, err := elementsutil.AssetHashToBytes(coin.Asset)
|
assetID, err := elementsutil.AssetHashToBytes(utxo.Asset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
value, err := elementsutil.ValueToBytes(coin.Amount)
|
value, err := elementsutil.ValueToBytes(utxo.Amount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1053,28 +961,32 @@ func addInputs(
|
|||||||
Nonce: []byte{0x00},
|
Nonce: []byte{0x00},
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := updater.AddInWitnessUtxo(len(updater.Pset.Inputs)-1, &witnessUtxo); err != nil {
|
if err := updater.AddInWitnessUtxo(
|
||||||
|
len(updater.Pset.Inputs)-1, &witnessUtxo,
|
||||||
|
); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(delayedSelected) > 0 {
|
if len(delayedUtxos) > 0 {
|
||||||
userPubKey, err := getWalletPublicKey()
|
userPubkey, err := getWalletPublicKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
aspPublicKey, err := getServiceProviderPublicKey()
|
aspPubkey, err := getAspPublicKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
exitDelay, err := getExitDelay()
|
unilateralExitDelay, err := getUnilateralExitDelay()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
vtxoTapKey, leafProof, err := computeVtxoTaprootScript(userPubKey, aspPublicKey, uint(exitDelay))
|
vtxoTapKey, leafProof, err := computeVtxoTaprootScript(
|
||||||
|
userPubkey, aspPubkey, uint(unilateralExitDelay),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1094,25 +1006,25 @@ func addInputs(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, coin := range delayedSelected {
|
for _, utxo := range delayedUtxos {
|
||||||
if err := addVtxoInput(
|
if err := addVtxoInput(
|
||||||
updater,
|
updater,
|
||||||
psetv2.InputArgs{
|
psetv2.InputArgs{
|
||||||
Txid: coin.Txid,
|
Txid: utxo.Txid,
|
||||||
TxIndex: coin.Vout,
|
TxIndex: utxo.Vout,
|
||||||
},
|
},
|
||||||
uint(exitDelay),
|
uint(unilateralExitDelay),
|
||||||
leafProof,
|
leafProof,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
assetID, err := elementsutil.AssetHashToBytes(coin.Asset)
|
assetID, err := elementsutil.AssetHashToBytes(utxo.Asset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
value, err := elementsutil.ValueToBytes(coin.Amount)
|
value, err := elementsutil.ValueToBytes(utxo.Amount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1124,7 +1036,9 @@ func addInputs(
|
|||||||
Nonce: []byte{0x00},
|
Nonce: []byte{0x00},
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := updater.AddInWitnessUtxo(len(updater.Pset.Inputs)-1, &witnessUtxo); err != nil {
|
if err := updater.AddInWitnessUtxo(
|
||||||
|
len(updater.Pset.Inputs)-1, &witnessUtxo,
|
||||||
|
); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,13 +10,13 @@ import (
|
|||||||
"golang.org/x/crypto/scrypt"
|
"golang.org/x/crypto/scrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Cypher struct{}
|
type cypher struct{}
|
||||||
|
|
||||||
func NewAES128Cypher() *Cypher {
|
func newAES128Cypher() *cypher {
|
||||||
return &Cypher{}
|
return &cypher{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cypher) Encrypt(privateKey, password []byte) ([]byte, error) {
|
func (c *cypher) encrypt(privateKey, password []byte) ([]byte, error) {
|
||||||
// Due to https://github.com/golang/go/issues/7168.
|
// Due to https://github.com/golang/go/issues/7168.
|
||||||
// This call makes sure that memory is freed in case the GC doesn't do that
|
// This call makes sure that memory is freed in case the GC doesn't do that
|
||||||
// right after the encryption/decryption.
|
// right after the encryption/decryption.
|
||||||
@@ -53,7 +53,7 @@ func (c *Cypher) Encrypt(privateKey, password []byte) ([]byte, error) {
|
|||||||
return ciphertext, nil
|
return ciphertext, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cypher) Decrypt(encrypted, password []byte) ([]byte, error) {
|
func (c *cypher) decrypt(encrypted, password []byte) ([]byte, error) {
|
||||||
defer debug.FreeOSMemory()
|
defer debug.FreeOSMemory()
|
||||||
|
|
||||||
if len(encrypted) == 0 {
|
if len(encrypted) == 0 {
|
||||||
|
|||||||
@@ -19,6 +19,6 @@ func dumpAction(ctx *cli.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return printJSON(map[string]interface{}{
|
return printJSON(map[string]interface{}{
|
||||||
"privateKey": hex.EncodeToString(privateKey.Serialize()),
|
"private_key": hex.EncodeToString(privateKey.Serialize()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,37 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/vulpemventures/go-elements/psetv2"
|
||||||
"github.com/vulpemventures/go-elements/transaction"
|
"github.com/vulpemventures/go-elements/transaction"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type utxo struct {
|
||||||
|
Txid string `json:"txid"`
|
||||||
|
Vout uint32 `json:"vout"`
|
||||||
|
Amount uint64 `json:"value"`
|
||||||
|
Asset string `json:"asset"`
|
||||||
|
Status struct {
|
||||||
|
Confirmed bool `json:"confirmed"`
|
||||||
|
Blocktime int64 `json:"block_time"`
|
||||||
|
} `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
type Explorer interface {
|
type Explorer interface {
|
||||||
GetTxHex(txid string) (string, error)
|
GetTxHex(txid string) (string, error)
|
||||||
Broadcast(txHex string) (string, error)
|
Broadcast(txHex string) (string, error)
|
||||||
|
GetUtxos(addr string) ([]utxo, error)
|
||||||
|
GetBalance(addr, asset string) (uint64, error)
|
||||||
|
GetRedeemedVtxosBalance(
|
||||||
|
addr string, unilateralExitDelay int64,
|
||||||
|
) (uint64, map[int64]uint64, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type explorer struct {
|
type explorer struct {
|
||||||
@@ -44,17 +64,28 @@ func (e *explorer) GetTxHex(txid string) (string, error) {
|
|||||||
return txHex, nil
|
return txHex, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *explorer) Broadcast(txHex string) (string, error) {
|
func (e *explorer) Broadcast(txStr string) (string, error) {
|
||||||
tx, err := transaction.NewTxFromHex(txHex)
|
tx, err := transaction.NewTxFromHex(txStr)
|
||||||
|
if err != nil {
|
||||||
|
pset, err := psetv2.NewPsetFromBase64(txStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extracted, err := psetv2.Extract(pset)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
txStr, _ = extracted.ToHex()
|
||||||
|
}
|
||||||
txid := tx.TxHash().String()
|
txid := tx.TxHash().String()
|
||||||
e.cache[txid] = txHex
|
e.cache[txid] = txStr
|
||||||
|
|
||||||
txid, err = broadcast(txHex)
|
txid, err = e.broadcast(txStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(strings.ToLower(err.Error()), "transaction already in block chain") {
|
if strings.Contains(
|
||||||
|
strings.ToLower(err.Error()), "transaction already in block chain",
|
||||||
|
) {
|
||||||
return txid, nil
|
return txid, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,6 +95,76 @@ func (e *explorer) Broadcast(txHex string) (string, error) {
|
|||||||
return txid, nil
|
return txid, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *explorer) GetUtxos(addr string) ([]utxo, error) {
|
||||||
|
resp, err := http.Get(fmt.Sprintf("%s/address/%s/utxo", e.baseUrl, addr))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf(string(body))
|
||||||
|
}
|
||||||
|
payload := []utxo{}
|
||||||
|
if err := json.Unmarshal(body, &payload); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return payload, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *explorer) GetBalance(addr, asset string) (uint64, error) {
|
||||||
|
payload, err := e.GetUtxos(addr)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
balance := uint64(0)
|
||||||
|
for _, p := range payload {
|
||||||
|
if p.Asset != asset {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
balance += p.Amount
|
||||||
|
}
|
||||||
|
return balance, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *explorer) GetRedeemedVtxosBalance(
|
||||||
|
addr string, unilateralExitDelay int64,
|
||||||
|
) (spendableBalance uint64, lockedBalance map[int64]uint64, err error) {
|
||||||
|
utxos, err := e.GetUtxos(addr)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lockedBalance = make(map[int64]uint64, 0)
|
||||||
|
now := time.Now()
|
||||||
|
for _, utxo := range utxos {
|
||||||
|
blocktime := now
|
||||||
|
if utxo.Status.Confirmed {
|
||||||
|
blocktime = time.Unix(utxo.Status.Blocktime, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
delay := time.Duration(unilateralExitDelay) * time.Second
|
||||||
|
availableAt := blocktime.Add(delay)
|
||||||
|
if availableAt.After(now) {
|
||||||
|
if _, ok := lockedBalance[availableAt.Unix()]; !ok {
|
||||||
|
lockedBalance[availableAt.Unix()] = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
lockedBalance[availableAt.Unix()] += utxo.Amount
|
||||||
|
} else {
|
||||||
|
spendableBalance += utxo.Amount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (e *explorer) getTxHex(txid string) (string, error) {
|
func (e *explorer) getTxHex(txid string) (string, error) {
|
||||||
resp, err := http.Get(fmt.Sprintf("%s/tx/%s/hex", e.baseUrl, txid))
|
resp, err := http.Get(fmt.Sprintf("%s/tx/%s/hex", e.baseUrl, txid))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -83,3 +184,23 @@ func (e *explorer) getTxHex(txid string) (string, error) {
|
|||||||
e.cache[txid] = hex
|
e.cache[txid] = hex
|
||||||
return hex, nil
|
return hex, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *explorer) broadcast(txHex string) (string, error) {
|
||||||
|
body := bytes.NewBuffer([]byte(txHex))
|
||||||
|
|
||||||
|
resp, err := http.Post(fmt.Sprintf("%s/tx", e.baseUrl), "text/plain", body)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
bodyResponse, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return "", fmt.Errorf(string(bodyResponse))
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(bodyResponse), nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1"
|
arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1"
|
||||||
@@ -57,7 +59,7 @@ func initAction(ctx *cli.Context) error {
|
|||||||
return fmt.Errorf("invalid network")
|
return fmt.Errorf("invalid network")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := connectToAsp(ctx, net, url); err != nil {
|
if err := connectToAsp(ctx.Context, net, url); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return initWallet(ctx, key, password)
|
return initWallet(ctx, key, password)
|
||||||
@@ -71,24 +73,24 @@ func generateRandomPrivateKey() (*secp256k1.PrivateKey, error) {
|
|||||||
return privKey, nil
|
return privKey, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func connectToAsp(ctx *cli.Context, net, url string) error {
|
func connectToAsp(ctx context.Context, net, url string) error {
|
||||||
client, close, err := getClient(ctx, url)
|
client, close, err := getClient(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer close()
|
defer close()
|
||||||
|
|
||||||
resp, err := client.GetInfo(ctx.Context, &arkv1.GetInfoRequest{})
|
resp, err := client.GetInfo(ctx, &arkv1.GetInfoRequest{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return setState(map[string]interface{}{
|
return setState(map[string]string{
|
||||||
"ark_url": url,
|
ASP_URL: url,
|
||||||
"network": net,
|
NETWORK: net,
|
||||||
"ark_pubkey": resp.Pubkey,
|
ASP_PUBKEY: resp.Pubkey,
|
||||||
"ark_lifetime": resp.Lifetime,
|
ROUND_LIFETIME: strconv.Itoa(int(resp.GetRoundLifetime())),
|
||||||
"exit_delay": resp.ExitDelay,
|
UNILATERAL_EXIT_DELAY: strconv.Itoa(int(resp.GetUnilateralExitDelay())),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,17 +111,20 @@ func initWallet(ctx *cli.Context, key, password string) error {
|
|||||||
privateKey = secp256k1.PrivKeyFromBytes(privKeyBytes)
|
privateKey = secp256k1.PrivKeyFromBytes(privKeyBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
encryptedPrivateKey, err := NewAES128Cypher().Encrypt(privateKey.Serialize(), []byte(password))
|
cypher := newAES128Cypher()
|
||||||
|
buf := privateKey.Serialize()
|
||||||
|
encryptedPrivateKey, err := cypher.encrypt(buf, []byte(password))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
passwordHash := hashPassword([]byte(password))
|
passwordHash := hashPassword([]byte(password))
|
||||||
|
|
||||||
state := map[string]interface{}{
|
pubkey := privateKey.PubKey().SerializeCompressed()
|
||||||
"encrypted_private_key": hex.EncodeToString(encryptedPrivateKey),
|
state := map[string]string{
|
||||||
"password_hash": hex.EncodeToString(passwordHash),
|
ENCRYPTED_PRVKEY: hex.EncodeToString(encryptedPrivateKey),
|
||||||
"public_key": hex.EncodeToString(privateKey.PubKey().SerializeCompressed()),
|
PASSWORD_HASH: hex.EncodeToString(passwordHash),
|
||||||
|
PUBKEY: hex.EncodeToString(pubkey),
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := setState(state); err != nil {
|
if err := setState(state); err != nil {
|
||||||
|
|||||||
@@ -17,6 +17,15 @@ const (
|
|||||||
DATADIR_ENVVAR = "ARK_WALLET_DATADIR"
|
DATADIR_ENVVAR = "ARK_WALLET_DATADIR"
|
||||||
STATE_FILE = "state.json"
|
STATE_FILE = "state.json"
|
||||||
defaultNetwork = "testnet"
|
defaultNetwork = "testnet"
|
||||||
|
|
||||||
|
ASP_URL = "asp_url"
|
||||||
|
ASP_PUBKEY = "asp_public_key"
|
||||||
|
ROUND_LIFETIME = "round_lifetime"
|
||||||
|
UNILATERAL_EXIT_DELAY = "unilateral_exit_delay "
|
||||||
|
ENCRYPTED_PRVKEY = "encrypted_private_key "
|
||||||
|
PASSWORD_HASH = "password_hash "
|
||||||
|
PUBKEY = "public_key "
|
||||||
|
NETWORK = "network "
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -29,14 +38,15 @@ var (
|
|||||||
network.Testnet.Name: "https://blockstream.info/liquidtestnet/api",
|
network.Testnet.Name: "https://blockstream.info/liquidtestnet/api",
|
||||||
}
|
}
|
||||||
|
|
||||||
initialState = map[string]interface{}{
|
initialState = map[string]string{
|
||||||
"ark_url": "",
|
ASP_URL: "",
|
||||||
"ark_pubkey": "",
|
ASP_PUBKEY: "",
|
||||||
"ark_lifetime": 0,
|
ROUND_LIFETIME: "",
|
||||||
"encrypted_private_key": "",
|
UNILATERAL_EXIT_DELAY: "",
|
||||||
"password_hash": "",
|
ENCRYPTED_PRVKEY: "",
|
||||||
"public_key": "",
|
PASSWORD_HASH: "",
|
||||||
"network": defaultNetwork,
|
PUBKEY: "",
|
||||||
|
NETWORK: defaultNetwork,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -110,7 +120,7 @@ func cleanAndExpandPath(path string) string {
|
|||||||
return filepath.Clean(os.ExpandEnv(path))
|
return filepath.Clean(os.ExpandEnv(path))
|
||||||
}
|
}
|
||||||
|
|
||||||
func getState() (map[string]interface{}, error) {
|
func getState() (map[string]string, error) {
|
||||||
file, err := os.ReadFile(statePath)
|
file, err := os.ReadFile(statePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !os.IsNotExist(err) {
|
if !os.IsNotExist(err) {
|
||||||
@@ -122,7 +132,7 @@ func getState() (map[string]interface{}, error) {
|
|||||||
return initialState, nil
|
return initialState, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
data := map[string]interface{}{}
|
data := map[string]string{}
|
||||||
if err := json.Unmarshal(file, &data); err != nil {
|
if err := json.Unmarshal(file, &data); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -138,7 +148,7 @@ func setInitialState() error {
|
|||||||
return os.WriteFile(statePath, jsonString, 0755)
|
return os.WriteFile(statePath, jsonString, 0755)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setState(data map[string]interface{}) error {
|
func setState(data map[string]string) error {
|
||||||
currentData, err := getState()
|
currentData, err := getState()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -158,8 +168,8 @@ func setState(data map[string]interface{}) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func merge(maps ...map[string]interface{}) map[string]interface{} {
|
func merge(maps ...map[string]string) map[string]string {
|
||||||
merge := make(map[string]interface{}, 0)
|
merge := make(map[string]string, 0)
|
||||||
for _, m := range maps {
|
for _, m := range maps {
|
||||||
for k, v := range m {
|
for k, v := range m {
|
||||||
merge[k] = v
|
merge[k] = v
|
||||||
|
|||||||
@@ -40,17 +40,17 @@ func onboardAction(ctx *cli.Context) error {
|
|||||||
|
|
||||||
_, net := getNetwork()
|
_, net := getNetwork()
|
||||||
|
|
||||||
aspPubkey, err := getServiceProviderPublicKey()
|
aspPubkey, err := getAspPublicKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
lifetime, err := getLifetime()
|
roundLifetime, err := getRoundLifetime()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
exitDelay, err := getExitDelay()
|
unilateralExitDelay, err := getUnilateralExitDelay()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -66,7 +66,8 @@ func onboardAction(ctx *cli.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
treeFactoryFn, sharedOutputScript, sharedOutputAmount, err := tree.CraftCongestionTree(
|
treeFactoryFn, sharedOutputScript, sharedOutputAmount, err := tree.CraftCongestionTree(
|
||||||
net.AssetID, aspPubkey, []tree.Receiver{congestionTreeLeaf}, minRelayFee, lifetime, exitDelay,
|
net.AssetID, aspPubkey, []tree.Receiver{congestionTreeLeaf},
|
||||||
|
minRelayFee, roundLifetime, unilateralExitDelay,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -92,13 +93,14 @@ func onboardAction(ctx *cli.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
txid, err := broadcastPset(pset)
|
explorer := NewExplorer()
|
||||||
|
txid, err := explorer.Broadcast(pset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("onboard txid:", txid)
|
fmt.Println("onboard_txid:", txid)
|
||||||
fmt.Println("waiting for confirmation... (this may take a while, do not cancel the process)")
|
fmt.Println("waiting for confirmation... (this may take up to a minute, do not cancel the process)")
|
||||||
|
|
||||||
// wait for the transaction to be confirmed
|
// wait for the transaction to be confirmed
|
||||||
if err := waitForTxConfirmation(ctx, txid); err != nil {
|
if err := waitForTxConfirmation(ctx, txid); err != nil {
|
||||||
@@ -116,11 +118,11 @@ func onboardAction(ctx *cli.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
client, close, err := getClientFromState(ctx)
|
client, cancel, err := getClientFromState()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer close()
|
defer cancel()
|
||||||
|
|
||||||
_, err = client.Onboard(ctx.Context, &arkv1.OnboardRequest{
|
_, err = client.Onboard(ctx.Context, &arkv1.OnboardRequest{
|
||||||
BoardingTx: pset,
|
BoardingTx: pset,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ var receiveCommand = cli.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func receiveAction(ctx *cli.Context) error {
|
func receiveAction(ctx *cli.Context) error {
|
||||||
offchainAddr, onchainAddr, err := getAddress()
|
offchainAddr, onchainAddr, _, err := getAddress()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
@@ -56,18 +57,26 @@ func redeemAction(ctx *cli.Context) error {
|
|||||||
return fmt.Errorf("missing amount flag (--amount)")
|
return fmt.Errorf("missing amount flag (--amount)")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
client, clean, err := getClientFromState()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer clean()
|
||||||
|
|
||||||
if force {
|
if force {
|
||||||
if amount > 0 {
|
if amount > 0 {
|
||||||
fmt.Printf("WARNING: unilateral exit (--force) ignores --amount flag, it will redeem all your VTXOs\n")
|
fmt.Printf("WARNING: unilateral exit (--force) ignores --amount flag, it will redeem all your VTXOs\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
return unilateralRedeem(ctx)
|
return unilateralRedeem(client, ctx.Context)
|
||||||
}
|
}
|
||||||
|
|
||||||
return collaborativeRedeem(ctx, addr, amount)
|
return collaborativeRedeem(client, ctx.Context, addr, amount)
|
||||||
}
|
}
|
||||||
|
|
||||||
func collaborativeRedeem(ctx *cli.Context, addr string, amount uint64) error {
|
func collaborativeRedeem(
|
||||||
|
client arkv1.ArkServiceClient, ctx context.Context, addr string, amount uint64,
|
||||||
|
) error {
|
||||||
if _, err := address.ToOutputScript(addr); err != nil {
|
if _, err := address.ToOutputScript(addr); err != nil {
|
||||||
return fmt.Errorf("invalid onchain address")
|
return fmt.Errorf("invalid onchain address")
|
||||||
}
|
}
|
||||||
@@ -86,7 +95,7 @@ func collaborativeRedeem(ctx *cli.Context, addr string, amount uint64) error {
|
|||||||
addr = info.Address
|
addr = info.Address
|
||||||
}
|
}
|
||||||
|
|
||||||
offchainAddr, _, err := getAddress()
|
offchainAddr, _, _, err := getAddress()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -98,12 +107,6 @@ func collaborativeRedeem(ctx *cli.Context, addr string, amount uint64) error {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
client, close, err := getClientFromState(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer close()
|
|
||||||
|
|
||||||
explorer := NewExplorer()
|
explorer := NewExplorer()
|
||||||
|
|
||||||
vtxos, err := getVtxos(ctx, explorer, client, offchainAddr, true)
|
vtxos, err := getVtxos(ctx, explorer, client, offchainAddr, true)
|
||||||
@@ -137,14 +140,14 @@ func collaborativeRedeem(ctx *cli.Context, addr string, amount uint64) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
registerResponse, err := client.RegisterPayment(ctx.Context, &arkv1.RegisterPaymentRequest{
|
registerResponse, err := client.RegisterPayment(ctx, &arkv1.RegisterPaymentRequest{
|
||||||
Inputs: inputs,
|
Inputs: inputs,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = client.ClaimPayment(ctx.Context, &arkv1.ClaimPaymentRequest{
|
_, err = client.ClaimPayment(ctx, &arkv1.ClaimPaymentRequest{
|
||||||
Id: registerResponse.GetId(),
|
Id: registerResponse.GetId(),
|
||||||
Outputs: receivers,
|
Outputs: receivers,
|
||||||
})
|
})
|
||||||
@@ -173,14 +176,8 @@ func collaborativeRedeem(ctx *cli.Context, addr string, amount uint64) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func unilateralRedeem(ctx *cli.Context) error {
|
func unilateralRedeem(client arkv1.ArkServiceClient, ctx context.Context) error {
|
||||||
client, close, err := getClientFromState(ctx)
|
offchainAddr, _, _, err := getAddress()
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer close()
|
|
||||||
|
|
||||||
offchainAddr, _, err := getAddress()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -212,7 +209,7 @@ func unilateralRedeem(ctx *cli.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, branch := range redeemBranches {
|
for _, branch := range redeemBranches {
|
||||||
branchTxs, err := branch.RedeemPath()
|
branchTxs, err := branch.redeemPath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ type receiver struct {
|
|||||||
Amount uint64 `json:"amount"`
|
Amount uint64 `json:"amount"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *receiver) IsOnchain() bool {
|
func (r *receiver) isOnchain() bool {
|
||||||
_, err := address.ToOutputScript(r.To)
|
_, err := address.ToOutputScript(r.To)
|
||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
@@ -75,20 +75,22 @@ func sendAction(ctx *cli.Context) error {
|
|||||||
offchainReceivers := make([]receiver, 0)
|
offchainReceivers := make([]receiver, 0)
|
||||||
|
|
||||||
for _, receiver := range receiversJSON {
|
for _, receiver := range receiversJSON {
|
||||||
if receiver.IsOnchain() {
|
if receiver.isOnchain() {
|
||||||
onchainReceivers = append(onchainReceivers, receiver)
|
onchainReceivers = append(onchainReceivers, receiver)
|
||||||
} else {
|
} else {
|
||||||
offchainReceivers = append(offchainReceivers, receiver)
|
offchainReceivers = append(offchainReceivers, receiver)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
explorer := NewExplorer()
|
||||||
|
|
||||||
if len(onchainReceivers) > 0 {
|
if len(onchainReceivers) > 0 {
|
||||||
pset, err := sendOnchain(ctx, onchainReceivers)
|
pset, err := sendOnchain(ctx, onchainReceivers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
txid, err := broadcastPset(pset)
|
txid, err := explorer.Broadcast(pset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -108,7 +110,7 @@ func sendAction(ctx *cli.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func sendOffchain(ctx *cli.Context, receivers []receiver) error {
|
func sendOffchain(ctx *cli.Context, receivers []receiver) error {
|
||||||
offchainAddr, _, err := getAddress()
|
offchainAddr, _, _, err := getAddress()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -127,7 +129,9 @@ func sendOffchain(ctx *cli.Context, receivers []receiver) error {
|
|||||||
return fmt.Errorf("invalid receiver address: %s", err)
|
return fmt.Errorf("invalid receiver address: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !bytes.Equal(aspPubKey.SerializeCompressed(), aspKey.SerializeCompressed()) {
|
if !bytes.Equal(
|
||||||
|
aspPubKey.SerializeCompressed(), aspKey.SerializeCompressed(),
|
||||||
|
) {
|
||||||
return fmt.Errorf("invalid receiver address '%s': must be associated with the connected service provider", receiver.To)
|
return fmt.Errorf("invalid receiver address '%s': must be associated with the connected service provider", receiver.To)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,7 +145,7 @@ func sendOffchain(ctx *cli.Context, receivers []receiver) error {
|
|||||||
})
|
})
|
||||||
sumOfReceivers += receiver.Amount
|
sumOfReceivers += receiver.Amount
|
||||||
}
|
}
|
||||||
client, close, err := getClientFromState(ctx)
|
client, close, err := getClientFromState()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -149,7 +153,7 @@ func sendOffchain(ctx *cli.Context, receivers []receiver) error {
|
|||||||
|
|
||||||
explorer := NewExplorer()
|
explorer := NewExplorer()
|
||||||
|
|
||||||
vtxos, err := getVtxos(ctx, explorer, client, offchainAddr, true)
|
vtxos, err := getVtxos(ctx.Context, explorer, client, offchainAddr, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -181,9 +185,9 @@ func sendOffchain(ctx *cli.Context, receivers []receiver) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
registerResponse, err := client.RegisterPayment(ctx.Context, &arkv1.RegisterPaymentRequest{
|
registerResponse, err := client.RegisterPayment(
|
||||||
Inputs: inputs,
|
ctx.Context, &arkv1.RegisterPaymentRequest{Inputs: inputs},
|
||||||
})
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -197,12 +201,8 @@ func sendOffchain(ctx *cli.Context, receivers []receiver) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
poolTxID, err := handleRoundStream(
|
poolTxID, err := handleRoundStream(
|
||||||
ctx,
|
ctx.Context, client, registerResponse.GetId(),
|
||||||
client,
|
selectedCoins, secKey, receiversOutput,
|
||||||
registerResponse.GetId(),
|
|
||||||
selectedCoins,
|
|
||||||
secKey,
|
|
||||||
receiversOutput,
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -248,17 +248,21 @@ func sendOnchain(ctx *cli.Context, receivers []receiver) (string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
selected, delayedSelected, change, err := coinSelectOnchain(targetAmount, nil)
|
explorer := NewExplorer()
|
||||||
|
|
||||||
|
utxos, delayedUtxos, change, err := coinSelectOnchain(
|
||||||
|
explorer, targetAmount, nil,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := addInputs(updater, selected, delayedSelected, net); err != nil {
|
if err := addInputs(updater, utxos, delayedUtxos, net); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if change > 0 {
|
if change > 0 {
|
||||||
_, changeAddr, err := getAddress()
|
_, changeAddr, _, err := getAddress()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -297,8 +301,7 @@ func sendOnchain(ctx *cli.Context, receivers []receiver) (string, error) {
|
|||||||
}
|
}
|
||||||
// reselect the difference
|
// reselect the difference
|
||||||
selected, delayedSelected, newChange, err := coinSelectOnchain(
|
selected, delayedSelected, newChange, err := coinSelectOnchain(
|
||||||
feeAmount-change,
|
explorer, feeAmount-change, append(utxos, delayedUtxos...),
|
||||||
append(selected, delayedSelected...),
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -309,7 +312,7 @@ func sendOnchain(ctx *cli.Context, receivers []receiver) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if newChange > 0 {
|
if newChange > 0 {
|
||||||
_, changeAddr, err := getAddress()
|
_, changeAddr, _, err := getAddress()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -345,8 +348,6 @@ func sendOnchain(ctx *cli.Context, receivers []receiver) (string, error) {
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
explorer := NewExplorer()
|
|
||||||
|
|
||||||
if err := signPset(updater.Pset, explorer, prvKey); err != nil {
|
if err := signPset(updater.Pset, explorer, prvKey); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -357,22 +358,3 @@ func sendOnchain(ctx *cli.Context, receivers []receiver) (string, error) {
|
|||||||
|
|
||||||
return updater.Pset.ToBase64()
|
return updater.Pset.ToBase64()
|
||||||
}
|
}
|
||||||
|
|
||||||
func broadcastPset(psetB64 string) (string, error) {
|
|
||||||
pset, err := psetv2.NewPsetFromBase64(psetB64)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
extracted, err := psetv2.Extract(pset)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
hex, err := extracted.ToHex()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return NewExplorer().Broadcast(hex)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -17,9 +17,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func signPset(
|
func signPset(
|
||||||
pset *psetv2.Pset,
|
pset *psetv2.Pset, explorer Explorer, prvKey *secp256k1.PrivateKey,
|
||||||
explorer Explorer,
|
|
||||||
prvKey *secp256k1.PrivateKey,
|
|
||||||
) error {
|
) error {
|
||||||
updater, err := psetv2.NewUpdater(pset)
|
updater, err := psetv2.NewUpdater(pset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -66,7 +64,7 @@ func signPset(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, onchainAddr, err := getAddress()
|
_, onchainAddr, _, err := getAddress()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,17 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ark-network/ark/common/tree"
|
"github.com/ark-network/ark/common/tree"
|
||||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
"github.com/vulpemventures/go-elements/psetv2"
|
"github.com/vulpemventures/go-elements/psetv2"
|
||||||
"github.com/vulpemventures/go-elements/taproot"
|
"github.com/vulpemventures/go-elements/taproot"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RedeemBranch interface {
|
|
||||||
// RedeemPath returns the list of transactions to broadcast in order to access the vtxo output
|
|
||||||
RedeemPath() ([]string, error)
|
|
||||||
// ExpireAt returns the expiration time of the branch
|
|
||||||
ExpireAt() (*time.Time, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type redeemBranch struct {
|
type redeemBranch struct {
|
||||||
vtxo *vtxo
|
vtxo *vtxo
|
||||||
branch []*psetv2.Pset
|
branch []*psetv2.Pset
|
||||||
@@ -29,11 +22,9 @@ type redeemBranch struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func newRedeemBranch(
|
func newRedeemBranch(
|
||||||
ctx *cli.Context,
|
ctx context.Context, explorer Explorer,
|
||||||
explorer Explorer,
|
congestionTree tree.CongestionTree, vtxo vtxo,
|
||||||
congestionTree tree.CongestionTree,
|
) (*redeemBranch, error) {
|
||||||
vtxo vtxo,
|
|
||||||
) (RedeemBranch, error) {
|
|
||||||
sweepClosure, seconds, err := findSweepClosure(congestionTree)
|
sweepClosure, seconds, err := findSweepClosure(congestionTree)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -75,7 +66,7 @@ func newRedeemBranch(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RedeemPath returns the list of transactions to broadcast in order to access the vtxo output
|
// RedeemPath returns the list of transactions to broadcast in order to access the vtxo output
|
||||||
func (r *redeemBranch) RedeemPath() ([]string, error) {
|
func (r *redeemBranch) redeemPath() ([]string, error) {
|
||||||
transactions := make([]string, 0, len(r.branch))
|
transactions := make([]string, 0, len(r.branch))
|
||||||
|
|
||||||
offchainPath, err := r.offchainPath()
|
offchainPath, err := r.offchainPath()
|
||||||
@@ -125,7 +116,7 @@ func (r *redeemBranch) RedeemPath() ([]string, error) {
|
|||||||
return transactions, nil
|
return transactions, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *redeemBranch) ExpireAt() (*time.Time, error) {
|
func (r *redeemBranch) expireAt() (*time.Time, error) {
|
||||||
lastKnownBlocktime := int64(0)
|
lastKnownBlocktime := int64(0)
|
||||||
|
|
||||||
confirmed, blocktime, _ := getTxBlocktime(r.vtxo.poolTxid)
|
confirmed, blocktime, _ := getTxBlocktime(r.vtxo.poolTxid)
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import (
|
|||||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
func EncodeAddress(hrp string, userKey, aspKey *secp256k1.PublicKey) (addr string, err error) {
|
func EncodeAddress(
|
||||||
|
hrp string, userKey, aspKey *secp256k1.PublicKey,
|
||||||
|
) (addr string, err error) {
|
||||||
if userKey == nil {
|
if userKey == nil {
|
||||||
err = fmt.Errorf("missing public key")
|
err = fmt.Errorf("missing public key")
|
||||||
return
|
return
|
||||||
@@ -20,7 +22,9 @@ func EncodeAddress(hrp string, userKey, aspKey *secp256k1.PublicKey) (addr strin
|
|||||||
err = fmt.Errorf("invalid prefix")
|
err = fmt.Errorf("invalid prefix")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
combinedKey := append(aspKey.SerializeCompressed(), userKey.SerializeCompressed()...)
|
combinedKey := append(
|
||||||
|
aspKey.SerializeCompressed(), userKey.SerializeCompressed()...,
|
||||||
|
)
|
||||||
grp, err := bech32.ConvertBits(combinedKey, 8, 5, true)
|
grp, err := bech32.ConvertBits(combinedKey, 8, 5, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -29,7 +33,9 @@ func EncodeAddress(hrp string, userKey, aspKey *secp256k1.PublicKey) (addr strin
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func DecodeAddress(addr string) (hrp string, userKey *secp256k1.PublicKey, aspKey *secp256k1.PublicKey, err error) {
|
func DecodeAddress(
|
||||||
|
addr string,
|
||||||
|
) (hrp string, userKey *secp256k1.PublicKey, aspKey *secp256k1.PublicKey, err error) {
|
||||||
prefix, buf, err := bech32.DecodeNoLimit(addr)
|
prefix, buf, err := bech32.DecodeNoLimit(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -10,9 +10,7 @@ import (
|
|||||||
// TaprootPreimage computes the hash for witness v1 input of a pset
|
// TaprootPreimage computes the hash for witness v1 input of a pset
|
||||||
// it implicitly assumes that the pset has witnessUtxo fields populated
|
// it implicitly assumes that the pset has witnessUtxo fields populated
|
||||||
func TaprootPreimage(
|
func TaprootPreimage(
|
||||||
genesisBlockHash *chainhash.Hash,
|
genesisBlockHash *chainhash.Hash, pset *psetv2.Pset, inputIndex int,
|
||||||
pset *psetv2.Pset,
|
|
||||||
inputIndex int,
|
|
||||||
leafHash *chainhash.Hash,
|
leafHash *chainhash.Hash,
|
||||||
) ([]byte, error) {
|
) ([]byte, error) {
|
||||||
prevoutScripts := make([][]byte, 0)
|
prevoutScripts := make([][]byte, 0)
|
||||||
@@ -35,14 +33,8 @@ func TaprootPreimage(
|
|||||||
}
|
}
|
||||||
|
|
||||||
preimage := utx.HashForWitnessV1(
|
preimage := utx.HashForWitnessV1(
|
||||||
inputIndex,
|
inputIndex, prevoutScripts, prevoutAssets, prevoutValues,
|
||||||
prevoutScripts,
|
pset.Inputs[inputIndex].SigHashType, genesisBlockHash, leafHash, nil,
|
||||||
prevoutAssets,
|
|
||||||
prevoutValues,
|
|
||||||
pset.Inputs[inputIndex].SigHashType,
|
|
||||||
genesisBlockHash,
|
|
||||||
leafHash,
|
|
||||||
nil,
|
|
||||||
)
|
)
|
||||||
return preimage[:], nil
|
return preimage[:], nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,14 +13,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func CraftCongestionTree(
|
func CraftCongestionTree(
|
||||||
asset string, aspPublicKey *secp256k1.PublicKey,
|
asset string, aspPubkey *secp256k1.PublicKey, receivers []Receiver,
|
||||||
receivers []Receiver, feeSatsPerNode uint64, roundLifetime int64, exitDelay int64,
|
feeSatsPerNode uint64, roundLifetime, unilateralExitDelay int64,
|
||||||
) (
|
) (
|
||||||
buildCongestionTree TreeFactory,
|
buildCongestionTree TreeFactory,
|
||||||
sharedOutputScript []byte, sharedOutputAmount uint64, err error,
|
sharedOutputScript []byte, sharedOutputAmount uint64, err error,
|
||||||
) {
|
) {
|
||||||
root, err := createPartialCongestionTree(
|
root, err := createPartialCongestionTree(
|
||||||
receivers, aspPublicKey, asset, feeSatsPerNode, roundLifetime, exitDelay,
|
asset, aspPubkey, receivers, feeSatsPerNode, roundLifetime, unilateralExitDelay,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -49,7 +49,7 @@ type node struct {
|
|||||||
asset string
|
asset string
|
||||||
feeSats uint64
|
feeSats uint64
|
||||||
roundLifetime int64
|
roundLifetime int64
|
||||||
exitDelay int64
|
unilateralExitDelay int64
|
||||||
|
|
||||||
_inputTaprootKey *secp256k1.PublicKey
|
_inputTaprootKey *secp256k1.PublicKey
|
||||||
_inputTaprootTree *taproot.IndexedElementsTapScriptTree
|
_inputTaprootTree *taproot.IndexedElementsTapScriptTree
|
||||||
@@ -260,7 +260,7 @@ func (n *node) getVtxoWitnessData() (
|
|||||||
|
|
||||||
redeemClosure := &CSVSigClosure{
|
redeemClosure := &CSVSigClosure{
|
||||||
Pubkey: pubkey,
|
Pubkey: pubkey,
|
||||||
Seconds: uint(n.exitDelay),
|
Seconds: uint(n.unilateralExitDelay),
|
||||||
}
|
}
|
||||||
|
|
||||||
redeemLeaf, err := redeemClosure.Leaf()
|
redeemLeaf, err := redeemClosure.Leaf()
|
||||||
@@ -412,12 +412,8 @@ func (n *node) createFinalCongestionTree() TreeFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func createPartialCongestionTree(
|
func createPartialCongestionTree(
|
||||||
receivers []Receiver,
|
asset string, aspPubkey *secp256k1.PublicKey, receivers []Receiver,
|
||||||
aspPublicKey *secp256k1.PublicKey,
|
feeSatsPerNode uint64, roundLifetime, unilateralExitDelay int64,
|
||||||
asset string,
|
|
||||||
feeSatsPerNode uint64,
|
|
||||||
roundLifetime int64,
|
|
||||||
exitDelay int64,
|
|
||||||
) (root *node, err error) {
|
) (root *node, err error) {
|
||||||
if len(receivers) == 0 {
|
if len(receivers) == 0 {
|
||||||
return nil, fmt.Errorf("no receivers provided")
|
return nil, fmt.Errorf("no receivers provided")
|
||||||
@@ -426,12 +422,12 @@ func createPartialCongestionTree(
|
|||||||
nodes := make([]*node, 0, len(receivers))
|
nodes := make([]*node, 0, len(receivers))
|
||||||
for _, r := range receivers {
|
for _, r := range receivers {
|
||||||
leafNode := &node{
|
leafNode := &node{
|
||||||
sweepKey: aspPublicKey,
|
sweepKey: aspPubkey,
|
||||||
receivers: []Receiver{r},
|
receivers: []Receiver{r},
|
||||||
asset: asset,
|
asset: asset,
|
||||||
feeSats: feeSatsPerNode,
|
feeSats: feeSatsPerNode,
|
||||||
roundLifetime: roundLifetime,
|
roundLifetime: roundLifetime,
|
||||||
exitDelay: exitDelay,
|
unilateralExitDelay: unilateralExitDelay,
|
||||||
}
|
}
|
||||||
nodes = append(nodes, leafNode)
|
nodes = append(nodes, leafNode)
|
||||||
}
|
}
|
||||||
@@ -476,7 +472,9 @@ func createUpperLevel(nodes []*node) ([]*node, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func taprootOutputScript(taprootKey *secp256k1.PublicKey) ([]byte, error) {
|
func taprootOutputScript(taprootKey *secp256k1.PublicKey) ([]byte, error) {
|
||||||
return txscript.NewScriptBuilder().AddOp(txscript.OP_1).AddData(schnorr.SerializePubKey(taprootKey)).Script()
|
return txscript.NewScriptBuilder().AddOp(txscript.OP_1).AddData(
|
||||||
|
schnorr.SerializePubKey(taprootKey),
|
||||||
|
).Script()
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPsetId(pset *psetv2.Pset) (string, error) {
|
func getPsetId(pset *psetv2.Pset) (string, error) {
|
||||||
@@ -488,10 +486,8 @@ func getPsetId(pset *psetv2.Pset) (string, error) {
|
|||||||
return utx.TxHash().String(), nil
|
return utx.TxHash().String(), 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(
|
func addTaprootInput(
|
||||||
updater *psetv2.Updater,
|
updater *psetv2.Updater, input psetv2.InputArgs,
|
||||||
input psetv2.InputArgs,
|
|
||||||
internalTaprootKey *secp256k1.PublicKey,
|
internalTaprootKey *secp256k1.PublicKey,
|
||||||
taprootTree *taproot.IndexedElementsTapScriptTree,
|
taprootTree *taproot.IndexedElementsTapScriptTree,
|
||||||
) error {
|
) error {
|
||||||
@@ -499,7 +495,9 @@ func addTaprootInput(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := updater.AddInTapInternalKey(0, schnorr.SerializePubKey(internalTaprootKey)); err != nil {
|
if err := updater.AddInTapInternalKey(
|
||||||
|
0, schnorr.SerializePubKey(internalTaprootKey),
|
||||||
|
); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -64,7 +64,9 @@ func (f *ForfeitClosure) Leaf() (*taproot.TapElementsLeaf, error) {
|
|||||||
aspKeyBytes := schnorr.SerializePubKey(f.AspPubkey)
|
aspKeyBytes := schnorr.SerializePubKey(f.AspPubkey)
|
||||||
userKeyBytes := schnorr.SerializePubKey(f.Pubkey)
|
userKeyBytes := schnorr.SerializePubKey(f.Pubkey)
|
||||||
|
|
||||||
script, err := txscript.NewScriptBuilder().AddData(aspKeyBytes).AddOp(txscript.OP_CHECKSIGVERIFY).AddData(userKeyBytes).AddOp(txscript.OP_CHECKSIG).Script()
|
script, err := txscript.NewScriptBuilder().AddData(aspKeyBytes).
|
||||||
|
AddOp(txscript.OP_CHECKSIGVERIFY).AddData(userKeyBytes).
|
||||||
|
AddOp(txscript.OP_CHECKSIG).Script()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -108,7 +110,7 @@ func (f *ForfeitClosure) Decode(script []byte) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *CSVSigClosure) Leaf() (*taproot.TapElementsLeaf, error) {
|
func (d *CSVSigClosure) Leaf() (*taproot.TapElementsLeaf, error) {
|
||||||
script, err := csvChecksigScript(d.Pubkey, d.Seconds)
|
script, err := encodeCsvWithChecksigScript(d.Pubkey, d.Seconds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -118,7 +120,9 @@ func (d *CSVSigClosure) Leaf() (*taproot.TapElementsLeaf, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *CSVSigClosure) Decode(script []byte) (bool, error) {
|
func (d *CSVSigClosure) Decode(script []byte) (bool, error) {
|
||||||
csvIndex := bytes.Index(script, []byte{txscript.OP_CHECKSEQUENCEVERIFY, txscript.OP_DROP})
|
csvIndex := bytes.Index(
|
||||||
|
script, []byte{txscript.OP_CHECKSEQUENCEVERIFY, txscript.OP_DROP},
|
||||||
|
)
|
||||||
if csvIndex == -1 || csvIndex == 0 {
|
if csvIndex == -1 || csvIndex == 0 {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
@@ -140,7 +144,7 @@ func (d *CSVSigClosure) Decode(script []byte) (bool, error) {
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
rebuilt, err := csvChecksigScript(pubkey, seconds)
|
rebuilt, err := encodeCsvWithChecksigScript(pubkey, seconds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@@ -160,14 +164,19 @@ func (c *UnrollClosure) Leaf() (*taproot.TapElementsLeaf, error) {
|
|||||||
return nil, fmt.Errorf("left key and amount are required")
|
return nil, fmt.Errorf("left key and amount are required")
|
||||||
}
|
}
|
||||||
|
|
||||||
nextScriptLeft := withOutput(txscript.OP_0, schnorr.SerializePubKey(c.LeftKey), c.LeftAmount, c.RightKey != nil)
|
nextScriptLeft := encodeIntrospectionScript(
|
||||||
|
txscript.OP_0,
|
||||||
|
schnorr.SerializePubKey(c.LeftKey), c.LeftAmount, c.RightKey != nil,
|
||||||
|
)
|
||||||
branchScript := append([]byte{}, nextScriptLeft...)
|
branchScript := append([]byte{}, nextScriptLeft...)
|
||||||
if c.RightKey != nil {
|
if c.RightKey != nil {
|
||||||
if c.RightAmount == 0 {
|
if c.RightAmount == 0 {
|
||||||
return nil, fmt.Errorf("right amount is required")
|
return nil, fmt.Errorf("right amount is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
nextScriptRight := withOutput(txscript.OP_1, schnorr.SerializePubKey(c.RightKey), c.RightAmount, false)
|
nextScriptRight := encodeIntrospectionScript(
|
||||||
|
txscript.OP_1, schnorr.SerializePubKey(c.RightKey), c.RightAmount, false,
|
||||||
|
)
|
||||||
branchScript = append(branchScript, nextScriptRight...)
|
branchScript = append(branchScript, nextScriptRight...)
|
||||||
}
|
}
|
||||||
leaf := taproot.NewBaseTapElementsLeaf(branchScript)
|
leaf := taproot.NewBaseTapElementsLeaf(branchScript)
|
||||||
@@ -181,7 +190,9 @@ func (c *UnrollClosure) Decode(script []byte) (valid bool, err error) {
|
|||||||
|
|
||||||
isLeftOnly := len(script) == 52
|
isLeftOnly := len(script) == 52
|
||||||
|
|
||||||
validLeft, leftKey, leftAmount, err := decodeWithOutputScript(script[:52], txscript.OP_0, !isLeftOnly)
|
validLeft, leftKey, leftAmount, err := decodeIntrospectionScript(
|
||||||
|
script[:52], txscript.OP_0, !isLeftOnly,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@@ -197,7 +208,9 @@ func (c *UnrollClosure) Decode(script []byte) (valid bool, err error) {
|
|||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
validRight, rightKey, rightAmount, err := decodeWithOutputScript(script[52:], txscript.OP_1, false)
|
validRight, rightKey, rightAmount, err := decodeIntrospectionScript(
|
||||||
|
script[52:], txscript.OP_1, false,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@@ -221,7 +234,9 @@ func (c *UnrollClosure) Decode(script []byte) (valid bool, err error) {
|
|||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeWithOutputScript(script []byte, expectedIndex byte, isVerify bool) (valid bool, pubkey *secp256k1.PublicKey, amount uint64, err error) {
|
func decodeIntrospectionScript(
|
||||||
|
script []byte, expectedIndex byte, isVerify bool,
|
||||||
|
) (bool, *secp256k1.PublicKey, uint64, error) {
|
||||||
if len(script) != 52 {
|
if len(script) != 52 {
|
||||||
return false, nil, 0, nil
|
return false, nil, 0, nil
|
||||||
}
|
}
|
||||||
@@ -231,7 +246,7 @@ func decodeWithOutputScript(script []byte, expectedIndex byte, isVerify bool) (v
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 32 bytes for the witness program
|
// 32 bytes for the witness program
|
||||||
pubkey, err = schnorr.ParsePubKey(script[5 : 5+32])
|
pubkey, err := schnorr.ParsePubKey(script[5 : 5+32])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil, 0, err
|
return false, nil, 0, err
|
||||||
}
|
}
|
||||||
@@ -243,9 +258,11 @@ func decodeWithOutputScript(script []byte, expectedIndex byte, isVerify bool) (v
|
|||||||
|
|
||||||
// 8 bytes for the amount
|
// 8 bytes for the amount
|
||||||
amountBytes := script[len(script)-9 : len(script)-1]
|
amountBytes := script[len(script)-9 : len(script)-1]
|
||||||
amount = binary.LittleEndian.Uint64(amountBytes)
|
amount := binary.LittleEndian.Uint64(amountBytes)
|
||||||
|
|
||||||
rebuilt := withOutput(expectedIndex, schnorr.SerializePubKey(pubkey), amount, isVerify)
|
rebuilt := encodeIntrospectionScript(
|
||||||
|
expectedIndex, schnorr.SerializePubKey(pubkey), amount, isVerify,
|
||||||
|
)
|
||||||
if !bytes.Equal(rebuilt, script) {
|
if !bytes.Equal(rebuilt, script) {
|
||||||
return false, nil, 0, nil
|
return false, nil, 0, nil
|
||||||
}
|
}
|
||||||
@@ -253,7 +270,7 @@ func decodeWithOutputScript(script []byte, expectedIndex byte, isVerify bool) (v
|
|||||||
return true, pubkey, amount, nil
|
return true, pubkey, amount, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeChecksigScript(script []byte) (valid bool, pubkey *secp256k1.PublicKey, err error) {
|
func decodeChecksigScript(script []byte) (bool, *secp256k1.PublicKey, error) {
|
||||||
data32Index := bytes.Index(script, []byte{txscript.OP_DATA_32})
|
data32Index := bytes.Index(script, []byte{txscript.OP_DATA_32})
|
||||||
if data32Index == -1 {
|
if data32Index == -1 {
|
||||||
return false, nil, nil
|
return false, nil, nil
|
||||||
@@ -264,7 +281,7 @@ func decodeChecksigScript(script []byte) (valid bool, pubkey *secp256k1.PublicKe
|
|||||||
return false, nil, nil
|
return false, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
pubkey, err = schnorr.ParsePubKey(key)
|
pubkey, err := schnorr.ParsePubKey(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil, err
|
return false, nil, err
|
||||||
}
|
}
|
||||||
@@ -273,7 +290,7 @@ func decodeChecksigScript(script []byte) (valid bool, pubkey *secp256k1.PublicKe
|
|||||||
}
|
}
|
||||||
|
|
||||||
// checkSequenceVerifyScript without checksig
|
// checkSequenceVerifyScript without checksig
|
||||||
func checkSequenceVerifyScript(seconds uint) ([]byte, error) {
|
func encodeCsvScript(seconds uint) ([]byte, error) {
|
||||||
sequence, err := common.BIP68Encode(seconds)
|
sequence, err := common.BIP68Encode(seconds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -286,13 +303,15 @@ func checkSequenceVerifyScript(seconds uint) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// checkSequenceVerifyScript + checksig
|
// checkSequenceVerifyScript + checksig
|
||||||
func csvChecksigScript(pubkey *secp256k1.PublicKey, seconds uint) ([]byte, error) {
|
func encodeCsvWithChecksigScript(
|
||||||
script, err := checksigScript(pubkey)
|
pubkey *secp256k1.PublicKey, seconds uint,
|
||||||
|
) ([]byte, error) {
|
||||||
|
script, err := encodeChecksigScript(pubkey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
csvScript, err := checkSequenceVerifyScript(seconds)
|
csvScript, err := encodeCsvScript(seconds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -300,15 +319,19 @@ func csvChecksigScript(pubkey *secp256k1.PublicKey, seconds uint) ([]byte, error
|
|||||||
return append(csvScript, script...), nil
|
return append(csvScript, script...), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func checksigScript(pubkey *secp256k1.PublicKey) ([]byte, error) {
|
func encodeChecksigScript(pubkey *secp256k1.PublicKey) ([]byte, error) {
|
||||||
key := schnorr.SerializePubKey(pubkey)
|
key := schnorr.SerializePubKey(pubkey)
|
||||||
return txscript.NewScriptBuilder().AddData(key).AddOp(txscript.OP_CHECKSIG).Script()
|
return txscript.NewScriptBuilder().AddData(key).
|
||||||
|
AddOp(txscript.OP_CHECKSIG).Script()
|
||||||
}
|
}
|
||||||
|
|
||||||
// withOutput returns an introspection script that checks the script and the amount of the output at the given index
|
// getIntrospectionScript returns an introspection script that checks the
|
||||||
// verify will add an OP_EQUALVERIFY at the end of the script, otherwise it will add an OP_EQUAL
|
// script and the amount of the output at the given index verify will add an
|
||||||
|
// OP_EQUALVERIFY at the end of the script, otherwise it will add an OP_EQUAL
|
||||||
// length = 52 bytes
|
// length = 52 bytes
|
||||||
func withOutput(index byte, taprootWitnessProgram []byte, amount uint64, verify bool) []byte {
|
func encodeIntrospectionScript(
|
||||||
|
index byte, taprootWitnessProgram []byte, amount uint64, verify bool,
|
||||||
|
) []byte {
|
||||||
amountBuffer := make([]byte, 8)
|
amountBuffer := make([]byte, 8)
|
||||||
binary.LittleEndian.PutUint64(amountBuffer, amount)
|
binary.LittleEndian.PutUint64(amountBuffer, amount)
|
||||||
|
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ func UnspendableKey() *secp256k1.PublicKey {
|
|||||||
|
|
||||||
// ValidateCongestionTree checks if the given congestion tree is valid
|
// ValidateCongestionTree checks if the given congestion tree is valid
|
||||||
// poolTxID & poolTxIndex & poolTxAmount are used to validate the root input outpoint
|
// poolTxID & poolTxIndex & poolTxAmount are used to validate the root input outpoint
|
||||||
// aspPublicKey & roundLifetimeSeconds are used to validate the sweep tapscript leaves
|
// aspPublicKey & roundLifetime are used to validate the sweep tapscript leaves
|
||||||
// besides that, the function validates:
|
// besides that, the function validates:
|
||||||
// - the number of nodes
|
// - the number of nodes
|
||||||
// - the number of leaves
|
// - the number of leaves
|
||||||
@@ -71,10 +71,8 @@ func UnspendableKey() *secp256k1.PublicKey {
|
|||||||
// - every control block and taproot output scripts
|
// - every control block and taproot output scripts
|
||||||
// - input and output amounts
|
// - input and output amounts
|
||||||
func ValidateCongestionTree(
|
func ValidateCongestionTree(
|
||||||
tree CongestionTree,
|
tree CongestionTree, poolTx string, aspPublicKey *secp256k1.PublicKey,
|
||||||
poolTx string,
|
roundLifetime int64,
|
||||||
aspPublicKey *secp256k1.PublicKey,
|
|
||||||
roundLifetimeSeconds int64,
|
|
||||||
) error {
|
) error {
|
||||||
poolTransaction, err := psetv2.NewPsetFromBase64(poolTx)
|
poolTransaction, err := psetv2.NewPsetFromBase64(poolTx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -115,7 +113,8 @@ func ValidateCongestionTree(
|
|||||||
}
|
}
|
||||||
|
|
||||||
rootInput := rootPset.Inputs[0]
|
rootInput := rootPset.Inputs[0]
|
||||||
if chainhash.Hash(rootInput.PreviousTxid).String() != poolTxID || rootInput.PreviousTxIndex != sharedOutputIndex {
|
if chainhash.Hash(rootInput.PreviousTxid).String() != poolTxID ||
|
||||||
|
rootInput.PreviousTxIndex != sharedOutputIndex {
|
||||||
return ErrWrongPoolTxID
|
return ErrWrongPoolTxID
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,7 +134,9 @@ func ValidateCongestionTree(
|
|||||||
// iterates over all the nodes of the tree
|
// iterates over all the nodes of the tree
|
||||||
for _, level := range tree {
|
for _, level := range tree {
|
||||||
for _, node := range level {
|
for _, node := range level {
|
||||||
if err := validateNodeTransaction(node, tree, UnspendableKey(), aspPublicKey, roundLifetimeSeconds); err != nil {
|
if err := validateNodeTransaction(
|
||||||
|
node, tree, UnspendableKey(), aspPublicKey, roundLifetime,
|
||||||
|
); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -145,11 +146,9 @@ func ValidateCongestionTree(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func validateNodeTransaction(
|
func validateNodeTransaction(
|
||||||
node Node,
|
node Node, tree CongestionTree,
|
||||||
tree CongestionTree,
|
expectedInternalKey, expectedPublicKeyASP *secp256k1.PublicKey,
|
||||||
expectedInternalKey,
|
expectedSequence int64,
|
||||||
expectedPublicKeyASP *secp256k1.PublicKey,
|
|
||||||
expectedSequenceSeconds int64,
|
|
||||||
) error {
|
) error {
|
||||||
if node.Tx == "" {
|
if node.Tx == "" {
|
||||||
return ErrNodeTransactionEmpty
|
return ErrNodeTransactionEmpty
|
||||||
@@ -186,7 +185,8 @@ func validateNodeTransaction(
|
|||||||
return ErrNumberOfTapscripts
|
return ErrNumberOfTapscripts
|
||||||
}
|
}
|
||||||
|
|
||||||
if chainhash.Hash(decodedPset.Inputs[0].PreviousTxid).String() != node.ParentTxid {
|
prevTxid := chainhash.Hash(decodedPset.Inputs[0].PreviousTxid).String()
|
||||||
|
if prevTxid != node.ParentTxid {
|
||||||
return ErrParentTxidInput
|
return ErrParentTxidInput
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,19 +225,24 @@ func validateNodeTransaction(
|
|||||||
rootHash := tapLeaf.ControlBlock.RootHash(tapLeaf.Script)
|
rootHash := tapLeaf.ControlBlock.RootHash(tapLeaf.Script)
|
||||||
outputScript := taproot.ComputeTaprootOutputKey(key, rootHash)
|
outputScript := taproot.ComputeTaprootOutputKey(key, rootHash)
|
||||||
|
|
||||||
if !bytes.Equal(schnorr.SerializePubKey(outputScript), previousScriptKey) {
|
if !bytes.Equal(
|
||||||
|
schnorr.SerializePubKey(outputScript), previousScriptKey,
|
||||||
|
) {
|
||||||
return ErrInvalidTaprootScript
|
return ErrInvalidTaprootScript
|
||||||
}
|
}
|
||||||
|
|
||||||
close, err := DecodeClosure(tapLeaf.Script)
|
closure, err := DecodeClosure(tapLeaf.Script)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
switch c := close.(type) {
|
switch c := closure.(type) {
|
||||||
case *CSVSigClosure:
|
case *CSVSigClosure:
|
||||||
isASP := bytes.Equal(schnorr.SerializePubKey(c.Pubkey), schnorr.SerializePubKey(expectedPublicKeyASP))
|
isASP := bytes.Equal(
|
||||||
isSweepDelay := int64(c.Seconds) == expectedSequenceSeconds
|
schnorr.SerializePubKey(c.Pubkey),
|
||||||
|
schnorr.SerializePubKey(expectedPublicKeyASP),
|
||||||
|
)
|
||||||
|
isSweepDelay := int64(c.Seconds) == expectedSequence
|
||||||
|
|
||||||
if isASP && !isSweepDelay {
|
if isASP && !isSweepDelay {
|
||||||
return ErrInvalidSweepSequence
|
return ErrInvalidSweepSequence
|
||||||
@@ -268,7 +273,9 @@ func validateNodeTransaction(
|
|||||||
leftWitnessProgram := childTx.Outputs[0].Script[2:]
|
leftWitnessProgram := childTx.Outputs[0].Script[2:]
|
||||||
leftOutputAmount := childTx.Outputs[0].Value
|
leftOutputAmount := childTx.Outputs[0].Value
|
||||||
|
|
||||||
if !bytes.Equal(leftWitnessProgram, schnorr.SerializePubKey(c.LeftKey)) {
|
if !bytes.Equal(
|
||||||
|
leftWitnessProgram, schnorr.SerializePubKey(c.LeftKey),
|
||||||
|
) {
|
||||||
return ErrInvalidLeftOutput
|
return ErrInvalidLeftOutput
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,7 +287,9 @@ func validateNodeTransaction(
|
|||||||
rightWitnessProgram := childTx.Outputs[1].Script[2:]
|
rightWitnessProgram := childTx.Outputs[1].Script[2:]
|
||||||
rightOutputAmount := childTx.Outputs[1].Value
|
rightOutputAmount := childTx.Outputs[1].Value
|
||||||
|
|
||||||
if !bytes.Equal(rightWitnessProgram, schnorr.SerializePubKey(c.RightKey)) {
|
if !bytes.Equal(
|
||||||
|
rightWitnessProgram, schnorr.SerializePubKey(c.RightKey),
|
||||||
|
) {
|
||||||
return ErrInvalidRightOutput
|
return ErrInvalidRightOutput
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -372,11 +372,11 @@
|
|||||||
"pubkey": {
|
"pubkey": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"lifetime": {
|
"roundLifetime": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "int64"
|
"format": "int64"
|
||||||
},
|
},
|
||||||
"exitDelay": {
|
"unilateralExitDelay": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "int64"
|
"format": "int64"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -169,6 +169,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"v1LockUtxosResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"expirationDate": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "int64"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"v1MintResponse": {
|
"v1MintResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -56,15 +56,6 @@ service ArkService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
message OnboardRequest {
|
|
||||||
string boarding_tx = 1;
|
|
||||||
Tree congestion_tree = 2;
|
|
||||||
string user_pubkey = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message OnboardResponse {
|
|
||||||
}
|
|
||||||
|
|
||||||
message RegisterPaymentRequest {
|
message RegisterPaymentRequest {
|
||||||
repeated Input inputs = 1;
|
repeated Input inputs = 1;
|
||||||
}
|
}
|
||||||
@@ -94,6 +85,63 @@ message GetRoundResponse {
|
|||||||
Round round = 1;
|
Round round = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 ListVtxosRequest {
|
||||||
|
string address = 1;
|
||||||
|
}
|
||||||
|
message ListVtxosResponse {
|
||||||
|
repeated Vtxo vtxos = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetInfoRequest {}
|
||||||
|
message GetInfoResponse {
|
||||||
|
string pubkey = 1;
|
||||||
|
int64 round_lifetime = 2;
|
||||||
|
int64 unilateral_exit_delay = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message OnboardRequest {
|
||||||
|
string boarding_tx = 1;
|
||||||
|
Tree congestion_tree = 2;
|
||||||
|
string user_pubkey = 3;
|
||||||
|
}
|
||||||
|
message OnboardResponse {
|
||||||
|
}
|
||||||
|
|
||||||
|
// EVENT TYPES
|
||||||
|
|
||||||
|
message RoundFinalizationEvent {
|
||||||
|
string id = 1;
|
||||||
|
string pool_partial_tx = 2;
|
||||||
|
repeated string forfeit_txs = 3;
|
||||||
|
Tree congestion_tree = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RoundFinalizedEvent {
|
||||||
|
string id = 1;
|
||||||
|
string pool_txid = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RoundFailed {
|
||||||
|
string id = 1;
|
||||||
|
string reason = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TYPES
|
||||||
|
|
||||||
message Round {
|
message Round {
|
||||||
string id = 1;
|
string id = 1;
|
||||||
int64 start = 2;
|
int64 start = 2;
|
||||||
@@ -114,13 +162,6 @@ message Output {
|
|||||||
uint64 amount = 2;
|
uint64 amount = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message RoundFinalizationEvent {
|
|
||||||
string id = 1;
|
|
||||||
string pool_partial_tx = 2;
|
|
||||||
repeated string forfeit_txs = 3;
|
|
||||||
Tree congestion_tree = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Tree {
|
message Tree {
|
||||||
repeated TreeLevel levels = 1;
|
repeated TreeLevel levels = 1;
|
||||||
}
|
}
|
||||||
@@ -135,51 +176,9 @@ message Node {
|
|||||||
string parent_txid = 3;
|
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 ListVtxosRequest {
|
|
||||||
string address = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ListVtxosResponse {
|
|
||||||
repeated Vtxo vtxos = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Vtxo {
|
message Vtxo {
|
||||||
Input outpoint = 1;
|
Input outpoint = 1;
|
||||||
Output receiver = 2;
|
Output receiver = 2;
|
||||||
bool spent = 3;
|
bool spent = 3;
|
||||||
string pool_txid = 4;
|
string pool_txid = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetInfoRequest {}
|
|
||||||
|
|
||||||
message GetInfoResponse {
|
|
||||||
string pubkey = 1;
|
|
||||||
int64 lifetime = 2;
|
|
||||||
int64 exit_delay = 3;
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -25,6 +25,8 @@ type TransactionServiceClient interface {
|
|||||||
// Selected utxos are locked for predefined amount of time to prevent
|
// Selected utxos are locked for predefined amount of time to prevent
|
||||||
// double-spending them.
|
// double-spending them.
|
||||||
SelectUtxos(ctx context.Context, in *SelectUtxosRequest, opts ...grpc.CallOption) (*SelectUtxosResponse, error)
|
SelectUtxos(ctx context.Context, in *SelectUtxosRequest, opts ...grpc.CallOption) (*SelectUtxosResponse, error)
|
||||||
|
// LockUtxos allows to manually select utxos to spend by a subsequent tx.
|
||||||
|
LockUtxos(ctx context.Context, in *LockUtxosRequest, opts ...grpc.CallOption) (*LockUtxosResponse, error)
|
||||||
// EstimateFees returns the fee amount to pay for a tx containing the given
|
// EstimateFees returns the fee amount to pay for a tx containing the given
|
||||||
// inputs and outputs.
|
// inputs and outputs.
|
||||||
EstimateFees(ctx context.Context, in *EstimateFeesRequest, opts ...grpc.CallOption) (*EstimateFeesResponse, error)
|
EstimateFees(ctx context.Context, in *EstimateFeesRequest, opts ...grpc.CallOption) (*EstimateFeesResponse, error)
|
||||||
@@ -87,6 +89,15 @@ func (c *transactionServiceClient) SelectUtxos(ctx context.Context, in *SelectUt
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *transactionServiceClient) LockUtxos(ctx context.Context, in *LockUtxosRequest, opts ...grpc.CallOption) (*LockUtxosResponse, error) {
|
||||||
|
out := new(LockUtxosResponse)
|
||||||
|
err := c.cc.Invoke(ctx, "/ocean.v1.TransactionService/LockUtxos", 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) {
|
func (c *transactionServiceClient) EstimateFees(ctx context.Context, in *EstimateFeesRequest, opts ...grpc.CallOption) (*EstimateFeesResponse, error) {
|
||||||
out := new(EstimateFeesResponse)
|
out := new(EstimateFeesResponse)
|
||||||
err := c.cc.Invoke(ctx, "/ocean.v1.TransactionService/EstimateFees", in, out, opts...)
|
err := c.cc.Invoke(ctx, "/ocean.v1.TransactionService/EstimateFees", in, out, opts...)
|
||||||
@@ -224,6 +235,8 @@ type TransactionServiceServer interface {
|
|||||||
// Selected utxos are locked for predefined amount of time to prevent
|
// Selected utxos are locked for predefined amount of time to prevent
|
||||||
// double-spending them.
|
// double-spending them.
|
||||||
SelectUtxos(context.Context, *SelectUtxosRequest) (*SelectUtxosResponse, error)
|
SelectUtxos(context.Context, *SelectUtxosRequest) (*SelectUtxosResponse, error)
|
||||||
|
// LockUtxos allows to manually select utxos to spend by a subsequent tx.
|
||||||
|
LockUtxos(context.Context, *LockUtxosRequest) (*LockUtxosResponse, error)
|
||||||
// EstimateFees returns the fee amount to pay for a tx containing the given
|
// EstimateFees returns the fee amount to pay for a tx containing the given
|
||||||
// inputs and outputs.
|
// inputs and outputs.
|
||||||
EstimateFees(context.Context, *EstimateFeesRequest) (*EstimateFeesResponse, error)
|
EstimateFees(context.Context, *EstimateFeesRequest) (*EstimateFeesResponse, error)
|
||||||
@@ -270,6 +283,9 @@ func (UnimplementedTransactionServiceServer) GetTransaction(context.Context, *Ge
|
|||||||
func (UnimplementedTransactionServiceServer) SelectUtxos(context.Context, *SelectUtxosRequest) (*SelectUtxosResponse, error) {
|
func (UnimplementedTransactionServiceServer) SelectUtxos(context.Context, *SelectUtxosRequest) (*SelectUtxosResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method SelectUtxos not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method SelectUtxos not implemented")
|
||||||
}
|
}
|
||||||
|
func (UnimplementedTransactionServiceServer) LockUtxos(context.Context, *LockUtxosRequest) (*LockUtxosResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method LockUtxos not implemented")
|
||||||
|
}
|
||||||
func (UnimplementedTransactionServiceServer) EstimateFees(context.Context, *EstimateFeesRequest) (*EstimateFeesResponse, error) {
|
func (UnimplementedTransactionServiceServer) EstimateFees(context.Context, *EstimateFeesRequest) (*EstimateFeesResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method EstimateFees not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method EstimateFees not implemented")
|
||||||
}
|
}
|
||||||
@@ -360,6 +376,24 @@ func _TransactionService_SelectUtxos_Handler(srv interface{}, ctx context.Contex
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _TransactionService_LockUtxos_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(LockUtxosRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(TransactionServiceServer).LockUtxos(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/ocean.v1.TransactionService/LockUtxos",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(TransactionServiceServer).LockUtxos(ctx, req.(*LockUtxosRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
func _TransactionService_EstimateFees_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
func _TransactionService_EstimateFees_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
in := new(EstimateFeesRequest)
|
in := new(EstimateFeesRequest)
|
||||||
if err := dec(in); err != nil {
|
if err := dec(in); err != nil {
|
||||||
@@ -627,6 +661,10 @@ var TransactionService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "SelectUtxos",
|
MethodName: "SelectUtxos",
|
||||||
Handler: _TransactionService_SelectUtxos_Handler,
|
Handler: _TransactionService_SelectUtxos_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "LockUtxos",
|
||||||
|
Handler: _TransactionService_LockUtxos_Handler,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
MethodName: "EstimateFees",
|
MethodName: "EstimateFees",
|
||||||
Handler: _TransactionService_EstimateFees_Handler,
|
Handler: _TransactionService_EstimateFees_Handler,
|
||||||
|
|||||||
@@ -31,18 +31,6 @@ func main() {
|
|||||||
NoTLS: cfg.NoTLS,
|
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.ExitDelay%512 != 0 {
|
|
||||||
setExitDelay := cfg.ExitDelay
|
|
||||||
cfg.ExitDelay = cfg.ExitDelay - (cfg.ExitDelay % 512)
|
|
||||||
log.Infof("exit delay must be a multiple of 512, %d -> %d", setExitDelay, cfg.ExitDelay)
|
|
||||||
}
|
|
||||||
|
|
||||||
appConfig := &appconfig.Config{
|
appConfig := &appconfig.Config{
|
||||||
DbType: cfg.DbType,
|
DbType: cfg.DbType,
|
||||||
DbDir: cfg.DbDir,
|
DbDir: cfg.DbDir,
|
||||||
@@ -54,7 +42,7 @@ func main() {
|
|||||||
WalletAddr: cfg.WalletAddr,
|
WalletAddr: cfg.WalletAddr,
|
||||||
MinRelayFee: cfg.MinRelayFee,
|
MinRelayFee: cfg.MinRelayFee,
|
||||||
RoundLifetime: cfg.RoundLifetime,
|
RoundLifetime: cfg.RoundLifetime,
|
||||||
ExitDelay: cfg.ExitDelay,
|
UnilateralExitDelay: cfg.UnilateralExitDelay,
|
||||||
}
|
}
|
||||||
svc, err := grpcservice.NewService(svcConfig, appConfig)
|
svc, err := grpcservice.NewService(svcConfig, appConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import (
|
|||||||
"github.com/vulpemventures/go-elements/network"
|
"github.com/vulpemventures/go-elements/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const minAllowedSequence = 512
|
||||||
|
|
||||||
var (
|
var (
|
||||||
supportedDbs = supportedType{
|
supportedDbs = supportedType{
|
||||||
"badger": {},
|
"badger": {},
|
||||||
@@ -41,7 +43,7 @@ type Config struct {
|
|||||||
WalletAddr string
|
WalletAddr string
|
||||||
MinRelayFee uint64
|
MinRelayFee uint64
|
||||||
RoundLifetime int64
|
RoundLifetime int64
|
||||||
ExitDelay int64
|
UnilateralExitDelay int64
|
||||||
|
|
||||||
repo ports.RepoManager
|
repo ports.RepoManager
|
||||||
svc application.Service
|
svc application.Service
|
||||||
@@ -95,25 +97,32 @@ func (c *Config) Validate() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// round life time must be a multiple of 512
|
// round life time must be a multiple of 512
|
||||||
if c.RoundLifetime < 512 || c.RoundLifetime%512 != 0 {
|
if c.RoundLifetime < minAllowedSequence {
|
||||||
return fmt.Errorf("invalid round lifetime, must be greater or equal than 512 and a multiple of 512")
|
return fmt.Errorf(
|
||||||
}
|
"invalid round lifetime, must be a at least %d", minAllowedSequence,
|
||||||
seq, err := common.BIP68Encode(uint(c.RoundLifetime))
|
)
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid round lifetime, %s", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
seconds, err := common.BIP68Decode(seq)
|
if c.UnilateralExitDelay < minAllowedSequence {
|
||||||
if err != nil {
|
return fmt.Errorf(
|
||||||
return fmt.Errorf("invalid round lifetime, %s", err)
|
"invalid unilateral exit delay, must at least %d", minAllowedSequence,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if seconds != uint(c.RoundLifetime) {
|
if c.RoundLifetime%minAllowedSequence != 0 {
|
||||||
return fmt.Errorf("invalid round lifetime, must be a multiple of 512")
|
c.RoundLifetime -= c.RoundLifetime % minAllowedSequence
|
||||||
|
log.Infof(
|
||||||
|
"round lifetime must be a multiple of %d, rounded to %d",
|
||||||
|
minAllowedSequence, c.RoundLifetime,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.ExitDelay < 512 || c.ExitDelay%512 != 0 {
|
if c.UnilateralExitDelay%minAllowedSequence != 0 {
|
||||||
return fmt.Errorf("invalid exit delay, must be greater or equal than 512 and a multiple of 512")
|
c.UnilateralExitDelay -= c.UnilateralExitDelay % minAllowedSequence
|
||||||
|
log.Infof(
|
||||||
|
"unilateral exit delay must be a multiple of %d, rounded to %d",
|
||||||
|
minAllowedSequence, c.UnilateralExitDelay,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -166,7 +175,9 @@ func (c *Config) txBuilderService() error {
|
|||||||
|
|
||||||
switch c.TxBuilderType {
|
switch c.TxBuilderType {
|
||||||
case "covenant":
|
case "covenant":
|
||||||
svc = txbuilder.NewTxBuilder(c.wallet, net, c.RoundLifetime, c.ExitDelay)
|
svc = txbuilder.NewTxBuilder(
|
||||||
|
c.wallet, net, c.RoundLifetime, c.UnilateralExitDelay,
|
||||||
|
)
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("unknown tx builder type")
|
err = fmt.Errorf("unknown tx builder type")
|
||||||
}
|
}
|
||||||
@@ -215,7 +226,8 @@ func (c *Config) schedulerService() error {
|
|||||||
func (c *Config) appService() error {
|
func (c *Config) appService() error {
|
||||||
net := c.mainChain()
|
net := c.mainChain()
|
||||||
svc, err := application.NewService(
|
svc, err := application.NewService(
|
||||||
c.Network, net, c.RoundInterval, c.RoundLifetime, c.ExitDelay, c.MinRelayFee,
|
c.Network, net,
|
||||||
|
c.RoundInterval, c.RoundLifetime, c.UnilateralExitDelay, c.MinRelayFee,
|
||||||
c.wallet, c.repo, c.txBuilder, c.scanner, c.scheduler,
|
c.wallet, c.repo, c.txBuilder, c.scanner, c.scheduler,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ type Config struct {
|
|||||||
LogLevel int
|
LogLevel int
|
||||||
MinRelayFee uint64
|
MinRelayFee uint64
|
||||||
RoundLifetime int64
|
RoundLifetime int64
|
||||||
ExitDelay int64
|
UnilateralExitDelay int64
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -41,7 +41,7 @@ var (
|
|||||||
Network = "NETWORK"
|
Network = "NETWORK"
|
||||||
MinRelayFee = "MIN_RELAY_FEE"
|
MinRelayFee = "MIN_RELAY_FEE"
|
||||||
RoundLifetime = "ROUND_LIFETIME"
|
RoundLifetime = "ROUND_LIFETIME"
|
||||||
ExitDelay = "EXIT_DELAY"
|
UnilateralExitDelay = "UNILATERAL_EXIT_DELAY"
|
||||||
|
|
||||||
defaultDatadir = common.AppDataDir("arkd", false)
|
defaultDatadir = common.AppDataDir("arkd", false)
|
||||||
defaultRoundInterval = 10
|
defaultRoundInterval = 10
|
||||||
@@ -55,7 +55,7 @@ var (
|
|||||||
defaultLogLevel = 5
|
defaultLogLevel = 5
|
||||||
defaultMinRelayFee = 30
|
defaultMinRelayFee = 30
|
||||||
defaultRoundLifetime = 512
|
defaultRoundLifetime = 512
|
||||||
defaultExitDelay = 512
|
defaultUnilateralExitDelay = 512
|
||||||
)
|
)
|
||||||
|
|
||||||
func LoadConfig() (*Config, error) {
|
func LoadConfig() (*Config, error) {
|
||||||
@@ -74,7 +74,7 @@ func LoadConfig() (*Config, error) {
|
|||||||
viper.SetDefault(Network, defaultNetwork)
|
viper.SetDefault(Network, defaultNetwork)
|
||||||
viper.SetDefault(RoundLifetime, defaultRoundLifetime)
|
viper.SetDefault(RoundLifetime, defaultRoundLifetime)
|
||||||
viper.SetDefault(MinRelayFee, defaultMinRelayFee)
|
viper.SetDefault(MinRelayFee, defaultMinRelayFee)
|
||||||
viper.SetDefault(ExitDelay, defaultExitDelay)
|
viper.SetDefault(UnilateralExitDelay, defaultUnilateralExitDelay)
|
||||||
|
|
||||||
net, err := getNetwork()
|
net, err := getNetwork()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -99,7 +99,7 @@ func LoadConfig() (*Config, error) {
|
|||||||
Network: net,
|
Network: net,
|
||||||
MinRelayFee: viper.GetUint64(MinRelayFee),
|
MinRelayFee: viper.GetUint64(MinRelayFee),
|
||||||
RoundLifetime: viper.GetInt64(RoundLifetime),
|
RoundLifetime: viper.GetInt64(RoundLifetime),
|
||||||
ExitDelay: viper.GetInt64(ExitDelay),
|
UnilateralExitDelay: viper.GetInt64(UnilateralExitDelay),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,8 +43,8 @@ type service struct {
|
|||||||
pubkey *secp256k1.PublicKey
|
pubkey *secp256k1.PublicKey
|
||||||
roundLifetime int64
|
roundLifetime int64
|
||||||
roundInterval int64
|
roundInterval int64
|
||||||
|
unilateralExitDelay int64
|
||||||
minRelayFee uint64
|
minRelayFee uint64
|
||||||
exitDelay int64
|
|
||||||
|
|
||||||
wallet ports.WalletService
|
wallet ports.WalletService
|
||||||
repoManager ports.RepoManager
|
repoManager ports.RepoManager
|
||||||
@@ -60,7 +60,7 @@ type service struct {
|
|||||||
|
|
||||||
func NewService(
|
func NewService(
|
||||||
network common.Network, onchainNetwork network.Network,
|
network common.Network, onchainNetwork network.Network,
|
||||||
roundInterval, roundLifetime int64, exitDelay int64, minRelayFee uint64,
|
roundInterval, roundLifetime, unilateralExitDelay int64, minRelayFee uint64,
|
||||||
walletSvc ports.WalletService, repoManager ports.RepoManager,
|
walletSvc ports.WalletService, repoManager ports.RepoManager,
|
||||||
builder ports.TxBuilder, scanner ports.BlockchainScanner,
|
builder ports.TxBuilder, scanner ports.BlockchainScanner,
|
||||||
scheduler ports.SchedulerService,
|
scheduler ports.SchedulerService,
|
||||||
@@ -79,7 +79,7 @@ func NewService(
|
|||||||
|
|
||||||
svc := &service{
|
svc := &service{
|
||||||
network, onchainNetwork, pubkey,
|
network, onchainNetwork, pubkey,
|
||||||
roundLifetime, roundInterval, minRelayFee, exitDelay,
|
roundLifetime, roundInterval, unilateralExitDelay, minRelayFee,
|
||||||
walletSvc, repoManager, builder, scanner, sweeper,
|
walletSvc, repoManager, builder, scanner, sweeper,
|
||||||
paymentRequests, forfeitTxs, eventsCh,
|
paymentRequests, forfeitTxs, eventsCh,
|
||||||
}
|
}
|
||||||
@@ -181,7 +181,8 @@ func (s *service) GetRoundByTxid(ctx context.Context, poolTxid string) (*domain.
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) GetInfo(ctx context.Context) (string, int64, int64, error) {
|
func (s *service) GetInfo(ctx context.Context) (string, int64, int64, error) {
|
||||||
return hex.EncodeToString(s.pubkey.SerializeCompressed()), s.roundLifetime, s.exitDelay, nil
|
pubkey := hex.EncodeToString(s.pubkey.SerializeCompressed())
|
||||||
|
return pubkey, s.roundLifetime, s.unilateralExitDelay, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) Onboard(
|
func (s *service) Onboard(
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ const (
|
|||||||
testingKey = "0218d5ca8b58797b7dbd65c075dd7ba7784b3f38ab71b1a5a8e3f94ba0257654a6"
|
testingKey = "0218d5ca8b58797b7dbd65c075dd7ba7784b3f38ab71b1a5a8e3f94ba0257654a6"
|
||||||
minRelayFee = uint64(30)
|
minRelayFee = uint64(30)
|
||||||
roundLifetime = int64(1209344)
|
roundLifetime = int64(1209344)
|
||||||
exitDelay = int64(512)
|
unilateralExitDelay = int64(512)
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -45,7 +45,9 @@ func TestMain(m *testing.M) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestBuildPoolTx(t *testing.T) {
|
func TestBuildPoolTx(t *testing.T) {
|
||||||
builder := txbuilder.NewTxBuilder(wallet, network.Liquid, roundLifetime, exitDelay)
|
builder := txbuilder.NewTxBuilder(
|
||||||
|
wallet, network.Liquid, roundLifetime, unilateralExitDelay,
|
||||||
|
)
|
||||||
|
|
||||||
fixtures, err := parsePoolTxFixtures()
|
fixtures, err := parsePoolTxFixtures()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -54,14 +56,18 @@ func TestBuildPoolTx(t *testing.T) {
|
|||||||
if len(fixtures.Valid) > 0 {
|
if len(fixtures.Valid) > 0 {
|
||||||
t.Run("valid", func(t *testing.T) {
|
t.Run("valid", func(t *testing.T) {
|
||||||
for _, f := range fixtures.Valid {
|
for _, f := range fixtures.Valid {
|
||||||
poolTx, congestionTree, err := builder.BuildPoolTx(pubkey, f.Payments, minRelayFee)
|
poolTx, congestionTree, err := builder.BuildPoolTx(
|
||||||
|
pubkey, f.Payments, minRelayFee,
|
||||||
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotEmpty(t, poolTx)
|
require.NotEmpty(t, poolTx)
|
||||||
require.NotEmpty(t, congestionTree)
|
require.NotEmpty(t, congestionTree)
|
||||||
require.Equal(t, f.ExpectedNumOfNodes, congestionTree.NumberOfNodes())
|
require.Equal(t, f.ExpectedNumOfNodes, congestionTree.NumberOfNodes())
|
||||||
require.Len(t, congestionTree.Leaves(), f.ExpectedNumOfLeaves)
|
require.Len(t, congestionTree.Leaves(), f.ExpectedNumOfLeaves)
|
||||||
|
|
||||||
err = tree.ValidateCongestionTree(congestionTree, poolTx, pubkey, roundLifetime)
|
err = tree.ValidateCongestionTree(
|
||||||
|
congestionTree, poolTx, pubkey, roundLifetime,
|
||||||
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -70,7 +76,9 @@ func TestBuildPoolTx(t *testing.T) {
|
|||||||
if len(fixtures.Invalid) > 0 {
|
if len(fixtures.Invalid) > 0 {
|
||||||
t.Run("invalid", func(t *testing.T) {
|
t.Run("invalid", func(t *testing.T) {
|
||||||
for _, f := range fixtures.Invalid {
|
for _, f := range fixtures.Invalid {
|
||||||
poolTx, congestionTree, err := builder.BuildPoolTx(pubkey, f.Payments, minRelayFee)
|
poolTx, congestionTree, err := builder.BuildPoolTx(
|
||||||
|
pubkey, f.Payments, minRelayFee,
|
||||||
|
)
|
||||||
require.EqualError(t, err, f.ExpectedErr)
|
require.EqualError(t, err, f.ExpectedErr)
|
||||||
require.Empty(t, poolTx)
|
require.Empty(t, poolTx)
|
||||||
require.Empty(t, congestionTree)
|
require.Empty(t, congestionTree)
|
||||||
@@ -80,7 +88,9 @@ func TestBuildPoolTx(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestBuildForfeitTxs(t *testing.T) {
|
func TestBuildForfeitTxs(t *testing.T) {
|
||||||
builder := txbuilder.NewTxBuilder(wallet, network.Liquid, 1209344, exitDelay)
|
builder := txbuilder.NewTxBuilder(
|
||||||
|
wallet, network.Liquid, 1209344, unilateralExitDelay,
|
||||||
|
)
|
||||||
|
|
||||||
fixtures, err := parseForfeitTxsFixtures()
|
fixtures, err := parseForfeitTxsFixtures()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|||||||
@@ -204,8 +204,8 @@ func (h *handler) GetInfo(ctx context.Context, req *arkv1.GetInfoRequest) (*arkv
|
|||||||
|
|
||||||
return &arkv1.GetInfoResponse{
|
return &arkv1.GetInfoResponse{
|
||||||
Pubkey: pubkey,
|
Pubkey: pubkey,
|
||||||
Lifetime: lifetime,
|
RoundLifetime: lifetime,
|
||||||
ExitDelay: delay,
|
UnilateralExitDelay: delay,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user