Skip to content

Commit 974a500

Browse files
committed
rpcserver+taprpc: implement new RegisterTransfer RPC
1 parent 6d58b38 commit 974a500

12 files changed

+861
-249
lines changed

perms/perms.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,13 @@ var (
9292
Entity: "assets",
9393
Action: "read",
9494
}},
95+
"/taprpc.TaprootAssets/RegisterTransfer": {{
96+
Entity: "assets",
97+
Action: "write",
98+
}, {
99+
Entity: "proofs",
100+
Action: "write",
101+
}},
95102
"/assetwalletrpc.AssetWallet/FundVirtualPsbt": {{
96103
Entity: "assets",
97104
Action: "write",

rpcserver.go

Lines changed: 160 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7953,7 +7953,7 @@ func (r *rpcServer) DecodeAssetPayReq(ctx context.Context,
79537953
AssetId: assetID[:],
79547954
}
79557955

7956-
// If this asset ID belongs to an asset group, then we'll display thiat
7956+
// If this asset ID belongs to an asset group, then we'll display that
79577957
// information as well.
79587958
//
79597959
// nolint:lll
@@ -8009,3 +8009,162 @@ func (r *rpcServer) DecodeAssetPayReq(ctx context.Context,
80098009

80108010
return &resp, nil
80118011
}
8012+
8013+
// RegisterTransfer informs the daemon about a new inbound transfer that has
8014+
// happened. This is used for interactive transfers where no TAP address is
8015+
// involved and the recipient is aware of the transfer through an out-of-band
8016+
// protocol but the daemon hasn't been informed about the completion of the
8017+
// transfer. For this to work, the proof must already be in the recipient's
8018+
// local universe (e.g. through the use of the universerpc.ImportProof RPC or
8019+
// the universe proof courier and universe sync mechanisms) and this call
8020+
// simply instructs the daemon to detect the transfer as an asset it owns.
8021+
func (r *rpcServer) RegisterTransfer(ctx context.Context,
8022+
req *taprpc.RegisterTransferRequest) (*taprpc.RegisterTransferResponse,
8023+
error) {
8024+
8025+
// First, we'll perform some basic input validation.
8026+
switch {
8027+
case len(req.AssetId) == 0:
8028+
return nil, fmt.Errorf("asset ID must be specified")
8029+
8030+
case len(req.AssetId) != 32:
8031+
return nil, fmt.Errorf("asset ID must be 32 bytes, was %d",
8032+
len(req.AssetId))
8033+
8034+
case len(req.GroupKey) > 0 && len(req.GroupKey) != 33:
8035+
return nil, fmt.Errorf("group key must be 33 bytes, was %d",
8036+
len(req.GroupKey))
8037+
8038+
case len(req.ScriptKey) == 0:
8039+
return nil, fmt.Errorf("script key must be specified")
8040+
8041+
case len(req.ScriptKey) != 33:
8042+
return nil, fmt.Errorf("script key must be 33 bytes, was %d",
8043+
len(req.ScriptKey))
8044+
8045+
case req.Outpoint == nil:
8046+
return nil, fmt.Errorf("outpoint must be specified")
8047+
}
8048+
8049+
// We'll query our local universe for the full proof. Since we're
8050+
// talking about a transfer here, we'll only look at transfer proofs.
8051+
var (
8052+
locator = proof.Locator{
8053+
AssetID: &asset.ID{},
8054+
}
8055+
err error
8056+
)
8057+
copy(locator.AssetID[:], req.AssetId)
8058+
8059+
if len(req.GroupKey) > 0 {
8060+
locator.GroupKey, err = btcec.ParsePubKey(req.GroupKey)
8061+
if err != nil {
8062+
return nil, fmt.Errorf("error parsing group key: %w",
8063+
err)
8064+
}
8065+
}
8066+
8067+
scriptPubKey, err := btcec.ParsePubKey(req.ScriptKey)
8068+
if err != nil {
8069+
return nil, fmt.Errorf("error parsing script key: %w", err)
8070+
}
8071+
locator.ScriptKey = *scriptPubKey
8072+
8073+
hash, err := chainhash.NewHash(req.Outpoint.Txid)
8074+
if err != nil {
8075+
return nil, err
8076+
}
8077+
locator.OutPoint = &wire.OutPoint{
8078+
Hash: *hash,
8079+
Index: req.Outpoint.OutputIndex,
8080+
}
8081+
8082+
// Before we query for the proof, we want to make sure the script key is
8083+
// already known to us. In an interactive transfer, we'd expect a script
8084+
// key to be derived on the recipient node, so it should already be
8085+
// registered. This is mainly to prevent the user from importing a proof
8086+
// for an asset that they won't be able to spend, because it doesn't
8087+
// belong to this node (which is the main issue with the old
8088+
// tapdevrpc.ImportProof RPC).
8089+
_, err = r.cfg.DatabaseConfig.TapAddrBook.FetchScriptKey(
8090+
ctx, scriptPubKey,
8091+
)
8092+
if err != nil {
8093+
return nil, fmt.Errorf("error fetching script key, consider "+
8094+
"declaring it with DeclareScriptKey if it does "+
8095+
"belong to this node: %w", err)
8096+
}
8097+
8098+
// Next, we make sure we don't already have this proof in the local
8099+
// archive (only in the universe). If we have, it means the user already
8100+
// imported it, and we don't want to overwrite it.
8101+
haveProof, err := r.cfg.ProofArchive.HasProof(ctx, locator)
8102+
if err != nil {
8103+
return nil, fmt.Errorf("error checking if proof is available: "+
8104+
"%w", err)
8105+
}
8106+
if haveProof {
8107+
return nil, fmt.Errorf("proof already exists for this transfer")
8108+
}
8109+
8110+
// We now fetch the full proof file from the local multiverse store,
8111+
// making sure we have the full proof chain for this transfer.
8112+
fullProvenance, err := r.cfg.Multiverse.FetchProof(ctx, locator)
8113+
if err != nil {
8114+
return nil, fmt.Errorf("error fetching full proof: %w", err)
8115+
}
8116+
8117+
// Let's make sure we can parse it as a file.
8118+
proofFile, err := fullProvenance.AsFile()
8119+
if err != nil {
8120+
return nil, fmt.Errorf("error converting proof to file: %w",
8121+
err)
8122+
}
8123+
8124+
// All seems well, we can now import the proof into our local proof
8125+
// archive, which will also materialize an asset in the asset database.
8126+
headerVerifier := tapgarden.GenHeaderVerifier(ctx, r.cfg.ChainBridge)
8127+
groupVerifier := tapgarden.GenGroupVerifier(ctx, r.cfg.MintingStore)
8128+
err = r.cfg.ProofArchive.ImportProofs(
8129+
ctx, headerVerifier, proof.DefaultMerkleVerifier, groupVerifier,
8130+
r.cfg.ChainBridge, false, &proof.AnnotatedProof{
8131+
Locator: locator,
8132+
Blob: fullProvenance,
8133+
},
8134+
)
8135+
if err != nil {
8136+
return nil, fmt.Errorf("error importing proof: %w", err)
8137+
}
8138+
8139+
// In case this proof hasn't been buried sufficiently, let's also hand
8140+
// it to the re-org watcher.
8141+
err = r.cfg.ReOrgWatcher.MaybeWatch(
8142+
proofFile, r.cfg.ReOrgWatcher.DefaultUpdateCallback(),
8143+
)
8144+
if err != nil {
8145+
return nil, fmt.Errorf("error watching received proof: %w", err)
8146+
}
8147+
8148+
lastProof, err := proofFile.LastProof()
8149+
if err != nil {
8150+
return nil, fmt.Errorf("error getting last proof: %w", err)
8151+
}
8152+
8153+
chainAsset, err := lastProof.ToChainAsset()
8154+
if err != nil {
8155+
return nil, fmt.Errorf("unable to convert proof to chain "+
8156+
"asset: %w", err)
8157+
}
8158+
8159+
rpcAsset, err := r.MarshalChainAsset(
8160+
ctx, chainAsset, lastProof.MetaReveal, false, r.cfg.AddrBook,
8161+
)
8162+
if err != nil {
8163+
return nil, fmt.Errorf("unable to marshal chain asset: %w",
8164+
err)
8165+
}
8166+
8167+
return &taprpc.RegisterTransferResponse{
8168+
RegisteredAsset: rpcAsset,
8169+
}, nil
8170+
}

taprpc/tapdevrpc/tapdev.pb.go

Lines changed: 21 additions & 21 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

taprpc/tapdevrpc/tapdev.proto

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,15 @@ option go_package = "github.com/lightninglabs/taproot-assets/taprpc/tapdevrpc";
88

99
service TapDev {
1010
/* tapcli: `dev importproof`
11-
ImportProof attempts to import a proof file into the daemon. If successful,
11+
Deprecated, use the new taprpc.RegisterTransfer RPC instead! ImportProof
12+
attempts to import a proof file into the daemon. If successful,
1213
a new asset will be inserted on disk, spendable using the specified target
1314
script key, and internal key.
15+
NOTE: This RPC will be removed with the next major release.
1416
*/
15-
rpc ImportProof (ImportProofRequest) returns (ImportProofResponse);
17+
rpc ImportProof (ImportProofRequest) returns (ImportProofResponse) {
18+
option deprecated = true;
19+
};
1620

1721
/*
1822
SubscribeSendAssetEventNtfns registers a subscription to the event

taprpc/tapdevrpc/tapdev_grpc.pb.go

Lines changed: 9 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)