Commit 00678c86 authored by Yacov Manevich's avatar Yacov Manevich Committed by Gerrit Code Review
Browse files

Merge "[FAB-12792] Prover Peer: ExpectationRequest"

parents 38157497 eccf2758
This diff is collapsed.
......@@ -12,6 +12,7 @@ option java_package = "org.hyperledger.fabric.protos.token";
package protos;
import "google/protobuf/timestamp.proto";
import "token/expectations.proto";
import "token/transaction.proto";
// TokenToIssue describes a token to be issued in the system
......@@ -110,6 +111,19 @@ message ApproveRequest{
repeated bytes token_ids = 3;
}
// ExpectationRequest is used to request indirect token import or transfer based on the token expectation
message ExpectationRequest {
// credential contains information for the party who is requesting the operation
// The content of this field depends on the characteristic of token manager system
bytes credential = 1;
// expectation contains the expected outputs for token import or transfer
TokenExpectation expectation = 2;
// TokenIds are the token identifiers used to fulfill the expectation
repeated bytes token_ids = 3;
}
// Header is a generic replay prevention and identity message to include in a signed command
message Header {
// Timestamp is the local time when the message was created
......@@ -142,6 +156,7 @@ message Command {
RedeemRequest redeem_request = 5;
ApproveRequest approve_request = 6;
TransferRequest transfer_from_request = 7;
ExpectationRequest expectation_request = 8;
}
}
......@@ -208,4 +223,4 @@ service Prover {
// operation was succeffully executed and if not, the response
// reports the reason of the failure.
rpc ProcessCommand(SignedCommand) returns (SignedCommandResponse) {}
}
\ No newline at end of file
}
......@@ -3,6 +3,7 @@ Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package server
import (
......@@ -82,7 +83,37 @@ func (ac *PolicyBasedAccessControl) Check(sc *token.SignedCommand, c *token.Comm
c.Header.ChannelId,
signedData,
)
case *token.Command_ExpectationRequest:
if c.GetExpectationRequest().GetExpectation() == nil {
return errors.New("ExpectationRequest has nil Expectation")
}
plainExpectation := c.GetExpectationRequest().GetExpectation().GetPlainExpectation()
if plainExpectation == nil {
return errors.New("ExpectationRequest has nil PlainExpectation")
}
return ac.checkExpectation(plainExpectation, signedData, c)
default:
return errors.Errorf("command type not recognized: %T", t)
}
}
// checkExpectation checks either issue policy or transfer policy depending on the payload type in expectation
func (ac *PolicyBasedAccessControl) checkExpectation(plainExpectation *token.PlainExpectation, signedData []*common.SignedData, c *token.Command) error {
switch t := plainExpectation.GetPayload().(type) {
case *token.PlainExpectation_ImportExpectation:
return ac.ACLProvider.CheckACL(
ac.ACLResources.IssueTokens,
c.Header.ChannelId,
signedData,
)
case *token.PlainExpectation_TransferExpectation:
return ac.ACLProvider.CheckACL(
ac.ACLResources.TransferTokens,
c.Header.ChannelId,
signedData,
)
default:
return errors.Errorf("expectation payload type not recognized: %T", t)
}
}
......@@ -143,4 +143,123 @@ var _ = Describe("AccessControl", func() {
Expect(err).To(MatchError("command type not recognized: <nil>"))
})
})
It("validates the policy for expectation import command", func() {
importExpectationRequest := &token.ExpectationRequest{
Expectation: &token.TokenExpectation{
Expectation: &token.TokenExpectation_PlainExpectation{
PlainExpectation: &token.PlainExpectation{
Payload: &token.PlainExpectation_ImportExpectation{
ImportExpectation: &token.PlainTokenExpectation{},
},
},
},
},
}
expectationCommand := &token.Command{
Header: header,
Payload: &token.Command_ExpectationRequest{
ExpectationRequest: importExpectationRequest,
},
}
signedExpectationCommand := &token.SignedCommand{
Command: ProtoMarshal(expectationCommand),
Signature: []byte("signature"),
}
err := pbac.Check(signedExpectationCommand, command)
Expect(err).NotTo(HaveOccurred())
Expect(fakeACLProvider.CheckACLCallCount()).To(Equal(1))
resourceName, channelID, signedData := fakeACLProvider.CheckACLArgsForCall(0)
Expect(resourceName).To(Equal(aclResources.IssueTokens))
Expect(channelID).To(Equal("channel-id"))
Expect(signedData).To(ConsistOf(&common.SignedData{
Data: signedExpectationCommand.Command,
Identity: []byte("creator"),
Signature: []byte("signature"),
}))
})
It("validates the policy for expectation transfer command", func() {
transferExpectationRequest := &token.ExpectationRequest{
Expectation: &token.TokenExpectation{
Expectation: &token.TokenExpectation_PlainExpectation{
PlainExpectation: &token.PlainExpectation{
Payload: &token.PlainExpectation_TransferExpectation{
TransferExpectation: &token.PlainTokenExpectation{},
},
},
},
},
}
expectationCommand := &token.Command{
Header: header,
Payload: &token.Command_ExpectationRequest{
ExpectationRequest: transferExpectationRequest,
},
}
signedExpectationCommand := &token.SignedCommand{
Command: ProtoMarshal(expectationCommand),
Signature: []byte("signature"),
}
err := pbac.Check(signedExpectationCommand, command)
Expect(err).NotTo(HaveOccurred())
Expect(fakeACLProvider.CheckACLCallCount()).To(Equal(1))
resourceName, channelID, signedData := fakeACLProvider.CheckACLArgsForCall(0)
Expect(resourceName).To(Equal(aclResources.IssueTokens))
Expect(channelID).To(Equal("channel-id"))
Expect(signedData).To(ConsistOf(&common.SignedData{
Data: signedExpectationCommand.Command,
Identity: []byte("creator"),
Signature: []byte("signature"),
}))
})
Context("when Expectationrequest has nil Expectation", func() {
BeforeEach(func() {
importExpectationRequest := &token.ExpectationRequest{
Credential: []byte("credential"),
}
command = &token.Command{
Header: header,
Payload: &token.Command_ExpectationRequest{
ExpectationRequest: importExpectationRequest,
},
}
})
It("returns the error", func() {
signedCommand := &token.SignedCommand{
Command: ProtoMarshal(command),
Signature: []byte("signature"),
}
err := pbac.Check(signedCommand, command)
Expect(err).To(MatchError("ExpectationRequest has nil Expectation"))
})
})
Context("when Expectationrequest has nil PlainExpectation", func() {
BeforeEach(func() {
importExpectationRequest := &token.ExpectationRequest{
Credential: []byte("credential"),
Expectation: &token.TokenExpectation{},
}
command = &token.Command{
Header: header,
Payload: &token.Command_ExpectationRequest{
ExpectationRequest: importExpectationRequest,
},
}
})
It("returns the error", func() {
signedCommand := &token.SignedCommand{
Command: ProtoMarshal(command),
Signature: []byte("signature"),
}
err := pbac.Check(signedCommand, command)
Expect(err).To(MatchError("ExpectationRequest has nil PlainExpectation"))
})
})
})
......@@ -9,6 +9,19 @@ import (
)
type Issuer struct {
RequestExpectationStub func(*token.ExpectationRequest) (*token.TokenTransaction, error)
requestExpectationMutex sync.RWMutex
requestExpectationArgsForCall []struct {
arg1 *token.ExpectationRequest
}
requestExpectationReturns struct {
result1 *token.TokenTransaction
result2 error
}
requestExpectationReturnsOnCall map[int]struct {
result1 *token.TokenTransaction
result2 error
}
RequestImportStub func([]*token.TokenToIssue) (*token.TokenTransaction, error)
requestImportMutex sync.RWMutex
requestImportArgsForCall []struct {
......@@ -26,6 +39,69 @@ type Issuer struct {
invocationsMutex sync.RWMutex
}
func (fake *Issuer) RequestExpectation(arg1 *token.ExpectationRequest) (*token.TokenTransaction, error) {
fake.requestExpectationMutex.Lock()
ret, specificReturn := fake.requestExpectationReturnsOnCall[len(fake.requestExpectationArgsForCall)]
fake.requestExpectationArgsForCall = append(fake.requestExpectationArgsForCall, struct {
arg1 *token.ExpectationRequest
}{arg1})
fake.recordInvocation("RequestExpectation", []interface{}{arg1})
fake.requestExpectationMutex.Unlock()
if fake.RequestExpectationStub != nil {
return fake.RequestExpectationStub(arg1)
}
if specificReturn {
return ret.result1, ret.result2
}
fakeReturns := fake.requestExpectationReturns
return fakeReturns.result1, fakeReturns.result2
}
func (fake *Issuer) RequestExpectationCallCount() int {
fake.requestExpectationMutex.RLock()
defer fake.requestExpectationMutex.RUnlock()
return len(fake.requestExpectationArgsForCall)
}
func (fake *Issuer) RequestExpectationCalls(stub func(*token.ExpectationRequest) (*token.TokenTransaction, error)) {
fake.requestExpectationMutex.Lock()
defer fake.requestExpectationMutex.Unlock()
fake.RequestExpectationStub = stub
}
func (fake *Issuer) RequestExpectationArgsForCall(i int) *token.ExpectationRequest {
fake.requestExpectationMutex.RLock()
defer fake.requestExpectationMutex.RUnlock()
argsForCall := fake.requestExpectationArgsForCall[i]
return argsForCall.arg1
}
func (fake *Issuer) RequestExpectationReturns(result1 *token.TokenTransaction, result2 error) {
fake.requestExpectationMutex.Lock()
defer fake.requestExpectationMutex.Unlock()
fake.RequestExpectationStub = nil
fake.requestExpectationReturns = struct {
result1 *token.TokenTransaction
result2 error
}{result1, result2}
}
func (fake *Issuer) RequestExpectationReturnsOnCall(i int, result1 *token.TokenTransaction, result2 error) {
fake.requestExpectationMutex.Lock()
defer fake.requestExpectationMutex.Unlock()
fake.RequestExpectationStub = nil
if fake.requestExpectationReturnsOnCall == nil {
fake.requestExpectationReturnsOnCall = make(map[int]struct {
result1 *token.TokenTransaction
result2 error
})
}
fake.requestExpectationReturnsOnCall[i] = struct {
result1 *token.TokenTransaction
result2 error
}{result1, result2}
}
func (fake *Issuer) RequestImport(arg1 []*token.TokenToIssue) (*token.TokenTransaction, error) {
var arg1Copy []*token.TokenToIssue
if arg1 != nil {
......@@ -97,6 +173,8 @@ func (fake *Issuer) RequestImportReturnsOnCall(i int, result1 *token.TokenTransa
func (fake *Issuer) Invocations() map[string][][]interface{} {
fake.invocationsMutex.RLock()
defer fake.invocationsMutex.RUnlock()
fake.requestExpectationMutex.RLock()
defer fake.requestExpectationMutex.RUnlock()
fake.requestImportMutex.RLock()
defer fake.requestImportMutex.RUnlock()
copiedInvocations := map[string][][]interface{}{}
......
This diff is collapsed.
......@@ -92,6 +92,8 @@ func (s *Prover) ProcessCommand(ctx context.Context, sc *token.SignedCommand) (*
payload, err = s.RequestApprove(ctx, command.Header, t.ApproveRequest)
case *token.Command_TransferFromRequest:
payload, err = s.RequestTransferFrom(ctx, command.Header, t.TransferFromRequest)
case *token.Command_ExpectationRequest:
payload, err = s.RequestExpectation(ctx, command.Header, t.ExpectationRequest)
default:
err = errors.Errorf("command type not recognized: %T", t)
}
......@@ -194,6 +196,47 @@ func (s *Prover) RequestTransferFrom(ctx context.Context, header *token.Header,
return &token.CommandResponse_TokenTransaction{TokenTransaction: tokenTransaction}, nil
}
// RequestExpectation gets an issuer or transactor and creates a token transaction response
// for import, transfer or redemption.
func (s *Prover) RequestExpectation(ctx context.Context, header *token.Header, request *token.ExpectationRequest) (*token.CommandResponse_TokenTransaction, error) {
if request.GetExpectation() == nil {
return nil, errors.New("ExpectationRequest has nil Expectation")
}
plainExpectation := request.GetExpectation().GetPlainExpectation()
if plainExpectation == nil {
return nil, errors.New("ExpectationRequest has nil PlainExpectation")
}
// get either issuer or transactor based on payload type in the request
var tokenTransaction *token.TokenTransaction
switch t := plainExpectation.GetPayload().(type) {
case *token.PlainExpectation_ImportExpectation:
issuer, err := s.TMSManager.GetIssuer(header.ChannelId, request.Credential, header.Creator)
if err != nil {
return nil, err
}
tokenTransaction, err = issuer.RequestExpectation(request)
if err != nil {
return nil, err
}
case *token.PlainExpectation_TransferExpectation:
transactor, err := s.TMSManager.GetTransactor(header.ChannelId, request.Credential, header.Creator)
if err != nil {
return nil, err
}
defer transactor.Done()
tokenTransaction, err = transactor.RequestExpectation(request)
if err != nil {
return nil, err
}
default:
return nil, errors.Errorf("expectation payload type not recognized: %T", t)
}
return &token.CommandResponse_TokenTransaction{TokenTransaction: tokenTransaction}, nil
}
func (s *Prover) ValidateHeader(header *token.Header) error {
if header == nil {
return errors.New("command header is required")
......
......@@ -57,6 +57,12 @@ var _ = Describe("Prover", func() {
listRequest *token.ListRequest
unspentTokens *token.UnspentTokens
transactorTokens []*token.TokenOutput
importExpectationRequest *token.ExpectationRequest
importExpectationTransaction *token.TokenTransaction
transferExpectationRequest *token.ExpectationRequest
transferExpectationTransaction *token.TokenTransaction
)
BeforeEach(func() {
......@@ -189,6 +195,50 @@ var _ = Describe("Prover", func() {
listRequest = &token.ListRequest{
Credential: []byte("credential"),
}
importExpectationRequest = &token.ExpectationRequest{
Credential: []byte("credential"),
Expectation: &token.TokenExpectation{
Expectation: &token.TokenExpectation_PlainExpectation{
PlainExpectation: &token.PlainExpectation{
Payload: &token.PlainExpectation_ImportExpectation{
ImportExpectation: &token.PlainTokenExpectation{
Outputs: []*token.PlainOutput{{
Owner: []byte("recipient"),
Type: "XYZ",
Quantity: 99,
}},
},
},
},
},
},
}
transferExpectationRequest = &token.ExpectationRequest{
Credential: []byte("credential"),
TokenIds: [][]byte{},
Expectation: &token.TokenExpectation{
Expectation: &token.TokenExpectation_PlainExpectation{
PlainExpectation: &token.PlainExpectation{
Payload: &token.PlainExpectation_TransferExpectation{
TransferExpectation: &token.PlainTokenExpectation{
Outputs: []*token.PlainOutput{{
Owner: []byte("token-owner"),
Type: "PDQ",
Quantity: 777,
}},
},
},
},
},
},
}
importExpectationTransaction = tokenTransaction
transferExpectationTransaction = trTokenTransaction
fakeIssuer.RequestExpectationReturns(tokenTransaction, nil)
fakeTransactor.RequestExpectationReturns(trTokenTransaction, nil)
})
Describe("ProcessCommand", func() {
......@@ -639,6 +689,129 @@ var _ = Describe("Prover", func() {
})
})
Describe("RequestExpectation import", func() {
It("gets an issuer", func() {
_, err := prover.RequestExpectation(context.Background(), command.Header, importExpectationRequest)
Expect(err).NotTo(HaveOccurred())
Expect(fakeTMSManager.GetIssuerCallCount()).To(Equal(1))
Expect(fakeTMSManager.GetTransactorCallCount()).To(Equal(0))
channel, cred, creator := fakeTMSManager.GetIssuerArgsForCall(0)
Expect(channel).To(Equal("channel-id"))
Expect(cred).To(Equal([]byte("credential")))
Expect(creator).To(Equal([]byte("creator")))
})
It("uses the issuer to request an import", func() {
resp, err := prover.RequestExpectation(context.Background(), command.Header, importExpectationRequest)
Expect(err).NotTo(HaveOccurred())
Expect(resp).To(Equal(&token.CommandResponse_TokenTransaction{
TokenTransaction: importExpectationTransaction,
}))
Expect(fakeIssuer.RequestExpectationCallCount()).To(Equal(1))
request := fakeIssuer.RequestExpectationArgsForCall(0)
Expect(request).To(Equal(importExpectationRequest))
})
Context("when the TMS manager fails to get an issuer", func() {
BeforeEach(func() {
fakeTMSManager.GetIssuerReturns(nil, errors.New("boing boing"))
})
It("returns the error", func() {
_, err := prover.RequestExpectation(context.Background(), command.Header, importExpectationRequest)
Expect(err).To(MatchError("boing boing"))
})
})
Context("when the issuer fails to import", func() {
BeforeEach(func() {
fakeIssuer.RequestExpectationReturns(nil, errors.New("watermelon"))
})
It("returns the error", func() {
_, err := prover.RequestExpectation(context.Background(), command.Header, importExpectationRequest)
Expect(err).To(MatchError("watermelon"))
})
})
Context("when Expectationrequest has nil Expectation", func() {
BeforeEach(func() {
importExpectationRequest = &token.ExpectationRequest{
Credential: []byte("credential"),
}
})
It("returns the error", func() {
_, err := prover.RequestExpectation(context.Background(), command.Header, importExpectationRequest)
Expect(err).To(MatchError("ExpectationRequest has nil Expectation"))
})
})
Context("when Expectationrequest has nil PlainExpectation", func() {
BeforeEach(func() {
importExpectationRequest = &token.ExpectationRequest{
Credential: []byte("credential"),
Expectation: &token.TokenExpectation{},
}
})
It("returns the error", func() {
_, err := prover.RequestExpectation(context.Background(), command.Header, importExpectationRequest)
Expect(err).To(MatchError("ExpectationRequest has nil PlainExpectation"))
})
})
})
Describe("RequestExpectation transfer", func() {
It("gets a transactor", func() {
_, err := prover.RequestExpectation(context.Background(), command.Header, transferExpectationRequest)
Expect(err).NotTo(HaveOccurred())
Expect(fakeTMSManager.GetIssuerCallCount()).To(Equal(0))
Expect(fakeTMSManager.GetTransactorCallCount()).To(Equal(1))
channel, cred, creator := fakeTMSManager.GetTransactorArgsForCall(0)
Expect(channel).To(Equal("channel-id"))
Expect(cred).To(Equal([]byte("credential")))
Expect(creator).To(Equal([]byte("creator")))
})
It("uses the transactor to request a transfer expectation", func() {
resp, err := prover.RequestExpectation(context.Background(), command.Header, transferExpectationRequest)
Expect(err).NotTo(HaveOccurred())
Expect(resp).To(Equal(&token.CommandResponse_TokenTransaction{
TokenTransaction: transferExpectationTransaction,
}))
Expect(fakeTransactor.RequestExpectationCallCount()).To(Equal(1))
tr := fakeTransactor.RequestExpectationArgsForCall(0)
Expect(tr).To(Equal(transferExpectationRequest))
})
Context("when the TMS manager fails to get a transactor", func() {
BeforeEach(func() {
fakeTMSManager.GetTransactorReturns(nil, errors.New("boing boing"))
})
It("returns the error", func() {
_, err := prover.RequestExpectation(context.Background(), command.Header, transferExpectationRequest)
Expect(err).To(MatchError("boing boing"))
})
})
Context("when the transactor fails to transfer", func() {
BeforeEach(func() {
fakeTransactor.RequestExpectationReturns(nil, errors.New("watermelon"))
})
It("returns the error", func() {
_, err := prover.RequestExpectation(context.Background(), command.Header, transferExpectationRequest)
Expect(err).To(MatchError("watermelon"))
})
})
})
Describe("Issue tokens by a plain issuer", func() {
var (
manager *server.Manager
......@@ -849,6 +1022,74 @@ var _ = Describe("Prover", func() {
})
})
})
Describe("ProcessCommand_RequestExpection for import", func() {
BeforeEach(func() {
command = &token.Command{
Header: &token.Header{
ChannelId: "channel-id",
Creator: []byte("creator"),
Nonce: []byte("nonce"),
},
Payload: &token.Command_ExpectationRequest{
ExpectationRequest: importExpectationRequest,
},
}
marshaledCommand = ProtoMarshal(command)
signedCommand = &token.SignedCommand{
Command: marshaledCommand,
Signature: []byte("command-signature"),
}
fakeMarshaler.MarshalCommandResponseReturns(marshaledResponse, nil)
})
It("returns a signed command response", func() {
resp, err := prover.ProcessCommand(context.Background(), signedCommand)
Expect(err).NotTo(HaveOccurred())
Expect(resp).To(Equal(marshaledResponse))
Expect(fakeMarshaler.MarshalCommandResponseCallCount()).To(Equal(1))
cmd, payload := fakeMarshaler.MarshalCommandResponseArgsForCall(0)
Expect(cmd).To(Equal(marshaledCommand))
Expect(payload).To(Equal(&token.CommandResponse_TokenTransaction{
TokenTransaction: importExpectationTransaction,
}))
})
})
<