Skip to content

Commit 8bd0643

Browse files
authored
Merge pull request #1105 from trung/multitenant
Serve multiple tenants from a single node
2 parents 382b02d + f165b8f commit 8bd0643

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+4501
-577
lines changed

accounts/abi/bind/backends/simulated.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,10 @@ import (
3939
"github.com/ethereum/go-ethereum/eth/filters"
4040
"github.com/ethereum/go-ethereum/ethdb"
4141
"github.com/ethereum/go-ethereum/event"
42+
"github.com/ethereum/go-ethereum/multitenancy"
4243
"github.com/ethereum/go-ethereum/params"
4344
"github.com/ethereum/go-ethereum/rpc"
45+
"github.com/jpmorganchase/quorum-security-plugin-sdk-go/proto"
4446
)
4547

4648
// This nil assignment ensures compile time that SimulatedBackend implements bind.ContractBackend.
@@ -541,3 +543,15 @@ func (fb *filterBackend) BloomStatus() (uint64, uint64) { return 4096, 0 }
541543
func (fb *filterBackend) ServiceFilter(ctx context.Context, ms *bloombits.MatcherSession) {
542544
panic("not supported")
543545
}
546+
547+
func (fb *filterBackend) AccountExtraDataStateGetterByNumber(context.Context, rpc.BlockNumber) (vm.AccountExtraDataStateGetter, error) {
548+
panic("not supported")
549+
}
550+
551+
func (fb *filterBackend) IsAuthorized(ctx context.Context, authToken *proto.PreAuthenticatedAuthenticationToken, attributes ...*multitenancy.ContractSecurityAttribute) (bool, error) {
552+
panic("not supported")
553+
}
554+
555+
func (fb *filterBackend) SupportsMultitenancy(context.Context) (*proto.PreAuthenticatedAuthenticationToken, bool) {
556+
panic("not supported")
557+
}

cmd/geth/main.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import (
4242
"github.com/ethereum/go-ethereum/les"
4343
"github.com/ethereum/go-ethereum/log"
4444
"github.com/ethereum/go-ethereum/metrics"
45+
"github.com/ethereum/go-ethereum/multitenancy"
4546
"github.com/ethereum/go-ethereum/node"
4647
"github.com/ethereum/go-ethereum/permission"
4748
"github.com/ethereum/go-ethereum/plugin"
@@ -169,6 +170,7 @@ var (
169170
utils.PluginPublicKeyFlag,
170171
utils.AllowedFutureBlockTimeFlag,
171172
utils.EVMCallTimeOutFlag,
173+
utils.MultitenancyFlag,
172174
// End-Quorum
173175
}
174176

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

390+
var ethService *eth.Ethereum
391+
if err := stack.Service(&ethService); err != nil {
392+
utils.Fatalf("Failed to retrieve ethereum service: %v", err)
393+
}
394+
setContractAuthzProviderFunc := ethService.SetContractAuthorizationProvider
386395
// Set contract backend for ethereum service if local node
387396
// is serving LES requests.
388397
if ctx.GlobalInt(utils.LightLegacyServFlag.Name) > 0 || ctx.GlobalInt(utils.LightServeFlag.Name) > 0 {
@@ -400,6 +409,17 @@ func startNode(ctx *cli.Context, stack *node.Node) {
400409
utils.Fatalf("Failed to retrieve light ethereum service: %v", err)
401410
}
402411
lesService.SetContractBackend(ethClient)
412+
setContractAuthzProviderFunc = lesService.SetContractAuthorizationManager
413+
}
414+
415+
// Set ContractAuthorizationProvider if multitenancy flag is on AND plugin security is configured
416+
if ctx.GlobalBool(utils.MultitenancyFlag.Name) {
417+
if stack.PluginManager().IsEnabled(plugin.SecurityPluginInterfaceName) {
418+
log.Info("Node supports multitenancy")
419+
setContractAuthzProviderFunc(&multitenancy.DefaultContractAuthorizationProvider{})
420+
} else {
421+
utils.Fatalf("multitenancy requires RPC Security Plugin to be configured")
422+
}
403423
}
404424

405425
go func() {

cmd/geth/usage.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,7 @@ var AppHelpFlagGroups = []flagGroup{
281281
utils.PluginLocalVerifyFlag,
282282
utils.PluginPublicKeyFlag,
283283
utils.AllowedFutureBlockTimeFlag,
284+
utils.MultitenancyFlag,
284285
},
285286
},
286287
{

cmd/utils/flags.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -875,6 +875,11 @@ var (
875875
Usage: "Default minimum difference between two consecutive block's timestamps in seconds",
876876
Value: eth.DefaultConfig.Istanbul.BlockPeriod,
877877
}
878+
// Multitenancy setting
879+
MultitenancyFlag = cli.BoolFlag{
880+
Name: "multitenancy",
881+
Usage: "Enable multitenancy support for this node. This requires RPC Security Plugin to also be configured.",
882+
}
878883
)
879884

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

15541559
func setQuorumConfig(ctx *cli.Context, cfg *eth.Config) {
15551560
cfg.EVMCallTimeOut = time.Duration(ctx.GlobalInt(EVMCallTimeOutFlag.Name)) * time.Second
1556-
1561+
cfg.EnableMultitenancy = ctx.GlobalBool(MultitenancyFlag.Name)
15571562
setIstanbul(ctx, cfg)
15581563
setRaft(ctx, cfg)
15591564
}

common/slice.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright 2014 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package common
18+
19+
// ContainsAll returns true if all elements in the target are in the source,
20+
// false otherwise.
21+
func ContainsAll(source, target []string) bool {
22+
mark := make(map[string]bool, len(source))
23+
for _, str := range source {
24+
mark[str] = true
25+
}
26+
for _, str := range target {
27+
if _, found := mark[str]; !found {
28+
return false
29+
}
30+
}
31+
return true
32+
}
33+
34+
// ContainsAll returns true if all elements in the target are NOT in the source,
35+
// false otherwise.
36+
func NotContainsAll(source, target []string) bool {
37+
return !ContainsAll(source, target)
38+
}
39+
40+
// AppendSkipDuplicates appends source with elements with a condition
41+
// that those elemments must NOT already exist in the source
42+
func AppendSkipDuplicates(slice []string, elems ...string) (result []string) {
43+
mark := make(map[string]bool, len(slice))
44+
for _, val := range slice {
45+
mark[val] = true
46+
}
47+
result = slice
48+
for _, val := range elems {
49+
if _, ok := mark[val]; !ok {
50+
result = append(result, val)
51+
}
52+
}
53+
return result
54+
}

common/slice_test.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Copyright 2014 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package common
18+
19+
import (
20+
"testing"
21+
22+
"github.com/stretchr/testify/assert"
23+
)
24+
25+
func TestContainsAll_whenTypical(t *testing.T) {
26+
source := []string{"1", "2"}
27+
target := []string{"1", "2"}
28+
29+
assert.True(t, ContainsAll(source, target))
30+
}
31+
32+
func TestContainsAll_whenNot(t *testing.T) {
33+
source := []string{"1", "2"}
34+
target := []string{"3", "4"}
35+
36+
assert.False(t, ContainsAll(source, target))
37+
}
38+
39+
func TestContainsAll_whenTargetIsSubset(t *testing.T) {
40+
source := []string{"1", "2"}
41+
target := []string{"1"}
42+
43+
assert.True(t, ContainsAll(source, target))
44+
}
45+
46+
func TestContainsAll_whenTargetIsSuperSet(t *testing.T) {
47+
source := []string{"2"}
48+
target := []string{"1", "2"}
49+
50+
assert.False(t, ContainsAll(source, target))
51+
}
52+
53+
func TestContainsAll_whenSourceIsEmpty(t *testing.T) {
54+
var source []string
55+
target := []string{"1", "2"}
56+
57+
assert.False(t, ContainsAll(source, target))
58+
}
59+
60+
func TestContainsAll_whenSourceIsNil(t *testing.T) {
61+
target := []string{"1", "2"}
62+
63+
assert.False(t, ContainsAll(nil, target))
64+
}
65+
66+
func TestContainsAll_whenTargetIsEmpty(t *testing.T) {
67+
source := []string{"1", "2"}
68+
69+
assert.True(t, ContainsAll(source, []string{}))
70+
}
71+
72+
func TestContainsAll_whenTargetIsNil(t *testing.T) {
73+
source := []string{"1", "2"}
74+
75+
assert.True(t, ContainsAll(source, nil))
76+
}
77+
78+
func TestAppendSkipDuplicates_whenTypical(t *testing.T) {
79+
source := []string{"1", "2"}
80+
additional := []string{"1", "3"}
81+
82+
assert.Equal(t, []string{"1", "2", "3"}, AppendSkipDuplicates(source, additional...))
83+
}
84+
85+
func TestAppendSkipDuplicates_whenSourceIsNil(t *testing.T) {
86+
additional := []string{"1", "3"}
87+
88+
assert.Equal(t, []string{"1", "3"}, AppendSkipDuplicates(nil, additional...))
89+
}
90+
91+
func TestAppendSkipDuplicates_whenElementIsNil(t *testing.T) {
92+
assert.Equal(t, []string{"1", "3"}, AppendSkipDuplicates([]string{"1", "3"}, nil...))
93+
}

common/types.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"encoding/base64"
2222
"encoding/hex"
2323
"encoding/json"
24+
"errors"
2425
"fmt"
2526
"math/big"
2627
"math/rand"
@@ -42,6 +43,9 @@ const (
4243
)
4344

4445
var (
46+
ErrNotPrivateContract = errors.New("the provided address is not a private contract")
47+
ErrNoAccountExtraData = errors.New("no account extra data found")
48+
4549
hashT = reflect.TypeOf(Hash{})
4650
addressT = reflect.TypeOf(Address{})
4751
)

core/blockchain.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package core
1919

2020
import (
21+
"context"
2122
"errors"
2223
"fmt"
2324
"io"
@@ -27,6 +28,8 @@ import (
2728
"sync/atomic"
2829
"time"
2930

31+
"github.com/jpmorganchase/quorum-security-plugin-sdk-go/proto"
32+
3033
"github.com/ethereum/go-ethereum/common"
3134
"github.com/ethereum/go-ethereum/common/math"
3235
"github.com/ethereum/go-ethereum/common/mclock"
@@ -180,6 +183,7 @@ type BlockChain struct {
180183
setPrivateState func([]*types.Log, *state.StateDB) // Function to check extension and set private state
181184

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

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

321+
func NewMultitenantBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *params.ChainConfig, engine consensus.Engine, vmConfig vm.Config, shouldPreserve func(block *types.Block) bool) (*BlockChain, error) {
322+
bc, err := NewBlockChain(db, cacheConfig, chainConfig, engine, vmConfig, shouldPreserve)
323+
if err != nil {
324+
return nil, err
325+
}
326+
bc.isMultitenant = true
327+
return bc, err
328+
}
329+
317330
func (bc *BlockChain) getProcInterrupt() bool {
318331
return atomic.LoadInt32(&bc.procInterrupt) == 1
319332
}
@@ -2366,3 +2379,7 @@ func (bc *BlockChain) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscript
23662379
func (bc *BlockChain) SubscribeBlockProcessingEvent(ch chan<- bool) event.Subscription {
23672380
return bc.scope.Track(bc.blockProcFeed.Subscribe(ch))
23682381
}
2382+
2383+
func (bc *BlockChain) SupportsMultitenancy(context.Context) (*proto.PreAuthenticatedAuthenticationToken, bool) {
2384+
return nil, bc.isMultitenant
2385+
}

core/evm.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,21 @@
1717
package core
1818

1919
import (
20+
"context"
2021
"math/big"
22+
"reflect"
2123

2224
"github.com/ethereum/go-ethereum/common"
2325
"github.com/ethereum/go-ethereum/consensus"
2426
"github.com/ethereum/go-ethereum/core/types"
2527
"github.com/ethereum/go-ethereum/core/vm"
28+
"github.com/ethereum/go-ethereum/multitenancy"
2629
)
2730

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

@@ -44,6 +48,12 @@ func NewEVMContext(msg Message, header *types.Header, chain ChainContext, author
4448
} else {
4549
beneficiary = *author
4650
}
51+
supportsMultitenancy := false
52+
// mainly to overcome lost of test cases which pass ChainContext as nil value
53+
// nil interface requires this check to make sure we don't get nil pointer reference error
54+
if chain != nil && !reflect.ValueOf(chain).IsNil() {
55+
_, supportsMultitenancy = chain.SupportsMultitenancy(nil)
56+
}
4757
return vm.Context{
4858
CanTransfer: CanTransfer,
4959
Transfer: Transfer,
@@ -55,7 +65,23 @@ func NewEVMContext(msg Message, header *types.Header, chain ChainContext, author
5565
Difficulty: new(big.Int).Set(header.Difficulty),
5666
GasLimit: header.GasLimit,
5767
GasPrice: new(big.Int).Set(msg.GasPrice()),
68+
69+
SupportsMultitenancy: supportsMultitenancy,
70+
}
71+
}
72+
73+
// Quorum
74+
//
75+
// This EVM context is meant for simulation when doing multitenancy check.
76+
// It enriches the given EVM context with multitenancy-specific references
77+
func NewMultitenancyAwareEVMContext(ctx context.Context, evmCtx vm.Context) vm.Context {
78+
if f, ok := ctx.Value(multitenancy.CtxKeyAuthorizeCreateFunc).(multitenancy.AuthorizeCreateFunc); ok {
79+
evmCtx.AuthorizeCreateFunc = f
80+
}
81+
if f, ok := ctx.Value(multitenancy.CtxKeyAuthorizeMessageCallFunc).(multitenancy.AuthorizeMessageCallFunc); ok {
82+
evmCtx.AuthorizeMessageCallFunc = f
5883
}
84+
return evmCtx
5985
}
6086

6187
// GetHashFn returns a GetHashFunc which retrieves header hashes by number

0 commit comments

Comments
 (0)