Skip to content

Commit

Permalink
Merge pull request #1105 from trung/multitenant
Browse files Browse the repository at this point in the history
Serve multiple tenants from a single node
  • Loading branch information
nmvalera authored Jan 26, 2021
2 parents 382b02d + f165b8f commit 8bd0643
Show file tree
Hide file tree
Showing 70 changed files with 4,501 additions and 577 deletions.
14 changes: 14 additions & 0 deletions accounts/abi/bind/backends/simulated.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,10 @@ import (
"github.com/ethereum/go-ethereum/eth/filters"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/multitenancy"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
"github.com/jpmorganchase/quorum-security-plugin-sdk-go/proto"
)

// This nil assignment ensures compile time that SimulatedBackend implements bind.ContractBackend.
Expand Down Expand Up @@ -541,3 +543,15 @@ func (fb *filterBackend) BloomStatus() (uint64, uint64) { return 4096, 0 }
func (fb *filterBackend) ServiceFilter(ctx context.Context, ms *bloombits.MatcherSession) {
panic("not supported")
}

func (fb *filterBackend) AccountExtraDataStateGetterByNumber(context.Context, rpc.BlockNumber) (vm.AccountExtraDataStateGetter, error) {
panic("not supported")
}

func (fb *filterBackend) IsAuthorized(ctx context.Context, authToken *proto.PreAuthenticatedAuthenticationToken, attributes ...*multitenancy.ContractSecurityAttribute) (bool, error) {
panic("not supported")
}

func (fb *filterBackend) SupportsMultitenancy(context.Context) (*proto.PreAuthenticatedAuthenticationToken, bool) {
panic("not supported")
}
20 changes: 20 additions & 0 deletions cmd/geth/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import (
"github.com/ethereum/go-ethereum/les"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/multitenancy"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/permission"
"github.com/ethereum/go-ethereum/plugin"
Expand Down Expand Up @@ -169,6 +170,7 @@ var (
utils.PluginPublicKeyFlag,
utils.AllowedFutureBlockTimeFlag,
utils.EVMCallTimeOutFlag,
utils.MultitenancyFlag,
// End-Quorum
}

Expand Down Expand Up @@ -345,6 +347,8 @@ func geth(ctx *cli.Context) error {
// startNode boots up the system node and all registered protocols, after which
// it unlocks any requested accounts, and starts the RPC/IPC interfaces and the
// miner.
// Quorum
// - Enrich eth/les service with ContractAuthorizationProvider for multitenancy support if prequisites are met
func startNode(ctx *cli.Context, stack *node.Node) {
log.DoEmitCheckpoints = ctx.GlobalBool(utils.EmitCheckpointsFlag.Name)
debug.Memsize.Add("node", stack)
Expand Down Expand Up @@ -383,6 +387,11 @@ func startNode(ctx *cli.Context, stack *node.Node) {
}
ethClient := ethclient.NewClient(rpcClient)

var ethService *eth.Ethereum
if err := stack.Service(&ethService); err != nil {
utils.Fatalf("Failed to retrieve ethereum service: %v", err)
}
setContractAuthzProviderFunc := ethService.SetContractAuthorizationProvider
// Set contract backend for ethereum service if local node
// is serving LES requests.
if ctx.GlobalInt(utils.LightLegacyServFlag.Name) > 0 || ctx.GlobalInt(utils.LightServeFlag.Name) > 0 {
Expand All @@ -400,6 +409,17 @@ func startNode(ctx *cli.Context, stack *node.Node) {
utils.Fatalf("Failed to retrieve light ethereum service: %v", err)
}
lesService.SetContractBackend(ethClient)
setContractAuthzProviderFunc = lesService.SetContractAuthorizationManager
}

// Set ContractAuthorizationProvider if multitenancy flag is on AND plugin security is configured
if ctx.GlobalBool(utils.MultitenancyFlag.Name) {
if stack.PluginManager().IsEnabled(plugin.SecurityPluginInterfaceName) {
log.Info("Node supports multitenancy")
setContractAuthzProviderFunc(&multitenancy.DefaultContractAuthorizationProvider{})
} else {
utils.Fatalf("multitenancy requires RPC Security Plugin to be configured")
}
}

go func() {
Expand Down
1 change: 1 addition & 0 deletions cmd/geth/usage.go
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ var AppHelpFlagGroups = []flagGroup{
utils.PluginLocalVerifyFlag,
utils.PluginPublicKeyFlag,
utils.AllowedFutureBlockTimeFlag,
utils.MultitenancyFlag,
},
},
{
Expand Down
7 changes: 6 additions & 1 deletion cmd/utils/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -875,6 +875,11 @@ var (
Usage: "Default minimum difference between two consecutive block's timestamps in seconds",
Value: eth.DefaultConfig.Istanbul.BlockPeriod,
}
// Multitenancy setting
MultitenancyFlag = cli.BoolFlag{
Name: "multitenancy",
Usage: "Enable multitenancy support for this node. This requires RPC Security Plugin to also be configured.",
}
)

// MakeDataDir retrieves the currently requested data directory, terminating
Expand Down Expand Up @@ -1553,7 +1558,7 @@ func setRaft(ctx *cli.Context, cfg *eth.Config) {

func setQuorumConfig(ctx *cli.Context, cfg *eth.Config) {
cfg.EVMCallTimeOut = time.Duration(ctx.GlobalInt(EVMCallTimeOutFlag.Name)) * time.Second

cfg.EnableMultitenancy = ctx.GlobalBool(MultitenancyFlag.Name)
setIstanbul(ctx, cfg)
setRaft(ctx, cfg)
}
Expand Down
54 changes: 54 additions & 0 deletions common/slice.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright 2014 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package common

// ContainsAll returns true if all elements in the target are in the source,
// false otherwise.
func ContainsAll(source, target []string) bool {
mark := make(map[string]bool, len(source))
for _, str := range source {
mark[str] = true
}
for _, str := range target {
if _, found := mark[str]; !found {
return false
}
}
return true
}

// ContainsAll returns true if all elements in the target are NOT in the source,
// false otherwise.
func NotContainsAll(source, target []string) bool {
return !ContainsAll(source, target)
}

// AppendSkipDuplicates appends source with elements with a condition
// that those elemments must NOT already exist in the source
func AppendSkipDuplicates(slice []string, elems ...string) (result []string) {
mark := make(map[string]bool, len(slice))
for _, val := range slice {
mark[val] = true
}
result = slice
for _, val := range elems {
if _, ok := mark[val]; !ok {
result = append(result, val)
}
}
return result
}
93 changes: 93 additions & 0 deletions common/slice_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright 2014 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package common

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestContainsAll_whenTypical(t *testing.T) {
source := []string{"1", "2"}
target := []string{"1", "2"}

assert.True(t, ContainsAll(source, target))
}

func TestContainsAll_whenNot(t *testing.T) {
source := []string{"1", "2"}
target := []string{"3", "4"}

assert.False(t, ContainsAll(source, target))
}

func TestContainsAll_whenTargetIsSubset(t *testing.T) {
source := []string{"1", "2"}
target := []string{"1"}

assert.True(t, ContainsAll(source, target))
}

func TestContainsAll_whenTargetIsSuperSet(t *testing.T) {
source := []string{"2"}
target := []string{"1", "2"}

assert.False(t, ContainsAll(source, target))
}

func TestContainsAll_whenSourceIsEmpty(t *testing.T) {
var source []string
target := []string{"1", "2"}

assert.False(t, ContainsAll(source, target))
}

func TestContainsAll_whenSourceIsNil(t *testing.T) {
target := []string{"1", "2"}

assert.False(t, ContainsAll(nil, target))
}

func TestContainsAll_whenTargetIsEmpty(t *testing.T) {
source := []string{"1", "2"}

assert.True(t, ContainsAll(source, []string{}))
}

func TestContainsAll_whenTargetIsNil(t *testing.T) {
source := []string{"1", "2"}

assert.True(t, ContainsAll(source, nil))
}

func TestAppendSkipDuplicates_whenTypical(t *testing.T) {
source := []string{"1", "2"}
additional := []string{"1", "3"}

assert.Equal(t, []string{"1", "2", "3"}, AppendSkipDuplicates(source, additional...))
}

func TestAppendSkipDuplicates_whenSourceIsNil(t *testing.T) {
additional := []string{"1", "3"}

assert.Equal(t, []string{"1", "3"}, AppendSkipDuplicates(nil, additional...))
}

func TestAppendSkipDuplicates_whenElementIsNil(t *testing.T) {
assert.Equal(t, []string{"1", "3"}, AppendSkipDuplicates([]string{"1", "3"}, nil...))
}
4 changes: 4 additions & 0 deletions common/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"math/big"
"math/rand"
Expand All @@ -42,6 +43,9 @@ const (
)

var (
ErrNotPrivateContract = errors.New("the provided address is not a private contract")
ErrNoAccountExtraData = errors.New("no account extra data found")

hashT = reflect.TypeOf(Hash{})
addressT = reflect.TypeOf(Address{})
)
Expand Down
17 changes: 17 additions & 0 deletions core/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package core

import (
"context"
"errors"
"fmt"
"io"
Expand All @@ -27,6 +28,8 @@ import (
"sync/atomic"
"time"

"github.com/jpmorganchase/quorum-security-plugin-sdk-go/proto"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/common/mclock"
Expand Down Expand Up @@ -180,6 +183,7 @@ type BlockChain struct {
setPrivateState func([]*types.Log, *state.StateDB) // Function to check extension and set private state

privateStateCache state.Database // Private state database to reuse between imports (contains state cache)
isMultitenant bool // if this blockchain supports multitenancy
}

// function pointer for updating private state
Expand Down Expand Up @@ -314,6 +318,15 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
return bc, nil
}

func NewMultitenantBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *params.ChainConfig, engine consensus.Engine, vmConfig vm.Config, shouldPreserve func(block *types.Block) bool) (*BlockChain, error) {
bc, err := NewBlockChain(db, cacheConfig, chainConfig, engine, vmConfig, shouldPreserve)
if err != nil {
return nil, err
}
bc.isMultitenant = true
return bc, err
}

func (bc *BlockChain) getProcInterrupt() bool {
return atomic.LoadInt32(&bc.procInterrupt) == 1
}
Expand Down Expand Up @@ -2366,3 +2379,7 @@ func (bc *BlockChain) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscript
func (bc *BlockChain) SubscribeBlockProcessingEvent(ch chan<- bool) event.Subscription {
return bc.scope.Track(bc.blockProcFeed.Subscribe(ch))
}

func (bc *BlockChain) SupportsMultitenancy(context.Context) (*proto.PreAuthenticatedAuthenticationToken, bool) {
return nil, bc.isMultitenant
}
26 changes: 26 additions & 0 deletions core/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,21 @@
package core

import (
"context"
"math/big"
"reflect"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/multitenancy"
)

// ChainContext supports retrieving headers and consensus parameters from the
// current blockchain to be used during transaction processing.
type ChainContext interface {
multitenancy.ContextAware
// Engine retrieves the chain's consensus engine.
Engine() consensus.Engine

Expand All @@ -44,6 +48,12 @@ func NewEVMContext(msg Message, header *types.Header, chain ChainContext, author
} else {
beneficiary = *author
}
supportsMultitenancy := false
// mainly to overcome lost of test cases which pass ChainContext as nil value
// nil interface requires this check to make sure we don't get nil pointer reference error
if chain != nil && !reflect.ValueOf(chain).IsNil() {
_, supportsMultitenancy = chain.SupportsMultitenancy(nil)
}
return vm.Context{
CanTransfer: CanTransfer,
Transfer: Transfer,
Expand All @@ -55,7 +65,23 @@ func NewEVMContext(msg Message, header *types.Header, chain ChainContext, author
Difficulty: new(big.Int).Set(header.Difficulty),
GasLimit: header.GasLimit,
GasPrice: new(big.Int).Set(msg.GasPrice()),

SupportsMultitenancy: supportsMultitenancy,
}
}

// Quorum
//
// This EVM context is meant for simulation when doing multitenancy check.
// It enriches the given EVM context with multitenancy-specific references
func NewMultitenancyAwareEVMContext(ctx context.Context, evmCtx vm.Context) vm.Context {
if f, ok := ctx.Value(multitenancy.CtxKeyAuthorizeCreateFunc).(multitenancy.AuthorizeCreateFunc); ok {
evmCtx.AuthorizeCreateFunc = f
}
if f, ok := ctx.Value(multitenancy.CtxKeyAuthorizeMessageCallFunc).(multitenancy.AuthorizeMessageCallFunc); ok {
evmCtx.AuthorizeMessageCallFunc = f
}
return evmCtx
}

// GetHashFn returns a GetHashFunc which retrieves header hashes by number
Expand Down
Loading

0 comments on commit 8bd0643

Please sign in to comment.