mirror of
https://github.com/aljazceru/ark.git
synced 2025-12-17 04:04:21 +01:00
271 lines
6.1 KiB
Go
271 lines
6.1 KiB
Go
package txbuilder
|
|
|
|
import (
|
|
"github.com/ark-network/ark/common"
|
|
"github.com/ark-network/ark/internal/core/domain"
|
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
|
"github.com/vulpemventures/go-elements/address"
|
|
"github.com/vulpemventures/go-elements/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
|
|
_, key, err := common.DecodePubKey(leaves[0].Pubkey)
|
|
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 []string, 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
|
|
}
|
|
}
|
|
|
|
var tree []string
|
|
|
|
psets, err := nodes[0].psets(psetv2.InputArgs{
|
|
Txid: poolTxID,
|
|
TxIndex: sharedOutputIndex,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, pset := range psets {
|
|
psetB64, err := pset.ToBase64()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tree = append(tree, psetB64)
|
|
}
|
|
|
|
return tree, 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 len(n.receivers) == 1
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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) ([]*psetv2.Pset, error) {
|
|
pset, err := n.pset(input)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if n.isLeaf() {
|
|
return []*psetv2.Pset{pset}, 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,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
psetsRight, err := n.right.psets(psetv2.InputArgs{
|
|
Txid: txID,
|
|
TxIndex: 1,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return append([]*psetv2.Pset{pset}, append(psetsLeft, psetsRight...)...), nil
|
|
}
|