mirror of
https://github.com/aljazceru/ark.git
synced 2025-12-17 12:14:21 +01:00
* v0 unilateral redemption * add fee outputs to congestion tree * unilateral exit * rework unilateral exit verbosity * substract fee from vtxo amount * remove unused functions and variables * fix after reviews * Update noah/explorer.go Co-authored-by: Pietralberto Mazza <18440657+altafan@users.noreply.github.com> Signed-off-by: Louis Singer <41042567+louisinger@users.noreply.github.com> * remove bufferutils --------- Signed-off-by: Louis Singer <41042567+louisinger@users.noreply.github.com> Co-authored-by: Pietralberto Mazza <18440657+altafan@users.noreply.github.com>
405 lines
10 KiB
Go
405 lines
10 KiB
Go
package txbuilder_test
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/ark-network/ark/common"
|
|
"github.com/ark-network/ark/internal/core/domain"
|
|
"github.com/ark-network/ark/internal/core/ports"
|
|
txbuilder "github.com/ark-network/ark/internal/infrastructure/tx-builder/dummy"
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
secp256k1 "github.com/decred/dcrd/dcrec/secp256k1/v4"
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/vulpemventures/go-elements/network"
|
|
"github.com/vulpemventures/go-elements/payment"
|
|
"github.com/vulpemventures/go-elements/psetv2"
|
|
)
|
|
|
|
const (
|
|
testingKey = "apub1qgvdtj5ttpuhkldavhq8thtm5auyk0ec4dcmrfdgu0u5hgp9we22v3hrs4x"
|
|
)
|
|
|
|
func createTestPoolTx(sharedOutputAmount, numberOfInputs uint64) (string, error) {
|
|
_, key, err := common.DecodePubKey(testingKey)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
payment := payment.FromPublicKey(key, &network.Testnet, nil)
|
|
script := payment.WitnessScript
|
|
|
|
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{
|
|
{
|
|
Txid: "2f8f5733734fd44d581976bd3c1aee098bd606402df2ce02ce908287f1d5ede4",
|
|
TxIndex: 0,
|
|
},
|
|
})
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
connectorsAmount := numberOfInputs * (450 + 500)
|
|
|
|
err = updater.AddOutputs([]psetv2.OutputArgs{
|
|
{
|
|
Asset: network.Regtest.AssetID,
|
|
Amount: sharedOutputAmount,
|
|
Script: script,
|
|
},
|
|
{
|
|
Asset: network.Regtest.AssetID,
|
|
Amount: connectorsAmount,
|
|
Script: script,
|
|
},
|
|
{
|
|
Asset: network.Regtest.AssetID,
|
|
Amount: 500,
|
|
},
|
|
})
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return pset.ToBase64()
|
|
}
|
|
|
|
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")
|
|
}
|
|
|
|
// Transfer implements ports.WalletService.
|
|
func (*mockedWalletService) Transfer(ctx context.Context, outs []ports.TxOutput) (string, error) {
|
|
return createTestPoolTx(1000, (450+500)*1)
|
|
}
|
|
|
|
func TestBuildCongestionTree(t *testing.T) {
|
|
builder := txbuilder.NewTxBuilder(network.Liquid)
|
|
|
|
fixtures := []struct {
|
|
payments []domain.Payment
|
|
expectedNodesNum int // 2*len(receivers)-1
|
|
expectedLeavesNum int
|
|
}{
|
|
{
|
|
payments: []domain.Payment{
|
|
{
|
|
Id: "0",
|
|
Inputs: []domain.Vtxo{
|
|
{
|
|
VtxoKey: domain.VtxoKey{
|
|
Txid: "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
|
VOut: 0,
|
|
},
|
|
Receiver: domain.Receiver{
|
|
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
|
|
Amount: 600,
|
|
},
|
|
},
|
|
},
|
|
Receivers: []domain.Receiver{
|
|
{
|
|
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
|
|
Amount: 600,
|
|
},
|
|
{
|
|
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
|
|
Amount: 400,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expectedNodesNum: 3,
|
|
expectedLeavesNum: 2,
|
|
},
|
|
{
|
|
payments: []domain.Payment{
|
|
{
|
|
Id: "0",
|
|
Inputs: []domain.Vtxo{
|
|
{
|
|
VtxoKey: domain.VtxoKey{
|
|
Txid: "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
|
VOut: 0,
|
|
},
|
|
Receiver: domain.Receiver{
|
|
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
|
|
Amount: 600,
|
|
},
|
|
},
|
|
},
|
|
Receivers: []domain.Receiver{
|
|
{
|
|
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
|
|
Amount: 600,
|
|
},
|
|
{
|
|
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
|
|
Amount: 400,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Id: "0",
|
|
Inputs: []domain.Vtxo{
|
|
{
|
|
VtxoKey: domain.VtxoKey{
|
|
Txid: "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
|
VOut: 0,
|
|
},
|
|
Receiver: domain.Receiver{
|
|
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
|
|
Amount: 600,
|
|
},
|
|
},
|
|
},
|
|
Receivers: []domain.Receiver{
|
|
{
|
|
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
|
|
Amount: 600,
|
|
},
|
|
{
|
|
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
|
|
Amount: 400,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Id: "0",
|
|
Inputs: []domain.Vtxo{
|
|
{
|
|
VtxoKey: domain.VtxoKey{
|
|
Txid: "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
|
VOut: 0,
|
|
},
|
|
Receiver: domain.Receiver{
|
|
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
|
|
Amount: 600,
|
|
},
|
|
},
|
|
},
|
|
Receivers: []domain.Receiver{
|
|
{
|
|
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
|
|
Amount: 600,
|
|
},
|
|
{
|
|
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
|
|
Amount: 400,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expectedNodesNum: 11,
|
|
expectedLeavesNum: 6,
|
|
},
|
|
}
|
|
|
|
_, key, err := common.DecodePubKey(testingKey)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, key)
|
|
|
|
for _, f := range fixtures {
|
|
poolTx, tree, err := builder.BuildPoolTx(key, &mockedWalletService{}, 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(network.Liquid)
|
|
|
|
poolTx, err := createTestPoolTx(1000, 450*2)
|
|
require.NoError(t, err)
|
|
|
|
poolPset, err := psetv2.NewPsetFromBase64(poolTx)
|
|
require.NoError(t, err)
|
|
|
|
poolTxUnsigned, err := poolPset.UnsignedTx()
|
|
require.NoError(t, err)
|
|
|
|
poolTxID := poolTxUnsigned.TxHash().String()
|
|
|
|
fixtures := []struct {
|
|
payments []domain.Payment
|
|
expectedNumOfForfeitTxs int
|
|
expectedNumOfConnectors int
|
|
}{
|
|
{
|
|
payments: []domain.Payment{
|
|
{
|
|
Id: "0",
|
|
Inputs: []domain.Vtxo{
|
|
{
|
|
VtxoKey: domain.VtxoKey{
|
|
Txid: "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
|
VOut: 0,
|
|
},
|
|
Receiver: domain.Receiver{
|
|
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
|
|
Amount: 600,
|
|
},
|
|
},
|
|
{
|
|
VtxoKey: domain.VtxoKey{
|
|
Txid: "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
|
|
VOut: 1,
|
|
},
|
|
Receiver: domain.Receiver{
|
|
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
|
|
Amount: 400,
|
|
},
|
|
},
|
|
},
|
|
Receivers: []domain.Receiver{
|
|
{
|
|
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
|
|
Amount: 600,
|
|
},
|
|
{
|
|
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
|
|
Amount: 400,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expectedNumOfForfeitTxs: 4,
|
|
expectedNumOfConnectors: 1,
|
|
},
|
|
}
|
|
|
|
_, key, err := common.DecodePubKey(testingKey)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, key)
|
|
|
|
for _, f := range fixtures {
|
|
connectors, forfeitTxs, err := builder.BuildForfeitTxs(
|
|
key, poolTx, 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)
|
|
}
|
|
}
|
|
}
|