Skip to content

Commit bf8e101

Browse files
committed
taprpc: add endpoint FetchSupplyCommit
Add FetchSupplyCommit endpoint to retrieve the on-chain supply commitment for a specific asset group. The response includes optional inclusion proofs for any provided leaf keys.
1 parent 66348ac commit bf8e101

File tree

9 files changed

+1463
-220
lines changed

9 files changed

+1463
-220
lines changed

rpcserver.go

Lines changed: 256 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3995,6 +3995,259 @@ func (r *rpcServer) UpdateSupplyCommit(ctx context.Context,
39953995
return &unirpc.UpdateSupplyCommitResponse{}, nil
39963996
}
39973997

3998+
// inclusionProofs fetches the inclusion proofs for the given leaf keys from
3999+
// the provided MSSMT tree. The leaf keys are expected to be 32-byte long.
4000+
func inclusionProofs(ctx context.Context, tree mssmt.Tree,
4001+
leafKeys [][]byte) ([][]byte, error) {
4002+
4003+
proofs := make([][]byte, 0, len(leafKeys))
4004+
for idx := range leafKeys {
4005+
leafKey := leafKeys[idx]
4006+
4007+
if len(leafKey) != 32 {
4008+
return nil, fmt.Errorf("leaf key is not 32 bytes long")
4009+
}
4010+
4011+
var key [32]byte
4012+
copy(key[:], leafKey)
4013+
4014+
inclusionProof, err := tree.MerkleProof(ctx, key)
4015+
if err != nil {
4016+
return nil, fmt.Errorf("failed to get inclusion proof "+
4017+
"for leaf key %x: %w", leafKey, err)
4018+
}
4019+
4020+
proofBytes, err := marshalMssmtProof(inclusionProof)
4021+
if err != nil {
4022+
return nil, fmt.Errorf("failed to marshal inclusion "+
4023+
"proof for leaf key %x: %w", leafKey, err)
4024+
}
4025+
4026+
proofs = append(proofs, proofBytes)
4027+
}
4028+
4029+
return proofs, nil
4030+
}
4031+
4032+
// supplySubtreeRoot fetches the root of a specific supply subtree and its
4033+
// supply tree inclusion proof.
4034+
func supplySubtreeRoot(ctx context.Context, supplyTree mssmt.Tree,
4035+
subtrees supplycommit.SupplyTrees,
4036+
subtreeType supplycommit.SupplySubTree) (
4037+
*unirpc.SupplyCommitSubtreeRoot, error) {
4038+
4039+
// Fetch supply subtree root for the given subtree type.
4040+
subtree := subtrees[subtreeType]
4041+
subtreeRoot, err := subtree.Root(ctx)
4042+
if err != nil {
4043+
return nil, fmt.Errorf("failed to get supply subtree root: "+
4044+
"%w", err)
4045+
}
4046+
4047+
// Fetch subtree inclusion proof for the given subtree type. This can
4048+
// be used to verify that the subtree root is indeed part of the
4049+
// supply tree.
4050+
subtreeRootLeafKey := subtreeType.UniverseKey()
4051+
subtreeInclusionProof, err := supplyTree.MerkleProof(
4052+
ctx, subtreeRootLeafKey,
4053+
)
4054+
if err != nil {
4055+
return nil, fmt.Errorf("failed to get supply subtree "+
4056+
"inclusion proof: %w", err)
4057+
}
4058+
4059+
inclusionProofBytes, err := marshalMssmtProof(subtreeInclusionProof)
4060+
if err != nil {
4061+
return nil, fmt.Errorf("failed to marshal inclusion "+
4062+
"proof for issuance subtree: %w", err)
4063+
}
4064+
4065+
return &unirpc.SupplyCommitSubtreeRoot{
4066+
Type: subtreeType.String(),
4067+
RootNode: marshalMssmtNode(subtreeRoot),
4068+
SupplyTreeLeafKey: subtreeRootLeafKey[:],
4069+
SupplyTreeInclusionProof: inclusionProofBytes,
4070+
}, nil
4071+
}
4072+
4073+
// FetchSupplyCommit fetches the on-chain supply commitment for a specific
4074+
// asset group.
4075+
func (r *rpcServer) FetchSupplyCommit(ctx context.Context,
4076+
req *unirpc.FetchSupplyCommitRequest) (
4077+
*unirpc.FetchSupplyCommitResponse, error) {
4078+
4079+
// Parse asset group key from the request.
4080+
var groupPubKey btcec.PublicKey
4081+
4082+
switch {
4083+
case len(req.GetGroupKeyBytes()) > 0:
4084+
gk, err := btcec.ParsePubKey(req.GetGroupKeyBytes())
4085+
if err != nil {
4086+
return nil, fmt.Errorf("parsing group key: %w", err)
4087+
}
4088+
4089+
groupPubKey = *gk
4090+
4091+
case len(req.GetGroupKeyStr()) > 0:
4092+
groupKeyBytes, err := hex.DecodeString(req.GetGroupKeyStr())
4093+
if err != nil {
4094+
return nil, fmt.Errorf("decoding group key: %w", err)
4095+
}
4096+
4097+
gk, err := btcec.ParsePubKey(groupKeyBytes)
4098+
if err != nil {
4099+
return nil, fmt.Errorf("parsing group key: %w", err)
4100+
}
4101+
4102+
groupPubKey = *gk
4103+
4104+
default:
4105+
return nil, fmt.Errorf("group key unspecified")
4106+
}
4107+
4108+
// Formulate an asset specifier from the asset group key.
4109+
assetSpec := asset.NewSpecifierFromGroupKey(groupPubKey)
4110+
4111+
// Fetch the supply commitment for the asset specifier.
4112+
respOpt, err := r.cfg.SupplyCommitManager.FetchCommitment(
4113+
ctx, assetSpec,
4114+
)
4115+
if err != nil {
4116+
return nil, fmt.Errorf("failed to fetch supply commit: %w",
4117+
err)
4118+
}
4119+
if respOpt.IsNone() {
4120+
return nil, fmt.Errorf("supply commitment not found for "+
4121+
"asset group with key %x",
4122+
groupPubKey.SerializeCompressed())
4123+
}
4124+
resp, err := respOpt.UnwrapOrErr(fmt.Errorf("unexpected None value " +
4125+
"for supply commitment response"))
4126+
if err != nil {
4127+
return nil, err
4128+
}
4129+
4130+
supplyTreeRoot, err := resp.SupplyTree.Root(ctx)
4131+
if err != nil {
4132+
return nil, fmt.Errorf("failed to get supply tree root: %w",
4133+
err)
4134+
}
4135+
4136+
// Fetch subtree commitment root and inclusion proofs for the issuance
4137+
// subtree.
4138+
rpcIssuanceSubtreeRoot, err := supplySubtreeRoot(
4139+
ctx, resp.SupplyTree, resp.Subtrees, supplycommit.MintTreeType,
4140+
)
4141+
if err != nil {
4142+
return nil, fmt.Errorf("failed to fetch supply issuance "+
4143+
"subtree root: %w", err)
4144+
}
4145+
4146+
// Fetch subtree commitment root and inclusion proofs for the burn
4147+
// subtree.
4148+
rpcBurnSubtreeRoot, err := supplySubtreeRoot(
4149+
ctx, resp.SupplyTree, resp.Subtrees, supplycommit.BurnTreeType,
4150+
)
4151+
if err != nil {
4152+
return nil, fmt.Errorf("failed to fetch supply burn subtree "+
4153+
"root: %w", err)
4154+
}
4155+
4156+
// Fetch subtree commitment root and inclusion proofs for the ignore
4157+
// subtree.
4158+
rpcIgnoreSubtreeRoot, err := supplySubtreeRoot(
4159+
ctx, resp.SupplyTree, resp.Subtrees,
4160+
supplycommit.IgnoreTreeType,
4161+
)
4162+
if err != nil {
4163+
return nil, fmt.Errorf("failed to fetch supply ignore subtree "+
4164+
"root: %w", err)
4165+
}
4166+
4167+
// Get inclusion proofs for any issuance leaf key specified in the
4168+
// request.
4169+
issuanceTree := resp.Subtrees[supplycommit.MintTreeType]
4170+
issuanceInclusionProofs, err := inclusionProofs(
4171+
ctx, issuanceTree, req.IssuanceLeafKeys,
4172+
)
4173+
if err != nil {
4174+
return nil, fmt.Errorf("failed to fetch issuance tree "+
4175+
"inclusion proofs: %w", err)
4176+
}
4177+
4178+
// Get inclusion proofs for any burn leaf key specified in the request.
4179+
burnTree := resp.Subtrees[supplycommit.BurnTreeType]
4180+
burnInclusionProofs, err := inclusionProofs(
4181+
ctx, burnTree, req.BurnLeafKeys,
4182+
)
4183+
if err != nil {
4184+
return nil, fmt.Errorf("failed to fetch burn tree "+
4185+
"inclusion proofs: %w", err)
4186+
}
4187+
4188+
// Get inclusion proofs for any ignore leaf key specified in the
4189+
// request.
4190+
ignoreTree := resp.Subtrees[supplycommit.IgnoreTreeType]
4191+
ignoreInclusionProofs, err := inclusionProofs(
4192+
ctx, ignoreTree, req.IgnoreLeafKeys,
4193+
)
4194+
if err != nil {
4195+
return nil, fmt.Errorf("failed to fetch ignore tree "+
4196+
"inclusion proofs: %w", err)
4197+
}
4198+
4199+
// Sanity check: ensure the supply root derived from the supply tree
4200+
// matches the root provided in the chain commitment.
4201+
if resp.ChainCommitment.SupplyRoot.NodeHash() !=
4202+
supplyTreeRoot.NodeHash() {
4203+
4204+
return nil, fmt.Errorf("mismatched supply commitment root: "+
4205+
"expected %x, got %x",
4206+
resp.ChainCommitment.SupplyRoot.NodeHash(),
4207+
supplyTreeRoot.NodeHash())
4208+
}
4209+
4210+
txOutInternalKey := resp.ChainCommitment.InternalKey.PubKey
4211+
4212+
// Extract the block height and hash from the chain commitment if
4213+
// present.
4214+
var (
4215+
blockHeight uint32
4216+
blockHash []byte
4217+
txIndex uint32
4218+
chainFees int64
4219+
)
4220+
resp.ChainCommitment.CommitmentBlock.WhenSome(
4221+
func(b supplycommit.CommitmentBlock) {
4222+
blockHeight = b.Height
4223+
blockHash = b.Hash[:]
4224+
txIndex = b.TxIndex
4225+
chainFees = b.ChainFees
4226+
},
4227+
)
4228+
4229+
return &unirpc.FetchSupplyCommitResponse{
4230+
SupplyCommitmentRoot: marshalMssmtNode(supplyTreeRoot),
4231+
4232+
AnchorTxid: resp.ChainCommitment.Txn.TxID(),
4233+
AnchorTxOutIdx: resp.ChainCommitment.TxOutIdx,
4234+
AnchorTxOutInternalKey: txOutInternalKey.SerializeCompressed(),
4235+
4236+
BlockHeight: blockHeight,
4237+
BlockHash: blockHash,
4238+
BlockTxIndex: txIndex,
4239+
TxChainFeesSats: chainFees,
4240+
4241+
IssuanceSubtreeRoot: rpcIssuanceSubtreeRoot,
4242+
BurnSubtreeRoot: rpcBurnSubtreeRoot,
4243+
IgnoreSubtreeRoot: rpcIgnoreSubtreeRoot,
4244+
4245+
IssuanceLeafInclusionProofs: issuanceInclusionProofs,
4246+
BurnLeafInclusionProofs: burnInclusionProofs,
4247+
IgnoreLeafInclusionProofs: ignoreInclusionProofs,
4248+
}, nil
4249+
}
4250+
39984251
// SubscribeSendAssetEventNtfns registers a subscription to the event
39994252
// notification stream which relates to the asset sending process.
40004253
func (r *rpcServer) SubscribeSendAssetEventNtfns(
@@ -4017,7 +4270,9 @@ func (r *rpcServer) SubscribeReceiveAssetEventNtfns(
40174270
_ *tapdevrpc.SubscribeReceiveAssetEventNtfnsRequest,
40184271
ntfnStream devReceiveEventStream) error {
40194272

4020-
marshaler := func(event fn.Event) (*tapdevrpc.ReceiveAssetEvent, error) {
4273+
marshaler := func(event fn.Event) (*tapdevrpc.ReceiveAssetEvent,
4274+
error) {
4275+
40214276
return marshallReceiveAssetEvent(
40224277
event, r.cfg.TapAddrBook,
40234278
)

taprpc/perms.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,10 @@ var (
263263
Entity: "universe",
264264
Action: "write",
265265
}},
266+
"/universerpc.Universe/FetchSupplyCommit": {{
267+
Entity: "universe",
268+
Action: "read",
269+
}},
266270
"/rfqrpc.Rfq/AddAssetBuyOrder": {{
267271
Entity: "rfq",
268272
Action: "write",

0 commit comments

Comments
 (0)