Skip to content

Commit

Permalink
Support mandatory recipients (#1243)
Browse files Browse the repository at this point in the history
* Add MandatoryRecipients data in SendRequest and SendSignedTxRequest to private TM

* Update send and sendsignedtx method to include MandatoryRecipients, reject if PTM does not support correct version

* Add new Tessera feature version - mandatoryRecipientsVersion

* Add PrivacyFlagMandatoryRecipients and new feature to support MandatoryRecipients

* Add mandatoryFor to PrivateTxArgs, validations and unit tests

Co-authored-by: Krish Swaminathan <[email protected]>
  • Loading branch information
namtruong and Krish1979 authored Sep 7, 2021
1 parent 15fcead commit 38999c7
Show file tree
Hide file tree
Showing 9 changed files with 288 additions and 21 deletions.
39 changes: 27 additions & 12 deletions internal/ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -1780,9 +1780,10 @@ type PrivateTxArgs struct {
PrivateFrom string `json:"privateFrom"`
// PrivateFor is the list of public keys which are available in the Private Transaction Managers in the network.
// The transaction payload is only visible to those party to the transaction.
PrivateFor []string `json:"privateFor"`
PrivateTxType string `json:"restriction"`
PrivacyFlag engine.PrivacyFlagType `json:"privacyFlag"`
PrivateFor []string `json:"privateFor"`
PrivateTxType string `json:"restriction"`
PrivacyFlag engine.PrivacyFlagType `json:"privacyFlag"`
MandatoryRecipients []string `json:"mandatoryFor"`
}

func (args *PrivateTxArgs) SetDefaultPrivateFrom(ctx context.Context, b Backend) error {
Expand Down Expand Up @@ -2711,6 +2712,16 @@ func checkAndHandlePrivateTransaction(ctx context.Context, b Backend, tx *types.
return
}

if engine.PrivacyFlagMandatoryRecipients == privateTxArgs.PrivacyFlag && len(privateTxArgs.MandatoryRecipients) == 0 {
err = fmt.Errorf("missing mandatory recipients data. if no mandatory recipients required consider using PrivacyFlag=1(PartyProtection)")
return
}

if engine.PrivacyFlagMandatoryRecipients != privateTxArgs.PrivacyFlag && len(privateTxArgs.MandatoryRecipients) > 0 {
err = fmt.Errorf("privacy metadata invalid. mandatory recipients are only applicable for PrivacyFlag=2(MandatoryRecipients)")
return
}

// validate that PrivateFrom is one of the addresses of the private state resolved from the user context
if b.ChainConfig().IsMPS {
var psm *mps.PrivateStateMetadata
Expand Down Expand Up @@ -2761,7 +2772,7 @@ func handlePrivateTransaction(ctx context.Context, b Backend, tx *types.Transact

data := tx.Data()

log.Debug("sending private tx", "txnType", txnType, "data", common.FormatTerminalString(data), "privatefrom", privateTxArgs.PrivateFrom, "privatefor", privateTxArgs.PrivateFor, "privacyFlag", privateTxArgs.PrivacyFlag)
log.Debug("sending private tx", "txnType", txnType, "data", common.FormatTerminalString(data), "privatefrom", privateTxArgs.PrivateFrom, "privatefor", privateTxArgs.PrivateFor, "privacyFlag", privateTxArgs.PrivacyFlag, "mandatoryfor", privateTxArgs.MandatoryRecipients)

switch txnType {
case FillTransaction:
Expand Down Expand Up @@ -2799,9 +2810,10 @@ func handleRawPrivateTransaction(ctx context.Context, b Backend, tx *types.Trans
}

metadata := engine.ExtraMetadata{
ACHashes: affectedCATxHashes,
ACMerkleRoot: merkleRoot,
PrivacyFlag: privateTxArgs.PrivacyFlag,
ACHashes: affectedCATxHashes,
ACMerkleRoot: merkleRoot,
PrivacyFlag: privateTxArgs.PrivacyFlag,
MandatoryRecipients: privateTxArgs.MandatoryRecipients,
}
_, _, data, err = private.P.SendSignedTx(hash, privateTxArgs.PrivateFor, &metadata)
if err != nil {
Expand All @@ -2815,7 +2827,8 @@ func handleRawPrivateTransaction(ctx context.Context, b Backend, tx *types.Trans
"privatefor", privateTxArgs.PrivateFor,
"affectedCATxHashes", metadata.ACHashes,
"merkleroot", metadata.ACHashes,
"privacyflag", metadata.PrivacyFlag)
"privacyflag", metadata.PrivacyFlag,
"mandatoryrecipients", metadata.MandatoryRecipients)
return
}

Expand All @@ -2828,9 +2841,10 @@ func handleNormalPrivateTransaction(ctx context.Context, b Backend, tx *types.Tr
}

metadata := engine.ExtraMetadata{
ACHashes: affectedCATxHashes,
ACMerkleRoot: merkleRoot,
PrivacyFlag: privateTxArgs.PrivacyFlag,
ACHashes: affectedCATxHashes,
ACMerkleRoot: merkleRoot,
PrivacyFlag: privateTxArgs.PrivacyFlag,
MandatoryRecipients: privateTxArgs.MandatoryRecipients,
}
_, _, hash, err = private.P.Send(data, privateTxArgs.PrivateFrom, privateTxArgs.PrivateFor, &metadata)
if err != nil {
Expand All @@ -2844,7 +2858,8 @@ func handleNormalPrivateTransaction(ctx context.Context, b Backend, tx *types.Tr
"privatefor", privateTxArgs.PrivateFor,
"affectedCATxHashes", metadata.ACHashes,
"merkleroot", metadata.ACHashes,
"privacyflag", metadata.PrivacyFlag)
"privacyflag", metadata.PrivacyFlag,
"mandatoryrecipients", metadata.MandatoryRecipients)
return
}

Expand Down
100 changes: 96 additions & 4 deletions internal/ethapi/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,11 @@ import (
)

var (
arbitraryCtx = context.Background()
arbitraryPrivateFrom = "arbitrary private from"
arbitraryPrivateFor = []string{"arbitrary party 1", "arbitrary party 2"}
privateTxArgs = &PrivateTxArgs{
arbitraryCtx = context.Background()
arbitraryPrivateFrom = "arbitrary private from"
arbitraryPrivateFor = []string{"arbitrary party 1", "arbitrary party 2"}
arbitraryMandatoryFor = []string{"arbitrary party 2"}
privateTxArgs = &PrivateTxArgs{
PrivateFrom: arbitraryPrivateFrom,
PrivateFor: arbitraryPrivateFor,
}
Expand Down Expand Up @@ -531,6 +532,97 @@ func TestHandlePrivateTransaction_whenRawStandardPrivateMessageCall(t *testing.T

}

func TestHandlePrivateTransaction_whenMandatoryRecipients(t *testing.T) {
assert := assert.New(t)
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()

mockTM := private.NewMockPrivateTransactionManager(mockCtrl)

saved := private.P
defer func() {
private.P = saved
privateTxArgs.MandatoryRecipients = nil
}()
private.P = mockTM
privateTxArgs.MandatoryRecipients = arbitraryMandatoryFor
privateTxArgs.PrivacyFlag = engine.PrivacyFlagMandatoryRecipients

var capturedMetadata engine.ExtraMetadata

mockTM.EXPECT().Send(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
Do(func(arg1 interface{}, arg2 string, arg3 interface{}, arg4 *engine.ExtraMetadata) {
capturedMetadata = *arg4
}).Times(1)

_, err := handlePrivateTransaction(arbitraryCtx, &StubBackend{}, simpleStorageContractCreationTx, privateTxArgs, arbitraryFrom, NormalTransaction)

assert.NoError(err)
assert.Equal(engine.PrivacyFlagMandatoryRecipients, capturedMetadata.PrivacyFlag)
assert.Equal(arbitraryMandatoryFor, capturedMetadata.MandatoryRecipients)

}

func TestHandlePrivateTransaction_whenRawPrivateWithMandatoryRecipients(t *testing.T) {
assert := assert.New(t)
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()

mockTM := private.NewMockPrivateTransactionManager(mockCtrl)

saved := private.P
defer func() {
private.P = saved
privateTxArgs.MandatoryRecipients = nil
}()
private.P = mockTM
privateTxArgs.MandatoryRecipients = arbitraryMandatoryFor

privateTxArgs.PrivacyFlag = engine.PrivacyFlagMandatoryRecipients

var capturedMetadata engine.ExtraMetadata

mockTM.EXPECT().ReceiveRaw(gomock.Any()).Times(1)

mockTM.EXPECT().SendSignedTx(gomock.Any(), gomock.Any(), gomock.Any()).
Do(func(arg1 interface{}, arg2 []string, arg3 *engine.ExtraMetadata) {
capturedMetadata = *arg3
}).Times(1)

_, err := handlePrivateTransaction(arbitraryCtx, &StubBackend{}, simpleStorageContractCreationTx, privateTxArgs, arbitraryFrom, RawTransaction)

assert.NoError(err)
assert.Equal(engine.PrivacyFlagMandatoryRecipients, capturedMetadata.PrivacyFlag)
assert.Equal(arbitraryMandatoryFor, capturedMetadata.MandatoryRecipients)

}

func TestHandlePrivateTransaction_whenMandatoryRecipientsDataInvalid(t *testing.T) {
assert := assert.New(t)

privateTxArgs.PrivacyFlag = engine.PrivacyFlagMandatoryRecipients

_, _, _, err := checkAndHandlePrivateTransaction(arbitraryCtx, &StubBackend{}, simpleStorageContractCreationTx, privateTxArgs, arbitraryFrom, NormalTransaction)

assert.Error(err, "missing mandatory recipients data. if no mandatory recipients required consider using PrivacyFlag=1(PartyProtection)")

}

func TestHandlePrivateTransaction_whenNoMandatoryRecipientsData(t *testing.T) {
assert := assert.New(t)

privateTxArgs.PrivacyFlag = engine.PrivacyFlagPartyProtection
defer func() {
privateTxArgs.MandatoryRecipients = nil
}()
privateTxArgs.MandatoryRecipients = arbitraryMandatoryFor

_, _, _, err := checkAndHandlePrivateTransaction(arbitraryCtx, &StubBackend{}, simpleStorageContractCreationTx, privateTxArgs, arbitraryFrom, NormalTransaction)

assert.Error(err, "privacy metadata invalid. mandatory recipients are only applicable for PrivacyFlag=2(MandatoryRecipients)")

}

func TestSubmitPrivateTransaction(t *testing.T) {
assert := assert.New(t)

Expand Down
15 changes: 10 additions & 5 deletions private/engine/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ var (
ErrPrivateTxManagerNotReady = errors.New("private transaction manager is not ready")
ErrPrivateTxManagerNotSupported = errors.New("private transaction manager does not support this operation")
ErrPrivateTxManagerDoesNotSupportPrivacyEnhancements = errors.New("private transaction manager does not support privacy enhancements")
ErrPrivateTxManagerDoesNotSupportMandatoryRecipients = errors.New("private transaction manager does not support mandatory recipients")
)

type PrivacyGroup struct {
Expand All @@ -41,8 +42,10 @@ type ExtraMetadata struct {
// Contract participants that are managed by the corresponding Tessera.
// Being used in Multi Tenancy
ManagedParties []string
// the sender of the transaction
// The sender of the transaction
Sender string
// Recipients that are mandated to be included
MandatoryRecipients []string
}

type Client struct {
Expand All @@ -65,9 +68,10 @@ func (c *Client) Get(path string) (*http.Response, error) {
type PrivacyFlagType uint64

const (
PrivacyFlagStandardPrivate PrivacyFlagType = iota // 0
PrivacyFlagPartyProtection PrivacyFlagType = 1 << PrivacyFlagType(iota-1) // 1
PrivacyFlagStateValidation = iota | PrivacyFlagPartyProtection // 3 which includes PrivacyFlagPartyProtection
PrivacyFlagStandardPrivate PrivacyFlagType = iota
PrivacyFlagPartyProtection
PrivacyFlagMandatoryRecipients
PrivacyFlagStateValidation
)

func (f PrivacyFlagType) IsNotStandardPrivate() bool {
Expand All @@ -91,7 +95,7 @@ func (f PrivacyFlagType) HasAll(others ...PrivacyFlagType) bool {
}

func (f PrivacyFlagType) Validate() error {
if f == PrivacyFlagStandardPrivate || f == PrivacyFlagPartyProtection || f == PrivacyFlagStateValidation {
if f == PrivacyFlagStandardPrivate || f == PrivacyFlagPartyProtection || f == PrivacyFlagMandatoryRecipients || f == PrivacyFlagStateValidation {
return nil
}
return fmt.Errorf("invalid privacy flag")
Expand All @@ -104,6 +108,7 @@ const (
PrivacyEnhancements PrivateTransactionManagerFeature = 1 << PrivateTransactionManagerFeature(iota-1) // 1
MultiTenancy PrivateTransactionManagerFeature = 1 << PrivateTransactionManagerFeature(iota-1) // 2
MultiplePrivateStates PrivateTransactionManagerFeature = 1 << PrivateTransactionManagerFeature(iota-1) // 4
MandatoryRecipients PrivateTransactionManagerFeature = 1 << PrivateTransactionManagerFeature(iota-1) // 8
)

type FeatureSet struct {
Expand Down
19 changes: 19 additions & 0 deletions private/engine/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,17 @@ func TestPrivacyFlag_whenPrivateStateValidation(t *testing.T) {
assert.True(PrivacyFlagStateValidation.Has(PrivacyFlagPartyProtection), "State Validation must have party protection by default")
}

func TestPrivacyFlag_whenMandatoryRecipients(t *testing.T) {
assert := assert.New(t)

flag := PrivacyFlagMandatoryRecipients

assert.NoError(flag.Validate())
assert.True(flag.Has(PrivacyFlagMandatoryRecipients))
assert.True(PrivacyFlagStateValidation.Has(flag))

}

func TestPrivacyFlagType_Validate_whenSuccess(t *testing.T) {
assert := assert.New(t)

Expand All @@ -69,3 +80,11 @@ func TestPrivacyFlagType_Validate_whenFailure(t *testing.T) {

assert.Error(flag.Validate())
}

func TestFeatureSet_HasFeature(t *testing.T) {
assert := assert.New(t)

featureSet := NewFeatureSet(PrivacyEnhancements, MultiTenancy, MultiplePrivateStates, MandatoryRecipients)
assert.True(featureSet.HasFeature(MandatoryRecipients))
assert.True(featureSet.HasFeature(MultiplePrivateStates))
}
4 changes: 4 additions & 0 deletions private/engine/tessera/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ type sendRequest struct {
ExecHash string `json:"execHash,omitempty"`

PrivacyFlag engine.PrivacyFlagType `json:"privacyFlag"`

MandatoryRecipients []string `json:"mandatoryRecipients"`
}

// request object for /send API
Expand Down Expand Up @@ -64,6 +66,8 @@ type sendSignedTxRequest struct {
ExecHash string `json:"execHash,omitempty"`

PrivacyFlag engine.PrivacyFlagType `json:"privacyFlag"`

MandatoryRecipients []string `json:"mandatoryRecipients"`
}

type sendSignedTxResponse struct {
Expand Down
11 changes: 11 additions & 0 deletions private/engine/tessera/tessera.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ func (t *tesseraPrivateTxManager) submitJSON(method, path string, request interf
if t.features.HasFeature(engine.MultiTenancy) {
apiVersion = "vnd.tessera-2.1+"
}
if t.features.HasFeature(engine.MandatoryRecipients) && (path == "/send" || path == "/sendsignedtx") {
apiVersion = "vnd.tessera-4.0+"
}
if t.features.HasFeature(engine.MultiplePrivateStates) && path == "/groups/resident" {
// for the groups API the Content-type/Accept is application/json
apiVersion = ""
Expand Down Expand Up @@ -95,6 +98,9 @@ func (t *tesseraPrivateTxManager) Send(data []byte, from string, to []string, ex
if extra.PrivacyFlag.IsNotStandardPrivate() && !t.features.HasFeature(engine.PrivacyEnhancements) {
return "", nil, common.EncryptedPayloadHash{}, engine.ErrPrivateTxManagerDoesNotSupportPrivacyEnhancements
}
if extra.PrivacyFlag == engine.PrivacyFlagMandatoryRecipients && !t.features.HasFeature(engine.MandatoryRecipients) {
return "", nil, common.EncryptedPayloadHash{}, engine.ErrPrivateTxManagerDoesNotSupportMandatoryRecipients
}
response := new(sendResponse)
acMerkleRoot := ""
if !common.EmptyHash(extra.ACMerkleRoot) {
Expand All @@ -107,6 +113,7 @@ func (t *tesseraPrivateTxManager) Send(data []byte, from string, to []string, ex
AffectedContractTransactions: extra.ACHashes.ToBase64s(),
ExecHash: acMerkleRoot,
PrivacyFlag: extra.PrivacyFlag,
MandatoryRecipients: extra.MandatoryRecipients,
}, response); err != nil {
return "", nil, common.EncryptedPayloadHash{}, err
}
Expand Down Expand Up @@ -218,6 +225,9 @@ func (t *tesseraPrivateTxManager) SendSignedTx(data common.EncryptedPayloadHash,
if extra.PrivacyFlag.IsNotStandardPrivate() && !t.features.HasFeature(engine.PrivacyEnhancements) {
return "", nil, nil, engine.ErrPrivateTxManagerDoesNotSupportPrivacyEnhancements
}
if extra.PrivacyFlag == engine.PrivacyFlagMandatoryRecipients && !t.features.HasFeature(engine.MandatoryRecipients) {
return "", nil, nil, engine.ErrPrivateTxManagerDoesNotSupportMandatoryRecipients
}
response := new(sendSignedTxResponse)
acMerkleRoot := ""
if !common.EmptyHash(extra.ACMerkleRoot) {
Expand All @@ -232,6 +242,7 @@ func (t *tesseraPrivateTxManager) SendSignedTx(data common.EncryptedPayloadHash,
AffectedContractTransactions: extra.ACHashes.ToBase64s(),
ExecHash: acMerkleRoot,
PrivacyFlag: extra.PrivacyFlag,
MandatoryRecipients: extra.MandatoryRecipients,
}, response); err != nil {
return "", nil, nil, err
}
Expand Down
Loading

0 comments on commit 38999c7

Please sign in to comment.