Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

asset transfer #639

Draft
wants to merge 23 commits into
base: main
Choose a base branch
from
Draft

asset transfer #639

wants to merge 23 commits into from

Conversation

adecaro
Copy link
Contributor

@adecaro adecaro commented May 27, 2024

This PR adapts the code in #437 to the current shape of the token-sdk. The code is mostly contribution of @HagarMeir .

To be addressed in another PR:

  • Unit tests
  • Resolve TODO
  • Additional integration tests

@adecaro adecaro force-pushed the f-447-bis branch 3 times, most recently from 33a1ca7 to 13f5dd3 Compare May 27, 2024 13:02
@adecaro adecaro requested a review from alexandrosfilios May 27, 2024 13:44
@adecaro adecaro mentioned this pull request May 27, 2024
@adecaro adecaro linked an issue May 27, 2024 that may be closed by this pull request
@adecaro adecaro self-assigned this May 27, 2024
@adecaro adecaro force-pushed the f-447-bis branch 2 times, most recently from bd56a9f to 6040977 Compare June 1, 2024 05:13
@adecaro adecaro force-pushed the f-447-bis branch 2 times, most recently from 321360b to f9d2870 Compare November 12, 2024 14:22
@adecaro adecaro requested a review from KElkhiyaoui November 16, 2024 07:02
Signed-off-by: Angelo De Caro <[email protected]>
Signed-off-by: Angelo De Caro <[email protected]>
Signed-off-by: Angelo De Caro <[email protected]>
Signed-off-by: Angelo De Caro <[email protected]>
Signed-off-by: Angelo De Caro <[email protected]>
Signed-off-by: Angelo De Caro <[email protected]>
Signed-off-by: Angelo De Caro <[email protected]>
Signed-off-by: Angelo De Caro <[email protected]>
Signed-off-by: Angelo De Caro <[email protected]>
Signed-off-by: Angelo De Caro <[email protected]>
Signed-off-by: Angelo De Caro <[email protected]>
Signed-off-by: Angelo De Caro <[email protected]>

func TestAssetTransferWithTwoNetworks(network *integration.Infrastructure) {
// give some time to the nodes to get the public parameters
time.Sleep(10 * time.Second)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this sleep is better after start

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have it in all tests, maybe we can replace it with a different check in another PR.

assert.Equal(me, pledgeInfo.Script.Recipient, "recipient is different [%s]!=[%s]", me, pledgeInfo.Script.Recipient)

// Store the pledge and send a notification back
_, err = context.RunView(pledge.NewAcceptPledgeIndoView(pledgeInfo))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Info*

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's this?

Copy link
Contributor

@KElkhiyaoui KElkhiyaoui Nov 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@adecaro There is a typo in the name of the method.
NewAcceptPledgeInfoView instead of NewAcceptPledgeIndoView

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

heheheh, good catch.

@@ -23,7 +24,7 @@ import (
var _ = Describe("DLog end to end", func() {
BeforeEach(func() { token.Drivers = append(token.Drivers, "dlog") })

for _, t := range integration2.AllTestTypes {
for _, t := range integration2.WebSocketNoReplicationOnly {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to revert this, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

switch {
case output.IsHTLC():
// check script details
script, err := output.HTLC()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we could use just one interface for both, like

Script() ValidatableScript

that only contains the method validate. That would be maybe more extensible

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is just an example, one might perform more checks...

"github.com/hyperledger-labs/fabric-token-sdk/token/token"
)

// Claim contains the input information to claim a token
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe to give more context

claim a token that has already been pledged, but not yet claimed by someone else, reclaimed by the owner or  redeemed by the issuer of the network.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added the comment to the View

assert.NoError(err, "failed to request claim")

return view2.AsResponder(context, session,
func(context view.Context) (interface{}, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could extract this to a view and re-use the logic in both pledge and fast pledge

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is just an example in an integration test.

}

func IssueActionMetadata(attributes map[string][]byte, opts *driver.IssueOptions) (map[string][]byte, error) {
var metadata *IssueMetadata
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe to avoid nesting

if len(opts.Attributes) == 0 {
 return nil, nil
}

tokenID, hasTokenID := ...
network, hasNetwork := ...
if !hasTokenID || !hasNetwork {
 return nil, nil
}

marshalled, err := json.Marshal(&IssueMetadata{tokenID.(*token.ID), network.(string)})
if err != nil {return nil, err}

key := common.Hashable(marshalled).String()
attributes := map[string][]byte{
 key: marshalled
}

if proofOpt, ok := ...; ok {
 attributes[key + ProofOfClaimSuffix] = proofOpt.([]byte)
}
return attributes, nil

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay

OriginNetwork string
}

func IssueActionMetadata(attributes map[string][]byte, opts *driver.IssueOptions) (map[string][]byte, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we don't use the attributes as an input, it's better to avoid passing it as a param for more clarity. Otherwise this implies that the attributes we output depend on the input attributes. Which would require that the different metadataGenerators be called in a specific order.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

attributes is used an input and output, you can have multiple functions manipulating these attributes

var metadata *IssueMetadata
var proof []byte
if len(opts.Attributes) != 0 {
tokenID, ok1 := opts.Attributes["github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/pledge/tokenID"]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could have these attributes as constants

Signed-off-by: Angelo De Caro <[email protected]>
Signed-off-by: Angelo De Caro <[email protected]>
assert.NoError(err, "failed to retrieve pledged token during redeem")

// make sure token was in fact claimed in the other network
proof, err := pledge.RequestProofOfTokenWithMetadataExistence(context, rv.TokenID, rv.TMSID, script)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we change the name of the method to RequestProofOfExistenceOfTokenWithMetadata?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay

Signed-off-by: Angelo De Caro <[email protected]>
if owner.Type == htlc.ScriptType {
// Then, the first output must be compatible with this input.
if len(ctx.TransferAction.GetOutputs()) != 1 {
return errors.New("invalid transfer action: an htlc script only transfers the ownership of a token")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should also enforce that len(Inputs) == 1 since a claim and a reclaim are just ownership transfers.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to have multiple inputs that point to the same output owner?


tokenID, hasTokenID := opts.Attributes[TokenIDKey]
network, hasNetwork := opts.Attributes[NetworkKey]
if !hasTokenID && !hasNetwork {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we perform this check? It does not seem to have an impact on the output of the method.

return err
}
if time.Now().Before(script.Deadline) {
return errors.New("cannot reclaim pledge yet: wait for timeout to elapse.")
Copy link
Contributor

@KElkhiyaoui KElkhiyaoui Nov 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's both reclaim and redeem. Shall we say cannot change the state of the pledge yet?

logger.Debugf("Is Mine [%s,%s,%s]? [%b] with [%s]", view2.Identity(tok.Owner.Raw), tok.Type, tok.Quantity, len(ids) != 0, ids)
return "", ids, len(ids) != 0
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does Issued implement. A comment will be helpful.

v, ok := ctx.TransferAction.GetMetadata()[pledge.MetadataKey+script.ID]
if !ok {
return errors.Errorf("empty metadata for pledge script with identifier %s", script.ID)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What information does this metadata convey?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Going through the rest of the code, this seems to enforce to enforce the uniqueness of PledgeID.

}

func (t *Output) GetOwner() []byte {
if t.Owner != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be if ! t.IsRedeem()?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or IsRedeem can be equivalently return len(t.GetOwner()) == 0

}, nil
}

func (p *StateQueryExecutor) Exist(tokenID *token.ID) ([]byte, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exists?

logger.Debugf("failed unmarshalling pledge script [%s]: [%s]", tok, err)
return nil
}
if script.Sender.IsNone() || script.Recipient.IsNone() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we return an error here?


// IDExists scans the ledger for a pledge identifier, taking into account the timeout
// IDExists returns true, if entry identified by key (MetadataKey+pledgeID) is occupied.
func IDExists(ctx view.Context, pledgeID string, timeout time.Duration, opts ...token.ServiceOption) (bool, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method does not seem to be used elsewhere.

return &ReceiveTransactionView{}
}

func (f *ReceiveTransactionView) Call(context view.Context) (interface{}, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we use v or rtv instead of f?

if err != nil {
return errors.Errorf("input owner at index [%d] cannot be unmarshalled", index)
}
scriptInf := &htlc.ScriptInfo{}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall move ScriptInfo under a common repo. Since this is a bit confusing.

if err := json.Unmarshal(token.Owner.OwnerInfo, scriptInf); err != nil {
return errors.Wrapf(err, "failed to unmarshal script info")
}
scriptSender, _, scriptIssuer, err := pledge.GetScriptSenderAndRecipient(owner)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we call the method GetScriptStakeholders?

if err := session.Receive(recipientData); err != nil {
return nil, errors.Wrapf(err, "failed to receive recipient data")
}
//if err := tms.WalletManager().RegisterRecipientIdentity(recipientData); err != nil {
Copy link
Contributor

@KElkhiyaoui KElkhiyaoui Nov 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we remove the commented code? Or is this a todo?

}
logger.Debugf("proof of existence in claim request is valid [%s]", err)

if !req.ClaimDeadline.After(time.Now()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we start with checking the time first?

return nil, errors.Wrapf(err, "failed storing pledge info")
}

// raw, err := a.info.Bytes()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we remove this?

if err != nil {
return nil, errors.WithMessagef(err, "failed getting prover for [%s]", c.source)
}
return p.Exist(c.tokenID)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exists?

}, nil
}

func (v *StateVerifier) VerifyProofExistence(proofRaw []byte, tokenID *token.ID, metadata []byte) error {
Copy link
Contributor

@KElkhiyaoui KElkhiyaoui Nov 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about VerifyProofOfTokenExistence?

return nil
}

func (v *StateVerifier) VerifyProofNonExistence(proofRaw []byte, tokenID *token.ID, origin string, deadline time.Time) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about VerifyProofOfTokenNonExistence

@@ -88,6 +94,25 @@ func (t *Translator) CreateTransferActionMetadataKey(key string) (translator.Key
return createCompositeKey(TransferActionMetadataPrefix, []string{key})
}

func (t *Translator) CreateProofOfExistenceKey(tokenId *token.ID) (string, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe CreateProofOfTokenExistenceKey

})
}

func (t *Translator) CreateProofOfMetadataExistenceKey(tokenID *token.ID, origin string) (string, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe CreateProofOfTokenMetadataExistenceKey

return createCompositeKey(ProofOfExistencePrefix, []string{id})
}

func (t *Translator) CreateProofOfNonExistenceKey(tokenID *token.ID, origin string) (string, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe CreateProofOfTokenMetadataNonExistenceKey

integration/token/common/sdk/fall/sdk.go Show resolved Hide resolved

if fabricEnabled {
return errors.Join(
digutils.Register[fabric2.RelayProvider](p.Container()),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do all of the apps that use the fdlog sdk need the relay and pledge services? Otherwise maybe itd make more sense to extend to another sdk

ts, selector := newTestSuiteInteropAssetTransfer(t.CommType, t.ReplicationFactor, "alice", "bob")
AfterEach(ts.TearDown)
BeforeEach(ts.Setup)
It("Performed a cross network asset transfer", Label("T7"), func() { interop.TestAssetTransferWithTwoNetworks(ts.II, selector) })
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Until we figure out a solution, maybe we could run both legs of this test as T7-websocket and T7-libp2p with a todo

destination string
}

func NewCollectProofOfTokenWithMetadataExistenceView(tokenID *token2.ID, origin string, destination string) *CollectProofOfTokenWithMetadataExistenceView {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it would be handy to use specific types for origin and destination like NetworkURL or NamespaceURL, because it is a bit ambiguous

}

var res *Info
for it.HasNext() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would the disadvantage be if we used a db? Or if we added tokenID as part of the compositeID

)

func MyOwnerWallet(sp view.ServiceProvider) (*token.OwnerWallet, error) {
w := token.GetManagementService(sp).WalletManager().OwnerWallet("")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return GetOwnerWallet(sp, "")

var res []*token2.UnspentToken
var scripts []*Script
for _, tok := range unspentTokens.Tokens {
if tok.Id.String() == tokenID.String() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We cannot have multiple tokens with the same id. So I would expose another method from the query service GetUnspentToken(id *token2.ID, typ string) (*token2.UnspentToken, error). As an interim solution we can have a decorator that just iterates and finds it (though I think a DB query is equally fast to implement).. In any case I would return at the end of this if.

ListUnspentTokens() (*token2.UnspentTokens, error)
}

type IssuerWallet struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The functionality is the same for OwnerWallet. I would create one type wallet and then

type IssuerWallet wallet
type OwnerWallet wallet

for _, beneficiary := range []struct {
identity view2.Identity
desc string
prefix string
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need prefix since it is only used in this method and it can be constructed by desc.

} {
logger.Debugf("Is Mine [%s,%s,%s] as a %s?", view2.Identity(tok.Owner.Raw), tok.Type, tok.Quantity, beneficiary.desc)
// TODO: differentiate better
if wallet, err := s.WalletService.OwnerWallet(beneficiary.identity); err == nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could extend this method to take more identities in the input to avoid redundant locks in the future.

@adecaro adecaro marked this pull request as draft November 26, 2024 14:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Introduce asset transfer between two fabric networks
3 participants