Files
ark/asp/internal/infrastructure/tx-builder/covenant/builder_test.go
Louis Singer 5dba216a98 Congestion tree validation (#84)
* add common/pkg/tree validation

* update noah go.mod

* cleaning and fixes

* fix builder_test.go

* Fix deferred func

* fix even number of vtxos in congestion tree

---------

Co-authored-by: altafan <18440657+altafan@users.noreply.github.com>
2024-01-23 15:38:43 +01:00

486 lines
13 KiB
Go

package txbuilder_test
import (
"context"
"testing"
"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/ports"
txbuilder "github.com/ark-network/ark/internal/infrastructure/tx-builder/covenant"
"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"
"github.com/vulpemventures/go-elements/transaction"
)
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
}
utx, err := pset.UnsignedTx()
if err != nil {
return "", err
}
return utx.ToHex()
}
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(outs[0].GetAmount(), 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: 1100,
},
},
},
Receivers: []domain.Receiver{
{
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
Amount: 1100,
},
},
},
},
expectedNodesNum: 1,
expectedLeavesNum: 1,
},
{
payments: []domain.Payment{
{
Id: "0",
Inputs: []domain.Vtxo{
{
VtxoKey: domain.VtxoKey{
Txid: "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
VOut: 0,
},
Receiver: domain.Receiver{
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
Amount: 1100,
},
},
},
Receivers: []domain.Receiver{
{
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
Amount: 600,
},
{
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
Amount: 500,
},
},
},
},
expectedNodesNum: 1,
expectedLeavesNum: 1,
},
{
payments: []domain.Payment{
{
Id: "0",
Inputs: []domain.Vtxo{
{
VtxoKey: domain.VtxoKey{
Txid: "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
VOut: 0,
},
Receiver: domain.Receiver{
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
Amount: 1100,
},
},
},
Receivers: []domain.Receiver{
{
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
Amount: 600,
},
{
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
Amount: 500,
},
},
},
{
Id: "0",
Inputs: []domain.Vtxo{
{
VtxoKey: domain.VtxoKey{
Txid: "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
VOut: 0,
},
Receiver: domain.Receiver{
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
Amount: 1100,
},
},
},
Receivers: []domain.Receiver{
{
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
Amount: 600,
},
{
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
Amount: 500,
},
},
},
{
Id: "0",
Inputs: []domain.Vtxo{
{
VtxoKey: domain.VtxoKey{
Txid: "fd68e3c5796cc7db0a8036d486d5f625b6b2f2c014810ac020e1ac23e82c59d6",
VOut: 0,
},
Receiver: domain.Receiver{
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
Amount: 1100,
},
},
},
Receivers: []domain.Receiver{
{
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
Amount: 600,
},
{
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
Amount: 500,
},
},
},
},
expectedNodesNum: 5,
expectedLeavesNum: 3,
}, {
payments: []domain.Payment{
{
Id: "a242cdd8-f3d5-46c0-ae98-94135a2bee3f",
Inputs: []domain.Vtxo{
{
VtxoKey: domain.VtxoKey{
Txid: "755c820771284d85ea4bbcc246565b4eddadc44237a7e57a0f9cb78a840d1d41",
VOut: 0,
},
Receiver: domain.Receiver{
Pubkey: "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
Amount: 1000,
},
},
{
VtxoKey: domain.VtxoKey{
Txid: "66a0df86fcdeb84b8877adfe0b2c556dba30305d72ddbd4c49355f6930355357",
VOut: 0,
},
Receiver: domain.Receiver{
Pubkey: "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
Amount: 1000,
},
},
{
VtxoKey: domain.VtxoKey{
Txid: "9913159bc7aa493ca53cbb9cbc88f97ba01137c814009dc7ef520c3fafc67909",
VOut: 1,
},
Receiver: domain.Receiver{
Pubkey: "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
Amount: 500,
},
},
{
VtxoKey: domain.VtxoKey{
Txid: "5e10e77a7cdedc153be5193a4b6055a7802706ded4f2a9efefe86ed2f9a6ae60",
VOut: 0,
},
Receiver: domain.Receiver{
Pubkey: "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
Amount: 1000,
},
},
{
VtxoKey: domain.VtxoKey{
Txid: "5e10e77a7cdedc153be5193a4b6055a7802706ded4f2a9efefe86ed2f9a6ae60",
VOut: 1,
},
Receiver: domain.Receiver{
Pubkey: "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
Amount: 1000,
},
},
},
Receivers: []domain.Receiver{
{
Pubkey: "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
Amount: 1000,
},
{
Pubkey: "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
Amount: 1000,
},
{
Pubkey: "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
Amount: 1000,
},
{
Pubkey: "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
Amount: 1000,
},
{
Pubkey: "02c87e5c1758df5ad42a918ec507b6e8dfcdcebf22f64f58eb4ad5804257d658a5",
Amount: 500,
},
},
},
},
expectedNodesNum: 4,
expectedLeavesNum: 3,
},
}
_, key, err := common.DecodePubKey(testingKey)
require.NoError(t, err)
require.NotNil(t, key)
for _, f := range fixtures {
poolTx, congestionTree, err := builder.BuildPoolTx(key, &mockedWalletService{}, f.payments, 30)
require.NoError(t, err)
require.Equal(t, f.expectedNodesNum, congestionTree.NumberOfNodes())
require.Len(t, congestionTree.Leaves(), f.expectedLeavesNum)
// check that the pool tx has the right number of inputs and outputs
err = tree.ValidateCongestionTree(
congestionTree,
poolTx,
key,
1209344, // 2 weeks - 8 minutes
)
require.NoError(t, err)
}
}
func TestBuildForfeitTxs(t *testing.T) {
builder := txbuilder.NewTxBuilder(network.Liquid)
// TODO: replace with fixture.
poolTxHex, err := createTestPoolTx(1000, 2)
require.NoError(t, err)
poolTx, err := transaction.NewTxFromHex(poolTxHex)
require.NoError(t, err)
poolTxid := poolTx.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: 500,
},
},
},
Receivers: []domain.Receiver{
{
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
Amount: 600,
},
{
Pubkey: "020000000000000000000000000000000000000000000000000000000000000002",
Amount: 500,
},
},
},
},
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, poolTxHex, 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, 2)
}
}
}