/* Copyright 2021 IBM All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ package gateway import ( "testing" "github.com/hyperledger/fabric-protos-go/peer" "github.com/stretchr/testify/require" ) func TestSingleLayoutPlan(t *testing.T) { layouts := []*layout{ {required: map[string]int{"g1": 1, "g2": 2}}, } groupEndorsers := map[string][]*endorser{ "g1": {peer1Mock}, "g2": {peer2Mock, peer3Mock}, } plan := newPlan(layouts, groupEndorsers) require.Equal(t, plan.size, 3) // total number of endorsers in all layouts endorsers := plan.endorsers() require.Len(t, endorsers, 3) require.ElementsMatch(t, endorsers, []*endorser{peer1Mock, peer2Mock, peer3Mock}) response1 := &peer.ProposalResponse{Payload: []byte("p"), Endorsement: &peer.Endorsement{Endorser: []byte("e1")}} response2 := &peer.ProposalResponse{Payload: []byte("p"), Endorsement: &peer.Endorsement{Endorser: []byte("e2")}} response3 := &peer.ProposalResponse{Payload: []byte("p"), Endorsement: &peer.Endorsement{Endorser: []byte("e3")}} success := plan.processEndorsement(peer1Mock, response1) require.True(t, success) require.Nil(t, plan.completedLayout) success = plan.processEndorsement(peer2Mock, response2) require.True(t, success) require.Nil(t, plan.completedLayout) success = plan.processEndorsement(peer3Mock, response3) require.True(t, success) require.Equal(t, plan.responsePayload, response1.Payload) require.Len(t, plan.completedLayout.endorsements, 3) require.ElementsMatch(t, plan.completedLayout.endorsements, []*peer.Endorsement{response1.Endorsement, response2.Endorsement, response3.Endorsement}) } func TestSingleLayoutRetry(t *testing.T) { layouts := []*layout{ {required: map[string]int{"g1": 1, "g2": 2}}, } groupEndorsers := map[string][]*endorser{ "g1": {localhostMock, peer1Mock}, "g2": {peer2Mock, peer3Mock, peer4Mock}, } plan := newPlan(layouts, groupEndorsers) require.Equal(t, plan.size, 5) // total number of endorsers in all layouts endorsers := plan.endorsers() require.Len(t, endorsers, 3) require.ElementsMatch(t, endorsers, []*endorser{localhostMock, peer2Mock, peer3Mock}) response1 := &peer.ProposalResponse{Payload: []byte("p"), Endorsement: &peer.Endorsement{Endorser: []byte("e1")}} response2 := &peer.ProposalResponse{Payload: []byte("p"), Endorsement: &peer.Endorsement{Endorser: []byte("e2")}} response3 := &peer.ProposalResponse{Payload: []byte("p"), Endorsement: &peer.Endorsement{Endorser: []byte("e3")}} retry := plan.nextPeerInGroup(localhostMock) require.Equal(t, peer1Mock, retry) success := plan.processEndorsement(retry, response1) require.True(t, success) require.Nil(t, plan.completedLayout) success = plan.processEndorsement(peer2Mock, response2) require.True(t, success) require.Nil(t, plan.completedLayout) retry = plan.nextPeerInGroup(peer3Mock) require.Equal(t, peer4Mock, retry) success = plan.processEndorsement(retry, response3) require.True(t, success) require.Equal(t, plan.responsePayload, response1.Payload) require.Len(t, plan.completedLayout.endorsements, 3) require.ElementsMatch(t, plan.completedLayout.endorsements, []*peer.Endorsement{response1.Endorsement, response2.Endorsement, response3.Endorsement}) } func TestMultiLayoutRetry(t *testing.T) { layouts := []*layout{ {required: map[string]int{"g1": 1, "g2": 1}}, {required: map[string]int{"g1": 1, "g3": 1}}, {required: map[string]int{"g2": 1, "g3": 1}}, } groupEndorsers := map[string][]*endorser{ "g1": {localhostMock, peer1Mock}, "g2": {peer2Mock, peer3Mock}, "g3": {peer4Mock}, } plan := newPlan(layouts, groupEndorsers) require.Equal(t, plan.size, 5) // total number of endorsers in all layouts endorsers := plan.endorsers() require.Len(t, endorsers, 2) require.ElementsMatch(t, endorsers, []*endorser{localhostMock, peer2Mock}) response1 := &peer.ProposalResponse{Payload: []byte("p"), Endorsement: &peer.Endorsement{Endorser: []byte("e1")}} response2 := &peer.ProposalResponse{Payload: []byte("p"), Endorsement: &peer.Endorsement{Endorser: []byte("e2")}} // localhost (g1) fails, returns peer1 to retry retry := plan.nextPeerInGroup(localhostMock) require.Equal(t, peer1Mock, retry) // peer2 (g2) succeeds success := plan.processEndorsement(peer2Mock, response1) require.True(t, success) // peer1 (g1) also fails - returns nil, since no more peers in g1 retry = plan.nextPeerInGroup(retry) require.Nil(t, retry) // get endorsers for next layout - should be layout 3 because second layout also required g1 endorsers = plan.endorsers() // layout 3 requires endorsement from g2 & g3, but we already have one for g2, so just need g3 peer require.Len(t, endorsers, 1) require.Equal(t, peer4Mock, endorsers[0]) success = plan.processEndorsement(peer4Mock, response2) require.True(t, success) require.Equal(t, plan.responsePayload, response1.Payload) require.Len(t, plan.completedLayout.endorsements, 2) require.ElementsMatch(t, plan.completedLayout.endorsements, []*peer.Endorsement{response1.Endorsement, response2.Endorsement}) } func TestMultiLayoutFailures(t *testing.T) { layouts := []*layout{ {required: map[string]int{"g1": 1, "g2": 1, "g3": 1}}, {required: map[string]int{"g1": 1, "g2": 2}}, {required: map[string]int{"g1": 2, "g2": 1}}, } groupEndorsers := map[string][]*endorser{ "g1": {localhostMock, peer1Mock}, "g2": {peer2Mock, peer3Mock}, "g3": {peer4Mock}, } plan := newPlan(layouts, groupEndorsers) require.Equal(t, plan.size, 5) // total number of endorsers in all layouts endorsers := plan.endorsers() // first layout require.Len(t, endorsers, 3) require.ElementsMatch(t, endorsers, []*endorser{localhostMock, peer2Mock, peer4Mock}) response1 := &peer.ProposalResponse{Payload: []byte("p"), Endorsement: &peer.Endorsement{Endorser: []byte("e1")}} response2 := &peer.ProposalResponse{Payload: []byte("p"), Endorsement: &peer.Endorsement{Endorser: []byte("e2")}} // localhost (g1) succeeds success := plan.processEndorsement(localhostMock, response1) require.True(t, success) // peer2 (g2) fails - returns peer3 to retry retry := plan.nextPeerInGroup(peer2Mock) require.Equal(t, peer3Mock, retry) // peer4 (g3) also fails - returns nil, since no more peers in g3 g3retry := plan.nextPeerInGroup(peer4Mock) require.Nil(t, g3retry) // retry g2 - succeeds success = plan.processEndorsement(retry, response2) require.True(t, success) // nothing more to try in this layout - get endorsers for next layout // layout 2 requires a second endorsement from g2, but all g2 peers have been tried - only 1 succeeded // should return layout 3 which requires a second endorsement from g1 endorsers = plan.endorsers() require.Len(t, endorsers, 1) require.Equal(t, peer1Mock, endorsers[0]) // this one fails too retry = plan.nextPeerInGroup(peer1Mock) // no more in this group require.Nil(t, retry) endorsers = plan.endorsers() // we've run out of layouts - failed to endorse! require.Nil(t, endorsers) } func TestMultiPlan(t *testing.T) { layouts1 := []*layout{ {required: map[string]int{"g1": 1}}, } groupEndorsers1 := map[string][]*endorser{ "g1": {localhostMock, peer1Mock}, } // plan 1 is used to determine the first endorser plan1 := newPlan(layouts1, groupEndorsers1) require.Equal(t, plan1.size, 2) layouts2 := []*layout{ {required: map[string]int{"g1": 1, "g2": 1}}, {required: map[string]int{"g1": 1, "g3": 1}}, {required: map[string]int{"g2": 1, "g3": 1}}, } groupEndorsers2 := map[string][]*endorser{ "g1": {localhostMock, peer1Mock}, "g2": {peer2Mock, peer3Mock}, "g3": {peer4Mock}, } // plan 2 is derived from the chaincode interest from the first endorsement plan2 := newPlan(layouts2, groupEndorsers2) require.Equal(t, plan2.size, 5) endorsers := plan1.endorsers() require.Len(t, endorsers, 1) require.ElementsMatch(t, endorsers, []*endorser{localhostMock}) response1 := &peer.ProposalResponse{Payload: []byte("p"), Endorsement: &peer.Endorsement{Endorser: []byte("e1")}} response2 := &peer.ProposalResponse{Payload: []byte("p"), Endorsement: &peer.Endorsement{Endorser: []byte("e2")}} // localhost succeeds, remove from plan 2 endorsers = plan2.endorsers() require.Len(t, endorsers, 2) success := plan2.processEndorsement(localhostMock, response1) require.True(t, success) // peer2 (g2) succeeds success = plan2.processEndorsement(peer2Mock, response2) require.True(t, success) require.Equal(t, plan2.responsePayload, response1.Payload) require.Len(t, plan2.completedLayout.endorsements, 2) require.ElementsMatch(t, plan2.completedLayout.endorsements, []*peer.Endorsement{response1.Endorsement, response2.Endorsement}) } func TestMultiPlanNoOverlap(t *testing.T) { layouts1 := []*layout{ {required: map[string]int{"g1": 1}}, } groupEndorsers1 := map[string][]*endorser{ "g1": {localhostMock, peer1Mock}, } // plan 1 is used to determine the first endorser plan1 := newPlan(layouts1, groupEndorsers1) require.Equal(t, plan1.size, 2) layouts2 := []*layout{ {required: map[string]int{"g2": 1, "g3": 1}}, } groupEndorsers2 := map[string][]*endorser{ "g2": {peer2Mock, peer3Mock}, "g3": {peer4Mock}, } // plan 2 is derived from the chaincode interest from the first endorsement, but doesn't include first endorser plan2 := newPlan(layouts2, groupEndorsers2) require.Equal(t, plan2.size, 3) endorsers := plan1.endorsers() require.Len(t, endorsers, 1) require.ElementsMatch(t, endorsers, []*endorser{localhostMock}) response1 := &peer.ProposalResponse{Payload: []byte("p"), Endorsement: &peer.Endorsement{Endorser: []byte("e1")}} response2 := &peer.ProposalResponse{Payload: []byte("p"), Endorsement: &peer.Endorsement{Endorser: []byte("e2")}} response3 := &peer.ProposalResponse{Payload: []byte("p"), Endorsement: &peer.Endorsement{Endorser: []byte("e3")}} // localhost succeeds, try to remove from plan 2 (should be no-op) endorsers = plan2.endorsers() require.Len(t, endorsers, 2) success := plan2.processEndorsement(localhostMock, response1) require.True(t, success) // peer2 (g2) succeeds success = plan2.processEndorsement(peer2Mock, response2) require.True(t, success) // peer4 (g3) succeeds success = plan2.processEndorsement(peer4Mock, response3) require.True(t, success) require.Equal(t, plan2.responsePayload, response1.Payload) require.Len(t, plan2.completedLayout.endorsements, 2) require.ElementsMatch(t, plan2.completedLayout.endorsements, []*peer.Endorsement{response2.Endorsement, response3.Endorsement}) } func TestUniqueEndorsements(t *testing.T) { e1 := &peer.Endorsement{Endorser: []byte("endorserA")} e2 := &peer.Endorsement{Endorser: []byte("endorserB")} e3 := &peer.Endorsement{Endorser: []byte("endorserA")} e4 := &peer.Endorsement{Endorser: []byte("endorserC")} unique := uniqueEndorsements([]*peer.Endorsement{e1, e2, e3, e4}) require.Len(t, unique, 3) require.ElementsMatch(t, unique, []*peer.Endorsement{e1, e2, e4}) }