-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
Conversation
fields["logs"] = filteredLogs | ||
receiptClone := &types.Receipt{PostState: receipt.PostState, Status: receipt.Status, Logs: filteredLogs} | ||
fields["logsBloom"] = types.CreateBloom(types.Receipts{receiptClone}) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-
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)
-
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)
-
An edge case is for failed deployment tx. I don't see any way to hide that the tx failed to non-authorized users.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Without multitenancy,
contractAddress
is always visible even if user was not a participant. - 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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-
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? -
- In a non-multitenant world, a non-participant always sees a success status for private transactions. Or am I missing something here?
6eed243
to
a6ab773
Compare
core/state/state_object.go
Outdated
// Note: | ||
// - update copy() method | ||
// - update DecodeRLP and EncodeRLP when adding new field | ||
type AccountExtraData struct { |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same question here
f18d1c4
to
642ffad
Compare
multitenancy/decision_manager.go
Outdated
// 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 { |
There was a problem hiding this comment.
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.
multitenancy/decision_manager.go
Outdated
// 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 { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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(...)
}
?
multitenancy/decision_manager.go
Outdated
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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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"?
core/state/state_object.go
Outdated
// Note: | ||
// - update copy() method | ||
// - update DecodeRLP and EncodeRLP when adding new field | ||
type AccountExtraData struct { |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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:
(mode AffectedMode) Update(authorizedRead, authorizedWrite bool) AffectedMode
that updates the mode- Update
(evm *EVM) captureAffectedContract(...) *AffectedType
(returning the Affected type but not doingupdateAffectedMode
so not returning any error) - In
getDualState
do
affectedType, err := evm.captureAffectedMode(...)
...
if evm.AuthorizeMessageCallFunc != nil {
authorizedRead, authorizedWrite, err := evm.AuthorizeMessageCallFunc(address)
...
affectedType.mode.Update(authorizedRead, authorizedWrite)
}
3020699
to
7a9f183
Compare
if !evm.SupportsMultitenancy || evm.AuthorizeMessageCallFunc == nil { | ||
return nil | ||
} | ||
affectedType, found := evm.affectedContracts[address] |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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)
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.
add privateFrom to contract extension automated transactions
…e privacy metadata
Co-authored-by: Nicolae Leonte <nicolae.leonte.go@gmail.com>
… seperating the loop
…gs/events to be retrieved)
…same change in tessera)
…ing simulation/eth_call and block processing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
--multitenancy
flag to enable the featureAccountExtraData
to provide flexibility in managing additional data for account state.AccountExtraData
is composed ofPrivacyMetadata
and managed partiesAlso fixes #1115