Skip to content

Commit b33cb3c

Browse files
authored
Merge pull request #1554 from lightninglabs/wip/1534-ignore-tree/1534-add-ignoreasset-rpc
RPC: Add Ignore Tree Endpoints
2 parents a41c113 + 1fdd9da commit b33cb3c

26 files changed

+3897
-613
lines changed

asset/asset.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,22 @@ const (
5555
// compressed, 33-byte form.
5656
type SerializedKey [33]byte
5757

58+
// NewSerializedKeyFromBytes creates a new SerializedKey from the given byte
59+
// slice.
60+
func NewSerializedKeyFromBytes(key []byte) (SerializedKey, error) {
61+
var zero SerializedKey
62+
63+
// Ensure that the key is a valid public key.
64+
if _, err := btcec.ParsePubKey(key); err != nil {
65+
return zero, fmt.Errorf("invalid public key: %w", err)
66+
}
67+
68+
var serialized SerializedKey
69+
copy(serialized[:], key)
70+
71+
return serialized, nil
72+
}
73+
5874
// ToPubKey returns the public key parsed from the serialized key.
5975
func (s SerializedKey) ToPubKey() (*btcec.PublicKey, error) {
6076
return btcec.ParsePubKey(s[:])
@@ -74,6 +90,11 @@ func (s SerializedKey) CopyBytes() []byte {
7490
return c
7591
}
7692

93+
// String returns the hex-encoded string representation of the serialized key.
94+
func (s SerializedKey) String() string {
95+
return hex.EncodeToString(s[:])
96+
}
97+
7798
// ToSerialized serializes a public key in its 33-byte compressed form.
7899
func ToSerialized(pubKey *btcec.PublicKey) SerializedKey {
79100
var serialized SerializedKey
@@ -330,6 +351,23 @@ func (g Genesis) TagHash() [sha256.Size]byte {
330351
// outputIndex || assetType)
331352
type ID [sha256.Size]byte
332353

354+
// NewIDFromBytes creates a new ID from the given byte slice. The byte slice
355+
// must be exactly 32 bytes long, otherwise an error is returned.
356+
func NewIDFromBytes(id []byte) (ID, error) {
357+
var zero ID
358+
359+
// Basic sanity check to ensure the ID is the correct length.
360+
if len(id) != sha256.Size {
361+
return zero, fmt.Errorf("invalid ID length: %d", len(id))
362+
}
363+
364+
// Copy the byte slice into a new ID instance.
365+
var assetID ID
366+
copy(assetID[:], id)
367+
368+
return assetID, nil
369+
}
370+
333371
// String returns the hex-encoded string representation of the ID.
334372
func (i ID) String() string {
335373
return hex.EncodeToString(i[:])
@@ -731,6 +769,10 @@ func (id PrevID) Hash() [sha256.Size]byte {
731769
return *(*[sha256.Size]byte)(h.Sum(nil))
732770
}
733771

772+
// AnchorPoint is a type alias for an asset anchor outpoint. It relates the
773+
// on-chain anchor outpoint (txid:vout) to the corresponding committed asset.
774+
type AnchorPoint = PrevID
775+
734776
// SplitCommitment represents the asset witness for an asset split.
735777
type SplitCommitment struct {
736778
// Proof is the proof for a particular asset split resulting from a

cmd/commands/universe.go

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
tap "github.com/lightninglabs/taproot-assets"
1010
"github.com/lightninglabs/taproot-assets/fn"
1111
"github.com/lightninglabs/taproot-assets/proof"
12+
"github.com/lightninglabs/taproot-assets/taprpc"
1213
unirpc "github.com/lightninglabs/taproot-assets/taprpc/universerpc"
1314
"github.com/lightninglabs/taproot-assets/universe"
1415
"github.com/lightningnetwork/lnd/lncfg"
@@ -48,6 +49,7 @@ var universeCommands = []cli.Command{
4849
universeFederationCommand,
4950
universeInfoCommand,
5051
universeStatsCommand,
52+
universeSupplyCommitCommand,
5153
},
5254
},
5355
}
@@ -1503,3 +1505,212 @@ func universeEventStatsQueryCommand(ctx *cli.Context) error {
15031505
printRespJSON(resp)
15041506
return nil
15051507
}
1508+
1509+
var universeSupplyCommitCommand = cli.Command{
1510+
Name: "supplycommit",
1511+
ShortName: "sc",
1512+
Usage: "manage the supply commitment for a Universe",
1513+
Description: `
1514+
Manage the supply commitment for a Universe. The supply commitment
1515+
is a cryptographic commitment to the total supply of assets in a
1516+
Universe.
1517+
`,
1518+
Subcommands: []cli.Command{
1519+
ignoreAssetOutPointCmd,
1520+
updateSupplyCommitCmd,
1521+
fetchSupplyCommitCmd,
1522+
},
1523+
}
1524+
1525+
var ignoreAssetOutPointCmd = cli.Command{
1526+
Name: "ignore",
1527+
Usage: "ignores an asset outpoint when committing to the asset supply",
1528+
Description: "This command allows us to ignore a specific asset UTXO " +
1529+
"when creating a new supply commitment. This is useful if " +
1530+
"an otherwise valid asset should not be counted towards the " +
1531+
"total asset group supply.",
1532+
Flags: []cli.Flag{
1533+
&cli.StringFlag{
1534+
Name: "asset_anchor_point",
1535+
Usage: "the asset anchor point to ignore, specified " +
1536+
"as txid:vout",
1537+
Required: true,
1538+
},
1539+
&cli.StringFlag{
1540+
Name: assetIDName,
1541+
Usage: "the asset ID of the asset to ignore",
1542+
Required: true,
1543+
},
1544+
&cli.StringFlag{
1545+
Name: scriptKeyName,
1546+
Usage: "the script key of the asset to ignore",
1547+
Required: true,
1548+
},
1549+
&cli.Uint64Flag{
1550+
Name: "amount",
1551+
Usage: "the amount of the asset at the anchor " +
1552+
"outpoint",
1553+
Required: true,
1554+
},
1555+
},
1556+
Action: ignoreAssetOutPoint,
1557+
}
1558+
1559+
func ignoreAssetOutPoint(ctx *cli.Context) error {
1560+
cliCtx := getContext()
1561+
client, cleanUp := getUniverseClient(ctx)
1562+
defer cleanUp()
1563+
1564+
anchorPointStr := ctx.String("asset_anchor_point")
1565+
1566+
assetID, err := hex.DecodeString(ctx.String(assetIDName))
1567+
if err != nil {
1568+
return fmt.Errorf("invalid asset_id: %w", err)
1569+
}
1570+
1571+
scriptKey, err := hex.DecodeString(ctx.String(scriptKeyName))
1572+
if err != nil {
1573+
return fmt.Errorf("invalid script_key: %w", err)
1574+
}
1575+
1576+
req := &unirpc.IgnoreAssetOutPointRequest{
1577+
AssetOutPoint: &taprpc.AssetOutPoint{
1578+
AnchorOutPoint: anchorPointStr,
1579+
AssetId: assetID,
1580+
ScriptKey: scriptKey,
1581+
},
1582+
Amount: ctx.Uint64("amount"),
1583+
}
1584+
1585+
resp, err := client.IgnoreAssetOutPoint(cliCtx, req)
1586+
if err != nil {
1587+
return err
1588+
}
1589+
1590+
printJSON(resp)
1591+
return nil
1592+
}
1593+
1594+
var updateSupplyCommitCmd = cli.Command{
1595+
Name: "update",
1596+
Usage: "updates the on-chain supply commitment for an asset group",
1597+
Description: "This command updates the on-chain supply " +
1598+
"commitment for a specific asset group.",
1599+
Flags: []cli.Flag{
1600+
&cli.StringFlag{
1601+
Name: "group_key",
1602+
Usage: "the group key of the asset group to update",
1603+
Required: true,
1604+
},
1605+
},
1606+
Action: updateSupplyCommit,
1607+
}
1608+
1609+
func updateSupplyCommit(ctx *cli.Context) error {
1610+
cliCtx := getContext()
1611+
client, cleanUp := getUniverseClient(ctx)
1612+
defer cleanUp()
1613+
1614+
req := &unirpc.UpdateSupplyCommitRequest{
1615+
GroupKey: &unirpc.UpdateSupplyCommitRequest_GroupKeyStr{
1616+
GroupKeyStr: ctx.String("group_key"),
1617+
},
1618+
}
1619+
1620+
resp, err := client.UpdateSupplyCommit(cliCtx, req)
1621+
if err != nil {
1622+
return err
1623+
}
1624+
1625+
printJSON(resp)
1626+
return nil
1627+
}
1628+
1629+
var fetchSupplyCommitCmd = cli.Command{
1630+
Name: "fetch",
1631+
Usage: "fetches the on-chain supply commitment for an asset group",
1632+
Description: "This command fetches the on-chain supply " +
1633+
"commitment for a specific asset group.",
1634+
Flags: []cli.Flag{
1635+
&cli.StringFlag{
1636+
Name: "group_key",
1637+
Usage: "the group key of the asset group to fetch",
1638+
Required: true,
1639+
},
1640+
&cli.StringSliceFlag{
1641+
Name: "issuance_leaf_keys",
1642+
Usage: "a list of issuance leaf keys to fetch " +
1643+
"inclusion proofs for",
1644+
},
1645+
&cli.StringSliceFlag{
1646+
Name: "burn_leaf_keys",
1647+
Usage: "a list of burn leaf keys to fetch inclusion " +
1648+
"proofs for",
1649+
},
1650+
&cli.StringSliceFlag{
1651+
Name: "ignore_leaf_keys",
1652+
Usage: "a list of ignore leaf keys to fetch " +
1653+
"inclusion proofs for",
1654+
},
1655+
},
1656+
Action: fetchSupplyCommit,
1657+
}
1658+
1659+
func fetchSupplyCommit(ctx *cli.Context) error {
1660+
cliCtx := getContext()
1661+
client, cleanUp := getUniverseClient(ctx)
1662+
defer cleanUp()
1663+
1664+
req := &unirpc.FetchSupplyCommitRequest{
1665+
GroupKey: &unirpc.FetchSupplyCommitRequest_GroupKeyStr{
1666+
GroupKeyStr: ctx.String("group_key"),
1667+
},
1668+
}
1669+
1670+
issuanceKeys, err := parseHexStrings(
1671+
ctx.StringSlice("issuance_leaf_keys"),
1672+
)
1673+
if err != nil {
1674+
return fmt.Errorf("invalid issuance_leaf_keys: %w", err)
1675+
}
1676+
req.IssuanceLeafKeys = issuanceKeys
1677+
1678+
burnKeys, err := parseHexStrings(ctx.StringSlice("burn_leaf_keys"))
1679+
if err != nil {
1680+
return fmt.Errorf("invalid burn_leaf_keys: %w", err)
1681+
}
1682+
req.BurnLeafKeys = burnKeys
1683+
1684+
ignoreKeys, err := parseHexStrings(ctx.StringSlice("ignore_leaf_keys"))
1685+
if err != nil {
1686+
return fmt.Errorf("invalid ignore_leaf_keys: %w", err)
1687+
}
1688+
req.IgnoreLeafKeys = ignoreKeys
1689+
1690+
resp, err := client.FetchSupplyCommit(cliCtx, req)
1691+
if err != nil {
1692+
return err
1693+
}
1694+
1695+
printJSON(resp)
1696+
return nil
1697+
}
1698+
1699+
// parseHexStrings converts a slice of hex-encoded strings into a slice of byte
1700+
// slices. Each string in hexStrings must be a valid hex representation, or an
1701+
// error is returned.
1702+
func parseHexStrings(hexStrings []string) ([][]byte, error) {
1703+
result := make([][]byte, len(hexStrings))
1704+
1705+
for i, hexStr := range hexStrings {
1706+
decoded, err := hex.DecodeString(hexStr)
1707+
if err != nil {
1708+
return nil, fmt.Errorf("invalid hex string %q: %w",
1709+
hexStr, err)
1710+
}
1711+
1712+
result[i] = decoded
1713+
}
1714+
1715+
return result, nil
1716+
}

internal/test/mock.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,11 @@ type MockSigner struct {
3737

3838
SignOutputRawChannel chan SignOutputRawRequest
3939

40-
Signature []byte
40+
Signature []byte
41+
42+
// SignMessageErr holds the error returned by SignMessage, if any.
43+
SignMessageErr error
44+
4145
SignatureMsg string
4246
}
4347

@@ -66,6 +70,10 @@ func (s *MockSigner) SignMessage(_ context.Context, _ []byte,
6670
_ keychain.KeyLocator, _ ...lndclient.SignMessageOption) ([]byte,
6771
error) {
6872

73+
if s.SignMessageErr != nil {
74+
return nil, s.SignMessageErr
75+
}
76+
6977
return s.Signature, nil
7078
}
7179

itest/psbt_test.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3619,10 +3619,19 @@ func sendToTapscriptAddr(ctx context.Context, t *harnessTest, alice,
36193619
AssertSendEventsComplete(t.t, bobAddr.ScriptKey, sendEvents)
36203620
}
36213621

3622+
// sendAssetAndAssertResponse is a struct that holds the response of a
3623+
// sendAssetsToAddr call.
3624+
type sendAssetAndAssertResponse struct {
3625+
// RpcResp is the response message returned by the SendAsset RPC
3626+
// endpoint.
3627+
RpcResp *taprpc.SendAssetResponse
3628+
}
3629+
36223630
func sendAssetAndAssert(ctx context.Context, t *harnessTest, alice,
36233631
bob *tapdHarness, numUnits, change uint64,
36243632
genInfo *taprpc.GenesisInfo, mintedAsset *taprpc.Asset,
3625-
outTransferIdx, numOutTransfers, numInTransfers int) {
3633+
outTransferIdx, numOutTransfers,
3634+
numInTransfers int) sendAssetAndAssertResponse {
36263635

36273636
// And finally, we make sure that we can send out one of the asset UTXOs
36283637
// that shared the anchor output and the other one is treated as a
@@ -3645,6 +3654,10 @@ func sendAssetAndAssert(ctx context.Context, t *harnessTest, alice,
36453654
// appear in that RPC output).
36463655
AssertNonInteractiveRecvComplete(t.t, bob, numInTransfers)
36473656
AssertSendEventsComplete(t.t, bobAddr.ScriptKey, sendEvents)
3657+
3658+
return sendAssetAndAssertResponse{
3659+
RpcResp: sendResp,
3660+
}
36483661
}
36493662

36503663
func signPacket(t *testing.T, lnd *node.HarnessNode,

0 commit comments

Comments
 (0)