mirror of
https://github.com/aljazceru/ark.git
synced 2025-12-17 12:14:21 +01:00
Support onboarding & Drop faucet (#119)
* Renaming * Add server-side support for onboarding * add onboard --amount command * support client side onboarding * Drop dummy tx builder * Drop faucet * Fixes * fix public key encoding * fix schnorr pub key check in validation * fix server/README to accomodate onboarding --------- Co-authored-by: Louis <louis@vulpem.com> Co-authored-by: João Bordalo <bordalix@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
a95a829b20
commit
1650ea5935
@@ -18,7 +18,7 @@ var expiryDetailsFlag = cli.BoolFlag{
|
|||||||
|
|
||||||
var balanceCommand = cli.Command{
|
var balanceCommand = cli.Command{
|
||||||
Name: "balance",
|
Name: "balance",
|
||||||
Usage: "Print balance of the Ark wallet",
|
Usage: "Shows the onchain and offchain balance of the Ark wallet",
|
||||||
Action: balanceAction,
|
Action: balanceAction,
|
||||||
Flags: []cli.Flag{&expiryDetailsFlag},
|
Flags: []cli.Flag{&expiryDetailsFlag},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
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/ark-network/ark/common"
|
"github.com/ark-network/ark/common"
|
||||||
"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/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/urfave/cli/v2"
|
||||||
@@ -596,7 +597,7 @@ func handleRoundStream(
|
|||||||
if len(output.Script) == 0 {
|
if len(output.Script) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if bytes.Equal(output.Script[2:], outputTapKey.SerializeCompressed()) {
|
if bytes.Equal(output.Script[2:], schnorr.SerializePubKey(outputTapKey)) {
|
||||||
if output.Value != receiver.Amount {
|
if output.Value != receiver.Amount {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -733,6 +734,29 @@ func toCongestionTree(treeFromProto *arkv1.Tree) (tree.CongestionTree, error) {
|
|||||||
return levels, nil
|
return levels, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// castCongestionTree converts a tree.CongestionTree to a repeated arkv1.TreeLevel
|
||||||
|
func castCongestionTree(congestionTree tree.CongestionTree) *arkv1.Tree {
|
||||||
|
levels := make([]*arkv1.TreeLevel, 0, len(congestionTree))
|
||||||
|
for _, level := range congestionTree {
|
||||||
|
levelProto := &arkv1.TreeLevel{
|
||||||
|
Nodes: make([]*arkv1.Node, 0, len(level)),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, node := range level {
|
||||||
|
levelProto.Nodes = append(levelProto.Nodes, &arkv1.Node{
|
||||||
|
Txid: node.Txid,
|
||||||
|
Tx: node.Tx,
|
||||||
|
ParentTxid: node.ParentTxid,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
levels = append(levels, levelProto)
|
||||||
|
}
|
||||||
|
return &arkv1.Tree{
|
||||||
|
Levels: levels,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func decodeReceiverAddress(addr string) (
|
func decodeReceiverAddress(addr string) (
|
||||||
isOnChainAddress bool,
|
isOnChainAddress bool,
|
||||||
onchainScript []byte,
|
onchainScript []byte,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
|
|
||||||
var configCommand = cli.Command{
|
var configCommand = cli.Command{
|
||||||
Name: "config",
|
Name: "config",
|
||||||
Usage: "Print local configuration of the Ark CLI",
|
Usage: "Shows configuration of the Ark wallet",
|
||||||
Action: printConfigAction,
|
Action: printConfigAction,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
|
|
||||||
var dumpCommand = cli.Command{
|
var dumpCommand = cli.Command{
|
||||||
Name: "dump-privkey",
|
Name: "dump-privkey",
|
||||||
Usage: "Dump private key of the Ark wallet",
|
Usage: "Dumps private key of the Ark wallet",
|
||||||
Action: dumpAction,
|
Action: dumpAction,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,71 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
var faucetCommand = cli.Command{
|
|
||||||
Name: "faucet",
|
|
||||||
Usage: "Faucet your wallet",
|
|
||||||
Action: faucetAction,
|
|
||||||
}
|
|
||||||
|
|
||||||
func faucetAction(ctx *cli.Context) error {
|
|
||||||
addr, _, err := getAddress()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
client, close, err := getClientFromState(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer close()
|
|
||||||
|
|
||||||
_, err = client.Faucet(ctx.Context, &arkv1.FaucetRequest{
|
|
||||||
Address: addr,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
eventStream, err := client.GetEventStream(ctx.Context, &arkv1.GetEventStreamRequest{})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
event, err := eventStream.Recv()
|
|
||||||
if err == io.EOF {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if event.GetRoundFinalization() != nil {
|
|
||||||
if _, err := client.FinalizePayment(context.Background(), &arkv1.FinalizePaymentRequest{
|
|
||||||
SignedForfeitTxs: event.GetRoundFinalization().GetForfeitTxs(),
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if event.GetRoundFailed() != nil {
|
|
||||||
return fmt.Errorf("faucet failed: %s", event.GetRoundFailed().GetReason())
|
|
||||||
}
|
|
||||||
|
|
||||||
if event.GetRoundFinalized() != nil {
|
|
||||||
return printJSON(map[string]interface{}{
|
|
||||||
"pool_txid": event.GetRoundFinalized().GetPoolTxid(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -36,7 +36,7 @@ var (
|
|||||||
|
|
||||||
var initCommand = cli.Command{
|
var initCommand = cli.Command{
|
||||||
Name: "init",
|
Name: "init",
|
||||||
Usage: "initialize the wallet with an encryption password, and connect it to an ASP",
|
Usage: "Initialize your Ark wallet with an encryption password, and connect it to an ASP",
|
||||||
Action: initAction,
|
Action: initAction,
|
||||||
Flags: []cli.Flag{&passwordFlag, &privateKeyFlag, &networkFlag, &urlFlag},
|
Flags: []cli.Flag{&passwordFlag, &privateKeyFlag, &networkFlag, &urlFlag},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,17 +57,17 @@ func main() {
|
|||||||
|
|
||||||
app.Version = version
|
app.Version = version
|
||||||
app.Name = "Ark CLI"
|
app.Name = "Ark CLI"
|
||||||
app.Usage = "command line interface for Ark wallet"
|
app.Usage = "ark wallet command line interface"
|
||||||
app.Commands = append(
|
app.Commands = append(
|
||||||
app.Commands,
|
app.Commands,
|
||||||
&balanceCommand,
|
&balanceCommand,
|
||||||
&configCommand,
|
&configCommand,
|
||||||
&dumpCommand,
|
&dumpCommand,
|
||||||
&faucetCommand,
|
|
||||||
&initCommand,
|
&initCommand,
|
||||||
&receiveCommand,
|
&receiveCommand,
|
||||||
&redeemCommand,
|
&redeemCommand,
|
||||||
&sendCommand,
|
&sendCommand,
|
||||||
|
&onboardCommand,
|
||||||
)
|
)
|
||||||
|
|
||||||
app.Before = func(ctx *cli.Context) error {
|
app.Before = func(ctx *cli.Context) error {
|
||||||
|
|||||||
147
client/onboard.go
Normal file
147
client/onboard.go
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1"
|
||||||
|
"github.com/ark-network/ark/common/tree"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
"github.com/vulpemventures/go-elements/payment"
|
||||||
|
"github.com/vulpemventures/go-elements/psetv2"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
minRelayFee = 30
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
amountOnboardFlag = cli.Uint64Flag{
|
||||||
|
Name: "amount",
|
||||||
|
Usage: "amount to onboard in sats",
|
||||||
|
Required: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var onboardCommand = cli.Command{
|
||||||
|
Name: "onboard",
|
||||||
|
Usage: "Onboard the Ark by lifting your funds",
|
||||||
|
Action: onboardAction,
|
||||||
|
Flags: []cli.Flag{&amountOnboardFlag},
|
||||||
|
}
|
||||||
|
|
||||||
|
func onboardAction(ctx *cli.Context) error {
|
||||||
|
amount := ctx.Uint64("amount")
|
||||||
|
|
||||||
|
if amount <= 0 {
|
||||||
|
return fmt.Errorf("missing amount flag (--amount)")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, net := getNetwork()
|
||||||
|
|
||||||
|
aspPubkey, err := getServiceProviderPublicKey()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
lifetime, err := getLifetime()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
exitDelay, err := getExitDelay()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
userPubKey, err := getWalletPublicKey()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
congestionTreeLeaf := tree.Receiver{
|
||||||
|
Pubkey: hex.EncodeToString(userPubKey.SerializeCompressed()),
|
||||||
|
Amount: amount,
|
||||||
|
}
|
||||||
|
|
||||||
|
treeFactoryFn, sharedOutputScript, sharedOutputAmount, err := tree.CraftCongestionTree(
|
||||||
|
net.AssetID, aspPubkey, []tree.Receiver{congestionTreeLeaf}, minRelayFee, lifetime, exitDelay,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
pay, err := payment.FromScript(sharedOutputScript, net, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
address, err := pay.TaprootAddress()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
onchainReceiver := receiver{
|
||||||
|
To: address,
|
||||||
|
Amount: sharedOutputAmount,
|
||||||
|
}
|
||||||
|
|
||||||
|
pset, err := sendOnchain(ctx, []receiver{onchainReceiver})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
txid, err := broadcastPset(pset)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("onboard txid:", txid)
|
||||||
|
fmt.Println("waiting for confirmation... (this may take a while, do not cancel the process)")
|
||||||
|
|
||||||
|
// wait for the transaction to be confirmed
|
||||||
|
if err := waitForTxConfirmation(ctx, txid); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("transaction confirmed")
|
||||||
|
|
||||||
|
congestionTree, err := treeFactoryFn(psetv2.InputArgs{
|
||||||
|
Txid: txid,
|
||||||
|
TxIndex: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
client, close, err := getClientFromState(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer close()
|
||||||
|
|
||||||
|
_, err = client.Onboard(ctx.Context, &arkv1.OnboardRequest{
|
||||||
|
BoardingTx: pset,
|
||||||
|
CongestionTree: castCongestionTree(congestionTree),
|
||||||
|
UserPubkey: hex.EncodeToString(userPubKey.SerializeCompressed()),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitForTxConfirmation(ctx *cli.Context, txid string) error {
|
||||||
|
isConfirmed := false
|
||||||
|
|
||||||
|
for !isConfirmed {
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
|
||||||
|
isConfirmed, _, _ = getTxBlocktime(txid)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
|
|
||||||
var receiveCommand = cli.Command{
|
var receiveCommand = cli.Command{
|
||||||
Name: "receive",
|
Name: "receive",
|
||||||
Usage: "Print the Ark address associated with your wallet and the connected Ark",
|
Usage: "Shows both onchain and offchain addresses",
|
||||||
Action: receiveAction,
|
Action: receiveAction,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ var (
|
|||||||
|
|
||||||
var redeemCommand = cli.Command{
|
var redeemCommand = cli.Command{
|
||||||
Name: "redeem",
|
Name: "redeem",
|
||||||
Usage: "Redeem VTXO(s) to onchain",
|
Usage: "Redeem your offchain funds, either collaboratively or unilaterally",
|
||||||
Flags: []cli.Flag{&addressFlag, &amountToRedeemFlag, &forceFlag},
|
Flags: []cli.Flag{&addressFlag, &amountToRedeemFlag, &forceFlag},
|
||||||
Action: redeemAction,
|
Action: redeemAction,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ var (
|
|||||||
|
|
||||||
var sendCommand = cli.Command{
|
var sendCommand = cli.Command{
|
||||||
Name: "send",
|
Name: "send",
|
||||||
Usage: "Send VTXOs to a list of addresses",
|
Usage: "Send your onchain or offchain funds to one or many receivers",
|
||||||
Action: sendAction,
|
Action: sendAction,
|
||||||
Flags: []cli.Flag{&receiversFlag, &toFlag, &amountFlag},
|
Flags: []cli.Flag{&receiversFlag, &toFlag, &amountFlag},
|
||||||
}
|
}
|
||||||
@@ -83,9 +83,19 @@ func sendAction(ctx *cli.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(onchainReceivers) > 0 {
|
if len(onchainReceivers) > 0 {
|
||||||
if err := sendOnchain(ctx, onchainReceivers); err != nil {
|
pset, err := sendOnchain(ctx, onchainReceivers)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
txid, err := broadcastPset(pset)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return printJSON(map[string]interface{}{
|
||||||
|
"txid": txid,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(offchainReceivers) > 0 {
|
if len(offchainReceivers) > 0 {
|
||||||
@@ -203,14 +213,14 @@ func sendOffchain(ctx *cli.Context, receivers []receiver) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendOnchain(ctx *cli.Context, receivers []receiver) error {
|
func sendOnchain(ctx *cli.Context, receivers []receiver) (string, error) {
|
||||||
pset, err := psetv2.New(nil, nil, nil)
|
pset, err := psetv2.New(nil, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
updater, err := psetv2.NewUpdater(pset)
|
updater, err := psetv2.NewUpdater(pset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, net := getNetwork()
|
_, net := getNetwork()
|
||||||
@@ -219,12 +229,12 @@ func sendOnchain(ctx *cli.Context, receivers []receiver) error {
|
|||||||
for _, receiver := range receivers {
|
for _, receiver := range receivers {
|
||||||
targetAmount += receiver.Amount
|
targetAmount += receiver.Amount
|
||||||
if receiver.Amount < DUST {
|
if receiver.Amount < DUST {
|
||||||
return fmt.Errorf("invalid amount (%d), must be greater than dust %d", receiver.Amount, DUST)
|
return "", fmt.Errorf("invalid amount (%d), must be greater than dust %d", receiver.Amount, DUST)
|
||||||
}
|
}
|
||||||
|
|
||||||
script, err := address.ToOutputScript(receiver.To)
|
script, err := address.ToOutputScript(receiver.To)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := updater.AddOutputs([]psetv2.OutputArgs{
|
if err := updater.AddOutputs([]psetv2.OutputArgs{
|
||||||
@@ -234,28 +244,28 @@ func sendOnchain(ctx *cli.Context, receivers []receiver) error {
|
|||||||
Script: script,
|
Script: script,
|
||||||
},
|
},
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
selected, delayedSelected, change, err := coinSelectOnchain(targetAmount, nil)
|
selected, delayedSelected, change, err := coinSelectOnchain(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, selected, delayedSelected, 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
|
||||||
}
|
}
|
||||||
|
|
||||||
changeScript, err := address.ToOutputScript(changeAddr)
|
changeScript, err := address.ToOutputScript(changeAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := updater.AddOutputs([]psetv2.OutputArgs{
|
if err := updater.AddOutputs([]psetv2.OutputArgs{
|
||||||
@@ -265,13 +275,13 @@ func sendOnchain(ctx *cli.Context, receivers []receiver) error {
|
|||||||
Script: changeScript,
|
Script: changeScript,
|
||||||
},
|
},
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
utx, err := updater.Pset.UnsignedTx()
|
utx, err := pset.UnsignedTx()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
vBytes := utx.VirtualSize()
|
vBytes := utx.VirtualSize()
|
||||||
@@ -291,22 +301,22 @@ func sendOnchain(ctx *cli.Context, receivers []receiver) error {
|
|||||||
append(selected, delayedSelected...),
|
append(selected, delayedSelected...),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := addInputs(updater, selected, delayedSelected, net); err != nil {
|
if err := addInputs(updater, selected, delayedSelected, net); err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if newChange > 0 {
|
if newChange > 0 {
|
||||||
_, changeAddr, err := getAddress()
|
_, changeAddr, err := getAddress()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
changeScript, err := address.ToOutputScript(changeAddr)
|
changeScript, err := address.ToOutputScript(changeAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := updater.AddOutputs([]psetv2.OutputArgs{
|
if err := updater.AddOutputs([]psetv2.OutputArgs{
|
||||||
@@ -316,7 +326,7 @@ func sendOnchain(ctx *cli.Context, receivers []receiver) error {
|
|||||||
Script: changeScript,
|
Script: changeScript,
|
||||||
},
|
},
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -327,40 +337,42 @@ func sendOnchain(ctx *cli.Context, receivers []receiver) error {
|
|||||||
Amount: feeAmount,
|
Amount: feeAmount,
|
||||||
},
|
},
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
prvKey, err := privateKeyFromPassword()
|
prvKey, err := privateKeyFromPassword()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
explorer := NewExplorer()
|
explorer := NewExplorer()
|
||||||
|
|
||||||
if err := signPset(updater.Pset, explorer, prvKey); err != nil {
|
if err := signPset(updater.Pset, explorer, prvKey); err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := psetv2.FinalizeAll(updater.Pset); err != nil {
|
if err := psetv2.FinalizeAll(updater.Pset); err != nil {
|
||||||
return err
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
extracted, err := psetv2.Extract(pset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
hex, err := extracted.ToHex()
|
hex, err := extracted.ToHex()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
txid, err := explorer.Broadcast(hex)
|
return NewExplorer().Broadcast(hex)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return printJSON(map[string]interface{}{
|
|
||||||
"txid": txid,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,49 @@
|
|||||||
package txbuilder
|
package tree
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/ark-network/ark/common/tree"
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||||
"github.com/ark-network/ark/internal/core/domain"
|
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
"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 treeFactory func(outpoint psetv2.InputArgs) (tree.CongestionTree, error)
|
func CraftCongestionTree(
|
||||||
|
asset string, aspPublicKey *secp256k1.PublicKey,
|
||||||
|
receivers []Receiver, feeSatsPerNode uint64, roundLifetime int64, exitDelay int64,
|
||||||
|
) (
|
||||||
|
buildCongestionTree TreeFactory,
|
||||||
|
sharedOutputScript []byte, sharedOutputAmount uint64, err error,
|
||||||
|
) {
|
||||||
|
root, err := createPartialCongestionTree(
|
||||||
|
receivers, aspPublicKey, asset, feeSatsPerNode, roundLifetime, exitDelay,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
taprootKey, _, err := root.getWitnessData()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sharedOutputScript, err = taprootOutputScript(taprootKey)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sharedOutputAmount = root.getAmount() + root.feeSats
|
||||||
|
buildCongestionTree = root.createFinalCongestionTree()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
type node struct {
|
type node struct {
|
||||||
sweepKey *secp256k1.PublicKey
|
sweepKey *secp256k1.PublicKey
|
||||||
receivers []domain.Receiver
|
receivers []Receiver
|
||||||
left *node
|
left *node
|
||||||
right *node
|
right *node
|
||||||
asset string
|
asset string
|
||||||
@@ -131,7 +158,7 @@ func (n *node) getWitnessData() (
|
|||||||
return n._inputTaprootKey, n._inputTaprootTree, nil
|
return n._inputTaprootKey, n._inputTaprootTree, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
sweepClosure := &tree.CSVSigClosure{
|
sweepClosure := &CSVSigClosure{
|
||||||
Pubkey: n.sweepKey,
|
Pubkey: n.sweepKey,
|
||||||
Seconds: uint(n.roundLifetime),
|
Seconds: uint(n.roundLifetime),
|
||||||
}
|
}
|
||||||
@@ -147,7 +174,7 @@ func (n *node) getWitnessData() (
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
unrollClosure := &tree.UnrollClosure{
|
unrollClosure := &UnrollClosure{
|
||||||
LeftKey: taprootKey,
|
LeftKey: taprootKey,
|
||||||
LeftAmount: n.getAmount(),
|
LeftAmount: n.getAmount(),
|
||||||
}
|
}
|
||||||
@@ -163,7 +190,7 @@ func (n *node) getWitnessData() (
|
|||||||
root := branchTaprootTree.RootNode.TapHash()
|
root := branchTaprootTree.RootNode.TapHash()
|
||||||
|
|
||||||
inputTapkey := taproot.ComputeTaprootOutputKey(
|
inputTapkey := taproot.ComputeTaprootOutputKey(
|
||||||
tree.UnspendableKey(),
|
UnspendableKey(),
|
||||||
root[:],
|
root[:],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -186,7 +213,7 @@ func (n *node) getWitnessData() (
|
|||||||
leftAmount := n.left.getAmount() + n.feeSats
|
leftAmount := n.left.getAmount() + n.feeSats
|
||||||
rightAmount := n.right.getAmount() + n.feeSats
|
rightAmount := n.right.getAmount() + n.feeSats
|
||||||
|
|
||||||
unrollClosure := &tree.UnrollClosure{
|
unrollClosure := &UnrollClosure{
|
||||||
LeftKey: leftKey,
|
LeftKey: leftKey,
|
||||||
LeftAmount: leftAmount,
|
LeftAmount: leftAmount,
|
||||||
RightKey: rightKey,
|
RightKey: rightKey,
|
||||||
@@ -204,7 +231,7 @@ func (n *node) getWitnessData() (
|
|||||||
root := branchTaprootTree.RootNode.TapHash()
|
root := branchTaprootTree.RootNode.TapHash()
|
||||||
|
|
||||||
taprootKey := taproot.ComputeTaprootOutputKey(
|
taprootKey := taproot.ComputeTaprootOutputKey(
|
||||||
tree.UnspendableKey(),
|
UnspendableKey(),
|
||||||
root[:],
|
root[:],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -231,7 +258,7 @@ func (n *node) getVtxoWitnessData() (
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
redeemClosure := &tree.CSVSigClosure{
|
redeemClosure := &CSVSigClosure{
|
||||||
Pubkey: pubkey,
|
Pubkey: pubkey,
|
||||||
Seconds: uint(n.exitDelay),
|
Seconds: uint(n.exitDelay),
|
||||||
}
|
}
|
||||||
@@ -241,7 +268,7 @@ func (n *node) getVtxoWitnessData() (
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
forfeitClosure := &tree.ForfeitClosure{
|
forfeitClosure := &ForfeitClosure{
|
||||||
Pubkey: pubkey,
|
Pubkey: pubkey,
|
||||||
AspPubkey: n.sweepKey,
|
AspPubkey: n.sweepKey,
|
||||||
}
|
}
|
||||||
@@ -257,7 +284,7 @@ func (n *node) getVtxoWitnessData() (
|
|||||||
root := leafTaprootTree.RootNode.TapHash()
|
root := leafTaprootTree.RootNode.TapHash()
|
||||||
|
|
||||||
taprootKey := taproot.ComputeTaprootOutputKey(
|
taprootKey := taproot.ComputeTaprootOutputKey(
|
||||||
tree.UnspendableKey(),
|
UnspendableKey(),
|
||||||
root[:],
|
root[:],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -266,25 +293,24 @@ func (n *node) getVtxoWitnessData() (
|
|||||||
|
|
||||||
func (n *node) getTreeNode(
|
func (n *node) getTreeNode(
|
||||||
input psetv2.InputArgs, tapTree *taproot.IndexedElementsTapScriptTree,
|
input psetv2.InputArgs, tapTree *taproot.IndexedElementsTapScriptTree,
|
||||||
) (tree.Node, error) {
|
) (Node, error) {
|
||||||
pset, err := n.getTx(input, tapTree)
|
pset, err := n.getTx(input, tapTree)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return tree.Node{}, err
|
return Node{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
txid, err := getPsetId(pset)
|
txid, err := getPsetId(pset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return tree.Node{}, err
|
return Node{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
tx, err := pset.ToBase64()
|
tx, err := pset.ToBase64()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return tree.Node{}, err
|
return Node{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
parentTxid := chainhash.Hash(pset.Inputs[0].PreviousTxid).String()
|
parentTxid := chainhash.Hash(pset.Inputs[0].PreviousTxid).String()
|
||||||
|
|
||||||
return tree.Node{
|
return Node{
|
||||||
Txid: txid,
|
Txid: txid,
|
||||||
Tx: tx,
|
Tx: tx,
|
||||||
ParentTxid: parentTxid,
|
ParentTxid: parentTxid,
|
||||||
@@ -306,7 +332,7 @@ func (n *node) getTx(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := addTaprootInput(
|
if err := addTaprootInput(
|
||||||
updater, input, tree.UnspendableKey(), inputTapTree,
|
updater, input, UnspendableKey(), inputTapTree,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -328,9 +354,9 @@ func (n *node) getTx(
|
|||||||
return pset, nil
|
return pset, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *node) createFinalCongestionTree() treeFactory {
|
func (n *node) createFinalCongestionTree() TreeFactory {
|
||||||
return func(poolTxInput psetv2.InputArgs) (tree.CongestionTree, error) {
|
return func(poolTxInput psetv2.InputArgs) (CongestionTree, error) {
|
||||||
congestionTree := make(tree.CongestionTree, 0)
|
congestionTree := make(CongestionTree, 0)
|
||||||
|
|
||||||
_, taprootTree, err := n.getWitnessData()
|
_, taprootTree, err := n.getWitnessData()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -346,7 +372,7 @@ func (n *node) createFinalCongestionTree() treeFactory {
|
|||||||
nextInputsArgs := make([]psetv2.InputArgs, 0)
|
nextInputsArgs := make([]psetv2.InputArgs, 0)
|
||||||
nextTaprootTrees := make([]*taproot.IndexedElementsTapScriptTree, 0)
|
nextTaprootTrees := make([]*taproot.IndexedElementsTapScriptTree, 0)
|
||||||
|
|
||||||
treeLevel := make([]tree.Node, 0)
|
treeLevel := make([]Node, 0)
|
||||||
|
|
||||||
for i, node := range nodes {
|
for i, node := range nodes {
|
||||||
treeNode, err := node.getTreeNode(ins[i], inTrees[i])
|
treeNode, err := node.getTreeNode(ins[i], inTrees[i])
|
||||||
@@ -385,38 +411,8 @@ func (n *node) createFinalCongestionTree() treeFactory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func craftCongestionTree(
|
|
||||||
asset string, aspPublicKey *secp256k1.PublicKey,
|
|
||||||
payments []domain.Payment, feeSatsPerNode uint64, roundLifetime int64, exitDelay int64,
|
|
||||||
) (
|
|
||||||
buildCongestionTree treeFactory,
|
|
||||||
sharedOutputScript []byte, sharedOutputAmount uint64, err error,
|
|
||||||
) {
|
|
||||||
receivers := getOffchainReceivers(payments)
|
|
||||||
root, err := createPartialCongestionTree(
|
|
||||||
receivers, aspPublicKey, asset, feeSatsPerNode, roundLifetime, exitDelay,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
taprootKey, _, err := root.getWitnessData()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
sharedOutputScript, err = taprootOutputScript(taprootKey)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
sharedOutputAmount = root.getAmount() + root.feeSats
|
|
||||||
buildCongestionTree = root.createFinalCongestionTree()
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func createPartialCongestionTree(
|
func createPartialCongestionTree(
|
||||||
receivers []domain.Receiver,
|
receivers []Receiver,
|
||||||
aspPublicKey *secp256k1.PublicKey,
|
aspPublicKey *secp256k1.PublicKey,
|
||||||
asset string,
|
asset string,
|
||||||
feeSatsPerNode uint64,
|
feeSatsPerNode uint64,
|
||||||
@@ -431,7 +427,7 @@ func createPartialCongestionTree(
|
|||||||
for _, r := range receivers {
|
for _, r := range receivers {
|
||||||
leafNode := &node{
|
leafNode := &node{
|
||||||
sweepKey: aspPublicKey,
|
sweepKey: aspPublicKey,
|
||||||
receivers: []domain.Receiver{r},
|
receivers: []Receiver{r},
|
||||||
asset: asset,
|
asset: asset,
|
||||||
feeSats: feeSatsPerNode,
|
feeSats: feeSatsPerNode,
|
||||||
roundLifetime: roundLifetime,
|
roundLifetime: roundLifetime,
|
||||||
@@ -478,3 +474,45 @@ func createUpperLevel(nodes []*node) ([]*node, error) {
|
|||||||
}
|
}
|
||||||
return pairs, nil
|
return pairs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func taprootOutputScript(taprootKey *secp256k1.PublicKey) ([]byte, error) {
|
||||||
|
return txscript.NewScriptBuilder().AddOp(txscript.OP_1).AddData(schnorr.SerializePubKey(taprootKey)).Script()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPsetId(pset *psetv2.Pset) (string, error) {
|
||||||
|
utx, err := pset.UnsignedTx()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
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(
|
||||||
|
updater *psetv2.Updater,
|
||||||
|
input psetv2.InputArgs,
|
||||||
|
internalTaprootKey *secp256k1.PublicKey,
|
||||||
|
taprootTree *taproot.IndexedElementsTapScriptTree,
|
||||||
|
) error {
|
||||||
|
if err := updater.AddInputs([]psetv2.InputArgs{input}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := updater.AddInTapInternalKey(0, schnorr.SerializePubKey(internalTaprootKey)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, proof := range taprootTree.LeafMerkleProofs {
|
||||||
|
controlBlock := proof.ToControlBlock(internalTaprootKey)
|
||||||
|
|
||||||
|
if err := updater.AddInTapLeafScript(0, psetv2.TapLeafScript{
|
||||||
|
TapElementsLeaf: taproot.NewBaseTapElementsLeaf(proof.Script),
|
||||||
|
ControlBlock: controlBlock,
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
10
common/tree/type.go
Normal file
10
common/tree/type.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package tree
|
||||||
|
|
||||||
|
import "github.com/vulpemventures/go-elements/psetv2"
|
||||||
|
|
||||||
|
type TreeFactory func(outpoint psetv2.InputArgs) (CongestionTree, error)
|
||||||
|
|
||||||
|
type Receiver struct {
|
||||||
|
Pubkey string
|
||||||
|
Amount uint64
|
||||||
|
}
|
||||||
@@ -236,7 +236,7 @@ func validateNodeTransaction(
|
|||||||
|
|
||||||
switch c := close.(type) {
|
switch c := close.(type) {
|
||||||
case *CSVSigClosure:
|
case *CSVSigClosure:
|
||||||
isASP := c.Pubkey.IsEqual(expectedPublicKeyASP)
|
isASP := bytes.Equal(schnorr.SerializePubKey(c.Pubkey), schnorr.SerializePubKey(expectedPublicKeyASP))
|
||||||
isSweepDelay := int64(c.Seconds) == expectedSequenceSeconds
|
isSweepDelay := int64(c.Seconds) == expectedSequenceSeconds
|
||||||
|
|
||||||
if isASP && !isSweepDelay {
|
if isASP && !isSweepDelay {
|
||||||
|
|||||||
@@ -73,22 +73,34 @@ This will add a `state.json` file to the following directory:
|
|||||||
```bash
|
```bash
|
||||||
$ export ARK_WALLET_DATADIR=path/to/custom
|
$ export ARK_WALLET_DATADIR=path/to/custom
|
||||||
$ ark init --password <password> --ark-url localhost:6000
|
$ ark init --password <password> --ark-url localhost:6000
|
||||||
|
```
|
||||||
|
|
||||||
Add funds to the ark wallet:
|
Add funds to the ark wallet:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ ark faucet
|
$ ark receive
|
||||||
# ark now has 10000 sats on its offchain balance
|
{
|
||||||
|
"offchain_address": <address starting with "tark1q...">,
|
||||||
|
"onchain_address": <address starting with "tex1q...">
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Fund the `onchain_address` with https://liquidtestnet.com/faucet.
|
||||||
|
|
||||||
|
Onboard the ark:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ ark onboard --amount 21000
|
||||||
|
```
|
||||||
|
|
||||||
|
After confirmation, ark wallet will be funded and ready to spend offchain.
|
||||||
|
|
||||||
In **another tab**, setup another ark wallet with:
|
In **another tab**, setup another ark wallet with:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ export ARK_WALLET_DATADIR=./datadir
|
$ export ARK_WALLET_DATADIR=./datadir
|
||||||
$ alias ark2=$(pwd)/build/ark-cli-<os>-<arch>
|
$ alias ark2=$(pwd)/build/ark-cli-<os>-<arch>
|
||||||
$ ark2 init --password <password> --ark-url localhost:6000
|
$ ark2 init --password <password> --ark-url localhost:6000
|
||||||
$ ark2 faucet
|
|
||||||
# ark2 now has 10000 sats on ark
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Note:** `ark2` should always run in the second tab.
|
**Note:** `ark2` should always run in the second tab.
|
||||||
@@ -98,7 +110,7 @@ $ ark2 faucet
|
|||||||
You can now make ark payments between the 2 ark wallets:
|
You can now make ark payments between the 2 ark wallets:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ ark receive
|
$ ark2 receive
|
||||||
{
|
{
|
||||||
"offchain_address": <address starting with "tark1q...">,
|
"offchain_address": <address starting with "tark1q...">,
|
||||||
"onchain_address": <address starting with "tex1q...">,
|
"onchain_address": <address starting with "tex1q...">,
|
||||||
@@ -109,7 +121,7 @@ $ ark receive
|
|||||||
```
|
```
|
||||||
|
|
||||||
```
|
```
|
||||||
$ ark2 send --to <offchain_address> --amount 2100
|
$ ark send --to <ark2 offchain address> --amount 2100
|
||||||
```
|
```
|
||||||
|
|
||||||
Both balances should reflect the payment:
|
Both balances should reflect the payment:
|
||||||
@@ -117,15 +129,15 @@ Both balances should reflect the payment:
|
|||||||
```
|
```
|
||||||
$ ark balance
|
$ ark balance
|
||||||
{
|
{
|
||||||
"offchain_balance": 12100,
|
"offchain_balance": 18900,
|
||||||
"onchain_balance": 0
|
"onchain_balance": 78872
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
```
|
```
|
||||||
$ ark2 balance
|
$ ark2 balance
|
||||||
{
|
{
|
||||||
"offchain_balance": 7900,
|
"offchain_balance": 2100,
|
||||||
"onchain_balance": 0
|
"onchain_balance": 0
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -47,36 +47,6 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/v1/faucet/{address}": {
|
|
||||||
"post": {
|
|
||||||
"operationId": "ArkService_Faucet",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "A successful response.",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/v1FaucetResponse"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"default": {
|
|
||||||
"description": "An unexpected error response.",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/rpcStatus"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "address",
|
|
||||||
"in": "path",
|
|
||||||
"required": true,
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"ArkService"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/v1/info": {
|
"/v1/info": {
|
||||||
"get": {
|
"get": {
|
||||||
"operationId": "ArkService_GetInfo",
|
"operationId": "ArkService_GetInfo",
|
||||||
@@ -99,6 +69,38 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/v1/onboard": {
|
||||||
|
"post": {
|
||||||
|
"operationId": "ArkService_Onboard",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A successful response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/v1OnboardResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"description": "An unexpected error response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/rpcStatus"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/v1OnboardRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"ArkService"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/v1/payment/claim": {
|
"/v1/payment/claim": {
|
||||||
"post": {
|
"post": {
|
||||||
"operationId": "ArkService_ClaimPayment",
|
"operationId": "ArkService_ClaimPayment",
|
||||||
@@ -335,9 +337,6 @@
|
|||||||
"v1ClaimPaymentResponse": {
|
"v1ClaimPaymentResponse": {
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"v1FaucetResponse": {
|
|
||||||
"type": "object"
|
|
||||||
},
|
|
||||||
"v1FinalizePaymentRequest": {
|
"v1FinalizePaymentRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -429,6 +428,23 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"v1OnboardRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"boardingTx": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"congestionTree": {
|
||||||
|
"$ref": "#/definitions/v1Tree"
|
||||||
|
},
|
||||||
|
"userPubkey": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"v1OnboardResponse": {
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
"v1Output": {
|
"v1Output": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -38,11 +38,6 @@ service ArkService {
|
|||||||
get: "/v1/ping/{payment_id}"
|
get: "/v1/ping/{payment_id}"
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
rpc Faucet(FaucetRequest) returns (FaucetResponse) {
|
|
||||||
option (google.api.http) = {
|
|
||||||
post: "/v1/faucet/{address}"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
rpc ListVtxos(ListVtxosRequest) returns (ListVtxosResponse) {
|
rpc ListVtxos(ListVtxosRequest) returns (ListVtxosResponse) {
|
||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
get: "/v1/vtxos/{address}"
|
get: "/v1/vtxos/{address}"
|
||||||
@@ -53,6 +48,21 @@ service ArkService {
|
|||||||
get: "/v1/info"
|
get: "/v1/info"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
rpc Onboard(OnboardRequest) returns (OnboardResponse) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
post: "/v1/onboard"
|
||||||
|
body: "*"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message OnboardRequest {
|
||||||
|
string boarding_tx = 1;
|
||||||
|
Tree congestion_tree = 2;
|
||||||
|
string user_pubkey = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message OnboardResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
message RegisterPaymentRequest {
|
message RegisterPaymentRequest {
|
||||||
@@ -151,12 +161,6 @@ message PingRequest {
|
|||||||
|
|
||||||
message PingResponse {}
|
message PingResponse {}
|
||||||
|
|
||||||
message FaucetRequest {
|
|
||||||
string address = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message FaucetResponse {}
|
|
||||||
|
|
||||||
message ListVtxosRequest {
|
message ListVtxosRequest {
|
||||||
string address = 1;
|
string address = 1;
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -230,58 +230,6 @@ func local_request_ArkService_Ping_0(ctx context.Context, marshaler runtime.Mars
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func request_ArkService_Faucet_0(ctx context.Context, marshaler runtime.Marshaler, client ArkServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
|
||||||
var protoReq FaucetRequest
|
|
||||||
var metadata runtime.ServerMetadata
|
|
||||||
|
|
||||||
var (
|
|
||||||
val string
|
|
||||||
ok bool
|
|
||||||
err error
|
|
||||||
_ = err
|
|
||||||
)
|
|
||||||
|
|
||||||
val, ok = pathParams["address"]
|
|
||||||
if !ok {
|
|
||||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "address")
|
|
||||||
}
|
|
||||||
|
|
||||||
protoReq.Address, err = runtime.String(val)
|
|
||||||
if err != nil {
|
|
||||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "address", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
msg, err := client.Faucet(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
|
||||||
return msg, metadata, err
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func local_request_ArkService_Faucet_0(ctx context.Context, marshaler runtime.Marshaler, server ArkServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
|
||||||
var protoReq FaucetRequest
|
|
||||||
var metadata runtime.ServerMetadata
|
|
||||||
|
|
||||||
var (
|
|
||||||
val string
|
|
||||||
ok bool
|
|
||||||
err error
|
|
||||||
_ = err
|
|
||||||
)
|
|
||||||
|
|
||||||
val, ok = pathParams["address"]
|
|
||||||
if !ok {
|
|
||||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "address")
|
|
||||||
}
|
|
||||||
|
|
||||||
protoReq.Address, err = runtime.String(val)
|
|
||||||
if err != nil {
|
|
||||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "address", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
msg, err := server.Faucet(ctx, &protoReq)
|
|
||||||
return msg, metadata, err
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func request_ArkService_ListVtxos_0(ctx context.Context, marshaler runtime.Marshaler, client ArkServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
func request_ArkService_ListVtxos_0(ctx context.Context, marshaler runtime.Marshaler, client ArkServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||||
var protoReq ListVtxosRequest
|
var protoReq ListVtxosRequest
|
||||||
var metadata runtime.ServerMetadata
|
var metadata runtime.ServerMetadata
|
||||||
@@ -352,6 +300,32 @@ func local_request_ArkService_GetInfo_0(ctx context.Context, marshaler runtime.M
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func request_ArkService_Onboard_0(ctx context.Context, marshaler runtime.Marshaler, client ArkServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||||
|
var protoReq OnboardRequest
|
||||||
|
var metadata runtime.ServerMetadata
|
||||||
|
|
||||||
|
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := client.Onboard(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||||
|
return msg, metadata, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func local_request_ArkService_Onboard_0(ctx context.Context, marshaler runtime.Marshaler, server ArkServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||||
|
var protoReq OnboardRequest
|
||||||
|
var metadata runtime.ServerMetadata
|
||||||
|
|
||||||
|
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := server.Onboard(ctx, &protoReq)
|
||||||
|
return msg, metadata, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// RegisterArkServiceHandlerServer registers the http handlers for service ArkService to "mux".
|
// RegisterArkServiceHandlerServer registers the http handlers for service ArkService to "mux".
|
||||||
// UnaryRPC :call ArkServiceServer directly.
|
// UnaryRPC :call ArkServiceServer directly.
|
||||||
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
|
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
|
||||||
@@ -490,31 +464,6 @@ func RegisterArkServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux,
|
|||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
mux.Handle("POST", pattern_ArkService_Faucet_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
|
||||||
ctx, cancel := context.WithCancel(req.Context())
|
|
||||||
defer cancel()
|
|
||||||
var stream runtime.ServerTransportStream
|
|
||||||
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
|
||||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
|
||||||
var err error
|
|
||||||
var annotatedContext context.Context
|
|
||||||
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/ark.v1.ArkService/Faucet", runtime.WithHTTPPathPattern("/v1/faucet/{address}"))
|
|
||||||
if err != nil {
|
|
||||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
resp, md, err := local_request_ArkService_Faucet_0(annotatedContext, inboundMarshaler, server, req, pathParams)
|
|
||||||
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
|
||||||
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
|
|
||||||
if err != nil {
|
|
||||||
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
forward_ArkService_Faucet_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
mux.Handle("GET", pattern_ArkService_ListVtxos_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
mux.Handle("GET", pattern_ArkService_ListVtxos_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
ctx, cancel := context.WithCancel(req.Context())
|
ctx, cancel := context.WithCancel(req.Context())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@@ -565,6 +514,31 @@ func RegisterArkServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux,
|
|||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
mux.Handle("POST", pattern_ArkService_Onboard_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
|
ctx, cancel := context.WithCancel(req.Context())
|
||||||
|
defer cancel()
|
||||||
|
var stream runtime.ServerTransportStream
|
||||||
|
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
|
||||||
|
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||||
|
var err error
|
||||||
|
var annotatedContext context.Context
|
||||||
|
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/ark.v1.ArkService/Onboard", runtime.WithHTTPPathPattern("/v1/onboard"))
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, md, err := local_request_ArkService_Onboard_0(annotatedContext, inboundMarshaler, server, req, pathParams)
|
||||||
|
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||||
|
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
forward_ArkService_Onboard_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -738,28 +712,6 @@ func RegisterArkServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux,
|
|||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
mux.Handle("POST", pattern_ArkService_Faucet_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
|
||||||
ctx, cancel := context.WithCancel(req.Context())
|
|
||||||
defer cancel()
|
|
||||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
|
||||||
var err error
|
|
||||||
var annotatedContext context.Context
|
|
||||||
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/ark.v1.ArkService/Faucet", runtime.WithHTTPPathPattern("/v1/faucet/{address}"))
|
|
||||||
if err != nil {
|
|
||||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
resp, md, err := request_ArkService_Faucet_0(annotatedContext, inboundMarshaler, client, req, pathParams)
|
|
||||||
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
|
|
||||||
if err != nil {
|
|
||||||
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
forward_ArkService_Faucet_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
mux.Handle("GET", pattern_ArkService_ListVtxos_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
mux.Handle("GET", pattern_ArkService_ListVtxos_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
ctx, cancel := context.WithCancel(req.Context())
|
ctx, cancel := context.WithCancel(req.Context())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@@ -804,6 +756,28 @@ func RegisterArkServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux,
|
|||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
mux.Handle("POST", pattern_ArkService_Onboard_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
|
ctx, cancel := context.WithCancel(req.Context())
|
||||||
|
defer cancel()
|
||||||
|
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||||
|
var err error
|
||||||
|
var annotatedContext context.Context
|
||||||
|
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/ark.v1.ArkService/Onboard", runtime.WithHTTPPathPattern("/v1/onboard"))
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, md, err := request_ArkService_Onboard_0(annotatedContext, inboundMarshaler, client, req, pathParams)
|
||||||
|
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
forward_ArkService_Onboard_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -820,11 +794,11 @@ var (
|
|||||||
|
|
||||||
pattern_ArkService_Ping_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "ping", "payment_id"}, ""))
|
pattern_ArkService_Ping_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "ping", "payment_id"}, ""))
|
||||||
|
|
||||||
pattern_ArkService_Faucet_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "faucet", "address"}, ""))
|
|
||||||
|
|
||||||
pattern_ArkService_ListVtxos_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "vtxos", "address"}, ""))
|
pattern_ArkService_ListVtxos_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "vtxos", "address"}, ""))
|
||||||
|
|
||||||
pattern_ArkService_GetInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "info"}, ""))
|
pattern_ArkService_GetInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "info"}, ""))
|
||||||
|
|
||||||
|
pattern_ArkService_Onboard_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "onboard"}, ""))
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -840,9 +814,9 @@ var (
|
|||||||
|
|
||||||
forward_ArkService_Ping_0 = runtime.ForwardResponseMessage
|
forward_ArkService_Ping_0 = runtime.ForwardResponseMessage
|
||||||
|
|
||||||
forward_ArkService_Faucet_0 = runtime.ForwardResponseMessage
|
|
||||||
|
|
||||||
forward_ArkService_ListVtxos_0 = runtime.ForwardResponseMessage
|
forward_ArkService_ListVtxos_0 = runtime.ForwardResponseMessage
|
||||||
|
|
||||||
forward_ArkService_GetInfo_0 = runtime.ForwardResponseMessage
|
forward_ArkService_GetInfo_0 = runtime.ForwardResponseMessage
|
||||||
|
|
||||||
|
forward_ArkService_Onboard_0 = runtime.ForwardResponseMessage
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -24,9 +24,9 @@ type ArkServiceClient interface {
|
|||||||
GetRound(ctx context.Context, in *GetRoundRequest, opts ...grpc.CallOption) (*GetRoundResponse, error)
|
GetRound(ctx context.Context, in *GetRoundRequest, opts ...grpc.CallOption) (*GetRoundResponse, error)
|
||||||
GetEventStream(ctx context.Context, in *GetEventStreamRequest, opts ...grpc.CallOption) (ArkService_GetEventStreamClient, error)
|
GetEventStream(ctx context.Context, in *GetEventStreamRequest, opts ...grpc.CallOption) (ArkService_GetEventStreamClient, error)
|
||||||
Ping(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*PingResponse, error)
|
Ping(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*PingResponse, error)
|
||||||
Faucet(ctx context.Context, in *FaucetRequest, opts ...grpc.CallOption) (*FaucetResponse, error)
|
|
||||||
ListVtxos(ctx context.Context, in *ListVtxosRequest, opts ...grpc.CallOption) (*ListVtxosResponse, error)
|
ListVtxos(ctx context.Context, in *ListVtxosRequest, opts ...grpc.CallOption) (*ListVtxosResponse, error)
|
||||||
GetInfo(ctx context.Context, in *GetInfoRequest, opts ...grpc.CallOption) (*GetInfoResponse, error)
|
GetInfo(ctx context.Context, in *GetInfoRequest, opts ...grpc.CallOption) (*GetInfoResponse, error)
|
||||||
|
Onboard(ctx context.Context, in *OnboardRequest, opts ...grpc.CallOption) (*OnboardResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type arkServiceClient struct {
|
type arkServiceClient struct {
|
||||||
@@ -114,15 +114,6 @@ func (c *arkServiceClient) Ping(ctx context.Context, in *PingRequest, opts ...gr
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *arkServiceClient) Faucet(ctx context.Context, in *FaucetRequest, opts ...grpc.CallOption) (*FaucetResponse, error) {
|
|
||||||
out := new(FaucetResponse)
|
|
||||||
err := c.cc.Invoke(ctx, "/ark.v1.ArkService/Faucet", in, out, opts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *arkServiceClient) ListVtxos(ctx context.Context, in *ListVtxosRequest, opts ...grpc.CallOption) (*ListVtxosResponse, error) {
|
func (c *arkServiceClient) ListVtxos(ctx context.Context, in *ListVtxosRequest, opts ...grpc.CallOption) (*ListVtxosResponse, error) {
|
||||||
out := new(ListVtxosResponse)
|
out := new(ListVtxosResponse)
|
||||||
err := c.cc.Invoke(ctx, "/ark.v1.ArkService/ListVtxos", in, out, opts...)
|
err := c.cc.Invoke(ctx, "/ark.v1.ArkService/ListVtxos", in, out, opts...)
|
||||||
@@ -141,6 +132,15 @@ func (c *arkServiceClient) GetInfo(ctx context.Context, in *GetInfoRequest, opts
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *arkServiceClient) Onboard(ctx context.Context, in *OnboardRequest, opts ...grpc.CallOption) (*OnboardResponse, error) {
|
||||||
|
out := new(OnboardResponse)
|
||||||
|
err := c.cc.Invoke(ctx, "/ark.v1.ArkService/Onboard", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ArkServiceServer is the server API for ArkService service.
|
// ArkServiceServer is the server API for ArkService service.
|
||||||
// All implementations should embed UnimplementedArkServiceServer
|
// All implementations should embed UnimplementedArkServiceServer
|
||||||
// for forward compatibility
|
// for forward compatibility
|
||||||
@@ -151,9 +151,9 @@ type ArkServiceServer interface {
|
|||||||
GetRound(context.Context, *GetRoundRequest) (*GetRoundResponse, error)
|
GetRound(context.Context, *GetRoundRequest) (*GetRoundResponse, error)
|
||||||
GetEventStream(*GetEventStreamRequest, ArkService_GetEventStreamServer) error
|
GetEventStream(*GetEventStreamRequest, ArkService_GetEventStreamServer) error
|
||||||
Ping(context.Context, *PingRequest) (*PingResponse, error)
|
Ping(context.Context, *PingRequest) (*PingResponse, error)
|
||||||
Faucet(context.Context, *FaucetRequest) (*FaucetResponse, error)
|
|
||||||
ListVtxos(context.Context, *ListVtxosRequest) (*ListVtxosResponse, error)
|
ListVtxos(context.Context, *ListVtxosRequest) (*ListVtxosResponse, error)
|
||||||
GetInfo(context.Context, *GetInfoRequest) (*GetInfoResponse, error)
|
GetInfo(context.Context, *GetInfoRequest) (*GetInfoResponse, error)
|
||||||
|
Onboard(context.Context, *OnboardRequest) (*OnboardResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnimplementedArkServiceServer should be embedded to have forward compatible implementations.
|
// UnimplementedArkServiceServer should be embedded to have forward compatible implementations.
|
||||||
@@ -178,15 +178,15 @@ func (UnimplementedArkServiceServer) GetEventStream(*GetEventStreamRequest, ArkS
|
|||||||
func (UnimplementedArkServiceServer) Ping(context.Context, *PingRequest) (*PingResponse, error) {
|
func (UnimplementedArkServiceServer) Ping(context.Context, *PingRequest) (*PingResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method Ping not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method Ping not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedArkServiceServer) Faucet(context.Context, *FaucetRequest) (*FaucetResponse, error) {
|
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method Faucet not implemented")
|
|
||||||
}
|
|
||||||
func (UnimplementedArkServiceServer) ListVtxos(context.Context, *ListVtxosRequest) (*ListVtxosResponse, error) {
|
func (UnimplementedArkServiceServer) ListVtxos(context.Context, *ListVtxosRequest) (*ListVtxosResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method ListVtxos not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method ListVtxos not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedArkServiceServer) GetInfo(context.Context, *GetInfoRequest) (*GetInfoResponse, error) {
|
func (UnimplementedArkServiceServer) GetInfo(context.Context, *GetInfoRequest) (*GetInfoResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GetInfo not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method GetInfo not implemented")
|
||||||
}
|
}
|
||||||
|
func (UnimplementedArkServiceServer) Onboard(context.Context, *OnboardRequest) (*OnboardResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method Onboard not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
// UnsafeArkServiceServer may be embedded to opt out of forward compatibility for this service.
|
// UnsafeArkServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||||
// Use of this interface is not recommended, as added methods to ArkServiceServer will
|
// Use of this interface is not recommended, as added methods to ArkServiceServer will
|
||||||
@@ -310,24 +310,6 @@ func _ArkService_Ping_Handler(srv interface{}, ctx context.Context, dec func(int
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
func _ArkService_Faucet_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
|
||||||
in := new(FaucetRequest)
|
|
||||||
if err := dec(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if interceptor == nil {
|
|
||||||
return srv.(ArkServiceServer).Faucet(ctx, in)
|
|
||||||
}
|
|
||||||
info := &grpc.UnaryServerInfo{
|
|
||||||
Server: srv,
|
|
||||||
FullMethod: "/ark.v1.ArkService/Faucet",
|
|
||||||
}
|
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
|
||||||
return srv.(ArkServiceServer).Faucet(ctx, req.(*FaucetRequest))
|
|
||||||
}
|
|
||||||
return interceptor(ctx, in, info, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _ArkService_ListVtxos_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
func _ArkService_ListVtxos_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
in := new(ListVtxosRequest)
|
in := new(ListVtxosRequest)
|
||||||
if err := dec(in); err != nil {
|
if err := dec(in); err != nil {
|
||||||
@@ -364,6 +346,24 @@ func _ArkService_GetInfo_Handler(srv interface{}, ctx context.Context, dec func(
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _ArkService_Onboard_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(OnboardRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(ArkServiceServer).Onboard(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/ark.v1.ArkService/Onboard",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(ArkServiceServer).Onboard(ctx, req.(*OnboardRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
// ArkService_ServiceDesc is the grpc.ServiceDesc for ArkService service.
|
// ArkService_ServiceDesc is the grpc.ServiceDesc for ArkService service.
|
||||||
// It's only intended for direct use with grpc.RegisterService,
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
// and not to be introspected or modified (even as a copy)
|
// and not to be introspected or modified (even as a copy)
|
||||||
@@ -391,10 +391,6 @@ var ArkService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "Ping",
|
MethodName: "Ping",
|
||||||
Handler: _ArkService_Ping_Handler,
|
Handler: _ArkService_Ping_Handler,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
MethodName: "Faucet",
|
|
||||||
Handler: _ArkService_Faucet_Handler,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
MethodName: "ListVtxos",
|
MethodName: "ListVtxos",
|
||||||
Handler: _ArkService_ListVtxos_Handler,
|
Handler: _ArkService_ListVtxos_Handler,
|
||||||
@@ -403,6 +399,10 @@ var ArkService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "GetInfo",
|
MethodName: "GetInfo",
|
||||||
Handler: _ArkService_GetInfo_Handler,
|
Handler: _ArkService_GetInfo_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "Onboard",
|
||||||
|
Handler: _ArkService_Onboard_Handler,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Streams: []grpc.StreamDesc{
|
Streams: []grpc.StreamDesc{
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
oceanwallet "github.com/ark-network/ark/internal/infrastructure/ocean-wallet"
|
oceanwallet "github.com/ark-network/ark/internal/infrastructure/ocean-wallet"
|
||||||
scheduler "github.com/ark-network/ark/internal/infrastructure/scheduler/gocron"
|
scheduler "github.com/ark-network/ark/internal/infrastructure/scheduler/gocron"
|
||||||
txbuilder "github.com/ark-network/ark/internal/infrastructure/tx-builder/covenant"
|
txbuilder "github.com/ark-network/ark/internal/infrastructure/tx-builder/covenant"
|
||||||
txbuilderdummy "github.com/ark-network/ark/internal/infrastructure/tx-builder/dummy"
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/vulpemventures/go-elements/network"
|
"github.com/vulpemventures/go-elements/network"
|
||||||
)
|
)
|
||||||
@@ -24,7 +23,6 @@ var (
|
|||||||
"gocron": {},
|
"gocron": {},
|
||||||
}
|
}
|
||||||
supportedTxBuilders = supportedType{
|
supportedTxBuilders = supportedType{
|
||||||
"dummy": {},
|
|
||||||
"covenant": {},
|
"covenant": {},
|
||||||
}
|
}
|
||||||
supportedScanners = supportedType{
|
supportedScanners = supportedType{
|
||||||
@@ -167,8 +165,6 @@ func (c *Config) txBuilderService() error {
|
|||||||
net := c.mainChain()
|
net := c.mainChain()
|
||||||
|
|
||||||
switch c.TxBuilderType {
|
switch c.TxBuilderType {
|
||||||
case "dummy":
|
|
||||||
svc = txbuilderdummy.NewTxBuilder(c.wallet, net)
|
|
||||||
case "covenant":
|
case "covenant":
|
||||||
svc = txbuilder.NewTxBuilder(c.wallet, net, c.RoundLifetime, c.ExitDelay)
|
svc = txbuilder.NewTxBuilder(c.wallet, net, c.RoundLifetime, c.ExitDelay)
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ark-network/ark/common"
|
"github.com/ark-network/ark/common"
|
||||||
|
"github.com/ark-network/ark/common/tree"
|
||||||
"github.com/ark-network/ark/internal/core/domain"
|
"github.com/ark-network/ark/internal/core/domain"
|
||||||
"github.com/ark-network/ark/internal/core/ports"
|
"github.com/ark-network/ark/internal/core/ports"
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
@@ -20,10 +21,6 @@ import (
|
|||||||
var (
|
var (
|
||||||
paymentsThreshold = int64(128)
|
paymentsThreshold = int64(128)
|
||||||
dustAmount = uint64(450)
|
dustAmount = uint64(450)
|
||||||
faucetVtxo = domain.VtxoKey{
|
|
||||||
Txid: "0000000000000000000000000000000000000000000000000000000000000000",
|
|
||||||
VOut: 0,
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Service interface {
|
type Service interface {
|
||||||
@@ -32,12 +29,12 @@ type Service interface {
|
|||||||
SpendVtxos(ctx context.Context, inputs []domain.VtxoKey) (string, error)
|
SpendVtxos(ctx context.Context, inputs []domain.VtxoKey) (string, error)
|
||||||
ClaimVtxos(ctx context.Context, creds string, receivers []domain.Receiver) error
|
ClaimVtxos(ctx context.Context, creds string, receivers []domain.Receiver) error
|
||||||
SignVtxos(ctx context.Context, forfeitTxs []string) error
|
SignVtxos(ctx context.Context, forfeitTxs []string) error
|
||||||
FaucetVtxos(ctx context.Context, pubkey *secp256k1.PublicKey) error
|
|
||||||
GetRoundByTxid(ctx context.Context, poolTxid string) (*domain.Round, error)
|
GetRoundByTxid(ctx context.Context, poolTxid string) (*domain.Round, error)
|
||||||
GetEventsChannel(ctx context.Context) <-chan domain.RoundEvent
|
GetEventsChannel(ctx context.Context) <-chan domain.RoundEvent
|
||||||
UpdatePaymentStatus(ctx context.Context, id string) error
|
UpdatePaymentStatus(ctx context.Context, id string) error
|
||||||
ListVtxos(ctx context.Context, pubkey *secp256k1.PublicKey) ([]domain.Vtxo, error)
|
ListVtxos(ctx context.Context, pubkey *secp256k1.PublicKey) ([]domain.Vtxo, error)
|
||||||
GetInfo(ctx context.Context) (string, int64, int64, error)
|
GetInfo(ctx context.Context) (string, int64, int64, error)
|
||||||
|
Onboard(ctx context.Context, boardingTx string, congestionTree tree.CongestionTree, userPubkey *secp256k1.PublicKey) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type service struct {
|
type service struct {
|
||||||
@@ -88,8 +85,9 @@ func NewService(
|
|||||||
}
|
}
|
||||||
repoManager.RegisterEventsHandler(
|
repoManager.RegisterEventsHandler(
|
||||||
func(round *domain.Round) {
|
func(round *domain.Round) {
|
||||||
svc.updateProjectionStore(round)
|
go svc.updateVtxoSet(round)
|
||||||
svc.propagateEvents(round)
|
go svc.propagateEvents(round)
|
||||||
|
go svc.scheduleSweepVtxosForRound(round)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -165,43 +163,6 @@ func (s *service) UpdatePaymentStatus(_ context.Context, id string) error {
|
|||||||
return s.paymentRequests.updatePingTimestamp(id)
|
return s.paymentRequests.updatePingTimestamp(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) FaucetVtxos(ctx context.Context, userPubkey *secp256k1.PublicKey) error {
|
|
||||||
pubkey := hex.EncodeToString(userPubkey.SerializeCompressed())
|
|
||||||
|
|
||||||
payment, err := domain.NewPayment([]domain.Vtxo{
|
|
||||||
{
|
|
||||||
VtxoKey: faucetVtxo,
|
|
||||||
Receiver: domain.Receiver{
|
|
||||||
Pubkey: pubkey,
|
|
||||||
Amount: 10000,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := payment.AddReceivers([]domain.Receiver{
|
|
||||||
{Pubkey: pubkey, Amount: 1000},
|
|
||||||
{Pubkey: pubkey, Amount: 1000},
|
|
||||||
{Pubkey: pubkey, Amount: 1000},
|
|
||||||
{Pubkey: pubkey, Amount: 1000},
|
|
||||||
{Pubkey: pubkey, Amount: 1000},
|
|
||||||
{Pubkey: pubkey, Amount: 1000},
|
|
||||||
{Pubkey: pubkey, Amount: 1000},
|
|
||||||
{Pubkey: pubkey, Amount: 1000},
|
|
||||||
{Pubkey: pubkey, Amount: 1000},
|
|
||||||
{Pubkey: pubkey, Amount: 1000},
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.paymentRequests.push(*payment); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return s.paymentRequests.updatePingTimestamp(payment.Id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *service) SignVtxos(ctx context.Context, forfeitTxs []string) error {
|
func (s *service) SignVtxos(ctx context.Context, forfeitTxs []string) error {
|
||||||
return s.forfeitTxs.sign(forfeitTxs)
|
return s.forfeitTxs.sign(forfeitTxs)
|
||||||
}
|
}
|
||||||
@@ -223,6 +184,40 @@ func (s *service) GetInfo(ctx context.Context) (string, int64, int64, error) {
|
|||||||
return hex.EncodeToString(s.pubkey.SerializeCompressed()), s.roundLifetime, s.exitDelay, nil
|
return hex.EncodeToString(s.pubkey.SerializeCompressed()), s.roundLifetime, s.exitDelay, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *service) Onboard(
|
||||||
|
ctx context.Context, boardingTx string,
|
||||||
|
congestionTree tree.CongestionTree, userPubkey *secp256k1.PublicKey,
|
||||||
|
) error {
|
||||||
|
if err := tree.ValidateCongestionTree(
|
||||||
|
congestionTree, boardingTx, s.pubkey, s.roundLifetime,
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ptx, err := psetv2.NewPsetFromBase64(boardingTx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to parse boarding tx: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
utx, _ := ptx.UnsignedTx()
|
||||||
|
txid := utx.TxHash().String()
|
||||||
|
|
||||||
|
isConfirmed, _, err := s.wallet.IsTransactionConfirmed(ctx, txid)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to fetch confirmation info for boaridng tx: %s", err)
|
||||||
|
}
|
||||||
|
if !isConfirmed {
|
||||||
|
return fmt.Errorf("boarding tx not confirmed yet, please retry later")
|
||||||
|
}
|
||||||
|
|
||||||
|
pubkey := hex.EncodeToString(userPubkey.SerializeCompressed())
|
||||||
|
payments := getPaymentsFromOnboarding(congestionTree, pubkey)
|
||||||
|
round := domain.NewFinalizedRound(
|
||||||
|
dustAmount, pubkey, txid, boardingTx, congestionTree, payments,
|
||||||
|
)
|
||||||
|
|
||||||
|
return s.saveEvents(ctx, round.Id, round.Events())
|
||||||
|
}
|
||||||
|
|
||||||
func (s *service) start() {
|
func (s *service) start() {
|
||||||
s.startRound()
|
s.startRound()
|
||||||
}
|
}
|
||||||
@@ -365,15 +360,6 @@ func (s *service) finalizeRound() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now().Unix()
|
|
||||||
expirationTimestamp := now + s.roundLifetime + 30 // add 30 secs to be sure that the tx is confirmed
|
|
||||||
|
|
||||||
if err := s.sweeper.schedule(expirationTimestamp, txid, round.CongestionTree); err != nil {
|
|
||||||
changes = round.Fail(fmt.Errorf("failed to schedule sweep tx: %s", err))
|
|
||||||
log.WithError(err).Warn("failed to schedule sweep tx")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
changes, _ = round.EndFinalization(forfeitTxs, txid)
|
changes, _ = round.EndFinalization(forfeitTxs, txid)
|
||||||
|
|
||||||
log.Debugf("finalized round %s with pool tx %s", round.Id, round.Txid)
|
log.Debugf("finalized round %s with pool tx %s", round.Id, round.Txid)
|
||||||
@@ -403,11 +389,13 @@ func (s *service) listenToRedemptions() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) updateProjectionStore(round *domain.Round) {
|
func (s *service) updateVtxoSet(round *domain.Round) {
|
||||||
ctx := context.Background()
|
|
||||||
lastChange := round.Events()[len(round.Events())-1]
|
|
||||||
// Update the vtxo set only after a round is finalized.
|
// Update the vtxo set only after a round is finalized.
|
||||||
if _, ok := lastChange.(domain.RoundFinalized); ok {
|
if !round.IsEnded() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
repo := s.repoManager.Vtxos()
|
repo := s.repoManager.Vtxos()
|
||||||
spentVtxos := getSpentVtxos(round.Payments)
|
spentVtxos := getSpentVtxos(round.Payments)
|
||||||
if len(spentVtxos) > 0 {
|
if len(spentVtxos) > 0 {
|
||||||
@@ -446,7 +434,6 @@ func (s *service) updateProjectionStore(round *domain.Round) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func (s *service) propagateEvents(round *domain.Round) {
|
func (s *service) propagateEvents(round *domain.Round) {
|
||||||
lastEvent := round.Events()[len(round.Events())-1]
|
lastEvent := round.Events()[len(round.Events())-1]
|
||||||
@@ -465,6 +452,23 @@ func (s *service) propagateEvents(round *domain.Round) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *service) scheduleSweepVtxosForRound(round *domain.Round) {
|
||||||
|
// Schedule the sweeping procedure only for completed round.
|
||||||
|
if !round.IsEnded() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
expirationTimestamp := time.Now().Add(
|
||||||
|
time.Duration(s.roundLifetime+30) * time.Second,
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := s.sweeper.schedule(
|
||||||
|
expirationTimestamp.Unix(), round.Txid, round.CongestionTree,
|
||||||
|
); err != nil {
|
||||||
|
log.WithError(err).Warn("failed to schedule sweep tx")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *service) getNewVtxos(round *domain.Round) []domain.Vtxo {
|
func (s *service) getNewVtxos(round *domain.Round) []domain.Vtxo {
|
||||||
leaves := round.CongestionTree.Leaves()
|
leaves := round.CongestionTree.Leaves()
|
||||||
vtxos := make([]domain.Vtxo, 0)
|
vtxos := make([]domain.Vtxo, 0)
|
||||||
@@ -590,11 +594,25 @@ func getSpentVtxos(payments map[string]domain.Payment) []domain.VtxoKey {
|
|||||||
vtxos := make([]domain.VtxoKey, 0)
|
vtxos := make([]domain.VtxoKey, 0)
|
||||||
for _, p := range payments {
|
for _, p := range payments {
|
||||||
for _, vtxo := range p.Inputs {
|
for _, vtxo := range p.Inputs {
|
||||||
if vtxo.VtxoKey == faucetVtxo {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
vtxos = append(vtxos, vtxo.VtxoKey)
|
vtxos = append(vtxos, vtxo.VtxoKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return vtxos
|
return vtxos
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getPaymentsFromOnboarding(
|
||||||
|
congestionTree tree.CongestionTree, userKey string,
|
||||||
|
) []domain.Payment {
|
||||||
|
leaves := congestionTree.Leaves()
|
||||||
|
receivers := make([]domain.Receiver, 0, len(leaves))
|
||||||
|
for _, node := range leaves {
|
||||||
|
ptx, _ := psetv2.NewPsetFromBase64(node.Tx)
|
||||||
|
receiver := domain.Receiver{
|
||||||
|
Pubkey: userKey,
|
||||||
|
Amount: ptx.Outputs[0].Value,
|
||||||
|
}
|
||||||
|
receivers = append(receivers, receiver)
|
||||||
|
}
|
||||||
|
payment := domain.NewPaymentUnsafe(nil, receivers)
|
||||||
|
return []domain.Payment{*payment}
|
||||||
|
}
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ func (s *sweeper) schedule(
|
|||||||
|
|
||||||
task := s.createTask(roundTxid, congestionTree)
|
task := s.createTask(roundTxid, congestionTree)
|
||||||
fancyTime := time.Unix(expirationTimestamp, 0).Format("2006-01-02 15:04:05")
|
fancyTime := time.Unix(expirationTimestamp, 0).Format("2006-01-02 15:04:05")
|
||||||
log.Debugf("scheduled sweep task at %s", fancyTime)
|
log.Debugf("scheduled sweep for round %s at %s", roundTxid, fancyTime)
|
||||||
if err := s.scheduler.ScheduleTaskOnce(expirationTimestamp, task); err != nil {
|
if err := s.scheduler.ScheduleTaskOnce(expirationTimestamp, task); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -290,7 +290,7 @@ func (s *sweeper) findSweepableOutputs(
|
|||||||
newNodesToCheck := make([]tree.Node, 0)
|
newNodesToCheck := make([]tree.Node, 0)
|
||||||
|
|
||||||
for _, node := range nodesToCheck {
|
for _, node := range nodesToCheck {
|
||||||
isPublished, blocktime, err := s.wallet.IsTransactionPublished(ctx, node.Txid)
|
isConfirmed, blocktime, err := s.wallet.IsTransactionConfirmed(ctx, node.Txid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -298,10 +298,10 @@ func (s *sweeper) findSweepableOutputs(
|
|||||||
var expirationTime int64
|
var expirationTime int64
|
||||||
var sweepInputs []ports.SweepInput
|
var sweepInputs []ports.SweepInput
|
||||||
|
|
||||||
if !isPublished {
|
if !isConfirmed {
|
||||||
if _, ok := blocktimeCache[node.ParentTxid]; !ok {
|
if _, ok := blocktimeCache[node.ParentTxid]; !ok {
|
||||||
isPublished, blocktime, err := s.wallet.IsTransactionPublished(ctx, node.ParentTxid)
|
isConfirmed, blocktime, err := s.wallet.IsTransactionConfirmed(ctx, node.ParentTxid)
|
||||||
if !isPublished || err != nil {
|
if !isConfirmed || err != nil {
|
||||||
return nil, fmt.Errorf("tx %s not found", node.Txid)
|
return nil, fmt.Errorf("tx %s not found", node.Txid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package application
|
package application
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -154,23 +152,13 @@ func (m *forfeitTxsMap) push(txs []string) {
|
|||||||
m.lock.Lock()
|
m.lock.Lock()
|
||||||
defer m.lock.Unlock()
|
defer m.lock.Unlock()
|
||||||
|
|
||||||
faucetTxID, _ := hex.DecodeString(faucetVtxo.Txid)
|
|
||||||
|
|
||||||
for _, tx := range txs {
|
for _, tx := range txs {
|
||||||
ptx, _ := psetv2.NewPsetFromBase64(tx)
|
ptx, _ := psetv2.NewPsetFromBase64(tx)
|
||||||
utx, _ := ptx.UnsignedTx()
|
utx, _ := ptx.UnsignedTx()
|
||||||
|
txid := utx.TxHash().String()
|
||||||
signed := false
|
signed := false
|
||||||
|
|
||||||
// find the faucet vtxos, and mark them as signed
|
m.forfeitTxs[txid] = &signedTx{tx, signed}
|
||||||
for _, input := range ptx.Inputs {
|
|
||||||
if bytes.Equal(input.PreviousTxid, faucetTxID) {
|
|
||||||
signed = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m.forfeitTxs[utx.TxHash().String()] = &signedTx{tx, signed}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,14 @@ func NewPayment(inputs []Vtxo) (*Payment, error) {
|
|||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewPaymentUnsafe(inputs []Vtxo, receivers []Receiver) *Payment {
|
||||||
|
return &Payment{
|
||||||
|
Id: uuid.New().String(),
|
||||||
|
Inputs: inputs,
|
||||||
|
Receivers: receivers,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Payment) AddReceivers(receivers []Receiver) (err error) {
|
func (p *Payment) AddReceivers(receivers []Receiver) (err error) {
|
||||||
if p.Receivers == nil {
|
if p.Receivers == nil {
|
||||||
p.Receivers = make([]Receiver, 0)
|
p.Receivers = make([]Receiver, 0)
|
||||||
|
|||||||
@@ -59,6 +59,39 @@ func NewRound(dustAmount uint64) *Round {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewFinalizedRound(
|
||||||
|
dustAmount uint64, userKey, poolTxid, poolTx string,
|
||||||
|
congestionTree tree.CongestionTree, payments []Payment,
|
||||||
|
) *Round {
|
||||||
|
r := NewRound(dustAmount)
|
||||||
|
events := []RoundEvent{
|
||||||
|
RoundStarted{
|
||||||
|
Id: r.Id,
|
||||||
|
Timestamp: time.Now().Unix(),
|
||||||
|
},
|
||||||
|
PaymentsRegistered{
|
||||||
|
Id: r.Id,
|
||||||
|
Payments: payments,
|
||||||
|
},
|
||||||
|
RoundFinalizationStarted{
|
||||||
|
Id: r.Id,
|
||||||
|
CongestionTree: congestionTree,
|
||||||
|
PoolTx: poolTx,
|
||||||
|
},
|
||||||
|
RoundFinalized{
|
||||||
|
Id: r.Id,
|
||||||
|
Txid: poolTxid,
|
||||||
|
Timestamp: time.Now().Unix(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, event := range events {
|
||||||
|
r.raise(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
func NewRoundFromEvents(events []RoundEvent) *Round {
|
func NewRoundFromEvents(events []RoundEvent) *Round {
|
||||||
r := &Round{}
|
r := &Round{}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ type WalletService interface {
|
|||||||
SelectUtxos(ctx context.Context, asset string, amount uint64) ([]TxInput, uint64, error)
|
SelectUtxos(ctx context.Context, asset string, amount uint64) ([]TxInput, uint64, error)
|
||||||
BroadcastTransaction(ctx context.Context, txHex string) (string, error)
|
BroadcastTransaction(ctx context.Context, txHex string) (string, error)
|
||||||
SignPsetWithKey(ctx context.Context, pset string, inputIndexes []int) (string, error) // inputIndexes == nil means sign all inputs
|
SignPsetWithKey(ctx context.Context, pset string, inputIndexes []int) (string, error) // inputIndexes == nil means sign all inputs
|
||||||
IsTransactionPublished(ctx context.Context, txid string) (isPublished bool, blocktime int64, err error)
|
IsTransactionConfirmed(ctx context.Context, txid string) (isConfirmed bool, blocktime int64, err error)
|
||||||
EstimateFees(ctx context.Context, pset string) (uint64, error)
|
EstimateFees(ctx context.Context, pset string) (uint64, error)
|
||||||
Close()
|
Close()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ func (s *service) BroadcastTransaction(
|
|||||||
return res.GetTxid(), nil
|
return res.GetTxid(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) IsTransactionPublished(
|
func (s *service) IsTransactionConfirmed(
|
||||||
ctx context.Context, txid string,
|
ctx context.Context, txid string,
|
||||||
) (bool, int64, error) {
|
) (bool, int64, error) {
|
||||||
_, blocktime, err := s.GetTransaction(ctx, txid)
|
_, blocktime, err := s.GetTransaction(ctx, txid)
|
||||||
|
|||||||
@@ -113,8 +113,8 @@ func (b *txBuilder) BuildPoolTx(
|
|||||||
// generated in the process and takes the shared utxo outpoint as argument.
|
// generated in the process and takes the shared utxo outpoint as argument.
|
||||||
// This is safe as the memory allocated for `craftCongestionTree` is freed
|
// This is safe as the memory allocated for `craftCongestionTree` is freed
|
||||||
// only after `BuildPoolTx` returns.
|
// only after `BuildPoolTx` returns.
|
||||||
treeFactoryFn, sharedOutputScript, sharedOutputAmount, err := craftCongestionTree(
|
treeFactoryFn, sharedOutputScript, sharedOutputAmount, err := tree.CraftCongestionTree(
|
||||||
b.net.AssetID, aspPubkey, payments, minRelayFee, b.roundLifetime, b.exitDelay,
|
b.net.AssetID, aspPubkey, getOffchainReceivers(payments), minRelayFee, b.roundLifetime, b.exitDelay,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ func (m *mockedWallet) EstimateFees(ctx context.Context, pset string) (uint64, e
|
|||||||
return res, args.Error(1)
|
return res, args.Error(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockedWallet) IsTransactionPublished(ctx context.Context, txid string) (bool, int64, error) {
|
func (m *mockedWallet) IsTransactionConfirmed(ctx context.Context, txid string) (bool, int64, error) {
|
||||||
args := m.Called(ctx, txid)
|
args := m.Called(ctx, txid)
|
||||||
|
|
||||||
var res bool
|
var res bool
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/ark-network/ark/common/tree"
|
||||||
"github.com/ark-network/ark/internal/core/domain"
|
"github.com/ark-network/ark/internal/core/domain"
|
||||||
"github.com/ark-network/ark/internal/core/ports"
|
"github.com/ark-network/ark/internal/core/ports"
|
||||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||||
@@ -14,7 +15,6 @@ import (
|
|||||||
"github.com/vulpemventures/go-elements/network"
|
"github.com/vulpemventures/go-elements/network"
|
||||||
"github.com/vulpemventures/go-elements/payment"
|
"github.com/vulpemventures/go-elements/payment"
|
||||||
"github.com/vulpemventures/go-elements/psetv2"
|
"github.com/vulpemventures/go-elements/psetv2"
|
||||||
"github.com/vulpemventures/go-elements/taproot"
|
|
||||||
"github.com/vulpemventures/go-elements/transaction"
|
"github.com/vulpemventures/go-elements/transaction"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -62,12 +62,15 @@ func getOnchainReceivers(
|
|||||||
|
|
||||||
func getOffchainReceivers(
|
func getOffchainReceivers(
|
||||||
payments []domain.Payment,
|
payments []domain.Payment,
|
||||||
) []domain.Receiver {
|
) []tree.Receiver {
|
||||||
receivers := make([]domain.Receiver, 0)
|
receivers := make([]tree.Receiver, 0)
|
||||||
for _, payment := range payments {
|
for _, payment := range payments {
|
||||||
for _, receiver := range payment.Receivers {
|
for _, receiver := range payment.Receivers {
|
||||||
if !receiver.IsOnchain() {
|
if !receiver.IsOnchain() {
|
||||||
receivers = append(receivers, receiver)
|
receivers = append(receivers, tree.Receiver{
|
||||||
|
Pubkey: receiver.Pubkey,
|
||||||
|
Amount: receiver.Amount,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,35 +136,6 @@ func addInputs(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// wrapper of updater methods adding a taproot input to the pset with all the necessary data to spend it via any taproot script
|
|
||||||
func addTaprootInput(
|
|
||||||
updater *psetv2.Updater,
|
|
||||||
input psetv2.InputArgs,
|
|
||||||
internalTaprootKey *secp256k1.PublicKey,
|
|
||||||
taprootTree *taproot.IndexedElementsTapScriptTree,
|
|
||||||
) error {
|
|
||||||
if err := updater.AddInputs([]psetv2.InputArgs{input}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := updater.AddInTapInternalKey(0, schnorr.SerializePubKey(internalTaprootKey)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, proof := range taprootTree.LeafMerkleProofs {
|
|
||||||
controlBlock := proof.ToControlBlock(internalTaprootKey)
|
|
||||||
|
|
||||||
if err := updater.AddInTapLeafScript(0, psetv2.TapLeafScript{
|
|
||||||
TapElementsLeaf: taproot.NewBaseTapElementsLeaf(proof.Script),
|
|
||||||
ControlBlock: controlBlock,
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func taprootOutputScript(taprootKey *secp256k1.PublicKey) ([]byte, error) {
|
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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,285 +0,0 @@
|
|||||||
package txbuilder
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/ark-network/ark/common/tree"
|
|
||||||
"github.com/ark-network/ark/internal/core/domain"
|
|
||||||
"github.com/ark-network/ark/internal/core/ports"
|
|
||||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
|
||||||
"github.com/vulpemventures/go-elements/address"
|
|
||||||
"github.com/vulpemventures/go-elements/network"
|
|
||||||
"github.com/vulpemventures/go-elements/payment"
|
|
||||||
"github.com/vulpemventures/go-elements/psetv2"
|
|
||||||
"github.com/vulpemventures/go-elements/transaction"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
connectorAmount = 450
|
|
||||||
sevenDays = 7 * 24 * 60 * 60
|
|
||||||
)
|
|
||||||
|
|
||||||
type txBuilder struct {
|
|
||||||
wallet ports.WalletService
|
|
||||||
net network.Network
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTxBuilder(
|
|
||||||
wallet ports.WalletService, net network.Network,
|
|
||||||
) ports.TxBuilder {
|
|
||||||
return &txBuilder{wallet, net}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BuildSweepTx implements ports.TxBuilder.
|
|
||||||
func (*txBuilder) BuildSweepTx(wallet ports.WalletService, inputs []ports.SweepInput) (signedSweepTx string, err error) {
|
|
||||||
panic("unimplemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// BuildForfeitTxs implements ports.TxBuilder.
|
|
||||||
func (b *txBuilder) BuildForfeitTxs(
|
|
||||||
aspPubkey *secp256k1.PublicKey, poolTx string, payments []domain.Payment,
|
|
||||||
) (connectors []string, forfeitTxs []string, err error) {
|
|
||||||
poolTxID, err := getTxid(poolTx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
aspScript, err := p2wpkhScript(aspPubkey, b.net)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
numberOfConnectors := countSpentVtxos(payments)
|
|
||||||
|
|
||||||
connectors, err = createConnectors(
|
|
||||||
poolTxID,
|
|
||||||
1,
|
|
||||||
psetv2.OutputArgs{
|
|
||||||
Asset: b.net.AssetID,
|
|
||||||
Amount: connectorAmount,
|
|
||||||
Script: aspScript,
|
|
||||||
},
|
|
||||||
aspScript,
|
|
||||||
numberOfConnectors,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
connectorsAsInputs, err := connectorsToInputArgs(connectors)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
forfeitTxs = make([]string, 0)
|
|
||||||
for _, payment := range payments {
|
|
||||||
for _, vtxo := range payment.Inputs {
|
|
||||||
for _, connector := range connectorsAsInputs {
|
|
||||||
forfeitTx, err := createForfeitTx(
|
|
||||||
connector,
|
|
||||||
psetv2.InputArgs{
|
|
||||||
Txid: vtxo.Txid,
|
|
||||||
TxIndex: vtxo.VOut,
|
|
||||||
},
|
|
||||||
vtxo.Amount,
|
|
||||||
aspScript,
|
|
||||||
b.net,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
forfeitTxs = append(forfeitTxs, forfeitTx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return connectors, forfeitTxs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// BuildPoolTx implements ports.TxBuilder.
|
|
||||||
func (b *txBuilder) BuildPoolTx(
|
|
||||||
aspPubkey *secp256k1.PublicKey, payments []domain.Payment, minRelayFee uint64,
|
|
||||||
) (poolTx string, congestionTree tree.CongestionTree, err error) {
|
|
||||||
aspScriptBytes, err := p2wpkhScript(aspPubkey, b.net)
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
offchainReceivers, onchainReceivers := receiversFromPayments(payments)
|
|
||||||
sharedOutputAmount := sumReceivers(offchainReceivers)
|
|
||||||
|
|
||||||
numberOfConnectors := countSpentVtxos(payments)
|
|
||||||
connectorOutputAmount := connectorAmount * numberOfConnectors
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
outputs := []psetv2.OutputArgs{
|
|
||||||
{
|
|
||||||
Asset: b.net.AssetID,
|
|
||||||
Amount: sharedOutputAmount,
|
|
||||||
Script: aspScriptBytes,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Asset: b.net.AssetID,
|
|
||||||
Amount: connectorOutputAmount,
|
|
||||||
Script: aspScriptBytes,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
amountToSelect := sharedOutputAmount + connectorOutputAmount
|
|
||||||
|
|
||||||
for _, receiver := range onchainReceivers {
|
|
||||||
amountToSelect += receiver.Amount
|
|
||||||
|
|
||||||
receiverScript, err := address.ToOutputScript(receiver.OnchainAddress)
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
outputs = append(outputs, psetv2.OutputArgs{
|
|
||||||
Asset: b.net.AssetID,
|
|
||||||
Amount: receiver.Amount,
|
|
||||||
Script: receiverScript,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
utxos, change, err := b.wallet.SelectUtxos(ctx, b.net.AssetID, amountToSelect)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if change > 0 {
|
|
||||||
outputs = append(outputs, psetv2.OutputArgs{
|
|
||||||
Asset: b.net.AssetID,
|
|
||||||
Amount: change,
|
|
||||||
Script: aspScriptBytes,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
ptx, err := psetv2.New(toInputArgs(utxos), outputs, nil)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
utx, err := ptx.UnsignedTx()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
congestionTree, err = buildCongestionTree(
|
|
||||||
newOutputScriptFactory(aspPubkey, b.net),
|
|
||||||
b.net,
|
|
||||||
utx.TxHash().String(),
|
|
||||||
offchainReceivers,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
poolTx, err = ptx.ToBase64()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return poolTx, congestionTree, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *txBuilder) GetVtxoScript(userPubkey, _ *secp256k1.PublicKey) ([]byte, error) {
|
|
||||||
p2wpkh := payment.FromPublicKey(userPubkey, &b.net, nil)
|
|
||||||
addr, _ := p2wpkh.WitnessPubKeyHash()
|
|
||||||
return address.ToOutputScript(addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *txBuilder) GetLeafSweepClosure(
|
|
||||||
node tree.Node, userPubKey *secp256k1.PublicKey,
|
|
||||||
) (*psetv2.TapLeafScript, int64, error) {
|
|
||||||
panic("unimplemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func connectorsToInputArgs(connectors []string) ([]psetv2.InputArgs, error) {
|
|
||||||
inputs := make([]psetv2.InputArgs, 0, len(connectors)+1)
|
|
||||||
for i, psetb64 := range connectors {
|
|
||||||
tx, err := psetv2.NewPsetFromBase64(psetb64)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
utx, err := tx.UnsignedTx()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
txid := utx.TxHash().String()
|
|
||||||
for j := range tx.Outputs {
|
|
||||||
inputs = append(inputs, psetv2.InputArgs{
|
|
||||||
Txid: txid,
|
|
||||||
TxIndex: uint32(j),
|
|
||||||
})
|
|
||||||
if i != len(connectors)-1 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return inputs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTxid(txStr string) (string, error) {
|
|
||||||
pset, err := psetv2.NewPsetFromBase64(txStr)
|
|
||||||
if err != nil {
|
|
||||||
tx, err := transaction.NewTxFromHex(txStr)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return tx.TxHash().String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
utx, err := pset.UnsignedTx()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return utx.TxHash().String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func countSpentVtxos(payments []domain.Payment) uint64 {
|
|
||||||
var sum uint64
|
|
||||||
for _, payment := range payments {
|
|
||||||
sum += uint64(len(payment.Inputs))
|
|
||||||
}
|
|
||||||
return sum
|
|
||||||
}
|
|
||||||
|
|
||||||
func receiversFromPayments(
|
|
||||||
payments []domain.Payment,
|
|
||||||
) (offchainReceivers, onchainReceivers []domain.Receiver) {
|
|
||||||
for _, payment := range payments {
|
|
||||||
for _, receiver := range payment.Receivers {
|
|
||||||
if receiver.IsOnchain() {
|
|
||||||
onchainReceivers = append(onchainReceivers, receiver)
|
|
||||||
} else {
|
|
||||||
offchainReceivers = append(offchainReceivers, receiver)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func sumReceivers(receivers []domain.Receiver) uint64 {
|
|
||||||
var sum uint64
|
|
||||||
for _, r := range receivers {
|
|
||||||
sum += r.Amount
|
|
||||||
}
|
|
||||||
return sum
|
|
||||||
}
|
|
||||||
|
|
||||||
func toInputArgs(
|
|
||||||
ins []ports.TxInput,
|
|
||||||
) []psetv2.InputArgs {
|
|
||||||
inputs := make([]psetv2.InputArgs, 0, len(ins))
|
|
||||||
for _, in := range ins {
|
|
||||||
inputs = append(inputs, psetv2.InputArgs{
|
|
||||||
Txid: in.GetTxid(),
|
|
||||||
TxIndex: in.GetIndex(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return inputs
|
|
||||||
}
|
|
||||||
@@ -1,403 +0,0 @@
|
|||||||
package txbuilder_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/hex"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/ark-network/ark/internal/core/domain"
|
|
||||||
"github.com/ark-network/ark/internal/core/ports"
|
|
||||||
txbuilder "github.com/ark-network/ark/internal/infrastructure/tx-builder/dummy"
|
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
||||||
secp256k1 "github.com/decred/dcrd/dcrec/secp256k1/v4"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"github.com/vulpemventures/go-elements/network"
|
|
||||||
"github.com/vulpemventures/go-elements/psetv2"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
testingKey = "0218d5ca8b58797b7dbd65c075dd7ba7784b3f38ab71b1a5a8e3f94ba0257654a6"
|
|
||||||
fakePoolTx = "cHNldP8BAgQCAAAAAQQBAQEFAQMBBgEDAfsEAgAAAAABDiDk7dXxh4KQzgLO8i1ABtaLCe4aPL12GVhN1E9zM1ePLwEPBAAAAAABEAT/////AAEDCOgDAAAAAAAAAQQWABSNnpy01UJqd99eTg2M1IpdKId11gf8BHBzZXQCICWyUQcOKcoZBDzzPM1zJOLdqwPsxK4LXnfE/A5c9slaB/wEcHNldAgEAAAAAAABAwh4BQAAAAAAAAEEFgAUjZ6ctNVCanffXk4NjNSKXSiHddYH/ARwc2V0AiAlslEHDinKGQQ88zzNcyTi3asD7MSuC153xPwOXPbJWgf8BHBzZXQIBAAAAAAAAQMI9AEAAAAAAAABBAAH/ARwc2V0AiAlslEHDinKGQQ88zzNcyTi3asD7MSuC153xPwOXPbJWgf8BHBzZXQIBAAAAAAA"
|
|
||||||
)
|
|
||||||
|
|
||||||
type input struct {
|
|
||||||
txid string
|
|
||||||
vout uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *input) GetTxid() string {
|
|
||||||
return i.txid
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *input) GetIndex() uint32 {
|
|
||||||
return i.vout
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *input) GetScript() string {
|
|
||||||
return "a914ea9f486e82efb3dd83a69fd96e3f0113757da03c87"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *input) GetAsset() string {
|
|
||||||
return "5ac9f65c0efcc4775e0baec4ec03abdde22473cd3cf33c0419ca290e0751b225"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *input) GetValue() uint64 {
|
|
||||||
return 1000
|
|
||||||
}
|
|
||||||
|
|
||||||
type mockedWalletService struct{}
|
|
||||||
|
|
||||||
// BroadcastTransaction implements ports.WalletService.
|
|
||||||
func (*mockedWalletService) BroadcastTransaction(ctx context.Context, txHex string) (string, error) {
|
|
||||||
panic("unimplemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close implements ports.WalletService.
|
|
||||||
func (*mockedWalletService) Close() {
|
|
||||||
panic("unimplemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeriveAddresses implements ports.WalletService.
|
|
||||||
func (*mockedWalletService) DeriveAddresses(ctx context.Context, num int) ([]string, error) {
|
|
||||||
panic("unimplemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPubkey implements ports.WalletService.
|
|
||||||
func (*mockedWalletService) GetPubkey(ctx context.Context) (*secp256k1.PublicKey, error) {
|
|
||||||
panic("unimplemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignPset implements ports.WalletService.
|
|
||||||
func (*mockedWalletService) SignPset(ctx context.Context, pset string, extractRawTx bool) (string, error) {
|
|
||||||
panic("unimplemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Status implements ports.WalletService.
|
|
||||||
func (*mockedWalletService) Status(ctx context.Context) (ports.WalletStatus, error) {
|
|
||||||
panic("unimplemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*mockedWalletService) WatchScripts(ctx context.Context, scripts []string) error {
|
|
||||||
panic("unimplemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*mockedWalletService) UnwatchScripts(ctx context.Context, scripts []string) error {
|
|
||||||
panic("unimplemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*mockedWalletService) GetNotificationChannel(ctx context.Context) chan []domain.VtxoKey {
|
|
||||||
panic("unimplemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*mockedWalletService) SelectUtxos(ctx context.Context, asset string, amount uint64) ([]ports.TxInput, uint64, error) {
|
|
||||||
// random txid
|
|
||||||
bytes := make([]byte, 32)
|
|
||||||
if _, err := rand.Read(bytes); err != nil {
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
fakeInput := input{
|
|
||||||
txid: hex.EncodeToString(bytes),
|
|
||||||
vout: 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
return []ports.TxInput{&fakeInput}, 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*mockedWalletService) EstimateFees(ctx context.Context, pset string) (uint64, error) {
|
|
||||||
return 100, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*mockedWalletService) SignPsetWithKey(ctx context.Context, pset string, inputIndex []int) (string, error) {
|
|
||||||
panic("unimplemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*mockedWalletService) IsTransactionPublished(ctx context.Context, txid string) (bool, int64, error) {
|
|
||||||
panic("unimplemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuildCongestionTree(t *testing.T) {
|
|
||||||
builder := txbuilder.NewTxBuilder(&mockedWalletService{}, network.Liquid)
|
|
||||||
|
|
||||||
fixtures := []struct {
|
|
||||||
payments []domain.Payment
|
|
||||||
expectedNodesNum int // 2*len(receivers)-1
|
|
||||||
expectedLeavesNum int
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
payments: []domain.Payment{
|
|
||||||
{
|
|
||||||
Id: "0",
|
|
||||||
Inputs: []domain.Vtxo{
|
|
||||||
{
|
|
||||||
VtxoKey: domain.VtxoKey{
|
|
||||||
Txid: "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
|
||||||
VOut: 0,
|
|
||||||
},
|
|
||||||
Receiver: domain.Receiver{
|
|
||||||
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
|
|
||||||
Amount: 600,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Receivers: []domain.Receiver{
|
|
||||||
{
|
|
||||||
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
|
|
||||||
Amount: 600,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
|
|
||||||
Amount: 400,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedNodesNum: 3,
|
|
||||||
expectedLeavesNum: 2,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
payments: []domain.Payment{
|
|
||||||
{
|
|
||||||
Id: "0",
|
|
||||||
Inputs: []domain.Vtxo{
|
|
||||||
{
|
|
||||||
VtxoKey: domain.VtxoKey{
|
|
||||||
Txid: "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
|
||||||
VOut: 0,
|
|
||||||
},
|
|
||||||
Receiver: domain.Receiver{
|
|
||||||
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
|
|
||||||
Amount: 600,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Receivers: []domain.Receiver{
|
|
||||||
{
|
|
||||||
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
|
|
||||||
Amount: 600,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
|
|
||||||
Amount: 400,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Id: "0",
|
|
||||||
Inputs: []domain.Vtxo{
|
|
||||||
{
|
|
||||||
VtxoKey: domain.VtxoKey{
|
|
||||||
Txid: "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
|
||||||
VOut: 0,
|
|
||||||
},
|
|
||||||
Receiver: domain.Receiver{
|
|
||||||
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
|
|
||||||
Amount: 600,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Receivers: []domain.Receiver{
|
|
||||||
{
|
|
||||||
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
|
|
||||||
Amount: 600,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
|
|
||||||
Amount: 400,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Id: "0",
|
|
||||||
Inputs: []domain.Vtxo{
|
|
||||||
{
|
|
||||||
VtxoKey: domain.VtxoKey{
|
|
||||||
Txid: "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
|
||||||
VOut: 0,
|
|
||||||
},
|
|
||||||
Receiver: domain.Receiver{
|
|
||||||
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
|
|
||||||
Amount: 600,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Receivers: []domain.Receiver{
|
|
||||||
{
|
|
||||||
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
|
|
||||||
Amount: 600,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
|
|
||||||
Amount: 400,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedNodesNum: 11,
|
|
||||||
expectedLeavesNum: 6,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
pubkeyBytes, _ := hex.DecodeString(testingKey)
|
|
||||||
pubkey, _ := secp256k1.ParsePubKey(pubkeyBytes)
|
|
||||||
|
|
||||||
for _, f := range fixtures {
|
|
||||||
poolTx, tree, err := builder.BuildPoolTx(pubkey, f.payments, 30)
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, f.expectedNodesNum, tree.NumberOfNodes())
|
|
||||||
require.Len(t, tree.Leaves(), f.expectedLeavesNum)
|
|
||||||
|
|
||||||
poolPset, err := psetv2.NewPsetFromBase64(poolTx)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
poolTxUnsigned, err := poolPset.UnsignedTx()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
poolTxID := poolTxUnsigned.TxHash().String()
|
|
||||||
|
|
||||||
// check the root
|
|
||||||
require.Len(t, tree[0], 1)
|
|
||||||
require.Equal(t, poolTxID, tree[0][0].ParentTxid)
|
|
||||||
|
|
||||||
// check the leaves
|
|
||||||
for _, leaf := range tree.Leaves() {
|
|
||||||
pset, err := psetv2.NewPsetFromBase64(leaf.Tx)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Len(t, pset.Inputs, 1)
|
|
||||||
require.Len(t, pset.Outputs, 1)
|
|
||||||
|
|
||||||
inputTxID := chainhash.Hash(pset.Inputs[0].PreviousTxid).String()
|
|
||||||
require.Equal(t, leaf.ParentTxid, inputTxID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// check the nodes
|
|
||||||
for _, level := range tree[:len(tree)-2] {
|
|
||||||
for _, node := range level {
|
|
||||||
pset, err := psetv2.NewPsetFromBase64(node.Tx)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Len(t, pset.Inputs, 1)
|
|
||||||
require.Len(t, pset.Outputs, 2)
|
|
||||||
|
|
||||||
inputTxID := chainhash.Hash(pset.Inputs[0].PreviousTxid).String()
|
|
||||||
require.Equal(t, node.ParentTxid, inputTxID)
|
|
||||||
|
|
||||||
children := tree.Children(node.Txid)
|
|
||||||
require.Len(t, children, 2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuildForfeitTxs(t *testing.T) {
|
|
||||||
builder := txbuilder.NewTxBuilder(&mockedWalletService{}, network.Liquid)
|
|
||||||
|
|
||||||
poolPset, err := psetv2.NewPsetFromBase64(fakePoolTx)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
poolTxUnsigned, err := poolPset.UnsignedTx()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
poolTxID := poolTxUnsigned.TxHash().String()
|
|
||||||
|
|
||||||
fixtures := []struct {
|
|
||||||
payments []domain.Payment
|
|
||||||
expectedNumOfForfeitTxs int
|
|
||||||
expectedNumOfConnectors int
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
payments: []domain.Payment{
|
|
||||||
{
|
|
||||||
Id: "0",
|
|
||||||
Inputs: []domain.Vtxo{
|
|
||||||
{
|
|
||||||
VtxoKey: domain.VtxoKey{
|
|
||||||
Txid: "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
|
||||||
VOut: 0,
|
|
||||||
},
|
|
||||||
Receiver: domain.Receiver{
|
|
||||||
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
|
|
||||||
Amount: 600,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
VtxoKey: domain.VtxoKey{
|
|
||||||
Txid: "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
|
||||||
VOut: 1,
|
|
||||||
},
|
|
||||||
Receiver: domain.Receiver{
|
|
||||||
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
|
|
||||||
Amount: 400,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Receivers: []domain.Receiver{
|
|
||||||
{
|
|
||||||
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
|
|
||||||
Amount: 600,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
|
|
||||||
Amount: 400,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedNumOfForfeitTxs: 4,
|
|
||||||
expectedNumOfConnectors: 1,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
pubkeyBytes, _ := hex.DecodeString(testingKey)
|
|
||||||
pubkey, _ := secp256k1.ParsePubKey(pubkeyBytes)
|
|
||||||
|
|
||||||
for _, f := range fixtures {
|
|
||||||
connectors, forfeitTxs, err := builder.BuildForfeitTxs(
|
|
||||||
pubkey, fakePoolTx, f.payments,
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Len(t, connectors, f.expectedNumOfConnectors)
|
|
||||||
require.Len(t, forfeitTxs, f.expectedNumOfForfeitTxs)
|
|
||||||
|
|
||||||
// decode and check connectors
|
|
||||||
connectorsPsets := make([]*psetv2.Pset, 0, f.expectedNumOfConnectors)
|
|
||||||
for _, pset := range connectors {
|
|
||||||
p, err := psetv2.NewPsetFromBase64(pset)
|
|
||||||
require.NoError(t, err)
|
|
||||||
connectorsPsets = append(connectorsPsets, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, pset := range connectorsPsets {
|
|
||||||
require.Len(t, pset.Inputs, 1)
|
|
||||||
require.Len(t, pset.Outputs, 2)
|
|
||||||
|
|
||||||
expectedInputTxid := poolTxID
|
|
||||||
expectedInputVout := uint32(1)
|
|
||||||
if i > 0 {
|
|
||||||
tx, err := connectorsPsets[i-1].UnsignedTx()
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, tx)
|
|
||||||
expectedInputTxid = tx.TxHash().String()
|
|
||||||
}
|
|
||||||
|
|
||||||
inputTxid := chainhash.Hash(pset.Inputs[0].PreviousTxid).String()
|
|
||||||
require.Equal(t, expectedInputTxid, inputTxid)
|
|
||||||
require.Equal(t, expectedInputVout, pset.Inputs[0].PreviousTxIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
// decode and check forfeit txs
|
|
||||||
forfeitTxsPsets := make([]*psetv2.Pset, 0, f.expectedNumOfForfeitTxs)
|
|
||||||
for _, pset := range forfeitTxs {
|
|
||||||
p, err := psetv2.NewPsetFromBase64(pset)
|
|
||||||
require.NoError(t, err)
|
|
||||||
forfeitTxsPsets = append(forfeitTxsPsets, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// each forfeit tx should have 2 inputs and 2 outputs
|
|
||||||
for _, pset := range forfeitTxsPsets {
|
|
||||||
require.Len(t, pset.Inputs, 2)
|
|
||||||
require.Len(t, pset.Outputs, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
package txbuilder
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/vulpemventures/go-elements/psetv2"
|
|
||||||
)
|
|
||||||
|
|
||||||
func createConnectors(
|
|
||||||
poolTxID string,
|
|
||||||
connectorOutputIndex uint32,
|
|
||||||
connectorOutput psetv2.OutputArgs,
|
|
||||||
changeScript []byte,
|
|
||||||
numberOfConnectors uint64,
|
|
||||||
) (connectorsPsets []string, err error) {
|
|
||||||
previousInput := psetv2.InputArgs{
|
|
||||||
Txid: poolTxID,
|
|
||||||
TxIndex: connectorOutputIndex,
|
|
||||||
}
|
|
||||||
|
|
||||||
if numberOfConnectors == 1 {
|
|
||||||
pset, err := psetv2.New(nil, nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
updater, err := psetv2.NewUpdater(pset)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = updater.AddInputs([]psetv2.InputArgs{previousInput})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = updater.AddOutputs([]psetv2.OutputArgs{connectorOutput})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
base64, err := pset.ToBase64()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return []string{base64}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// compute the initial amount of the connectors output in pool transaction
|
|
||||||
remainingAmount := connectorAmount * numberOfConnectors
|
|
||||||
|
|
||||||
connectorsPset := make([]string, 0, numberOfConnectors-1)
|
|
||||||
for i := uint64(0); i < numberOfConnectors-1; i++ {
|
|
||||||
// create a new pset
|
|
||||||
pset, err := psetv2.New(nil, nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
updater, err := psetv2.NewUpdater(pset)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = updater.AddInputs([]psetv2.InputArgs{previousInput})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = updater.AddOutputs([]psetv2.OutputArgs{connectorOutput})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
changeAmount := remainingAmount - connectorOutput.Amount
|
|
||||||
if changeAmount > 0 {
|
|
||||||
changeOutput := psetv2.OutputArgs{
|
|
||||||
Asset: connectorOutput.Asset,
|
|
||||||
Amount: changeAmount,
|
|
||||||
Script: changeScript,
|
|
||||||
}
|
|
||||||
err = updater.AddOutputs([]psetv2.OutputArgs{changeOutput})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
tx, _ := pset.UnsignedTx()
|
|
||||||
txid := tx.TxHash().String()
|
|
||||||
|
|
||||||
// make the change the next previousInput
|
|
||||||
previousInput = psetv2.InputArgs{
|
|
||||||
Txid: txid,
|
|
||||||
TxIndex: 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
base64, err := pset.ToBase64()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
connectorsPset = append(connectorsPset, base64)
|
|
||||||
}
|
|
||||||
|
|
||||||
return connectorsPset, nil
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
package txbuilder
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/vulpemventures/go-elements/network"
|
|
||||||
"github.com/vulpemventures/go-elements/psetv2"
|
|
||||||
)
|
|
||||||
|
|
||||||
func createForfeitTx(
|
|
||||||
connectorInput psetv2.InputArgs,
|
|
||||||
vtxoInput psetv2.InputArgs,
|
|
||||||
vtxoAmount uint64,
|
|
||||||
aspScript []byte,
|
|
||||||
net network.Network,
|
|
||||||
) (forfeitTx string, err error) {
|
|
||||||
pset, err := psetv2.New(nil, nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
updater, err := psetv2.NewUpdater(pset)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = updater.AddInputs([]psetv2.InputArgs{connectorInput, vtxoInput})
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = updater.AddOutputs([]psetv2.OutputArgs{
|
|
||||||
{
|
|
||||||
Asset: net.AssetID,
|
|
||||||
Amount: vtxoAmount,
|
|
||||||
Script: aspScript,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return pset.ToBase64()
|
|
||||||
}
|
|
||||||
@@ -1,309 +0,0 @@
|
|||||||
package txbuilder
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/hex"
|
|
||||||
|
|
||||||
"github.com/ark-network/ark/common/tree"
|
|
||||||
"github.com/ark-network/ark/internal/core/domain"
|
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
||||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
|
||||||
"github.com/vulpemventures/go-elements/address"
|
|
||||||
"github.com/vulpemventures/go-elements/network"
|
|
||||||
"github.com/vulpemventures/go-elements/payment"
|
|
||||||
"github.com/vulpemventures/go-elements/psetv2"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
sharedOutputIndex = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
type outputScriptFactory func(leaves []domain.Receiver) ([]byte, error)
|
|
||||||
|
|
||||||
func p2wpkhScript(publicKey *secp256k1.PublicKey, net network.Network) ([]byte, error) {
|
|
||||||
payment := payment.FromPublicKey(publicKey, &net, nil)
|
|
||||||
addr, err := payment.WitnessPubKeyHash()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return address.ToOutputScript(addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// newOtputScriptFactory returns an output script factory func that lock funds using the ASP public key only on all branches psbt. The leaves are instead locked by the leaf public key.
|
|
||||||
func newOutputScriptFactory(aspPublicKey *secp256k1.PublicKey, net network.Network) outputScriptFactory {
|
|
||||||
return func(leaves []domain.Receiver) ([]byte, error) {
|
|
||||||
aspScript, err := p2wpkhScript(aspPublicKey, net)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch len(leaves) {
|
|
||||||
case 0:
|
|
||||||
return nil, nil
|
|
||||||
case 1: // it's a leaf
|
|
||||||
buf, err := hex.DecodeString(leaves[0].Pubkey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
key, err := secp256k1.ParsePubKey(buf)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return p2wpkhScript(key, net)
|
|
||||||
default: // it's a branch, lock funds with ASP public key
|
|
||||||
return aspScript, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// congestionTree builder iteratively creates a binary tree of Pset from a set of receivers
|
|
||||||
// it also expect createOutputScript func managing the output script creation and the network to use (mainly for L-BTC asset id)
|
|
||||||
func buildCongestionTree(
|
|
||||||
createOutputScript outputScriptFactory,
|
|
||||||
net network.Network,
|
|
||||||
poolTxID string,
|
|
||||||
receivers []domain.Receiver,
|
|
||||||
) (congestionTree tree.CongestionTree, err error) {
|
|
||||||
var nodes []*node
|
|
||||||
|
|
||||||
for _, r := range receivers {
|
|
||||||
nodes = append(nodes, newLeaf(createOutputScript, net, r))
|
|
||||||
}
|
|
||||||
|
|
||||||
for len(nodes) > 1 {
|
|
||||||
nodes, err = createTreeLevel(nodes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
psets, err := nodes[0].psets(psetv2.InputArgs{
|
|
||||||
Txid: poolTxID,
|
|
||||||
TxIndex: sharedOutputIndex,
|
|
||||||
}, 0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
maxLevel := 0
|
|
||||||
for _, psetWithLevel := range psets {
|
|
||||||
if psetWithLevel.level > maxLevel {
|
|
||||||
maxLevel = psetWithLevel.level
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
congestionTree = make(tree.CongestionTree, maxLevel+1)
|
|
||||||
|
|
||||||
for _, psetWithLevel := range psets {
|
|
||||||
utx, err := psetWithLevel.pset.UnsignedTx()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
txid := utx.TxHash().String()
|
|
||||||
|
|
||||||
psetB64, err := psetWithLevel.pset.ToBase64()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
parentTxid := chainhash.Hash(psetWithLevel.pset.Inputs[0].PreviousTxid).String()
|
|
||||||
|
|
||||||
congestionTree[psetWithLevel.level] = append(congestionTree[psetWithLevel.level], tree.Node{
|
|
||||||
Txid: txid,
|
|
||||||
Tx: psetB64,
|
|
||||||
ParentTxid: parentTxid,
|
|
||||||
Leaf: psetWithLevel.leaf,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return congestionTree, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func createTreeLevel(nodes []*node) ([]*node, error) {
|
|
||||||
if len(nodes)%2 != 0 {
|
|
||||||
last := nodes[len(nodes)-1]
|
|
||||||
pairs, err := createTreeLevel(nodes[:len(nodes)-1])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return append(pairs, last), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
pairs := make([]*node, 0, len(nodes)/2)
|
|
||||||
for i := 0; i < len(nodes); i += 2 {
|
|
||||||
pairs = append(pairs, newBranch(nodes[i], nodes[i+1]))
|
|
||||||
}
|
|
||||||
return pairs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// internal struct to build a binary tree of Pset
|
|
||||||
type node struct {
|
|
||||||
receivers []domain.Receiver
|
|
||||||
left *node
|
|
||||||
right *node
|
|
||||||
createOutputScript outputScriptFactory
|
|
||||||
network network.Network
|
|
||||||
}
|
|
||||||
|
|
||||||
// create a node from a single receiver
|
|
||||||
func newLeaf(
|
|
||||||
createOutputScript outputScriptFactory,
|
|
||||||
network network.Network,
|
|
||||||
receiver domain.Receiver,
|
|
||||||
) *node {
|
|
||||||
return &node{
|
|
||||||
receivers: []domain.Receiver{receiver},
|
|
||||||
createOutputScript: createOutputScript,
|
|
||||||
network: network,
|
|
||||||
left: nil,
|
|
||||||
right: nil,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// aggregate two nodes into a branch node
|
|
||||||
func newBranch(
|
|
||||||
left *node,
|
|
||||||
right *node,
|
|
||||||
) *node {
|
|
||||||
return &node{
|
|
||||||
receivers: append(left.receivers, right.receivers...),
|
|
||||||
createOutputScript: left.createOutputScript,
|
|
||||||
network: left.network,
|
|
||||||
left: left,
|
|
||||||
right: right,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// is it the final node of the tree
|
|
||||||
func (n *node) isLeaf() bool {
|
|
||||||
return n.left == nil && n.right == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// compute the output amount of a node
|
|
||||||
func (n *node) amount() uint64 {
|
|
||||||
var amount uint64
|
|
||||||
for _, r := range n.receivers {
|
|
||||||
amount += r.Amount
|
|
||||||
}
|
|
||||||
return amount
|
|
||||||
}
|
|
||||||
|
|
||||||
// compute the output script of a node
|
|
||||||
func (n *node) script() ([]byte, error) {
|
|
||||||
return n.createOutputScript(n.receivers)
|
|
||||||
}
|
|
||||||
|
|
||||||
// use script & amount to create OutputArgs
|
|
||||||
func (n *node) output() (*psetv2.OutputArgs, error) {
|
|
||||||
script, err := n.script()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &psetv2.OutputArgs{
|
|
||||||
Asset: n.network.AssetID,
|
|
||||||
Amount: n.amount(),
|
|
||||||
Script: script,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// create the node Pset from the previous node Pset represented by input arg
|
|
||||||
// if node is a branch, it adds two outputs to the Pset, one for the left branch and one for the right branch
|
|
||||||
// if node is a leaf, it only adds one output to the Pset (the node output)
|
|
||||||
func (n *node) pset(input psetv2.InputArgs) (*psetv2.Pset, error) {
|
|
||||||
pset, err := psetv2.New(nil, nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
updater, err := psetv2.NewUpdater(pset)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = updater.AddInputs([]psetv2.InputArgs{input})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if n.isLeaf() {
|
|
||||||
output, err := n.output()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = updater.AddOutputs([]psetv2.OutputArgs{*output})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return pset, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
outputLeft, err := n.left.output()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
outputRight, err := n.right.output()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = updater.AddOutputs([]psetv2.OutputArgs{*outputLeft, *outputRight})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return pset, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type psetWithLevel struct {
|
|
||||||
pset *psetv2.Pset
|
|
||||||
level int
|
|
||||||
leaf bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// create the node pset and all the psets of its children recursively, updating the input arg at each step
|
|
||||||
// the function stops when it reaches a leaf node
|
|
||||||
func (n *node) psets(input psetv2.InputArgs, level int) ([]psetWithLevel, error) {
|
|
||||||
pset, err := n.pset(input)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeResult := []psetWithLevel{
|
|
||||||
{pset, level, n.isLeaf()},
|
|
||||||
}
|
|
||||||
|
|
||||||
if n.isLeaf() {
|
|
||||||
return nodeResult, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
unsignedTx, err := pset.UnsignedTx()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
txID := unsignedTx.TxHash().String()
|
|
||||||
|
|
||||||
psetsLeft, err := n.left.psets(psetv2.InputArgs{
|
|
||||||
Txid: txID,
|
|
||||||
TxIndex: 0,
|
|
||||||
}, level+1)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
psetsRight, err := n.right.psets(psetv2.InputArgs{
|
|
||||||
Txid: txID,
|
|
||||||
TxIndex: 1,
|
|
||||||
}, level+1)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return append(nodeResult, append(psetsLeft, psetsRight...)...), nil
|
|
||||||
}
|
|
||||||
@@ -40,6 +40,37 @@ func NewHandler(service application.Service) arkv1.ArkServiceServer {
|
|||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *handler) Onboard(ctx context.Context, req *arkv1.OnboardRequest) (*arkv1.OnboardResponse, error) {
|
||||||
|
if req.GetUserPubkey() == "" {
|
||||||
|
return nil, status.Error(codes.InvalidArgument, "missing user pubkey")
|
||||||
|
}
|
||||||
|
|
||||||
|
pubKey, err := hex.DecodeString(req.GetUserPubkey())
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Error(codes.InvalidArgument, "invalid user pubkey")
|
||||||
|
}
|
||||||
|
|
||||||
|
decodedPubKey, err := secp256k1.ParsePubKey(pubKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Error(codes.InvalidArgument, "invalid user pubkey")
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.GetBoardingTx() == "" {
|
||||||
|
return nil, status.Error(codes.InvalidArgument, "missing boarding tx id")
|
||||||
|
}
|
||||||
|
|
||||||
|
tree, err := toCongestionTree(req.GetCongestionTree())
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Error(codes.InvalidArgument, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.svc.Onboard(ctx, req.GetBoardingTx(), tree, decodedPubKey); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &arkv1.OnboardResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (h *handler) Ping(ctx context.Context, req *arkv1.PingRequest) (*arkv1.PingResponse, error) {
|
func (h *handler) Ping(ctx context.Context, req *arkv1.PingRequest) (*arkv1.PingResponse, error) {
|
||||||
if req.GetPaymentId() == "" {
|
if req.GetPaymentId() == "" {
|
||||||
return nil, status.Error(codes.InvalidArgument, "missing payment id")
|
return nil, status.Error(codes.InvalidArgument, "missing payment id")
|
||||||
@@ -96,19 +127,6 @@ func (h *handler) FinalizePayment(ctx context.Context, req *arkv1.FinalizePaymen
|
|||||||
return &arkv1.FinalizePaymentResponse{}, nil
|
return &arkv1.FinalizePaymentResponse{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) Faucet(ctx context.Context, req *arkv1.FaucetRequest) (*arkv1.FaucetResponse, error) {
|
|
||||||
_, pubkey, _, err := parseAddress(req.GetAddress())
|
|
||||||
if err != nil {
|
|
||||||
return nil, status.Error(codes.InvalidArgument, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := h.svc.FaucetVtxos(ctx, pubkey); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &arkv1.FaucetResponse{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *handler) GetRound(ctx context.Context, req *arkv1.GetRoundRequest) (*arkv1.GetRoundResponse, error) {
|
func (h *handler) GetRound(ctx context.Context, req *arkv1.GetRoundRequest) (*arkv1.GetRoundResponse, error) {
|
||||||
if len(req.GetTxid()) <= 0 {
|
if len(req.GetTxid()) <= 0 {
|
||||||
return nil, status.Error(codes.InvalidArgument, "missing pool txid")
|
return nil, status.Error(codes.InvalidArgument, "missing pool txid")
|
||||||
@@ -305,3 +323,32 @@ func castCongestionTree(congestionTree tree.CongestionTree) *arkv1.Tree {
|
|||||||
Levels: levels,
|
Levels: levels,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func toCongestionTree(treeFromProto *arkv1.Tree) (tree.CongestionTree, error) {
|
||||||
|
levels := make(tree.CongestionTree, 0, len(treeFromProto.Levels))
|
||||||
|
|
||||||
|
for _, level := range treeFromProto.Levels {
|
||||||
|
nodes := make([]tree.Node, 0, len(level.Nodes))
|
||||||
|
|
||||||
|
for _, node := range level.Nodes {
|
||||||
|
nodes = append(nodes, tree.Node{
|
||||||
|
Txid: node.Txid,
|
||||||
|
Tx: node.Tx,
|
||||||
|
ParentTxid: node.ParentTxid,
|
||||||
|
Leaf: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
levels = append(levels, nodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
for j, treeLvl := range levels {
|
||||||
|
for i, node := range treeLvl {
|
||||||
|
if len(levels.Children(node.Txid)) == 0 {
|
||||||
|
levels[j][i].Leaf = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return levels, nil
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user