Skip to content

Commit

Permalink
Merge pull request lightninglabs#726 from lightninglabs/custodian-loc…
Browse files Browse the repository at this point in the history
…al-universe

[1/2] custodian: look up proofs in local universe as well
  • Loading branch information
Roasbeef authored Jan 12, 2024
2 parents d1db63e + 864b554 commit 1741b14
Show file tree
Hide file tree
Showing 20 changed files with 1,011 additions and 99 deletions.
44 changes: 44 additions & 0 deletions fn/func.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,18 @@ func All[T any](xs []T, pred func(T) bool) bool {
return true
}

// AllMapItems returns true if the passed predicate returns true for all items
// in the map.
func AllMapItems[T any, K comparable](xs map[K]T, pred func(T) bool) bool {
for i := range xs {
if !pred(xs[i]) {
return false
}
}

return true
}

// Any returns true if the passed predicate returns true for any item in the
// slice.
func Any[T any](xs []T, pred func(T) bool) bool {
Expand All @@ -142,12 +154,30 @@ func Any[T any](xs []T, pred func(T) bool) bool {
return false
}

// AnyMapItem returns true if the passed predicate returns true for any item in
// the map.
func AnyMapItem[T any, K comparable](xs map[K]T, pred func(T) bool) bool {
for i := range xs {
if pred(xs[i]) {
return true
}
}

return false
}

// NotAny returns true if the passed predicate returns false for all items in
// the slice.
func NotAny[T any](xs []T, pred func(T) bool) bool {
return !Any(xs, pred)
}

// NotAnyMapItem returns true if the passed predicate returns false for all
// items in the map.
func NotAnyMapItem[T any, K comparable](xs map[K]T, pred func(T) bool) bool {
return !AnyMapItem(xs, pred)
}

// Count returns the number of items in the slice that match the predicate.
func Count[T any](xs []T, pred func(T) bool) int {
var count int
Expand All @@ -161,6 +191,20 @@ func Count[T any](xs []T, pred func(T) bool) int {
return count
}

// CountMapItems returns the number of items in the map that match the
// predicate.
func CountMapItems[T any, K comparable](xs map[K]T, pred func(T) bool) int {
var count int

for i := range xs {
if pred(xs[i]) {
count++
}
}

return count
}

// First returns the first item in the slice that matches the predicate, or an
// error if none matches.
func First[T any](xs []*T, pred func(*T) bool) (*T, error) {
Expand Down
28 changes: 21 additions & 7 deletions fn/iter.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,8 @@ package fn
// This function can be used instead of the normal range loop to ensure that a
// loop scoping bug isn't introduced.
func ForEachErr[T any](s []T, f func(T) error) error {
for _, item := range s {
item := item

if err := f(item); err != nil {
for i := range s {
if err := f(s[i]); err != nil {
return err
}
}
Expand All @@ -22,9 +20,17 @@ func ForEachErr[T any](s []T, f func(T) error) error {
// This can be used to ensure that any normal for-loop don't run into bugs due
// to loop variable scoping.
func ForEach[T any](items []T, f func(T)) {
for _, item := range items {
item := item
f(item)
for i := range items {
f(items[i])
}
}

// ForEachMapItem is a generic implementation of a for-each (map with side
// effects). This can be used to ensure that any normal for-loop don't run into
// bugs due to loop variable scoping.
func ForEachMapItem[T any, K comparable](items map[K]T, f func(T)) {
for i := range items {
f(items[i])
}
}

Expand All @@ -38,6 +44,14 @@ func Enumerate[T any](items []T, f func(int, T)) {
}
}

// EnumerateMap is a generic enumeration function. The closure will be called
// for each key and item in the passed-in map.
func EnumerateMap[T any, K comparable](items map[K]T, f func(K, T)) {
for key := range items {
f(key, items[key])
}
}

// MakeSlice is a generic function shorthand for making a slice out of a set
// of elements. This can be used to avoid having to specify the type of the
// slice as well as the types of the elements.
Expand Down
82 changes: 82 additions & 0 deletions itest/addrs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
tap "github.com/lightninglabs/taproot-assets"
"github.com/lightninglabs/taproot-assets/fn"
"github.com/lightninglabs/taproot-assets/internal/test"
"github.com/lightninglabs/taproot-assets/proof"
"github.com/lightninglabs/taproot-assets/tappsbt"
"github.com/lightninglabs/taproot-assets/taprpc"
wrpc "github.com/lightninglabs/taproot-assets/taprpc/assetwalletrpc"
Expand Down Expand Up @@ -512,6 +513,8 @@ func runMultiSendTest(ctxt context.Context, t *harnessTest, alice,
require.NoError(t.t, err)
}

// sendProof manually exports a proof from the given source node and imports it
// using the development only ImportProof RPC on the destination node.
func sendProof(t *harnessTest, src, dst *tapdHarness, scriptKey []byte,
genInfo *taprpc.GenesisInfo) *tapdevrpc.ImportProofResponse {

Expand Down Expand Up @@ -543,6 +546,85 @@ func sendProof(t *harnessTest, src, dst *tapdHarness, scriptKey []byte,
return importResp
}

// sendProofUniRPC manually exports a proof from the given source node and
// imports it using the universe related InsertProof RPC on the destination
// node.
func sendProofUniRPC(t *harnessTest, src, dst *tapdHarness, scriptKey []byte,
genInfo *taprpc.GenesisInfo) *unirpc.AssetProofResponse {

ctxb := context.Background()

var proofResp *taprpc.ProofFile
waitErr := wait.NoError(func() error {
resp, err := src.ExportProof(ctxb, &taprpc.ExportProofRequest{
AssetId: genInfo.AssetId,
ScriptKey: scriptKey,
})
if err != nil {
return err
}

proofResp = resp
return nil
}, defaultWaitTimeout)
require.NoError(t.t, waitErr)

t.Logf("Importing proof %x using InsertProof", proofResp.RawProofFile)

f := proof.File{}
err := f.Decode(bytes.NewReader(proofResp.RawProofFile))
require.NoError(t.t, err)

lastProof, err := f.LastProof()
require.NoError(t.t, err)

var lastProofBytes bytes.Buffer
err = lastProof.Encode(&lastProofBytes)
require.NoError(t.t, err)
asset := lastProof.Asset

proofType := universe.ProofTypeTransfer
if asset.IsGenesisAsset() {
proofType = universe.ProofTypeIssuance
}

uniID := universe.Identifier{
AssetID: asset.ID(),
ProofType: proofType,
}
if asset.GroupKey != nil {
uniID.GroupKey = &asset.GroupKey.GroupPubKey
}

rpcUniID, err := tap.MarshalUniID(uniID)
require.NoError(t.t, err)

outpoint := &unirpc.Outpoint{
HashStr: lastProof.AnchorTx.TxHash().String(),
Index: int32(lastProof.InclusionProof.OutputIndex),
}

importResp, err := dst.InsertProof(ctxb, &unirpc.AssetProof{
Key: &unirpc.UniverseKey{
Id: rpcUniID,
LeafKey: &unirpc.AssetKey{
Outpoint: &unirpc.AssetKey_Op{
Op: outpoint,
},
ScriptKey: &unirpc.AssetKey_ScriptKeyBytes{
ScriptKeyBytes: scriptKey,
},
},
},
AssetLeaf: &unirpc.AssetLeaf{
Proof: lastProofBytes.Bytes(),
},
})
require.NoError(t.t, err)

return importResp
}

// sendAssetsToAddr spends the given input asset and sends the amount specified
// in the address to the Taproot output derived from the address.
func sendAssetsToAddr(t *harnessTest, sender *tapdHarness,
Expand Down
61 changes: 61 additions & 0 deletions itest/send_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1374,6 +1374,67 @@ func testSendMultipleCoins(t *harnessTest) {
AssertNonInteractiveRecvComplete(t.t, secondTapd, 5)
}

// testSendNoCourierUniverseImport tests that we can send assets to a node that
// has no courier, and then manually transfer the proof to the receiving using
// the universe proof import RPC method.
func testSendNoCourierUniverseImport(t *harnessTest) {
ctxb := context.Background()

// First, we'll make a normal assets with enough units.
rpcAssets := MintAssetsConfirmBatch(
t.t, t.lndHarness.Miner.Client, t.tapd,
[]*mintrpc.MintAssetRequest{simpleAssets[0]},
)

firstAsset := rpcAssets[0]
genInfo := firstAsset.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. We turn off the proof
// courier by supplying a dummy implementation.
secondTapd := setupTapdHarness(
t.t, t, t.lndHarness.Bob, t.universeServer,
func(params *tapdHarnessParams) {
params.proofCourier = &proof.MockProofCourier{}
},
)
defer func() {
require.NoError(t.t, secondTapd.stop(!*noDelete))
}()

// Next, we'll attempt to transfer some amount of assets[0] to the
// receiving node.
numUnitsSend := uint64(1200)

// Get a new address (which accepts the first asset) from the
// receiving node.
receiveAddr, err := secondTapd.NewAddr(ctxb, &taprpc.NewAddrRequest{
AssetId: genInfo.AssetId,
Amt: numUnitsSend,
})
require.NoError(t.t, err)
AssertAddrCreated(t.t, secondTapd, firstAsset, receiveAddr)

// Send the assets to the receiving node.
sendResp := sendAssetsToAddr(t, t.tapd, receiveAddr)

// Assert that the outbound transfer was confirmed.
expectedAmtAfterSend := firstAsset.Amount - numUnitsSend
ConfirmAndAssertOutboundTransfer(
t.t, t.lndHarness.Miner.Client, t.tapd, sendResp,
genInfo.AssetId,
[]uint64{expectedAmtAfterSend, numUnitsSend}, 0, 1,
)

// Since we disabled proof couriers, we need to manually transfer the
// proof from the sender to the receiver now. We use the universe RPC
// InsertProof method to do this.
sendProofUniRPC(t, t.tapd, secondTapd, receiveAddr.ScriptKey, genInfo)

// And now, the transfer should be completed on the receiver side too.
AssertNonInteractiveRecvComplete(t.t, secondTapd, 1)
}

// addProofTestVectorFromFile adds a proof test vector by extracting it from the
// proof file found at the given asset ID and script key.
func addProofTestVectorFromFile(t *testing.T, testName string,
Expand Down
8 changes: 8 additions & 0 deletions itest/test_list_on_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ var testCases = []*testCase{
test: testOfflineReceiverEventuallyReceives,
proofCourierType: proof.HashmailCourierType,
},
{
name: "addr send no proof courier with local universe import",
test: testSendNoCourierUniverseImport,
},
{
name: "basic send passive asset",
test: testBasicSendPassiveAsset,
Expand Down Expand Up @@ -179,6 +183,10 @@ var testCases = []*testCase{
name: "universe sync",
test: testUniverseSync,
},
{
name: "universe sync manual insert",
test: testUniverseManualSync,
},
{
name: "universe federation",
test: testUniverseFederation,
Expand Down
Loading

0 comments on commit 1741b14

Please sign in to comment.