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

check what happens when wallets on the same nodes are used in an htlc tx #469

Merged
merged 1 commit into from
May 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 35 additions & 2 deletions integration/token/interop/support.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ func Restart(network *integration.Infrastructure, ids ...string) {
time.Sleep(10 * time.Second)
}

func htlcLock(network *integration.Infrastructure, tmsID token.TMSID, id string, wallet string, typ string, amount uint64, receiver string, deadline time.Duration, hash []byte, hashFunc crypto.Hash, errorMsgs ...string) (string, []byte, []byte) {
func HTLCLock(network *integration.Infrastructure, tmsID token.TMSID, id string, wallet string, typ string, amount uint64, receiver string, deadline time.Duration, hash []byte, hashFunc crypto.Hash, errorMsgs ...string) (string, []byte, []byte) {
result, err := network.Client(id).CallView("htlc.lock", common.JSONMarshall(&htlc.Lock{
TMSID: tmsID,
ReclamationDeadline: deadline,
Expand Down Expand Up @@ -309,7 +309,7 @@ func htlcLock(network *integration.Infrastructure, tmsID token.TMSID, id string,
}
}

func htlcReclaimAll(network *integration.Infrastructure, id string, wallet string, errorMsgs ...string) {
func HTLCReclaimAll(network *integration.Infrastructure, id string, wallet string, errorMsgs ...string) {
txID, err := network.Client(id).CallView("htlc.reclaimAll", common.JSONMarshall(&htlc.ReclaimAll{
Wallet: wallet,
}))
Expand All @@ -325,6 +325,39 @@ func htlcReclaimAll(network *integration.Infrastructure, id string, wallet strin
}
}

func HTLCReclaimByHash(network *integration.Infrastructure, id string, wallet string, hash []byte, errorMsgs ...string) {
txID, err := network.Client(id).CallView("htlc.reclaimByHash", common.JSONMarshall(&htlc.ReclaimByHash{
Wallet: wallet,
Hash: hash,
}))
if len(errorMsgs) == 0 {
Expect(err).NotTo(HaveOccurred())
Expect(network.Client(id).IsTxFinal(common.JSONUnmarshalString(txID))).NotTo(HaveOccurred())
} else {
Expect(err).To(HaveOccurred())
for _, msg := range errorMsgs {
Expect(err.Error()).To(ContainSubstring(msg))
}
time.Sleep(5 * time.Second)
}
}

func HTLCCheckExistenceReceivedExpiredByHash(network *integration.Infrastructure, id string, wallet string, hash []byte, exists bool, errorMsgs ...string) {
_, err := network.Client(id).CallView("htlc.CheckExistenceReceivedExpiredByHash", common.JSONMarshall(&htlc.CheckExistenceReceivedExpiredByHash{
Wallet: wallet,
Hash: hash,
Exists: exists,
}))
if len(errorMsgs) == 0 {
Expect(err).NotTo(HaveOccurred())
} else {
Expect(err).To(HaveOccurred())
for _, msg := range errorMsgs {
Expect(err.Error()).To(ContainSubstring(msg))
}
}
}

func htlcClaim(network *integration.Infrastructure, tmsID token.TMSID, id string, wallet string, preImage []byte, errorMsgs ...string) string {
txIDBoxed, err := network.Client(id).CallView("htlc.claim", common.JSONMarshall(&htlc.Claim{
TMSID: tmsID,
Expand Down
28 changes: 17 additions & 11 deletions integration/token/interop/tests.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func TestHTLCSingleNetwork(network *integration.Infrastructure) {
RegisterAuditor(network)

// htlc (lock, failing claim, reclaim)
_, preImage, _ := htlcLock(network, token.TMSID{}, "alice", "", "USD", 10, "bob", 10*time.Second, nil, crypto.SHA512)
_, preImage, _ := HTLCLock(network, token.TMSID{}, "alice", "", "USD", 10, "bob", 10*time.Second, nil, crypto.SHA512)
CheckBalanceWithLockedAndHolding(network, "alice", "", "USD", 110, 0, 0, -1)
CheckBalanceWithLockedAndHolding(network, "alice", "", "EUR", 0, 0, 0, -1)
CheckBalanceWithLockedAndHolding(network, "bob", "", "EUR", 30, 0, 0, -1)
Expand All @@ -85,14 +85,14 @@ func TestHTLCSingleNetwork(network *integration.Infrastructure) {
CheckBalanceWithLockedAndHolding(network, "bob", "", "EUR", 30, 0, 0, -1)
CheckBalanceWithLockedAndHolding(network, "bob", "", "USD", 0, 0, 10, -1)

htlcReclaimAll(network, "alice", "")
HTLCReclaimAll(network, "alice", "")
CheckBalanceWithLockedAndHolding(network, "alice", "", "USD", 120, 0, 0, -1)
CheckBalanceWithLockedAndHolding(network, "alice", "", "EUR", 0, 0, 0, -1)
CheckBalanceWithLockedAndHolding(network, "bob", "", "EUR", 30, 0, 0, -1)
CheckBalanceWithLockedAndHolding(network, "bob", "", "USD", 0, 0, 0, -1)

// htlc (lock, claim)
_, preImage, _ = htlcLock(network, defaultTMSID, "alice", "", "USD", 20, "bob", 1*time.Hour, nil, crypto.SHA3_256)
_, preImage, _ = HTLCLock(network, defaultTMSID, "alice", "", "USD", 20, "bob", 1*time.Hour, nil, crypto.SHA3_256)
CheckBalanceWithLockedAndHolding(network, "alice", "", "USD", 100, 0, 0, -1)
CheckBalanceWithLockedAndHolding(network, "alice", "", "EUR", 0, 0, 0, -1)
CheckBalanceWithLockedAndHolding(network, "bob", "", "EUR", 30, 0, 0, -1)
Expand All @@ -105,7 +105,7 @@ func TestHTLCSingleNetwork(network *integration.Infrastructure) {
CheckBalanceWithLockedAndHolding(network, "bob", "", "USD", 20, 0, 0, -1)

// payment limit reached
htlcLock(network, defaultTMSID, "alice", "", "USD", uint64(views.Limit+10), "bob", 1*time.Hour, nil, crypto.SHA3_256, "payment limit reached")
HTLCLock(network, defaultTMSID, "alice", "", "USD", uint64(views.Limit+10), "bob", 1*time.Hour, nil, crypto.SHA3_256, "payment limit reached")
CheckBalanceWithLockedAndHolding(network, "alice", "", "USD", 100, 0, 0, -1)
CheckBalanceWithLockedAndHolding(network, "alice", "", "EUR", 0, 0, 0, -1)
CheckBalanceWithLockedAndHolding(network, "bob", "", "EUR", 30, 0, 0, -1)
Expand All @@ -126,15 +126,15 @@ func TestHTLCSingleNetwork(network *integration.Infrastructure) {
})

// lock two times with the same hash, the second lock should fail
_, _, hash := htlcLock(network, defaultTMSID, "alice", "", "USD", 1, "bob", 1*time.Hour, nil, crypto.SHA3_256)
failedLockTXID, _, _ := htlcLock(network, defaultTMSID, "alice", "", "USD", 1, "bob", 1*time.Hour, hash, crypto.SHA3_256,
_, _, hash := HTLCLock(network, defaultTMSID, "alice", "", "USD", 1, "bob", 1*time.Hour, nil, crypto.SHA3_256)
failedLockTXID, _, _ := HTLCLock(network, defaultTMSID, "alice", "", "USD", 1, "bob", 1*time.Hour, hash, crypto.SHA3_256,
fmt.Sprintf(
"entry with transfer metadata key [%s] is already occupied by [%s]",
htlc.LockKey(hash),
base64.StdEncoding.EncodeToString(htlc.LockValue(hash)),
),
)
htlcLock(network, defaultTMSID, "alice", "", "USD", 1, "bob", 1*time.Hour, nil, crypto.SHA3_256)
HTLCLock(network, defaultTMSID, "alice", "", "USD", 1, "bob", 1*time.Hour, nil, crypto.SHA3_256)

CheckPublicParams(network, defaultTMSID, "issuer", "auditor", "alice", "bob")
CheckOwnerDB(network, defaultTMSID, nil, "issuer", "auditor", "alice", "bob")
Expand Down Expand Up @@ -182,8 +182,8 @@ func TestHTLCTwoNetworks(network *integration.Infrastructure) {
IssueCashWithTMS(network, beta, "issuer", "", "USD", 30, "bob")
CheckBalanceAndHolding(network, "bob", "", "USD", 30, token.WithTMSID(beta))

_, preImage, hash := htlcLock(network, alpha, "alice", "", "EUR", 10, "bob", 1*time.Hour, nil, 0)
htlcLock(network, beta, "bob", "", "USD", 10, "alice", 1*time.Hour, hash, 0)
_, preImage, hash := HTLCLock(network, alpha, "alice", "", "EUR", 10, "bob", 1*time.Hour, nil, 0)
HTLCLock(network, beta, "bob", "", "USD", 10, "alice", 1*time.Hour, hash, 0)
htlcClaim(network, beta, "alice", "", preImage)
htlcClaim(network, alpha, "bob", "", preImage)

Expand Down Expand Up @@ -226,6 +226,12 @@ func TestHTLCTwoNetworks(network *integration.Infrastructure) {
bIDs := ListVaultUnspentTokens(network, beta, name)
CheckIfExistsInVault(network, beta, "auditor", bIDs)
}

// "alice" locks to "alice.id1", the deadline expires, "alice" reclaims, "alice.id1" checks the existence of an expired received locked token
_, _, h := HTLCLock(network, alpha, "alice", "", "EUR", 10, "alice.id1", 10*time.Second, nil, 0)
time.Sleep(10 * time.Second)
HTLCReclaimByHash(network, "alice", "", h)
HTLCCheckExistenceReceivedExpiredByHash(network, "alice", "alice.id1", h, false)
}

func TestHTLCNoCrossClaimTwoNetworks(network *integration.Infrastructure) {
Expand All @@ -244,8 +250,8 @@ func TestHTLCNoCrossClaimTwoNetworks(network *integration.Infrastructure) {
IssueCashWithTMS(network, beta, "issuer", "", "USD", 30, "bob.id1")
CheckBalanceAndHolding(network, "bob", "bob.id1", "USD", 30, token.WithTMSID(beta))

aliceLockTxID, preImage, hash := htlcLock(network, alpha, "alice", "alice.id1", "EUR", 10, "alice.id2", 30*time.Second, nil, 0)
bobLockTxID, _, _ := htlcLock(network, beta, "bob", "bob.id1", "USD", 10, "bob.id2", 30*time.Second, hash, 0)
aliceLockTxID, preImage, hash := HTLCLock(network, alpha, "alice", "alice.id1", "EUR", 10, "alice.id2", 30*time.Second, nil, 0)
bobLockTxID, _, _ := HTLCLock(network, beta, "bob", "bob.id1", "USD", 10, "bob.id2", 30*time.Second, hash, 0)

go func() { htlcClaim(network, alpha, "alice", "alice.id2", preImage) }()
go func() { htlcClaim(network, beta, "bob", "bob.id2", preImage) }()
Expand Down
2 changes: 2 additions & 0 deletions integration/token/interop/topology.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,8 @@ func HTLCTwoFabricNetworksTopology(tokenSDKDriver string) []api.Topology {
alice.RegisterViewFactory("htlc.lock", &htlc.LockViewFactory{})
alice.RegisterViewFactory("htlc.reclaimAll", &htlc.ReclaimAllViewFactory{})
alice.RegisterViewFactory("htlc.claim", &htlc.ClaimViewFactory{})
alice.RegisterViewFactory("htlc.reclaimByHash", &htlc.ReclaimByHashViewFactory{})
alice.RegisterViewFactory("htlc.CheckExistenceReceivedExpiredByHash", &htlc.CheckExistenceReceivedExpiredByHashViewFactory{})
alice.RegisterResponder(&htlc.LockAcceptView{}, &htlc.LockView{})
alice.RegisterViewFactory("htlc.fastExchange", &htlc.FastExchangeInitiatorViewFactory{})
alice.RegisterViewFactory("balance", &views2.BalanceViewFactory{})
Expand Down
99 changes: 99 additions & 0 deletions integration/token/interop/views/htlc/reclaim.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,102 @@ func (p *ReclaimAllViewFactory) NewView(in []byte) (view.View, error) {

return f, nil
}

// ReclaimByHash contains the input information to reclaim tokens
type ReclaimByHash struct {
// TMSID identifies the TMS to use to perform the token operation
TMSID token.TMSID
// Wallet is the identifier of the wallet that owns the tokens to reclaim
Wallet string
// Hash is the hash the reclaim should refer to
Hash []byte
}

// ReclaimByHashView is the view of a party who wants to reclaim all previously locked tokens with an expired timeout
type ReclaimByHashView struct {
*ReclaimByHash
}

func (r *ReclaimByHashView) Call(context view.Context) (interface{}, error) {
// The sender will select tokens owned by this wallet
senderWallet := htlc.GetWallet(context, r.Wallet, token.WithTMSID(r.TMSID))
assert.NotNil(senderWallet, "sender wallet [%s] not found", r.Wallet)

expired, err := htlc.Wallet(context, senderWallet).GetExpiredByHash(r.Hash)
assert.NoError(err, "cannot retrieve list of expired tokens")
assert.NotNil(expired, "no htlc script with hash [%v] has expired", r.Hash)

tx, err := htlc.NewAnonymousTransaction(
context,
ttx.WithAuditor(view2.GetIdentityProvider(context).Identity("auditor")),
ttx.WithTMSID(r.TMSID),
)
assert.NoError(err, "failed to create an htlc transaction")
assert.NoError(tx.Reclaim(senderWallet, expired), "failed adding a reclaim for [%s]", expired)

// The sender is ready to collect all the required signatures.
// In this case, the sender's and the auditor's signatures.
// Invoke the Token Chaincode to collect endorsements on the Token Request and prepare the relative transaction.
// This is all done in one shot running the following view.
_, err = context.RunView(htlc.NewCollectEndorsementsView(tx))
assert.NoError(err, "failed to collect endorsements on htlc transaction")

// Last but not least, the transaction is sent for ordering, and we wait for transaction finality.
_, err = context.RunView(htlc.NewOrderingAndFinalityView(tx))
assert.NoError(err, "failed to commit htlc transaction")

return tx.ID(), nil
}

type ReclaimByHashViewFactory struct{}

func (p *ReclaimByHashViewFactory) NewView(in []byte) (view.View, error) {
f := &ReclaimByHashView{ReclaimByHash: &ReclaimByHash{}}
err := json.Unmarshal(in, f.ReclaimByHash)
assert.NoError(err, "failed unmarshalling input")

return f, nil
}

// CheckExistenceReceivedExpiredByHash contains the input information to reclaim tokens
type CheckExistenceReceivedExpiredByHash struct {
// TMSID identifies the TMS to use to perform the token operation
TMSID token.TMSID
// Wallet is the identifier of the wallet that owns the tokens to reclaim
Wallet string
// Hash is the hash the lookup should refer to
Hash []byte
// Exists if true, enforce that a token exists, false otherwiese
Exists bool
}

// CheckExistenceReceivedExpiredByHashView is the view of a party who wants to reclaim all previously locked tokens with an expired timeout
type CheckExistenceReceivedExpiredByHashView struct {
*CheckExistenceReceivedExpiredByHash
}

func (r *CheckExistenceReceivedExpiredByHashView) Call(context view.Context) (interface{}, error) {
// The sender will select tokens owned by this wallet
senderWallet := htlc.GetWallet(context, r.Wallet, token.WithTMSID(r.TMSID))
assert.NotNil(senderWallet, "sender wallet [%s] not found", r.Wallet)

expired, err := htlc.Wallet(context, senderWallet).GetExpiredReceivedTokenByHash(r.Hash)
if r.Exists {
assert.NoError(err, "cannot retrieve expired received token by hash [%s]", r.Hash)
assert.NotNil(expired, "no htlc script with hash [%v] has expired", r.Hash)
} else {
assert.Error(err)
assert.True(expired == nil)
}
return nil, nil
}

type CheckExistenceReceivedExpiredByHashViewFactory struct{}

func (p *CheckExistenceReceivedExpiredByHashViewFactory) NewView(in []byte) (view.View, error) {
f := &CheckExistenceReceivedExpiredByHashView{CheckExistenceReceivedExpiredByHash: &CheckExistenceReceivedExpiredByHash{}}
err := json.Unmarshal(in, f.CheckExistenceReceivedExpiredByHash)
assert.NoError(err, "failed unmarshalling input")

return f, nil
}
36 changes: 36 additions & 0 deletions token/services/interop/htlc/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,24 @@ func (w *OwnerWallet) ListTokensAsSender(opts ...token.ListTokensOption) (*Filte
return w.filterIterator(compiledOpts.TokenType, true, SelectNonExpired)
}

// GetExpiredByHash returns the expired htlc-token whose sender id is in this wallet and whose hash is equal to the one passed as argument.
// It fails if no tokens are found or if more than one token is found.
func (w *OwnerWallet) GetExpiredByHash(hash []byte, opts ...token.ListTokensOption) (*token2.UnspentToken, error) {
compiledOpts, err := token.CompileListTokensOption(opts...)
if err != nil {
return nil, errors.Wrapf(err, "failed to compile options")
}

tokens, err := w.filter(compiledOpts.TokenType, true, (&ExpiredAndHashSelector{Hash: hash}).Select)
if err != nil {
return nil, errors.Wrapf(err, "failed to filter")
}
if len(tokens.Tokens) != 1 {
return nil, errors.Errorf("expected to find only one token for the hash [%v], found [%d]", hash, len(tokens.Tokens))
}
return tokens.Tokens[0], nil
}

// ListExpired returns a list of expired htlc-tokens whose sender id is in this wallet
func (w *OwnerWallet) ListExpired(opts ...token.ListTokensOption) (*token2.UnspentTokens, error) {
compiledOpts, err := token.CompileListTokensOption(opts...)
Expand Down Expand Up @@ -104,6 +122,24 @@ func (w *OwnerWallet) ListTokensIterator(opts ...token.ListTokensOption) (*Filte
return w.filterIterator(compiledOpts.TokenType, false, SelectNonExpired)
}

// GetExpiredReceivedTokenByHash returns the expired htlc-token that matches the passed options, whose recipient belongs to this wallet, is expired, and hash the same hash.
// It fails if no tokens are found or if more than one token is found.
func (w *OwnerWallet) GetExpiredReceivedTokenByHash(hash []byte, opts ...token.ListTokensOption) (*token2.UnspentToken, error) {
compiledOpts, err := token.CompileListTokensOption(opts...)
if err != nil {
return nil, errors.Wrapf(err, "failed to compile options")
}

tokens, err := w.filter(compiledOpts.TokenType, false, (&ExpiredAndHashSelector{Hash: hash}).Select)
if err != nil {
return nil, errors.Wrapf(err, "failed to filter")
}
if len(tokens.Tokens) != 1 {
return nil, errors.Errorf("expected to find only one token for the hash [%v], found [%d]", hash, len(tokens.Tokens))
}
return tokens.Tokens[0], nil
}

// ListExpiredReceivedTokens returns a list of tokens that matches the passed options, whose recipient belongs to this wallet, and are expired
func (w *OwnerWallet) ListExpiredReceivedTokens(opts ...token.ListTokensOption) (*token2.UnspentTokens, error) {
compiledOpts, err := token.CompileListTokensOption(opts...)
Expand Down
12 changes: 12 additions & 0 deletions token/services/interop/htlc/wallet_filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,15 @@ func SelectNonExpired(tok *token.UnspentToken, script *Script) (bool, error) {
logger.Debugf("[%v]>=[%v], sender [%s], recipient [%s]?", script.Deadline, now, script.Sender.UniqueID(), script.Recipient.UniqueID())
return script.Deadline.After(now), nil
}

// ExpiredAndHashSelector selects expired htlc-tokens with a specific hash
type ExpiredAndHashSelector struct {
Hash []byte
}

func (s *ExpiredAndHashSelector) Select(tok *token.UnspentToken, script *Script) (bool, error) {
logger.Debugf("token [%s,%s,%s,%s] contains a script? Yes", tok.Id, view.Identity(tok.Owner.Raw).UniqueID(), tok.Type, tok.Quantity)
now := time.Now()
logger.Debugf("[%v]<=[%v], sender [%s], recipient [%s]?", script.Deadline, now, script.Sender.UniqueID(), script.Recipient.UniqueID())
return script.Deadline.Before(now) && bytes.Equal(script.HashInfo.Hash, s.Hash), nil
}