Skip to content

Enhanced Redeem #990

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

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,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 @@ -100,6 +100,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
2 changes: 2 additions & 0 deletions integration/token/fungible/odlog/dlog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ var _ = Describe("Orion EndToEnd", func() {
ts, selector := newTestSuite(t.CommType, t.ReplicationFactor, "alice", "bob", "charlie")
BeforeEach(ts.Setup)
AfterEach(ts.TearDown)

It("succeeded", func() { fungible.TestAll(ts.II, "auditor", nil, true, true, selector) })
It("Test redeem flow", Label("T1"), func() { fungible.TestRedeem(ts.II, selector, "orion") })
})
}
})
Expand Down
3 changes: 2 additions & 1 deletion integration/token/fungible/ofabtoken/fabtoken_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ var _ = Describe("Orion EndToEnd", func() {
ts, selector := newTestSuite(t.CommType, t.ReplicationFactor, "alice", "bob", "charlie")
BeforeEach(ts.Setup)
AfterEach(ts.TearDown)
It("succeeded", func() { fungible.TestAll(ts.II, "auditor", nil, true, true, selector) })
It("succeeded", Label("T0"), func() { fungible.TestAll(ts.II, "auditor", nil, true, 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{}),
registry.RegisterFactory("FetchAndUpdatePublicParams", &views.UpdatePublicParamsViewFactory{}),
)
}); err != nil {
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,9 +52,11 @@ 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.RegisterFactory("FetchAndUpdatePublicParams", &views.UpdatePublicParamsViewFactory{}),
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.RegisterFactory("GetPublicParams", &views.GetPublicParamsViewFactory{}),
registry.RegisterFactory("FetchAndUpdatePublicParams", &views.UpdatePublicParamsViewFactory{}),
registry.RegisterResponder(&views.AcceptCashView{}, &views.IssueCashView{}),
Expand Down
15 changes: 15 additions & 0 deletions integration/token/fungible/support.go
Original file line number Diff line number Diff line change
Expand Up @@ -1369,6 +1369,21 @@ func MultiSigSpendCashForTMSID(network *integration.Infrastructure, sender *toke

}

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())

}
}
}
}

func PrepareUpdatedPublicParams(network *integration.Infrastructure, auditor string, networkName string) []byte {
tms := GetTMSByNetworkName(network, networkName)
auditorId := GetAuditorIdentity(tms, auditor)
Expand Down
44 changes: 44 additions & 0 deletions integration/token/fungible/tests.go
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,8 @@ func TestAll(network *integration.Infrastructure, auditorId string, onRestart On
}
newPP := PreparePublicParamsWithNewIssuer(network, newIssuerWalletPath, networkName)
UpdatePublicParamsAndWait(network, newPP, GetTMSByNetworkName(network, networkName), orion, alice, bob, charlie, manager, issuer, auditor, custodian)
tms := GetTMSByNetworkName(network, networkName)
SetBinding(network, issuer, GetIssuerIdentity(tms, issuer.Id()), bob)
CheckPublicParams(network, issuer, auditor, alice, bob, charlie, manager)

// Issuer tokens with this new wallet
Expand Down Expand Up @@ -1445,3 +1447,45 @@ 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")
custodian := sel.Get("custodian")

RegisterAuditor(network, auditor)

// give some time to the nodes to get the public parameters - Q - may now be needed. waiting in UpdatePublicParamsAndWait.
time.Sleep(10 * time.Second)

SetKVSEntry(network, issuer, "auditor", auditor.Id())

// update public parameters with new issuer - START
tokenPlatform := token.GetPlatform(network.Ctx, "token")
Expect(tokenPlatform).ToNot(BeNil(), "cannot find token platform in context")
Expect(tokenPlatform.GetTopology()).ToNot(BeNil(), "invalid token topology, it is nil")
Expect(len(tokenPlatform.GetTopology().TMSs)).ToNot(BeEquivalentTo(0), "no tms defined in token topology")

// Gen crypto material for the new issuer wallet
newIssuerWalletPath := tokenPlatform.GenIssuerCryptoMaterial(tokenPlatform.GetTopology().TMSs[0].BackendTopology.Name(), issuer.Id(), "issuer.ExtraId")
// Register it
RegisterIssuerIdentity(network, issuer, "newIssuerWallet", newIssuerWalletPath)
// Update public parameters
orion := (networkName == "orion")
newPP := PreparePublicParamsWithNewIssuer(network, newIssuerWalletPath, networkName)
UpdatePublicParamsAndWait(network, newPP, GetTMSByNetworkName(network, networkName), orion, alice, issuer, auditor, custodian)

SetBinding(network, issuer, GetIssuerIdentity(tms, issuer.Id()), alice)
CheckPublicParams(network, issuer, auditor, alice)
// update public parameters with new issuer - END

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)
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 @@ -238,6 +238,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
}
21 changes: 21 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 Down Expand Up @@ -106,3 +108,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
}
3 changes: 3 additions & 0 deletions token/core/fabtoken/v1/core/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,9 @@ func (p *PublicParams) Validate() error {
if p.MaxToken > maxTokenValue {
return errors.Errorf("max token value is invalid [%d]>[%d]", p.MaxToken, maxTokenValue)
}
if len(p.IssuerIDs) == 0 {
return errors.New("invalid public parameters: empty list of issuers")
}
return nil
}

Expand Down
2 changes: 2 additions & 0 deletions token/core/fabtoken/v1/core/setup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ package core
import (
"testing"

"github.com/hyperledger-labs/fabric-token-sdk/token/driver"
"github.com/stretchr/testify/assert"
)

Expand Down Expand Up @@ -43,6 +44,7 @@ func TestPublicParams_Validate_Valid(t *testing.T) {
Label: "fabtoken",
QuantityPrecision: 32,
MaxToken: 1<<32 - 1,
IssuerIDs: []driver.Identity{[]byte("issuer1"), []byte("issuer2")},
}
err := pp.Validate()
assert.NoError(t, err)
Expand Down
18 changes: 18 additions & 0 deletions token/core/fabtoken/v1/transfer.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ 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) {
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,26 @@ 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 {
issuers := s.PublicParametersManager.PublicParameters().Issuers()
if len(issuers) < 1 {
return nil, nil, errors.New("no issuer found")
}
issuer := issuers[0]
Copy link
Contributor

Choose a reason for hiding this comment

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

Can it be possible that there is more than one issuer? If so, how do we know which one we have to pick?

Copy link
Author

Choose a reason for hiding this comment

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

We assumed that it is enough for a single issuer to sign and if there are multiple we take the first. It's about the definition of the flow #890

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok I don't know about that, maybe @KElkhiyaoui or @adecaro can confirm :)

Copy link
Contributor

Choose a reason for hiding this comment

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

good catch, Arne, we will need to fix this in coming PRs to let the user decide which issuer to choose.


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