Skip to content

Commit

Permalink
Merge pull request #431 from lightninglabs/asset-coin-locking
Browse files Browse the repository at this point in the history
      wallet: add asset coin locking
  • Loading branch information
Roasbeef authored Aug 16, 2023
2 parents 5cd6878 + 91c3848 commit 6bbe56a
Show file tree
Hide file tree
Showing 36 changed files with 2,237 additions and 865 deletions.
2 changes: 2 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ type Config struct {

AssetWallet tapfreighter.Wallet

CoinSelect *tapfreighter.CoinSelect

ChainPorter tapfreighter.Porter

BaseUniverse *universe.MintingArchive
Expand Down
17 changes: 16 additions & 1 deletion itest/assertions.go
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,19 @@ func confirmAndAssetOutboundTransferWithOutputs(t *harnessTest,
assetID []byte, expectedAmounts []uint64, currentTransferIdx,
numTransfers, numOutputs int) {

assertAssetOutboundTransferWithOutputs(
t, sender, sendResp, assetID, expectedAmounts,
currentTransferIdx, numTransfers, numOutputs, true,
)
}

// assertAssetOutboundTransferWithOutputs makes sure the given outbound transfer
// has the correct state and number of outputs.
func assertAssetOutboundTransferWithOutputs(t *harnessTest,
sender *tapdHarness, sendResp *taprpc.SendAssetResponse,
assetID []byte, expectedAmounts []uint64, currentTransferIdx,
numTransfers, numOutputs int, confirm bool) {

ctxb := context.Background()

// Check that we now have two new outputs, and that they differ
Expand All @@ -459,7 +472,9 @@ func confirmAndAssetOutboundTransferWithOutputs(t *harnessTest,
t.Logf("Got response from sending assets: %v", sendRespJSON)

// Mine a block to force the send event to complete (confirm on-chain).
_ = mineBlocks(t, t.lndHarness, 1, 1)
if confirm {
_ = mineBlocks(t, t.lndHarness, 1, 1)
}

// Confirm that we can externally view the transfer.
require.Eventually(t.t, func() bool {
Expand Down
101 changes: 101 additions & 0 deletions itest/send_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -687,3 +687,104 @@ func testMultiInputSendNonInteractiveSingleID(t *harnessTest) {
_ = sendProof(t, bobTapd, t.tapd, addr.ScriptKey, genInfo)
assertNonInteractiveRecvComplete(t, t.tapd, 1)
}

// testSendMultipleCoins tests that we can send multiple transfers at the same
// time if we have multiple managed UTXOs/asset coins available.
func testSendMultipleCoins(t *harnessTest) {
ctxb := context.Background()

// First, we'll make a normal assets with enough units to allow us to
// send it to different UTXOs
rpcAssets := mintAssetsConfirmBatch(
t, t.tapd, []*mintrpc.MintAssetRequest{simpleAssets[0]},
)

genInfo := rpcAssets[0].AssetGenesis

// Now that we have the asset created, we'll make a new node that'll
// serve as the node which'll receive the assets. The existing tapd
// node will be used to synchronize universe state.
secondTapd := setupTapdHarness(
t.t, t, t.lndHarness.Bob, t.universeServer,
func(params *tapdHarnessParams) {
params.startupSyncNode = t.tapd
params.startupSyncNumAssets = len(rpcAssets)
},
)
defer func() {
require.NoError(t.t, secondTapd.stop(!*noDelete))
}()

// Next, we split the asset into 5 different UTXOs, each with 1k units.
const (
numParts = 5
unitsPerPart = 1000
)
addrs := make([]*taprpc.Addr, numParts)
for i := 0; i < numParts; i++ {
newAddr, err := t.tapd.NewAddr(ctxb, &taprpc.NewAddrRequest{
AssetId: genInfo.AssetId,
Amt: unitsPerPart,
})
require.NoError(t.t, err)

assertAddrCreated(t.t, t.tapd, rpcAssets[0], newAddr)
addrs[i] = newAddr
}

// We created 5 addresses in our first node now, so we can initiate the
// transfer to send the coins back to our wallet in 5 pieces now.
sendResp := sendAssetsToAddr(t, t.tapd, addrs...)
confirmAndAssetOutboundTransferWithOutputs(
t, t.tapd, sendResp, genInfo.AssetId, []uint64{
0, unitsPerPart, unitsPerPart, unitsPerPart,
unitsPerPart, unitsPerPart,
}, 0, 1, numParts+1,
)
assertNonInteractiveRecvComplete(t, t.tapd, 5)

// Next, we'll attempt to complete 5 parallel transfers with distinct
// addresses from our main node to Bob.
bobAddrs := make([]*taprpc.Addr, numParts)
for i := 0; i < numParts; i++ {
var err error
bobAddrs[i], err = secondTapd.NewAddr(
ctxb, &taprpc.NewAddrRequest{
AssetId: genInfo.AssetId,
Amt: unitsPerPart,
},
)
require.NoError(t.t, err)

sendResp := sendAssetsToAddr(t, t.tapd, bobAddrs[i])
assertAssetOutboundTransferWithOutputs(
t, t.tapd, sendResp, genInfo.AssetId,
[]uint64{0, unitsPerPart}, i+1, i+2, 2, false,
)
}

// Before we mine the next block, we'll make sure that we get a proper
// error message when trying to send more assets (there are currently no
// asset UTXOs available).
bobAddr, err := secondTapd.NewAddr(ctxb, &taprpc.NewAddrRequest{
AssetId: genInfo.AssetId,
Amt: 1,
})
require.NoError(t.t, err)

_, err = t.tapd.SendAsset(ctxb, &taprpc.SendAssetRequest{
TapAddrs: []string{bobAddr.Encoded},
})
require.ErrorContains(
t.t, err, "failed to find coin(s) that satisfy given "+
"constraints",
)

// Now we confirm the 5 transfers and make sure they complete as
// expected.
_ = mineBlocks(t, t.lndHarness, 1, 5)
for _, addr := range bobAddrs {
_ = sendProof(t, t.tapd, secondTapd, addr.ScriptKey, genInfo)
}
assertNonInteractiveRecvComplete(t, secondTapd, 5)
}
4 changes: 4 additions & 0 deletions itest/test_list_on_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ var testCases = []*testCase{
test: testBasicSendPassiveAsset,
proofCourierType: proof.ApertureCourier,
},
{
name: "send multiple coins",
test: testSendMultipleCoins,
},
{
name: "multi input send non-interactive single ID",
test: testMultiInputSendNonInteractiveSingleID,
Expand Down
4 changes: 4 additions & 0 deletions perms/perms.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ var (
Entity: "assets",
Action: "read",
}},
"/assetwalletrpc.AssetWallet/RemoveUTXOLease": {{
Entity: "assets",
Action: "write",
}},
"/mintrpc.Mint/MintAsset": {{
Entity: "mint",
Action: "write",
Expand Down
59 changes: 50 additions & 9 deletions rpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -536,8 +536,14 @@ func (r *rpcServer) checkBalanceOverflow(ctx context.Context,
func (r *rpcServer) ListAssets(ctx context.Context,
req *taprpc.ListAssetRequest) (*taprpc.ListAssetResponse, error) {

switch {
case req.IncludeSpent && req.IncludeLeased:
return nil, fmt.Errorf("cannot specify both include_spent " +
"and include_leased")
}

rpcAssets, err := r.fetchRpcAssets(
ctx, req.WithWitness, req.IncludeSpent,
ctx, req.WithWitness, req.IncludeSpent, req.IncludeLeased,
)
if err != nil {
return nil, err
Expand All @@ -548,10 +554,12 @@ func (r *rpcServer) ListAssets(ctx context.Context,
}, nil
}

func (r *rpcServer) fetchRpcAssets(ctx context.Context,
withWitness, includeSpent bool) ([]*taprpc.Asset, error) {
func (r *rpcServer) fetchRpcAssets(ctx context.Context, withWitness,
includeSpent, includeLeased bool) ([]*taprpc.Asset, error) {

assets, err := r.cfg.AssetStore.FetchAllAssets(ctx, includeSpent, nil)
assets, err := r.cfg.AssetStore.FetchAllAssets(
ctx, includeSpent, includeLeased, nil,
)
if err != nil {
return nil, fmt.Errorf("unable to read chain assets: %w", err)
}
Expand Down Expand Up @@ -600,6 +608,11 @@ func (r *rpcServer) marshalChainAsset(ctx context.Context, a *tapdb.ChainAsset,
BlockHeight: a.AnchorBlockHeight,
}

if a.AnchorLeaseOwner != [32]byte{} {
rpcAsset.LeaseOwner = a.AnchorLeaseOwner[:]
rpcAsset.LeaseExpiry = a.AnchorLeaseExpiry.UTC().Unix()
}

return rpcAsset, nil
}

Expand Down Expand Up @@ -763,9 +776,9 @@ func (r *rpcServer) listBalancesByGroupKey(ctx context.Context,
// ListUtxos lists the UTXOs managed by the target daemon, and the assets they
// hold.
func (r *rpcServer) ListUtxos(ctx context.Context,
_ *taprpc.ListUtxosRequest) (*taprpc.ListUtxosResponse, error) {
req *taprpc.ListUtxosRequest) (*taprpc.ListUtxosResponse, error) {

rpcAssets, err := r.fetchRpcAssets(ctx, false, false)
rpcAssets, err := r.fetchRpcAssets(ctx, false, false, req.IncludeLeased)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -1566,7 +1579,7 @@ func (r *rpcServer) AnchorVirtualPsbts(ctx context.Context,
prevID := vPacket.Inputs[0].PrevID
inputCommitment, err := r.cfg.AssetStore.FetchCommitment(
ctx, inputAsset.ID(), prevID.OutPoint, inputAsset.GroupKey,
&inputAsset.ScriptKey,
&inputAsset.ScriptKey, true,
)
if err != nil {
return nil, fmt.Errorf("error fetching input commitment: %w",
Expand Down Expand Up @@ -2794,7 +2807,7 @@ func (r *rpcServer) QueryProof(ctx context.Context,
return r.marshalIssuanceProof(ctx, req, proof)
}

// unmarsalAssetLeaf unmarshals an asset leaf from the RPC form.
// unmarshalAssetLeaf unmarshals an asset leaf from the RPC form.
func unmarshalAssetLeaf(leaf *unirpc.AssetLeaf) (*universe.MintingLeaf, error) {
// We'll just pull the asset details from the serialized issuance proof
// itself.
Expand Down Expand Up @@ -3101,7 +3114,7 @@ func (r *rpcServer) ProveAssetOwnership(ctx context.Context,
inputAsset := lastSnapshot.Asset
inputCommitment, err := r.cfg.AssetStore.FetchCommitment(
ctx, inputAsset.ID(), lastSnapshot.OutPoint,
inputAsset.GroupKey, &inputAsset.ScriptKey,
inputAsset.GroupKey, &inputAsset.ScriptKey, false,
)
if err != nil {
return nil, fmt.Errorf("error fetching commitment: %w", err)
Expand Down Expand Up @@ -3322,3 +3335,31 @@ func (r *rpcServer) QueryEvents(ctx context.Context,

return rpcStats, nil
}

// RemoveUTXOLease removes the lease/lock/reservation of the given managed
// UTXO.
func (r *rpcServer) RemoveUTXOLease(ctx context.Context,
in *wrpc.RemoveUTXOLeaseRequest) (*wrpc.RemoveUTXOLeaseResponse,
error) {

if in.Outpoint == nil {
return nil, fmt.Errorf("outpoint must be specified")
}

hash, err := chainhash.NewHash(in.Outpoint.Txid)
if err != nil {
return nil, fmt.Errorf("error parsing txid: %w", err)
}

outPoint := wire.OutPoint{
Hash: *hash,
Index: in.Outpoint.OutputIndex,
}

err = r.cfg.CoinSelect.ReleaseCoins(ctx, outPoint)
if err != nil {
return nil, err
}

return &wrpc.RemoveUTXOLeaseResponse{}, nil
}
15 changes: 8 additions & 7 deletions tapcfg/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ func genServerConfig(cfg *Config, cfgLogger btclog.Logger,
return nil, fmt.Errorf("unable to open database: %v", err)
}

defaultClock := clock.NewDefaultClock()
rksDB := tapdb.NewTransactionExecutor(
db, func(tx *sql.Tx) tapdb.KeyStore {
return db.WithTx(tx)
Expand All @@ -86,7 +87,7 @@ func genServerConfig(cfg *Config, cfgLogger btclog.Logger,
)
tapChainParams := address.ParamsForChain(cfg.ActiveNetParams.Name)
tapdbAddrBook := tapdb.NewTapAddressBook(
addrBookDB, &tapChainParams,
addrBookDB, &tapChainParams, defaultClock,
)

keyRing := tap.NewLndRpcKeyRing(lndServices)
Expand All @@ -100,7 +101,7 @@ func genServerConfig(cfg *Config, cfgLogger btclog.Logger,
Chain: tapChainParams,
})

assetStore := tapdb.NewAssetStore(assetDB)
assetStore := tapdb.NewAssetStore(assetDB, defaultClock)

uniDB := tapdb.NewTransactionExecutor(
db, func(tx *sql.Tx) tapdb.BaseUniverseStore {
Expand All @@ -119,9 +120,7 @@ func genServerConfig(cfg *Config, cfgLogger btclog.Logger,
return db.WithTx(tx)
},
)
universeStats := tapdb.NewUniverseStats(
uniStatsDB, clock.NewDefaultClock(),
)
universeStats := tapdb.NewUniverseStats(uniStatsDB, defaultClock)

headerVerifier := tapgarden.GenHeaderVerifier(
context.Background(), chainBridge,
Expand All @@ -142,7 +141,9 @@ func genServerConfig(cfg *Config, cfgLogger btclog.Logger,
return db.WithTx(tx)
},
)
federationDB := tapdb.NewUniverseFederationDB(federationStore)
federationDB := tapdb.NewUniverseFederationDB(
federationStore, defaultClock,
)

proofFileStore, err := proof.NewFileArchiver(cfg.networkDir)
if err != nil {
Expand Down Expand Up @@ -261,9 +262,9 @@ func genServerConfig(cfg *Config, cfgLogger btclog.Logger,
AddrBook: addrBook,
ProofArchive: proofArchive,
AssetWallet: assetWallet,
CoinSelect: coinSelect,
ChainPorter: tapfreighter.NewChainPorter(
&tapfreighter.ChainPorterConfig{
CoinSelector: coinSelect,
Signer: virtualTxSigner,
TxValidator: &tap.ValidatorV0{},
ExportLog: assetStore,
Expand Down
Loading

0 comments on commit 6bbe56a

Please sign in to comment.