diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 18bc3522e..0fa84ba62 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -66,6 +66,7 @@ jobs: atsafsc, iou, iou-hsm, + newiou, orioncars, pingpong, stoprestart, diff --git a/Makefile b/Makefile index ce7bdd6ff..646f14e2e 100755 --- a/Makefile +++ b/Makefile @@ -90,6 +90,7 @@ testing-docker-images: docker tag postgres:16.2-alpine fsc.itests/postgres:latest INTEGRATION_TARGETS = integration-tests-iou +INTEGRATION_TARGETS = integration-tests-newiou INTEGRATION_TARGETS += integration-tests-atsacc INTEGRATION_TARGETS += integration-tests-chaincode-events INTEGRATION_TARGETS += integration-tests-atsafsc @@ -104,6 +105,10 @@ integration-tests: $(INTEGRATION_TARGETS) integration-tests-iou: cd ./integration/fabric/iou; export FAB_BINS=$(FAB_BINS); ginkgo $(GINKGO_TEST_OPTS) . +.PHONY: integration-tests-newiou +integration-tests-newiou: + cd ./integration/fabric/newiou; export FAB_BINS=$(FAB_BINS); ginkgo $(GINKGO_TEST_OPTS) . + .PHONY: integration-tests-iou-hsm integration-tests-iou-hsm: @echo "Setup SoftHSM" diff --git a/integration/fabric/newiou/iou_suite_test.go b/integration/fabric/newiou/iou_suite_test.go new file mode 100644 index 000000000..174510346 --- /dev/null +++ b/integration/fabric/newiou/iou_suite_test.go @@ -0,0 +1,24 @@ +/* +Copyright IBM Corp All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package newiou_test + +import ( + "testing" + + "github.com/hyperledger-labs/fabric-smart-client/integration" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestEndToEnd(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "NewIOU Suite") +} + +func StartPort() int { + return integration.NewIOUPort.StartPortForNode() +} diff --git a/integration/fabric/newiou/iou_test.go b/integration/fabric/newiou/iou_test.go new file mode 100644 index 000000000..814738926 --- /dev/null +++ b/integration/fabric/newiou/iou_test.go @@ -0,0 +1,107 @@ +/* +Copyright IBM Corp All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package newiou_test + +import ( + "github.com/hyperledger-labs/fabric-smart-client/integration" + iou "github.com/hyperledger-labs/fabric-smart-client/integration/fabric/iou" + "github.com/hyperledger-labs/fabric-smart-client/integration/fabric/newiou" + "github.com/hyperledger-labs/fabric-smart-client/integration/nwo/fsc" + "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/db/driver/sql/postgres" + . "github.com/onsi/ginkgo/v2" +) + +var _ = Describe("EndToEnd", func() { + Describe("IOU Life Cycle With LibP2P", func() { + s := NewTestSuite(fsc.LibP2P, integration.NoReplication, true) + BeforeEach(s.Setup) + AfterEach(s.TearDown) + It("succeeded", s.TestSucceeded) + }) + + Describe("IOU Life Cycle With Websockets", func() { + s := NewTestSuite(fsc.WebSocket, integration.NoReplication, true) + BeforeEach(s.Setup) + AfterEach(s.TearDown) + It("succeeded", s.TestSucceeded) + }) + + Describe("IOU Life Cycle With Websockets and no TLS", func() { + s := NewTestSuite(fsc.WebSocket, integration.NoReplication, false) + BeforeEach(s.Setup) + AfterEach(s.TearDown) + It("succeeded", s.TestSucceeded) + }) + + Describe("IOU Life Cycle With Websockets and replicas", func() { + s := NewTestSuite( + fsc.WebSocket, + &integration.ReplicationOptions{ + ReplicationFactors: map[string]int{ + "borrower": 3, + "lender": 2, + }, + SQLConfigs: map[string]*postgres.ContainerConfig{ + "borrower": postgres.DefaultConfig("borrower-db"), + "lender": postgres.DefaultConfig("lender-db"), + }, + }, + true, + ) + BeforeEach(s.Setup) + AfterEach(s.TearDown) + It("succeeded", s.TestSucceededWithReplicas) + }) +}) + +type TestSuite struct { + *integration.TestSuite +} + +func NewTestSuite(commType fsc.P2PCommunicationType, nodeOpts *integration.ReplicationOptions, tlsEnabled bool) *TestSuite { + return &TestSuite{TestSuite: integration.NewTestSuite(func() (*integration.Infrastructure, error) { + return integration.Generate(StartPort(), true, newiou.Topology(&iou.Opts{ + SDK: &newiou.SDK{}, + CommType: commType, + ReplicationOpts: nodeOpts, + TLSEnabled: tlsEnabled, + })...) + })} +} + +func (s *TestSuite) TestSucceeded() { + iou.InitApprover(s.II, "approver1") + iou.InitApprover(s.II, "approver2") + iouState := iou.CreateIOU(s.II, "", 10, "approver1") + iou.CheckState(s.II, "borrower", iouState, 10) + iou.CheckState(s.II, "lender", iouState, 10) + iou.UpdateIOU(s.II, iouState, 5, "approver2") + iou.CheckState(s.II, "borrower", iouState, 5) + iou.CheckState(s.II, "lender", iouState, 5) + + iou.CheckLocalMetrics(s.II, "borrower", "github.com/hyperledger-labs/fabric-smart-client/platform/fabric/services/endorser/CollectEndorsementsView") + iou.CheckPrometheusMetrics(s.II, "github.com/hyperledger-labs/fabric-smart-client/platform/fabric/services/endorser/CollectEndorsementsView") +} + +func (s *TestSuite) TestSucceededWithReplicas() { + iou.InitApprover(s.II, "approver1") + iou.InitApprover(s.II, "approver2") + + iouState := iou.CreateIOUWithBorrower(s.II, "fsc.borrower.0", "", 10, "approver1") + iou.CheckState(s.II, "fsc.borrower.0", iouState, 10) + iou.CheckState(s.II, "fsc.borrower.1", iouState, 10) + iou.CheckState(s.II, "fsc.borrower.2", iouState, 10) + iou.CheckState(s.II, "fsc.lender.0", iouState, 10) + iou.CheckState(s.II, "fsc.lender.1", iouState, 10) + + iou.UpdateIOUWithBorrower(s.II, "fsc.borrower.1", iouState, 5, "approver2") + iou.CheckState(s.II, "fsc.borrower.0", iouState, 5) + iou.CheckState(s.II, "fsc.borrower.1", iouState, 5) + iou.CheckState(s.II, "fsc.borrower.2", iouState, 5) + iou.CheckState(s.II, "fsc.lender.0", iouState, 5) + iou.CheckState(s.II, "fsc.lender.1", iouState, 5) +} diff --git a/integration/fabric/newiou/sdk.go b/integration/fabric/newiou/sdk.go new file mode 100644 index 000000000..7ae6e2713 --- /dev/null +++ b/integration/fabric/newiou/sdk.go @@ -0,0 +1,121 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package newiou + +import ( + "errors" + + "github.com/hyperledger-labs/fabric-smart-client/integration/fabric/newiou/views" + "github.com/hyperledger-labs/fabric-smart-client/pkg/node" + dig2 "github.com/hyperledger-labs/fabric-smart-client/platform/common/sdk/dig" + sdk "github.com/hyperledger-labs/fabric-smart-client/platform/fabric/sdk/dig" + "github.com/hyperledger-labs/fabric-smart-client/platform/fabric/services/endorser" + "github.com/hyperledger-labs/fabric-smart-client/platform/fabric/services/state" + "github.com/hyperledger-labs/fabric-smart-client/platform/view/sdk/vfsdk" +) + +type SDK struct { + dig2.SDK +} + +func NewSDK(registry node.Registry) *SDK { + return NewFrom(sdk.NewFrom(vfsdk.NewSDK(registry))) +} + +func NewFrom(sdk dig2.SDK) *SDK { + return &SDK{SDK: sdk} +} + +func (p *SDK) Install() error { + err := errors.Join( + p.Container().Provide(endorser.NewEndorseViewFactory), + p.Container().Provide(endorser.NewCollectEndorsementsViewFactory), + p.Container().Provide(state.NewRespondExchangeRecipientIdentitiesViewFactory), + p.Container().Provide(state.NewExchangeRecipientIdentitiesViewFactory), + p.Container().Provide(endorser.NewOrderingAndFinalityViewFactory), + p.Container().Provide(state.NewReceiveTransactionViewFactory), + ) + if err != nil { + return err + } + + return p.SDK.Install() +} + +func NewApproverSDK(registry node.Registry) *ApproverSDK { + return NewApproverSDKFrom(NewSDK(registry)) +} + +func NewApproverSDKFrom(sdk dig2.SDK) *ApproverSDK { + return &ApproverSDK{SDK: sdk} +} + +type ApproverSDK struct { + dig2.SDK +} + +func (p *ApproverSDK) Install() error { + err := errors.Join( + p.Container().Provide(views.NewApproverViewFactory, vfsdk.WithInitiators(&views.CreateIOUView{}, &views.UpdateIOUView{})), + p.Container().Provide(views.NewApproverInitViewFactory, vfsdk.WithFactoryId("init")), + p.Container().Provide(endorser.NewFinalityViewFactory, vfsdk.WithFactoryId("finality")), + ) + if err != nil { + return err + } + return p.SDK.Install() +} + +func NewBorrowerSDK(registry node.Registry) *BorrowerSDK { + return NewBorrowerSDKFrom(NewSDK(registry)) +} + +func NewBorrowerSDKFrom(sdk dig2.SDK) *BorrowerSDK { + return &BorrowerSDK{SDK: sdk} +} + +type BorrowerSDK struct { + dig2.SDK +} + +func (p *BorrowerSDK) Install() error { + err := errors.Join( + p.Container().Provide(views.NewCreateIOUViewFactory, vfsdk.WithFactoryId("create")), + p.Container().Provide(views.NewUpdateIOUViewFactory, vfsdk.WithFactoryId("update")), + p.Container().Provide(views.NewQueryViewFactory, vfsdk.WithFactoryId("query")), + p.Container().Provide(endorser.NewFinalityViewFactory, vfsdk.WithFactoryId("finality")), + ) + if err != nil { + return err + } + return p.SDK.Install() +} + +func NewLenderSDK(registry node.Registry) *LenderSDK { + return NewLenderSDKFrom(NewSDK(registry)) +} + +func NewLenderSDKFrom(sdk dig2.SDK) *LenderSDK { + return &LenderSDK{SDK: sdk} +} + +type LenderSDK struct { + dig2.SDK +} + +func (p *LenderSDK) Install() error { + err := errors.Join( + p.Container().Provide(views.NewCreateIOUResponderViewFactory, vfsdk.WithInitiators(&views.CreateIOUView{})), + p.Container().Provide(views.NewUpdateIOUResponderViewFactory, vfsdk.WithInitiators(&views.UpdateIOUView{})), + p.Container().Provide(views.NewQueryViewFactory, vfsdk.WithFactoryId("query")), + p.Container().Provide(endorser.NewFinalityViewFactory, vfsdk.WithFactoryId("finality")), + ) + if err != nil { + return err + } + return p.SDK.Install() +} diff --git a/integration/fabric/newiou/topology.go b/integration/fabric/newiou/topology.go new file mode 100644 index 000000000..a2f6af866 --- /dev/null +++ b/integration/fabric/newiou/topology.go @@ -0,0 +1,75 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package newiou + +import ( + "github.com/hyperledger-labs/fabric-smart-client/integration/fabric/iou" + "github.com/hyperledger-labs/fabric-smart-client/integration/nwo/api" + "github.com/hyperledger-labs/fabric-smart-client/integration/nwo/fabric" + "github.com/hyperledger-labs/fabric-smart-client/integration/nwo/fsc" + "github.com/hyperledger-labs/fabric-smart-client/integration/nwo/monitoring" + "github.com/hyperledger-labs/fabric-smart-client/platform/view/sdk/tracing" +) + +func Topology(opts *iou.Opts) []api.Topology { + // Define a Fabric topology with: + // 1. Three organization: Org1, Org2, and Org3 + // 2. A namespace whose changes can be endorsed by Org1. + fabricTopology := fabric.NewDefaultTopology() + fabricTopology.AddOrganizationsByName("Org1", "Org2", "Org3") + fabricTopology.SetNamespaceApproverOrgs("Org1") + fabricTopology.AddNamespaceWithUnanimity("iou", "Org1") + fabricTopology.TLSEnabled = opts.TLSEnabled + + // Define an FSC topology with 3 FCS nodes. + // One for the approver, one for the borrower, and one for the lender. + fscTopology := fsc.NewTopology() + fscTopology.P2PCommunicationType = opts.CommType + fscTopology.EnablePrometheusMetrics() + + // fscTopology.SetLogging("debug", "") + fscTopology.EnableTracing(tracing.Otpl) + + // Add the approver FSC node. + fscTopology.AddNodeByName("approver1"). + // This option equips the approver's FSC node with an identity belonging to Org1. + // Therefore, the approver is an endorser of the Fabric namespace we defined above. + AddOptions(fabric.WithOrganization("Org1")). + AddOptions(opts.ReplicationOpts.For("approver1")...). + AddSDK(&ApproverSDK{}) + + // Add another approver as well + fscTopology.AddNodeByName("approver2"). + // This option equips the approver's FSC node with an identity belonging to Org1. + // Therefore, the approver is an endorser of the Fabric namespace we defined above. + AddOptions(fabric.WithOrganization("Org1")). + AddOptions(opts.ReplicationOpts.For("approver2")...). + AddSDK(&ApproverSDK{}) + + // Add the borrower's FSC node + fscTopology.AddNodeByName("borrower"). + AddOptions(fabric.WithOrganization("Org2")). + AddOptions(opts.ReplicationOpts.For("borrower")...). + AddSDK(&BorrowerSDK{}) + + // Add the lender's FSC node + fscTopology.AddNodeByName("lender"). + AddOptions(fabric.WithOrganization("Org3")). + AddOptions(opts.ReplicationOpts.For("lender")...). + AddSDK(&LenderSDK{}) + + // Monitoring + monitoringTopology := monitoring.NewTopology() + monitoringTopology.EnablePrometheusGrafana() + monitoringTopology.EnableOPTL() + + return []api.Topology{ + fabricTopology, + fscTopology, + monitoringTopology, + } +} diff --git a/integration/fabric/newiou/views/approver.go b/integration/fabric/newiou/views/approver.go new file mode 100644 index 000000000..347110cb6 --- /dev/null +++ b/integration/fabric/newiou/views/approver.go @@ -0,0 +1,167 @@ +/* +Copyright IBM Corp All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package views + +import ( + "sync" + "time" + + "github.com/hyperledger-labs/fabric-smart-client/integration/fabric/iou/states" + "github.com/hyperledger-labs/fabric-smart-client/integration/fabric/iou/views" + "github.com/hyperledger-labs/fabric-smart-client/platform/fabric" + "github.com/hyperledger-labs/fabric-smart-client/platform/fabric/driver" + "github.com/hyperledger-labs/fabric-smart-client/platform/fabric/services/endorser" + "github.com/hyperledger-labs/fabric-smart-client/platform/fabric/services/state" + "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 ApproverView struct { + receiveTransactionView *state.ReceiveTransactionViewFactory + endorseView *endorser.EndorseViewFactory + finalityView *endorser.FinalityViewFactory + fnsProvider *fabric.NetworkServiceProvider +} + +func (i *ApproverView) Call(context view.Context) (interface{}, error) { + // When the borrower runs the CollectEndorsementsView, at some point, the borrower sends the assembled transaction + // to the approver. Therefore, the approver waits to receive the transaction. + txBoxed, err := context.RunView(i.receiveTransactionView.New(nil), view.WithSameContext()) + if err != nil { + return nil, err + } + + tx := txBoxed.(*state.Transaction) + assert.NoError(err, "failed receiving transaction") + + // The approver can now inspect the transaction to ensure it is as expected. + // Here are examples of possible checks + + // Namespaces are properly populated + assert.Equal(1, len(tx.Namespaces()), "expected only one namespace") + assert.Equal("iou", tx.Namespaces()[0], "expected the [iou] namespace, got [%s]", tx.Namespaces()[0]) + + // Commands are properly populated + assert.Equal(1, tx.Commands().Count(), "expected only a single command, got [%s]", tx.Commands().Count()) + switch command := tx.Commands().At(0); command.Name { + case "create": + // If the create command is attached to the transaction then... + + // No inputs expected. The single output at index 0 should be an IOU state + assert.Equal(0, tx.NumInputs(), "invalid number of inputs, expected 0, was [%d]", tx.NumInputs()) + assert.Equal(1, tx.NumOutputs(), "invalid number of outputs, expected 1, was [%d]", tx.NumOutputs()) + iouState := &states.IOU{} + assert.NoError(tx.GetOutputAt(0, iouState)) + + assert.True(iouState.Amount >= 5, "invalid amount, expected at least 5, was [%d]", iouState.Amount) + assert.Equal(2, iouState.Owners().Count(), "invalid state, expected 2 identities, was [%d]", iouState.Owners().Count()) + assert.False(iouState.Owners()[0].Equal(iouState.Owners()[1]), "owner identities must be different") + assert.True(iouState.Owners().Match(command.Ids), "invalid state, it does not contain command's identities") + assert.NoError(tx.HasBeenEndorsedBy(iouState.Owners()...), "signatures are missing") + case "update": + // If the update command is attached to the transaction then... + + // The single input and output should be an IOU state + assert.Equal(1, tx.NumInputs(), "invalid number of inputs, expected 1, was [%d]", tx.NumInputs()) + assert.Equal(1, tx.NumOutputs(), "invalid number of outputs, expected 1, was [%d]", tx.NumOutputs()) + + inState := &states.IOU{} + assert.NoError(tx.GetInputAt(0, inState)) + outState := &states.IOU{} + assert.NoError(tx.GetOutputAt(0, outState)) + + assert.Equal(inState.LinearID, outState.LinearID, "invalid state id, [%s] != [%s]", inState.LinearID, outState.LinearID) + assert.True(outState.Amount < inState.Amount, "invalid amount, [%d] expected to be less or equal [%d]", outState.Amount, inState.Amount) + assert.True(inState.Owners().Match(outState.Owners()), "invalid owners, input and output should have the same owners") + assert.NoError(tx.HasBeenEndorsedBy(outState.Owners()...), "signatures are missing") + default: + return nil, errors.Errorf("invalid command, expected [create] or [update], was [%s]", command.Name) + } + + // The approver is ready to send back the transaction signed + _, err = context.RunView(i.endorseView.New(tx.Transaction)) + assert.NoError(err) + + // Check committer events + var wg sync.WaitGroup + wg.Add(1) + fns, err := i.fnsProvider.FabricNetworkService(fabric.DefaultNetwork) + assert.NoError(err) + ch, err := fns.Channel(fabric.DefaultChannel) + assert.NoError(err) + committer := ch.Committer() + assert.NoError(err, committer.AddFinalityListener(tx.ID(), views.NewFinalityListener(tx.ID(), driver.Valid, &wg)), "failed to add committer listener") + assert.Error(committer.AddFinalityListener("", views.NewFinalityListener(tx.ID(), driver.Valid, &wg)), "must have failed") + + // Finally, the approver waits that the transaction completes its lifecycle + _, err = context.RunView(i.finalityView.NewWithTimeout(tx.Transaction, 1*time.Minute)) + assert.NoError(err, "failed to run finality view") + wg.Wait() + + wg = sync.WaitGroup{} + wg.Add(1) + assert.NoError(err, committer.AddFinalityListener(tx.ID(), views.NewFinalityListener(tx.ID(), driver.Valid, &wg)), "failed to add committer listener") + wg.Wait() + + return nil, nil +} + +type ApproverViewFactory struct { + receiveTransactionView *state.ReceiveTransactionViewFactory + endorseView *endorser.EndorseViewFactory + finalityView *endorser.FinalityViewFactory + fnsProvider *fabric.NetworkServiceProvider +} + +func NewApproverViewFactory( + receiveTransactionView *state.ReceiveTransactionViewFactory, + endorseView *endorser.EndorseViewFactory, + finalityView *endorser.FinalityViewFactory, + fnsProvider *fabric.NetworkServiceProvider, +) *ApproverViewFactory { + return &ApproverViewFactory{ + receiveTransactionView: receiveTransactionView, + endorseView: endorseView, + finalityView: finalityView, + fnsProvider: fnsProvider, + } +} + +func (c *ApproverViewFactory) NewView([]byte) (view.View, error) { + return &ApproverView{ + receiveTransactionView: c.receiveTransactionView, + endorseView: c.endorseView, + finalityView: c.finalityView, + fnsProvider: c.fnsProvider, + }, nil +} + +type ApproverInitView struct { + fnsProvider *fabric.NetworkServiceProvider +} + +func (a *ApproverInitView) Call(view.Context) (interface{}, error) { + fns, err := a.fnsProvider.FabricNetworkService(fabric.DefaultNetwork) + assert.NoError(err) + ch, err := fns.Channel(fabric.DefaultChannel) + assert.NoError(err) + assert.NoError(ch.Committer().ProcessNamespace("iou"), "failed to setup namespace to process") + return nil, nil +} + +func NewApproverInitViewFactory(fnsProvider *fabric.NetworkServiceProvider) *ApproverInitViewFactory { + return &ApproverInitViewFactory{fnsProvider: fnsProvider} +} + +type ApproverInitViewFactory struct { + fnsProvider *fabric.NetworkServiceProvider +} + +func (c *ApproverInitViewFactory) NewView([]byte) (view.View, error) { + return &ApproverInitView{fnsProvider: c.fnsProvider}, nil +} diff --git a/integration/fabric/newiou/views/borrower.go b/integration/fabric/newiou/views/borrower.go new file mode 100644 index 000000000..5d6d5255b --- /dev/null +++ b/integration/fabric/newiou/views/borrower.go @@ -0,0 +1,233 @@ +/* +Copyright IBM Corp All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package views + +import ( + "encoding/json" + "sync" + "time" + + "github.com/hyperledger-labs/fabric-smart-client/integration/fabric/iou/states" + "github.com/hyperledger-labs/fabric-smart-client/integration/fabric/iou/views" + "github.com/hyperledger-labs/fabric-smart-client/platform/fabric" + "github.com/hyperledger-labs/fabric-smart-client/platform/fabric/driver" + "github.com/hyperledger-labs/fabric-smart-client/platform/fabric/services/endorser" + "github.com/hyperledger-labs/fabric-smart-client/platform/fabric/services/state" + "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/assert" + "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" +) + +// Create contains the input to create an IOU state +type Create struct { + // Identity is the label of the the borrower's identity to use. + // Empty means the default identity. + Identity string + // Amount the borrower owes the lender + Amount uint + // Lender is the identity of the lender's FSC node + Lender view.Identity + // Approver is the identity of the approver's FSC node + Approver view.Identity +} + +type CreateIOUView struct { + Create + + collectEndorsementsView *endorser.CollectEndorsementsViewFactory + exchangeRecipientIdentitiesView *state.ExchangeRecipientIdentitiesViewFactory + orderingAndFinalityView *endorser.OrderingAndFinalityViewFactory + fnsProvider *fabric.NetworkServiceProvider +} + +func (i *CreateIOUView) Call(context view.Context) (interface{}, error) { + // As a first step operation, the borrower contacts the lender's FSC node + // to exchange the identities to use to assign ownership of the freshly created IOU state. + v, err := i.exchangeRecipientIdentitiesView.New(i.Lender, state.WithIdentity(i.Identity)) + assert.NoError(err) + ids, err := context.RunView(v) + assert.NoError(err, "failed exchanging recipient identity") + borrower, lender := ids.([]view.Identity)[0], ids.([]view.Identity)[1] + + // The borrower creates a new transaction + tx, err := state.NewTransactionWithFNSP(i.fnsProvider, context) + assert.NoError(err, "failed creating a new transaction") + + // Sets the namespace where the state should be stored + tx.SetNamespace("iou") + + // Specifies the command this transaction wants to execute. + // In particular, the borrower wants to create a new IOU state owned by the borrower and the lender + // The approver will use this information to decide how validate the transaction + assert.NoError(tx.AddCommand("create", borrower, lender)) + + // The borrower prepares the IOU state + iou := &states.IOU{ + Amount: i.Amount, + Parties: []view.Identity{borrower, lender}, + } + // and add it to the transaction. At this stage, the ID gets set automatically. + assert.NoError(tx.AddOutput(iou)) + + // The borrower is ready to collect all the required signatures. + // Namely from the borrower itself, the lender, and the approver. In this order. + // All signatures are required. + _, err = context.RunView(i.collectEndorsementsView.New(tx.Transaction, borrower, lender, i.Approver)) + assert.NoError(err) + + // Check committer events + var wg sync.WaitGroup + wg.Add(1) + fns, err := i.fnsProvider.FabricNetworkService(fabric.DefaultNetwork) + assert.NoError(err) + ch, err := fns.Channel(fabric.DefaultChannel) + assert.NoError(err) + committer := ch.Committer() + assert.NoError(err, committer.AddFinalityListener(tx.ID(), views.NewFinalityListener(tx.ID(), driver.Valid, &wg)), "failed to add committer listener") + + // At this point the borrower can send the transaction to the ordering service and wait for finality. + _, err = context.RunView(i.orderingAndFinalityView.NewWithTimeout(tx.Transaction, true, 1*time.Minute)) + assert.NoError(err) + + wg.Wait() + + // Return the state ID + return iou.LinearID, nil +} + +func NewCreateIOUViewFactory( + collectEndorsementsView *endorser.CollectEndorsementsViewFactory, + exchangeRecipientIdentitiesView *state.ExchangeRecipientIdentitiesViewFactory, + orderingAndFinalityView *endorser.OrderingAndFinalityViewFactory, + fnsProvider *fabric.NetworkServiceProvider, +) *CreateIOUViewFactory { + return &CreateIOUViewFactory{ + collectEndorsementsView: collectEndorsementsView, + exchangeRecipientIdentitiesView: exchangeRecipientIdentitiesView, + orderingAndFinalityView: orderingAndFinalityView, + fnsProvider: fnsProvider, + } +} + +type CreateIOUViewFactory struct { + collectEndorsementsView *endorser.CollectEndorsementsViewFactory + exchangeRecipientIdentitiesView *state.ExchangeRecipientIdentitiesViewFactory + orderingAndFinalityView *endorser.OrderingAndFinalityViewFactory + fnsProvider *fabric.NetworkServiceProvider +} + +func (c *CreateIOUViewFactory) NewView(in []byte) (view.View, error) { + f := &CreateIOUView{ + collectEndorsementsView: c.collectEndorsementsView, + exchangeRecipientIdentitiesView: c.exchangeRecipientIdentitiesView, + orderingAndFinalityView: c.orderingAndFinalityView, + fnsProvider: c.fnsProvider, + } + err := json.Unmarshal(in, &f.Create) + assert.NoError(err) + return f, nil +} + +// Update contains the input to update an IOU state +type Update struct { + // LinearID is the unique identifier of the IOU state + LinearID string + // Amount is the new amount. It should smaller than the current amount + Amount uint + // Approver is the identity of the approver's FSC node + Approver view.Identity +} + +type UpdateIOUView struct { + Update + + collectEndorsementsView *endorser.CollectEndorsementsViewFactory + exchangeRecipientIdentitiesView *state.ExchangeRecipientIdentitiesViewFactory + orderingAndFinalityView *endorser.OrderingAndFinalityViewFactory + fnsProvider *fabric.NetworkServiceProvider +} + +func (u UpdateIOUView) Call(context view.Context) (interface{}, error) { + // The borrower starts by creating a new transaction to update the IOU state + tx, err := state.NewTransactionWithFNSP(u.fnsProvider, context) + assert.NoError(err) + + // Sets the namespace where the state is stored + tx.SetNamespace("iou") + + // To update the state, the borrower, first add a dependency to the IOU state of interest. + iouState := &states.IOU{} + assert.NoError(tx.AddInputByLinearID(u.LinearID, iouState)) + // The borrower sets the command to the operation to be performed + assert.NoError(tx.AddCommand("update", iouState.Owners()...)) + + // Then, the borrower updates the amount, + iouState.Amount = u.Amount + + // and add the modified IOU state as output of the transaction. + err = tx.AddOutput(iouState) + assert.NoError(err) + + // The borrower is ready to collect all the required signatures. + // Namely from the borrower itself, the lender, and the approver. In this order. + // All signatures are required. + _, err = context.RunView(u.collectEndorsementsView.New(tx.Transaction, iouState.Owners()[0], iouState.Owners()[1], u.Approver)) + assert.NoError(err) + + // Check committer events + var wg sync.WaitGroup + wg.Add(1) + fns, err := u.fnsProvider.FabricNetworkService(fabric.DefaultNetwork) + assert.NoError(err) + ch, err := fns.Channel(fabric.DefaultChannel) + assert.NoError(err) + committer := ch.Committer() + assert.NoError(err, committer.AddFinalityListener(tx.ID(), views.NewFinalityListener(tx.ID(), driver.Valid, &wg)), "failed to add committer listener") + + // At this point the borrower can send the transaction to the ordering service and wait for finality. + _, err = context.RunView(u.orderingAndFinalityView.NewWithTimeout(tx.Transaction, true, 1*time.Minute)) + assert.NoError(err, "failed ordering and finalizing") + wg.Wait() + + wg = sync.WaitGroup{} + wg.Add(1) + assert.NoError(err, committer.AddFinalityListener(tx.ID(), views.NewFinalityListener(tx.ID(), driver.Valid, &wg)), "failed to add committer listener") + wg.Wait() + + return tx.ID(), nil +} + +func NewUpdateIOUViewFactory( + collectEndorsementsView *endorser.CollectEndorsementsViewFactory, + exchangeRecipientIdentitiesView *state.ExchangeRecipientIdentitiesViewFactory, + orderingAndFinalityView *endorser.OrderingAndFinalityViewFactory, + fnsProvider *fabric.NetworkServiceProvider) *UpdateIOUViewFactory { + return &UpdateIOUViewFactory{ + collectEndorsementsView: collectEndorsementsView, + exchangeRecipientIdentitiesView: exchangeRecipientIdentitiesView, + orderingAndFinalityView: orderingAndFinalityView, + fnsProvider: fnsProvider, + } +} + +type UpdateIOUViewFactory struct { + collectEndorsementsView *endorser.CollectEndorsementsViewFactory + exchangeRecipientIdentitiesView *state.ExchangeRecipientIdentitiesViewFactory + orderingAndFinalityView *endorser.OrderingAndFinalityViewFactory + fnsProvider *fabric.NetworkServiceProvider +} + +func (c *UpdateIOUViewFactory) NewView(in []byte) (view.View, error) { + f := &UpdateIOUView{ + collectEndorsementsView: c.collectEndorsementsView, + exchangeRecipientIdentitiesView: c.exchangeRecipientIdentitiesView, + orderingAndFinalityView: c.orderingAndFinalityView, + fnsProvider: c.fnsProvider, + } + err := json.Unmarshal(in, &f.Update) + assert.NoError(err) + return f, nil +} diff --git a/integration/fabric/newiou/views/lender.go b/integration/fabric/newiou/views/lender.go new file mode 100644 index 000000000..65b1fc38a --- /dev/null +++ b/integration/fabric/newiou/views/lender.go @@ -0,0 +1,208 @@ +/* +Copyright IBM Corp All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ +package views + +import ( + "time" + + "github.com/hyperledger-labs/fabric-smart-client/platform/fabric/services/endorser" + "github.com/pkg/errors" + + "github.com/hyperledger-labs/fabric-smart-client/integration/fabric/iou/states" + "github.com/hyperledger-labs/fabric-smart-client/platform/fabric" + "github.com/hyperledger-labs/fabric-smart-client/platform/fabric/services/state" + "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/assert" + "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" +) + +type CreateIOUResponderView struct { + respondExchangeRecipientIdentitiesView *state.RespondExchangeRecipientIdentitiesViewFactory + receiveTransactionView *state.ReceiveTransactionViewFactory + endorseView *endorser.EndorseViewFactory + finalityView *endorser.FinalityViewFactory + fnsProvider *fabric.NetworkServiceProvider +} + +func (i *CreateIOUResponderView) Call(context view.Context) (interface{}, error) { + // As a first step, the lender responds to the request to exchange recipient identities. + v, err := i.respondExchangeRecipientIdentitiesView.New() + assert.NoError(err) + ids, err := context.RunView(v) + assert.NoError(err, "failed exchanging recipient identities") + + lender, borrower := ids.([]view.Identity)[0], ids.([]view.Identity)[1] + + // When the borrower runs the CollectEndorsementsView, at some point, the borrower sends the assembled transaction + // to the lender. Therefore, the lender waits to receive the transaction. + txBoxed, err := context.RunView(i.receiveTransactionView.New(nil), view.WithSameContext()) + assert.NoError(err, "failed receiving transaction") + + tx := txBoxed.(*state.Transaction) + + // The lender can now inspect the transaction to ensure it is as expected. + // Here are examples of possible checks + + // Namespaces are properly populated + assert.Equal(1, len(tx.Namespaces()), "expected only one namespace") + assert.Equal("iou", tx.Namespaces()[0], "expected the [iou] namespace, got [%s]", tx.Namespaces()[0]) + + // Commands are properly populated + assert.Equal(1, tx.Commands().Count(), "expected only a single command, got [%s]", tx.Commands().Count()) + switch command := tx.Commands().At(0); command.Name { + case "create": + // If the create command is attached to the transaction then... + + // No inputs expected. The single output at index 0 should be an IOU state + assert.Equal(0, tx.NumInputs(), "invalid number of inputs, expected 0, was [%d]", tx.NumInputs()) + assert.Equal(1, tx.NumOutputs(), "invalid number of outputs, expected 1, was [%d]", tx.NumOutputs()) + iouState := &states.IOU{} + assert.NoError(tx.GetOutputAt(0, iouState)) + + assert.False(iouState.Amount < 5, "invalid amount, expected at least 5, was [%d]", iouState.Amount) + assert.Equal(2, iouState.Owners().Count(), "invalid state, expected 2 identities, was [%d]", iouState.Owners().Count()) + assert.True(iouState.Owners().Contain(lender), "invalid state, it does not contain lender identity") + assert.True(command.Ids.Match([]view.Identity{lender, borrower}), "the command does not contain the lender and borrower identities") + assert.True(iouState.Owners().Match([]view.Identity{lender, borrower}), "the state does not contain the lender and borrower identities") + assert.NoError(tx.HasBeenEndorsedBy(borrower), "the borrower has not endorsed") + default: + return nil, errors.Errorf("invalid command, expected [create], was [%s]", command.Name) + } + + // The lender is ready to send back the transaction signed + _, err = context.RunView(i.endorseView.New(tx.Transaction)) + assert.NoError(err) + + // Finally, the lender waits that the transaction completes its lifecycle + return context.RunView(i.finalityView.NewWithTimeout(tx.Transaction, 1*time.Minute)) +} + +func NewCreateIOUResponderViewFactory( + respondExchangeRecipientIdentitiesView *state.RespondExchangeRecipientIdentitiesViewFactory, + receiveTransactionView *state.ReceiveTransactionViewFactory, + endorseView *endorser.EndorseViewFactory, + finalityView *endorser.FinalityViewFactory, + fnsProvider *fabric.NetworkServiceProvider, +) *CreateIOUResponderViewFactory { + return &CreateIOUResponderViewFactory{ + respondExchangeRecipientIdentitiesView: respondExchangeRecipientIdentitiesView, + receiveTransactionView: receiveTransactionView, + endorseView: endorseView, + finalityView: finalityView, + fnsProvider: fnsProvider, + } +} + +type CreateIOUResponderViewFactory struct { + respondExchangeRecipientIdentitiesView *state.RespondExchangeRecipientIdentitiesViewFactory + receiveTransactionView *state.ReceiveTransactionViewFactory + endorseView *endorser.EndorseViewFactory + finalityView *endorser.FinalityViewFactory + fnsProvider *fabric.NetworkServiceProvider +} + +func (c *CreateIOUResponderViewFactory) NewView([]byte) (view.View, error) { + return &CreateIOUResponderView{ + respondExchangeRecipientIdentitiesView: c.respondExchangeRecipientIdentitiesView, + receiveTransactionView: c.receiveTransactionView, + endorseView: c.endorseView, + finalityView: c.finalityView, + fnsProvider: c.fnsProvider, + }, nil +} + +type UpdateIOUResponderView struct { + receiveTransactionView *state.ReceiveTransactionViewFactory + endorseView *endorser.EndorseViewFactory + finalityView *endorser.FinalityViewFactory + fnsProvider *fabric.NetworkServiceProvider +} + +func (i *UpdateIOUResponderView) Call(context view.Context) (interface{}, error) { + // When the borrower runs the CollectEndorsementsView, at some point, the borrower sends the assembled transaction + // to the lender. Therefore, the lender waits to receive the transaction. + txBoxed, err := context.RunView(i.receiveTransactionView.New(nil), view.WithSameContext()) + assert.NoError(err, "failed receiving transaction") + + tx := txBoxed.(*state.Transaction) + + // The lender can now inspect the transaction to ensure it is as expected. + // Here are examples of possible checks + + // Namespaces are properly populated + assert.Equal(1, len(tx.Namespaces()), "expected only one namespace") + assert.Equal("iou", tx.Namespaces()[0], "expected the [iou] namespace, got [%s]", tx.Namespaces()[0]) + + switch command := tx.Commands().At(0); command.Name { + case "update": + // If the update command is attached to the transaction then... + + // One input and one output containing IOU states are expected + assert.Equal(1, tx.NumInputs(), "invalid number of inputs, expected 1, was %d", tx.NumInputs()) + assert.Equal(1, tx.NumOutputs(), "invalid number of outputs, expected 1, was %d", tx.NumInputs()) + inState := &states.IOU{} + assert.NoError(tx.GetInputAt(0, inState)) + outState := &states.IOU{} + assert.NoError(tx.GetOutputAt(0, outState)) + + // Additional checks + // Same IDs + assert.Equal(inState.LinearID, outState.LinearID, "invalid state id, [%s] != [%s]", inState.LinearID, outState.LinearID) + // Valid Amount + assert.False(outState.Amount >= inState.Amount, "invalid amount, [%d] expected to be less or equal [%d]", outState.Amount, inState.Amount) + // Same owners + assert.True(inState.Owners().Match(outState.Owners()), "invalid owners, input and output should have the same owners") + assert.Equal(2, inState.Owners().Count(), "invalid state, expected 2 identities, was [%d]", inState.Owners().Count()) + // Is the lender one of the owners? + fns, err := i.fnsProvider.FabricNetworkService(fabric.DefaultNetwork) + assert.NoError(err) + lenderFound := fns.LocalMembership().IsMe(inState.Owners()[0]) != fns.LocalMembership().IsMe(inState.Owners()[1]) + assert.True(lenderFound, "lender identity not found") + // Did the borrower sign? + assert.NoError(tx.HasBeenEndorsedBy(inState.Owners().Filter( + func(identity view.Identity) bool { + return !fns.LocalMembership().IsMe(identity) + })...), "the borrower has not endorsed") + default: + return nil, errors.Errorf("invalid command, expected [create], was [%s]", command.Name) + } + + // The lender is ready to send back the transaction signed + _, err = context.RunView(i.endorseView.New(tx.Transaction)) + assert.NoError(err) + + // Finally, the lender waits that the transaction completes its lifecycle + return context.RunView(i.finalityView.NewWithTimeout(tx.Transaction, 1*time.Minute)) +} + +func NewUpdateIOUResponderViewFactory( + receiveTransactionView *state.ReceiveTransactionViewFactory, + endorseView *endorser.EndorseViewFactory, + finalityView *endorser.FinalityViewFactory, + fnsProvider *fabric.NetworkServiceProvider, +) *UpdateIOUResponderViewFactory { + return &UpdateIOUResponderViewFactory{ + receiveTransactionView: receiveTransactionView, + endorseView: endorseView, + finalityView: finalityView, + fnsProvider: fnsProvider, + } +} + +type UpdateIOUResponderViewFactory struct { + receiveTransactionView *state.ReceiveTransactionViewFactory + endorseView *endorser.EndorseViewFactory + finalityView *endorser.FinalityViewFactory + fnsProvider *fabric.NetworkServiceProvider +} + +func (c *UpdateIOUResponderViewFactory) NewView([]byte) (view.View, error) { + return &UpdateIOUResponderView{ + receiveTransactionView: c.receiveTransactionView, + endorseView: c.endorseView, + finalityView: c.finalityView, + fnsProvider: c.fnsProvider, + }, nil +} diff --git a/integration/fabric/newiou/views/query.go b/integration/fabric/newiou/views/query.go new file mode 100644 index 000000000..f4469abd7 --- /dev/null +++ b/integration/fabric/newiou/views/query.go @@ -0,0 +1,50 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package views + +import ( + "encoding/json" + + "github.com/hyperledger-labs/fabric-smart-client/integration/fabric/iou/states" + "github.com/hyperledger-labs/fabric-smart-client/platform/fabric" + "github.com/hyperledger-labs/fabric-smart-client/platform/fabric/services/state" + "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/assert" + "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" +) + +type Query struct { + LinearID string +} + +type QueryView struct { + Query + + vaultService state.VaultService +} + +func (q *QueryView) Call(context view.Context) (interface{}, error) { + iouState := &states.IOU{} + vault, err := q.vaultService.Vault(fabric.DefaultNetwork, fabric.DefaultChannel) + assert.NoError(err) + assert.NoError(vault.GetState(context.Context(), "iou", q.LinearID, iouState)) + return iouState.Amount, nil +} + +func NewQueryViewFactory(vaultService state.VaultService) *QueryViewFactory { + return &QueryViewFactory{vaultService: vaultService} +} + +type QueryViewFactory struct { + vaultService state.VaultService +} + +func (c *QueryViewFactory) NewView(in []byte) (view.View, error) { + f := &QueryView{vaultService: c.vaultService} + err := json.Unmarshal(in, &f.Query) + assert.NoError(err) + return f, nil +} diff --git a/integration/fsc/pingpong/mock/factory.go b/integration/fsc/pingpong/mock/factory.go deleted file mode 100644 index 5e89a8e14..000000000 --- a/integration/fsc/pingpong/mock/factory.go +++ /dev/null @@ -1,25 +0,0 @@ -/* -Copyright IBM Corp. All Rights Reserved. - -SPDX-License-Identifier: Apache-2.0 -*/ - -package mock - -import ( - "encoding/json" - - "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/assert" - "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" -) - -// InitiatorViewFactory is the factory of Initiator views -type InitiatorViewFactory struct{} - -// NewView returns a new instance of the Initiator view -func (i *InitiatorViewFactory) NewView(in []byte) (view.View, error) { - f := &Initiator{Params: &Params{}} - err := json.Unmarshal(in, f.Params) - assert.NoError(err, "failed unmarshalling input") - return f, nil -} diff --git a/integration/ports.go b/integration/ports.go index 1bac30b96..f652f3a0c 100755 --- a/integration/ports.go +++ b/integration/ports.go @@ -29,6 +29,7 @@ const ( PingPongWithAdminPort IOUPort IOUHSMPort + NewIOUPort IOUWithOrionBackendPort AssetTransferSecuredAgreementWithChaincode AssetTransferSecuredAgreementWithApprovers diff --git a/platform/fabric/fns.go b/platform/fabric/fns.go index ed5e357ea..fe754c101 100644 --- a/platform/fabric/fns.go +++ b/platform/fabric/fns.go @@ -23,6 +23,9 @@ var ( logger = logging.MustGetLogger("fabric-sdk") ) +const DefaultNetwork = "" +const DefaultChannel = "" + // NetworkService models a Fabric Network type NetworkService struct { subscriber events.Subscriber @@ -186,7 +189,7 @@ func GetFabricNetworkService(sp view2.ServiceProvider, id string) (*NetworkServi // GetDefaultFNS returns the default Fabric Network Service func GetDefaultFNS(sp view2.ServiceProvider) (*NetworkService, error) { - return GetFabricNetworkService(sp, "") + return GetFabricNetworkService(sp, DefaultNetwork) } // GetDefaultChannel returns the default channel of the default fns diff --git a/platform/fabric/services/endorser/endorsement.go b/platform/fabric/services/endorser/endorsement.go index 7d8a84d6a..a2620a75c 100644 --- a/platform/fabric/services/endorser/endorsement.go +++ b/platform/fabric/services/endorser/endorsement.go @@ -11,8 +11,9 @@ import ( "encoding/json" "time" + "github.com/hyperledger-labs/fabric-smart-client/platform/common/utils" "github.com/hyperledger-labs/fabric-smart-client/platform/fabric" - view2 "github.com/hyperledger-labs/fabric-smart-client/platform/view" + "github.com/hyperledger-labs/fabric-smart-client/platform/view/driver" "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" "github.com/pkg/errors" "go.opentelemetry.io/otel/trace" @@ -26,6 +27,28 @@ type collectEndorsementsView struct { } func (c *collectEndorsementsView) Call(context view.Context) (interface{}, error) { + return context.RunView(&CollectEndorsementsView{ + tx: c.tx, + parties: c.parties, + deleteTransient: c.deleteTransient, + verifierProviders: c.verifierProviders, + + endpointService: driver.GetEndpointService(context), + fnsProvider: utils.MustGet(fabric.GetNetworkServiceProvider(context)), + }) +} + +type CollectEndorsementsView struct { + tx *Transaction + parties []view.Identity + deleteTransient bool + verifierProviders []fabric.VerifierProvider + + endpointService driver.EndpointService + fnsProvider *fabric.NetworkServiceProvider +} + +func (c *CollectEndorsementsView) Call(context view.Context) (interface{}, error) { span := trace.SpanFromContext(context.Context()) // Prepare verifiers ch, err := c.tx.FabricNetworkService().Channel(c.tx.Channel()) @@ -123,7 +146,7 @@ func (c *collectEndorsementsView) Call(context view.Context) (interface{}, error } found := true - fns, err := fabric.GetFabricNetworkService(context, c.tx.Network()) + fns, err := c.fnsProvider.FabricNetworkService(c.tx.Network()) if err != nil { return nil, errors.WithMessagef(err, "fabric network service [%s] not found", c.tx.Network()) } @@ -139,7 +162,7 @@ func (c *collectEndorsementsView) Call(context view.Context) (interface{}, error // Check the validity of the response span.AddEvent("Check response validity") - if view2.GetEndpointService(context).IsBoundTo(endorser, party) { + if c.endpointService.IsBoundTo(endorser, party) { found = true } @@ -182,6 +205,30 @@ func (c *collectEndorsementsView) Call(context view.Context) (interface{}, error return c.tx, nil } +func NewCollectEndorsementsViewFactory( + endpointService driver.EndpointService, + fnsProvider *fabric.NetworkServiceProvider, +) *CollectEndorsementsViewFactory { + return &CollectEndorsementsViewFactory{ + endpointService: endpointService, + fnsProvider: fnsProvider, + } +} + +type CollectEndorsementsViewFactory struct { + endpointService driver.EndpointService + fnsProvider *fabric.NetworkServiceProvider +} + +func (f *CollectEndorsementsViewFactory) New(tx *Transaction, parties ...view.Identity) *CollectEndorsementsView { + return &CollectEndorsementsView{ + tx: tx, + parties: parties, + endpointService: f.endpointService, + fnsProvider: f.fnsProvider, + } +} + func (c *collectEndorsementsView) SetVerifierProviders(p []fabric.VerifierProvider) *collectEndorsementsView { c.verifierProviders = p return c @@ -201,8 +248,24 @@ type endorseView struct { } func (s *endorseView) Call(context view.Context) (interface{}, error) { + return context.RunView(&EndorseView{ + tx: s.tx, + identities: s.identities, + + fnsProvider: utils.MustGet(fabric.GetNetworkServiceProvider(context)), + }) +} + +type EndorseView struct { + tx *Transaction + identities []view.Identity + + fnsProvider *fabric.NetworkServiceProvider +} + +func (s *EndorseView) Call(context view.Context) (interface{}, error) { if len(s.identities) == 0 { - fns, err := fabric.GetFabricNetworkService(context, s.tx.Network()) + fns, err := s.fnsProvider.FabricNetworkService(s.tx.Network()) if err != nil { return nil, errors.WithMessagef(err, "fabric network service [%s] not found", s.tx.Network()) } @@ -227,7 +290,7 @@ func (s *endorseView) Call(context view.Context) (interface{}, error) { if err != nil { return nil, errors.Wrap(err, "failed marshalling tx") } - fns, err := fabric.GetDefaultFNS(context) + fns, err := s.fnsProvider.FabricNetworkService(fabric.DefaultNetwork) if err != nil { return nil, errors.WithMessage(err, "failed getting default network") } @@ -254,6 +317,22 @@ func (s *endorseView) Call(context view.Context) (interface{}, error) { return s.tx, nil } +type EndorseViewFactory struct { + fnsProvider *fabric.NetworkServiceProvider +} + +func NewEndorseViewFactory(fnsProvider *fabric.NetworkServiceProvider) *EndorseViewFactory { + return &EndorseViewFactory{fnsProvider: fnsProvider} +} + +func (f *EndorseViewFactory) New(tx *Transaction, ids ...view.Identity) *EndorseView { + return &EndorseView{ + tx: tx, + identities: ids, + fnsProvider: f.fnsProvider, + } +} + func NewEndorseView(tx *Transaction, ids ...view.Identity) *endorseView { return &endorseView{tx: tx, identities: ids} } diff --git a/platform/fabric/services/endorser/finality.go b/platform/fabric/services/endorser/finality.go index e204e37e5..81ea35854 100644 --- a/platform/fabric/services/endorser/finality.go +++ b/platform/fabric/services/endorser/finality.go @@ -12,6 +12,7 @@ import ( "time" driver2 "github.com/hyperledger-labs/fabric-smart-client/platform/common/driver" + "github.com/hyperledger-labs/fabric-smart-client/platform/common/utils" "github.com/hyperledger-labs/fabric-smart-client/platform/fabric" "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" "github.com/pkg/errors" @@ -29,7 +30,32 @@ type finalityView struct { } func (f *finalityView) Call(ctx view.Context) (interface{}, error) { - fns, err := fabric.GetFabricNetworkService(ctx, f.Network) + return ctx.RunView(&FinalityView{ + Finality: f.Finality, + timeout: f.timeout, + fnsProvider: utils.MustGet(fabric.GetNetworkServiceProvider(ctx)), + }) +} + +type EndorserFinalityViewFactory struct{} + +func (f *EndorserFinalityViewFactory) NewView(in []byte) (view.View, error) { + v := &finalityView{Finality: &Finality{}} + if err := json.Unmarshal(in, v.Finality); err != nil { + return nil, errors.Wrapf(err, "failed unmarshalling input") + } + return v, nil +} + +type FinalityView struct { + *Finality + timeout time.Duration + + fnsProvider *fabric.NetworkServiceProvider +} + +func (f *FinalityView) Call(ctx view.Context) (interface{}, error) { + fns, err := f.fnsProvider.FabricNetworkService(f.Network) if err != nil { return nil, errors.WithMessagef(err, "fabric network service [%s] not found", f.Network) } @@ -55,12 +81,34 @@ func NewFinalityWithTimeoutView(tx *Transaction, timeout time.Duration) *finalit return &finalityView{Finality: &Finality{TxID: tx.ID(), Network: tx.Network(), Channel: tx.Channel()}, timeout: timeout} } -type FinalityViewFactory struct{} +func NewFinalityViewFactory(fnsProvider *fabric.NetworkServiceProvider) *FinalityViewFactory { + return &FinalityViewFactory{fnsProvider: fnsProvider} +} + +type FinalityViewFactory struct { + fnsProvider *fabric.NetworkServiceProvider +} -func (p *FinalityViewFactory) NewView(in []byte) (view.View, error) { - f := &finalityView{Finality: &Finality{}} - if err := json.Unmarshal(in, f.Finality); err != nil { +func (f *FinalityViewFactory) NewView(in []byte) (view.View, error) { + v := &FinalityView{Finality: &Finality{}, fnsProvider: f.fnsProvider} + if err := json.Unmarshal(in, v.Finality); err != nil { return nil, errors.Wrapf(err, "failed unmarshalling input") } - return f, nil + return v, nil +} + +func (f *FinalityViewFactory) New(tx *Transaction) *FinalityView { + return f.NewWithTimeout(tx, 0) +} + +func (f *FinalityViewFactory) NewWithTimeout(tx *Transaction, timeout time.Duration) *FinalityView { + return &FinalityView{ + Finality: &Finality{ + TxID: tx.ID(), + Network: tx.Network(), + Channel: tx.Channel(), + }, + timeout: timeout, + fnsProvider: f.fnsProvider, + } } diff --git a/platform/fabric/services/endorser/ordering.go b/platform/fabric/services/endorser/ordering.go index b35e42e97..1302dbc33 100644 --- a/platform/fabric/services/endorser/ordering.go +++ b/platform/fabric/services/endorser/ordering.go @@ -9,6 +9,7 @@ package endorser import ( "time" + "github.com/hyperledger-labs/fabric-smart-client/platform/common/utils" "github.com/hyperledger-labs/fabric-smart-client/platform/fabric" "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" "github.com/pkg/errors" @@ -21,7 +22,26 @@ type orderingView struct { } func (o *orderingView) Call(ctx view.Context) (interface{}, error) { - fns, err := fabric.GetFabricNetworkService(ctx, o.tx.Network()) + return ctx.RunView(&OrderingView{ + tx: o.tx, + finality: o.finality, + timeout: o.timeout, + fnsProvider: utils.MustGet(fabric.GetNetworkServiceProvider(ctx)), + finalityView: NewFinalityViewFactory(utils.MustGet(fabric.GetNetworkServiceProvider(ctx))), + }) +} + +type OrderingView struct { + tx *Transaction + finality bool + timeout time.Duration + + fnsProvider *fabric.NetworkServiceProvider + finalityView *FinalityViewFactory +} + +func (o *OrderingView) Call(ctx view.Context) (interface{}, error) { + fns, err := o.fnsProvider.FabricNetworkService(o.tx.Network()) if err != nil { return nil, errors.WithMessagef(err, "fabric network service [%s] not found", o.tx.Network()) } @@ -30,7 +50,7 @@ func (o *orderingView) Call(ctx view.Context) (interface{}, error) { return nil, errors.WithMessagef(err, "failed broadcasting to [%s:%s]", o.tx.Network(), o.tx.Channel()) } if o.finality { - return ctx.RunView(NewFinalityWithTimeoutView(tx, o.timeout)) + return ctx.RunView(o.finalityView.NewWithTimeout(tx, o.timeout)) } return tx, nil } @@ -46,3 +66,32 @@ func NewOrderingAndFinalityWithTimeoutView(tx *Transaction, timeout time.Duratio func NewOrderingView(tx *Transaction) *orderingView { return &orderingView{tx: tx, finality: false} } + +type OrderingAndFinalityViewFactory struct { + fnsProvider *fabric.NetworkServiceProvider + finalityView *FinalityViewFactory +} + +func NewOrderingAndFinalityViewFactory( + fnsProvider *fabric.NetworkServiceProvider, + finalityView *FinalityViewFactory, +) *OrderingAndFinalityViewFactory { + return &OrderingAndFinalityViewFactory{ + fnsProvider: fnsProvider, + finalityView: finalityView, + } +} + +func (f *OrderingAndFinalityViewFactory) New(tx *Transaction, finality bool) *OrderingView { + return f.NewWithTimeout(tx, finality, 0) +} + +func (f *OrderingAndFinalityViewFactory) NewWithTimeout(tx *Transaction, finality bool, timeout time.Duration) *OrderingView { + return &OrderingView{ + tx: tx, + finality: finality, + timeout: timeout, + fnsProvider: f.fnsProvider, + finalityView: f.finalityView, + } +} diff --git a/platform/fabric/services/state/recipients.go b/platform/fabric/services/state/recipients.go index 68a6b8741..8ed912551 100644 --- a/platform/fabric/services/state/recipients.go +++ b/platform/fabric/services/state/recipients.go @@ -10,8 +10,10 @@ import ( "encoding/json" "time" + "github.com/hyperledger-labs/fabric-smart-client/platform/common/utils" "github.com/hyperledger-labs/fabric-smart-client/platform/fabric" view2 "github.com/hyperledger-labs/fabric-smart-client/platform/view" + "github.com/hyperledger-labs/fabric-smart-client/platform/view/driver" session2 "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/session" "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" "github.com/pkg/errors" @@ -198,6 +200,9 @@ type ExchangeRecipientIdentitiesView struct { Network string IdentityLabel string Other view.Identity + + fnsProvider *fabric.NetworkServiceProvider + binder driver.EndpointService } // ExchangeRecipientIdentities runs the ExchangeRecipientIdentitiesView against the passed receiver. @@ -211,6 +216,9 @@ func ExchangeRecipientIdentities(context view.Context, recipient view.Identity, Network: opt.Network, Other: recipient, IdentityLabel: opt.Identity, + + fnsProvider: utils.MustGet(fabric.GetNetworkServiceProvider(context)), + binder: driver.GetEndpointService(context), }) if err != nil { return nil, nil, err @@ -225,7 +233,7 @@ func (f *ExchangeRecipientIdentitiesView) Call(context view.Context) (interface{ return nil, err } - fns, err := fabric.GetFabricNetworkService(context, f.Network) + fns, err := f.fnsProvider.FabricNetworkService(f.Network) if err != nil { return nil, err } @@ -270,14 +278,13 @@ func (f *ExchangeRecipientIdentitiesView) Call(context view.Context) (interface{ // Update the Endpoint Resolver logger.Debugf("bind [%s] to other [%s]", recipientData.Identity, f.Other) - resolver := view2.GetEndpointService(context) - err = resolver.Bind(f.Other, recipientData.Identity) + err = f.binder.Bind(f.Other, recipientData.Identity) if err != nil { return nil, err } logger.Debugf("bind me [%s] to [%s]", me, context.Me()) - err = resolver.Bind(context.Me(), me) + err = f.binder.Bind(context.Me(), me) if err != nil { return nil, err } @@ -285,10 +292,44 @@ func (f *ExchangeRecipientIdentitiesView) Call(context view.Context) (interface{ return []view.Identity{me, recipientData.Identity}, nil } +type ExchangeRecipientIdentitiesViewFactory struct { + fnsProvider *fabric.NetworkServiceProvider + binder driver.EndpointService +} + +func NewExchangeRecipientIdentitiesViewFactory( + fnsProvider *fabric.NetworkServiceProvider, + binder driver.EndpointService, +) *ExchangeRecipientIdentitiesViewFactory { + return &ExchangeRecipientIdentitiesViewFactory{ + fnsProvider: fnsProvider, + binder: binder, + } + +} + +func (f *ExchangeRecipientIdentitiesViewFactory) New(recipient view.Identity, opts ...ServiceOption) (view.View, error) { + opt, err := CompileServiceOptions(opts...) + if err != nil { + return nil, errors.Wrapf(err, "failed to compile service options") + } + return &ExchangeRecipientIdentitiesView{ + Network: opt.Network, + Other: recipient, + IdentityLabel: opt.Identity, + + fnsProvider: f.fnsProvider, + binder: f.binder, + }, nil +} + // RespondExchangeRecipientIdentitiesView models the view of the responder of an exchange of recipient identities. type RespondExchangeRecipientIdentitiesView struct { Network string IdentityLabel string + + fnsProvider *fabric.NetworkServiceProvider + binder driver.EndpointService } // RespondExchangeRecipientIdentities runs the RespondExchangeRecipientIdentitiesView @@ -300,6 +341,9 @@ func RespondExchangeRecipientIdentities(context view.Context, opts ...ServiceOpt ids, err := context.RunView(&RespondExchangeRecipientIdentitiesView{ Network: opt.Network, IdentityLabel: opt.Identity, + + fnsProvider: utils.MustGet(fabric.GetNetworkServiceProvider(context)), + binder: driver.GetEndpointService(context), }) if err != nil { return nil, nil, err @@ -320,7 +364,7 @@ func (s *RespondExchangeRecipientIdentitiesView) Call(context view.Context) (int return nil, err } - fns, err := fabric.GetFabricNetworkService(context, s.Network) + fns, err := s.fnsProvider.FabricNetworkService(s.Network) if err != nil { return nil, err } @@ -352,15 +396,43 @@ func (s *RespondExchangeRecipientIdentitiesView) Call(context view.Context) (int } // Update the Endpoint Resolver - resolver := view2.GetEndpointService(context) - err = resolver.Bind(context.Me(), me) + err = s.binder.Bind(context.Me(), me) if err != nil { return nil, err } - err = resolver.Bind(session.Info().Caller, other) + err = s.binder.Bind(session.Info().Caller, other) if err != nil { return nil, err } return []view.Identity{me, other}, nil } + +type RespondExchangeRecipientIdentitiesViewFactory struct { + fnsProvider *fabric.NetworkServiceProvider + binder driver.EndpointService +} + +func NewRespondExchangeRecipientIdentitiesViewFactory( + fnsProvider *fabric.NetworkServiceProvider, + binder driver.EndpointService, +) *RespondExchangeRecipientIdentitiesViewFactory { + return &RespondExchangeRecipientIdentitiesViewFactory{ + fnsProvider: fnsProvider, + binder: binder, + } +} + +func (f *RespondExchangeRecipientIdentitiesViewFactory) New(opts ...ServiceOption) (*RespondExchangeRecipientIdentitiesView, error) { + opt, err := CompileServiceOptions(opts...) + if err != nil { + return nil, errors.Wrapf(err, "failed to compile service options") + } + return &RespondExchangeRecipientIdentitiesView{ + Network: opt.Network, + IdentityLabel: opt.Identity, + + fnsProvider: f.fnsProvider, + binder: f.binder, + }, nil +} diff --git a/platform/fabric/services/state/transaction.go b/platform/fabric/services/state/transaction.go index d93932e2e..e5222eb25 100755 --- a/platform/fabric/services/state/transaction.go +++ b/platform/fabric/services/state/transaction.go @@ -36,10 +36,10 @@ func Wrap(tx *endorser.Transaction) (*Transaction, error) { // NewTransaction returns a new instance of a state-based transaction that embeds a single namespace. func NewTransaction(context view.Context) (*Transaction, error) { - return newTransaction(utils.MustGet(fabric.GetNetworkServiceProvider(context)), context) + return NewTransactionWithFNSP(utils.MustGet(fabric.GetNetworkServiceProvider(context)), context) } -func newTransaction(fnsProvider *fabric.NetworkServiceProvider, context view.Context) (*Transaction, error) { +func NewTransactionWithFNSP(fnsProvider *fabric.NetworkServiceProvider, context view.Context) (*Transaction, error) { _, tx, err := endorser.NewTransaction(fnsProvider, context) if err != nil { return nil, err