Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Serve multiple tenants from a single node #1105

Merged
merged 66 commits into from
Jan 26, 2021
Merged

Conversation

trung
Copy link
Contributor

@trung trung commented Dec 12, 2020

  • Support multiple tenants in a single node while preserving data isolation
    • Introduce --multitenancy flag to enable the feature
  • Use RPC Security Plugin to support tenancy protection
    • Design access token scope to provide granular access controls
  • Introduce AccountExtraData to provide flexibility in managing additional data for account state. AccountExtraData is composed of PrivacyMetadata and managed parties

Also fixes #1115

core/blockchain.go Show resolved Hide resolved
core/state_transition.go Show resolved Hide resolved
core/vm/evm.go Show resolved Hide resolved
extension/api.go Outdated Show resolved Hide resolved
extension/api.go Outdated Show resolved Hide resolved
internal/ethapi/api.go Show resolved Hide resolved
multitenancy/decision_manager.go Outdated Show resolved Hide resolved
multitenancy/types.go Show resolved Hide resolved
rpc/server.go Show resolved Hide resolved
multitenancy/decision_manager.go Outdated Show resolved Hide resolved
fields["logs"] = filteredLogs
receiptClone := &types.Receipt{PostState: receipt.PostState, Status: receipt.Status, Logs: filteredLogs}
fields["logsBloom"] = types.CreateBloom(types.Receipts{receiptClone})
}
Copy link
Contributor

Choose a reason for hiding this comment

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

  1. It looks like "status" field is never overridden in case of multi-tenancy. So if a user accesses a receipt of a tx that failed and that was sent to a contract the user has no access to then the user would still be able to deduce that the transaction failed from the receipt. Is my understanding correct here? If correct, I think the user should instead see a success status in the receipt (same as if the user was accessing receipt through a node that was not a participant of the transaction)

  2. It looks like "contractAddress" is never overridden. So if a user tries to access a receipt of a successful contract deployment and the user has no access to the deployed contract then the user would still be able to deduce that the deployment actually succeeded from the receipt. Is my understanding correct here? If correct, I think the user should instead see an empty "contractAddress" field in this case (same as if the user was accessing receipt through a node that was not a participant of the transaction)

  3. An edge case is for failed deployment tx. I don't see any way to hide that the tx failed to non-authorized users.

Copy link
Contributor Author

@trung trung Jan 14, 2021

Choose a reason for hiding this comment

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

  1. Without multitenancy, contractAddress is always visible even if user was not a participant.
  2. and 1. seem having the same context, do you foresee any further security concern after knowing if a contract is accessible or not? I would argue to keep the original tx status to reflect the correct status of the transaction execution. Without multitenancy, party-protection transactions also produce fail status for transactions sent by non-party.

Copy link
Contributor

Choose a reason for hiding this comment

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

  1. Without multitenancy, a non participant would see an empty contractAddress and not the effective deployment address (since a non-participant has no access to the tx payload for deployment). Or am I missing something here?

    1. In a non-multitenant world, a non-participant always sees a success status for private transactions. Or am I missing something here?

core/vm/evm.go Show resolved Hide resolved
core/vm/evm.go Show resolved Hide resolved
core/vm/evm.go Show resolved Hide resolved
core/vm/evm.go Show resolved Hide resolved
internal/ethapi/api.go Show resolved Hide resolved
internal/ethapi/api.go Show resolved Hide resolved
internal/ethapi/api.go Show resolved Hide resolved
internal/ethapi/api.go Show resolved Hide resolved
internal/ethapi/api.go Show resolved Hide resolved
@trung trung force-pushed the multitenant branch 6 times, most recently from 6eed243 to a6ab773 Compare January 15, 2021 18:46
// Note:
// - update copy() method
// - update DecodeRLP and EncodeRLP when adding new field
type AccountExtraData struct {
Copy link
Contributor

Choose a reason for hiding this comment

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

to improve the structure, I think would be better to move AccountExtraData to a separate file (e.g core/state/account_extra_data.go). moreover, it could be applied the same approach to PrivacyMetadata.

Copy link
Contributor

Choose a reason for hiding this comment

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

+1

@@ -217,7 +217,9 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
if in.evm.quorumReadOnly && operation.writes {
return nil, fmt.Errorf("VM in read-only mode. Mutating opcode prohibited")
}

if err := in.evm.captureOperationMode(operation.writes); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

should we add the Quorum section comments?

// Quorum
//
// Retrieve private payload and construct the original transaction
func buildPrivateTransaction(tx *types.Transaction) (*types.Transaction, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

those functions seem not directly related to the api.go and to avoid more conflicts on geth upgrades, could be moved to a separate file.

@@ -304,6 +314,28 @@ func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bo
}
// End Quorum - Privacy Enhancements

// do the affected contract managed party checks
if msg, ok := msg.(PrivateMessage); ok && isQuorum && st.evm.SupportsMultitenancy && msg.IsPrivate() {
if len(managedPartiesInTx) > 0 {
Copy link
Contributor

Choose a reason for hiding this comment

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

non-blocking: would make sense to move blocks of code like this to be done in other external functions to make less impact on geth code?

core/vm/evm.go Outdated
evm.affectedContracts[address] = newAffectedType(MessageCall, ModeUnknown)
// for multitenancy, we cache the expectation of AffectedMode to be used later
// when an opcode is executed.
if evm.SupportsMultitenancy && evm.AuthorizeMessageCallFunc != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

same question here

@trung trung force-pushed the multitenant branch 2 times, most recently from f18d1c4 to 642ffad Compare January 22, 2021 20:24
// based on what is entitled in the proto.PreAuthenticatedAuthenticationToken
// and what is asked in ContractSecurityAttribute list.
// Note: place holder for future, this is to protect Value Transfer between accounts.
type AccountAccessDecisionManager interface {
Copy link
Contributor

Choose a reason for hiding this comment

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

AccessDecisionManager naming is not very standard also this is not really a manager since it only does checks.

I think AccountAuthorizationChecker would be clearer.

// ContractAccessDecisionManager performs authorization checks for contract
// based on what is entitled in the proto.PreAuthenticatedAuthenticationToken
// and what is asked in ContractSecurityAttribute list.
type ContractAccessDecisionManager interface {
Copy link
Contributor

Choose a reason for hiding this comment

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

Same goes here: renaming ContractAccessDecisionManager to ContractAuthorizationChecker would be clearer.

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually re-reading this what is the upside of splitting into 2 interfaces?

Could we not have a single interface:

type Checker interface {
     IsAccountAuthorized(...)
     IsContractAuthorized(...)
}

?

type AuthorizeCreateFunc func() bool

// AuthorizeMessageCallFunc returns if a contract is authorized to be read / write
type AuthorizeMessageCallFunc func(contractAddress common.Address) (authorizedRead bool, authorizedWrite bool, err error)
Copy link
Contributor

Choose a reason for hiding this comment

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

AuthorizeMessageCallFunc is misleading: it returns authorizations to be checked elsewhere in the codebase which I don't think should happen.

Authorization checks logic should be centralized in the multitenancy package that then exposes IsAuthorized functions to the rest of the codebase.

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually something quite misleading overall is also that we not clearly differentiate between multi-tenancy

  • at block processing time
  • at simulation time

I think this should be captured in some interfaces declared under multitenancy package and then imported.


// ContractSecurityAttribute contains security configuration ask
// which are defined for a secure contract account
type ContractSecurityAttribute struct {
Copy link
Contributor

Choose a reason for hiding this comment

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

I am not a fan of SecurityAttribute naming which is not standard for authentication stuff.

What about "Claim"?

multitenancy/types.go Show resolved Hide resolved
// Note:
// - update copy() method
// - update DecodeRLP and EncodeRLP when adding new field
type AccountExtraData struct {
Copy link
Contributor

Choose a reason for hiding this comment

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

+1

core/vm/evm.go Outdated
@@ -258,7 +249,7 @@ func ModeOf(isWrite bool) AffectedMode {
}

func (mode AffectedMode) IsAuthorized(actualMode AffectedMode) bool {
return mode&actualMode != actualMode
return (mode&ModeUpdated == ModeUpdated) && (mode&actualMode != actualMode)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we need to do also check for ModeUpdated?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That is to indicate that the expectation has been set. We need this new mode as we set default to ModeUnknown when capturing affected contracts.

@@ -705,6 +696,7 @@ func getDualState(evm *EVM, addr common.Address) StateDB {

if evm.PrivateState().Exist(addr) {
state = evm.PrivateState()
evm.captureAffectedContract(addr, ModeUnknown)
Copy link
Contributor

Choose a reason for hiding this comment

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

Good! 👍

core/vm/evm.go Outdated
//
// updateAffectedMode is usied in simulation/eth_call for multitenancy, it sets the expectation of AffectedMode
// to be verified later when an opcode is executed.
func (evm *EVM) updateAffectedMode(address common.Address) error {
Copy link
Contributor

Choose a reason for hiding this comment

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

updateAffectedMode looks to do a lot could we instead do:

  1. (mode AffectedMode) Update(authorizedRead, authorizedWrite bool) AffectedMode that updates the mode
  2. Update (evm *EVM) captureAffectedContract(...) *AffectedType (returning the Affected type but not doing updateAffectedMode so not returning any error)
  3. In getDualState do
affectedType, err := evm.captureAffectedMode(...)
...
if evm.AuthorizeMessageCallFunc != nil {
    authorizedRead, authorizedWrite, err := evm.AuthorizeMessageCallFunc(address)
	...
	affectedType.mode.Update(authorizedRead, authorizedWrite)
}

@trung trung force-pushed the multitenant branch 2 times, most recently from 3020699 to 7a9f183 Compare January 25, 2021 15:17
if !evm.SupportsMultitenancy || evm.AuthorizeMessageCallFunc == nil {
return nil
}
affectedType, found := evm.affectedContracts[address]
Copy link
Contributor

Choose a reason for hiding this comment

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

On line 860 you are already looking for evm.affectedContracts[address] we probably do not need to do it a second time here.

core/vm/evm.go Outdated
log.Debug("AffectedMode changed", "address", address.Hex(), "old", affectedType.mode, "new", authorizedMode, "read", authorizedRead, "write", authorizedWrite)
// if we don't authorize either read/write, it's unauthorized access
// and we need to inform EVM
if !authorizedRead && !authorizedWrite {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not checking this right after authorizedRead, authorizedWrite, err := evm.AuthorizeMessageCallFunc(address) on line 871?

Actually would it even make sense to return an error from evm.AuthorizeMessageCallFunc() in case both authorizedRead and authorizedWrite are false ?

So then we can directly do
affectedType.mode = affectedType.mode.Update(authorizedRead, authorizedWrite)

trung and others added 5 commits January 26, 2021 09:41
PrivacyMetadata was added in Privacy Enhancements. It is linked to each
private contract account and stored in a trie to allow rollback,
especially for contract extension.

This change is to generalize the extra data that supplements the account
data by introducing AccountExtraData. The first extra data is
PrivacyMetadata.

An additional improvement is to make sure AccountExtraData is also
linked to public accounts for future needs.
trung and others added 26 commits January 26, 2021 10:36
add privateFrom to contract extension automated transactions
Co-authored-by: Nicolae Leonte <nicolae.leonte.go@gmail.com>
…ing simulation/eth_call and block processing
Copy link
Contributor

@nmvalera nmvalera left a comment

Choose a reason for hiding this comment

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

LGTM

@nmvalera nmvalera merged commit 8bd0643 into Consensys:master Jan 26, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

use dual state in ExtCodeHash opcode
6 participants