Skip to content

Enhanced Redeem: add option to specify the issuer #1031

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

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
Draft
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
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,10 @@ clean-fabric-peer-images:
docker images -a | grep "_peer.org" | awk '{print $3}' | xargs docker rmi
docker images -a | grep "_peer_" | awk '{print $3}' | xargs docker rmi

.PHONY: clean-all-containers
clean-all-containers:
@if [ -n "$$(docker ps -aq)" ]; then docker rm -f $$(docker ps -aq); else echo "No containers to remove"; fi

.PHONY: tokengen
tokengen:
@go install ./cmd/tokengen
Expand Down
7 changes: 7 additions & 0 deletions integration/token/fungible/dlog/dlog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,13 @@ var _ = Describe("EndToEnd", func() {
AfterEach(ts.TearDown)
It("succeeded", Label("T12"), func() { fungible.TestMultiSig(ts.II, selector) })
})

Describe("Redeem flow", t.Label, func() {
ts, selector := newTestSuite(t.CommType, Aries, t.ReplicationFactor, "", "alice", "bob", "charlie")
BeforeEach(ts.Setup)
AfterEach(ts.TearDown)
It("succeeded", Label("T13"), func() { fungible.TestRedeem(ts.II, selector, "default") })
})
}

for _, tokenSelector := range integration2.TokenSelectors {
Expand Down
1 change: 1 addition & 0 deletions integration/token/fungible/fabtoken/fabtoken_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ var _ = Describe("EndToEnd", func() {
It("Test Identity Revocation", Label("T3"), func() { fungible.TestRevokeIdentity(ts.II, "auditor", selector) })
It("Test Remote Wallet (GRPC)", Label("T4"), func() { fungible.TestRemoteOwnerWallet(ts.II, "auditor", selector, false) })
It("Test Remote Wallet (WebSocket)", Label("T5"), func() { fungible.TestRemoteOwnerWallet(ts.II, "auditor", selector, true) })
It("Test redeem flow", Label("T6"), func() { fungible.TestRedeem(ts.II, selector, "default") })
})
}
})
Expand Down
1 change: 1 addition & 0 deletions integration/token/fungible/odlog/dlog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ var _ = Describe("Orion EndToEnd", func() {
BeforeEach(ts.Setup)
AfterEach(ts.TearDown)
It("succeeded", func() { fungible.TestAll(ts.II, "auditor", nil, true, selector) })
It("Test redeem flow", Label("T1"), func() { fungible.TestRedeem(ts.II, selector, "orion") })
})
}
})
Expand Down
1 change: 1 addition & 0 deletions integration/token/fungible/ofabtoken/fabtoken_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ var _ = Describe("Orion EndToEnd", func() {
BeforeEach(ts.Setup)
AfterEach(ts.TearDown)
It("succeeded", func() { fungible.TestAll(ts.II, "auditor", nil, true, selector) })
It("Test redeem flow", Label("T1"), func() { fungible.TestRedeem(ts.II, selector, "orion") })
})
}
})
Expand Down
1 change: 1 addition & 0 deletions integration/token/fungible/sdk/auditor/sdk.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ func (p *SDK) Install() error {
registry.RegisterFactory("DoesWalletExist", &views.DoesWalletExistViewFactory{}),
registry.RegisterFactory("TxFinality", &views1.TxFinalityViewFactory{}),
registry.RegisterFactory("GetPublicParams", &views.GetPublicParamsViewFactory{}),
registry.RegisterFactory("SetBinding", &views.SetBindingViewFactory{}),
)
}); err != nil {
return errors.WithMessage(err, "failed to install auditor's views")
Expand Down
2 changes: 2 additions & 0 deletions integration/token/fungible/sdk/issuer/sdk.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,10 @@ func (p *SDK) Install() error {
registry.RegisterFactory("DoesWalletExist", &views.DoesWalletExistViewFactory{}),
registry.RegisterFactory("TxFinality", &views1.TxFinalityViewFactory{}),
registry.RegisterFactory("issue", &views.IssueCashViewFactory{}),
registry.RegisterFactory("SetBinding", &views.SetBindingViewFactory{}),
registry.RegisterResponder(&views.WithdrawalResponderView{}, &views.WithdrawalInitiatorView{}),
registry.RegisterResponder(&views.TokensUpgradeResponderView{}, &views.TokensUpgradeInitiatorView{}),
registry.RegisterResponder(&views.IssuerRedeemAcceptView{}, &views.RedeemView{}),
)
}); err != nil {
return errors.WithMessage(err, "failed to install issuer's views")
Expand Down
1 change: 1 addition & 0 deletions integration/token/fungible/sdk/party/sdk.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ func (p *SDK) Install() error {
registry.RegisterFactory("TokenSelectorUnlock", &views.TokenSelectorUnlockViewFactory{}),
registry.RegisterFactory("FinalityWithTimeout", &views.FinalityWithTimeoutViewFactory{}),
registry.RegisterFactory("GetRevocationHandle", &views.GetRevocationHandleViewFactory{}),
registry.RegisterFactory("SetBinding", &views.SetBindingViewFactory{}),
registry.RegisterResponder(&views.AcceptCashView{}, &views.IssueCashView{}),
registry.RegisterResponder(&views.AcceptCashView{}, &views.TransferView{}),
registry.RegisterResponder(&views.AcceptCashView{}, &views.TransferWithSelectorView{}),
Expand Down
32 changes: 24 additions & 8 deletions integration/token/fungible/support.go
Original file line number Diff line number Diff line change
Expand Up @@ -806,17 +806,18 @@ func TransferCashWithSelector(network *integration.Infrastructure, sender *token
}
}

func RedeemCash(network *integration.Infrastructure, id *token3.NodeReference, wallet string, typ token.Type, amount uint64, auditor *token3.NodeReference) {
RedeemCashForTMSID(network, id, wallet, typ, amount, auditor, nil)
func RedeemCash(network *integration.Infrastructure, id *token3.NodeReference, wallet string, typ token.Type, amount uint64, auditor *token3.NodeReference, issuerPublicKey []byte) {
RedeemCashForTMSID(network, id, wallet, typ, amount, auditor, issuerPublicKey, nil)
}

func RedeemCashForTMSID(network *integration.Infrastructure, id *token3.NodeReference, wallet string, typ token.Type, amount uint64, auditor *token3.NodeReference, tmsID *token2.TMSID) {
func RedeemCashForTMSID(network *integration.Infrastructure, id *token3.NodeReference, wallet string, typ token.Type, amount uint64, auditor *token3.NodeReference, issuerPublicKey []byte, tmsID *token2.TMSID) {
txid, err := network.Client(id.ReplicaName()).CallView("redeem", common.JSONMarshall(&views.Redeem{
Auditor: auditor.Id(),
Wallet: wallet,
Type: typ,
Amount: amount,
TMSID: tmsID,
Auditor: auditor.Id(),
IssuerPublicKey: issuerPublicKey,
Wallet: wallet,
Type: typ,
Amount: amount,
TMSID: tmsID,
}))
Expect(err).NotTo(HaveOccurred())
common2.CheckFinality(network, auditor, common.JSONUnmarshalString(txid), tmsID, false)
Expand Down Expand Up @@ -1346,3 +1347,18 @@ func MultiSigSpendCashForTMSID(network *integration.Infrastructure, sender *toke
return txID

}

func SetBinding(network *integration.Infrastructure, issuer *token3.NodeReference, issuerPublicKey []byte, onNodes ...*token3.NodeReference) {
for _, node := range onNodes {
for _, nodeReplica := range node.AllNames() {
for _, issuerName := range issuer.AllNames() {
_, err := network.Client(nodeReplica).CallView("SetBinding", common.JSONMarshall(&views.Binding{
FSCNodeIdentity: network.Identity(issuerName), // issuer's network node identity.
Alias: issuerPublicKey, // issuer's public key for the token issuance
}))
Expect(err).NotTo(HaveOccurred())

}
}
}
}
30 changes: 27 additions & 3 deletions integration/token/fungible/tests.go
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,7 @@ func TestAll(network *integration.Infrastructure, auditorId string, onRestart On
Expect(ut.Sum(64).ToBigInt().Cmp(big.NewInt(111))).To(BeEquivalentTo(0), "got [%d], expected 111", ut.Sum(64).ToBigInt())
Expect(ut.ByType("USD").Count()).To(BeEquivalentTo(ut.Count()))

RedeemCash(network, bob, "", "USD", 11, auditor)
RedeemCash(network, bob, "", "USD", 11, auditor, nil)
t10 := time.Now()
CheckAcceptedTransactions(network, bob, "", BobAcceptedTransactions[:6], nil, nil, nil)
CheckAcceptedTransactions(network, bob, "", BobAcceptedTransactions[5:6], nil, nil, nil, ttxdb.Redeem)
Expand Down Expand Up @@ -456,7 +456,7 @@ func TestAll(network *integration.Infrastructure, auditorId string, onRestart On
CheckSpending(network, alice, "", "USD", auditor, 121)
CheckSpending(network, bob, "", "EUR", auditor, 10)

RedeemCash(network, bob, "", "USD", 10, auditor)
RedeemCash(network, bob, "", "USD", 10, auditor, nil)
CheckBalanceAndHolding(network, bob, "", "USD", 110, auditor)
CheckSpending(network, bob, "", "USD", auditor, 21)

Expand Down Expand Up @@ -952,7 +952,7 @@ func TestMixed(network *integration.Infrastructure, onRestart OnRestartFunc, sel
TransferCashForTMSID(network, alice, "", "USD", 20, bob, auditor1, dlogId)
TransferCashForTMSID(network, alice, "", "USD", 30, bob, auditor2, fabTokenId)

RedeemCashForTMSID(network, bob, "", "USD", 11, auditor1, dlogId)
RedeemCashForTMSID(network, bob, "", "USD", 11, auditor1, nil, dlogId)
CheckSpendingForTMSID(network, bob, "", "USD", auditor1, 11, dlogId)

CheckBalanceAndHoldingForTMSID(network, alice, "", "USD", 90, auditor1, dlogId)
Expand Down Expand Up @@ -1430,3 +1430,27 @@ func TestMultiSig(network *integration.Infrastructure, sel *token3.ReplicaSelect
CheckCoOwnedBalance(network, charlie, "", "USD", 0)
CheckCoOwnedBalance(network, manager, "", "USD", 0)
}

func TestRedeem(network *integration.Infrastructure, sel *token3.ReplicaSelector, networkName string) {
tms := GetTMSByNetworkName(network, networkName)
auditor := sel.Get("auditor")
issuer := sel.Get("issuer")
alice := sel.Get("alice")
RegisterAuditor(network, auditor)

// give some time to the nodes to get the public parameters
time.Sleep(10 * time.Second)

SetKVSEntry(network, issuer, "auditor", auditor.Id())
var issuerPublicKey []byte = GetIssuerIdentity(tms, issuer.Id())
SetBinding(network, issuer, issuerPublicKey, alice)
CheckPublicParams(network, issuer, auditor, alice)

IssueCash(network, "", "USD", 110, alice, auditor, true, issuer)
CheckBalance(network, alice, "", "USD", 110)
CheckHolding(network, alice, "", "USD", 110, auditor)

RedeemCash(network, alice, "", "USD", 10, auditor, issuerPublicKey)
CheckBalance(network, alice, "", "USD", 100)
CheckHolding(network, alice, "", "USD", 100, auditor)
}
3 changes: 3 additions & 0 deletions integration/token/fungible/topology/topology.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,9 @@ func Topology(opts common.Opts) []api.Topology {
}
}

// Add the default issuer
tms.AddIssuer(issuer)

// any extra TMS
for _, tmsOpts := range opts.ExtraTMSs {
tms := tokenTopology.AddTMS(nodeList, backendNetwork, backendChannel, tmsOpts.TokenSDKDriver)
Expand Down
42 changes: 42 additions & 0 deletions integration/token/fungible/views/binding.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package views

import (
"encoding/json"

view2 "github.com/hyperledger-labs/fabric-smart-client/platform/view"
"github.com/hyperledger-labs/fabric-smart-client/platform/view/services/assert"
"github.com/hyperledger-labs/fabric-smart-client/platform/view/view"
"github.com/pkg/errors"
)

type Binding struct {
FSCNodeIdentity view.Identity
Alias view.Identity
}

type SetBindingView struct {
*Binding
}

func (s *SetBindingView) Call(context view.Context) (interface{}, error) {
es := view2.GetEndpointService(context)
if err := es.Bind(s.FSCNodeIdentity, s.Alias); err != nil {
return nil, errors.Wrap(err, `failed to bind fsc node identity`)
}
return nil, nil
}

type SetBindingViewFactory struct{}

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

return f, nil
}
24 changes: 24 additions & 0 deletions integration/token/fungible/views/redeem.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ package views
import (
"encoding/json"

"github.com/pkg/errors"

view2 "github.com/hyperledger-labs/fabric-smart-client/platform/view"
"github.com/hyperledger-labs/fabric-smart-client/platform/view/services/assert"
"github.com/hyperledger-labs/fabric-smart-client/platform/view/view"
Expand All @@ -21,6 +23,8 @@ import (
type Redeem struct {
// Auditor is the name of the auditor that must be contacted to approve the operation
Auditor string
// IssuerPublicKey is the (optional) PK of an issuer that would need to be contacted to approve the operation
IssuerPublicKey []byte
// Wallet is the identifier of the wallet that owns the tokens to redeem
Wallet string
// TokenIDs contains a list of token ids to redeem. If empty, tokens are selected on the spot.
Expand Down Expand Up @@ -64,6 +68,7 @@ func (t *RedeemView) Call(context view.Context) (interface{}, error) {
senderWallet,
t.Type,
t.Amount,
t.IssuerPublicKey,
token2.WithTokenIDs(t.TokenIDs...),
)
assert.NoError(err, "failed adding new tokens")
Expand Down Expand Up @@ -106,3 +111,22 @@ func (p *RedeemViewFactory) NewView(in []byte) (view.View, error) {
assert.NoError(err, "failed unmarshalling input")
return f, nil
}

type IssuerRedeemAcceptView struct{}

func (a *IssuerRedeemAcceptView) Call(context view.Context) (interface{}, error) {
// Verify Token Request against Metadata
tx, err := ttx.ReceiveTransaction(context)
if err != nil {
return nil, errors.Wrap(err, "failed getting transaction")
}

// Sign the transaction
// EndorserView generates the signature and send in back on the same communication session
_, err = context.RunView(ttx.NewEndorseView(tx))
if err != nil {
return nil, errors.Wrap(err, "issuer failed endorsing transaction")
}

return nil, nil
}
4 changes: 3 additions & 1 deletion token/core/fabtoken/v1/core/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,8 @@ type TransferAction struct {
Outputs []*Output
// Metadata contains the transfer action's metadata
Metadata map[string][]byte
// ExtraSigners contains the identities that need to sign the transfer action
ESigners []driver.Identity
}

func (t *TransferAction) NumInputs() int {
Expand Down Expand Up @@ -282,5 +284,5 @@ func (t *TransferAction) Validate() error {
}

func (t *TransferAction) ExtraSigners() []driver.Identity {
return nil
return t.ESigners
}
25 changes: 24 additions & 1 deletion token/core/fabtoken/v1/transfer.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ func NewTransferService(

// Transfer returns a TransferAction as a function of the passed arguments
// It also returns the corresponding TransferMetadata
func (s *TransferService) Transfer(ctx context.Context, _ string, _ driver.OwnerWallet, tokenIDs []*token.ID, Outputs []*token.Token, opts *driver.TransferOptions) (driver.TransferAction, *driver.TransferMetadata, error) {
func (s *TransferService) Transfer(ctx context.Context, _ string, _ driver.OwnerWallet, tokenIDs []*token.ID, Outputs []*token.Token, issuerPublicKey []byte, opts *driver.TransferOptions) (driver.TransferAction, *driver.TransferMetadata, error) {
var isRedeem bool
// select inputs
inputTokens, err := s.TokenLoader.GetTokens(tokenIDs)
if err != nil {
Expand All @@ -66,6 +67,10 @@ func (s *TransferService) Transfer(ctx context.Context, _ string, _ driver.Owner
Type: output.Type,
Quantity: output.Quantity,
})

if len(output.Owner) == 0 {
isRedeem = true
}
}

// assemble transfer transferMetadata
Expand Down Expand Up @@ -146,13 +151,31 @@ func (s *TransferService) Transfer(ctx context.Context, _ string, _ driver.Owner
InputTokens: inputs,
Outputs: outs,
Metadata: meta.TransferActionMetadata(opts.Attributes),
ESigners: nil,
}
transferMetadata := &driver.TransferMetadata{
Inputs: transferInputsMetadata,
Outputs: transferOutputsMetadata,
ExtraSigners: nil,
}

// add redeem signer
if isRedeem {
var issuer driver.Identity
if issuerPublicKey != nil {
issuer = issuerPublicKey
} else {
issuers := s.PublicParametersManager.PublicParameters().Issuers()
if len(issuers) < 1 {
return nil, nil, errors.New("no issuer found")
}
issuer = issuers[0]
}

transfer.ESigners = []driver.Identity{issuer}
transferMetadata.ExtraSigners = []driver.Identity{issuer}
}

return transfer, transferMetadata, nil
}

Expand Down
30 changes: 30 additions & 0 deletions token/core/fabtoken/v1/validator/validator_transfer.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,36 @@ func TransferSignatureValidate(ctx *Context) error {
}
ctx.Signatures = append(ctx.Signatures, sigma)
}

// If transfer action is a redeem, verify the signature of the issuer
isRedeem := func() bool {
for _, output := range ctx.TransferAction.Outputs {
if output.Owner == nil {
return true
}
}
return false
}

if isRedeem() {
ctx.Logger.Debugf("at redeem action validation")
extraSigners := ctx.TransferAction.ESigners
if len(extraSigners) < 1 {
return errors.New("no extra signers provided for redeem action")
}
issuer := ctx.TransferAction.ESigners[0] // issuer is the first extra signer

verifier, err := ctx.Deserializer.GetIssuerVerifier(issuer)
if err != nil {
return errors.Wrapf(err, "failed deserializing issuer [%s]", issuer.UniqueID())
}

sigma, err := ctx.SignatureProvider.HasBeenSignedBy(issuer, verifier)
if err != nil {
return errors.Wrapf(err, "failed signature verification [%s]", issuer.UniqueID())
}
ctx.Signatures = append(ctx.Signatures, sigma)
}
return nil
}

Expand Down
Loading
Loading